Skip to content

Commit

Permalink
feat(PostgreSQL): trigger functions support
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabio286 committed Jul 3, 2021
1 parent faa07a0 commit 75bbd5f
Show file tree
Hide file tree
Showing 17 changed files with 747 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/common/customizations/defaults.js
Expand Up @@ -68,6 +68,8 @@ module.exports = {
triggerTableInName: false,
triggerUpdateColumns: false,
triggerOnlyRename: false,
triggerFunctionSql: false,
triggerFunctionlanguages: false,
parametersLength: false,
languages: false
};
6 changes: 5 additions & 1 deletion src/common/customizations/postgresql.js
Expand Up @@ -21,12 +21,14 @@ module.exports = {
tableAdd: true,
viewAdd: true,
triggerAdd: true,
triggerFunctionAdd: true,
routineAdd: true,
functionAdd: true,
databaseEdit: false,
tableSettings: true,
viewSettings: true,
triggerSettings: true,
triggerFunctionSettings: true,
routineSettings: true,
functionSettings: true,
indexes: true,
Expand All @@ -36,7 +38,9 @@ module.exports = {
procedureSql: '$BODY$\r\n\r\n$BODY$',
procedureContext: true,
procedureLanguage: true,
functionSql: '$BODY$\r\n\r\n$BODY$',
functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',
triggerFunctionlanguages: ['plpgsql'],
functionContext: true,
functionLanguage: true,
triggerSql: 'EXECUTE PROCEDURE ',
Expand Down
20 changes: 20 additions & 0 deletions src/main/ipc-handlers/functions.js
Expand Up @@ -31,6 +31,16 @@ export default (connections) => {
}
});

