Skip to content

Commit

Permalink
initial duckduckapi scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
coco98 committed Oct 19, 2024
1 parent 0359df7 commit 01a308b
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 256 deletions.
41 changes: 17 additions & 24 deletions generate-config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { NotSupported, ObjectType } from '@hasura/ndc-sdk-typescript';
import { Configuration } from './src';
import * as duckdb from 'duckdb';
import * as fs from 'fs';
import { promisify } from "util";
import { Configuration } from './src/duckduckapi';
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);
let HASURA_CONFIGURATION_DIRECTORY = process.env["HASURA_CONFIGURATION_DIRECTORY"] as string | undefined;
if (HASURA_CONFIGURATION_DIRECTORY === undefined || HASURA_CONFIGURATION_DIRECTORY.length === 0){
HASURA_CONFIGURATION_DIRECTORY = ".";
}
const DUCKDB_URL = process.env["DUCKDB_URL"] as string;
const db = new duckdb.Database(DUCKDB_URL);
const con = db.connect();

const determineType = (t: string): string => {
switch (t) {
Expand Down Expand Up @@ -78,14 +75,16 @@ async function queryAll(con: any, query: any): Promise<any[]> {
resolve(res);
}
})

})
};

async function main() {

export async function generateConfig(db: duckdb.Database): Promise<Configuration> {
const tableNames: string[] = [];
const tableAliases: {[k: string]: string} = {};
const objectTypes: { [k: string]: ObjectType } = {};
const tables = await queryAll(con, "SHOW ALL TABLES");
const tables = await queryAll(db.connect(), "SHOW ALL TABLES");
for (let table of tables){
const tableName = `${table.database}_${table.schema}_${table.name}`;
const aliasName = `${table.database}.${table.schema}.${table.name}`;
Expand All @@ -110,25 +109,19 @@ async function main() {
collection_names: tableNames,
collection_aliases: tableAliases,
object_types: objectTypes,
functions: [],
functions: [{
name: "Hello",
arguments: {},
result_type: {
type: "named",
name: "String"
},
description: "hello function"
}],
procedures: []
}
};
const jsonString = JSON.stringify(res, null, 4);
let filePath = `${HASURA_CONFIGURATION_DIRECTORY}/config.json`;
try {
const existingData = await readFile(filePath, 'utf8');
if (existingData !== jsonString) {
await writeFile(filePath, jsonString);
console.log('File updated.');
} else {
console.log('No changes detected. File not updated.');
}
} catch (error) {
await writeFile(filePath, jsonString);
console.log('New file written.');
}
};

main();
console.log(res);
return Promise.resolve(res);
}

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"start": "ts-node ./src/index.ts serve --configuration=.",
"generate-config": "ts-node generate-config"
"generate-config": "ts-node generate-config",
"watch": "nodemon --watch \"src/**\" --ext \"ts,json\" --ignore \"src/**/*.spec.ts\" --exec \"ts-node src/index.ts serve --configuration=."
},
"devDependencies": {
"ts-node": "^10.9.1",
Expand Down
223 changes: 223 additions & 0 deletions src/duckduckapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
SchemaResponse,
ObjectType,
FunctionInfo,
ProcedureInfo,
QueryRequest,
QueryResponse,
MutationRequest,
MutationResponse,
Capabilities,
ExplainResponse,
Connector,
Forbidden,
} from "@hasura/ndc-sdk-typescript";

import { CAPABILITIES_RESPONSE, DUCKDB_CONFIG } from "./constants";
import { do_get_schema } from "./handlers/schema";
import { do_explain } from "./handlers/explain";
import { do_query } from "./handlers/query";
import { do_mutation } from "./handlers/mutation";
import { readFileSync } from "fs";
import * as duckdb from "duckdb";
import { generateConfig } from "../generate-config";

const DUCKDB_URL = 'duck.db'; // process.env["DUCKDB_URL"] as string || "duck.db";
export const db = new duckdb.Database(DUCKDB_URL);

type ConfigurationSchema = {
collection_names: string[];
collection_aliases: { [k: string]: string };
object_types: { [k: string]: ObjectType };
functions: FunctionInfo[];
procedures: ProcedureInfo[];
};

type CredentialSchema = {
url: string;
};

export type Configuration = {
config?: ConfigurationSchema;
};

export interface State {
client: duckdb.Database;
}

