feat: basic firebird analyser

This commit is contained in:
Nybkox
2025-05-06 15:52:15 +02:00
parent bac8bd0006
commit 839ec9a456
17 changed files with 610 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
const _ = require('lodash');
const sql = require('./sql');
const { getDataTypeString } = 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.driver.query(this.dbhan, sql.tables);
const columnsResult = await this.driver.query(this.dbhan, sql.columns);
const columns = columnsResult.rows.map(i => ({
tableName: i.TABLENAME,
columnName: i.COLUMNNAME,
notNull: i.NOTNULL,
isPrimaryKey: i.ISPRIMARYKEY,
dataType: getDataTypeString(i),
precision: i.NUMBERPRECISION,
scale: i.SCALE,
length: i.LENGTH,
defaultValue: i.DEFAULTVALUE,
columnComment: i.COLUMNCOMMENT,
isUnsigned: i.ISUNSIGNED,
pureName: i.PURENAME,
schemaName: i.SCHEMANAME,
}));
const tables = tablesResult.rows.map(i => ({
pureName: i.PURENAME,
objectId: i.OBJECTID,
schemaName: i.SCHEMANAME,
objectComment: i.OBJECTCOMMENT,
}));
return {
tables: tables.map(table => ({
...table,
columns: columns.filter(column => column.tableName === table.pureName),
})),
};
}
async _getFastSnapshot() {
return this._runAnalysis();
}
}
module.exports = Analyser;

View File

@@ -0,0 +1,146 @@
const _ = require('lodash');
const driverBase = require('../frontend/driver');
const Analyser = require('./Analyser');
const Firebird = require('node-firebird');
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.attach(options, (err, db) => {
if (err) {
reject(err);
return;
}
resolve(db);
});
});
return {
client: db,
};
},
async query(dbhan, sql) {
const res = await new Promise((resolve, reject) => {
dbhan.client.query(sql, (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
const columns = res[0] ? Object.keys(res[0]).map(i => ({ columnName: i })) : [];
return {
rows: res,
columns,
};
},
async script(dbhan, sql) {
throw new Error('Not implemented');
},
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 readQuery(dbhan, sql, structure) {
throw new Error('Not implemented');
},
async writeTable(dbhan, name, options) {
return createBulkInsertStream(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 listDatabases(dbhan) {
return [
{
name: 'default',
},
];
},
async createDatabase(dbhan, name) {},
async dropDatabase(dbhan, name) {},
async close(dbhan) {
return new Promise((resolve, reject) => {
dbhan.client.detach(err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
},
};
module.exports = driver;

View File

@@ -0,0 +1,54 @@
function getDataTypeString(column) {
if (!column) {
return null;
}
const { DATATYPECODE, SCALE, LENGTH, NUMBERPRECISION } = column;
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(${NUMBERPRECISION}, ${SCALE})`;
case 27:
return 'DOUBLE PRECISION';
case 35:
return 'BLOB';
case 37:
return `VARCHAR(${LENGTH})`;
case 261:
return 'CSTRING';
default:
return `UNKNOWN (${DATATYPECODE})`;
}
}
module.exports = {
getDataTypeString,
};

View File

@@ -0,0 +1,7 @@
const driver = require('./driver');
module.exports = {
packageName: 'dbgate-plugin-firebird',
drivers: [driver],
initialize(dbgateEnv) {},
};

View File

@@ -0,0 +1,43 @@
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 FALSE ELSE TRUE 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 numberprecision,
f.rdb$field_scale AS scale,
f.rdb$field_length AS length,
CAST(TRIM(rf.rdb$default_value) 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$field_name) AS VARCHAR(255)) AS pureName,
CAST(TRIM(r.rdb$owner_name) AS VARCHAR(255)) AS schemaName
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
ORDER BY
tableName, rf.rdb$field_position;
`;

View File

@@ -0,0 +1,9 @@
const version = require('./version');
const tables = require('./tables');
const columns = require('./columns');
module.exports = {
version,
columns,
tables,
};

View File

@@ -0,0 +1,9 @@
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
ORDER BY pureName;
`;

View File

@@ -0,0 +1 @@
module.exports = `SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version from rdb$database;`;