ipcMain.handle('alter-trigger-function', async (event, params) => {
try {
await connections[params.uid].alterTriggerFunction(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});

ipcMain.handle('create-function', async (event, params) => {
try {
await connections[params.uid].createFunction(params);
Expand All @@ -40,4 +50,14 @@ export default (connections) => {
return { status: 'error', response: err.toString() };
}
});

ipcMain.handle('create-trigger-function', async (event, params) => {
try {
await connections[params.uid].createTriggerFunction(params);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};
45 changes: 44 additions & 1 deletion src/main/libs/clients/PostgreSQLClient.js
Expand Up @@ -215,6 +215,7 @@ export class PostgreSQLClient extends AntaresCore {
functions: [],
procedures: [],
triggers: [],
triggerFunctions: [],
schedulers: []
};
}
Expand Down Expand Up @@ -822,7 +823,7 @@ export class PostgreSQLClient extends AntaresCore {
if (this._schema !== 'public')
await this.use(this._schema);

const body = func.returns ? func.sql : '$BODY$\n$BODY$';
const body = func.returns ? func.sql : '$function$\n$function$';

const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
RETURNS ${func.returns || 'void'}
Expand All @@ -833,6 +834,48 @@ export class PostgreSQLClient extends AntaresCore {
return await this.raw(sql, { split: false });
}

/**
* ALTER TRIGGER FUNCTION
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async alterTriggerFunction (params) {
const { func } = params;

if (this._schema !== 'public')
await this.use(this._schema);

const body = func.returns ? func.sql : '$function$\n$function$';

const sql = `CREATE OR REPLACE FUNCTION ${this._schema}.${func.name}()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;

return await this.raw(sql, { split: false });
}

/**
* CREATE TRIGGER FUNCTION
*
* @returns {Array.<Object>} parameters
* @memberof PostgreSQLClient
*/
async createTriggerFunction (func) {
if (this._schema !== 'public')
await this.use(this._schema);

const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$';

const sql = `CREATE FUNCTION ${this._schema}.${func.name}()
RETURNS TRIGGER
LANGUAGE ${func.language}
AS ${body}`;

return await this.raw(sql, { split: false });
}

/**
* SELECT * FROM pg_collation
*
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/BaseContextMenu.vue
Expand Up @@ -104,6 +104,7 @@ export default {
cursor: pointer;
justify-content: space-between;
position: relative;
white-space: nowrap;
.context-submenu {
border-radius: $border-radius;
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/ModalNewFunction.vue
Expand Up @@ -155,8 +155,8 @@ export default {
if (this.customizations.languages)
this.localFunction.language = this.customizations.languages[0];
if (this.customizations.procedureSql)
this.localFunction.sql = this.customizations.procedureSql;
if (this.customizations.functionSql)
this.localFunction.sql = this.customizations.functionSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
Expand Down
132 changes: 132 additions & 0 deletions src/renderer/components/ModalNewTriggerFunction.vue
@@ -0,0 +1,132 @@
<template>
<ConfirmModal
:confirm-text="$t('word.confirm')"
size="400"
@confirm="confirmNewFunction"
@hide="$emit('close')"
>
<template :slot="'header'">
<div class="d-flex">
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewFunction') }}
</div>
</template>
<div :slot="'body'">
<form class="form-horizontal">
<div class="form-group">
<label class="form-label col-4">
{{ $t('word.name') }}
</label>
<div class="column">
<input
ref="firstInput"
v-model="localFunction.name"
class="form-input"
type="text"
>
</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="localFunction.language" class="form-select">
<option v-for="language in customizations.triggerFunctionlanguages" :key="language">
{{ language }}
</option>
</select>
</div>
</div>
<div v-if="customizations.definer" class="form-group">
<label class="form-label col-4">
{{ $t('word.definer') }}
</label>
<div class="column">
<select
v-if="workspace.users.length"
v-model="localFunction.definer"
class="form-select"
>
<option value="">
{{ $t('message.currentUser') }}
</option>
<option
v-for="user in workspace.users"
:key="`${user.name}@${user.host}`"
:value="`\`${user.name}\`@\`${user.host}\``"
>
{{ user.name }}@{{ user.host }}
</option>
</select>
<select v-if="!workspace.users.length" class="form-select">
<option value="">
{{ $t('message.currentUser') }}
</option>
</select>
</div>
</div>
<div v-if="customizations.comment" class="form-group">
<label class="form-label col-4">
{{ $t('word.comment') }}
</label>
<div class="column">
<input
v-model="localFunction.comment"
class="form-input"
type="text"
>
</div>
</div>
</form>
</div>
</ConfirmModal>
</template>

<script>
import ConfirmModal from '@/components/BaseConfirmModal';
export default {
name: 'ModalNewTriggerFunction',
components: {
ConfirmModal
},
props: {
workspace: Object
},
data () {
return {
localFunction: {
definer: '',
sql: '',
name: '',
comment: '',
language: null
},
isOptionsChanging: false
};
},
computed: {
schema () {
return this.workspace.breadcrumbs.schema;
},
customizations () {
return this.workspace.customizations;
}
},
mounted () {
if (this.customizations.triggerFunctionlanguages)
this.localFunction.language = this.customizations.triggerFunctionlanguages[0];
if (this.customizations.triggerFunctionSql)
this.localFunction.sql = this.customizations.triggerFunctionSql;
setTimeout(() => {
this.$refs.firstInput.focus();
}, 20);
},
methods: {
confirmNewFunction () {
this.$emit('open-create-function-editor', this.localFunction);
}
}
};
</script>
10 changes: 10 additions & 0 deletions src/renderer/components/Workspace.vue
Expand Up @@ -127,6 +127,12 @@
:connection="connection"
:function="workspace.breadcrumbs.function"
/>
<WorkspacePropsTabTriggerFunction
v-show="selectedTab === 'prop' && workspace.breadcrumbs.triggerFunction"
:is-selected="selectedTab === 'prop'"
:connection="connection"
:function="workspace.breadcrumbs.triggerFunction"
/>
<WorkspacePropsTabScheduler
v-show="selectedTab === 'prop' && workspace.breadcrumbs.scheduler"
:is-selected="selectedTab === 'prop'"
Expand Down Expand Up @@ -165,6 +171,7 @@ import WorkspacePropsTabView from '@/components/WorkspacePropsTabView';
import WorkspacePropsTabTrigger from '@/components/WorkspacePropsTabTrigger';
import WorkspacePropsTabRoutine from '@/components/WorkspacePropsTabRoutine';
import WorkspacePropsTabFunction from '@/components/WorkspacePropsTabFunction';
import WorkspacePropsTabTriggerFunction from '@/components/WorkspacePropsTabTriggerFunction';
import WorkspacePropsTabScheduler from '@/components/WorkspacePropsTabScheduler';
import ModalProcessesList from '@/components/ModalProcessesList';
Expand All @@ -179,6 +186,7 @@ export default {
WorkspacePropsTabTrigger,
WorkspacePropsTabRoutine,
WorkspacePropsTabFunction,
WorkspacePropsTabTriggerFunction,
WorkspacePropsTabScheduler,
ModalProcessesList
},
Expand Down Expand Up @@ -208,6 +216,7 @@ export default {
if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true;
if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true;
if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.triggerFunction && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true;
return false;
},
Expand All @@ -219,6 +228,7 @@ export default {
this.workspace.breadcrumbs.trigger === null &&
this.workspace.breadcrumbs.procedure === null &&
this.workspace.breadcrumbs.function === null &&
this.workspace.breadcrumbs.triggerFunction === null &&
this.workspace.breadcrumbs.scheduler === null &&
['data', 'prop'].includes(this.workspace.selected_tab)
) ||
Expand Down

0 comments on commit 75bbd5f

Please sign in to comment.