1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-02-18 12:40:41 +01:00

feat(PostgreSQL): functions management

This commit is contained in:
Fabio Di Stasio 2021-04-13 18:05:03 +02:00
parent b33199ea59
commit cd31413256
8 changed files with 130 additions and 64 deletions

View File

@ -727,7 +727,7 @@ export class PostgreSQLClient extends AntaresCore {
const results = await this.raw(sql); const results = await this.raw(sql);
const parameters = results.rows.map(row => { const parameters = results.rows.filter(row => row.parameter_mode).map(row => {
return { return {
name: row.parameter_name, name: row.parameter_name,
type: row.data_type.toUpperCase(), type: row.data_type.toUpperCase(),
@ -746,7 +746,7 @@ export class PostgreSQLClient extends AntaresCore {
deterministic: null, deterministic: null,
dataAccess: null, dataAccess: null,
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0], language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
returns: row.pg_get_functiondef.match(/(?<=RETURNS SETOF )(.*)(?<=[\S+\n\r\s])/gm)[0].toUpperCase() returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
}; };
})[0]; })[0];
} }
@ -758,7 +758,7 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropFunction (params) { async dropFunction (params) {
const sql = `DROP FUNCTION \`${params.func}\``; const sql = `DROP FUNCTION ${this._schema}.${params.func}`;
return await this.raw(sql); return await this.raw(sql);
} }
@ -791,18 +791,23 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createFunction (func) { async createFunction (func) {
const parameters = func.parameters.reduce((acc, curr) => { const parameters = 'parameters' in func
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); ? func.parameters.reduce((acc, curr) => {
return acc; acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
}, []).join(','); return acc;
}, []).join(',')
: '';
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''} if (this._schema !== 'public')
this.use(this._schema);
const body = func.returns ? func.sql : '$BODY$\n$BODY$';
const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
RETURNS ${func.returns || 'void'}
LANGUAGE ${func.language} LANGUAGE ${func.language}
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} SECURITY ${func.security}
${func.dataAccess} AS ${body}`;
SQL SECURITY ${func.security}
COMMENT '${func.comment}'
${func.sql}`;
return await this.raw(sql, { split: false }); return await this.raw(sql, { split: false });
} }

View File

