From 82fdc0bcd7514b321c1c9852a773adacf81baf87 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 5 Jan 2021 17:25:18 +0100 Subject: [PATCH] feat: stored routines edit --- src/main/ipc-handlers/index.js | 2 + src/main/ipc-handlers/routines.js | 43 +++ src/main/libs/clients/MySQLClient.js | 124 +++++++- src/renderer/components/ModalNewTrigger.vue | 4 - src/renderer/components/Workspace.vue | 11 +- .../WorkspaceExploreBarDatabase.vue | 32 +- .../WorkspaceExploreBarDatabaseContext.vue | 9 +- .../WorkspacePropsRoutineOptionsModal.vue | 145 +++++++++ .../WorkspacePropsRoutineParamsModal.vue | 287 ++++++++++++++++++ .../components/WorkspacePropsTabRoutine.vue | 262 ++++++++++++++++ src/renderer/i18n/en-US.js | 12 +- src/renderer/ipc-api/Routines.js | 20 ++ .../store/modules/workspaces.store.js | 1 + 13 files changed, 937 insertions(+), 15 deletions(-) create mode 100644 src/main/ipc-handlers/routines.js create mode 100644 src/renderer/components/WorkspacePropsRoutineOptionsModal.vue create mode 100644 src/renderer/components/WorkspacePropsRoutineParamsModal.vue create mode 100644 src/renderer/components/WorkspacePropsTabRoutine.vue create mode 100644 src/renderer/ipc-api/Routines.js diff --git a/src/main/ipc-handlers/index.js b/src/main/ipc-handlers/index.js index 9b5672b9..666e9090 100644 --- a/src/main/ipc-handlers/index.js +++ b/src/main/ipc-handlers/index.js @@ -2,6 +2,7 @@ import connection from './connection'; import tables from './tables'; import views from './views'; import triggers from './triggers'; +import routines from './routines'; import updates from './updates'; import application from './application'; import database from './database'; @@ -14,6 +15,7 @@ export default () => { tables(connections); views(connections); triggers(connections); + routines(connections); database(connections); users(connections); updates(); diff --git a/src/main/ipc-handlers/routines.js b/src/main/ipc-handlers/routines.js new file mode 100644 index 00000000..b1b232e7 --- /dev/null +++ b/src/main/ipc-handlers/routines.js @@ -0,0 +1,43 @@ +import { ipcMain } from 'electron'; + +export default (connections) => { + ipcMain.handle('get-routine-informations', async (event, params) => { + try { + const result = await connections[params.uid].getRoutineInformations(params); + return { status: 'success', response: result }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); + + ipcMain.handle('drop-routine', async (event, params) => { + try { + await connections[params.uid].dropRoutine(params); + return { status: 'success' }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); + + ipcMain.handle('alter-routine', async (event, params) => { + try { + await connections[params.uid].alterRoutine(params); + return { status: 'success' }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); + + ipcMain.handle('create-routine', async (event, params) => { + try { + await connections[params.uid].createRoutine(params); + return { status: 'success' }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); +}; diff --git a/src/main/libs/clients/MySQLClient.js b/src/main/libs/clients/MySQLClient.js index 65507407..a1339322 100644 --- a/src/main/libs/clients/MySQLClient.js +++ b/src/main/libs/clients/MySQLClient.js @@ -104,6 +104,20 @@ export class MySQLClient extends AntaresCore { }; }); + // FUNCTIONS + const remappedFunctions = functions.filter(func => func.Db === db.Database).map(func => { + return { + name: func.Name, + type: func.Type, + definer: func.Definer, + created: func.Created, + updated: func.Modified, + comment: func.Comment, + charset: func.character_set_client, + security: func.Security_type + }; + }); + // SCHEDULERS const remappedSchedulers = schedulers.filter(scheduler => scheduler.Db === db.Database).map(scheduler => { return { @@ -148,7 +162,7 @@ export class MySQLClient extends AntaresCore { return { name: db.Database, tables: remappedTables, - functions: functions.filter(func => func.Db === db.Database), // TODO: remap functions + functions: remappedFunctions, procedures: remappedProcedures, triggers: remappedTriggers, schedulers: remappedSchedulers @@ -355,7 +369,7 @@ export class MySQLClient extends AntaresCore { return results.rows.map(row => { return { definer: row['SQL Original Statement'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], - sql: row['SQL Original Statement'].match(/BEGIN(.*)END/gs)[0], + sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], name: row.Trigger, table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0], event1: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0], @@ -405,9 +419,111 @@ export class MySQLClient extends AntaresCore { */ async createTrigger (trigger) { const sql = `CREATE ${trigger.definer ? `DEFINER=${trigger.definer} ` : ''}TRIGGER \`${trigger.name}\` ${trigger.event1} ${trigger.event2} ON \`${trigger.table}\` FOR EACH ROW ${trigger.sql}`; + return await this.raw(sql, { split: false }); + } + + /** + * SHOW CREATE PROCEDURE + * + * @returns {Array.} view informations + * @memberof MySQLClient + */ + async getRoutineInformations ({ schema, routine }) { + const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``; + const results = await this.raw(sql); + + return results.rows.map(row => { + const parameters = row['Create Procedure'] + .match(/(?<=\().*?(?=\))/s)[0] + .replaceAll('\r', '') + .replaceAll('\t', '') + .split(',') + .map(el => { + const param = el.split(' '); + return { + name: param[1] ? param[1].replaceAll('`', '') : '', + type: param[2] ? param[2].replace(',', '') : '', + context: param[0] ? param[0].replace('\n', '') : '' + }; + }).filter(el => el.name); + + let dataAccess = 'CONTAINS SQL'; + if (row['Create Procedure'].includes('NO SQL')) + dataAccess = 'NO SQL'; + if (row['Create Procedure'].includes('READS SQL DATA')) + dataAccess = 'READS SQL DATA'; + if (row['Create Procedure'].includes('MODIFIES SQL DATA')) + dataAccess = 'MODIFIES SQL DATA'; + + return { + definer: row['Create Procedure'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], + sql: row['Create Procedure'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], + parameters, + name: row.Procedure, + comment: row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '', + security: row['Create Procedure'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER', + deterministic: row['Create Procedure'].includes('DETERMINISTIC'), + dataAccess + }; + })[0]; + } + + /** + * DROP PROCEDURE + * + * @returns {Array.} parameters + * @memberof MySQLClient + */ + async dropRoutine (params) { + const sql = `DROP PROCEDURE \`${params.routine}\``; return await this.raw(sql); } + /** + * ALTER PROCEDURE + * + * @returns {Array.} parameters + * @memberof MySQLClient + */ + async alterRoutine (params) { + const { routine } = params; + const tempProcedure = Object.assign({}, routine); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createRoutine(tempProcedure); + await this.dropRoutine({ routine: tempProcedure.name }); + await this.dropRoutine({ routine: routine.oldName }); + await this.createRoutine(routine); + } + catch (err) { + return Promise.reject(err); + } + } + + /** + * CREATE PROCEDURE + * + * @returns {Array.} parameters + * @memberof MySQLClient + */ + async createRoutine (routine) { + const parameters = routine.parameters.reduce((acc, curr) => { + acc.push(`${curr.context} \`${curr.name}\` ${curr.type}`); + return acc; + }, []).join(','); + + const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters}) + LANGUAGE SQL + ${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} + ${routine.dataAccess} + SQL SECURITY ${routine.security} + COMMENT '${routine.comment}' + ${routine.sql}`; + + return await this.raw(sql, { split: false }); + } + /** * SHOW COLLATION * @@ -709,6 +825,7 @@ export class MySQLClient extends AntaresCore { * @param {object} args * @param {boolean} args.nest * @param {boolean} args.details + * @param {boolean} args.split * @returns {Promise} * @memberof MySQLClient */ @@ -716,13 +833,14 @@ export class MySQLClient extends AntaresCore { args = { nest: false, details: false, + split: true, ...args }; const nestTables = args.nest ? '.' : false; const resultsArr = []; let paramsArr = []; let selectedFields = []; - const queries = sql.split(';'); + const queries = args.split ? sql.split(';') : [sql]; if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder diff --git a/src/renderer/components/ModalNewTrigger.vue b/src/renderer/components/ModalNewTrigger.vue index 733388cb..dcec2233 100644 --- a/src/renderer/components/ModalNewTrigger.vue +++ b/src/renderer/components/ModalNewTrigger.vue @@ -89,7 +89,6 @@ diff --git a/src/renderer/components/WorkspacePropsRoutineParamsModal.vue b/src/renderer/components/WorkspacePropsRoutineParamsModal.vue new file mode 100644 index 00000000..c494112a --- /dev/null +++ b/src/renderer/components/WorkspacePropsRoutineParamsModal.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/src/renderer/components/WorkspacePropsTabRoutine.vue b/src/renderer/components/WorkspacePropsTabRoutine.vue new file mode 100644 index 00000000..6c4cd8d6 --- /dev/null +++ b/src/renderer/components/WorkspacePropsTabRoutine.vue @@ -0,0 +1,262 @@ + + + diff --git a/src/renderer/i18n/en-US.js b/src/renderer/i18n/en-US.js index 4a071d43..b6cda7ec 100644 --- a/src/renderer/i18n/en-US.js +++ b/src/renderer/i18n/en-US.js @@ -74,7 +74,11 @@ module.exports = { trigger: 'Trigger | Triggers', storedRoutine: 'Stored routine | Stored routines', scheduler: 'Scheduler | Schedulers', - event: 'Event' + event: 'Event', + parameters: 'Parameters', + function: 'Function | Functions', + deterministic: 'Deterministic', + context: 'Context' }, message: { appWelcome: 'Welcome to Antares SQL Client!', @@ -148,7 +152,11 @@ module.exports = { createNewView: 'Create new view', deleteTrigger: 'Delete trigger', createNewTrigger: 'Create new trigger', - currentUser: 'Current user' + currentUser: 'Current user', + routineBody: 'Routine body', + dataAccess: 'Data access', + thereAreNoParameters: 'There are no parameters', + createNewParameter: 'Create new parameter' }, // Date and Time short: { diff --git a/src/renderer/ipc-api/Routines.js b/src/renderer/ipc-api/Routines.js new file mode 100644 index 00000000..49ac3610 --- /dev/null +++ b/src/renderer/ipc-api/Routines.js @@ -0,0 +1,20 @@ +'use strict'; +import { ipcRenderer } from 'electron'; + +export default class { + static getRoutineInformations (params) { + return ipcRenderer.invoke('get-routine-informations', params); + } + + static dropRoutine (params) { + return ipcRenderer.invoke('drop-routine', params); + } + + static alterRoutine (params) { + return ipcRenderer.invoke('alter-routine', params); + } + + static createRoutine (params) { + return ipcRenderer.invoke('create-routine', params); + } +} diff --git a/src/renderer/store/modules/workspaces.store.js b/src/renderer/store/modules/workspaces.store.js index d9e04e58..5736f9e9 100644 --- a/src/renderer/store/modules/workspaces.store.js +++ b/src/renderer/store/modules/workspaces.store.js @@ -345,6 +345,7 @@ export default { table: null, trigger: null, procedure: null, + function: null, scheduler: null, view: null };