const connector: Connector<Configuration, State> = {
/**
* Validate the configuration files provided by the user, returning a validated 'Configuration',
* or throwing an 'Error'. Throwing an error prevents Connector startup.
* @param configuration
*/

parseConfiguration(configurationDir: string): Promise<Configuration> {
return Promise.resolve(generateConfig(db));
},

/**
* Initialize the connector's in-memory state.
*
* For example, any connection pools, prepared queries,
* or other managed resources would be allocated here.
*
* In addition, this function should register any
* connector-specific metrics with the metrics registry.
* @param configuration
* @param metrics
*/
tryInitState(_: Configuration, __: unknown): Promise<State> {
const credentials: CredentialSchema = { url: DUCKDB_URL };
const client = new duckdb.Database(credentials.url, DUCKDB_CONFIG);
return Promise.resolve({ client: client });
},

/**
* Get the connector's capabilities.
*
* This function implements the [capabilities endpoint](https://hasura.github.io/ndc-spec/specification/capabilities.html)
* from the NDC specification.
* @param configuration
*/
getCapabilities(_: Configuration): Capabilities {
return CAPABILITIES_RESPONSE;
},

/**
* Get the connector's schema.
*
* This function implements the [schema endpoint](https://hasura.github.io/ndc-spec/specification/schema/index.html)
* from the NDC specification.
* @param configuration
*/
async getSchema(configuration: Configuration): Promise<SchemaResponse> {
return Promise.resolve(do_get_schema(configuration));
},

/**
* Explain a query by creating an execution plan
*
* This function implements the [explain endpoint](https://hasura.github.io/ndc-spec/specification/explain.html)
* from the NDC specification.
* @param configuration
* @param state
* @param request
*/
queryExplain(
configuration: Configuration,
_: State,
request: QueryRequest
): Promise<ExplainResponse> {
return do_explain(configuration, request);
},

/**
* Explain a mutation by creating an execution plan
* @param configuration
* @param state
* @param request
*/
mutationExplain(
configuration: Configuration,
_: State,
request: MutationRequest
): Promise<ExplainResponse> {
throw new Forbidden("Not implemented", {});
},

/**
* Execute a query
*
* This function implements the [query endpoint](https://hasura.github.io/ndc-spec/specification/queries/index.html)
* from the NDC specification.
* @param configuration
* @param state
* @param request
*/
query(
configuration: Configuration,
state: State,
request: QueryRequest
): Promise<QueryResponse> {
return do_query(configuration, state, request);
},

/**
* Execute a mutation
*
* This function implements the [mutation endpoint](https://hasura.github.io/ndc-spec/specification/mutations/index.html)
* from the NDC specification.
* @param configuration
* @param state
* @param request
*/
mutation(
configuration: Configuration,
_: State,
request: MutationRequest
): Promise<MutationResponse> {
return do_mutation(configuration, request);
},

/**
* Check the health of the connector.
*
* For example, this function should check that the connector
* is able to reach its data source over the network.
* @param configuration
* @param state
*/
getHealthReadiness(_: Configuration, __: State): Promise<undefined> {
return Promise.resolve(undefined);
},

/**
*
* Update any metrics from the state
*
* Note: some metrics can be updated directly, and do not
* need to be updated here. This function can be useful to
* query metrics which cannot be updated directly, e.g.
* the number of idle connections in a connection pool
* can be polled but not updated directly.
* @param configuration
* @param state
*/
fetchMetrics(_: Configuration, __: State): Promise<undefined> {
return Promise.resolve(undefined);
},
};

async function createDuckDBFile(schema: string): Promise<void> {
return new Promise((resolve, reject) => {

db.run(schema, (err) => {
if (err) {
console.error('Error creating schema:', err);
reject(err);
} else {
console.log('Schema created successfully');
resolve();
}
});

});
}

export interface duckduckapi {
dbSchema: string
loaderJob(): void;
getFunctions(): void;
}

export async function makeConnector(dda: duckduckapi): Promise<Connector<Configuration, State>> {
/*
TODO: create the db and load the DB path as a global variable
Create the configuration object
*/
await createDuckDBFile(dda.dbSchema);
// spawn loaderjob execution on a cron
dda.loaderJob();
return Promise.resolve(connector);
}
3 changes: 2 additions & 1 deletion src/handlers/explain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExplainResponse, QueryRequest } from "@hasura/ndc-sdk-typescript";
import { Configuration } from "..";
// import { Configuration } from "..";
import { Configuration } from "../duckduckapi";

export async function do_explain(configuration: Configuration, query: QueryRequest): Promise<ExplainResponse>{
return {
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/mutation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MutationOperation, MutationOperationResults, MutationRequest, MutationResponse, Forbidden } from "@hasura/ndc-sdk-typescript";
import { Configuration } from "..";
import { Configuration } from "../duckduckapi";


export async function do_mutation(configuration: Configuration, mutation: MutationRequest): Promise<MutationResponse> {
Expand Down
26 changes: 23 additions & 3 deletions src/handlers/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
Conflict,
Relationship
} from "@hasura/ndc-sdk-typescript";
import { Configuration, State } from "..";
import { Configuration, State } from "../duckduckapi";
const SqlString = require("sqlstring-sqlite");
import { MAX_32_INT } from "../constants";
// import { format } from "sql-formatter";
// import { helloWorld } from "../stub.functions";
import { functions } from "../stub.functions";

const escape_single = (s: any) => SqlString.escape(s);
const escape_double = (s: any) => `"${SqlString.escape(s).slice(1, -1)}"`;
Expand Down Expand Up @@ -465,11 +467,29 @@ async function perform_query(
return response;
}

function is_query_function(query: QueryRequest, configuration: Configuration): boolean {
if (configuration.config) {
return configuration.config.functions.some(func => func.name === query.collection);
};
return false;
}

async function perform_query_function(state: State, query: QueryRequest) {
return functions[query.collection]()
}

export async function do_query(
configuration: Configuration,
state: State,
query: QueryRequest
): Promise<QueryResponse> {
let query_plans = await plan_queries(configuration, query);
return await perform_query(state, query_plans);
console.log(query);

if (is_query_function(query, configuration)) {
return await perform_query_function(state, query);
} else {
let query_plans = await plan_queries(configuration, query);
return await perform_query(state, query_plans);
}
}

9 changes: 8 additions & 1 deletion src/handlers/schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { CollectionInfo, SchemaResponse } from "@hasura/ndc-sdk-typescript";
import { Configuration } from "..";
import { Configuration } from "../duckduckapi";
import { SCALAR_TYPES } from "../constants";

export function do_get_schema(configuration: Configuration): SchemaResponse {
/** TODO
*
- Get the user to write a duckdb schema file
- Get the user to write some functions
*
*
*/
const {config} = configuration;
if (!config){
throw new Error("Configuration is missing");
Expand Down
Loading

0 comments on commit 01a308b

Please sign in to comment.