mirror of
https://github.com/Fabio286/antares.git
synced 2025-03-04 03:17:40 +01:00
feat(Firebird SQL): support to indexes and foreign keys
This commit is contained in:
parent
76df6319c2
commit
2c8509ff41
@ -22,7 +22,7 @@ export const customizations: Customizations = {
|
||||
schemas: false,
|
||||
tables: true,
|
||||
views: false,
|
||||
triggers: false,
|
||||
triggers: true,
|
||||
triggerFunctions: false,
|
||||
routines: false,
|
||||
functions: false,
|
||||
|
@ -1,5 +1,4 @@
|
||||
export default [
|
||||
'PRIMARY',
|
||||
'INDEX',
|
||||
'UNIQUE'
|
||||
];
|
||||
|
@ -105,6 +105,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
break;
|
||||
case 'pg':
|
||||
case 'sqlite':
|
||||
case 'firebird':
|
||||
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
|
||||
break;
|
||||
}
|
||||
@ -124,6 +125,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||
break;
|
||||
case 'pg':
|
||||
case 'firebird':
|
||||
fileBlob = fs.readFileSync(params.content);
|
||||
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
|
||||
break;
|
||||
@ -141,6 +143,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
escapedParam = '\'\'';
|
||||
break;
|
||||
case 'pg':
|
||||
case 'firebird':
|
||||
escapedParam = 'decode(\'\', \'hex\')';
|
||||
break;
|
||||
case 'sqlite':
|
||||
@ -158,6 +161,7 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
case 'mysql':
|
||||
case 'maria':
|
||||
case 'pg':
|
||||
case 'firebird':
|
||||
escapedParam = params.content;
|
||||
break;
|
||||
case 'sqlite':
|
||||
@ -382,7 +386,20 @@ export default (connections: {[key: string]: antares.Client}) => {
|
||||
if (description)
|
||||
query.select(`LEFT(${description}, 20) AS foreign_description`);
|
||||
|
||||
const results = await query.run();
|
||||
const results = await query.run<{[key: string]: string}>();
|
||||
|
||||
const parsedResults: {[key: string]: string}[] = [];
|
||||
|
||||
for (const row of results.rows) {
|
||||
const remappedRow: {[key: string]: string} = {};
|
||||
|
||||
for (const key in row)
|
||||
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
|
||||
|
||||
parsedResults.push(remappedRow);
|
||||
}
|
||||
|
||||
results.rows = parsedResults;
|
||||
|
||||
return { status: 'success', response: results };
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import dataTypes from 'common/data-types/sqlite';
|
||||
|
||||
export class FirebirdSQLClient extends AntaresCore {
|
||||
private _schema?: string;
|
||||
private _runningConnections: Map<string, number>;
|
||||
private _connectionsToCommit: Map<string, firebird.Database>;
|
||||
protected _connection?: firebird.Database;
|
||||
_params: firebird.Options;
|
||||
@ -69,7 +70,7 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
}
|
||||
|
||||
destroy () {
|
||||
return (this._connection as firebird.Database).detach();
|
||||
return this._connection.detach();
|
||||
}
|
||||
|
||||
use (): void {
|
||||
@ -84,7 +85,11 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
DESCRIPTION: string | null;
|
||||
}
|
||||
|
||||
// type ShowTriggersResult = ShowTableResult
|
||||
interface ShowTriggersResult {
|
||||
NAME: string;
|
||||
RELATION: 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');
|
||||
|
||||
@ -93,10 +98,11 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
const tablesArr: ShowTableResult[] = [];
|
||||
// const triggersArr: ShowTriggersResult[] = [];
|
||||
const triggersArr: ShowTriggersResult[] = [];
|
||||
let schemaSize = 0;
|
||||
|
||||
for (const db of filteredDatabases) {
|
||||
// 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>>(`
|
||||
@ -111,6 +117,17 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
`);
|
||||
|
||||
tablesArr.push(...tables);
|
||||
|
||||
const { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`
|
||||
SELECT
|
||||
RDB$TRIGGER_NAME as name,
|
||||
RDB$RELATION_NAME as relation,
|
||||
RDB$TRIGGER_SOURCE as source
|
||||
FROM RDB$TRIGGERS
|
||||
WHERE RDB$SYSTEM_FLAG=0;
|
||||
`);
|
||||
|
||||
triggersArr.push(...triggers);
|
||||
}
|
||||
|
||||
return filteredDatabases.map(db => {
|
||||
@ -128,12 +145,13 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
// const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => {
|
||||
// return {
|
||||
// name: trigger.name,
|
||||
// table: trigger.tbl_name
|
||||
// };
|
||||
// });
|
||||
const remappedTriggers = triggersArr.map(trigger => {
|
||||
return {
|
||||
name: trigger.NAME,
|
||||
table: trigger.RELATION,
|
||||
statement: trigger.SOURCE
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: db.name,
|
||||
@ -141,7 +159,7 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
tables: remappedTables,
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
triggers: remappedTriggers,
|
||||
schedulers: []
|
||||
};
|
||||
});
|
||||
@ -226,97 +244,111 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
});
|
||||
}
|
||||
|
||||
async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise<number> {
|
||||
const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`);
|
||||
async getTableApproximateCount ({ table }: { schema: string; table: string }): Promise<number> {
|
||||
const { rows } = await this.raw(`SELECT COUNT(*) AS nRows FROM "${table}"`);
|
||||
|
||||
return rows.length ? rows[0].count : 0;
|
||||
return rows.length ? rows[0].NROWS : 0;
|
||||
}
|
||||
|
||||
async getTableOptions ({ table }: { table: string }) {
|
||||
return { name: table };
|
||||
}
|
||||
|
||||
async getTableIndexes ({ schema, table }: { schema: string; table: string }) {
|
||||
interface TableColumnsResult {
|
||||
type: string;
|
||||
name: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
tbl_name: string;
|
||||
rootpage:4;
|
||||
sql: string;
|
||||
}
|
||||
|
||||
async getTableIndexes ({ table }: { schema: string; table: string }) {
|
||||
interface ShowIndexesResult {
|
||||
seq: number;
|
||||
name: string;
|
||||
unique: 0 | 1;
|
||||
origin: string;
|
||||
partial: 0 | 1;
|
||||
INDEX_NAME: string;
|
||||
FIELD_NAME: string;
|
||||
TABLE_NAME: string;
|
||||
INDEX_TYPE: string;
|
||||
INDEX_UNIQUE: number;
|
||||
}
|
||||
|
||||
const remappedIndexes = [];
|
||||
const { rows: primaryKeys } = await this.raw<antares.QueryResult<TableColumnsResult>>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`);
|
||||
|
||||
for (const key of primaryKeys) {
|
||||
const { rows: indexes } = await this.raw<antares.QueryResult<ShowIndexesResult>>(`
|
||||
SELECT
|
||||
ix.rdb$index_name AS INDEX_NAME,
|
||||
sg.rdb$field_name AS FIELD_NAME,
|
||||
rc.rdb$relation_name AS TABLE_NAME,
|
||||
rc.rdb$constraint_type AS INDEX_TYPE,
|
||||
ix.RDB$UNIQUE_FLAG AS INDEX_UNIQUE
|
||||
FROM
|
||||
rdb$indices ix
|
||||
LEFT JOIN rdb$index_segments sg ON ix.rdb$index_name = sg.rdb$index_name
|
||||
LEFT JOIN rdb$relation_constraints rc ON rc.rdb$index_name = ix.rdb$index_name
|
||||
WHERE
|
||||
rc.rdb$relation_name = '${table}'
|
||||
`);
|
||||
|
||||
for (const index of indexes) {
|
||||
remappedIndexes.push({
|
||||
name: 'PRIMARY',
|
||||
column: key.name,
|
||||
name: index.INDEX_NAME.trim(),
|
||||
column: index.FIELD_NAME.trim(),
|
||||
indexType: null as never,
|
||||
type: 'PRIMARY',
|
||||
type: index.INDEX_TYPE.trim(),
|
||||
cardinality: null as never,
|
||||
comment: '',
|
||||
indexComment: ''
|
||||
});
|
||||
}
|
||||
|
||||
const { rows: indexes } = await this.raw<antares.QueryResult<ShowIndexesResult>>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`);
|
||||
|
||||
for (const index of indexes) {
|
||||
const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`);
|
||||
|
||||
for (const detail of details) {
|
||||
remappedIndexes.push({
|
||||
name: index.name,
|
||||
column: detail.name,
|
||||
indexType: null as never,
|
||||
type: index.unique === 1 ? 'UNIQUE' : 'INDEX',
|
||||
cardinality: null as never,
|
||||
comment: '',
|
||||
indexComment: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return remappedIndexes;
|
||||
}
|
||||
|
||||
async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
|
||||
/* eslint-disable camelcase */
|
||||
interface KeyResult {
|
||||
from: string;
|
||||
id: number;
|
||||
table: string;
|
||||
to: string;
|
||||
on_update: string;
|
||||
on_delete: string;
|
||||
PKTABLE_NAME: string;
|
||||
PKCOLUMN_NAME: string;
|
||||
FKTABLE_NAME: string;
|
||||
FKCOLUMN_NAME: string;
|
||||
KEY_SEQ: number;
|
||||
UPDATE_RULE: string;
|
||||
DELETE_RULE: string;
|
||||
PK_NAME: string;
|
||||
FK_NAME: string;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
const { rows } = await this.raw<antares.QueryResult<KeyResult>>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`);
|
||||
const { rows } = await this.raw<antares.QueryResult<KeyResult>>(`
|
||||
SELECT
|
||||
PK.RDB$RELATION_NAME as PKTABLE_NAME,
|
||||
ISP.RDB$FIELD_NAME as PKCOLUMN_NAME,
|
||||
FK.RDB$RELATION_NAME as FKTABLE_NAME,
|
||||
ISF.RDB$FIELD_NAME as FKCOLUMN_NAME,
|
||||
(ISP.RDB$FIELD_POSITION + 1) as KEY_SEQ,
|
||||
RC.RDB$UPDATE_RULE as UPDATE_RULE,
|
||||
RC.RDB$DELETE_RULE as DELETE_RULE,
|
||||
PK.RDB$CONSTRAINT_NAME as PK_NAME,
|
||||
FK.RDB$CONSTRAINT_NAME as FK_NAME
|
||||
FROM
|
||||
RDB$RELATION_CONSTRAINTS PK,
|
||||
RDB$RELATION_CONSTRAINTS FK,
|
||||
RDB$REF_CONSTRAINTS RC,
|
||||
RDB$INDEX_SEGMENTS ISP,
|
||||
RDB$INDEX_SEGMENTS ISF
|
||||
WHERE FK.RDB$RELATION_NAME = '${table}'
|
||||
and FK.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME
|
||||
and PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ
|
||||
and ISP.RDB$INDEX_NAME = PK.RDB$INDEX_NAME
|
||||
and ISF.RDB$INDEX_NAME = FK.RDB$INDEX_NAME
|
||||
and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION
|
||||
ORDER BY 1, 5
|
||||
`);
|
||||
|
||||
return rows.map(field => {
|
||||
return {
|
||||
schema: schema,
|
||||
table: table,
|
||||
field: field.from,
|
||||
position: field.id + 1,
|
||||
field: field.FKCOLUMN_NAME.trim(),
|
||||
position: field.KEY_SEQ,
|
||||
constraintPosition: null,
|
||||
constraintName: field.id,
|
||||
constraintName: field.FK_NAME.trim(),
|
||||
refSchema: schema,
|
||||
refTable: field.table,
|
||||
refField: field.to,
|
||||
onUpdate: field.on_update,
|
||||
onDelete: field.on_delete
|
||||
refTable: field.PKTABLE_NAME.trim(),
|
||||
refField: field.PKCOLUMN_NAME.trim(),
|
||||
onUpdate: field.UPDATE_RULE.trim(),
|
||||
onDelete: field.DELETE_RULE.trim()
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -582,12 +614,15 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
// LIMIT
|
||||
const limitRaw = this._query.limit ? ` first ${this._query.limit}` : '';
|
||||
|
||||
// OFFSET
|
||||
const offsetRaw = this._query.offset ? ` skip ${this._query.offset}` : '';
|
||||
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw = '';
|
||||
|
||||
if (selectArray.length)
|
||||
selectRaw = selectArray.length ? `SELECT${limitRaw||''} ${selectArray.join(', ')} ` : `SELECT${limitRaw||''} * `;
|
||||
selectRaw = selectArray.length ? `SELECT${limitRaw||''}${offsetRaw||''} ${selectArray.join(', ')} ` : `SELECT${limitRaw||''}${offsetRaw||''} * `;
|
||||
|
||||
// FROM
|
||||
let fromRaw = '';
|
||||
@ -627,10 +662,7 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||
|
||||
// OFFSET
|
||||
const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : '';
|
||||
|
||||
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${offsetRaw}${insertRaw}`;
|
||||
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${insertRaw}`;
|
||||
}
|
||||
|
||||
async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
|
||||
@ -660,6 +692,7 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
const queries = args.split
|
||||
? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
|
||||
.filter(Boolean)
|
||||
@ -690,52 +723,114 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
if (!query) continue;
|
||||
const timeStart = new Date();
|
||||
let timeStop;
|
||||
const keysArr: antares.QueryForeign[] = [];
|
||||
let keysArr: antares.QueryForeign[] = [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
let queryResult;
|
||||
let remappedFields;
|
||||
let remappedFields: {
|
||||
name: string;
|
||||
alias: string;
|
||||
orgName: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
tableAlias: string;
|
||||
orgTable: string;
|
||||
type: string;
|
||||
length: number;
|
||||
key?: string;
|
||||
}[];
|
||||
|
||||
try {
|
||||
queryResult = await new Promise<unknown[]>((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(connection as any).query(query, [], (err: any, res: any, fields: FieldData[]) => { // <- fields is not natively typed or documented
|
||||
(connection as any).query(query, [], async (err: any, res: any, fields: FieldData[]) => { // <- fields is not natively typed or documented
|
||||
if (err) reject(err);
|
||||
else {
|
||||
const remappedResponse = [];
|
||||
|
||||
for (const row of res) {
|
||||
for (const key in row) {
|
||||
if (Buffer.isBuffer(row[key]))
|
||||
row[key] = row[key].toString('binary');
|
||||
else if (typeof row[key] === 'function')
|
||||
row[key] = row[key].toString('binary');
|
||||
}
|
||||
if (res) {
|
||||
for (const row of res) {
|
||||
for (const key in row) {
|
||||
if (Buffer.isBuffer(row[key]))
|
||||
row[key] = row[key].toString('binary');
|
||||
else if (typeof row[key] === 'function')
|
||||
row[key] = row[key].toString('binary');
|
||||
}
|
||||
|
||||
remappedResponse.push(row);
|
||||
remappedResponse.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
remappedFields = fields.map((field: any) => {
|
||||
return {
|
||||
name: field.alias,
|
||||
alias: field.alias,
|
||||
orgName: field.field,
|
||||
schema: args.schema,
|
||||
table: field.relation,
|
||||
tableAlias: field.relation,
|
||||
orgTable: field.relation,
|
||||
type: this.types[field.type],
|
||||
length: field.length
|
||||
};
|
||||
});
|
||||
if (fields) {
|
||||
remappedFields = fields.map(field => {
|
||||
return {
|
||||
name: field.alias,
|
||||
alias: field.alias,
|
||||
orgName: field.field,
|
||||
schema: args.schema,
|
||||
table: field.relation,
|
||||
tableAlias: field.relation,
|
||||
orgTable: field.relation,
|
||||
type: this.types[field.type],
|
||||
length: field.length,
|
||||
key: undefined as string
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
resolve(remappedResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (args.details) {
|
||||
if (remappedFields.length) {
|
||||
paramsArr = remappedFields.map(field => {
|
||||
return {
|
||||
table: field.orgTable,
|
||||
schema: field.schema
|
||||
};
|
||||
}).filter((val, i, arr) => arr.findIndex(el => el.table === val.table) === i);
|
||||
|
||||
for (const paramObj of paramsArr) {
|
||||
if (!paramObj.table || !paramObj.schema) continue;
|
||||
|
||||
try { // Column details
|
||||
const indexes = await this.getTableIndexes(paramObj);
|
||||
remappedFields = remappedFields.map(field => {
|
||||
const fieldIndex = indexes.find(i => i.column === field.name);
|
||||
if (fieldIndex) {
|
||||
const key = fieldIndex.type === 'PRIMARY KEY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'fk';
|
||||
field = { ...field, key };
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (args.autocommit) {
|
||||
this._connection.detach();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
|
||||
try { // Key usage (foreign keys)
|
||||
const response = await this.getKeyUsage(paramObj);
|
||||
keysArr = keysArr ? [...keysArr, ...response] : response;
|
||||
}
|
||||
catch (err) {
|
||||
if (args.autocommit) {
|
||||
this._connection.detach();
|
||||
this._runningConnections.delete(args.tabUid);
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
@ -744,10 +839,6 @@ export class FirebirdSQLClient extends AntaresCore {
|
||||
|
||||
timeStop = new Date();
|
||||
|
||||
// if (args.details) {
|
||||
|
||||
// }
|
||||
|
||||
resolve({
|
||||
duration: timeStop.getTime() - timeStart.getTime(),
|
||||
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
|
||||
|
@ -272,6 +272,8 @@ const keyName = (key: string) => {
|
||||
return 'UNIQUE';
|
||||
case 'mul':
|
||||
return 'INDEX';
|
||||
case 'fk':
|
||||
return 'FOREIGN KEY';
|
||||
default:
|
||||
return 'UNKNOWN ' + key;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
&.key-mul,
|
||||
&.key-INDEX,
|
||||
&.key-fk,
|
||||
&.key-KEY {
|
||||
color: limegreen;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user