feat(PostgreSQL): procedure language select

This commit is contained in:
Fabio Di Stasio 2021-04-12 18:46:35 +02:00
parent dea5ec7513
commit b33199ea59
8 changed files with 98 additions and 62 deletions

View File

@ -52,5 +52,13 @@ module.exports = {
procedureDeterministic: false, procedureDeterministic: false,
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: false, procedureSql: false,
parametersLength: false procedureContext: false,
procedureLanguage: false,
functionDeterministic: false,
functionDataAccess: false,
functionSql: false,
functionContext: false,
functionLanguage: false,
parametersLength: false,
languages: false
}; };

View File

@ -50,5 +50,9 @@ module.exports = {
procedureDeterministic: true, procedureDeterministic: true,
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true,
functionDeterministic: true,
functionDataAccess: true,
functionSql: 'BEGIN\r\n\r\nEND',
parametersLength: true parametersLength: true
}; };

View File

@ -15,25 +15,28 @@ module.exports = {
views: true, views: true,
triggers: false, triggers: false,
routines: true, routines: true,
functions: false, functions: true,
schedulers: false,
// Settings // Settings
tableAdd: true, tableAdd: true,
viewAdd: true, viewAdd: true,
triggerAdd: false, triggerAdd: false,
routineAdd: true, routineAdd: true,
functionAdd: false, functionAdd: true,
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: false, triggerSettings: false,
routineSettings: true, routineSettings: true,
functionSettings: false, functionSettings: true,
schedulerSettings: false,
indexes: true, indexes: true,
foreigns: true, foreigns: true,
sortableFields: false,
nullable: true, nullable: true,
tableArray: true, tableArray: true,
procedureSql: '$BODY$\r\n\r\n$BODY$' procedureSql: '$BODY$\r\n\r\n$BODY$',
procedureContext: true,
procedureLanguage: true,
functionSql: '$BODY$\r\n\r\n$BODY$',
functionContext: true,
functionLanguage: true,
languages: ['sql', 'plpgsql', 'c', 'internal']
}; };

View File

