Merge pull request #985 from dbgate/feature/mysql-event-scheduler

Feature/mysql event scheduler
This commit is contained in:
Jan Prochazka
2025-01-07 14:37:30 +01:00
committed by GitHub
16 changed files with 276 additions and 34 deletions

View File

@@ -1492,6 +1492,7 @@ export const chinookDbInfo: DatabaseInfo = {
collections: [],
matviews: [],
triggers: [],
schedulerEvents: [],
};
// const ARTIST_TABLE: TableInfo = {

View File

@@ -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: [],
};
}

View File

@@ -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);

View File

@@ -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') {

View File

@@ -157,6 +157,18 @@ 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;
}
export interface SchemaInfo {
objectId?: string;
schemaName: string;
@@ -171,6 +183,7 @@ export interface DatabaseInfoObjects {
procedures: ProcedureInfo[];
functions: FunctionInfo[];
triggers: TriggerInfo[];
schedulerEvents: SchedulerEventInfo[];
}
export interface DatabaseInfo extends DatabaseInfoObjects {

View File

@@ -42,6 +42,7 @@
functions: 'img function',
queries: 'img query-data',
triggers: 'icon trigger',
schedulerEvents: 'icon scheduler-event',
};
const defaultTabs = {
@@ -87,10 +88,12 @@
isDropCollection?: boolean;
isRenameCollection?: boolean;
isDuplicateCollection?: boolean;
isDisableEvent?: boolean;
isEnableEvent?: boolean;
submenu?: DbObjMenuItem[];
}
function createMenusCore(objectTypeField, driver): DbObjMenuItem[] {
function createMenusCore(objectTypeField, driver, data): DbObjMenuItem[] {
switch (objectTypeField) {
case 'tables':
return [
@@ -344,6 +347,7 @@
},
];
case 'functions':
return [...defaultDatabaseObjectAppObjectActions['functions']];
case 'triggers':
return [...defaultDatabaseObjectAppObjectActions['triggers']];
case 'collections':
@@ -383,6 +387,28 @@
},
...(driver?.getScriptTemplates?.('collections') || []),
];
case 'schedulerEvents':
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;
}
}
@@ -480,6 +506,36 @@
x => x.schemaName == data.schemaName && x.pureName == data.pureName
);
});
} else if (menu.isDisableEvent) {
const { conid, database, pureName } = data;
const driver = await getDriver();
const dmp = driver.createDumper();
dmp.put('^alter ^event %i ^disable', pureName);
const sql = dmp.s;
showModal(ConfirmSqlModal, {
sql,
onConfirm: async () => {
saveScriptToDatabase({ conid, database }, sql);
},
engine: driver.engine,
});
} else if (menu.isEnableEvent) {
const { conid, database, pureName } = data;
const driver = await getDriver();
const dmp = driver.createDumper();
dmp.put('^alter ^event %i ^enable', pureName);
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();
@@ -625,8 +681,8 @@
}
}
function createMenus(objectTypeField, driver): ReturnType<typeof createMenusCore> {
return createMenusCore(objectTypeField, driver).filter(x => {
function createMenus(objectTypeField, driver, data): ReturnType<typeof createMenusCore> {
return createMenusCore(objectTypeField, driver, data).filter(x => {
if (x.scriptTemplate) {
return hasPermission(`dbops/sql-template/${x.scriptTemplate}`);
}
@@ -844,7 +900,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));
}
@@ -941,6 +997,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);
}

View File

@@ -85,4 +85,12 @@ export const defaultDatabaseObjectAppObjectActions = {
},
},
],
schedulerEvents: [
{
label: 'Show SQL',
tab: 'SqlObjectTab',
defaultActionId: 'showSql',
icon: 'img sql-file',
},
],
};

View File

@@ -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',

View File

@@ -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 [];

View File

@@ -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 => ({