mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-26 04:05:59 +00:00
Merge branch 'master' of https://github.com/dbgate/dbgate
This commit is contained in:
11
.github/workflows/run-tests.yaml
vendored
11
.github/workflows/run-tests.yaml
vendored
@@ -102,3 +102,14 @@ jobs:
|
|||||||
image: ghcr.io/tursodatabase/libsql-server:latest
|
image: ghcr.io/tursodatabase/libsql-server:latest
|
||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
|
firebird:
|
||||||
|
image: firebirdsql/firebird:latest
|
||||||
|
env:
|
||||||
|
FIREBIRD_DATABASE: mydatabase.fdb
|
||||||
|
FIREBIRD_USER: dbuser
|
||||||
|
FIREBIRD_PASSWORD: dbpassword
|
||||||
|
ISC_PASSWORD: masterkey
|
||||||
|
FIREBIRD_TRACE: false
|
||||||
|
FIREBIRD_USE_LEGACY_AUTH: true
|
||||||
|
ports:
|
||||||
|
- '3050:3050'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ async function testDatabaseDiff(conn, driver, mangle, createObject = null) {
|
|||||||
driver,
|
driver,
|
||||||
`create table ~t2 (
|
`create table ~t2 (
|
||||||
~id int not null primary key,
|
~id int not null primary key,
|
||||||
~t1_id int null references ~t1(~id)
|
~t1_id int ${driver.dialect.implicitNullDeclaration ? '' : 'null'} references ~t1(~id)
|
||||||
)`
|
)`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ async function testTableDiff(engine, conn, driver, mangle) {
|
|||||||
if (!engine.skipReferences) {
|
if (!engine.skipReferences) {
|
||||||
const query = formatQueryWithoutParams(
|
const query = formatQueryWithoutParams(
|
||||||
driver,
|
driver,
|
||||||
`create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))`
|
`create table ~t2 (~id int not null primary key, ~fkval int ${
|
||||||
|
driver.dialect.implicitNullDeclaration ? '' : 'null'
|
||||||
|
} references ~t1(~col_ref))`
|
||||||
);
|
);
|
||||||
|
|
||||||
await driver.query(conn, transformSqlForEngine(engine, query));
|
await driver.query(conn, transformSqlForEngine(engine, query));
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
|||||||
|
|
||||||
for (const loadedDbModel of dbModelsYaml) {
|
for (const loadedDbModel of dbModelsYaml) {
|
||||||
if (_.isString(loadedDbModel)) {
|
if (_.isString(loadedDbModel)) {
|
||||||
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel));
|
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel), {
|
||||||
|
useTransaction: engine.runDeployInTransaction,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const { sql, isEmpty } = await generateDeploySql({
|
const { sql, isEmpty } = await generateDeploySql({
|
||||||
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
||||||
@@ -131,6 +133,7 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
|||||||
driver,
|
driver,
|
||||||
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
|
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
|
||||||
dbdiffOptionsExtra,
|
dbdiffOptionsExtra,
|
||||||
|
useTransaction: engine.runDeployInTransaction,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +609,7 @@ describe('Deploy database', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(engines.filter(i => !i.skipDeploy).map(engine => [engine.label, engine]))(
|
test.each(engines.filter(i => !i.skipDeploy && !i.skipRenameTable).map(engine => [engine.label, engine]))(
|
||||||
'Mark table removed - %s',
|
'Mark table removed - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
await testDatabaseDeploy(engine, conn, driver, [[T1], [], []], {
|
await testDatabaseDeploy(engine, conn, driver, [[T1], [], []], {
|
||||||
@@ -822,7 +825,7 @@ describe('Deploy database', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(engines.filter(i => !i.skipDeploy).map(engine => [engine.label, engine]))(
|
test.each(engines.filter(i => !i.skipDeploy && !i.skipRenameTable).map(engine => [engine.label, engine]))(
|
||||||
'Mark table removed, one remains - %s',
|
'Mark table removed, one remains - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
await testDatabaseDeploy(engine, conn, driver, [[T1, T2], [T2], [T2]], {
|
await testDatabaseDeploy(engine, conn, driver, [[T1, T2], [T2], [T2]], {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ describe('Table analyse', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(engines.map(engine => [engine.label, engine]))(
|
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||||
'Table add - incremental analysis - %s',
|
'Table add - incremental analysis - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
|
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
|
||||||
@@ -112,7 +112,7 @@ describe('Table analyse', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(engines.map(engine => [engine.label, engine]))(
|
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||||
'Table remove - incremental analysis - %s',
|
'Table remove - incremental analysis - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
||||||
@@ -130,7 +130,7 @@ describe('Table analyse', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(engines.map(engine => [engine.label, engine]))(
|
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
|
||||||
'Table change - incremental analysis - %s',
|
'Table change - incremental analysis - %s',
|
||||||
testWrapper(async (conn, driver, engine) => {
|
testWrapper(async (conn, driver, engine) => {
|
||||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
|
||||||
|
|||||||
@@ -100,3 +100,28 @@ services:
|
|||||||
# - '5002:5001'
|
# - '5002:5001'
|
||||||
# volumes:
|
# volumes:
|
||||||
# - ./data/libsql:/var/lib/sqld
|
# - ./data/libsql:/var/lib/sqld
|
||||||
|
|
||||||
|
firebird:
|
||||||
|
image: firebirdsql/firebird:latest
|
||||||
|
container_name: firebird-db
|
||||||
|
environment:
|
||||||
|
- FIREBIRD_DATABASE=mydatabase.fdb
|
||||||
|
- FIREBIRD_USER=dbuser
|
||||||
|
- FIREBIRD_PASSWORD=dbpassword
|
||||||
|
- ISC_PASSWORD=masterkey
|
||||||
|
- FIREBIRD_TRACE=false
|
||||||
|
- FIREBIRD_USE_LEGACY_AUTH=true
|
||||||
|
ports:
|
||||||
|
- '3050:3050'
|
||||||
|
volumes:
|
||||||
|
- firebird-data:/firebird/data
|
||||||
|
- ./firebird.conf:/firebird/firebird.conf # Mount custom config file
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'nc', '-z', 'localhost', '3050']
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
firebird-data:
|
||||||
|
|||||||
@@ -680,6 +680,56 @@ const duckdbEngine = {
|
|||||||
skipDropReferences: true,
|
skipDropReferences: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||||
|
const firebirdEngine = {
|
||||||
|
label: 'Firebird',
|
||||||
|
generateDbFile: true,
|
||||||
|
databaseFileLocationOnServer: '/var/lib/firebird/data/',
|
||||||
|
defaultSchemaName: 'main',
|
||||||
|
connection: {
|
||||||
|
engine: 'firebird@dbgate-plugin-firebird',
|
||||||
|
server: 'localhost',
|
||||||
|
port: 3050,
|
||||||
|
// databaseUrl: '/var/lib/firebird/data/mydatabase.fdb',
|
||||||
|
// databaseFile: '/var/lib/firebird/data/mydatabase.fdb',
|
||||||
|
user: 'SYSDBA',
|
||||||
|
password: 'masterkey',
|
||||||
|
},
|
||||||
|
objects: [],
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
testName: 'triggers after each row',
|
||||||
|
create: `CREATE OR ALTER TRIGGER ~obj1 AFTER INSERT ON ~t1 AS BEGIN END;`,
|
||||||
|
drop: 'DROP TRIGGER ~obj1;',
|
||||||
|
objectTypeField: 'triggers',
|
||||||
|
expected: {
|
||||||
|
pureName: 'obj1',
|
||||||
|
tableName: 't1',
|
||||||
|
eventType: 'INSERT',
|
||||||
|
triggerTiming: 'AFTER',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
skipOnCI: false,
|
||||||
|
runDeployInTransaction: true,
|
||||||
|
skipDataModifications: true,
|
||||||
|
skipChangeColumn: true,
|
||||||
|
// skipIndexes: true,
|
||||||
|
// skipStringLength: true,
|
||||||
|
// skipTriggers: true,
|
||||||
|
skipDataReplicator: true,
|
||||||
|
skipAutoIncrement: true,
|
||||||
|
// skipDropColumn: true,
|
||||||
|
skipRenameColumn: true,
|
||||||
|
// skipChangeNullability: true,
|
||||||
|
// skipDeploy: true,
|
||||||
|
// supportRenameSqlObject: true,
|
||||||
|
skipIncrementalAnalysis: true,
|
||||||
|
skipRenameTable: true,
|
||||||
|
// skipDefaultValue: true,
|
||||||
|
skipDropReferences: true,
|
||||||
|
};
|
||||||
|
|
||||||
const enginesOnCi = [
|
const enginesOnCi = [
|
||||||
// all engines, which would be run on GitHub actions
|
// all engines, which would be run on GitHub actions
|
||||||
mysqlEngine,
|
mysqlEngine,
|
||||||
@@ -694,6 +744,7 @@ const enginesOnCi = [
|
|||||||
oracleEngine,
|
oracleEngine,
|
||||||
cassandraEngine,
|
cassandraEngine,
|
||||||
duckdbEngine,
|
duckdbEngine,
|
||||||
|
firebirdEngine,
|
||||||
];
|
];
|
||||||
|
|
||||||
const enginesOnLocal = [
|
const enginesOnLocal = [
|
||||||
@@ -709,7 +760,8 @@ const enginesOnLocal = [
|
|||||||
// libsqlFileEngine,
|
// libsqlFileEngine,
|
||||||
// libsqlWsEngine,
|
// libsqlWsEngine,
|
||||||
// oracleEngine,
|
// oracleEngine,
|
||||||
duckdbEngine,
|
// duckdbEngine,
|
||||||
|
firebirdEngine,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */
|
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */
|
||||||
@@ -727,3 +779,4 @@ module.exports.cassandraEngine = cassandraEngine;
|
|||||||
module.exports.libsqlFileEngine = libsqlFileEngine;
|
module.exports.libsqlFileEngine = libsqlFileEngine;
|
||||||
module.exports.libsqlWsEngine = libsqlWsEngine;
|
module.exports.libsqlWsEngine = libsqlWsEngine;
|
||||||
module.exports.duckdbEngine = duckdbEngine;
|
module.exports.duckdbEngine = duckdbEngine;
|
||||||
|
module.exports.firebirdEngine = firebirdEngine;
|
||||||
|
|||||||
45
integration-tests/firebird.conf
Normal file
45
integration-tests/firebird.conf
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Custom Firebird Configuration
|
||||||
|
|
||||||
|
# Wire encryption settings
|
||||||
|
# Options: Enabled, Required, Disabled
|
||||||
|
WireCrypt = Disabled
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
# Add Legacy_Auth to support older clients
|
||||||
|
AuthServer = Legacy_Auth
|
||||||
|
|
||||||
|
# User manager plugin
|
||||||
|
UserManager = Legacy_UserManager
|
||||||
|
|
||||||
|
# Default character set
|
||||||
|
DefaultCharSet = UTF8
|
||||||
|
|
||||||
|
# Buffer settings for better performance
|
||||||
|
DefaultDbCachePages = 2048
|
||||||
|
TempCacheLimit = 512M
|
||||||
|
|
||||||
|
# Connection settings
|
||||||
|
ConnectionTimeout = 180
|
||||||
|
DatabaseGrowthIncrement = 128M
|
||||||
|
|
||||||
|
# TCP Protocol settings
|
||||||
|
TcpRemoteBufferSize = 8192
|
||||||
|
TcpNoNagle = 1
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
RemoteServiceName = gds_db
|
||||||
|
RemoteServicePort = 3050
|
||||||
|
RemoteAuxPort = 0
|
||||||
|
RemotePipeName = firebird
|
||||||
|
|
||||||
|
# Lock settings
|
||||||
|
LockMemSize = 1M
|
||||||
|
LockHashSlots = 8191
|
||||||
|
LockAcquireSpins = 0
|
||||||
|
|
||||||
|
# Log settings
|
||||||
|
FileSystemCacheThreshold = 65536
|
||||||
|
FileSystemCacheSize = 0
|
||||||
|
|
||||||
|
# Compatibility settings for older clients
|
||||||
|
CompatiblityDialect = 3
|
||||||
@@ -5,7 +5,12 @@ const crypto = require('crypto');
|
|||||||
function randomDbName(dialect) {
|
function randomDbName(dialect) {
|
||||||
const generatedKey = crypto.randomBytes(6);
|
const generatedKey = crypto.randomBytes(6);
|
||||||
const newKey = generatedKey.toString('hex');
|
const newKey = generatedKey.toString('hex');
|
||||||
const res = `db${newKey}`;
|
let res = `db${newKey}`;
|
||||||
|
|
||||||
|
if (dialect.dbFileExtension) {
|
||||||
|
res += dialect.dbFileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
if (dialect.upperCaseAllDbObjectNames) return res.toUpperCase();
|
if (dialect.upperCaseAllDbObjectNames) return res.toUpperCase();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -17,7 +22,7 @@ async function connect(engine, database) {
|
|||||||
if (engine.generateDbFile) {
|
if (engine.generateDbFile) {
|
||||||
const conn = await driver.connect({
|
const conn = await driver.connect({
|
||||||
...connection,
|
...connection,
|
||||||
databaseFile: `dbtemp/${database}`,
|
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
|
||||||
});
|
});
|
||||||
return conn;
|
return conn;
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +47,7 @@ async function prepareConnection(engine, database) {
|
|||||||
if (engine.generateDbFile) {
|
if (engine.generateDbFile) {
|
||||||
return {
|
return {
|
||||||
...connection,
|
...connection,
|
||||||
databaseFile: `dbtemp/${database}`,
|
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
|
||||||
isPreparedOnly: true,
|
isPreparedOnly: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const crypto = require('crypto');
|
|||||||
* @param {string} options.ignoreNameRegex - regex for ignoring objects by name
|
* @param {string} options.ignoreNameRegex - regex for ignoring objects by name
|
||||||
* @param {string} options.targetSchema - target schema for deployment
|
* @param {string} options.targetSchema - target schema for deployment
|
||||||
* @param {number} options.maxMissingTablesRatio - maximum ratio of missing tables in database. Safety check, if missing ratio is highe, deploy is stopped (preventing accidental drop of all tables)
|
* @param {number} options.maxMissingTablesRatio - maximum ratio of missing tables in database. Safety check, if missing ratio is highe, deploy is stopped (preventing accidental drop of all tables)
|
||||||
|
* @param {boolean} options.useTransaction - run deploy in transaction. If not provided, it will be set to true if driver supports transactions
|
||||||
*/
|
*/
|
||||||
async function deployDb({
|
async function deployDb({
|
||||||
connection,
|
connection,
|
||||||
@@ -33,6 +34,7 @@ async function deployDb({
|
|||||||
ignoreNameRegex = '',
|
ignoreNameRegex = '',
|
||||||
targetSchema = null,
|
targetSchema = null,
|
||||||
maxMissingTablesRatio = undefined,
|
maxMissingTablesRatio = undefined,
|
||||||
|
useTransaction,
|
||||||
}) {
|
}) {
|
||||||
if (!driver) driver = requireEngineDriver(connection);
|
if (!driver) driver = requireEngineDriver(connection);
|
||||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||||
@@ -60,7 +62,14 @@ async function deployDb({
|
|||||||
maxMissingTablesRatio,
|
maxMissingTablesRatio,
|
||||||
});
|
});
|
||||||
// console.log('RUNNING DEPLOY SCRIPT:', sql);
|
// console.log('RUNNING DEPLOY SCRIPT:', sql);
|
||||||
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
|
await executeQuery({
|
||||||
|
connection,
|
||||||
|
systemConnection: dbhan,
|
||||||
|
driver,
|
||||||
|
sql,
|
||||||
|
logScriptItems: true,
|
||||||
|
useTransaction,
|
||||||
|
});
|
||||||
|
|
||||||
await scriptDeployer.runPost();
|
await scriptDeployer.runPost();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const logger = getLogger('execQuery');
|
|||||||
* @param {string} [options.sql] - SQL query
|
* @param {string} [options.sql] - SQL query
|
||||||
* @param {string} [options.sqlFile] - SQL file
|
* @param {string} [options.sqlFile] - SQL file
|
||||||
* @param {boolean} [options.logScriptItems] - whether to log script items instead of whole script
|
* @param {boolean} [options.logScriptItems] - whether to log script items instead of whole script
|
||||||
|
* @param {boolean} [options.useTransaction] - run query in transaction
|
||||||
* @param {boolean} [options.skipLogging] - whether to skip logging
|
* @param {boolean} [options.skipLogging] - whether to skip logging
|
||||||
*/
|
*/
|
||||||
async function executeQuery({
|
async function executeQuery({
|
||||||
@@ -24,6 +25,7 @@ async function executeQuery({
|
|||||||
sqlFile = undefined,
|
sqlFile = undefined,
|
||||||
logScriptItems = false,
|
logScriptItems = false,
|
||||||
skipLogging = false,
|
skipLogging = false,
|
||||||
|
useTransaction,
|
||||||
}) {
|
}) {
|
||||||
if (!logScriptItems && !skipLogging) {
|
if (!logScriptItems && !skipLogging) {
|
||||||
logger.info({ sql: getLimitedQuery(sql) }, `Execute query`);
|
logger.info({ sql: getLimitedQuery(sql) }, `Execute query`);
|
||||||
@@ -42,7 +44,7 @@ async function executeQuery({
|
|||||||
logger.debug(`Running SQL query, length: ${sql.length}`);
|
logger.debug(`Running SQL query, length: ${sql.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await driver.script(dbhan, sql, { logScriptItems });
|
await driver.script(dbhan, sql, { logScriptItems, useTransaction });
|
||||||
} finally {
|
} finally {
|
||||||
if (!systemConnection) {
|
if (!systemConnection) {
|
||||||
await driver.close(dbhan);
|
await driver.close(dbhan);
|
||||||
|
|||||||
@@ -41,13 +41,14 @@ program
|
|||||||
'regex, which table data will be loaded and stored in model (in load command)'
|
'regex, which table data will be loaded and stored in model (in load command)'
|
||||||
)
|
)
|
||||||
.option('-e, --engine <engine>', 'engine name, eg. mysql@dbgate-plugin-mysql')
|
.option('-e, --engine <engine>', 'engine name, eg. mysql@dbgate-plugin-mysql')
|
||||||
.option('--commonjs', 'Creates CommonJS module');
|
.option('--commonjs', 'Creates CommonJS module')
|
||||||
|
.option('--transaction', 'Run deploy query in transaction');
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('deploy <modelFolder>')
|
.command('deploy <modelFolder>')
|
||||||
.description('Deploys model to database')
|
.description('Deploys model to database')
|
||||||
.action(modelFolder => {
|
.action(modelFolder => {
|
||||||
const { engine, server, user, password, database } = program.opts();
|
const { engine, server, user, password, database, transaction } = program.opts();
|
||||||
// const hooks = [];
|
// const hooks = [];
|
||||||
// if (program.autoIndexForeignKeys) hooks.push(dbmodel.hooks.autoIndexForeignKeys);
|
// if (program.autoIndexForeignKeys) hooks.push(dbmodel.hooks.autoIndexForeignKeys);
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ program
|
|||||||
database,
|
database,
|
||||||
},
|
},
|
||||||
modelFolder,
|
modelFolder,
|
||||||
|
useTransaction: transaction,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
|||||||
if (cmd.topRecords) {
|
if (cmd.topRecords) {
|
||||||
if (!dmp.dialect.rangeSelect || dmp.dialect.offsetFetchRangeSyntax) dmp.put('^top %s ', cmd.topRecords);
|
if (!dmp.dialect.rangeSelect || dmp.dialect.offsetFetchRangeSyntax) dmp.put('^top %s ', cmd.topRecords);
|
||||||
}
|
}
|
||||||
|
if (cmd.range && dmp.dialect.offsetFirstSkipSyntax) {
|
||||||
|
dmp.put('^first %s ^skip %s ', cmd.range.limit, cmd.range.offset);
|
||||||
|
}
|
||||||
if (cmd.selectAll) {
|
if (cmd.selectAll) {
|
||||||
dmp.put('* ');
|
dmp.put('* ');
|
||||||
}
|
}
|
||||||
@@ -52,6 +55,8 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
|||||||
if (cmd.range) {
|
if (cmd.range) {
|
||||||
if (dmp.dialect.offsetFetchRangeSyntax) {
|
if (dmp.dialect.offsetFetchRangeSyntax) {
|
||||||
dmp.put('^offset %s ^rows ^fetch ^next %s ^rows ^only', cmd.range.offset, cmd.range.limit);
|
dmp.put('^offset %s ^rows ^fetch ^next %s ^rows ^only', cmd.range.offset, cmd.range.limit);
|
||||||
|
} else if (dmp.dialect.offsetFirstSkipSyntax) {
|
||||||
|
//
|
||||||
} else if (dmp.dialect.offsetNotSupported) {
|
} else if (dmp.dialect.offsetNotSupported) {
|
||||||
dmp.put('^limit %s', cmd.range.limit + cmd.range.offset);
|
dmp.put('^limit %s', cmd.range.limit + cmd.range.offset);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -266,11 +266,11 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
this.columnDefault(column);
|
this.columnDefault(column);
|
||||||
}
|
}
|
||||||
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
|
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
|
||||||
this.put(column.notNull ? '^not ^null' : '^null');
|
this.put(column.notNull ? '^not ^null' : this.dialect.implicitNullDeclaration ? '' : '^null');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
|
if (includeNullable && !this.dialect?.specificNullabilityImplementation) {
|
||||||
this.put(column.notNull ? '^not ^null' : '^null');
|
this.put(column.notNull ? '^not ^null' : this.dialect.implicitNullDeclaration ? '' : '^null');
|
||||||
}
|
}
|
||||||
if (includeDefault && column.defaultValue?.toString()?.trim()) {
|
if (includeDefault && column.defaultValue?.toString()?.trim()) {
|
||||||
this.columnDefault(column);
|
this.columnDefault(column);
|
||||||
|
|||||||
3
packages/types/dialect.d.ts
vendored
3
packages/types/dialect.d.ts
vendored
@@ -8,6 +8,7 @@ export interface SqlDialect {
|
|||||||
topRecords?: boolean;
|
topRecords?: boolean;
|
||||||
stringEscapeChar: string;
|
stringEscapeChar: string;
|
||||||
offsetFetchRangeSyntax?: boolean;
|
offsetFetchRangeSyntax?: boolean;
|
||||||
|
offsetFirstSkipSyntax?: boolean;
|
||||||
offsetNotSupported?: boolean;
|
offsetNotSupported?: boolean;
|
||||||
quoteIdentifier(s: string): string;
|
quoteIdentifier(s: string): string;
|
||||||
fallbackDataType?: string;
|
fallbackDataType?: string;
|
||||||
@@ -47,6 +48,7 @@ export interface SqlDialect {
|
|||||||
namedDefaultConstraint?: boolean;
|
namedDefaultConstraint?: boolean;
|
||||||
|
|
||||||
specificNullabilityImplementation?: boolean;
|
specificNullabilityImplementation?: boolean;
|
||||||
|
implicitNullDeclaration?: boolean;
|
||||||
omitForeignKeys?: boolean;
|
omitForeignKeys?: boolean;
|
||||||
omitUniqueConstraints?: boolean;
|
omitUniqueConstraints?: boolean;
|
||||||
omitIndexes?: boolean;
|
omitIndexes?: boolean;
|
||||||
@@ -66,6 +68,7 @@ export interface SqlDialect {
|
|||||||
requireFromDual?: boolean;
|
requireFromDual?: boolean;
|
||||||
userDatabaseNamePrefix?: string; // c## in Oracle
|
userDatabaseNamePrefix?: string; // c## in Oracle
|
||||||
upperCaseAllDbObjectNames?: boolean;
|
upperCaseAllDbObjectNames?: boolean;
|
||||||
|
dbFileExtension?: string;
|
||||||
defaultValueBeforeNullability?: boolean;
|
defaultValueBeforeNullability?: boolean;
|
||||||
|
|
||||||
predefinedDataTypes: string[];
|
predefinedDataTypes: string[];
|
||||||
|
|||||||
4
packages/types/test-engines.d.ts
vendored
4
packages/types/test-engines.d.ts
vendored
@@ -45,12 +45,14 @@ export type TestEngineInfo = {
|
|||||||
skipChangeNullability?: boolean;
|
skipChangeNullability?: boolean;
|
||||||
skipRenameColumn?: boolean;
|
skipRenameColumn?: boolean;
|
||||||
skipDropReferences?: boolean;
|
skipDropReferences?: boolean;
|
||||||
|
skipRenameTable?: boolean;
|
||||||
|
|
||||||
forceSortResults?: boolean;
|
forceSortResults?: boolean;
|
||||||
forceSortStructureColumns?: boolean;
|
forceSortStructureColumns?: boolean;
|
||||||
alterTableAddColumnSyntax?: boolean;
|
alterTableAddColumnSyntax?: boolean;
|
||||||
dbSnapshotBySeconds?: boolean;
|
dbSnapshotBySeconds?: boolean;
|
||||||
setNullDefaultInsteadOfDrop?: boolean;
|
setNullDefaultInsteadOfDrop?: boolean;
|
||||||
|
runDeployInTransaction?: boolean;
|
||||||
|
|
||||||
useTextTypeForStrings?: boolean;
|
useTextTypeForStrings?: boolean;
|
||||||
|
|
||||||
@@ -60,6 +62,8 @@ export type TestEngineInfo = {
|
|||||||
defaultSchemaName?: string;
|
defaultSchemaName?: string;
|
||||||
|
|
||||||
generateDbFile?: boolean;
|
generateDbFile?: boolean;
|
||||||
|
generateDbFileOnServer?: boolean;
|
||||||
|
databaseFileLocationOnServer?: string;
|
||||||
dbSnapshotBySeconds?: boolean;
|
dbSnapshotBySeconds?: boolean;
|
||||||
dumpFile?: string;
|
dumpFile?: string;
|
||||||
dumpChecks?: Array<{ sql: string; res: string }>;
|
dumpChecks?: Array<{ sql: string; res: string }>;
|
||||||
|
|||||||
6
plugins/dbgate-plugin-firebird/README.md
Normal file
6
plugins/dbgate-plugin-firebird/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[](https://github.com/prettier/prettier)
|
||||||
|
[](https://www.npmjs.com/package/dbgate-plugin-firebird)
|
||||||
|
|
||||||
|
# dbgate-plugin-firebird
|
||||||
|
|
||||||
|
Firebid/Interbase plugin for DbGate
|
||||||
46
plugins/dbgate-plugin-firebird/package.json
Normal file
46
plugins/dbgate-plugin-firebird/package.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "dbgate-plugin-firebird",
|
||||||
|
"main": "dist/backend.js",
|
||||||
|
"version": "6.0.0-alpha.1",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"description": "firebirdQL connector plugin for DbGate",
|
||||||
|
"homepage": "https://dbgate.org",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dbgate/dbgate"
|
||||||
|
},
|
||||||
|
"author": "Jan Prochazka",
|
||||||
|
"keywords": [
|
||||||
|
"dbgate",
|
||||||
|
"firebird",
|
||||||
|
"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-firebird",
|
||||||
|
"copydist": "yarn build && yarn pack && dbgate-copydist ../dist/dbgate-plugin-firebird",
|
||||||
|
"plugout": "dbgate-plugout dbgate-plugin-firebird",
|
||||||
|
"prepublishOnly": "yarn build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
|
"webpack": "^5.91.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"wkx": "^0.5.0",
|
||||||
|
"pg-copy-streams": "^6.0.6",
|
||||||
|
"node-firebird": "^1.1.9",
|
||||||
|
"dbgate-query-splitter": "^4.11.3",
|
||||||
|
"dbgate-tools": "^6.0.0-alpha.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"pg": "^8.11.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
plugins/dbgate-plugin-firebird/prettier.config.js
Normal file
9
plugins/dbgate-plugin-firebird/prettier.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
trailingComma: 'es5',
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
arrowParen: 'avoid',
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
printWidth: 120,
|
||||||
|
};
|
||||||
147
plugins/dbgate-plugin-firebird/src/backend/Analyser.js
Normal file
147
plugins/dbgate-plugin-firebird/src/backend/Analyser.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const sql = require('./sql');
|
||||||
|
const {
|
||||||
|
getDataTypeString,
|
||||||
|
getTriggerTiming,
|
||||||
|
getTriggerEventType,
|
||||||
|
getFormattedDefaultValue,
|
||||||
|
getTriggerCreateSql,
|
||||||
|
} = require('./helpers');
|
||||||
|
|
||||||
|
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||||
|
|
||||||
|
class Analyser extends DatabaseAnalyser {
|
||||||
|
constructor(dbhan, driver, version) {
|
||||||
|
super(dbhan, driver, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _runAnalysis() {
|
||||||
|
const tablesResult = await this.analyserQuery(sql.tables, ['tables']);
|
||||||
|
const columnsResult = await this.analyserQuery(sql.columns, ['tables', 'views']);
|
||||||
|
const triggersResult = await this.analyserQuery(sql.triggers, ['triggers']);
|
||||||
|
const primaryKeysResult = await this.analyserQuery(sql.primaryKeys, ['primaryKeys']);
|
||||||
|
const foreignKeysResult = await this.analyserQuery(sql.foreignKeys, ['foreignKeys']);
|
||||||
|
const functionsResults = await this.analyserQuery(sql.functions, ['functions']);
|
||||||
|
const functionParametersResults = await this.analyserQuery(sql.functionParameters, ['functions']);
|
||||||
|
const proceduresResults = await this.analyserQuery(sql.procedures, ['procedures']);
|
||||||
|
const procedureParametersResults = await this.analyserQuery(sql.procedureParameters, ['procedures']);
|
||||||
|
const viewsResults = await this.analyserQuery(sql.views, ['views']);
|
||||||
|
const unqiuesResults = await this.analyserQuery(sql.uniques, ['tables']);
|
||||||
|
const indexesResults = await this.analyserQuery(sql.indexes, ['tables']);
|
||||||
|
|
||||||
|
const columns =
|
||||||
|
columnsResult.rows?.map(column => ({
|
||||||
|
...column,
|
||||||
|
objectId: `tables:${column.columnName}`,
|
||||||
|
dataType: getDataTypeString(column),
|
||||||
|
defaultValue: getFormattedDefaultValue(column.defaultValue),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const triggers =
|
||||||
|
triggersResult.rows?.map(i => ({
|
||||||
|
...i,
|
||||||
|
objectId: `triggers:${i.pureName}`,
|
||||||
|
eventType: getTriggerEventType(i.TRIGGERTYPE),
|
||||||
|
triggerTiming: getTriggerTiming(i.TRIGGERTYPE),
|
||||||
|
createSql: getTriggerCreateSql(i),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const primaryKeys =
|
||||||
|
primaryKeysResult.rows?.map(primaryKey => ({
|
||||||
|
...primaryKey,
|
||||||
|
objectId: `tables:${primaryKey.pureName}`,
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const foreignKeys =
|
||||||
|
foreignKeysResult.rows?.map(foreignKey => ({
|
||||||
|
...foreignKey,
|
||||||
|
objectId: `tables:${foreignKey.pureName}`,
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const functions =
|
||||||
|
functionsResults.rows?.map(func => ({
|
||||||
|
...func,
|
||||||
|
objectId: `functions:${func.pureName}`,
|
||||||
|
returnType: functionParametersResults.rows?.filter(
|
||||||
|
param => param.owningObjectName === func.pureName && param.parameterMode === 'RETURN'
|
||||||
|
)?.dataType,
|
||||||
|
parameters: functionParametersResults.rows
|
||||||
|
?.filter(param => param.owningObjectName === func.pureName)
|
||||||
|
.map(param => ({
|
||||||
|
...param,
|
||||||
|
dataType: getDataTypeString(param),
|
||||||
|
})),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const uniques =
|
||||||
|
unqiuesResults.rows?.map(unique => ({
|
||||||
|
pureName: unique.pureName,
|
||||||
|
constraintName: unique.constraintName,
|
||||||
|
constraintType: unique.constraintType,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
columnName: unique.columnName,
|
||||||
|
isDescending: unique.isDescending,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const indexesGrouped = _.groupBy(indexesResults.rows, 'constraintName');
|
||||||
|
const indexes = Object.values(indexesGrouped).map(indexGroup => ({
|
||||||
|
pureName: indexGroup[0].pureName,
|
||||||
|
constraintName: indexGroup[0].constraintName,
|
||||||
|
constraintType: indexGroup[0].constraintType,
|
||||||
|
columns: indexGroup.map(index => ({
|
||||||
|
columnName: index.columnName,
|
||||||
|
isDescending: index.isDescending,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const procedures =
|
||||||
|
proceduresResults.rows?.map(proc => ({
|
||||||
|
...proc,
|
||||||
|
objectId: `procedures:${proc.pureName}`,
|
||||||
|
parameters: procedureParametersResults.rows
|
||||||
|
?.filter(param => param.owningObjectName === proc.pureName)
|
||||||
|
.map(param => ({
|
||||||
|
...param,
|
||||||
|
dataType: getDataTypeString(param),
|
||||||
|
})),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const tables =
|
||||||
|
tablesResult.rows?.map(table => ({
|
||||||
|
...table,
|
||||||
|
objectId: `tables:${table.pureName}`,
|
||||||
|
columns: columns.filter(column => column.tableName === table.pureName),
|
||||||
|
primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, primaryKeys),
|
||||||
|
foreignKeys: DatabaseAnalyser.extractForeignKeys(table, foreignKeys),
|
||||||
|
uniques: uniques.filter(unique => unique.pureName === table.pureName),
|
||||||
|
indexes: indexes.filter(index => index.pureName === table.pureName),
|
||||||
|
})) ?? [];
|
||||||
|
console.log(uniques);
|
||||||
|
|
||||||
|
const views =
|
||||||
|
viewsResults.rows?.map(view => ({
|
||||||
|
...view,
|
||||||
|
objectId: `views:${view.pureName}`,
|
||||||
|
columns: columns.filter(column => column.tableName === view.pureName),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
views,
|
||||||
|
tables,
|
||||||
|
triggers,
|
||||||
|
functions,
|
||||||
|
procedures,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async _computeSingleObjectId() {
|
||||||
|
const { typeField, pureName } = this.singleObjectFilter;
|
||||||
|
console.log('Computing single object ID for', typeField, pureName);
|
||||||
|
this.singleObjectId = `${typeField}:${pureName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Analyser;
|
||||||
239
plugins/dbgate-plugin-firebird/src/backend/driver.js
Normal file
239
plugins/dbgate-plugin-firebird/src/backend/driver.js
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const { splitQuery } = require('dbgate-query-splitter');
|
||||||
|
const stream = require('stream');
|
||||||
|
const driverBase = require('../frontend/driver');
|
||||||
|
const Analyser = require('./Analyser');
|
||||||
|
const Firebird = require('node-firebird');
|
||||||
|
const { normalizeRow, createFirebirdInsertStream } = require('./helpers');
|
||||||
|
const { getLogger, extractErrorLogData, createBulkInsertStreamBase } = require('dbgate-tools');
|
||||||
|
const sql = require('./sql');
|
||||||
|
|
||||||
|
const logger = getLogger('firebird');
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').EngineDriver<Firebird.Database>} */
|
||||||
|
const driver = {
|
||||||
|
...driverBase,
|
||||||
|
analyserClass: Analyser,
|
||||||
|
async connect({ port, user, password, server, databaseFile }) {
|
||||||
|
const options = {
|
||||||
|
host: server,
|
||||||
|
port,
|
||||||
|
database: databaseFile,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**@type {Firebird.Database} */
|
||||||
|
const db = await new Promise((resolve, reject) => {
|
||||||
|
Firebird.attachOrCreate(options, (err, db) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(db);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
client: db,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async query(dbhan, sql, { discardResult } = {}) {
|
||||||
|
const res = await new Promise((resolve, reject) => {
|
||||||
|
dbhan.client.query(sql, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
console.error(err);
|
||||||
|
console.error('Executing query:', sql);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (discardResult) {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
columns: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = res?.[0] ? Object.keys(res[0]).map(i => ({ columnName: i })) : [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: res ? await Promise.all(res.map(normalizeRow)) : [],
|
||||||
|
columns,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async stream(dbhan, sql, options) {
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
let hasSentColumns = false;
|
||||||
|
dbhan.client.sequentially(
|
||||||
|
sql,
|
||||||
|
[],
|
||||||
|
(row, index) => {
|
||||||
|
if (!hasSentColumns) {
|
||||||
|
hasSentColumns = true;
|
||||||
|
const columns = Object.keys(row).map(i => ({ columnName: i }));
|
||||||
|
options.recordset(columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.row(row);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
options.done();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(extractErrorLogData(err), 'Stream error');
|
||||||
|
options.info({
|
||||||
|
message: err.message,
|
||||||
|
line: err.line,
|
||||||
|
// procedure: procName,
|
||||||
|
time: new Date(),
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
options.done();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async script(dbhan, sql, { useTransaction } = {}) {
|
||||||
|
if (useTransaction) return this.runSqlInTransaction(dbhan, sql);
|
||||||
|
|
||||||
|
const sqlItems = splitQuery(sql, driver.sqlSplitterOptions);
|
||||||
|
for (const sqlItem of sqlItems) {
|
||||||
|
await this.query(dbhan, sqlItem, { discardResult: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async readQuery(dbhan, sql, structure) {
|
||||||
|
const pass = new stream.PassThrough({
|
||||||
|
objectMode: true,
|
||||||
|
highWaterMark: 100,
|
||||||
|
});
|
||||||
|
let hasSentColumns = false;
|
||||||
|
|
||||||
|
dbhan.client.sequentially(
|
||||||
|
sql,
|
||||||
|
[],
|
||||||
|
(row, index) => {
|
||||||
|
if (!hasSentColumns) {
|
||||||
|
hasSentColumns = true;
|
||||||
|
|
||||||
|
const columns = Object.keys(row).map(i => ({ columnName: i }));
|
||||||
|
|
||||||
|
pass.write({
|
||||||
|
__isStreamHeader: true,
|
||||||
|
...(structure || { columns }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.write(row);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
pass.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return pass;
|
||||||
|
},
|
||||||
|
|
||||||
|
async writeTable(dbhan, name, options) {
|
||||||
|
return createFirebirdInsertStream(this, stream, dbhan, name, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getVersion(dbhan) {
|
||||||
|
const res = await this.query(dbhan, sql.version);
|
||||||
|
const version = res.rows?.[0]?.VERSION;
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
versionText: `Firebird ${version}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async close(dbhan) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
dbhan.client.detach(err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('dbgate-types').DatabaseHandle<Firebird.Database>} dbhan
|
||||||
|
* @param {string} sql
|
||||||
|
*/
|
||||||
|
async runSqlInTransaction(dbhan, sql) {
|
||||||
|
/** @type {Firebird.Transaction} */
|
||||||
|
let transactionPromise;
|
||||||
|
const sqlItems = splitQuery(sql, driver.sqlSplitterOptions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transactionPromise = await new Promise((resolve, reject) => {
|
||||||
|
dbhan.client.transaction(Firebird.ISOLATION_SNAPSHOT, function (err, currentTransaction) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
resolve(currentTransaction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < sqlItems.length; i++) {
|
||||||
|
const currentSql = sqlItems[i];
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
transactionPromise.query(currentSql, function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
logger.error(extractErrorLogData(err), 'Error executing SQL in transaction');
|
||||||
|
logger.error({ sql: currentSql }, 'SQL that caused the error');
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
transactionPromise.commit(function (err) {
|
||||||
|
if (err) {
|
||||||
|
logger.error(extractErrorLogData(err), 'Error committing transaction');
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(extractErrorLogData(error), 'Transaction error');
|
||||||
|
if (transactionPromise) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
transactionPromise.rollback(function (rollbackErr) {
|
||||||
|
if (rollbackErr) {
|
||||||
|
logger.error(extractErrorLogData(rollbackErr), 'Error rolling back transaction');
|
||||||
|
return reject(rollbackErr); // Re-reject the rollback error
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionPromise;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = driver;
|
||||||
169
plugins/dbgate-plugin-firebird/src/backend/helpers.js
Normal file
169
plugins/dbgate-plugin-firebird/src/backend/helpers.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
const { createBulkInsertStreamBase } = require('dbgate-tools');
|
||||||
|
|
||||||
|
function getDataTypeString({ dataTypeCode, scale, length, precision }) {
|
||||||
|
switch (dataTypeCode) {
|
||||||
|
case 7:
|
||||||
|
return 'smallint';
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
return 'integer';
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
return 'bigint';
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
return 'float';
|
||||||
|
|
||||||
|
case 11:
|
||||||
|
return 'DOUBLE precision';
|
||||||
|
|
||||||
|
case 12:
|
||||||
|
return 'date';
|
||||||
|
|
||||||
|
case 13:
|
||||||
|
return 'time';
|
||||||
|
|
||||||
|
case 14:
|
||||||
|
return `char(${length})`;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
return `decimal(${precision}, ${scale})`;
|
||||||
|
|
||||||
|
case 27:
|
||||||
|
return 'double precision';
|
||||||
|
|
||||||
|
case 35:
|
||||||
|
return 'blob';
|
||||||
|
|
||||||
|
case 37:
|
||||||
|
return `varchar(${length})`;
|
||||||
|
|
||||||
|
case 261:
|
||||||
|
return 'cstring';
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (dataTypeCode === null || dataTypeCode === undefined) return 'UNKNOWN';
|
||||||
|
return `unknown (${dataTypeCode})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventMap = {
|
||||||
|
1: { triggerTiming: 'BEFORE', eventType: 'INSERT' },
|
||||||
|
2: { triggerTiming: 'AFTER', eventType: 'INSERT' },
|
||||||
|
3: { triggerTiming: 'BEFORE', eventType: 'UPDATE' },
|
||||||
|
4: { triggerTiming: 'AFTER', eventType: 'UPDATE' },
|
||||||
|
5: { triggerTiming: 'BEFORE', eventType: 'DELETE' },
|
||||||
|
6: { triggerTiming: 'AFTER', eventType: 'DELETE' },
|
||||||
|
17: { triggerTiming: 'BEFORE', eventType: 'INSERT' }, // OR UPDATE
|
||||||
|
18: { triggerTiming: 'AFTER', eventType: 'INSERT' }, // OR UPDATE
|
||||||
|
25: { triggerTiming: 'BEFORE', eventType: 'INSERT' }, // OR DELETE
|
||||||
|
26: { triggerTiming: 'AFTER', eventType: 'INSERT' }, // OR DELETE
|
||||||
|
27: { triggerTiming: 'BEFORE', eventType: 'UPDATE' }, // OR DELETE
|
||||||
|
28: { triggerTiming: 'AFTER', eventType: 'UPDATE' }, // OR DELETE
|
||||||
|
113: { triggerTiming: 'BEFORE', eventType: 'INSERT' }, // OR UPDATE OR DELETE
|
||||||
|
114: { triggerTiming: 'AFTER', eventType: 'INSERT' }, // OR UPDATE OR DELETE
|
||||||
|
8192: { triggerTiming: 'BEFORE EVENT', eventType: null }, // ON CONNECT
|
||||||
|
8193: { triggerTiming: 'AFTER EVENT', eventType: null }, // ON DISCONNECT
|
||||||
|
8194: { triggerTiming: 'BEFORE STATEMENT', eventType: null }, // ON TRANSACTION START
|
||||||
|
8195: { triggerTiming: 'AFTER STATEMENT', eventType: null }, // ON TRANSACTION COMMIT
|
||||||
|
8196: { triggerTiming: 'AFTER STATEMENT', eventType: null }, // ON TRANSACTION ROLLBACK
|
||||||
|
};
|
||||||
|
|
||||||
|
function getTriggerEventType(triggerType) {
|
||||||
|
return eventMap[triggerType]?.eventType || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTriggerCreateSql(triggerResult) {
|
||||||
|
const eventType = getTriggerEventType(triggerResult.TRIGGERTYPE);
|
||||||
|
const triggerTiming = getTriggerTiming(triggerResult.TRIGGERTYPE);
|
||||||
|
const body = triggerResult.TRIGGER_BODY_SQL;
|
||||||
|
|
||||||
|
const createSql = `CREATE OR ALTER TRIGGER "${triggerResult.pureName}" ${triggerTiming} ${eventType} ON "${triggerResult.tableName}" ${body};`;
|
||||||
|
return createSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTriggerTiming(triggerType) {
|
||||||
|
return eventMap[triggerType]?.triggerTiming || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormattedDefaultValue(defaultValue) {
|
||||||
|
if (defaultValue === null) return null;
|
||||||
|
|
||||||
|
return defaultValue.replace(/^default\s*/i, '');
|
||||||
|
}
|
||||||
|
function blobStreamToString(stream, encoding = 'utf8') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunks = [];
|
||||||
|
stream.on('data', chunk => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
stream.on('end', () => {
|
||||||
|
resolve(Buffer.concat(chunks).toString(encoding));
|
||||||
|
});
|
||||||
|
stream.on('error', err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function normalizeRow(row) {
|
||||||
|
const entries = await Promise.all(
|
||||||
|
Object.entries(row).map(async ([key, value]) => {
|
||||||
|
if (value === null || value === undefined) return [key, null];
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
value(async (_err, _name, eventEmitter) => {
|
||||||
|
try {
|
||||||
|
const stringValue = await blobStreamToString(eventEmitter, 'utf8');
|
||||||
|
resolve(stringValue);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [key, result];
|
||||||
|
}
|
||||||
|
return [key, value];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return Object.fromEntries(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformRow(row) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(row).map(([key, value]) => {
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(value)) {
|
||||||
|
return [key, value.replace('T', ' ')];
|
||||||
|
}
|
||||||
|
return [key, value];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFirebirdInsertStream(driver, stream, dbhan, name, options) {
|
||||||
|
const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options);
|
||||||
|
|
||||||
|
writable.addRow = async row => {
|
||||||
|
const transformedRow = transformRow(row);
|
||||||
|
|
||||||
|
if (writable.structure) {
|
||||||
|
writable.buffer.push(transformedRow);
|
||||||
|
} else {
|
||||||
|
writable.structure = transformedRow;
|
||||||
|
await writable.checkStructure();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getDataTypeString,
|
||||||
|
getTriggerEventType,
|
||||||
|
getTriggerTiming,
|
||||||
|
getFormattedDefaultValue,
|
||||||
|
getTriggerCreateSql,
|
||||||
|
blobStreamToString,
|
||||||
|
normalizeRow,
|
||||||
|
createFirebirdInsertStream,
|
||||||
|
};
|
||||||
7
plugins/dbgate-plugin-firebird/src/backend/index.js
Normal file
7
plugins/dbgate-plugin-firebird/src/backend/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const driver = require('./driver');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
packageName: 'dbgate-plugin-firebird',
|
||||||
|
drivers: [driver],
|
||||||
|
initialize(dbgateEnv) {},
|
||||||
|
};
|
||||||
44
plugins/dbgate-plugin-firebird/src/backend/sql/columns.js
Normal file
44
plugins/dbgate-plugin-firebird/src/backend/sql/columns.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT DISTINCT
|
||||||
|
CAST(TRIM(rf.rdb$relation_name) AS VARCHAR(255)) AS "tableName",
|
||||||
|
CAST(TRIM(rf.rdb$field_name) AS VARCHAR(255)) AS "columnName",
|
||||||
|
CASE rf.rdb$null_flag WHEN 1 THEN TRUE ELSE FALSE END AS "notNull",
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM rdb$relation_constraints rc
|
||||||
|
JOIN rdb$index_segments idx ON rc.rdb$index_name = idx.rdb$index_name
|
||||||
|
WHERE rc.rdb$relation_name = rf.rdb$relation_name
|
||||||
|
AND idx.rdb$field_name = rf.rdb$field_name
|
||||||
|
AND rc.rdb$constraint_type = 'PRIMARY KEY'
|
||||||
|
) THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS "isPrimaryKey",
|
||||||
|
f.rdb$field_type AS "dataTypeCode",
|
||||||
|
f.rdb$field_precision AS "precision",
|
||||||
|
f.rdb$field_scale AS "scale",
|
||||||
|
f.rdb$field_length / 4 AS "length",
|
||||||
|
CAST(TRIM(rf.RDB$DEFAULT_SOURCE) AS VARCHAR(255)) AS "defaultValue",
|
||||||
|
CAST(TRIM(rf.rdb$description) AS VARCHAR(255)) AS "columnComment",
|
||||||
|
CASE
|
||||||
|
WHEN f.rdb$field_type IN (8, 9, 16) AND f.rdb$field_scale < 0 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS "isUnsigned",
|
||||||
|
CAST(TRIM(rf.rdb$relation_name) AS VARCHAR(255)) AS "pureName"
|
||||||
|
FROM
|
||||||
|
rdb$relation_fields rf
|
||||||
|
JOIN
|
||||||
|
rdb$relations r ON rf.rdb$relation_name = r.rdb$relation_name
|
||||||
|
LEFT JOIN
|
||||||
|
rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||||
|
LEFT JOIN
|
||||||
|
rdb$character_sets cs ON f.rdb$character_set_id = cs.rdb$character_set_id
|
||||||
|
LEFT JOIN
|
||||||
|
rdb$collations co ON f.rdb$collation_id = co.rdb$collation_id
|
||||||
|
WHERE
|
||||||
|
r.rdb$system_flag = 0
|
||||||
|
AND
|
||||||
|
('tables:' || CAST(TRIM(rf.rdb$relation_name) AS VARCHAR(255))) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"tableName", rf.rdb$field_position;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(rc_fk.RDB$RELATION_NAME) AS "pureName",
|
||||||
|
TRIM(rc_fk.RDB$CONSTRAINT_NAME) AS "constraintName",
|
||||||
|
TRIM(iseg_fk.RDB$FIELD_NAME) AS "columnName",
|
||||||
|
TRIM(iseg_pk.RDB$FIELD_NAME) AS "refColumnName",
|
||||||
|
TRIM(rc_pk.RDB$RELATION_NAME) AS "refTableName",
|
||||||
|
FALSE AS "isIncludedColumn",
|
||||||
|
CASE COALESCE(idx_fk.RDB$INDEX_TYPE, 0)
|
||||||
|
WHEN 1 THEN TRUE -- For the FK's own index, 1 = Descending (modern Firebird)
|
||||||
|
ELSE FALSE -- 0 or NULL = Ascending for the FK's own index
|
||||||
|
END AS "isDescending" -- Refers to the sort order of the index on the FK column(s)
|
||||||
|
FROM
|
||||||
|
RDB$RELATION_CONSTRAINTS rc_fk
|
||||||
|
JOIN
|
||||||
|
RDB$RELATIONS rel ON rc_fk.RDB$RELATION_NAME = rel.RDB$RELATION_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDEX_SEGMENTS iseg_fk ON rc_fk.RDB$INDEX_NAME = iseg_fk.RDB$INDEX_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDICES idx_fk ON rc_fk.RDB$INDEX_NAME = idx_fk.RDB$INDEX_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$REF_CONSTRAINTS refc ON rc_fk.RDB$CONSTRAINT_NAME = refc.RDB$CONSTRAINT_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$RELATION_CONSTRAINTS rc_pk ON refc.RDB$CONST_NAME_UQ = rc_pk.RDB$CONSTRAINT_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDEX_SEGMENTS iseg_pk ON rc_pk.RDB$INDEX_NAME = iseg_pk.RDB$INDEX_NAME
|
||||||
|
AND iseg_fk.RDB$FIELD_POSITION = iseg_pk.RDB$FIELD_POSITION -- Critical for matching columns in composite keys
|
||||||
|
WHERE
|
||||||
|
rc_fk.RDB$CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||||
|
AND
|
||||||
|
('tables:' || TRIM(rc_fk.RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName",
|
||||||
|
"constraintName",
|
||||||
|
iseg_fk.RDB$FIELD_POSITION;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(FA.RDB$FUNCTION_NAME) AS "owningObjectName", -- Name of the function this parameter belongs to
|
||||||
|
TRIM(FA.RDB$ARGUMENT_NAME) AS "parameterName",
|
||||||
|
FFLDS.RDB$FIELD_TYPE AS "dataTypeCode", -- SQL data type code from RDB$FIELDS
|
||||||
|
FFLDS.rdb$field_precision AS "precision",
|
||||||
|
FFLDS.rdb$field_scale AS "scale",
|
||||||
|
FFLDS.rdb$field_length AS "length",
|
||||||
|
|
||||||
|
TRIM(CASE
|
||||||
|
WHEN FA.RDB$ARGUMENT_POSITION = F.RDB$RETURN_ARGUMENT THEN 'RETURN'
|
||||||
|
ELSE 'IN' -- For PSQL functions, non-return arguments are IN.
|
||||||
|
END) AS "parameterMode",
|
||||||
|
FA.RDB$ARGUMENT_POSITION AS "position", -- 0-based index for arguments
|
||||||
|
|
||||||
|
-- Fields for ParameterInfo.NamedObjectInfo
|
||||||
|
TRIM(FA.RDB$FUNCTION_NAME) AS "pureName" -- NamedObjectInfo.pureName for the parameter
|
||||||
|
|
||||||
|
FROM
|
||||||
|
RDB$FUNCTION_ARGUMENTS FA
|
||||||
|
JOIN
|
||||||
|
RDB$FUNCTIONS F ON FA.RDB$FUNCTION_NAME = F.RDB$FUNCTION_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$FIELDS FFLDS ON FA.RDB$FIELD_SOURCE = FFLDS.RDB$FIELD_NAME -- Crucial join to get RDB$FIELDS.RDB$TYPE
|
||||||
|
WHERE
|
||||||
|
COALESCE(F.RDB$SYSTEM_FLAG, 0) = 0 -- Filter for user-defined functions
|
||||||
|
ORDER BY
|
||||||
|
"owningObjectName", "position";
|
||||||
|
`;
|
||||||
16
plugins/dbgate-plugin-firebird/src/backend/sql/functions.js
Normal file
16
plugins/dbgate-plugin-firebird/src/backend/sql/functions.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(F.RDB$FUNCTION_NAME) AS "pureName",
|
||||||
|
TRIM(F.RDB$FUNCTION_NAME) AS "objectId",
|
||||||
|
TRIM('FUNCTION') AS "objectTypeField",
|
||||||
|
TRIM(F.RDB$DESCRIPTION) AS "objectComment",
|
||||||
|
F.RDB$FUNCTION_SOURCE AS "createSql", -- This is the PSQL body or definition for UDRs
|
||||||
|
FALSE AS "requiresFormat" -- Assuming PSQL source is generally readable
|
||||||
|
FROM
|
||||||
|
RDB$FUNCTIONS F
|
||||||
|
WHERE
|
||||||
|
COALESCE(F.RDB$SYSTEM_FLAG, 0) = 0 -- User-defined functions
|
||||||
|
AND ('funcitons:' || TRIM(F.RDB$FUNCTION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName";
|
||||||
|
`;
|
||||||
29
plugins/dbgate-plugin-firebird/src/backend/sql/index.js
Normal file
29
plugins/dbgate-plugin-firebird/src/backend/sql/index.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const version = require('./version');
|
||||||
|
const tables = require('./tables');
|
||||||
|
const columns = require('./columns');
|
||||||
|
const triggers = require('./triggers');
|
||||||
|
const primaryKeys = require('./primaryKeys');
|
||||||
|
const foreignKeys = require('./foreignKeys');
|
||||||
|
const functions = require('./functions');
|
||||||
|
const functionParameters = require('./functionParameters');
|
||||||
|
const procedures = require('./procedures');
|
||||||
|
const procedureParameters = require('./procedureParameters');
|
||||||
|
const views = require('./views');
|
||||||
|
const uniques = require('./uniques');
|
||||||
|
const indexes = require('./indexes');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version,
|
||||||
|
columns,
|
||||||
|
views,
|
||||||
|
tables,
|
||||||
|
triggers,
|
||||||
|
primaryKeys,
|
||||||
|
foreignKeys,
|
||||||
|
functions,
|
||||||
|
functionParameters,
|
||||||
|
procedures,
|
||||||
|
procedureParameters,
|
||||||
|
uniques,
|
||||||
|
indexes,
|
||||||
|
};
|
||||||
42
plugins/dbgate-plugin-firebird/src/backend/sql/indexes.js
Normal file
42
plugins/dbgate-plugin-firebird/src/backend/sql/indexes.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT -- Index name, maps to pureName
|
||||||
|
TRIM(I.RDB$INDEX_NAME) AS "constraintName", -- Index name, maps to constraintName
|
||||||
|
TRIM('index') AS "constraintType", -- ConstraintType for IndexInfo
|
||||||
|
TRIM(I.RDB$RELATION_NAME) AS "pureName", -- Context: Table the index is on
|
||||||
|
CASE COALESCE(I.RDB$UNIQUE_FLAG, 0) -- isUnique: 1 for unique, 0 or NULL for non-unique [cite: 46, 838]
|
||||||
|
WHEN 1 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS "isUnique",
|
||||||
|
CASE
|
||||||
|
WHEN I.RDB$EXPRESSION_SOURCE IS NOT NULL THEN TRIM('expression') -- indexType: if an expression index [cite: 46, 262]
|
||||||
|
ELSE TRIM('normal')
|
||||||
|
END AS "indexType",
|
||||||
|
I.RDB$CONDITION_SOURCE AS "idx_filterDefinition", -- filterDefinition: for partial indexes [cite: 46, 261, 838]
|
||||||
|
COALESCE(I.RDB$INDEX_INACTIVE, 0) AS "idx_isInactive", -- 0 for active, 1 for inactive [cite: 46, 838]
|
||||||
|
I.RDB$DESCRIPTION AS "idx_description", -- Index description/comment [cite: 46, 838]
|
||||||
|
|
||||||
|
-- Column specific fields from RDB$INDEX_SEGMENTS
|
||||||
|
TRIM(S.RDB$FIELD_NAME) AS "columnName", -- columnName for ColumnReference [cite: 46, 837]
|
||||||
|
CASE COALESCE(I.RDB$INDEX_TYPE, 0) -- isDescending: 0 for ASC (default), 1 for DESC for the whole index [cite: 46, 838]
|
||||||
|
WHEN 1 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS "isDescending",
|
||||||
|
S.RDB$FIELD_POSITION AS "col_fieldPosition" -- 0-based position of the column in the index [cite: 46, 837]
|
||||||
|
FROM
|
||||||
|
RDB$INDICES I
|
||||||
|
JOIN
|
||||||
|
RDB$INDEX_SEGMENTS S ON I.RDB$INDEX_NAME = S.RDB$INDEX_NAME
|
||||||
|
WHERE
|
||||||
|
COALESCE(I.RDB$SYSTEM_FLAG, 0) = 0 -- Filter for user-defined indexes [cite: 46, 838]
|
||||||
|
AND I.RDB$FOREIGN_KEY IS NULL -- Exclude indexes backing foreign keys [cite: 46, 838]
|
||||||
|
-- (RDB$FOREIGN_KEY is not null if the index is for an FK)
|
||||||
|
AND NOT EXISTS ( -- Exclude indexes that are the chosen supporting index for a PK or UQ constraint
|
||||||
|
SELECT 1
|
||||||
|
FROM RDB$RELATION_CONSTRAINTS rc
|
||||||
|
WHERE rc.RDB$INDEX_NAME = I.RDB$INDEX_NAME
|
||||||
|
AND rc.RDB$CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE')
|
||||||
|
)
|
||||||
|
|
||||||
|
AND
|
||||||
|
('tables:' || TRIM(i.RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
`;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(rc.RDB$RELATION_NAME) AS "pureName",
|
||||||
|
TRIM(rc.RDB$CONSTRAINT_NAME) AS "constraintName",
|
||||||
|
TRIM(iseg.RDB$FIELD_NAME) AS "columnName",
|
||||||
|
CAST(NULL AS VARCHAR(63)) AS "refColumnName",
|
||||||
|
FALSE AS "isIncludedColumn",
|
||||||
|
CASE COALESCE(idx.RDB$INDEX_TYPE, 0) -- Treat NULL as 0 (ascending)
|
||||||
|
WHEN 1 THEN TRUE -- Assuming 1 means DESCENDING for regular (non-expression) indexes
|
||||||
|
ELSE FALSE -- Assuming 0 (or NULL) means ASCENDING for regular indexes
|
||||||
|
END AS "isDescending"
|
||||||
|
FROM
|
||||||
|
RDB$RELATION_CONSTRAINTS rc
|
||||||
|
JOIN
|
||||||
|
RDB$RELATIONS rel ON rc.RDB$RELATION_NAME = rel.RDB$RELATION_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDICES idx ON rc.RDB$INDEX_NAME = idx.RDB$INDEX_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDEX_SEGMENTS iseg ON idx.RDB$INDEX_NAME = iseg.RDB$INDEX_NAME
|
||||||
|
WHERE
|
||||||
|
rc.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||||
|
AND COALESCE(rel.RDB$SYSTEM_FLAG, 0) = 0 -- Typically, you only want user-defined tables
|
||||||
|
AND ('tables:' || TRIM(rc.RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName",
|
||||||
|
"constraintName",
|
||||||
|
iseg.RDB$FIELD_POSITION;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(PP.RDB$PROCEDURE_NAME) AS "owningObjectName", -- Name of the procedure this parameter belongs to
|
||||||
|
TRIM(PP.RDB$PARAMETER_NAME) AS "parameterName", -- ParameterInfo.parameterName
|
||||||
|
FFLDS.RDB$FIELD_TYPE AS "dataTypeCode", -- SQL data type code from RDB$FIELDS
|
||||||
|
FFLDS.rdb$field_precision AS "precision",
|
||||||
|
FFLDS.rdb$field_scale AS "scale",
|
||||||
|
FFLDS.rdb$field_length AS "length",
|
||||||
|
|
||||||
|
CASE PP.RDB$PARAMETER_TYPE
|
||||||
|
WHEN 0 THEN 'IN'
|
||||||
|
WHEN 1 THEN 'OUT'
|
||||||
|
ELSE CAST(PP.RDB$PARAMETER_TYPE AS VARCHAR(10)) -- Should ideally not happen for valid params
|
||||||
|
END AS "parameterMode",
|
||||||
|
PP.RDB$PARAMETER_NUMBER AS "position", -- 0-based for IN params, then 0-based for OUT params
|
||||||
|
|
||||||
|
-- Fields for ParameterInfo.NamedObjectInfo
|
||||||
|
TRIM(PP.RDB$PARAMETER_NAME) AS "pureName" -- NamedObjectInfo.pureName for the parameter
|
||||||
|
|
||||||
|
FROM
|
||||||
|
RDB$PROCEDURE_PARAMETERS PP
|
||||||
|
JOIN
|
||||||
|
RDB$PROCEDURES P ON PP.RDB$PROCEDURE_NAME = P.RDB$PROCEDURE_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$FIELDS FFLDS ON PP.RDB$FIELD_SOURCE = FFLDS.RDB$FIELD_NAME -- Links parameter to its base field type
|
||||||
|
WHERE
|
||||||
|
COALESCE(P.RDB$SYSTEM_FLAG, 0) = 0 -- Filter for user-defined procedures
|
||||||
|
ORDER BY
|
||||||
|
"owningObjectName", PP.RDB$PARAMETER_TYPE, "position"; -- Order by IN(0)/OUT(1) then by position
|
||||||
|
`;
|
||||||
16
plugins/dbgate-plugin-firebird/src/backend/sql/procedures.js
Normal file
16
plugins/dbgate-plugin-firebird/src/backend/sql/procedures.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(P.RDB$PROCEDURE_NAME) AS "pureName",
|
||||||
|
TRIM('PROCEDURE') AS "objectTypeField",
|
||||||
|
TRIM(P.RDB$DESCRIPTION) AS "objectComment",
|
||||||
|
P.RDB$PROCEDURE_SOURCE AS "createSql", -- Contains the PSQL body
|
||||||
|
FALSE AS "requiresFormat"
|
||||||
|
FROM
|
||||||
|
RDB$PROCEDURES P
|
||||||
|
WHERE
|
||||||
|
COALESCE(P.RDB$SYSTEM_FLAG, 0) = 0 -- Filter for user-defined procedures
|
||||||
|
AND P.RDB$PROCEDURE_TYPE IS NOT NULL -- Ensure it's a valid procedure type (0, 1, or 2)
|
||||||
|
AND ('procedures:' || TRIM(P.RDB$PROCEDURE_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName";
|
||||||
|
`;
|
||||||
14
plugins/dbgate-plugin-firebird/src/backend/sql/tables.js
Normal file
14
plugins/dbgate-plugin-firebird/src/backend/sql/tables.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = `SELECT
|
||||||
|
TRIM(RDB$RELATION_NAME) AS "pureName",
|
||||||
|
RDB$DESCRIPTION AS "objectComment",
|
||||||
|
RDB$FORMAT AS "objectTypeField"
|
||||||
|
FROM
|
||||||
|
RDB$RELATIONS
|
||||||
|
WHERE
|
||||||
|
RDB$SYSTEM_FLAG = 0 -- only user-defined tables
|
||||||
|
AND
|
||||||
|
RDB$RELATION_TYPE = 0 -- only tables (not views, etc.)
|
||||||
|
AND
|
||||||
|
('tables:' || TRIM(RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName";`;
|
||||||
13
plugins/dbgate-plugin-firebird/src/backend/sql/triggers.js
Normal file
13
plugins/dbgate-plugin-firebird/src/backend/sql/triggers.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(rtr.RDB$TRIGGER_NAME) as "pureName",
|
||||||
|
TRIM(rtr.RDB$RELATION_NAME) as "tableName",
|
||||||
|
rtr.RDB$TRIGGER_TYPE as TRIGGERTYPE,
|
||||||
|
CAST(rtr.RDB$TRIGGER_SOURCE AS VARCHAR(8191)) AS TRIGGER_BODY_SQL
|
||||||
|
FROM
|
||||||
|
RDB$TRIGGERS rtr
|
||||||
|
JOIN RDB$RELATIONS rel ON rtr.RDB$RELATION_NAME = rel.RDB$RELATION_NAME
|
||||||
|
WHERE rtr.RDB$SYSTEM_FLAG = 0
|
||||||
|
AND ('triggers:' || TRIM(rtr.RDB$TRIGGER_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY rtr.RDB$TRIGGER_NAME
|
||||||
|
`;
|
||||||
25
plugins/dbgate-plugin-firebird/src/backend/sql/uniques.js
Normal file
25
plugins/dbgate-plugin-firebird/src/backend/sql/uniques.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = `
|
||||||
|
SELECT
|
||||||
|
TRIM(rc.RDB$CONSTRAINT_NAME) AS "constraintName", -- Name of the constraint
|
||||||
|
TRIM('unique') AS "constraintType", -- Type of the constraint
|
||||||
|
TRIM(rc.RDB$RELATION_NAME) AS "pureName", -- Context: Table the constraint is on
|
||||||
|
|
||||||
|
-- Column specific fields from RDB$INDEX_SEGMENTS for the backing index
|
||||||
|
TRIM(s.RDB$FIELD_NAME) AS "columnName", -- Name of the column in the unique key
|
||||||
|
CASE COALESCE(i.RDB$INDEX_TYPE, 0) -- isDescending: 0 for ASC (default), 1 for DESC for the backing index
|
||||||
|
WHEN 1 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END AS "isDescending"
|
||||||
|
FROM
|
||||||
|
RDB$RELATION_CONSTRAINTS rc
|
||||||
|
JOIN
|
||||||
|
-- RDB$INDEX_NAME in RDB$RELATION_CONSTRAINTS is the name of the index that enforces the UNIQUE constraint
|
||||||
|
RDB$INDICES i ON rc.RDB$INDEX_NAME = i.RDB$INDEX_NAME
|
||||||
|
JOIN
|
||||||
|
RDB$INDEX_SEGMENTS s ON i.RDB$INDEX_NAME = s.RDB$INDEX_NAME
|
||||||
|
WHERE
|
||||||
|
rc.RDB$CONSTRAINT_TYPE = 'UNIQUE' -- Filter for UNIQUE constraints
|
||||||
|
AND COALESCE(i.RDB$SYSTEM_FLAG, 0) = 0 -- Typically, backing indexes for user UQ constraints are user-related.
|
||||||
|
AND
|
||||||
|
('tables:' || TRIM(rc.RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
`;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = `SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version from rdb$database;`;
|
||||||
14
plugins/dbgate-plugin-firebird/src/backend/sql/views.js
Normal file
14
plugins/dbgate-plugin-firebird/src/backend/sql/views.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = `SELECT
|
||||||
|
TRIM(RDB$RELATION_NAME) AS "pureName",
|
||||||
|
RDB$DESCRIPTION AS "objectComment",
|
||||||
|
RDB$FORMAT AS "objectTypeField"
|
||||||
|
FROM
|
||||||
|
RDB$RELATIONS
|
||||||
|
WHERE
|
||||||
|
RDB$SYSTEM_FLAG = 0 -- only user-defined tables
|
||||||
|
AND
|
||||||
|
RDB$RELATION_TYPE = 1 -- only views (not tables, etc.)
|
||||||
|
AND
|
||||||
|
('tables:' || TRIM(RDB$RELATION_NAME)) =OBJECT_ID_CONDITION
|
||||||
|
ORDER BY
|
||||||
|
"pureName";`;
|
||||||
69
plugins/dbgate-plugin-firebird/src/frontend/Dumper.js
Normal file
69
plugins/dbgate-plugin-firebird/src/frontend/Dumper.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const { SqlDumper } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||||
|
|
||||||
|
class Dumper extends SqlDumper {
|
||||||
|
autoIncrement() {
|
||||||
|
this.put(' ^generated ^by ^default ^as ^identity');
|
||||||
|
}
|
||||||
|
|
||||||
|
dropColumn(column) {
|
||||||
|
this.putCmd('^alter ^table %f ^drop %i', column, column.columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
renameColumn(column, newName) {
|
||||||
|
this.putCmd('^alter ^table %f ^alter ^column %i ^to %i', column, column.columnName, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeColumn(oldcol, newcol, constraints) {
|
||||||
|
if (oldcol.columnName != newcol.columnName) {
|
||||||
|
this.putCmd('^alter ^table %f ^alter ^column %i ^to %i', oldcol, oldcol.columnName, newcol.columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldcol.notNull != newcol.notNull) {
|
||||||
|
if (newcol.notNull) {
|
||||||
|
this.putCmd('^alter ^table %f ^alter ^column %i ^set ^not ^null', newcol, newcol.columnName);
|
||||||
|
} else {
|
||||||
|
this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^not ^null', newcol, newcol.columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldcol.defaultValue != newcol.defaultValue) {
|
||||||
|
if (newcol.defaultValue) {
|
||||||
|
this.putCmd(
|
||||||
|
'^alter ^table %f ^alter ^column %i ^set ^default %s',
|
||||||
|
newcol,
|
||||||
|
newcol.columnName,
|
||||||
|
newcol.defaultValue
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^default', newcol, newcol.columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginTransaction() {
|
||||||
|
this.putCmd('^set ^transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
createIndex(ix) {
|
||||||
|
const firstCol = ix.columns[0];
|
||||||
|
this.put('^create');
|
||||||
|
if (ix.isUnique) this.put(' ^unique');
|
||||||
|
this.put(
|
||||||
|
' %k ^index %i &n^on %f (&>&n',
|
||||||
|
firstCol.isDescending == true ? 'DESCENDING' : 'ASCENDING',
|
||||||
|
ix.constraintName,
|
||||||
|
ix
|
||||||
|
);
|
||||||
|
|
||||||
|
this.putCollection(',&n', ix.columns, col => {
|
||||||
|
this.put('%i', col.columnName);
|
||||||
|
});
|
||||||
|
this.put('&<&n)');
|
||||||
|
if (ix.filterDefinition && this.dialect.filteredIndexes) {
|
||||||
|
this.put('&n^where %s', ix.filterDefinition);
|
||||||
|
}
|
||||||
|
this.endCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Dumper;
|
||||||
105
plugins/dbgate-plugin-firebird/src/frontend/driver.js
Normal file
105
plugins/dbgate-plugin-firebird/src/frontend/driver.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||||
|
const Dumper = require('./Dumper');
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').SqlDialect} */
|
||||||
|
const dialect = {
|
||||||
|
rangeSelect: true,
|
||||||
|
ilike: true,
|
||||||
|
multipleSchema: false,
|
||||||
|
stringEscapeChar: "'",
|
||||||
|
fallbackDataType: 'varchar(256)',
|
||||||
|
anonymousPrimaryKey: false,
|
||||||
|
enableConstraintsPerTable: true,
|
||||||
|
stringAgg: true,
|
||||||
|
offsetFirstSkipSyntax: true,
|
||||||
|
dropColumnDependencies: ['dependencies', 'primaryKeys', 'foreignKeys', 'indexes', 'uniques'],
|
||||||
|
changeColumnDependencies: ['dependencies', 'primaryKeys', 'indexes', 'uniques'],
|
||||||
|
renameColumnDependencies: ['dependencies', 'foreignKeys', 'uniques'],
|
||||||
|
defaultValueBeforeNullability: true,
|
||||||
|
|
||||||
|
quoteIdentifier(s) {
|
||||||
|
return `"${s}"`;
|
||||||
|
},
|
||||||
|
|
||||||
|
dbFileExtension: '.fdb',
|
||||||
|
|
||||||
|
implicitNullDeclaration: true,
|
||||||
|
createColumn: true,
|
||||||
|
dropColumn: true,
|
||||||
|
changeColumn: true,
|
||||||
|
createIndex: true,
|
||||||
|
dropIndex: true,
|
||||||
|
createForeignKey: true,
|
||||||
|
dropForeignKey: true,
|
||||||
|
createPrimaryKey: true,
|
||||||
|
dropPrimaryKey: true,
|
||||||
|
createUnique: true,
|
||||||
|
dropUnique: true,
|
||||||
|
createCheck: true,
|
||||||
|
dropCheck: true,
|
||||||
|
allowMultipleValuesInsert: false,
|
||||||
|
renameSqlObject: true,
|
||||||
|
filteredIndexes: true,
|
||||||
|
disableRenameTable: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const firebirdSplitterOptions = {
|
||||||
|
stringsBegins: ["'", '"'],
|
||||||
|
stringsEnds: {
|
||||||
|
"'": "'",
|
||||||
|
'"': '"',
|
||||||
|
},
|
||||||
|
stringEscapes: {
|
||||||
|
"'": "'", // Single quote is escaped by another single quote
|
||||||
|
'"': '"', // Double quote is escaped by another double quote
|
||||||
|
},
|
||||||
|
allowSemicolon: true,
|
||||||
|
allowCustomDelimiter: false,
|
||||||
|
allowCustomSqlTerminator: false,
|
||||||
|
allowGoDelimiter: false,
|
||||||
|
allowSlashDelimiter: false,
|
||||||
|
allowDollarDollarString: false,
|
||||||
|
noSplit: false,
|
||||||
|
doubleDashComments: true,
|
||||||
|
multilineComments: true,
|
||||||
|
javaScriptComments: false,
|
||||||
|
skipSeparatorBeginEnd: false,
|
||||||
|
ignoreComments: false,
|
||||||
|
preventSingleLineSplit: false,
|
||||||
|
adaptiveGoSplit: false,
|
||||||
|
returnRichInfo: false,
|
||||||
|
splitByLines: false,
|
||||||
|
splitByEmptyLine: false,
|
||||||
|
copyFromStdin: false,
|
||||||
|
queryParameterStyle: ':', // Firebird uses colon-prefixed parameters (:param_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').EngineDriver} */
|
||||||
|
const firebirdDriverBase = {
|
||||||
|
...driverBase,
|
||||||
|
defaultPort: 3050,
|
||||||
|
showConnectionField: field => ['port', 'user', 'password', 'server', 'databaseFile'].includes(field),
|
||||||
|
getQuerySplitterOptions: () => firebirdSplitterOptions,
|
||||||
|
beforeConnectionSave: connection => {
|
||||||
|
const { databaseFile } = connection;
|
||||||
|
return {
|
||||||
|
...connection,
|
||||||
|
singleDatabase: true,
|
||||||
|
defaultDatabase: databaseFile,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
adaptDataType(dataType) {
|
||||||
|
if (dataType?.toLowerCase() == 'datetime') return 'TIMESTAMP';
|
||||||
|
if (dataType?.toLowerCase() == 'text') return 'BLOB SUB_TYPE 1 CHARACTER SET UTF8';
|
||||||
|
return dataType;
|
||||||
|
},
|
||||||
|
|
||||||
|
engine: 'firebird@dbgate-plugin-firebird',
|
||||||
|
title: 'Firebird',
|
||||||
|
supportsTransactions: true,
|
||||||
|
dumperClass: Dumper,
|
||||||
|
dialect,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = firebirdDriverBase;
|
||||||
6
plugins/dbgate-plugin-firebird/src/frontend/index.js
Normal file
6
plugins/dbgate-plugin-firebird/src/frontend/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import driver from './driver';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
packageName: 'dbgate-plugin-firebird',
|
||||||
|
drivers: [driver],
|
||||||
|
};
|
||||||
46
plugins/dbgate-plugin-firebird/webpack-backend.config.js
Normal file
46
plugins/dbgate-plugin-firebird/webpack-backend.config.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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,
|
||||||
|
// },
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.IgnorePlugin({
|
||||||
|
checkResource(resource) {
|
||||||
|
const lazyImports = ['pg-native', 'uws'];
|
||||||
|
if (!lazyImports.includes(resource)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
require.resolve(resource);
|
||||||
|
} catch (err) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
externals,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
30
plugins/dbgate-plugin-firebird/webpack-frontend.config.js
Normal file
30
plugins/dbgate-plugin-firebird/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;
|
||||||
@@ -81,13 +81,13 @@ jobs:
|
|||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 15000:5432
|
- 15000:5432
|
||||||
|
|
||||||
mysql-integr:
|
mysql-integr:
|
||||||
image: mysql:8.0.18
|
image: mysql:8.0.18
|
||||||
env:
|
env:
|
||||||
MYSQL_ROOT_PASSWORD: Pwd2020Db
|
MYSQL_ROOT_PASSWORD: Pwd2020Db
|
||||||
ports:
|
ports:
|
||||||
- 15001:3306
|
- 15001:3306
|
||||||
|
|
||||||
mssql-integr:
|
mssql-integr:
|
||||||
@@ -96,14 +96,14 @@ jobs:
|
|||||||
ACCEPT_EULA: Y
|
ACCEPT_EULA: Y
|
||||||
SA_PASSWORD: Pwd2020Db
|
SA_PASSWORD: Pwd2020Db
|
||||||
MSSQL_PID: Express
|
MSSQL_PID: Express
|
||||||
ports:
|
ports:
|
||||||
- 15002:1433
|
- 15002:1433
|
||||||
|
|
||||||
clickhouse-integr:
|
clickhouse-integr:
|
||||||
image: bitnami/clickhouse:24.8.4
|
image: bitnami/clickhouse:24.8.4
|
||||||
env:
|
env:
|
||||||
CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db
|
CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db
|
||||||
ports:
|
ports:
|
||||||
- 15005:8123
|
- 15005:8123
|
||||||
|
|
||||||
oracle-integr:
|
oracle-integr:
|
||||||
@@ -122,3 +122,15 @@ jobs:
|
|||||||
image: ghcr.io/tursodatabase/libsql-server:latest
|
image: ghcr.io/tursodatabase/libsql-server:latest
|
||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
|
|
||||||
|
firebird:
|
||||||
|
image: firebirdsql/firebird:latest
|
||||||
|
env:
|
||||||
|
FIREBIRD_DATABASE: mydatabase.fdb
|
||||||
|
FIREBIRD_USER: dbuser
|
||||||
|
FIREBIRD_PASSWORD: dbpassword
|
||||||
|
ISC_PASSWORD: masterkey
|
||||||
|
FIREBIRD_TRACE: false
|
||||||
|
FIREBIRD_USE_LEGACY_AUTH: true
|
||||||
|
ports:
|
||||||
|
- '3050:3050'
|
||||||
|
|||||||
18
yarn.lock
18
yarn.lock
@@ -3405,6 +3405,11 @@ better-sqlite3@11.8.1:
|
|||||||
bindings "^1.5.0"
|
bindings "^1.5.0"
|
||||||
prebuild-install "^7.1.1"
|
prebuild-install "^7.1.1"
|
||||||
|
|
||||||
|
big-integer@^1.6.51:
|
||||||
|
version "1.6.52"
|
||||||
|
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
||||||
|
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
|
||||||
|
|
||||||
big.js@^5.2.2:
|
big.js@^5.2.2:
|
||||||
version "5.2.2"
|
version "5.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||||
@@ -7973,6 +7978,11 @@ long@*, long@^5.2.1, long@~5.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||||
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
||||||
|
|
||||||
|
long@^5.2.3:
|
||||||
|
version "5.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83"
|
||||||
|
integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
|
||||||
|
|
||||||
lru-cache@^10.2.0:
|
lru-cache@^10.2.0:
|
||||||
version "10.4.3"
|
version "10.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
||||||
@@ -8537,6 +8547,14 @@ node-cron@^2.0.3:
|
|||||||
opencollective-postinstall "^2.0.0"
|
opencollective-postinstall "^2.0.0"
|
||||||
tz-offset "0.0.1"
|
tz-offset "0.0.1"
|
||||||
|
|
||||||
|
node-firebird@^1.1.9:
|
||||||
|
version "1.1.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-firebird/-/node-firebird-1.1.9.tgz#0e6815b4e209812a4c85b71227e40e268bedeb8b"
|
||||||
|
integrity sha512-6Ol+Koide1WbfUp4BJ1dSA4wm091jAgCwwSoihxO/RRdcfR+dMVDE9jd2Z2ixjk7q/vSNJUYORXv7jmRfvwdrw==
|
||||||
|
dependencies:
|
||||||
|
big-integer "^1.6.51"
|
||||||
|
long "^5.2.3"
|
||||||
|
|
||||||
node-gyp@^7.1.0:
|
node-gyp@^7.1.0:
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae"
|
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae"
|
||||||
|
|||||||
Reference in New Issue
Block a user