feat(Firebird SQL): procedure add/edit/delete support

This commit is contained in:
Fabio Di Stasio 2022-11-17 15:27:39 +01:00
parent 8e422e3f07
commit ae312efbbc
13 changed files with 223 additions and 43 deletions

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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$',

View File

@ -27,6 +27,7 @@ export const customizations: Customizations = {
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableDuplicate: true,
viewAdd: true,
triggerAdd: true,
schemaEdit: false,

View File

@ -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;

View File

@ -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};

View File

@ -105,15 +105,7 @@ export class FirebirdSQLClient extends AntaresCore {
}
getConnectionPool () {
const pool = firebird.pool(this._poolSize, { ...this._params, blobAsText: true });
// return new Promise<firebird.Database>((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<string>) {
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<antares.QueryResult<{ NAME: string}>>('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<antares.QueryResult<ShowTableResult>>(`
const { rows: tables } = await this.raw<antares.QueryResult<TableResult>>(`
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<antares.QueryResult<ShowTableResult>>(`
const { rows: views } = await this.raw<antares.QueryResult<TableResult>>(`
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<antares.QueryResult<ShowTriggersResult>>(`
const { rows: triggers } = await this.raw<antares.QueryResult<TriggersResult>>(`
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<antares.QueryResult<ProcedureResult>>(`
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<antares.QueryResult<ProcedureParamsResult>>(`
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',

View File

@ -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;

View File

@ -18,7 +18,7 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span>
</div>
<div
v-if="selectedTable && selectedTable.type === 'table'"
v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
class="context-element"
@click="duplicateTable"
>

View File

@ -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: '',

View File

@ -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;

View File

@ -118,29 +118,17 @@
{{ t('word.context') }}
</label>
<div class="column">
<label class="form-radio">
<label
v-for="condext in customizations.procedureContextValues"
:key="condext"
class="form-radio"
>
<input
v-model="selectedParamObj.context"
type="radio"
name="context"
value="IN"
> <i class="form-icon" /> IN
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="OUT"
name="context"
> <i class="form-icon" /> OUT
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="INOUT"
name="context"
> <i class="form-icon" /> INOUT
:value="condext"
> <i class="form-icon" /> {{ condext }}
</label>
</div>
</div>