diff --git a/src/common/customizations/defaults.js b/src/common/customizations/defaults.js index 41d86bdc..8671bd6f 100644 --- a/src/common/customizations/defaults.js +++ b/src/common/customizations/defaults.js @@ -68,6 +68,8 @@ module.exports = { triggerTableInName: false, triggerUpdateColumns: false, triggerOnlyRename: false, + triggerFunctionSql: false, + triggerFunctionlanguages: false, parametersLength: false, languages: false }; diff --git a/src/common/customizations/postgresql.js b/src/common/customizations/postgresql.js index 872e2f6b..d3ac99e6 100644 --- a/src/common/customizations/postgresql.js +++ b/src/common/customizations/postgresql.js @@ -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, @@ -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 ', diff --git a/src/main/ipc-handlers/functions.js b/src/main/ipc-handlers/functions.js index ed3cc554..0d793a61 100644 --- a/src/main/ipc-handlers/functions.js +++ b/src/main/ipc-handlers/functions.js @@ -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); @@ -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() }; + } + }); }; diff --git a/src/main/libs/clients/PostgreSQLClient.js b/src/main/libs/clients/PostgreSQLClient.js index 86e9cf10..d828df7a 100644 --- a/src/main/libs/clients/PostgreSQLClient.js +++ b/src/main/libs/clients/PostgreSQLClient.js @@ -215,6 +215,7 @@ export class PostgreSQLClient extends AntaresCore { functions: [], procedures: [], triggers: [], + triggerFunctions: [], schedulers: [] }; } @@ -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'} @@ -833,6 +834,48 @@ export class PostgreSQLClient extends AntaresCore { return await this.raw(sql, { split: false }); } + /** + * ALTER TRIGGER FUNCTION + * + * @returns {Array.} 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.} 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 * diff --git a/src/renderer/components/BaseContextMenu.vue b/src/renderer/components/BaseContextMenu.vue index fbdcad44..7ddb2e1d 100644 --- a/src/renderer/components/BaseContextMenu.vue +++ b/src/renderer/components/BaseContextMenu.vue @@ -104,6 +104,7 @@ export default { cursor: pointer; justify-content: space-between; position: relative; + white-space: nowrap; .context-submenu { border-radius: $border-radius; diff --git a/src/renderer/components/ModalNewFunction.vue b/src/renderer/components/ModalNewFunction.vue index 4c319923..df4f52a7 100644 --- a/src/renderer/components/ModalNewFunction.vue +++ b/src/renderer/components/ModalNewFunction.vue @@ -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); diff --git a/src/renderer/components/ModalNewTriggerFunction.vue b/src/renderer/components/ModalNewTriggerFunction.vue new file mode 100644 index 00000000..3bd1d726 --- /dev/null +++ b/src/renderer/components/ModalNewTriggerFunction.vue @@ -0,0 +1,132 @@ + + + diff --git a/src/renderer/components/Workspace.vue b/src/renderer/components/Workspace.vue index 360d3cad..e8e53c93 100644 --- a/src/renderer/components/Workspace.vue +++ b/src/renderer/components/Workspace.vue @@ -127,6 +127,12 @@ :connection="connection" :function="workspace.breadcrumbs.function" /> + + @@ -147,6 +154,7 @@ import ModalNewView from '@/components/ModalNewView'; import ModalNewTrigger from '@/components/ModalNewTrigger'; import ModalNewRoutine from '@/components/ModalNewRoutine'; import ModalNewFunction from '@/components/ModalNewFunction'; +import ModalNewTriggerFunction from '@/components/ModalNewTriggerFunction'; import ModalNewScheduler from '@/components/ModalNewScheduler'; export default { @@ -163,6 +171,7 @@ export default { ModalNewTrigger, ModalNewRoutine, ModalNewFunction, + ModalNewTriggerFunction, ModalNewScheduler }, props: { @@ -179,6 +188,7 @@ export default { isNewTriggerModal: false, isNewRoutineModal: false, isNewFunctionModal: false, + isNewTriggerFunctionModal: false, isNewSchedulerModal: false, localWidth: null, @@ -403,6 +413,13 @@ export default { hideCreateFunctionModal () { this.isNewFunctionModal = false; }, + showCreateTriggerFunctionModal () { + this.closeDatabaseContext(); + this.isNewTriggerFunctionModal = true; + }, + hideCreateTriggerFunctionModal () { + this.isNewTriggerFunctionModal = false; + }, showCreateSchedulerModal () { this.closeDatabaseContext(); this.isNewSchedulerModal = true; @@ -426,6 +443,22 @@ export default { else this.addNotification({ status: 'error', message: response }); }, + async openCreateTriggerFunctionEditor (payload) { + const params = { + uid: this.connection.uid, + ...payload + }; + + const { status, response } = await Functions.createTriggerFunction(params); + + if (status === 'success') { + await this.refresh(); + this.changeBreadcrumbs({ schema: this.selectedDatabase, triggerFunction: payload.name }); + this.selectTab({ uid: this.workspace.uid, tab: 'prop' }); + } + else + this.addNotification({ status: 'error', message: response }); + }, async openCreateSchedulerEditor (payload) { const params = { uid: this.connection.uid, diff --git a/src/renderer/components/WorkspaceExploreBarMiscContext.vue b/src/renderer/components/WorkspaceExploreBarMiscContext.vue index d07a89c4..819a27b4 100644 --- a/src/renderer/components/WorkspaceExploreBarMiscContext.vue +++ b/src/renderer/components/WorkspaceExploreBarMiscContext.vue @@ -84,6 +84,7 @@ export default { case 'procedure': return this.$t('message.deleteRoutine'); case 'function': + case 'triggerFunction': return this.$t('message.deleteFunction'); case 'scheduler': return this.$t('message.deleteScheduler'); @@ -135,6 +136,7 @@ export default { }); break; case 'function': + case 'triggerFunction': res = await Functions.dropFunction({ uid: this.selectedWorkspace, func: this.selectedMisc.name diff --git a/src/renderer/components/WorkspaceExploreBarSchema.vue b/src/renderer/components/WorkspaceExploreBarSchema.vue index 70397455..57dd6991 100644 --- a/src/renderer/components/WorkspaceExploreBarSchema.vue +++ b/src/renderer/components/WorkspaceExploreBarSchema.vue @@ -93,6 +93,34 @@ +
+
+ + + {{ $tc('word.triggerFunction', 2) }} + +
+
+ +
+
+
+
+
@@ -189,6 +217,11 @@ export default { filteredFunctions () { return this.database.functions.filter(func => func.name.search(this.searchTerm) >= 0); }, + filteredTriggerFunctions () { + return this.database.triggerFunctions + ? this.database.triggerFunctions.filter(func => func.name.search(this.searchTerm) >= 0) + : []; + }, filteredSchedulers () { return this.database.schedulers.filter(scheduler => scheduler.name.search(this.searchTerm) >= 0); }, diff --git a/src/renderer/components/WorkspaceExploreBarSchemaContext.vue b/src/renderer/components/WorkspaceExploreBarSchemaContext.vue index c553e26f..7e42ae76 100644 --- a/src/renderer/components/WorkspaceExploreBarSchemaContext.vue +++ b/src/renderer/components/WorkspaceExploreBarSchemaContext.vue @@ -42,6 +42,13 @@ > {{ $tc('word.function', 1) }}
+
+ {{ $tc('word.triggerFunction', 1) }} +
this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``) : true; + return this.originalFunction + ? this.workspace.users.some(user => this.originalFunction.definer === `\`${user.name}\`@\`${user.host}\``) + : true; }, schemaTables () { const schemaTables = this.workspace.structure diff --git a/src/renderer/components/WorkspacePropsTabTriggerFunction.vue b/src/renderer/components/WorkspacePropsTabTriggerFunction.vue new file mode 100644 index 00000000..b3dd0f80 --- /dev/null +++ b/src/renderer/components/WorkspacePropsTabTriggerFunction.vue @@ -0,0 +1,315 @@ + + + diff --git a/src/renderer/components/WorkspacePropsTriggerFunctionOptionsModal.vue b/src/renderer/components/WorkspacePropsTriggerFunctionOptionsModal.vue new file mode 100644 index 00000000..345aa25a --- /dev/null +++ b/src/renderer/components/WorkspacePropsTriggerFunctionOptionsModal.vue @@ -0,0 +1,125 @@ + + + diff --git a/src/renderer/i18n/en-US.js b/src/renderer/i18n/en-US.js index da26498f..169958df 100644 --- a/src/renderer/i18n/en-US.js +++ b/src/renderer/i18n/en-US.js @@ -111,7 +111,8 @@ module.exports = { medium: 'Medium', large: 'Large', row: 'Row | Rows', - cell: 'Cell | Cells' + cell: 'Cell | Cells', + triggerFunction: 'Trigger function | Trigger functions' }, message: { appWelcome: 'Welcome to Antares SQL Client!', diff --git a/src/renderer/ipc-api/Functions.js b/src/renderer/ipc-api/Functions.js index 75ef6e69..9d5da831 100644 --- a/src/renderer/ipc-api/Functions.js +++ b/src/renderer/ipc-api/Functions.js @@ -14,7 +14,15 @@ export default class { return ipcRenderer.invoke('alter-function', params); } + static alterTriggerFunction (params) { + return ipcRenderer.invoke('alter-trigger-function', params); + } + static createFunction (params) { return ipcRenderer.invoke('create-function', params); } + + static createTriggerFunction (params) { + return ipcRenderer.invoke('create-trigger-function', params); + } }