@ -15,7 +15,7 @@
<div class="content"> <div class="content">
<form class="form-horizontal"> <form class="form-horizontal">
<div <div
v-for="(parameter, i) in localRoutine.parameters" v-for="(parameter, i) in inParameters"
:key="parameter._id" :key="parameter._id"
class="form-group" class="form-group"
> >
@ -66,6 +66,11 @@ export default {
values: {} values: {}
}; };
}, },
computed: {
inParameters () {
return this.localRoutine.parameters.filter(param => param.context === 'IN');
}
},
created () { created () {
window.addEventListener('keydown', this.onKey); window.addEventListener('keydown', this.onKey);

View File

@ -7,7 +7,7 @@
> >
<template :slot="'header'"> <template :slot="'header'">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }} <i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewFunction') }}
</div> </div>
</template> </template>
<div :slot="'body'"> <div :slot="'body'">
@ -25,7 +25,19 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.languages" class="form-group">
<label class="form-label col-4">
{{ $t('word.language') }}
</label>
<div class="column">
<select v-model="localFunction.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">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.definer') }} {{ $t('word.definer') }}
</label> </label>
@ -53,42 +65,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.returns') }}
</label>
<div class="column">
<div class="input-group">
<select
v-model="localFunction.returns"
class="form-select text-uppercase"
style="width: 0;"
>
<optgroup
v-for="group in workspace.dataTypes"
:key="group.group"
:label="group.group"
>
<option
v-for="type in group.types"
:key="type.name"
:selected="localFunction.returns === type.name"
:value="type.name"
>
{{ type.name }}
</option>
</optgroup>
</select>
<input
v-model="localFunction.returnsLength"
class="form-input"
type="number"
min="0"
>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</label> </label>
@ -111,7 +88,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.functionDataAccess" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('message.dataAccess') }} {{ $t('message.dataAccess') }}
</label> </label>
@ -124,7 +101,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.functionDeterministic" class="form-group">
<div class="col-4" /> <div class="col-4" />
<div class="column"> <div class="column">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline">
@ -152,11 +129,12 @@ export default {
return { return {
localFunction: { localFunction: {
definer: '', definer: '',
sql: 'BEGIN\r\n RETURN NULL;\r\nEND', sql: '',
parameters: [], parameters: [],
name: '', name: '',
comment: '', comment: '',
returns: 'INT', language: null,
returns: null,
returnsLength: 10, returnsLength: 10,
security: 'DEFINER', security: 'DEFINER',
deterministic: false, deterministic: false,
@ -168,9 +146,17 @@ export default {
computed: { computed: {
schema () { schema () {
return this.workspace.breadcrumbs.schema; return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
} }
}, },
mounted () { mounted () {
if (this.customizations.languages)
this.localFunction.language = this.customizations.languages[0];
if (this.customizations.procedureSql)
this.localFunction.sql = this.customizations.procedureSql;
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); this.$refs.firstInput.focus();
}, 20); }, 20);

View File

@ -25,6 +25,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="localRoutine.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') }}
@ -121,6 +133,7 @@ export default {
parameters: [], parameters: [],
name: '', name: '',
comment: '', comment: '',
language: null,
security: 'DEFINER', security: 'DEFINER',
deterministic: false, deterministic: false,
dataAccess: 'CONTAINS SQL' dataAccess: 'CONTAINS SQL'
@ -137,6 +150,9 @@ export default {
} }
}, },
mounted () { mounted () {
if (this.customizations.languages)
this.localRoutine.language = this.customizations.languages[0];
if (this.customizations.procedureSql) if (this.customizations.procedureSql)
this.localRoutine.sql = this.customizations.procedureSql; this.localRoutine.sql = this.customizations.procedureSql;
setTimeout(() => { setTimeout(() => {

View File

@ -248,9 +248,11 @@ export default {
switch (this.workspace.client) { // TODO: move in a better place switch (this.workspace.client) { // TODO: move in a better place
case 'maria': case 'maria':
case 'mysql': case 'mysql':
case 'pg':
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`; sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
break; break;
case 'pg':
sql = `SELECT ${this.localElement.name}(${params.join(',')})`;
break;
case 'mssql': case 'mssql':
sql = `SELECT ${this.localElement.name} ${params.join(',')}`; sql = `SELECT ${this.localElement.name} ${params.join(',')}`;
break; break;

View File

@ -26,7 +26,19 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <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">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.definer') }} {{ $t('word.definer') }}
</label> </label>
@ -81,6 +93,7 @@
</optgroup> </optgroup>
</select> </select>
<input <input
v-if="customizations.parametersLength"
v-model="optionsProxy.returnsLength" v-model="optionsProxy.returnsLength"
class="form-input" class="form-input"
type="number" type="number"
@ -89,7 +102,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.comment" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</label> </label>
@ -112,7 +125,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.functionDataAccess" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('message.dataAccess') }} {{ $t('message.dataAccess') }}
</label> </label>
@ -125,7 +138,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.functionDeterministic" class="form-group">
<div class="col-4" /> <div class="col-4" />
<div class="column"> <div class="column">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline">
@ -159,6 +172,9 @@ export default {
computed: { computed: {
isTableNameValid () { isTableNameValid () {
return this.optionsProxy.name !== ''; return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
} }
}, },
created () { created () {

View File

@ -49,7 +49,7 @@
<div class="tile-title"> <div class="tile-title">
{{ param.name }} {{ param.name }}
</div> </div>
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }}</small> <small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }} · {{ param.context }}</small>
</div> </div>
<div class="tile-action"> <div class="tile-action">
<button <button
@ -106,7 +106,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.parametersLength" class="form-group">
<label class="form-label col-3"> <label class="form-label col-3">
{{ $t('word.length') }} {{ $t('word.length') }}
</label> </label>
@ -119,6 +119,37 @@
> >
</div> </div>
</div> </div>
<div v-if="customizations.functionContext" class="form-group">
<label class="form-label col-3">
{{ $t('word.context') }}
</label>
<div class="column">
<label 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
</label>
</div>
</div>
</form> </form>
<div v-if="!parametersProxy.length" class="empty"> <div v-if="!parametersProxy.length" class="empty">
<div class="empty-icon"> <div class="empty-icon">
@ -168,6 +199,9 @@ export default {
}, },
isChanged () { isChanged () {
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy); return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
},
customizations () {
return this.workspace.customizations;
} }
}, },
mounted () { mounted () {

View File

@ -281,9 +281,11 @@ export default {
switch (this.connection.client) { // TODO: move in a better place switch (this.connection.client) { // TODO: move in a better place
case 'maria': case 'maria':
case 'mysql': case 'mysql':
case 'pg':
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`; sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
break; break;
case 'pg':
sql = `SELECT ${this.originalFunction.name}(${params.join(',')})`;
break;
case 'mssql': case 'mssql':
sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`; sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`;
break; break;