feat(PostgreSQL): procedures management

This commit is contained in:
Fabio Di Stasio 2021-04-10 20:38:46 +02:00
parent d0b3e1b1b8
commit 3dde1c109e
10 changed files with 93 additions and 47 deletions

View File

@ -51,5 +51,6 @@ module.exports = {
viewUpdateOption: false, viewUpdateOption: false,
procedureDeterministic: false, procedureDeterministic: false,
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: false procedureSql: false,
parametersLength: false
}; };

View File

@ -46,5 +46,9 @@ module.exports = {
onUpdate: true, onUpdate: true,
viewAlgorithm: true, viewAlgorithm: true,
viewSqlSecurity: true, viewSqlSecurity: true,
viewUpdateOption: true viewUpdateOption: true,
procedureDeterministic: true,
procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true
}; };

View File

@ -556,7 +556,7 @@ export class PostgreSQLClient extends AntaresCore {
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${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(async row => {
if (!row.pg_get_functiondef) { if (!row.pg_get_functiondef) {
return { return {
definer: null, definer: null,
@ -570,30 +570,37 @@ export class PostgreSQLClient extends AntaresCore {
}; };
} }
const parameters = row.pg_get_functiondef 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[2] ? param[2].replace(')', '').split('(') : ['', null]; LEFT JOIN information_schema.parameters args
return { ON proc.specific_schema = args.specific_schema
name: param[1] ? param[1].replaceAll('`', '') : '', AND proc.specific_name = args.specific_name
type: type[0].replaceAll('\n', ''), WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
length: +type[1] ? +type[1].replace(/\D/g, '') : '', AND proc.routine_type = 'PROCEDURE'
context: param[0] ? param[0].replace('\n', '') : '' AND proc.routine_name = '${routine}'
}; AND proc.specific_schema = '${schema}'
}).filter(el => el.name); ORDER BY procedure_schema,
specific_name,
procedure_name,
args.ordinal_position
`;
let dataAccess = 'CONTAINS SQL'; const results = await this.raw(sql);
if (row.pg_get_functiondef.includes('NO SQL'))
dataAccess = 'NO SQL'; const parameters = results.rows.map(row => {
if (row.pg_get_functiondef.includes('READS SQL DATA')) return {
dataAccess = 'READS SQL DATA'; name: row.parameter_name,
if (row.pg_get_functiondef.includes('MODIFIES SQL DATA')) type: row.data_type.toUpperCase(),
dataAccess = 'MODIFIES SQL DATA'; length: '',
context: row.parameter_mode
};
});
return { return {
definer: '', definer: '',
@ -601,9 +608,9 @@ export class PostgreSQLClient extends AntaresCore {
parameters: parameters || [], parameters: parameters || [],
name: routine, name: routine,
comment: '', comment: '',
security: row.pg_get_functiondef.includes('SECURITY INVOKER') ? 'INVOKER' : 'DEFINER', security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
deterministic: null, deterministic: null,
dataAccess, 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]
}; };
})[0]; })[0];
@ -656,6 +663,9 @@ export class PostgreSQLClient extends AntaresCore {
}, []).join(',') }, []).join(',')
: ''; : '';
if (this._schema !== 'public')
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 SQL
SECURITY ${routine.security} SECURITY ${routine.security}

View File

@ -43,6 +43,7 @@
</template> </template>
<script> <script>
import { NUMBER, FLOAT } from 'common/fieldTypes';
import ConfirmModal from '@/components/BaseConfirmModal'; import ConfirmModal from '@/components/BaseConfirmModal';
export default { export default {
@ -57,7 +58,8 @@ export default {
} }
}, },
props: { props: {
localRoutine: Object localRoutine: Object,
client: String
}, },
data () { data () {
return { return {
@ -82,7 +84,22 @@ export default {
}, },
runRoutine () { runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr) => { const valArr = Object.keys(this.values).reduce((acc, curr) => {
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr]; let qc;
switch (this.client) {
case 'maria':
case 'mysql':
qc = '"';
break;
case 'pg':
qc = '\'';
break;
default:
qc = '"';
}
const param = this.localRoutine.parameters.find(param => param.name === curr);
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
acc.push(value); acc.push(value);
return acc; return acc;
}, []); }, []);

View File

@ -25,7 +25,7 @@
> >
</div> </div>
</div> </div>
<div 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') }}
</label> </label>
@ -53,7 +53,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"> <label class="form-label col-4">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</label> </label>
@ -76,7 +76,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"> <label class="form-label col-4">
{{ $t('message.dataAccess') }} {{ $t('message.dataAccess') }}
</label> </label>
@ -89,7 +89,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.procedureDeterministic" 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">
@ -117,7 +117,7 @@ export default {
return { return {
localRoutine: { localRoutine: {
definer: '', definer: '',
sql: 'BEGIN\r\n\r\nEND', sql: '',
parameters: [], parameters: [],
name: '', name: '',
comment: '', comment: '',
@ -131,9 +131,14 @@ 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.procedureSql)
this.localRoutine.sql = this.customizations.procedureSql;
setTimeout(() => { setTimeout(() => {
this.$refs.firstInput.focus(); this.$refs.firstInput.focus();
}, 20); }, 20);

View File

@ -32,6 +32,7 @@
<ModalAskParameters <ModalAskParameters
v-if="isAskingParameters" v-if="isAskingParameters"
:local-routine="localElement" :local-routine="localElement"
:client="workspace.client"
@confirm="runElement" @confirm="runElement"
@close="hideAskParamsModal" @close="hideAskParamsModal"
/> />
@ -205,13 +206,13 @@ export default {
case 'maria': case 'maria':
case 'mysql': case 'mysql':
case 'pg': case 'pg':
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`; sql = `CALL ${this.localElement.name}(${params.join(',')})`;
break; break;
case 'mssql': case 'mssql':
sql = `EXEC ${this.localElement.name} ${params.join(',')}`; sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
break; break;
default: default:
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`; sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
} }
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true }); this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });

View File

@ -26,7 +26,7 @@
> >
</div> </div>
</div> </div>
<div 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') }}
</label> </label>
@ -54,7 +54,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"> <label class="form-label col-4">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</label> </label>
@ -77,7 +77,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.procedureDataAccess" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('message.dataAccess') }} {{ $t('message.dataAccess') }}
</label> </label>
@ -90,7 +90,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.procedureDeterministic" 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">
@ -124,6 +124,9 @@ export default {
computed: { computed: {
isTableNameValid () { isTableNameValid () {
return this.optionsProxy.name !== ''; return this.optionsProxy.name !== '';
},
customizations () {
return this.workspace.customizations;
} }
}, },
created () { created () {

View File

@ -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>
@ -199,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 () {
@ -235,10 +238,10 @@ export default {
addParameter () { addParameter () {
this.parametersProxy = [...this.parametersProxy, { this.parametersProxy = [...this.parametersProxy, {
_id: uidGen(), _id: uidGen(),
name: `Param${this.i++}`, name: `param${this.i++}`,
type: 'INT', type: this.workspace.dataTypes[0].types[0].name,
context: 'IN', context: 'IN',
length: 10 length: ''
}]; }];
if (this.parametersProxy.length === 1) if (this.parametersProxy.length === 1)

View File

@ -73,6 +73,7 @@
<ModalAskParameters <ModalAskParameters
v-if="isAskingParameters" v-if="isAskingParameters"
:local-routine="localFunction" :local-routine="localFunction"
:client="workspace.client"
@confirm="runFunction" @confirm="runFunction"
@close="hideAskParamsModal" @close="hideAskParamsModal"
/> />

View File

@ -74,6 +74,7 @@
<ModalAskParameters <ModalAskParameters
v-if="isAskingParameters" v-if="isAskingParameters"
:local-routine="localRoutine" :local-routine="localRoutine"
:client="workspace.client"
@confirm="runRoutine" @confirm="runRoutine"
@close="hideAskParamsModal" @close="hideAskParamsModal"
/> />
@ -281,13 +282,13 @@ export default {
case 'maria': case 'maria':
case 'mysql': case 'mysql':
case 'pg': case 'pg':
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`; sql = `CALL ${this.originalRoutine.name}(${params.join(',')})`;
break; break;
case 'mssql': case 'mssql':
sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`; sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`;
break; break;
default: default:
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`; sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
} }
this.newTab({ uid: this.connection.uid, content: sql, autorun: true }); this.newTab({ uid: this.connection.uid, content: sql, autorun: true });