From 83f69d89ffb6458a10954a1a8af46681042610cf Mon Sep 17 00:00:00 2001 From: Nybkox Date: Tue, 1 Apr 2025 17:32:24 +0200 Subject: [PATCH] WIP --- common/volatilePackages.js | 1 + .../__tests__/alter-database.spec.js | 2 + .../__tests__/alter-table.spec.js | 140 +++---- .../__tests__/object-analyse.spec.js | 6 +- integration-tests/__tests__/query.spec.js | 6 +- integration-tests/engines.js | 21 +- integration-tests/package.json | 2 +- packages/api/testduck.db | Bin 0 -> 2371584 bytes packages/types/engines.d.ts | 1 + packages/types/query.d.ts | 1 + packages/types/test-engines.d.ts | 1 + plugins/dbgate-plugin-duckdb/.gitignore | 25 ++ plugins/dbgate-plugin-duckdb/README.md | 6 + plugins/dbgate-plugin-duckdb/icon.svg | 1 + plugins/dbgate-plugin-duckdb/package.json | 45 +++ .../dbgate-plugin-duckdb/prettier.config.js | 8 + .../src/backend/Analyser.helpers.js | 374 ++++++++++++++++++ .../src/backend/Analyser.js | 85 ++++ .../src/backend/driver.js | 165 ++++++++ .../src/backend/helpers.js | 65 +++ .../dbgate-plugin-duckdb/src/backend/index.js | 6 + .../src/backend/sql/columns.js | 1 + .../src/backend/sql/foreignKeys.js | 1 + .../src/backend/sql/index.js | 17 + .../src/backend/sql/indexes.js | 1 + .../src/backend/sql/primaryKeys.js | 1 + .../src/backend/sql/tables.js | 1 + .../src/backend/sql/uniques.js | 1 + .../src/backend/sql/views.js | 1 + .../src/frontend/Dumper.js | 7 + .../src/frontend/driver.js | 72 ++++ .../src/frontend/index.js | 6 + .../webpack-backend.config.js | 28 ++ .../webpack-frontend.config.js | 30 ++ yarn.lock | 43 ++ 35 files changed, 1096 insertions(+), 75 deletions(-) create mode 100644 packages/api/testduck.db create mode 100644 plugins/dbgate-plugin-duckdb/.gitignore create mode 100644 plugins/dbgate-plugin-duckdb/README.md create mode 100644 plugins/dbgate-plugin-duckdb/icon.svg create mode 100644 plugins/dbgate-plugin-duckdb/package.json create mode 100644 plugins/dbgate-plugin-duckdb/prettier.config.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/Analyser.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/driver.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/helpers.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/index.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/foreignKeys.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/index.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/primaryKeys.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js create mode 100644 plugins/dbgate-plugin-duckdb/src/backend/sql/views.js create mode 100644 plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js create mode 100644 plugins/dbgate-plugin-duckdb/src/frontend/driver.js create mode 100644 plugins/dbgate-plugin-duckdb/src/frontend/index.js create mode 100644 plugins/dbgate-plugin-duckdb/webpack-backend.config.js create mode 100644 plugins/dbgate-plugin-duckdb/webpack-frontend.config.js diff --git a/common/volatilePackages.js b/common/volatilePackages.js index 00ee25a33..f3ffe1285 100644 --- a/common/volatilePackages.js +++ b/common/volatilePackages.js @@ -21,6 +21,7 @@ const volatilePackages = [ 'axios', 'ssh2', 'wkx', + '@duckdb/node-api', ]; module.exports = volatilePackages; diff --git a/integration-tests/__tests__/alter-database.spec.js b/integration-tests/__tests__/alter-database.spec.js index 490de76a9..6ffaa35f7 100644 --- a/integration-tests/__tests__/alter-database.spec.js +++ b/integration-tests/__tests__/alter-database.spec.js @@ -36,6 +36,8 @@ async function testDatabaseDiff(conn, driver, mangle, createObject = null) { if (createObject) await driver.query(conn, createObject); const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn))); + console.log('str1'); + console.dir(structure1, { depth: 10 }); let structure2 = _.cloneDeep(structure1); mangle(structure2); structure2 = extendDatabaseInfo(structure2); diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 06baeab95..31303a3c3 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -136,76 +136,76 @@ describe('Alter table', () => { ); } - const hasEnginesWithNullable = engines.filter(x => !x.skipNullable).length > 0; - - if (hasEnginesWithNullable) { - const source = create_engines_columns_source(engines.filter(x => !x.skipNullable)); - - test.each(source)( - 'Change nullability - %s - %s', - testWrapper(async (conn, driver, column, engine) => { - await testTableDiff( - engine, - conn, - driver, - tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x))) - ); - }) - ); - } - - test.each(columnsSource)( - 'Rename column - %s - %s', - testWrapper(async (conn, driver, column, engine) => { - await testTableDiff( - engine, - conn, - driver, - tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x))) - ); - }) - ); - - test.each(engines.map(engine => [engine.label, engine]))( - 'Drop index - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.indexes = []; - }); - }) - ); - - const enginesWithDefault = engines.filter(x => !x.skipDefaultValue); - const hasEnginesWithDefault = enginesWithDefault.length > 0; - - if (hasEnginesWithDefault) { - test.each(enginesWithDefault.map(engine => [engine.label, engine]))( - 'Add default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123'; - }); - }) - ); - - test.each(enginesWithDefault.map(engine => [engine.label, engine]))( - 'Unset default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined; - }); - }) - ); - - test.each(enginesWithDefault.map(engine => [engine.label, engine]))( - 'Change default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567'; - }); - }) - ); - } + // const hasEnginesWithNullable = engines.filter(x => !x.skipNullable).length > 0; + // + // if (hasEnginesWithNullable) { + // const source = create_engines_columns_source(engines.filter(x => !x.skipNullable)); + // + // test.each(source)( + // 'Change nullability - %s - %s', + // testWrapper(async (conn, driver, column, engine) => { + // await testTableDiff( + // engine, + // conn, + // driver, + // tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x))) + // ); + // }) + // ); + // } + // + // test.each(columnsSource)( + // 'Rename column - %s - %s', + // testWrapper(async (conn, driver, column, engine) => { + // await testTableDiff( + // engine, + // conn, + // driver, + // tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x))) + // ); + // }) + // ); + // + // test.each(engines.map(engine => [engine.label, engine]))( + // 'Drop index - %s', + // testWrapper(async (conn, driver, engine) => { + // await testTableDiff(engine, conn, driver, tbl => { + // tbl.indexes = []; + // }); + // }) + // ); + // + // const enginesWithDefault = engines.filter(x => !x.skipDefaultValue); + // const hasEnginesWithDefault = enginesWithDefault.length > 0; + // + // if (hasEnginesWithDefault) { + // test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + // 'Add default value - %s', + // testWrapper(async (conn, driver, engine) => { + // await testTableDiff(engine, conn, driver, tbl => { + // tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123'; + // }); + // }) + // ); + // + // test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + // 'Unset default value - %s', + // testWrapper(async (conn, driver, engine) => { + // await testTableDiff(engine, conn, driver, tbl => { + // tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined; + // }); + // }) + // ); + // + // test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + // 'Change default value - %s', + // testWrapper(async (conn, driver, engine) => { + // await testTableDiff(engine, conn, driver, tbl => { + // tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567'; + // }); + // }) + // ); + // } // test.each(engines.map(engine => [engine.label, engine]))( // 'Change autoincrement - %s', diff --git a/integration-tests/__tests__/object-analyse.spec.js b/integration-tests/__tests__/object-analyse.spec.js index 25055fd04..60bfbaf18 100644 --- a/integration-tests/__tests__/object-analyse.spec.js +++ b/integration-tests/__tests__/object-analyse.spec.js @@ -20,7 +20,11 @@ function flatSourceParameters() { } function flatSourceTriggers() { - return _.flatten(engines.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine]))); + return _.flatten( + engines + .filter(engine => !engine.skipTriggers) + .map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine])) + ); } function flatSourceSchedulerEvents() { diff --git a/integration-tests/__tests__/query.spec.js b/integration-tests/__tests__/query.spec.js index 4f6896a54..e774594fd 100644 --- a/integration-tests/__tests__/query.spec.js +++ b/integration-tests/__tests__/query.spec.js @@ -147,6 +147,8 @@ describe('Query', () => { engine.skipOrderBy ? '' : 'ORDER BY ~id' }; ` ); + console.log('res'); + console.dir(results, { depth: 10 }); expect(results.length).toEqual(1); const res1 = results[0]; @@ -183,8 +185,8 @@ describe('Query', () => { { discardResult: true } ); const res = await runQueryOnDriver(conn, driver, dmp => dmp.put('SELECT COUNT(*) AS ~cnt FROM ~t1')); - // console.log(res); - expect(res.rows[0].cnt == 3).toBeTruthy(); + const cnt = parseInt(res.rows[0].cnt); + expect(cnt).toEqual(3); }) ); diff --git a/integration-tests/engines.js b/integration-tests/engines.js index 75ead4348..0073f5fb5 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -654,6 +654,24 @@ const cassandraEngine = { objects: [], }; +/** @type {import('dbgate-types').TestEngineInfo} */ +const duckdbEngine = { + label: 'DuckDB', + generateDbFile: true, + connection: { + engine: 'duckdb@dbgate-plugin-duckdb', + }, + objects: [views], + skipOnCI: false, + skipChangeColumn: true, + skipIndexes: true, + skipStringLength: true, + skipTriggers: true, + skipDataDuplicator: true, + skipAutoIncrement: true, + supportRenameSqlObject: true, +}; + const enginesOnCi = [ // all engines, which would be run on GitHub actions mysqlEngine, @@ -680,8 +698,9 @@ const enginesOnLocal = [ // cockroachDbEngine, // clickhouseEngine, // libsqlFileEngine, - libsqlWsEngine, + // libsqlWsEngine, // oracleEngine, + duckdbEngine, ]; /** @type {import('dbgate-types').TestEngineInfo[] & Record} */ diff --git a/integration-tests/package.json b/integration-tests/package.json index 5e8bb394d..a8c3e670b 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -12,7 +12,7 @@ "wait:local": "cross-env DEVMODE=1 LOCALTEST=1 node wait.js", "wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js", "test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest --testTimeout=5000", - "test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js", + "test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/alter-database.spec.js", "test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit --testTimeout=10000", "run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local" }, diff --git a/packages/api/testduck.db b/packages/api/testduck.db new file mode 100644 index 0000000000000000000000000000000000000000..9b9f42b80c1d4d9e43b35563bbed9de7a252f6ad GIT binary patch literal 2371584 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 zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7e?U*EoMZ3009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PGKMaO}P#4=um)#_v{Q+|~H0wbIz>d|ECIA3PtzYW%FlkAVOI0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oM-;f%iT?b^FGfAAfeI zX&^v=0D=AmDq*GbXQ9@btvSy2uPcVh5jb4zIF)L#52cXVN*+F2ZH@DhzXS*nAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+Kp6t>eSYfpjW<93>`v1_fB*pk z{R>pWO6S44)|#z3&i1b>hRG2)T#^O3&<2oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!89Q0`Gl(>h_H{KmP1a(?Ea#0RsICRKiN< z!MfI(tvSy2uPcVh5jb4zIF)L#52cXVN*+F2ZH@DhzXS*nAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly cK!5-N0t5&UAV7cs0RjXF5FkK+Kp6u65A8A|761SM literal 0 HcmV?d00001 diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index 652592dd8..e2d6abfb0 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -33,6 +33,7 @@ export interface QueryOptions { discardResult?: boolean; importSqlDump?: boolean; range?: { offset: number; limit: number }; + readonly?: boolean; } export interface WriteTableOptions { diff --git a/packages/types/query.d.ts b/packages/types/query.d.ts index e3abbccfe..633c1b01d 100644 --- a/packages/types/query.d.ts +++ b/packages/types/query.d.ts @@ -7,6 +7,7 @@ export interface QueryResultColumn { columnName: string; notNull: boolean; autoIncrement?: boolean; + dataType?: string; } export interface QueryResult { diff --git a/packages/types/test-engines.d.ts b/packages/types/test-engines.d.ts index c08e07f56..d0aeed3ee 100644 --- a/packages/types/test-engines.d.ts +++ b/packages/types/test-engines.d.ts @@ -40,6 +40,7 @@ export type TestEngineInfo = { skipPkDrop?: boolean; skipOrderBy?: boolean; skipImportModel?: boolean; + skipTriggers?: boolean; forceSortResults?: boolean; forceSortStructureColumns?: boolean; diff --git a/plugins/dbgate-plugin-duckdb/.gitignore b/plugins/dbgate-plugin-duckdb/.gitignore new file mode 100644 index 000000000..1dad8826d --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +build +dist +lib + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/plugins/dbgate-plugin-duckdb/README.md b/plugins/dbgate-plugin-duckdb/README.md new file mode 100644 index 000000000..b47943413 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/README.md @@ -0,0 +1,6 @@ +[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![NPM version](https://img.shields.io/npm/v/dbgate-plugin-sqlite.svg)](https://www.npmjs.com/package/dbgate-plugin-sqlite) + +# dbgate-plugin-sqlite + +Use DbGate for install of this plugin diff --git a/plugins/dbgate-plugin-duckdb/icon.svg b/plugins/dbgate-plugin-duckdb/icon.svg new file mode 100644 index 000000000..e5e0d0138 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/dbgate-plugin-duckdb/package.json b/plugins/dbgate-plugin-duckdb/package.json new file mode 100644 index 000000000..fd1b853c1 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/package.json @@ -0,0 +1,45 @@ +{ + "name": "dbgate-plugin-duckdb", + "main": "dist/backend.js", + "version": "6.0.0-alpha.1", + "homepage": "https://dbgate.org", + "description": "DuckDB connect plugin for DbGate", + "repository": { + "type": "git", + "url": "https://github.com/dbgate/dbgate" + }, + "author": "Jan Prochazka", + "license": "GPL-3.0", + "keywords": [ + "dbgate", + "duckdb", + "dbgatebuiltin" + ], + "files": [ + "dist", + "icon.svg" + ], + "scripts": { + "build:frontend": "webpack --config webpack-frontend.config", + "build:frontend:watch": "webpack --watch --config webpack-frontend.config", + "build:backend": "webpack --config webpack-backend.config.js", + "build": "yarn build:frontend && yarn build:backend", + "plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-duckdb", + "copydist": "yarn build && yarn pack && dbgate-copydist ../dist/dbgate-plugin-duckdb", + "plugout": "dbgate-plugout dbgate-plugin-duckdb", + "prepublishOnly": "yarn build" + }, + "devDependencies": { + "dbgate-plugin-tools": "^1.0.4", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "dbgate-tools": "^6.0.0-alpha.1", + "lodash": "^4.17.21", + "dbgate-query-splitter": "^4.11.3" + }, + "optionalDependencies": { + "@duckdb/node-api": "^1.2.1-alpha.16" + } +} diff --git a/plugins/dbgate-plugin-duckdb/prettier.config.js b/plugins/dbgate-plugin-duckdb/prettier.config.js new file mode 100644 index 000000000..406484074 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/prettier.config.js @@ -0,0 +1,8 @@ +module.exports = { + trailingComma: 'es5', + tabWidth: 2, + semi: true, + singleQuote: true, + arrowParen: 'avoid', + printWidth: 120, +}; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js new file mode 100644 index 000000000..99ab7ad55 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js @@ -0,0 +1,374 @@ +/** + * @typedef {object} DuckDbStringList + * @property {string[]} items + */ + +/** + * @typedef {object} DuckDbColumnRow + * @property {number | null} numeric_scale + * @property {number | null} numeric_precision_radix + * @property {number | null} numeric_precision + * @property {number | null} character_maximum_length + * @property {string | null} data_type_id + * @property {string} data_type + * @property {boolean} is_nullable + * @property {string | null} column_default + * @property {boolean} internal + * @property {string | null} comment + * @property {number} column_index + * @property {string} column_name + * @property {string} table_oid + * @property {string} table_name + * @property {string} schema_oid + * @property {string} schema_name + * @property {string} database_oid + * @property {string} database_name + */ + +/** + * @typedef {object} DuckDbConstraintRow + * @property {DuckDbStringList} referenced_column_names + * @property {string | null} referenced_table + * @property {string | null} constraint_name + * @property {DuckDbStringList} constraint_column_names + * @property {DuckDbStringList} constraint_column_indexes + * @property {string | null} expression + * @property {string | null} constraint_text + * @property {string} constraint_type + * @property {string} constraint_index + * @property {string} table_oid + * @property {string} table_name + * @property {string} schema_oid + * @property {string} schema_name + * @property {string} database_oid + * @property {string} database_name + */ + +/** + * @typedef {object} DuckDbTableRow + * @property {string | null} sql + * @property {string} check_constraint_count + * @property {string} index_count + * @property {string} column_count + * @property {string} estimated_size + * @property {boolean} has_primary_key + * @property {boolean} temporary + * @property {boolean} internal + * @property {{ entries: Array }} tags + * @property {string | null} comment + * @property {string} table_oid + * @property {string} table_name + * @property {string} schema_oid + * @property {string} schema_name + * @property {string} database_oid + * @property {string} database_name + */ + +/** + * Represents a single row returned from the duckdb_views() function. + * Note: Assumes OIDs and counts are represented as strings based on previous examples. + * + * @typedef {object} DuckDbViewRow + * @property {string} database_name + * @property {string} database_oid + * @property {string} schema_name + * @property {string} schema_oid + * @property {string} view_name + * @property {string} view_oid + * @property {string | null} comment + * @property {{ [key: string]: string } | null} tags + * @property {boolean} internal + * @property {boolean} temporary + * @property {string} column_count + * @property {string | null} sql + */ + +/** + * @param {DuckDbViewRow} duckDbViewRow + * @returns {import("dbgate-types").ViewInfo} + */ +function mapViewRowToViewInfo(duckDbViewRow) { + const viewInfo = { + pureName: duckDbViewRow.view_name, + schemaName: duckDbViewRow.schema_name, + objectId: duckDbViewRow.view_oid, + objectTypeField: 'view', + columns: [], + }; + + if (duckDbViewRow.comment != null) { + viewInfo.objectComment = duckDbViewRow.comment; + } + + if (duckDbViewRow.sql != null) { + viewInfo.createSql = duckDbViewRow.sql; + } + + return /** @type {import("dbgate-types").ViewInfo} */ (viewInfo); +} + +/** + * @param {DuckDbTableRow} rawTableData + */ +function mapRawTableToTableInfo(rawTableData) { + const pureName = rawTableData.table_name; + const schemaName = rawTableData.schema_name; + const objectId = rawTableData.table_oid; + const objectTypeField = 'table'; + const objectComment = rawTableData.comment; + + return { + pureName: pureName, + schemaName: schemaName, + objectId: objectId, + objectTypeField: objectTypeField, + objectComment: objectComment, + }; +} + +/** + * @typedef {object} DuckDbColumnDataTypeInfo + * @property {string} data_type + * @property {number | null} numeric_precision + * @property {number | null} numeric_scale + * @property {number | null} character_maximum_length + */ + +/** + * @param {DuckDbColumnDataTypeInfo | null | undefined} columnInfo + * @returns {string} + */ +function extractDataType(columnInfo) { + const baseType = columnInfo.data_type.toUpperCase(); + const precision = columnInfo.numeric_precision; + const scale = columnInfo.numeric_scale; + const maxLength = columnInfo.character_maximum_length; + + switch (baseType) { + case 'DECIMAL': + case 'NUMERIC': + if (typeof precision === 'number' && precision > 0 && typeof scale === 'number' && scale >= 0) { + return `${baseType}(${precision}, ${scale})`; + } + return baseType; + + case 'VARCHAR': + case 'CHAR': + console.log('this', maxLength); + if (typeof maxLength === 'number' && maxLength > 0) { + return `${baseType}(${maxLength})`; + } + return baseType; + + default: + return baseType; + } +} + +/** + * @param {DuckDbColumnRow} duckDbColumnData + */ +function mapRawColumnToColumnInfo(duckDbColumnData) { + const columnInfo = { + pureName: duckDbColumnData.table_name, + schemaName: duckDbColumnData.schema_name, + columnName: duckDbColumnData.column_name, + dataType: extractDataType(duckDbColumnData), + }; + + columnInfo.notNull = !duckDbColumnData.is_nullable; + + if (duckDbColumnData.column_default != null) { + columnInfo.defaultValue = duckDbColumnData.column_default; + } + + if (duckDbColumnData.comment != null) { + columnInfo.columnComment = duckDbColumnData.comment; + } + + if (duckDbColumnData.numeric_precision != null) { + columnInfo.precision = duckDbColumnData.numeric_precision; + } + + if (duckDbColumnData.numeric_scale != null) { + columnInfo.scale = duckDbColumnData.numeric_scale; + } + + if (duckDbColumnData.character_maximum_length != null) { + columnInfo.length = duckDbColumnData.character_maximum_length; + } + + return columnInfo; +} + +/** + * @param {DuckDbConstraintRow} duckDbConstraintData + * @returns {import("dbgate-types").ForeignKeyInfo} + */ +function mapConstraintRowToForeignKeyInfo(duckDbConstraintData) { + if ( + !duckDbConstraintData || + duckDbConstraintData.constraint_type !== 'FOREIGN KEY' || + duckDbConstraintData.referenced_table == null + ) { + return null; + } + + const columns = []; + const constraintColumns = duckDbConstraintData.constraint_column_names?.items; + const referencedColumns = duckDbConstraintData.referenced_column_names?.items; + + for (let i = 0; i < constraintColumns.length; i++) { + columns.push({ + columnName: constraintColumns[i], + refColumnName: referencedColumns[i], + }); + } + + const foreignKeyInfo = { + pureName: duckDbConstraintData.table_name, + schemaName: duckDbConstraintData.schema_name, + constraintType: 'foreignKey', + columns: columns, + refTableName: duckDbConstraintData.referenced_table, + }; + + if (duckDbConstraintData.constraint_name != null) { + foreignKeyInfo.constraintName = duckDbConstraintData.constraint_name; + } + + return /** @type {import("dbgate-types").ForeignKeyInfo} */ (foreignKeyInfo); +} + +/** + * @param {DuckDbConstraintRow} duckDbConstraintData + * @returns {import("dbgate-types").PrimaryKeyInfo} + */ +function mapConstraintRowToPrimaryKeyInfo(duckDbConstraintData) { + const columns = []; + const constraintColumns = duckDbConstraintData.constraint_column_names?.items; + + for (let i = 0; i < constraintColumns.length; i++) { + columns.push({ + columnName: constraintColumns[i], + }); + } + + const primaryKeyInfo = { + pureName: duckDbConstraintData.table_name, + schemaName: duckDbConstraintData.schema_name, + constraintType: 'primaryKey', + columns: columns, + }; + + if (duckDbConstraintData.constraint_name != null) { + primaryKeyInfo.constraintName = duckDbConstraintData.constraint_name; + } + + return /** @type {import("dbgate-types").PrimaryKeyInfo} */ (primaryKeyInfo); +} + +/** + * @typedef {object} DuckDbConstraintRow + * @property {DuckDbStringList} referenced_column_names + * @property {string | null} referenced_table + * @property {string | null} constraint_name + * @property {DuckDbStringList} constraint_column_names + * @property {DuckDbStringList} constraint_column_indexes + * @property {string | null} expression + * @property {string | null} constraint_text + * @property {string} constraint_type + * @property {string} constraint_index + * @property {string} table_oid + * @property {string} table_name + * @property {string} schema_oid + * @property {string} schema_name + * @property {string} database_oid + * @property {string} database_name + */ + +/** + * Maps a single DuckDbConstraintRow object to a UniqueInfo object if it represents a UNIQUE constraint. + * Assumes UniqueInfo and DuckDbConstraintRow are defined types/interfaces. + * @param {DuckDbConstraintRow} duckDbConstraintData - A single object conforming to DuckDbConstraintRow. + * @returns {import("dbgate-types").UniqueInfo | null} An object structured like UniqueInfo, or null if the input is not a valid UNIQUE constraint. + */ +function mapConstraintRowToUniqueInfo(duckDbConstraintData) { + if (!duckDbConstraintData || duckDbConstraintData.constraint_type !== 'UNIQUE') { + return null; + } + + const columns = []; + const constraintColumns = duckDbConstraintData.constraint_column_names?.items; + + if (Array.isArray(constraintColumns) && constraintColumns.length > 0) { + for (let i = 0; i < constraintColumns.length; i++) { + columns.push({ + columnName: constraintColumns[i], + }); + } + } else { + return null; + } + + const uniqueInfo = { + pureName: duckDbConstraintData.table_name, + schemaName: duckDbConstraintData.schema_name, + constraintType: 'unique', + columns: columns, + }; + + if (duckDbConstraintData.constraint_name != null) { + uniqueInfo.constraintName = duckDbConstraintData.constraint_name; + } + + return /** @type {import("dbgate-types").UniqueInfo} */ (uniqueInfo); +} + +/** + * @typedef {object} DuckDbIndexRow + * @property {string} database_name + * @property {string} database_oid + * @property {string} schema_name + * @property {string} schema_oid + * @property {string} index_name + * @property {string} index_oid + * @property {string} table_name + * @property {string} table_oid + * @property {string | null} comment + * @property {{ [key: string]: string } | null} tags + * @property {boolean} is_unique + * @property {boolean} is_primary + * @property {string | null} expressions + * @property {string | null} sql + */ + +/** + * @param {DuckDbIndexRow} duckDbIndexRow + * @returns {import("dbgate-types").IndexInfo} + */ +function mapIndexRowToIndexInfo(duckDbIndexRow) { + const indexInfo = { + pureName: duckDbIndexRow.table_name, + schemaName: duckDbIndexRow.schema_name, + constraintType: 'index', + columns: [], + isUnique: duckDbIndexRow.is_unique, + }; + + if (duckDbIndexRow.index_name != null) { + indexInfo.constraintName = duckDbIndexRow.index_name; + } + + return /** @type {import("dbgate-types").IndexInfo} */ (indexInfo); +} + +module.exports = { + mapRawTableToTableInfo, + mapRawColumnToColumnInfo, + mapConstraintRowToForeignKeyInfo, + mapConstraintRowToPrimaryKeyInfo, + mapConstraintRowToUniqueInfo, + mapViewRowToViewInfo, + mapIndexRowToIndexInfo, +}; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/Analyser.js b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.js new file mode 100644 index 000000000..81cfb02f4 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.js @@ -0,0 +1,85 @@ +const _ = require('lodash'); +const { DatabaseAnalyser } = require('dbgate-tools'); +const sql = require('./sql'); +const { + mapRawTableToTableInfo, + mapRawColumnToColumnInfo, + mapConstraintRowToForeignKeyInfo: mapDuckDbFkConstraintToForeignKeyInfo, + mapConstraintRowToPrimaryKeyInfo, + mapIndexRowToIndexInfo, + mapConstraintRowToUniqueInfo, + mapViewRowToViewInfo, +} = require('./Analyser.helpers'); + +class Analyser extends DatabaseAnalyser { + constructor(dbhan, driver, version) { + super(dbhan, driver, version); + } + + async _computeSingleObjectId() { + const { pureName } = this.singleObjectFilter; + this.singleObjectId = pureName; + } + + async _getFastSnapshot() { + const tablesResult = await this.driver.query(this.dbhan, sql.tables); + const columnsResult = await this.driver.query(this.dbhan, sql.columns); + const foreignKeysResult = await this.driver.query(this.dbhan, sql.foreignKeys); + const primaryKeysResult = await this.driver.query(this.dbhan, sql.primaryKeys); + const uniquesResults = await this.driver.query(this.dbhan, sql.uniques); + const indexesResult = await this.driver.query(this.dbhan, sql.indexes); + const viewsResult = await this.driver.query(this.dbhan, sql.views); + + /** + * @type {import('dbgate-types').ForeignKeyInfo[]} + */ + const foreignKeys = foreignKeysResult.rows?.map(mapDuckDbFkConstraintToForeignKeyInfo).filter(Boolean); + + /** + * @type {import('dbgate-types').PrimaryKeyInfo[]} + */ + const primaryKeys = primaryKeysResult.rows?.map(mapConstraintRowToPrimaryKeyInfo).filter(Boolean); + + /** + * @type {import('dbgate-types').UniqueInfo[]} + */ + const uniques = uniquesResults.rows?.map(mapConstraintRowToUniqueInfo).filter(Boolean); + + /** + * @type {import('dbgate-types').IndexInfo[]} + */ + const indexes = indexesResult.rows?.map(mapIndexRowToIndexInfo).filter(Boolean); + + const views = viewsResult.rows?.map(mapViewRowToViewInfo); + + const columns = columnsResult.rows?.map(mapRawColumnToColumnInfo); + const tables = tablesResult.rows?.map(mapRawTableToTableInfo); + const tablesExtended = tables.map((table) => ({ + ...table, + columns: columns.filter((x) => x.pureName == table.pureName && x.schemaName == table.schemaName), + foreignKeys: foreignKeys.filter((x) => x.pureName == table.pureName && x.schemaName == table.schemaName), + primaryKey: primaryKeys.find((x) => x.pureName == table.pureName && x.schemaName == table.schemaName), + indexes: indexes.filter((x) => x.pureName == table.pureName && x.schemaName == table.schemaName), + uniques: uniques.filter((x) => x.pureName == table.pureName && x.schemaName == table.schemaName), + })); + + const viewsExtended = views.map((view) => ({ + ...view, + columns: columns.filter((x) => x.pureName == view.pureName && x.schemaName == view.schemaName), + })); + + return { + tables: tablesExtended, + views: viewsExtended, + }; + } + + async _runAnalysis() { + const structure = await this._getFastSnapshot(); + return structure; + throw new Error('Not implemented'); + return this._getFastSnapshot(); + } +} + +module.exports = Analyser; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/driver.js b/plugins/dbgate-plugin-duckdb/src/backend/driver.js new file mode 100644 index 000000000..be7cc23c4 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/driver.js @@ -0,0 +1,165 @@ +const Analyser = require('./Analyser'); +const Dumper = require('../frontend/Dumper'); +const driverBase = require('../frontend/driver'); +const { getLogger, extractErrorLogData } = require('dbgate-tools'); +const { getColumnsInfo, serializeRow, normalizeRow } = require('./helpers'); + +const logger = getLogger('sqliteDriver'); + +/** + * @type {import('@duckdb/node-api')} + */ +let duckDb; + +function getDuckDb() { + if (!duckDb) { + duckDb = require('@duckdb/node-api'); + } + + return duckDb; +} + +let fileToCon = {}; +async function getConnection(file) { + if (fileToCon[file]) { + fileToCon[file].close(); + } + + const duckDb = getDuckDb(); + const instance = await duckDb.DuckDBInstance.create(file); + console.log('DuckDB instance created', instance); + const connection = await instance.connect(); + + fileToCon[file] = connection; + + return fileToCon[file]; +} + +/** @type {import('dbgate-types').EngineDriver} */ +const driver = { + ...driverBase, + analyserClass: Analyser, + async connect({ databaseFile, isReadOnly }) { + return { + client: await getConnection(databaseFile), + }; + }, + async close(dbhan) { + dbhan.client.disconnect(); + dbhan.client.close(); + }, + async query(dbhan, sql, { readonly } = {}) { + const res = await dbhan.client.runAndReadAll(sql); + const rowsObjects = res.getRowObjects(); + + const columnNames = res.columnNames(); + const columnTypes = res.columnTypes(); + + const columns = getColumnsInfo(columnNames, columnTypes).map(normalizeRow); + + const rows = rowsObjects.map(normalizeRow); + return { + rows, + columns, + }; + }, + async stream(dbhan, sql, options) { + const duckdb = getDuckDb(); + const statements = await dbhan.client.extractStatements(sql); + const count = statements.count; + + try { + for (let i = 0; i < count; i++) { + let hasSentColumns = false; + const stmt = await statements.prepare(i); + const res = await stmt.runAndReadAll(); + + const returningStatemetes = [ + duckdb.StatementType.SELECT, + duckdb.StatementType.EXPLAIN, + duckdb.StatementType.EXECUTE, + duckdb.StatementType.RELATION, + duckdb.StatementType.LOGICAL_PLAN, + ]; + + if (!returningStatemetes.includes(stmt.statementType)) { + continue; + } + + options.info({ + message: JSON.stringify(res), + time: new Date(), + severity: 'info', + }); + + if (!hasSentColumns) { + const columnNames = res.columnNames(); + const columnTypes = res.columnTypes(); + const columns = getColumnsInfo(columnNames, columnTypes); + + options.recordset(columns); + hasSentColumns = true; + } + + const rows = res.getRowObjects(); + + for (const row of rows) { + options.row(normalizeRow(row)); + } + } + + options.done(); + } catch (error) { + logger.error(extractErrorLogData(error), 'Stream error'); + const { message, procName } = error; + options.info({ + message, + line: 0, + procedure: procName, + time: new Date(), + severity: 'error', + }); + options.done(); + } + }, + async script(dbhan, sql) { + const dmp1 = driver.createDumper(); + dmp1.beginTransaction(); + + await dbhan.client.run(dmp1.s); + + const statements = await dbhan.client.extractStatements(sql); + const count = statements.count; + + for (let i = 0; i < count; i++) { + const stmt = await statements.prepare(i); + await stmt.run(); + } + + const dmp2 = driver.createDumper(); + dmp2.commitTransaction(); + + await dbhan.client.run(dmp2.s); + }, + + async readQueryTask(stmt, pass) { + throw new Error('Not implemented'); + }, + async readQuery(dbhan, sql, structure) { + throw new Error('Not implemented'); + }, + async writeTable(dbhan, name, options) { + return createBulkInsertStreamBase(this, stream, dbhan, name, options); + }, + async getVersion(dbhan) { + const { rows } = await this.query(dbhan, 'SELECT version() AS version;'); + const { version } = rows[0]; + + return { + version, + versionText: `DuchDB ${version}`, + }; + }, +}; + +module.exports = driver; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/helpers.js b/plugins/dbgate-plugin-duckdb/src/backend/helpers.js new file mode 100644 index 000000000..0319b428a --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/helpers.js @@ -0,0 +1,65 @@ +/** + * @param {string[} columnNames + * @param {import('@duckdb/node-api').DuckDBType[]} columnTypes + */ +function getColumnsInfo(columnNames, columnTypes) { + const columns = []; + + for (let i = columnNames.length - 1; i >= 0; i--) { + columns.push({ + columnName: columnNames[i], + dataType: columnTypes[i], + }); + } + + return columns; +} + +function _normalizeValue(value) { + if (value === null) { + return null; + } + + if (typeof value === 'bigint') { + return `${value}n`; + } + + if (Array.isArray(value)) { + return value.map((item) => _normalizeValue(item)); + } + + if (typeof value === 'object') { + const normalizedObj = {}; + for (const key in value) { + if (Object.hasOwnProperty.call(value, key)) { + normalizedObj[key] = _normalizeValue(value[key]); + } + } + return normalizedObj; + } + + return value; +} + +/** + * @param {Record} obj + * + */ +function normalizeRow(obj) { + if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { + return _normalizeValue(obj); + } + + const normalized = {}; + for (const key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + normalized[key] = _normalizeValue(obj[key]); + } + } + return normalized; +} + +module.exports = { + normalizeRow, + getColumnsInfo, +}; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/index.js b/plugins/dbgate-plugin-duckdb/src/backend/index.js new file mode 100644 index 000000000..7e25f4c20 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/index.js @@ -0,0 +1,6 @@ +const driver = require('./driver'); + +module.exports = { + packageName: 'dbgate-plugin-duckdb', + drivers: [driver], +}; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js new file mode 100644 index 000000000..02b187cdc --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js @@ -0,0 +1 @@ +module.exports = `SELECT * from duckdb_columns() WHERE internal = false`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/foreignKeys.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/foreignKeys.js new file mode 100644 index 000000000..5f36ecc85 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/foreignKeys.js @@ -0,0 +1 @@ +module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'FOREIGN KEY'`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/index.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/index.js new file mode 100644 index 000000000..ed1ee30bb --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/index.js @@ -0,0 +1,17 @@ +const tables = require('./tables.js'); +const columns = require('./columns.js'); +const foreignKeys = require('./foreignKeys.js'); +const primaryKeys = require('./primaryKeys.js'); +const indexes = require('./indexes.js'); +const uniques = require('./uniques.js'); +const views = require('./views.js'); + +module.exports = { + tables, + columns, + foreignKeys, + primaryKeys, + indexes, + uniques, + views, +}; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js new file mode 100644 index 000000000..888739a03 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js @@ -0,0 +1 @@ +module.exports = `SELECT * FROM duckdb_indexes()`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/primaryKeys.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/primaryKeys.js new file mode 100644 index 000000000..f95f72444 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/primaryKeys.js @@ -0,0 +1 @@ +module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'PRIMARY KEY'`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js new file mode 100644 index 000000000..aaaebe624 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js @@ -0,0 +1 @@ +module.exports = `SELECT * from duckdb_tables() WHERE internal = false`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js new file mode 100644 index 000000000..69de129e5 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js @@ -0,0 +1 @@ +module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'UNIQUE'`; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/sql/views.js b/plugins/dbgate-plugin-duckdb/src/backend/sql/views.js new file mode 100644 index 000000000..cb1ff41d6 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/sql/views.js @@ -0,0 +1 @@ +module.exports = `SELECT * FROM duckdb_views() WHERE internal = false`; diff --git a/plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js b/plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js new file mode 100644 index 000000000..5e45dae5d --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js @@ -0,0 +1,7 @@ +const { SqlDumper, arrayToHexString } = require('dbgate-tools'); + +class Dumper extends SqlDumper { + autoIncrement() {} +} + +module.exports = Dumper; diff --git a/plugins/dbgate-plugin-duckdb/src/frontend/driver.js b/plugins/dbgate-plugin-duckdb/src/frontend/driver.js new file mode 100644 index 000000000..70c39b282 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/frontend/driver.js @@ -0,0 +1,72 @@ +// @ts-check + +const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools']; +const Dumper = require('./Dumper'); +const { sqliteSplitterOptions, noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options'); + +/** + * @param {string} databaseFile + */ +function getDatabaseFileLabel(databaseFile) { + if (!databaseFile) return databaseFile; + const m = databaseFile.match(/[\/]([^\/]+)$/); + if (m) return m[1]; + return databaseFile; +} + +/** @type {import('dbgate-types').SqlDialect} */ +const dialect = { + limitSelect: true, + rangeSelect: true, + offsetFetchRangeSyntax: false, + explicitDropConstraint: true, + stringEscapeChar: "'", + fallbackDataType: 'nvarchar', + allowMultipleValuesInsert: true, + dropColumnDependencies: ['indexes', 'primaryKey', 'uniques'], + quoteIdentifier(s) { + return `"${s}"`; + }, + anonymousPrimaryKey: true, + requireStandaloneSelectForScopeIdentity: true, + + createColumn: true, + dropColumn: true, + createIndex: true, + dropIndex: true, + createForeignKey: false, + enableForeignKeyChecks: false, + dropForeignKey: false, + createPrimaryKey: false, + dropPrimaryKey: false, + dropReferencesWhenDropTable: false, + filteredIndexes: true, + anonymousForeignKey: true, +}; + +/** @type {import('dbgate-types').EngineDriver} */ +const driver = { + ...driverBase, + dumperClass: Dumper, + dialect, + engine: 'duckdb@dbgate-plugin-duckdb', + title: 'DuckDB', + readOnlySessions: true, + supportsTransactions: true, + + getQuerySplitterOptions: (usage) => + usage == 'editor' + ? { ...sqliteSplitterOptions, ignoreComments: true, preventSingleLineSplit: true } + : usage == 'stream' + ? noSplitSplitterOptions + : sqliteSplitterOptions, + showConnectionTab: (field) => false, + showConnectionField: (field) => ['databaseFile'].includes(field), + beforeConnectionSave: (connection) => ({ + ...connection, + singleDatabase: true, + defaultDatabase: getDatabaseFileLabel(connection.databaseFile), + }), +}; + +module.exports = driver; diff --git a/plugins/dbgate-plugin-duckdb/src/frontend/index.js b/plugins/dbgate-plugin-duckdb/src/frontend/index.js new file mode 100644 index 000000000..cb650c7ec --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/frontend/index.js @@ -0,0 +1,6 @@ +import driver from './driver'; + +export default { + packageName: 'dbgate-plugin-duckdb', + drivers: [driver], +}; diff --git a/plugins/dbgate-plugin-duckdb/webpack-backend.config.js b/plugins/dbgate-plugin-duckdb/webpack-backend.config.js new file mode 100644 index 000000000..ebecca263 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/webpack-backend.config.js @@ -0,0 +1,28 @@ +var webpack = require('webpack'); +var path = require('path'); + +const packageJson = require('./package.json'); +const buildPluginExternals = require('../../common/buildPluginExternals'); +const externals = buildPluginExternals(packageJson); + +var config = { + context: __dirname + '/src/backend', + + entry: { + app: './index.js', + }, + target: 'node', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'backend.js', + libraryTarget: 'commonjs2', + }, + + // uncomment for disable minimalization + // optimization: { + // minimize: false, + // }, + externals, +}; + +module.exports = config; diff --git a/plugins/dbgate-plugin-duckdb/webpack-frontend.config.js b/plugins/dbgate-plugin-duckdb/webpack-frontend.config.js new file mode 100644 index 000000000..cbc4a0a5a --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/webpack-frontend.config.js @@ -0,0 +1,30 @@ +var webpack = require('webpack'); +var path = require('path'); + +var config = { + context: __dirname + '/src/frontend', + + entry: { + app: './index.js', + }, + target: 'web', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'frontend.js', + libraryTarget: 'var', + library: 'plugin', + }, + + plugins: [ + new webpack.DefinePlugin({ + 'global.DBGATE_PACKAGES': 'window.DBGATE_PACKAGES', + }), + ], + + // uncomment for disable minimalization + // optimization: { + // minimize: false, + // }, +}; + +module.exports = config; diff --git a/yarn.lock b/yarn.lock index ee2f4a95e..7df1f9368 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1100,6 +1100,49 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@duckdb/node-api@^1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-api/-/node-api-1.2.1-alpha.16.tgz#4e0d8a17f227eed336ab4d50922bfc7be0b1024d" + integrity sha512-r2wkrqcDl3IsmFffpTj7xgSUEkjVaqi7wV0uypQWw4wTM5bIYk5ABc/Hxn2xlwV9UBRnxhk0Ls0EJypYS7g8ZQ== + dependencies: + "@duckdb/node-bindings" "1.2.1-alpha.16" + +"@duckdb/node-bindings-darwin-arm64@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings-darwin-arm64/-/node-bindings-darwin-arm64-1.2.1-alpha.16.tgz#a7061c8fc8d968bf9657211ccacd1139bfad96af" + integrity sha512-anfLXcxjo6S0Kx8Z+e6/ca7WayprJ8iI4cpTvzWQc9NT/vKFHcGjvhGAiosHvtjWGOvAYo+O/eyAcmzMzazlMg== + +"@duckdb/node-bindings-darwin-x64@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings-darwin-x64/-/node-bindings-darwin-x64-1.2.1-alpha.16.tgz#7d21558a50384115ba8eb8c41a84d689e2c797e9" + integrity sha512-IA2bQ/f0qFYb7Sd+leSjNg/JMBpWVVBoCmqp/1zzlw6fwhtT0BMSAT3FL4306t5StA8biOznlHz3rN3jovdVxg== + +"@duckdb/node-bindings-linux-arm64@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings-linux-arm64/-/node-bindings-linux-arm64-1.2.1-alpha.16.tgz#5915a71f3520b8a2cfbf63ad89d63198aec6db57" + integrity sha512-zy9jTrrhTXJAOrYRTbT/HtBLClAoyo8vNRAqojFHVBxXL1nr4o+5Je9AJwb9IfS1/e38zdykDWeGnY/gB3NpfA== + +"@duckdb/node-bindings-linux-x64@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings-linux-x64/-/node-bindings-linux-x64-1.2.1-alpha.16.tgz#8cff0e412d6201b57069c311775c375268cd5707" + integrity sha512-tdDAhUKenBhUQiTN+qvKj6nBshoooBLPbxVuLas8v64KmphjxOHd9zQ2KMzw1tN+fXhV9dqUTbCqiUN9A6ZFSQ== + +"@duckdb/node-bindings-win32-x64@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings-win32-x64/-/node-bindings-win32-x64-1.2.1-alpha.16.tgz#9fb46578f55d5b24524ea9a2d791f8fa6982e613" + integrity sha512-FE9bZV8+LIiy5jQIsLoEmwLFIyPPJD4SXjKNCaU48DNVf1q81ZkhnoT7PVgeyNJmkR6rG2+mq6LzTSmdCBX0ig== + +"@duckdb/node-bindings@1.2.1-alpha.16": + version "1.2.1-alpha.16" + resolved "https://registry.yarnpkg.com/@duckdb/node-bindings/-/node-bindings-1.2.1-alpha.16.tgz#700ce66c74772be7a870ae68dd91a968d736ed7b" + integrity sha512-6ITHy26o99zxUhCGOxwkQbfmi5I8VXNGanhnrOe3pqUYRDXvGAe6T2MmBymYwU+fMZB341UE8krw7hUkPLfIeA== + optionalDependencies: + "@duckdb/node-bindings-darwin-arm64" "1.2.1-alpha.16" + "@duckdb/node-bindings-darwin-x64" "1.2.1-alpha.16" + "@duckdb/node-bindings-linux-arm64" "1.2.1-alpha.16" + "@duckdb/node-bindings-linux-x64" "1.2.1-alpha.16" + "@duckdb/node-bindings-win32-x64" "1.2.1-alpha.16" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"