From 903297a1e96a962a08620b5f4ae917f167fb6cf9 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Thu, 2 Jan 2025 12:48:28 +0100 Subject: [PATCH 1/9] feat: add scheduler events to analyser --- packages/tools/src/DatabaseAnalyser.ts | 17 ++++++++- packages/types/dbinfo.d.ts | 15 ++++++++ .../src/backend/Analyser.js | 38 +++++++++++++++++++ .../src/backend/sql/index.js | 2 + .../src/backend/sql/schedulerEvents.js | 34 +++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js diff --git a/packages/tools/src/DatabaseAnalyser.ts b/packages/tools/src/DatabaseAnalyser.ts index b0e622616..31c86d93a 100644 --- a/packages/tools/src/DatabaseAnalyser.ts +++ b/packages/tools/src/DatabaseAnalyser.ts @@ -10,7 +10,16 @@ import { extractErrorLogData } from './stringTools'; const logger = getLogger('dbAnalyser'); -const STRUCTURE_FIELDS = ['tables', 'collections', 'views', 'matviews', 'functions', 'procedures', 'triggers']; +const STRUCTURE_FIELDS = [ + 'tables', + 'collections', + 'views', + 'matviews', + 'functions', + 'procedures', + 'triggers', + 'schedulerEvents', +]; const fp_pick = arg => array => _pick(array, arg); @@ -70,7 +79,9 @@ export class DatabaseAnalyser { } async fullAnalysis() { - logger.debug(`Performing full analysis, DB=${dbNameLogCategory(this.dbhan.database)}, engine=${this.driver.engine}`); + logger.debug( + `Performing full analysis, DB=${dbNameLogCategory(this.dbhan.database)}, engine=${this.driver.engine}` + ); const res = this.addEngineField(await this._runAnalysis()); // console.log('FULL ANALYSIS', res); return res; @@ -255,6 +266,7 @@ export class DatabaseAnalyser { ...this.getDeletedObjectsForField(snapshot, 'procedures'), ...this.getDeletedObjectsForField(snapshot, 'functions'), ...this.getDeletedObjectsForField(snapshot, 'triggers'), + ...this.getDeletedObjectsForField(snapshot, 'schedulerEvents'), ]; } @@ -355,6 +367,7 @@ export class DatabaseAnalyser { functions: [], procedures: [], triggers: [], + schedulerEvents: [], }; } diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index 66529f00b..0ef99a779 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -157,6 +157,20 @@ export interface TriggerInfo extends SqlObjectInfo { eventType?: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE'; } +export interface SchedulerEventInfo extends SqlObjectInfo { + definer: string; + eventType: 'RECURRING' | 'ONE TIME'; + onCompletion: 'PRESERVE' | 'NOT PRESERVE'; + status: 'ENABLED' | 'DISABLED'; + lastExecuted?: string; + intervalValue: number; + intervalField: string; + starts: string; + executeAt: string; + enableSql: string; + disableSql: string; +} + export interface SchemaInfo { objectId?: string; schemaName: string; @@ -171,6 +185,7 @@ export interface DatabaseInfoObjects { procedures: ProcedureInfo[]; functions: FunctionInfo[]; triggers: TriggerInfo[]; + schedulerEvents: SchedulerEventInfo[]; } export interface DatabaseInfo extends DatabaseInfoObjects { diff --git a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js index d904b3654..1e7ae1b42 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js @@ -164,6 +164,9 @@ class Analyser extends DatabaseAnalyser { this.feedback({ analysingMessage: 'Loading triggers' }); const triggers = await this.analyserQuery('triggers'); + this.feedback({ analysingMessage: 'Loading scehduler events' }); + const schedulerEvents = await this.analyserQuery('schedulerEvents'); + const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']); this.feedback({ analysingMessage: 'Finalizing DB structure' }); @@ -249,6 +252,23 @@ class Analyser extends DatabaseAnalyser { tableName: row.tableName, createSql: `CREATE TRIGGER ${row.triggerName} ${row.triggerTiming} ${row.eventType} ON ${row.tableName} FOR EACH ROW ${row.definition}`, })), + schedulerEvents: schedulerEvents.rows.map(row => ({ + contentHash: _.isDate(row.LAST_ALTERED) ? row.LAST_ALTERED.toISOString() : row.LAST_ALTERED, + pureName: row.EVENT_NAME, + createSql: row.CREATE_SQL, + enableSql: row.ENABLE_SQL, + disableSql: row.DISABLE_SQL, + objectId: row.EVENT_NAME, + intervalValue: row.INTERVAL_VALUE, + intervalField: row.INTERVAL_FIELD, + starts: row.STARTS, + status: row.STATUS, + executeAt: row.EXECUTE_AT, + lastExecuted: row.LAST_EXECUTED, + eventType: row.EVENT_TYPE, + definer: row.DEFINER, + objectTypeField: 'schedulerEvents', + })), }; this.feedback({ analysingMessage: null }); return res; @@ -258,6 +278,7 @@ class Analyser extends DatabaseAnalyser { const tableModificationsQueryData = await this.analyserQuery('tableModifications'); const procedureModificationsQueryData = await this.analyserQuery('procedureModifications'); const functionModificationsQueryData = await this.analyserQuery('functionModifications'); + const schedulerEvents = await this.analyserQuery('schedulerEvents'); return { tables: tableModificationsQueryData.rows @@ -285,6 +306,23 @@ class Analyser extends DatabaseAnalyser { objectId: x.Name, pureName: x.Name, })), + schedulerEvents: schedulerEvents.rows.map(row => ({ + contentHash: _.isDate(row.LAST_ALTERED) ? row.LAST_ALTERED.toISOString() : row.LAST_ALTERED, + pureName: row.EVENT_NAME, + createSql: row.CREATE_SQL, + enableSql: row.ENABLE_SQL, + disableSql: row.DISABLE_SQL, + objectId: row.EVENT_NAME, + intervalValue: row.INTERVAL_VALUE, + intervalField: row.INTERVAL_FIELD, + starts: row.STARTS, + status: row.STATUS, + executeAt: row.EXECUTE_AT, + lastExecuted: row.LAST_EXECUTED, + eventType: row.EVENT_TYPE, + definer: row.DEFINER, + objectTypeField: 'schedulerEvents', + })), }; } } diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js index 1a7eda8d7..ab23eeaca 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js @@ -12,6 +12,7 @@ const uniqueNames = require('./uniqueNames'); const viewTexts = require('./viewTexts'); const parameters = require('./parameters'); const triggers = require('./triggers'); +const schedulerEvents = require('./schedulerEvents.js'); module.exports = { columns, @@ -28,4 +29,5 @@ module.exports = { uniqueNames, viewTexts, triggers, + schedulerEvents, }; diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js new file mode 100644 index 000000000..9558eb70f --- /dev/null +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js @@ -0,0 +1,34 @@ +module.exports = ` +SELECT + EVENT_SCHEMA, + EVENT_NAME, + DEFINER, + EVENT_TYPE, + EXECUTE_AT, + INTERVAL_VALUE, + INTERVAL_FIELD, + CREATED, + LAST_EXECUTED, + LAST_ALTERED, + STARTS, + ENDS, + STATUS, + ON_COMPLETION, + CONCAT( + 'CREATE EVENT ', EVENT_NAME, ' ', + CASE WHEN EVENT_TYPE = 'RECURRING' THEN + 'ON SCHEDULE EVERY ' + ELSE 'ON SCHEDULE AT ' + END, + CASE WHEN EVENT_TYPE = 'RECURRING' THEN + CONCAT(INTERVAL_VALUE, ' ', INTERVAL_FIELD) + ELSE DATE_FORMAT(EXECUTE_AT, '%Y-%m-%d %H:%i:%s') + END, + ' DO ', + EVENT_DEFINITION + ) AS CREATE_SQL, + CONCAT('ALTER EVENT ', EVENT_NAME, ' DISABLE;') AS DISABLE_SQL, + CONCAT('ALTER EVENT ', EVENT_NAME, ' ENABLE;') AS ENABLE_SQL +FROM INFORMATION_SCHEMA.EVENTS +WHERE EVENT_SCHEMA = '#DATABASE#' AND EVENT_NAME =OBJECT_ID_CONDITION +`; From 891fb1529066cdff0426d0da8fe9518c8739a1b6 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Thu, 2 Jan 2025 14:38:01 +0100 Subject: [PATCH 2/9] feat: add scheduler events to ui --- .../src/appobj/DatabaseObjectAppObject.svelte | 57 +++++++++++++++++++ packages/web/src/appobj/appObjectTools.ts | 8 +++ packages/web/src/icons/FontIcon.svelte | 1 + .../web/src/utility/applyScriptTemplate.ts | 7 +++ packages/web/src/widgets/SqlObjectList.svelte | 13 +++-- 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index daf58d4fd..11a578c08 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -42,6 +42,7 @@ functions: 'img function', queries: 'img query-data', triggers: 'icon trigger', + schedulerEvents: 'icon scheduler-event', }; const defaultTabs = { @@ -87,6 +88,8 @@ isDropCollection?: boolean; isRenameCollection?: boolean; isDuplicateCollection?: boolean; + isDisableEvent?: boolean; + isEnableEvent?: boolean; submenu?: DbObjMenuItem[]; } @@ -383,6 +386,21 @@ }, ...(driver?.getScriptTemplates?.('collections') || []), ]; + case 'schedulerEvents': + return [ + ...defaultDatabaseObjectAppObjectActions['schedulerEvents'], + { + divider: true, + }, + { + label: 'Disable', + isDisableEvent: true, + }, + { + label: 'Enable', + isEnableEvent: true, + }, + ]; } } @@ -479,6 +497,36 @@ x => x.schemaName == data.schemaName && x.pureName == data.pureName ); }); + } else if (menu.isDisableEvent) { + const { conid, database, disableSql } = data; + const driver = await getDriver(); + const dmp = driver.createDumper(); + dmp.put(disableSql); + + const sql = dmp.s; + + showModal(ConfirmSqlModal, { + sql, + onConfirm: async () => { + saveScriptToDatabase({ conid, database }, sql); + }, + engine: driver.engine, + }); + } else if (menu.isEnableEvent) { + const { conid, database, enableSql } = data; + const driver = await getDriver(); + const dmp = driver.createDumper(); + dmp.put(enableSql); + + const sql = dmp.s; + + showModal(ConfirmSqlModal, { + sql, + onConfirm: async () => { + saveScriptToDatabase({ conid, database }, sql); + }, + engine: driver.engine, + }); } else if (menu.isTruncate) { const { conid, database } = data; const driver = await getDriver(); @@ -939,6 +987,15 @@ if (data.objectTypeField === 'triggers') { res.push(`${data.tableName}, ${data.triggerTiming?.toLowerCase() ?? ''} ${data.eventType?.toLowerCase() ?? ''}`); } + + if (data.objectTypeField == 'schedulerEvents') { + if (data.eventType == 'RECURRING') { + res.push(`${data.status}, ${data.eventType}, ${data.intervalValue} ${data.intervalField}`); + } else { + res.push(`${data.status}, ${data.eventType}, ${data.executeAt}`); + } + } + if (data.objectComment) { res.push(data.objectComment); } diff --git a/packages/web/src/appobj/appObjectTools.ts b/packages/web/src/appobj/appObjectTools.ts index 7b3e0bb8c..346c94d23 100644 --- a/packages/web/src/appobj/appObjectTools.ts +++ b/packages/web/src/appobj/appObjectTools.ts @@ -85,4 +85,12 @@ export const defaultDatabaseObjectAppObjectActions = { }, }, ], + schedulerEvents: [ + { + label: 'Show SQL', + tab: 'SqlObjectTab', + defaultActionId: 'showSql', + icon: 'img sql-file', + }, + ], }; diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index 77bed6e6c..bc080b50a 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -65,6 +65,7 @@ 'icon add-column': 'mdi mdi-table-column-plus-after', 'icon parameter': 'mdi mdi-at', 'icon trigger': 'mdi mdi-lightning-bolt', + 'icon scheduler-event': 'mdi mdi-calendar-blank', 'icon window-restore': 'mdi mdi-window-restore', 'icon window-maximize': 'mdi mdi-window-maximize', diff --git a/packages/web/src/utility/applyScriptTemplate.ts b/packages/web/src/utility/applyScriptTemplate.ts index ac2de4b0f..6644e2dac 100644 --- a/packages/web/src/utility/applyScriptTemplate.ts +++ b/packages/web/src/utility/applyScriptTemplate.ts @@ -173,6 +173,13 @@ export function getSupportedScriptTemplates(objectTypeField: string): { label: s scriptTemplate: 'CREATE OBJECT', }, ]; + case 'schedulerEvents': + return [ + { + label: 'CREATE SCHEDULER EVENT', + scriptTemplate: 'CREATE OBJECT', + }, + ]; } return []; diff --git a/packages/web/src/widgets/SqlObjectList.svelte b/packages/web/src/widgets/SqlObjectList.svelte index 7638f6cbd..77d926ff0 100644 --- a/packages/web/src/widgets/SqlObjectList.svelte +++ b/packages/web/src/widgets/SqlObjectList.svelte @@ -2,7 +2,7 @@ function generateObjectList(seed = 0) { const counts = [1000, 1200, 1100, 2100, 720]; const schemas = ['A', 'dev', 'public', 'dbo']; - const types = ['tables', 'views', 'functions', 'procedures', 'matviews', 'triggers']; + const types = ['tables', 'views', 'functions', 'procedures', 'matviews', 'triggers', 'schedulerEvents']; const res = _.range(1, counts[seed % counts.length]).map(i => ({ pureName: `name ${i}`, schemaName: schemas[i % schemas.length], @@ -80,11 +80,12 @@ // $: console.log('OBJECTS', $objects); $: objectList = _.flatten([ - ...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers'].map(objectTypeField => - _.sortBy( - (($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })), - ['schemaName', 'pureName'] - ) + ...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers', 'schedulerEvents'].map( + objectTypeField => + _.sortBy( + (($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })), + ['schemaName', 'pureName'] + ) ), ...dbApps.map(app => app.queries.map(query => ({ From c90011fc27633e71b401d596e39622206013f281 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Fri, 3 Jan 2025 11:01:16 +0100 Subject: [PATCH 3/9] feat: scheduler events tests --- .../__tests__/object-analyse.spec.js | 26 ++++++++++- integration-tests/engines.js | 43 +++++++++++++++++-- packages/datalib/src/tests/chinookDbInfo.ts | 1 + 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/integration-tests/__tests__/object-analyse.spec.js b/integration-tests/__tests__/object-analyse.spec.js index 5d871724a..bc84a14e3 100644 --- a/integration-tests/__tests__/object-analyse.spec.js +++ b/integration-tests/__tests__/object-analyse.spec.js @@ -23,6 +23,12 @@ function flatSourceTriggers() { return _.flatten(engines.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine]))); } +function flatSourceSchedulerEvents() { + return _.flatten( + engines.map(engine => (engine.schedulerEvents || []).map(schedulerEvent => [engine.label, schedulerEvent, engine])) + ); +} + const obj1Match = expect.objectContaining({ pureName: 'obj1', }); @@ -168,6 +174,22 @@ describe('Object analyse', () => { expect(createdTrigger2).toEqual(expect.objectContaining(expected)); }) ); -}); -console.log(flatSourceTriggers()); + const schedulerEvents = flatSourceSchedulerEvents(); + if (schedulerEvents.length > 0) { + test.each(schedulerEvents)( + 'Test scheduler events - %s - %s', + testWrapper(async (conn, driver, event) => { + for (const sql of initSql) await runCommandOnDriver(conn, driver, sql); + const { create, drop, objectTypeField, expected } = event; + + await runCommandOnDriver(conn, driver, create); + const structure = await driver.analyseFull(conn); + await runCommandOnDriver(conn, driver, drop); + + const createdEvent = structure[objectTypeField].find(x => x.pureName == expected.pureName); + expect(createdEvent).toEqual(expect.objectContaining(expected)); + }) + ); + } +}); diff --git a/integration-tests/engines.js b/integration-tests/engines.js index 9e24835bf..f68ef5e77 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -28,6 +28,13 @@ const mysqlEngine = { }, objects: [ views, + { + type: 'schedulerEvents', + create1: 'CREATE EVENT obj1 ON SCHEDULE EVERY 1 HOUR DO BEGIN END', + create2: 'CREATE EVENT obj2 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY DO BEGIN END', + drop1: 'DROP EVENT obj1', + drop2: 'DROP EVENT obj2', + }, { type: 'procedures', create1: 'CREATE PROCEDURE obj1() BEGIN SELECT * FROM t1; END', @@ -130,6 +137,34 @@ const mysqlEngine = { }, }, ], + schedulerEvents: [ + { + create: 'CREATE EVENT obj1 ON SCHEDULE EVERY 1 HOUR DO BEGIN END', + drop: 'DROP EVENT obj1', + objectTypeField: 'schedulerEvents', + expected: { + pureName: 'obj1', + status: 'ENABLED', + eventType: 'RECURRING', + enableSql: 'ALTER EVENT obj1 ENABLE;', + disableSql: 'ALTER EVENT obj1 DISABLE;', + intervalValue: '1', + intervalField: 'HOUR', + }, + }, + { + create: 'CREATE EVENT obj1 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY DO BEGIN END', + drop: 'DROP EVENT obj1', + objectTypeField: 'schedulerEvents', + expected: { + pureName: 'obj1', + status: 'ENABLED', + eventType: 'ONE TIME', + enableSql: 'ALTER EVENT obj1 ENABLE;', + disableSql: 'ALTER EVENT obj1 DISABLE;', + }, + }, + ], }; const mariaDbEngine = { @@ -355,12 +390,12 @@ const sqlServerEngine = { drop2: 'DROP PROCEDURE obj2', }, { - type:'triggers', + type: 'triggers', create1: 'CREATE TRIGGER obj1 ON t1 AFTER INSERT AS BEGIN SELECT * FROM t1 END', create2: 'CREATE TRIGGER obj2 ON t2 AFTER INSERT AS BEGIN SELECT * FROM t2 END', drop1: 'DROP TRIGGER obj1', drop2: 'DROP TRIGGER obj2', - } + }, ], parametersOtherSql: ['CREATE PROCEDURE obj2 (@p1 int, @p2 int) AS SELECT id from t1'], parameters: [ @@ -577,14 +612,14 @@ const enginesOnCi = [ const enginesOnLocal = [ // all engines, which would be run on local test - // mysqlEngine, + mysqlEngine, // mariaDbEngine, // postgreSqlEngine, // sqlServerEngine, // sqliteEngine, // cockroachDbEngine, // clickhouseEngine, - oracleEngine, + // oracleEngine, ]; module.exports = process.env.CITEST ? enginesOnCi : enginesOnLocal; diff --git a/packages/datalib/src/tests/chinookDbInfo.ts b/packages/datalib/src/tests/chinookDbInfo.ts index b36a8cda3..22b89ce48 100644 --- a/packages/datalib/src/tests/chinookDbInfo.ts +++ b/packages/datalib/src/tests/chinookDbInfo.ts @@ -1492,6 +1492,7 @@ export const chinookDbInfo: DatabaseInfo = { collections: [], matviews: [], triggers: [], + schedulerEvents: [], }; // const ARTIST_TABLE: TableInfo = { From 0ffe2d88115d42c4b5c9ccec322a7f2a818e31fe Mon Sep 17 00:00:00 2001 From: Nybkox Date: Fri, 3 Jan 2025 14:45:06 +0100 Subject: [PATCH 4/9] fix: disable rename tests for mysql --- .../__tests__/alter-database.spec.js | 31 ++++++++++--------- integration-tests/engines.js | 1 + 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/integration-tests/__tests__/alter-database.spec.js b/integration-tests/__tests__/alter-database.spec.js index 490de76a9..2a008c9dc 100644 --- a/integration-tests/__tests__/alter-database.spec.js +++ b/integration-tests/__tests__/alter-database.spec.js @@ -76,23 +76,26 @@ describe('Alter database', () => { }) ); - test.each(flatSource(x => x.supportRenameSqlObject))( - 'Rename object - %s - %s', - testWrapper(async (conn, driver, type, object, engine) => { - for (const sql of initSql) await runCommandOnDriver(conn, driver, sql); + const objectsSupportingRename = flatSource(x => x.supportRenameSqlObject); + if (objectsSupportingRename.length > 0) { + test.each(objectsSupportingRename)( + 'Rename object - %s - %s', + testWrapper(async (conn, driver, type, object, engine) => { + for (const sql of initSql) await runCommandOnDriver(conn, driver, sql); - await runCommandOnDriver(conn, driver, object.create1); + await runCommandOnDriver(conn, driver, object.create1); - const structure = extendDatabaseInfo(await driver.analyseFull(conn)); + const structure = extendDatabaseInfo(await driver.analyseFull(conn)); - const dmp = driver.createDumper(); - dmp.renameSqlObject(structure[type][0], 'renamed1'); + const dmp = driver.createDumper(); + dmp.renameSqlObject(structure[type][0], 'renamed1'); - await driver.query(conn, dmp.s); + await driver.query(conn, dmp.s); - const structure2 = await driver.analyseFull(conn); - expect(structure2[type].length).toEqual(1); - expect(structure2[type][0].pureName).toEqual('renamed1'); - }) - ); + const structure2 = await driver.analyseFull(conn); + expect(structure2[type].length).toEqual(1); + expect(structure2[type][0].pureName).toEqual('renamed1'); + }) + ); + } }); diff --git a/integration-tests/engines.js b/integration-tests/engines.js index f68ef5e77..14dadddd5 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -43,6 +43,7 @@ const mysqlEngine = { drop2: 'DROP PROCEDURE obj2', }, ], + supportRenameSqlObject: false, dbSnapshotBySeconds: true, dumpFile: 'data/chinook-mysql.sql', dumpChecks: [ From e73356c8da64b2c3114eefb7538bfd0bb2e21be1 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Fri, 3 Jan 2025 15:02:03 +0100 Subject: [PATCH 5/9] feat: add schedulerEvents to tools --- packages/tools/src/SqlDumper.ts | 7 +++---- packages/tools/src/diffTools.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 3ec1572ae..04314dfc2 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -708,6 +708,8 @@ export class SqlDumper implements AlterProcessor { return 'TRIGGER'; case 'matviews': return 'MATERIALIZED VIEW'; + case 'schedulerEvents': + return 'EVENT'; } } @@ -785,10 +787,7 @@ export class SqlDumper implements AlterProcessor { } callableTemplate(func: CallableObjectInfo) { - this.put( - '^call %f(&>&n', - func, - ); + this.put('^call %f(&>&n', func); this.putCollection(',&n', func.parameters || [], param => { this.putRaw(param.parameterMode == 'IN' ? ':' + param.parameterName : param.parameterName); diff --git a/packages/tools/src/diffTools.ts b/packages/tools/src/diffTools.ts index 22cf09e7d..1bb7ee7d5 100644 --- a/packages/tools/src/diffTools.ts +++ b/packages/tools/src/diffTools.ts @@ -155,6 +155,7 @@ export function generateDbPairingId(db: DatabaseInfo): DatabaseInfo { procedures: db.procedures?.map(generateObjectPairingId), functions: db.functions?.map(generateObjectPairingId), triggers: db.triggers?.map(generateObjectPairingId), + schedulerEvents: db.schedulerEvents?.map(generateObjectPairingId), matviews: db.matviews?.map(generateObjectPairingId), }; } @@ -715,7 +716,15 @@ export function createAlterDatabasePlan( ): AlterPlan { const plan = new AlterPlan(wholeOldDb, wholeNewDb, driver.dialect, opts); - for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions', 'triggers']) { + for (const objectTypeField of [ + 'tables', + 'views', + 'procedures', + 'matviews', + 'functions', + 'triggers', + 'schedulerEvents', + ]) { for (const oldobj of oldDb[objectTypeField] || []) { const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId); if (objectTypeField == 'tables') { From 1be73d8a4c510fc3f442bf05a77d6241829a082f Mon Sep 17 00:00:00 2001 From: Nybkox Date: Fri, 3 Jan 2025 22:57:33 +0100 Subject: [PATCH 6/9] fix: build enable/disable sql on frontend --- integration-tests/engines.js | 4 ---- packages/types/dbinfo.d.ts | 2 -- packages/web/src/appobj/DatabaseObjectAppObject.svelte | 8 ++++---- plugins/dbgate-plugin-mysql/src/backend/Analyser.js | 4 ---- .../src/backend/sql/schedulerEvents.js | 4 +--- 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/integration-tests/engines.js b/integration-tests/engines.js index 14dadddd5..5d0a06c7c 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -147,8 +147,6 @@ const mysqlEngine = { pureName: 'obj1', status: 'ENABLED', eventType: 'RECURRING', - enableSql: 'ALTER EVENT obj1 ENABLE;', - disableSql: 'ALTER EVENT obj1 DISABLE;', intervalValue: '1', intervalField: 'HOUR', }, @@ -161,8 +159,6 @@ const mysqlEngine = { pureName: 'obj1', status: 'ENABLED', eventType: 'ONE TIME', - enableSql: 'ALTER EVENT obj1 ENABLE;', - disableSql: 'ALTER EVENT obj1 DISABLE;', }, }, ], diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index 0ef99a779..6bd700e75 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -167,8 +167,6 @@ export interface SchedulerEventInfo extends SqlObjectInfo { intervalField: string; starts: string; executeAt: string; - enableSql: string; - disableSql: string; } export interface SchemaInfo { diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index 11a578c08..58702e440 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -498,10 +498,10 @@ ); }); } else if (menu.isDisableEvent) { - const { conid, database, disableSql } = data; + const { conid, database, pureName } = data; const driver = await getDriver(); const dmp = driver.createDumper(); - dmp.put(disableSql); + dmp.put('^alter ^event %i ^disable', pureName); const sql = dmp.s; @@ -513,10 +513,10 @@ engine: driver.engine, }); } else if (menu.isEnableEvent) { - const { conid, database, enableSql } = data; + const { conid, database, pureName } = data; const driver = await getDriver(); const dmp = driver.createDumper(); - dmp.put(enableSql); + dmp.put('^alter ^event %i ^enable', pureName); const sql = dmp.s; diff --git a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js index 1e7ae1b42..c51bd310a 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js @@ -256,8 +256,6 @@ class Analyser extends DatabaseAnalyser { contentHash: _.isDate(row.LAST_ALTERED) ? row.LAST_ALTERED.toISOString() : row.LAST_ALTERED, pureName: row.EVENT_NAME, createSql: row.CREATE_SQL, - enableSql: row.ENABLE_SQL, - disableSql: row.DISABLE_SQL, objectId: row.EVENT_NAME, intervalValue: row.INTERVAL_VALUE, intervalField: row.INTERVAL_FIELD, @@ -310,8 +308,6 @@ class Analyser extends DatabaseAnalyser { contentHash: _.isDate(row.LAST_ALTERED) ? row.LAST_ALTERED.toISOString() : row.LAST_ALTERED, pureName: row.EVENT_NAME, createSql: row.CREATE_SQL, - enableSql: row.ENABLE_SQL, - disableSql: row.DISABLE_SQL, objectId: row.EVENT_NAME, intervalValue: row.INTERVAL_VALUE, intervalField: row.INTERVAL_FIELD, diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js index 9558eb70f..29ccf67e1 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js @@ -26,9 +26,7 @@ SELECT END, ' DO ', EVENT_DEFINITION - ) AS CREATE_SQL, - CONCAT('ALTER EVENT ', EVENT_NAME, ' DISABLE;') AS DISABLE_SQL, - CONCAT('ALTER EVENT ', EVENT_NAME, ' ENABLE;') AS ENABLE_SQL + ) AS CREATE_SQL FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA = '#DATABASE#' AND EVENT_NAME =OBJECT_ID_CONDITION `; From a8a5864ff0bc48e06d7ad51e309c4b6728cc5e93 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Tue, 7 Jan 2025 13:18:23 +0100 Subject: [PATCH 7/9] fix: escape mysql event name in createSql --- plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js index 29ccf67e1..9a62c0835 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/schedulerEvents.js @@ -15,7 +15,7 @@ SELECT STATUS, ON_COMPLETION, CONCAT( - 'CREATE EVENT ', EVENT_NAME, ' ', + 'CREATE EVENT \`', EVENT_NAME, '\` ', CASE WHEN EVENT_TYPE = 'RECURRING' THEN 'ON SCHEDULE EVERY ' ELSE 'ON SCHEDULE AT ' From 8c5e23f7734427453c40097b5803527589fa4893 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Tue, 7 Jan 2025 14:29:17 +0100 Subject: [PATCH 8/9] feat: show only 1 of disable/enable actions in events context menu --- .../src/appobj/DatabaseObjectAppObject.svelte | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index 58702e440..fa60d760e 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -93,7 +93,7 @@ submenu?: DbObjMenuItem[]; } - function createMenusCore(objectTypeField, driver): DbObjMenuItem[] { + function createMenusCore(objectTypeField, driver, data): DbObjMenuItem[] { switch (objectTypeField) { case 'tables': return [ @@ -387,20 +387,27 @@ ...(driver?.getScriptTemplates?.('collections') || []), ]; case 'schedulerEvents': - return [ + const menu: DbObjMenuItem[] = [ ...defaultDatabaseObjectAppObjectActions['schedulerEvents'], { divider: true, }, - { + , + ]; + + if (data?.status === 'ENABLED') { + menu.push({ label: 'Disable', isDisableEvent: true, - }, - { + }); + } else { + menu.push({ label: 'Enable', isEnableEvent: true, - }, - ]; + }); + } + + return menu; } } @@ -672,8 +679,8 @@ } } - function createMenus(objectTypeField, driver): ReturnType { - return createMenusCore(objectTypeField, driver).filter(x => { + function createMenus(objectTypeField, driver, data): ReturnType { + return createMenusCore(objectTypeField, driver, data).filter(x => { if (x.scriptTemplate) { return hasPermission(`dbops/sql-template/${x.scriptTemplate}`); } @@ -890,7 +897,7 @@ const driver = findEngineDriver(data, getExtensions()); const { objectTypeField } = data; - return createMenus(objectTypeField, driver) + return createMenus(objectTypeField, driver, data) .filter(x => x) .map(menu => menuItemMapper(menu, data, connection)); } From 202716e28c31d012040e12398cd9a7401ce5ccf5 Mon Sep 17 00:00:00 2001 From: Nybkox Date: Tue, 7 Jan 2025 14:35:40 +0100 Subject: [PATCH 9/9] fix: show funcitons specific menu for functions --- packages/web/src/appobj/DatabaseObjectAppObject.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index fa60d760e..cbc03e976 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -347,6 +347,7 @@ }, ]; case 'functions': + return [...defaultDatabaseObjectAppObjectActions['functions']]; case 'triggers': return [...defaultDatabaseObjectAppObjectActions['triggers']]; case 'collections':