mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 21:26:00 +00:00
WIP
This commit is contained in:
@@ -21,6 +21,7 @@ const volatilePackages = [
|
||||
'axios',
|
||||
'ssh2',
|
||||
'wkx',
|
||||
'@duckdb/node-api',
|
||||
];
|
||||
|
||||
module.exports = volatilePackages;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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<string, import('dbgate-types').TestEngineInfo>} */
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
BIN
packages/api/testduck.db
Normal file
BIN
packages/api/testduck.db
Normal file
Binary file not shown.
1
packages/types/engines.d.ts
vendored
1
packages/types/engines.d.ts
vendored
@@ -33,6 +33,7 @@ export interface QueryOptions {
|
||||
discardResult?: boolean;
|
||||
importSqlDump?: boolean;
|
||||
range?: { offset: number; limit: number };
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export interface WriteTableOptions {
|
||||
|
||||
1
packages/types/query.d.ts
vendored
1
packages/types/query.d.ts
vendored
@@ -7,6 +7,7 @@ export interface QueryResultColumn {
|
||||
columnName: string;
|
||||
notNull: boolean;
|
||||
autoIncrement?: boolean;
|
||||
dataType?: string;
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
|
||||
1
packages/types/test-engines.d.ts
vendored
1
packages/types/test-engines.d.ts
vendored
@@ -40,6 +40,7 @@ export type TestEngineInfo = {
|
||||
skipPkDrop?: boolean;
|
||||
skipOrderBy?: boolean;
|
||||
skipImportModel?: boolean;
|
||||
skipTriggers?: boolean;
|
||||
|
||||
forceSortResults?: boolean;
|
||||
forceSortStructureColumns?: boolean;
|
||||
|
||||
25
plugins/dbgate-plugin-duckdb/.gitignore
vendored
Normal file
25
plugins/dbgate-plugin-duckdb/.gitignore
vendored
Normal file
@@ -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*
|
||||
6
plugins/dbgate-plugin-duckdb/README.md
Normal file
6
plugins/dbgate-plugin-duckdb/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://www.npmjs.com/package/dbgate-plugin-sqlite)
|
||||
|
||||
# dbgate-plugin-sqlite
|
||||
|
||||
Use DbGate for install of this plugin
|
||||
1
plugins/dbgate-plugin-duckdb/icon.svg
Normal file
1
plugins/dbgate-plugin-duckdb/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 6.554 6.555" preserveAspectRatio="xMidYMid"><defs><linearGradient x1="2.983" y1=".53" x2="2.983" y2="4.744" id="A" gradientUnits="userSpaceOnUse"><stop stop-color="#97d9f6" offset="0%"/><stop stop-color="#0f80cc" offset="92.024%"/><stop stop-color="#0f80cc" offset="100%"/></linearGradient></defs><path d="M4.96.29H.847c-.276 0-.5.226-.5.5v4.536c0 .276.226.5.5.5h2.71c-.03-1.348.43-3.964 1.404-5.54z" fill="#0f80cc"/><path d="M4.81.437H.847c-.196 0-.355.16-.355.355v4.205c.898-.345 2.245-.642 3.177-.628A28.93 28.93 0 0 1 4.811.437z" fill="url(#A)"/><path d="M5.92.142c-.282-.25-.623-.15-.96.148l-.15.146c-.576.61-1.1 1.742-1.276 2.607a2.38 2.38 0 0 1 .148.426l.022.1.022.102s-.005-.02-.026-.08l-.014-.04a.461.461 0 0 0-.009-.022c-.038-.087-.14-.272-.187-.352a8.789 8.789 0 0 0-.103.321c.132.242.212.656.212.656s-.007-.027-.04-.12c-.03-.083-.176-.34-.21-.4-.06.22-.083.368-.062.404.04.07.08.2.115.324a7.52 7.52 0 0 1 .132.666l.005.062a6.11 6.11 0 0 0 .015.75c.026.313.075.582.137.726l.042-.023c-.09-.284-.128-.655-.112-1.084.025-.655.175-1.445.454-2.268C4.548 1.938 5.2.94 5.798.464c-.545.492-1.282 2.084-1.502 2.673-.247.66-.422 1.28-.528 1.873.182-.556.77-.796.77-.796s.29-.356.626-.865l-.645.172-.208.092s.53-.323.987-.47c.627-.987 1.31-2.39.622-3.002" fill="#003b57"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
45
plugins/dbgate-plugin-duckdb/package.json
Normal file
45
plugins/dbgate-plugin-duckdb/package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
8
plugins/dbgate-plugin-duckdb/prettier.config.js
Normal file
8
plugins/dbgate-plugin-duckdb/prettier.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
arrowParen: 'avoid',
|
||||
printWidth: 120,
|
||||
};
|
||||
374
plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js
Normal file
374
plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js
Normal file
@@ -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<any> }} 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,
|
||||
};
|
||||
85
plugins/dbgate-plugin-duckdb/src/backend/Analyser.js
Normal file
85
plugins/dbgate-plugin-duckdb/src/backend/Analyser.js
Normal file
@@ -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;
|
||||
165
plugins/dbgate-plugin-duckdb/src/backend/driver.js
Normal file
165
plugins/dbgate-plugin-duckdb/src/backend/driver.js
Normal file
@@ -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<import('@duckdb/node-api').DuckDBConnection>} */
|
||||
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;
|
||||
65
plugins/dbgate-plugin-duckdb/src/backend/helpers.js
Normal file
65
plugins/dbgate-plugin-duckdb/src/backend/helpers.js
Normal file
@@ -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<string, import('@duckdb/node-api').DuckDBValue>} 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,
|
||||
};
|
||||
6
plugins/dbgate-plugin-duckdb/src/backend/index.js
Normal file
6
plugins/dbgate-plugin-duckdb/src/backend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const driver = require('./driver');
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-duckdb',
|
||||
drivers: [driver],
|
||||
};
|
||||
1
plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js
Normal file
1
plugins/dbgate-plugin-duckdb/src/backend/sql/columns.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * from duckdb_columns() WHERE internal = false`;
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'FOREIGN KEY'`;
|
||||
17
plugins/dbgate-plugin-duckdb/src/backend/sql/index.js
Normal file
17
plugins/dbgate-plugin-duckdb/src/backend/sql/index.js
Normal file
@@ -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,
|
||||
};
|
||||
1
plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js
Normal file
1
plugins/dbgate-plugin-duckdb/src/backend/sql/indexes.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * FROM duckdb_indexes()`;
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'PRIMARY KEY'`;
|
||||
1
plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js
Normal file
1
plugins/dbgate-plugin-duckdb/src/backend/sql/tables.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * from duckdb_tables() WHERE internal = false`;
|
||||
1
plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js
Normal file
1
plugins/dbgate-plugin-duckdb/src/backend/sql/uniques.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * FROM duckdb_constraints() WHERE constraint_type = 'UNIQUE'`;
|
||||
1
plugins/dbgate-plugin-duckdb/src/backend/sql/views.js
Normal file
1
plugins/dbgate-plugin-duckdb/src/backend/sql/views.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = `SELECT * FROM duckdb_views() WHERE internal = false`;
|
||||
7
plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js
Normal file
7
plugins/dbgate-plugin-duckdb/src/frontend/Dumper.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { SqlDumper, arrayToHexString } = require('dbgate-tools');
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
autoIncrement() {}
|
||||
}
|
||||
|
||||
module.exports = Dumper;
|
||||
72
plugins/dbgate-plugin-duckdb/src/frontend/driver.js
Normal file
72
plugins/dbgate-plugin-duckdb/src/frontend/driver.js
Normal file
@@ -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;
|
||||
6
plugins/dbgate-plugin-duckdb/src/frontend/index.js
Normal file
6
plugins/dbgate-plugin-duckdb/src/frontend/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import driver from './driver';
|
||||
|
||||
export default {
|
||||
packageName: 'dbgate-plugin-duckdb',
|
||||
drivers: [driver],
|
||||
};
|
||||
28
plugins/dbgate-plugin-duckdb/webpack-backend.config.js
Normal file
28
plugins/dbgate-plugin-duckdb/webpack-backend.config.js
Normal file
@@ -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;
|
||||
30
plugins/dbgate-plugin-duckdb/webpack-frontend.config.js
Normal file
30
plugins/dbgate-plugin-duckdb/webpack-frontend.config.js
Normal file
@@ -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;
|
||||
43
yarn.lock
43
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"
|
||||
|
||||
Reference in New Issue
Block a user