1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

feat(PostgreSQL): support of arrays in table settings

This commit is contained in:
2021-04-09 19:31:41 +02:00
parent c20bff7bcb
commit d0b3e1b1b8
9 changed files with 89 additions and 91 deletions

View File

@ -44,9 +44,12 @@ module.exports = {
comment: false, comment: false,
collation: false, collation: false,
definer: false, definer: false,
arrays: false,
onUpdate: false, onUpdate: false,
tableArray: false,
viewAlgorithm: false, viewAlgorithm: false,
viewSqlSecurity: false, viewSqlSecurity: false,
viewUpdateOption: false viewUpdateOption: false,
procedureDeterministic: false,
procedureDataAccess: false,
procedureSql: false
}; };

View File

@ -14,22 +14,26 @@ module.exports = {
tables: true, tables: true,
views: true, views: true,
triggers: false, triggers: false,
routines: false, routines: true,
functions: false, functions: false,
schedulers: false, schedulers: false,
// Settings // Settings
tableAdd: true, tableAdd: true,
viewAdd: true, viewAdd: true,
triggerAdd: false,
routineAdd: true,
functionAdd: true,
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: false, triggerSettings: false,
routineSettings: false, routineSettings: true,
functionSettings: false, functionSettings: false,
schedulerSettings: false, schedulerSettings: false,
indexes: true, indexes: true,
foreigns: true, foreigns: true,
sortableFields: false, sortableFields: false,
nullable: true, nullable: true,
arrays: true tableArray: true,
procedureSql: '$BODY$\r\n\r\n$BODY$'
}; };

View File