@ -166,18 +166,23 @@ export class PostgreSQLClient extends AntaresCore {
}); });
// FUNCTIONS // FUNCTIONS
const remappedFunctions = functions.filter(func => func.Db === db.database).map(func => { const remappedFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type !== 'trigger').map(func => {
return { return {
name: func.routine_name, name: func.routine_name,
type: func.routine_type, type: func.routine_type,
definer: null, // func.Definer,
created: null, // func.Created,
updated: null, // func.Modified,
comment: null, // func.Comment,
charset: null, // func.character_set_client,
security: func.security_type security: func.security_type
}; };
}); });
// TRIGGER FUNCTIONS
const remappedTriggerFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type === 'trigger').map(func => {
return {
name: func.routine_name,
type: func.routine_type,
security: func.security_type
};
});
// TRIGGERS // TRIGGERS
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.database).map(trigger => { const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.database).map(trigger => {
return { return {
@ -196,6 +201,7 @@ export class PostgreSQLClient extends AntaresCore {
functions: remappedFunctions, functions: remappedFunctions,
procedures: remappedProcedures, procedures: remappedProcedures,
triggers: remappedTriggers, triggers: remappedTriggers,
triggerFunctions: remappedTriggerFunctions,
schedulers: [] schedulers: []
}; };
} }
@ -667,7 +673,7 @@ export class PostgreSQLClient extends AntaresCore {
this.use(this._schema); this.use(this._schema);
const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters}) const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
LANGUAGE SQL LANGUAGE ${routine.language}
SECURITY ${routine.security} SECURITY ${routine.security}
AS ${routine.sql}`; AS ${routine.sql}`;
@ -681,63 +687,66 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async getFunctionInformations ({ schema, func }) { async getFunctionInformations ({ schema, func }) {
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``; const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func}'));`;
const results = await this.raw(sql); const results = await this.raw(sql);
return results.rows.map(row => { return results.rows.map(async row => {
if (!row['Create Function']) { if (!row.pg_get_functiondef) {
return { return {
definer: null, definer: null,
sql: '', sql: '',
parameters: [], parameters: [],
name: row.Procedure, name: func,
comment: '', comment: '',
security: 'DEFINER', security: 'DEFINER',
deterministic: false, deterministic: false,
dataAccess: 'CONTAINS SQL', dataAccess: 'CONTAINS SQL'
returns: 'INT',
returnsLength: null
}; };
} }
const parameters = row['Create Function'] const sql = `SELECT proc.specific_schema AS procedure_schema,
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0] proc.specific_name,
.replaceAll('\r', '') proc.routine_name AS procedure_name,
.replaceAll('\t', '') proc.external_language,
.slice(1, -1) args.parameter_name,
.split(',') args.parameter_mode,
.map(el => { args.data_type
const param = el.split(' '); FROM information_schema.routines proc
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null]; LEFT JOIN information_schema.parameters args
ON proc.specific_schema = args.specific_schema
AND proc.specific_name = args.specific_name
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
AND proc.routine_type = 'FUNCTION'
AND proc.routine_name = '${func}'
AND proc.specific_schema = '${schema}'
ORDER BY procedure_schema,
specific_name,
procedure_name,
args.ordinal_position
`;
return { const results = await this.raw(sql);
name: param[0] ? param[0].replaceAll('`', '') : '',
type: type[0],
length: +type[1] ? +type[1].replace(/\D/g, '') : ''
};
}).filter(el => el.name);
let dataAccess = 'CONTAINS SQL'; const parameters = results.rows.map(row => {
if (row['Create Function'].includes('NO SQL')) return {
dataAccess = 'NO SQL'; name: row.parameter_name,
if (row['Create Function'].includes('READS SQL DATA')) type: row.data_type.toUpperCase(),
dataAccess = 'READS SQL DATA'; length: '',
if (row['Create Function'].includes('MODIFIES SQL DATA')) context: row.parameter_mode
dataAccess = 'MODIFIES SQL DATA'; };
});
const output = row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs).length ? row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs)[0].replace(')', '').split('(') : ['', null];
return { return {
definer: row['Create Function'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], definer: '',
sql: row['Create Function'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
parameters: parameters || [], parameters: parameters || [],
name: row.Function, name: func,
comment: row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '', comment: '',
security: row['Create Function'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER', security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: row['Create Function'].includes('DETERMINISTIC'), deterministic: null,
dataAccess, dataAccess: null,
returns: output[0].toUpperCase(), language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
returnsLength: +output[1] returns: row.pg_get_functiondef.match(/(?<=RETURNS SETOF )(.*)(?<=[\S+\n\r\s])/gm)[0].toUpperCase()
}; };
})[0]; })[0];
} }
@ -788,7 +797,7 @@ export class PostgreSQLClient extends AntaresCore {
}, []).join(','); }, []).join(',');
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''} const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
LANGUAGE SQL LANGUAGE ${func.language}
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} ${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
${func.dataAccess} ${func.dataAccess}
SQL SECURITY ${func.security} SQL SECURITY ${func.security}

View File

@ -38,7 +38,6 @@
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a> <a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
</li> </li>
<li <li
v-if="updateStatus !== 'disabled'"
class="tab-item" class="tab-item"
:class="{'active': selectedTab === 'changelog'}" :class="{'active': selectedTab === 'changelog'}"
@click="selectTab('changelog')" @click="selectTab('changelog')"
@ -416,6 +415,7 @@ ORDER BY
.panel-body { .panel-body {
min-height: calc(25vh - 70px); min-height: calc(25vh - 70px);
max-height: 65vh;
overflow: auto; overflow: auto;
.theme-block { .theme-block {

View File

@ -75,8 +75,8 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="procedure of filteredProcedures" v-for="(procedure, i) of filteredProcedures"
:key="procedure.name" :key="`${procedure.name}-${i}`"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}"
@click="setBreadcrumbs({schema: database.name, procedure: procedure.name})" @click="setBreadcrumbs({schema: database.name, procedure: procedure.name})"
@ -103,8 +103,8 @@
<div> <div>
<ul class="menu menu-nav pt-0"> <ul class="menu menu-nav pt-0">
<li <li
v-for="func of filteredFunctions" v-for="(func, i) of filteredFunctions"
:key="func.name" :key="`${func.name}-${i}`"
class="menu-item" class="menu-item"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}" :class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
@click="setBreadcrumbs({schema: database.name, function: func.name})" @click="setBreadcrumbs({schema: database.name, function: func.name})"

View File

@ -26,6 +26,18 @@
> >
</div> </div>
</div> </div>
<div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="optionsProxy.language" class="form-select">
<option v-for="language in customizations.languages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group"> <div v-if="customizations.definer" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.definer') }} {{ $t('word.definer') }}

View File

@ -119,7 +119,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.procedureContext" class="form-group">
<label class="form-label col-3"> <label class="form-label col-3">
{{ $t('word.context') }} {{ $t('word.context') }}
</label> </label>