From ae312efbbc3a9941380477b9849bdd8edc5b9fbf Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Thu, 17 Nov 2022 15:27:39 +0100 Subject: [PATCH] feat(Firebird SQL): procedure add/edit/delete support --- src/common/customizations/defaults.ts | 2 + src/common/customizations/firebird.ts | 8 +- src/common/customizations/mysql.ts | 2 + src/common/customizations/postgresql.ts | 2 + src/common/customizations/sqlite.ts | 1 + src/common/interfaces/customizations.ts | 2 + src/main/libs/AntaresCore.ts | 2 +- src/main/libs/clients/FirebirdSQLClient.ts | 211 ++++++++++++++++-- .../WorkspaceExploreBarMiscContext.vue | 3 + .../WorkspaceExploreBarTableContext.vue | 2 +- .../components/WorkspaceTabNewRoutine.vue | 2 +- .../components/WorkspaceTabPropsRoutine.vue | 3 + .../WorkspaceTabPropsRoutineParamsModal.vue | 26 +-- 13 files changed, 223 insertions(+), 43 deletions(-) diff --git a/src/common/customizations/defaults.ts b/src/common/customizations/defaults.ts index e9c9a496..fdc33e0d 100644 --- a/src/common/customizations/defaults.ts +++ b/src/common/customizations/defaults.ts @@ -51,6 +51,7 @@ export const defaults: Customizations = { tableSettings: false, tableArray: false, tableRealCount: false, + tableDuplicate: false, viewSettings: false, triggerSettings: false, triggerFunctionSettings: false, @@ -76,6 +77,7 @@ export const defaults: Customizations = { procedureDataAccess: false, procedureSql: null, procedureContext: false, + procedureContextValues: [], procedureLanguage: false, functionDeterministic: false, functionDataAccess: false, diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index fba97912..89bd6ba3 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -39,18 +39,24 @@ export const customizations: Customizations = { tables: true, views: true, triggers: true, - routines: false, + routines: true, functions: false, // Settings elementsWrapper: '"', stringsWrapper: '\'', tableAdd: true, tableSettings: true, + tableRealCount: true, viewAdd: true, viewSettings: true, triggerAdd: true, triggerMultipleEvents: true, triggerSql: 'BEGIN\r\n\r\nEND', + routineAdd: true, + procedureContext: true, + procedureContextValues: ['IN', 'OUT'], + procedureSql: 'BEGIN\r\n\r\nEND', + parametersLength: true, indexes: true, foreigns: true, nullable: true diff --git a/src/common/customizations/mysql.ts b/src/common/customizations/mysql.ts index c3bfa830..f5e0c2fe 100644 --- a/src/common/customizations/mysql.ts +++ b/src/common/customizations/mysql.ts @@ -43,6 +43,7 @@ export const customizations: Customizations = { stringsWrapper: '"', tableAdd: true, tableTruncateDisableFKCheck: true, + tableDuplicate: true, viewAdd: true, triggerAdd: true, routineAdd: true, @@ -77,6 +78,7 @@ export const customizations: Customizations = { procedureDataAccess: true, procedureSql: 'BEGIN\r\n\r\nEND', procedureContext: true, + procedureContextValues: ['IN', 'OUT', 'INOUT'], triggerSql: 'BEGIN\r\n\r\nEND', functionDeterministic: true, functionDataAccess: true, diff --git a/src/common/customizations/postgresql.ts b/src/common/customizations/postgresql.ts index 39ca38e8..2e3bc42a 100644 --- a/src/common/customizations/postgresql.ts +++ b/src/common/customizations/postgresql.ts @@ -39,6 +39,7 @@ export const customizations: Customizations = { elementsWrapper: '"', stringsWrapper: '\'', tableAdd: true, + tableDuplicate: true, viewAdd: true, triggerAdd: true, triggerFunctionAdd: true, @@ -60,6 +61,7 @@ export const customizations: Customizations = { tableArray: true, procedureSql: '$procedure$\r\n\r\n$procedure$', procedureContext: true, + procedureContextValues: ['IN', 'OUT', 'INOUT'], procedureLanguage: true, functionSql: '$function$\r\n\r\n$function$', triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', diff --git a/src/common/customizations/sqlite.ts b/src/common/customizations/sqlite.ts index f31515ee..3bacd28f 100644 --- a/src/common/customizations/sqlite.ts +++ b/src/common/customizations/sqlite.ts @@ -27,6 +27,7 @@ export const customizations: Customizations = { elementsWrapper: '"', stringsWrapper: '\'', tableAdd: true, + tableDuplicate: true, viewAdd: true, triggerAdd: true, schemaEdit: false, diff --git a/src/common/interfaces/customizations.ts b/src/common/interfaces/customizations.ts index 03578e21..09aa647a 100644 --- a/src/common/interfaces/customizations.ts +++ b/src/common/interfaces/customizations.ts @@ -35,6 +35,7 @@ export interface Customizations { stringsWrapper: string; tableAdd?: boolean; tableSettings?: boolean; + tableDuplicate?: boolean; tableArray?: boolean; tableRealCount?: boolean; tableTruncateDisableFKCheck?: boolean; @@ -75,6 +76,7 @@ export interface Customizations { procedureDataAccess?: boolean; procedureSql?: string; procedureContext?: boolean; + procedureContextValues?: string[]; procedureLanguage?: boolean; functionDeterministic?: boolean; functionDataAccess?: boolean; diff --git a/src/main/libs/AntaresCore.ts b/src/main/libs/AntaresCore.ts index c0c9cede..a0506c45 100644 --- a/src/main/libs/AntaresCore.ts +++ b/src/main/libs/AntaresCore.ts @@ -16,7 +16,7 @@ const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => { /** * As Simple As Possible Query Builder Core */ -export class AntaresCore { +export abstract class AntaresCore { _client: antares.ClientCode; protected _cUid: string protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index df65240f..0432f6aa 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -105,15 +105,7 @@ export class FirebirdSQLClient extends AntaresCore { } getConnectionPool () { - const pool = firebird.pool(this._poolSize, { ...this._params, blobAsText: true }); - // return new Promise((resolve, reject) => { - // pool.get((err, db) => { - // if (err) reject(err); - // else resolve(db); - // }); - // }); - - return pool; + return firebird.pool(this._poolSize, { ...this._params, blobAsText: true }); } destroy () { @@ -127,34 +119,42 @@ export class FirebirdSQLClient extends AntaresCore { // eslint-disable-next-line @typescript-eslint/no-unused-vars async getStructure (_schemas: Set) { - interface ShowTableResult { + interface TableResult { FORMAT: number; NAME: string; TYPE: string; DESCRIPTION: string | null; } - interface ShowTriggersResult { + interface TriggersResult { NAME: string; RELATION: string; SOURCE: string; } + interface ProcedureResult { + NAME: string; + COMMENT: string; + DEFINER: string; + SOURCE: string; + } + const { rows: databases } = await this.raw>('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') as name FROM rdb$database'); const filteredDatabases = databases.map(db => { return { name: path.basename(db.NAME) }; }); - const tablesArr: ShowTableResult[] = []; - const triggersArr: ShowTriggersResult[] = []; + const tablesArr: TableResult[] = []; + const triggersArr: TriggersResult[] = []; + const proceduresArr: ProcedureResult[] = []; let schemaSize = 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _db of filteredDatabases) { // if (!schemas.has(db.name)) continue; - const { rows: tables } = await this.raw>(` + const { rows: tables } = await this.raw>(` SELECT rdb$relation_name AS name, rdb$format AS format, @@ -165,7 +165,7 @@ export class FirebirdSQLClient extends AntaresCore { AND RDB$RELATION_TYPE = 0 `); - const { rows: views } = await this.raw>(` + const { rows: views } = await this.raw>(` SELECT DISTINCT RDB$VIEW_NAME AS name, 'view' AS type @@ -174,16 +174,30 @@ export class FirebirdSQLClient extends AntaresCore { tablesArr.push(...tables, ...views); - const { rows: triggers } = await this.raw>(` + const { rows: triggers } = await this.raw>(` SELECT RDB$TRIGGER_NAME as name, RDB$RELATION_NAME as relation, RDB$TRIGGER_SOURCE as source FROM RDB$TRIGGERS - WHERE RDB$SYSTEM_FLAG=0; + WHERE RDB$SYSTEM_FLAG=0 + ORDER BY RDB$TRIGGER_NAME; `); triggersArr.push(...triggers); + + const { rows: procedures } = await this.raw(` + SELECT + RDB$PROCEDURE_NAME AS NAME, + RDB$DESCRIPTION AS COMMENT, + RDB$PROCEDURE_SOURCE AS SOURCE, + RDB$OWNER_NAME AS DEFINER + FROM RDB$PROCEDURES + WHERE RDB$SYSTEM_FLAG=0 + ORDER BY RDB$PROCEDURE_NAME; + `); + + proceduresArr.push(...procedures); } return filteredDatabases.map(db => { @@ -209,12 +223,21 @@ export class FirebirdSQLClient extends AntaresCore { }; }); + // PROCEDURES + const remappedProcedures = proceduresArr.map(procedure => { + return { + name: procedure.NAME.trim(), + definer: procedure.DEFINER, + comment: procedure.COMMENT?.trim() + }; + }); + return { name: db.name, size: schemaSize, tables: remappedTables, functions: [], - procedures: [], + procedures: remappedProcedures, triggers: remappedTriggers, schedulers: [] }; @@ -600,9 +623,10 @@ export class FirebirdSQLClient extends AntaresCore { return await this.raw(sql); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy - const sql = `CREATE TABLE "${params.table}_copy" AS SELECT * FROM "${params.table}"`; - return await this.raw(sql); + // const sql = `CREATE TABLE "${params.table}_copy" AS SELECT * FROM "${params.table}"`; + // return await this.raw(sql); } async truncateTable (params: { schema: string; table: string }) { @@ -723,6 +747,153 @@ export class FirebirdSQLClient extends AntaresCore { return await this.raw(sql, { split: false }); } + async getRoutineInformations ({ routine }: { schema: string; routine: string }) { + interface ProcedureResult { + NAME: string; + COMMENT: string; + DEFINER: string; + SOURCE: string; + SECURITY: boolean; + } + + interface ProcedureParamsResult { + PARAMETER_NAME: string; + FIELD_TYPE: string; + FIELD_LENGTH: string; + FIELD_PRECISION: string; + FIELD_SCALE: string; + CONTEXT: string; + } + + const { rows: [procedure] } = await this.raw>(` + SELECT + RDB$PROCEDURE_NAME AS NAME, + RDB$DESCRIPTION AS COMMENT, + RDB$PROCEDURE_SOURCE AS SOURCE, + RDB$OWNER_NAME AS DEFINER, + RDB$SQL_SECURITY AS SECURITY + FROM RDB$PROCEDURES + WHERE RDB$SYSTEM_FLAG = 0 + AND RDB$PROCEDURE_NAME = '${routine}'; + `); + + if (procedure) { + const { rows: parameters } = await this.raw>(` + SELECT + p.RDB$PARAMETER_NAME AS PARAMETER_NAME, + p.RDB$PARAMETER_TYPE AS CONTEXT, + CASE f.RDB$FIELD_TYPE + WHEN 261 THEN 'BLOB' + WHEN 14 THEN 'CHAR' + WHEN 40 THEN 'CSTRING' + WHEN 11 THEN 'D_FLOAT' + WHEN 27 THEN 'DOUBLE PRECISION' + WHEN 10 THEN 'FLOAT' + WHEN 16 THEN 'BIGINT' + WHEN 8 THEN 'INTEGER' + WHEN 9 THEN 'QUAD' + WHEN 7 THEN 'SMALLINT' + WHEN 12 THEN 'DATE' + WHEN 13 THEN 'TIME' + WHEN 35 THEN 'TIMESTAMP' + WHEN 37 THEN 'VARCHAR' + ELSE 'UNKNOWN' + END AS FIELD_TYPE, + f.RDB$FIELD_LENGTH AS FIELD_LENGTH, + f.RDB$FIELD_PRECISION AS FIELD_PRECISION, + f.RDB$FIELD_SCALE AS FIELD_SCALE + FROM RDB$PROCEDURE_PARAMETERS p + JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = p.RDB$FIELD_SOURCE + WHERE p.RDB$SYSTEM_FLAG = 0 + AND RDB$PROCEDURE_NAME = '${routine}' + ORDER BY p.RDB$PARAMETER_TYPE, p.RDB$PARAMETER_NUMBER + `); + + const remappedParams = parameters.map(param => { + const length = this.getTypeInfo(param.FIELD_TYPE.trim()).length ? param.FIELD_LENGTH || param.FIELD_PRECISION : null; + return { + name: param.PARAMETER_NAME.trim(), + type: param.FIELD_TYPE.trim(), + length: length, + context: param.CONTEXT ? 'OUT' : 'IN' + }; + }); + + return { + definer: procedure.DEFINER, + sql: procedure.SOURCE, + parameters: remappedParams || [], + name: procedure.NAME.trim(), + comment: '', + security: procedure.SECURITY === false ? 'INVOKER' : 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL' + }; + } + else { + return { + definer: null, + sql: '', + parameters: [], + name: routine, + comment: '', + security: 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL' + }; + } + } + + async dropRoutine (params: { routine: string }) { + const sql = `DROP PROCEDURE "${params.routine}"`; + return await this.raw(sql); + } + + async alterRoutine ({ routine }: {routine: antares.AlterRoutineParams}) { + const tempProcedure = Object.assign({}, routine); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createRoutine(tempProcedure); + await this.dropRoutine({ routine: tempProcedure.name }); + await this.dropRoutine({ routine: routine.oldName }); + await this.createRoutine(routine); + } + catch (err) { + return Promise.reject(err); + } + } + + async createRoutine (params: antares.CreateRoutineParams) { + const inParams = 'parameters' in params + ? params.parameters + .filter(param => param.context === 'IN') + .reduce((acc: string[], curr) => { + acc.push(`"${curr.name}" ${curr.type}${curr.length ? `(${curr.length})` : ''}`); + return acc; + }, []).join(',') + : ''; + + const ourParams = 'parameters' in params + ? params.parameters + .filter(param => param.context === 'OUT') + .reduce((acc: string[], curr) => { + acc.push(`"${curr.name}" ${curr.type}${curr.length ? `(${curr.length})` : ''}`); + return acc; + }, []).join(',') + : ''; + + const sql = ` + CREATE PROCEDURE "${params.name}"(${inParams}) + ${ourParams ? `RETURNS (${ourParams})` : ''} + SQL SECURITY ${params.security} + AS + ${params.sql} + `; + + return await this.raw(sql, { split: false }); + } + async getEngines () { return { name: 'Firebird', diff --git a/src/renderer/components/WorkspaceExploreBarMiscContext.vue b/src/renderer/components/WorkspaceExploreBarMiscContext.vue index aa6b7b80..bb4745af 100644 --- a/src/renderer/components/WorkspaceExploreBarMiscContext.vue +++ b/src/renderer/components/WorkspaceExploreBarMiscContext.vue @@ -258,6 +258,9 @@ const runRoutine = (params?: string[]) => { case 'pg': sql = `CALL ${localElement.value.name}(${params.join(',')})`; break; + case 'firebird': + sql = `EXECUTE PROCEDURE "${localElement.value.name}"(${params.join(',')})`; + break; // case 'mssql': // sql = `EXEC ${localElement.value.name} ${params.join(',')}`; // break; diff --git a/src/renderer/components/WorkspaceExploreBarTableContext.vue b/src/renderer/components/WorkspaceExploreBarTableContext.vue index 171134df..2dec3d87 100644 --- a/src/renderer/components/WorkspaceExploreBarTableContext.vue +++ b/src/renderer/components/WorkspaceExploreBarTableContext.vue @@ -18,7 +18,7 @@ {{ t('word.settings') }}
diff --git a/src/renderer/components/WorkspaceTabNewRoutine.vue b/src/renderer/components/WorkspaceTabNewRoutine.vue index f648bd8e..e51859ef 100644 --- a/src/renderer/components/WorkspaceTabNewRoutine.vue +++ b/src/renderer/components/WorkspaceTabNewRoutine.vue @@ -291,7 +291,7 @@ watch(consoleHeight, () => { }); originalRoutine.value = { - sql: customizations.value.functionSql, + sql: customizations.value.procedureSql, language: customizations.value.languages ? customizations.value.languages[0] : null, name: '', definer: '', diff --git a/src/renderer/components/WorkspaceTabPropsRoutine.vue b/src/renderer/components/WorkspaceTabPropsRoutine.vue index 13718781..87ad1c3b 100644 --- a/src/renderer/components/WorkspaceTabPropsRoutine.vue +++ b/src/renderer/components/WorkspaceTabPropsRoutine.vue @@ -351,6 +351,9 @@ const runRoutine = (params?: string[]) => { case 'pg': sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`; break; + case 'firebird': + sql = `EXECUTE PROCEDURE "${originalRoutine.value.name}"(${params.join(',')})`; + break; case 'mssql': sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`; break; diff --git a/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue b/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue index 85b7a036..e54d508e 100644 --- a/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue +++ b/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue @@ -118,29 +118,17 @@ {{ t('word.context') }}
-