@ -7,6 +7,8 @@ export class MySQLClient extends AntaresCore {
constructor (args) { constructor (args) {
super(args); super(args);
this._schema = null;
this.types = { this.types = {
0: 'DECIMAL', 0: 'DECIMAL',
1: 'TINYINT', 1: 'TINYINT',
@ -120,6 +122,7 @@ export class MySQLClient extends AntaresCore {
* @memberof MySQLClient * @memberof MySQLClient
*/ */
use (schema) { use (schema) {
this._schema = schema;
return this.raw(`USE \`${schema}\``); return this.raw(`USE \`${schema}\``);
} }
@ -1049,7 +1052,7 @@ export class MySQLClient extends AntaresCore {
options options
} = params; } = params;
let sql = `ALTER TABLE \`${table}\` `; let sql = `ALTER TABLE \`${this._schema}\`.\`${table}\` `;
const alterColumns = []; const alterColumns = [];
// OPTIONS // OPTIONS

View File

@ -23,54 +23,16 @@ export class PostgreSQLClient extends AntaresCore {
this.types = {}; this.types = {};
for (const key in types.builtins) for (const key in types.builtins)
this.types[types.builtins[key]] = key; this.types[types.builtins[key]] = key;
}
_getType (field) { this._arrayTypes = {
let name = this.types[field.columnType]; _int2: 'SMALLINT',
let length = field.columnLength; _int4: 'INTEGER',
_int8: 'BIGINT',
if (['DATE', 'TIME', 'YEAR', 'DATETIME'].includes(name)) _float4: 'REAL',
length = field.decimals; _float8: 'DOUBLE PRECISION',
_char: '"CHAR"',
if (name === 'TIMESTAMP') _varchar: 'CHARACTER VARYING'
length = 0; };
if (field.charsetNr === 63) { // if binary
if (name === 'CHAR')
name = 'BINARY';
else if (name === 'VARCHAR')
name = 'VARBINARY';
}
if (name === 'BLOB') {
switch (length) {
case 765:
name = 'TYNITEXT';
break;
case 196605:
name = 'TEXT';
break;
case 50331645:
name = 'MEDIUMTEXT';
break;
case 4294967295:
name = field.charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT';
break;
case 255:
name = 'TINYBLOB';
break;
case 65535:
name = 'BLOB';
break;
case 16777215:
name = 'MEDIUMBLOB';
break;
default:
name = field.charsetNr === 63 ? 'BLOB' : 'TEXT';
}
}
return { name, length };
} }
_getTypeInfo (type) { _getTypeInfo (type) {
@ -79,6 +41,12 @@ export class PostgreSQLClient extends AntaresCore {
.filter(_type => _type.name === type.toUpperCase())[0]; .filter(_type => _type.name === type.toUpperCase())[0];
} }
_getArrayType (type) {
if (Object.keys(this._arrayTypes).includes(type))
return this._arrayTypes[type];
return type.replace('_', '');
}
/** /**
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
@ -189,16 +157,11 @@ export class PostgreSQLClient extends AntaresCore {
}); });
// PROCEDURES // PROCEDURES
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.database).map(procedure => { const remappedProcedures = procedures.filter(procedure => procedure.routine_schema === db.database).map(procedure => {
return { return {
name: procedure.Name, name: procedure.routine_name,
type: procedure.Type, type: procedure.routine_type,
definer: procedure.Definer, security: procedure.security_type
created: procedure.Created,
updated: procedure.Modified,
comment: procedure.Comment,
charset: procedure.character_set_client,
security: procedure.Security_type
}; };
}); });
@ -256,7 +219,7 @@ export class PostgreSQLClient extends AntaresCore {
* @returns {Object} table scructure * @returns {Object} table scructure
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async getTableColumns ({ schema, table }) { async getTableColumns ({ schema, table }, arrayRemap = true) {
const { rows } = await this const { rows } = await this
.select('*') .select('*')
.schema('information_schema') .schema('information_schema')
@ -266,10 +229,17 @@ export class PostgreSQLClient extends AntaresCore {
.run(); .run();
return rows.map(field => { return rows.map(field => {
let type = field.data_type;
const isArray = type === 'ARRAY';
if (isArray && arrayRemap)
type = this._getArrayType(field.udt_name);
return { return {
name: field.column_name, name: field.column_name,
key: null, key: null,
type: field.data_type.toUpperCase(), type: type.toUpperCase(),
isArray,
schema: field.table_schema, schema: field.table_schema,
table: field.table_name, table: field.table_name,
numPrecision: field.numeric_precision, numPrecision: field.numeric_precision,
@ -583,16 +553,16 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async getRoutineInformations ({ schema, routine }) { async getRoutineInformations ({ schema, routine }) {
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``; const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${routine}'));`;
const results = await this.raw(sql); const results = await this.raw(sql);
return results.rows.map(row => { return results.rows.map(row => {
if (!row['Create Procedure']) { if (!row.pg_get_functiondef) {
return { return {
definer: null, definer: null,
sql: '', sql: '',
parameters: [], parameters: [],
name: row.Procedure, name: routine,
comment: '', comment: '',
security: 'DEFINER', security: 'DEFINER',
deterministic: false, deterministic: false,
@ -600,7 +570,7 @@ export class PostgreSQLClient extends AntaresCore {
}; };
} }
const parameters = row['Create Procedure'] const parameters = row.pg_get_functiondef
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0] .match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
.replaceAll('\r', '') .replaceAll('\r', '')
.replaceAll('\t', '') .replaceAll('\t', '')
@ -618,22 +588,23 @@ export class PostgreSQLClient extends AntaresCore {
}).filter(el => el.name); }).filter(el => el.name);
let dataAccess = 'CONTAINS SQL'; let dataAccess = 'CONTAINS SQL';
if (row['Create Procedure'].includes('NO SQL')) if (row.pg_get_functiondef.includes('NO SQL'))
dataAccess = 'NO SQL'; dataAccess = 'NO SQL';
if (row['Create Procedure'].includes('READS SQL DATA')) if (row.pg_get_functiondef.includes('READS SQL DATA'))
dataAccess = 'READS SQL DATA'; dataAccess = 'READS SQL DATA';
if (row['Create Procedure'].includes('MODIFIES SQL DATA')) if (row.pg_get_functiondef.includes('MODIFIES SQL DATA'))
dataAccess = 'MODIFIES SQL DATA'; dataAccess = 'MODIFIES SQL DATA';
return { return {
definer: row['Create Procedure'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], definer: '',
sql: row['Create Procedure'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
parameters: parameters || [], parameters: parameters || [],
name: row.Procedure, name: routine,
comment: row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '', comment: '',
security: row['Create Procedure'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER', security: row.pg_get_functiondef.includes('SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
deterministic: row['Create Procedure'].includes('DETERMINISTIC'), deterministic: null,
dataAccess dataAccess,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
}; };
})[0]; })[0];
} }
@ -645,7 +616,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropRoutine (params) { async dropRoutine (params) {
const sql = `DROP PROCEDURE \`${params.routine}\``; const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`;
return await this.raw(sql); return await this.raw(sql);
} }
@ -680,18 +651,15 @@ export class PostgreSQLClient extends AntaresCore {
async createRoutine (routine) { async createRoutine (routine) {
const parameters = 'parameters' in routine const parameters = 'parameters' in routine
? routine.parameters.reduce((acc, curr) => { ? routine.parameters.reduce((acc, curr) => {
acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
return acc; return acc;
}, []).join(',') }, []).join(',')
: ''; : '';
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters}) const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
LANGUAGE SQL LANGUAGE SQL
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} SECURITY ${routine.security}
${routine.dataAccess} AS ${routine.sql}`;
SQL SECURITY ${routine.security}
COMMENT '${routine.comment}'
${routine.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }
@ -1043,7 +1011,7 @@ export class PostgreSQLClient extends AntaresCore {
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false; const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN ${addition.name} alterColumns.push(`ADD COLUMN ${addition.name}
${addition.type.toUpperCase()}${length ? `(${length})` : ''} ${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
${addition.unsigned ? 'UNSIGNED' : ''} ${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''} ${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'} ${addition.nullable ? 'NULL' : 'NOT NULL'}
@ -1092,7 +1060,7 @@ export class PostgreSQLClient extends AntaresCore {
localType = change.type.toLowerCase(); localType = change.type.toLowerCase();
} }
alterColumns.push(`ALTER COLUMN "${change.orgName}" TYPE ${localType}${length ? `(${length})` : ''} USING "${change.orgName}"::${localType}`); alterColumns.push(`ALTER COLUMN "${change.orgName}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.orgName}"::${localType}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`); alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`); alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) { if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) {
@ -1345,7 +1313,7 @@ export class PostgreSQLClient extends AntaresCore {
if (!paramObj.table || !paramObj.schema) continue; if (!paramObj.table || !paramObj.schema) continue;
try { // Column details try { // Column details
const columns = await this.getTableColumns(paramObj); const columns = await this.getTableColumns(paramObj, false);
const indexes = await this.getTableIndexes(paramObj); const indexes = await this.getTableIndexes(paramObj);
remappedFields = remappedFields.map(field => { remappedFields = remappedFields.map(field => {

View File

@ -69,7 +69,7 @@ export default {
this.editor = ace.edit(`editor-${this.id}`, { this.editor = ace.edit(`editor-${this.id}`, {
mode: `ace/mode/${this.mode}`, mode: `ace/mode/${this.mode}`,
theme: `ace/theme/${this.editorTheme}`, theme: `ace/theme/${this.editorTheme}`,
value: this.value, value: this.value || '',
fontSize: '14px', fontSize: '14px',
printMargin: false, printMargin: false,
readOnly: this.readOnly, readOnly: this.readOnly,

View File

@ -42,6 +42,13 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="customizations.tableArray" class="th">
<div class="column-resizable">
<div class="table-column-title">
{{ $t('word.array') }}
</div>
</div>
</div>
<div class="th"> <div class="th">
<div class="column-resizable"> <div class="column-resizable">
<div class="table-column-title"> <div class="table-column-title">

View File

@ -76,6 +76,16 @@
</optgroup> </optgroup>
</select> </select>
</div> </div>
<div
v-if="customizations.tableArray"
class="td"
tabindex="0"
>
<label class="form-checkbox">
<input v-model="localRow.isArray" type="checkbox">
<i class="form-icon" />
</label>
</div>
<div class="td type-int" tabindex="0"> <div class="td type-int" tabindex="0">
<template v-if="fieldType.length"> <template v-if="fieldType.length">
<span <span

View File

@ -102,7 +102,8 @@ module.exports = {
variables: 'Variables', variables: 'Variables',
processes: 'Processes', processes: 'Processes',
database: 'Database', database: 'Database',
scratchpad: 'Scratchpad' scratchpad: 'Scratchpad',
array: 'Array'
}, },
message: { message: {
appWelcome: 'Welcome to Antares SQL Client!', appWelcome: 'Welcome to Antares SQL Client!',

View File

@ -30,6 +30,7 @@
"macaddr": $string-color, "macaddr": $string-color,
"macaddr8": $string-color, "macaddr8": $string-color,
"uuid": $string-color, "uuid": $string-color,
"regproc": $string-color,
"int": $number-color, "int": $number-color,
"tinyint": $number-color, "tinyint": $number-color,
"smallint": $number-color, "smallint": $number-color,
@ -76,6 +77,7 @@
"tsvector": $array-color, "tsvector": $array-color,
"tsquery": $array-color, "tsquery": $array-color,
"pg_node_tree": $array-color, "pg_node_tree": $array-color,
"aclitem": $array-color,
"unknown": $unknown-color, "unknown": $unknown-color,
) )
); );