diff --git a/plugins/dbgate-plugin-firebird/src/backend/Analyser.js b/plugins/dbgate-plugin-firebird/src/backend/Analyser.js index a6f9172c1..915af42a6 100644 --- a/plugins/dbgate-plugin-firebird/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-firebird/src/backend/Analyser.js @@ -13,31 +13,19 @@ class Analyser extends DatabaseAnalyser { const tablesResult = await this.driver.query(this.dbhan, sql.tables); const columnsResult = await this.driver.query(this.dbhan, sql.columns); const triggersResult = await this.driver.query(this.dbhan, sql.triggers); + const primaryKeysResult = await this.driver.query(this.dbhan, sql.primaryKeys); + const foreignKeysResult = await this.driver.query(this.dbhan, sql.foreignKeys); + const functionsResults = await this.driver.query(this.dbhan, sql.functions); + const functionParametersResults = await this.driver.query(this.dbhan, sql.functionParameters); + const proceduresResults = await this.driver.query(this.dbhan, sql.procedures); + const procedureParametersResults = await this.driver.query(this.dbhan, sql.procedureParameters); - 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 columns = columnsResult.rows?.map(column => ({ + ...column, + dataType: getDataTypeString(column), })); - const tables = tablesResult.rows.map(i => ({ - pureName: i.PURENAME, - objectId: i.OBJECTID, - schemaName: i.SCHEMANAME, - objectComment: i.OBJECTCOMMENT, - })); - - const triggers = triggersResult.rows.map(i => ({ + const triggers = triggersResult.rows?.map(i => ({ pureName: i.PURENAME, tableName: i.TABLENAME, shcemaName: i.SCHEMANAME, @@ -45,14 +33,48 @@ class Analyser extends DatabaseAnalyser { triggerTiming: getTriggerTiming(i.TRIGGERTYPE), })); - return { - tables: tables.map(table => ({ + const primaryKeys = primaryKeysResult.rows ?? []; + + const foreignKeys = foreignKeysResult.rows ?? []; + + const functions = functionsResults.rows?.map(func => ({ + ...func, + 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 procedures = proceduresResults.rows.map(proc => ({ + ...proc, + parameters: procedureParametersResults.rows + ?.filter(param => param.owningObjectName === proc.pureName) + .map(param => ({ + ...param, + dataType: getDataTypeString(param), + })), + })); + + const tables = + tablesResult.rows?.map(table => ({ ...table, columns: columns.filter( column => column.tableName === table.pureName && column.schemaName === table.schemaName ), - })), + primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, primaryKeys), + foreignKeys: DatabaseAnalyser.extractForeignKeys(table, foreignKeys), + })) ?? []; + + return { + tables, triggers, + functions, + procedures, }; } } diff --git a/plugins/dbgate-plugin-firebird/src/backend/helpers.js b/plugins/dbgate-plugin-firebird/src/backend/helpers.js index 868296713..2eecf501d 100644 --- a/plugins/dbgate-plugin-firebird/src/backend/helpers.js +++ b/plugins/dbgate-plugin-firebird/src/backend/helpers.js @@ -1,10 +1,5 @@ -function getDataTypeString(column) { - if (!column) { - return null; - } - const { DATATYPECODE, SCALE, LENGTH, NUMBERPRECISION } = column; - - switch (DATATYPECODE) { +function getDataTypeString({ dataTypeCode, scale, length, precision }) { + switch (dataTypeCode) { case 7: return 'SMALLINT'; @@ -27,10 +22,10 @@ function getDataTypeString(column) { return 'TIME'; case 14: - return `CHAR(${LENGTH})`; + return `CHAR(${length})`; case 16: - return `DECIMAL(${NUMBERPRECISION}, ${SCALE})`; + return `DECIMAL(${precision}, ${scale})`; case 27: return 'DOUBLE PRECISION'; @@ -39,13 +34,14 @@ function getDataTypeString(column) { return 'BLOB'; case 37: - return `VARCHAR(${LENGTH})`; + return `VARCHAR(${length})`; case 261: return 'CSTRING'; default: - return `UNKNOWN (${DATATYPECODE})`; + if (dataTypeCode === null || dataTypeCode === undefined) return 'UNKNOWN'; + return `UNKNOWN (${dataTypeCode})`; } } diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/columns.js b/plugins/dbgate-plugin-firebird/src/backend/sql/columns.js index 8096ad9b8..ea8804134 100644 --- a/plugins/dbgate-plugin-firebird/src/backend/sql/columns.js +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/columns.js @@ -1,8 +1,8 @@ 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, + 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 @@ -13,19 +13,19 @@ SELECT DISTINCT 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, + 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 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 + 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 @@ -39,5 +39,5 @@ LEFT JOIN WHERE r.rdb$system_flag = 0 ORDER BY - tableName, rf.rdb$field_position; + "tableName", rf.rdb$field_position; `; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/foreignKeys.js b/plugins/dbgate-plugin-firebird/src/backend/sql/foreignKeys.js new file mode 100644 index 000000000..b11af75b3 --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/foreignKeys.js @@ -0,0 +1,35 @@ +module.exports = ` +SELECT + TRIM(rel.RDB$OWNER_NAME) AS "schemaName", + 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' +ORDER BY + "pureName", + "constraintName", + iseg_fk.RDB$FIELD_POSITION; +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/functionParameters.js b/plugins/dbgate-plugin-firebird/src/backend/sql/functionParameters.js new file mode 100644 index 000000000..efde85aef --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/functionParameters.js @@ -0,0 +1,31 @@ +module.exports = ` +SELECT + TRIM(F.RDB$OWNER_NAME) AS "owningObjectSchemaName", -- Schema of the function this parameter belongs to + 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 + TRIM(F.RDB$OWNER_NAME) AS "schemaName" -- NamedObjectInfo.schemaName (owner of the function) + +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 + "owningObjectSchemaName", "owningObjectName", "position"; +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/functions.js b/plugins/dbgate-plugin-firebird/src/backend/sql/functions.js new file mode 100644 index 000000000..4603dd8cb --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/functions.js @@ -0,0 +1,16 @@ +module.exports = ` +SELECT + TRIM(F.RDB$FUNCTION_NAME) AS "pureName", + TRIM(F.RDB$OWNER_NAME) AS "schemaName", + 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 +ORDER BY + "schemaName", "pureName"; +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/index.js b/plugins/dbgate-plugin-firebird/src/backend/sql/index.js index f31c83640..a253c99a2 100644 --- a/plugins/dbgate-plugin-firebird/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/index.js @@ -2,10 +2,22 @@ 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'); module.exports = { version, columns, tables, triggers, + primaryKeys, + foreignKeys, + functions, + functionParameters, + procedures, + procedureParameters, }; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/primaryKeys.js b/plugins/dbgate-plugin-firebird/src/backend/sql/primaryKeys.js new file mode 100644 index 000000000..c73cad3d3 --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/primaryKeys.js @@ -0,0 +1,29 @@ +module.exports = ` +SELECT + TRIM(rel.RDB$OWNER_NAME) AS "schemaName", + 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 +ORDER BY + "schemaName", + "pureName", + "constraintName", + iseg.RDB$FIELD_POSITION; +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/procedureParameters.js b/plugins/dbgate-plugin-firebird/src/backend/sql/procedureParameters.js new file mode 100644 index 000000000..de7a3e95b --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/procedureParameters.js @@ -0,0 +1,32 @@ +module.exports = ` +SELECT + TRIM(P.RDB$OWNER_NAME) AS "owningObjectSchemaName", -- Schema of the procedure this parameter belongs to + 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 + TRIM(P.RDB$OWNER_NAME) AS "schemaName" -- NamedObjectInfo.schemaName (owner of the procedure) + +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 + "owningObjectSchemaName", "owningObjectName", PP.RDB$PARAMETER_TYPE, "position"; -- Order by IN(0)/OUT(1) then by position +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/procedures.js b/plugins/dbgate-plugin-firebird/src/backend/sql/procedures.js new file mode 100644 index 000000000..16da59b34 --- /dev/null +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/procedures.js @@ -0,0 +1,17 @@ +module.exports = ` +SELECT + TRIM(P.RDB$PROCEDURE_NAME) AS "pureName", + TRIM(P.RDB$OWNER_NAME) AS "schemaName", + TRIM(P.RDB$PROCEDURE_NAME) AS "objectId", -- Using procedure name as a practical objectId + 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) +ORDER BY + "schemaName", "pureName"; +`; diff --git a/plugins/dbgate-plugin-firebird/src/backend/sql/tables.js b/plugins/dbgate-plugin-firebird/src/backend/sql/tables.js index ac9f2d533..25876ca33 100644 --- a/plugins/dbgate-plugin-firebird/src/backend/sql/tables.js +++ b/plugins/dbgate-plugin-firebird/src/backend/sql/tables.js @@ -1,10 +1,10 @@ module.exports = ` SELECT - TRIM(RDB$RELATION_NAME) AS pureName, - RDB$DESCRIPTION AS objectComment, - RDB$FORMAT AS objectTypeField, - RDB$OWNER_NAME AS schemaName + TRIM(RDB$RELATION_NAME) AS "pureName", + RDB$DESCRIPTION AS "objectComment", + RDB$FORMAT AS "objectTypeField", + TRIM(RDB$OWNER_NAME) AS "schemaName" FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0 -- only user-defined tables -ORDER BY pureName; +ORDER BY "pureName"; `;