mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 13:13:58 +00:00
Merge pull request #985 from dbgate/feature/mysql-event-scheduler
Feature/mysql event scheduler
This commit is contained in:
@@ -76,23 +76,26 @@ describe('Alter database', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
test.each(flatSource(x => x.supportRenameSqlObject))(
|
const objectsSupportingRename = flatSource(x => x.supportRenameSqlObject);
|
||||||
'Rename object - %s - %s',
|
if (objectsSupportingRename.length > 0) {
|
||||||
testWrapper(async (conn, driver, type, object, engine) => {
|
test.each(objectsSupportingRename)(
|
||||||
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
|
'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();
|
const dmp = driver.createDumper();
|
||||||
dmp.renameSqlObject(structure[type][0], 'renamed1');
|
dmp.renameSqlObject(structure[type][0], 'renamed1');
|
||||||
|
|
||||||
await driver.query(conn, dmp.s);
|
await driver.query(conn, dmp.s);
|
||||||
|
|
||||||
const structure2 = await driver.analyseFull(conn);
|
const structure2 = await driver.analyseFull(conn);
|
||||||
expect(structure2[type].length).toEqual(1);
|
expect(structure2[type].length).toEqual(1);
|
||||||
expect(structure2[type][0].pureName).toEqual('renamed1');
|
expect(structure2[type][0].pureName).toEqual('renamed1');
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ function flatSourceTriggers() {
|
|||||||
return _.flatten(engines.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine])));
|
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({
|
const obj1Match = expect.objectContaining({
|
||||||
pureName: 'obj1',
|
pureName: 'obj1',
|
||||||
});
|
});
|
||||||
@@ -172,6 +178,22 @@ describe('Object analyse', () => {
|
|||||||
expect(createdTrigger2).toEqual(expect.objectContaining(expected));
|
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));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ const mysqlEngine = {
|
|||||||
},
|
},
|
||||||
objects: [
|
objects: [
|
||||||
views,
|
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',
|
type: 'procedures',
|
||||||
create1: 'CREATE PROCEDURE obj1() BEGIN SELECT * FROM t1; END',
|
create1: 'CREATE PROCEDURE obj1() BEGIN SELECT * FROM t1; END',
|
||||||
@@ -36,6 +43,7 @@ const mysqlEngine = {
|
|||||||
drop2: 'DROP PROCEDURE obj2',
|
drop2: 'DROP PROCEDURE obj2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
supportRenameSqlObject: false,
|
||||||
dbSnapshotBySeconds: true,
|
dbSnapshotBySeconds: true,
|
||||||
dumpFile: 'data/chinook-mysql.sql',
|
dumpFile: 'data/chinook-mysql.sql',
|
||||||
dumpChecks: [
|
dumpChecks: [
|
||||||
@@ -130,6 +138,30 @@ 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',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const mariaDbEngine = {
|
const mariaDbEngine = {
|
||||||
@@ -601,7 +633,7 @@ const enginesOnCi = [
|
|||||||
|
|
||||||
const enginesOnLocal = [
|
const enginesOnLocal = [
|
||||||
// all engines, which would be run on local test
|
// all engines, which would be run on local test
|
||||||
// mysqlEngine,
|
mysqlEngine,
|
||||||
// mariaDbEngine,
|
// mariaDbEngine,
|
||||||
// postgreSqlEngine,
|
// postgreSqlEngine,
|
||||||
// sqlServerEngine,
|
// sqlServerEngine,
|
||||||
|
|||||||
@@ -1492,6 +1492,7 @@ export const chinookDbInfo: DatabaseInfo = {
|
|||||||
collections: [],
|
collections: [],
|
||||||
matviews: [],
|
matviews: [],
|
||||||
triggers: [],
|
triggers: [],
|
||||||
|
schedulerEvents: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// const ARTIST_TABLE: TableInfo = {
|
// const ARTIST_TABLE: TableInfo = {
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ import { extractErrorLogData } from './stringTools';
|
|||||||
|
|
||||||
const logger = getLogger('dbAnalyser');
|
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);
|
const fp_pick = arg => array => _pick(array, arg);
|
||||||
|
|
||||||
@@ -70,7 +79,9 @@ export class DatabaseAnalyser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fullAnalysis() {
|
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());
|
const res = this.addEngineField(await this._runAnalysis());
|
||||||
// console.log('FULL ANALYSIS', res);
|
// console.log('FULL ANALYSIS', res);
|
||||||
return res;
|
return res;
|
||||||
@@ -255,6 +266,7 @@ export class DatabaseAnalyser {
|
|||||||
...this.getDeletedObjectsForField(snapshot, 'procedures'),
|
...this.getDeletedObjectsForField(snapshot, 'procedures'),
|
||||||
...this.getDeletedObjectsForField(snapshot, 'functions'),
|
...this.getDeletedObjectsForField(snapshot, 'functions'),
|
||||||
...this.getDeletedObjectsForField(snapshot, 'triggers'),
|
...this.getDeletedObjectsForField(snapshot, 'triggers'),
|
||||||
|
...this.getDeletedObjectsForField(snapshot, 'schedulerEvents'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +367,7 @@ export class DatabaseAnalyser {
|
|||||||
functions: [],
|
functions: [],
|
||||||
procedures: [],
|
procedures: [],
|
||||||
triggers: [],
|
triggers: [],
|
||||||
|
schedulerEvents: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -708,6 +708,8 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
return 'TRIGGER';
|
return 'TRIGGER';
|
||||||
case 'matviews':
|
case 'matviews':
|
||||||
return 'MATERIALIZED VIEW';
|
return 'MATERIALIZED VIEW';
|
||||||
|
case 'schedulerEvents':
|
||||||
|
return 'EVENT';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,10 +787,7 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callableTemplate(func: CallableObjectInfo) {
|
callableTemplate(func: CallableObjectInfo) {
|
||||||
this.put(
|
this.put('^call %f(&>&n', func);
|
||||||
'^call %f(&>&n',
|
|
||||||
func,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.putCollection(',&n', func.parameters || [], param => {
|
this.putCollection(',&n', func.parameters || [], param => {
|
||||||
this.putRaw(param.parameterMode == 'IN' ? ':' + param.parameterName : param.parameterName);
|
this.putRaw(param.parameterMode == 'IN' ? ':' + param.parameterName : param.parameterName);
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export function generateDbPairingId(db: DatabaseInfo): DatabaseInfo {
|
|||||||
procedures: db.procedures?.map(generateObjectPairingId),
|
procedures: db.procedures?.map(generateObjectPairingId),
|
||||||
functions: db.functions?.map(generateObjectPairingId),
|
functions: db.functions?.map(generateObjectPairingId),
|
||||||
triggers: db.triggers?.map(generateObjectPairingId),
|
triggers: db.triggers?.map(generateObjectPairingId),
|
||||||
|
schedulerEvents: db.schedulerEvents?.map(generateObjectPairingId),
|
||||||
matviews: db.matviews?.map(generateObjectPairingId),
|
matviews: db.matviews?.map(generateObjectPairingId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -715,7 +716,15 @@ export function createAlterDatabasePlan(
|
|||||||
): AlterPlan {
|
): AlterPlan {
|
||||||
const plan = new AlterPlan(wholeOldDb, wholeNewDb, driver.dialect, opts);
|
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] || []) {
|
for (const oldobj of oldDb[objectTypeField] || []) {
|
||||||
const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId);
|
const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId);
|
||||||
if (objectTypeField == 'tables') {
|
if (objectTypeField == 'tables') {
|
||||||
|
|||||||
13
packages/types/dbinfo.d.ts
vendored
13
packages/types/dbinfo.d.ts
vendored
@@ -157,6 +157,18 @@ export interface TriggerInfo extends SqlObjectInfo {
|
|||||||
eventType?: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE';
|
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 {
|
export interface SchemaInfo {
|
||||||
objectId?: string;
|
objectId?: string;
|
||||||
schemaName: string;
|
schemaName: string;
|
||||||
@@ -171,6 +183,7 @@ export interface DatabaseInfoObjects {
|
|||||||
procedures: ProcedureInfo[];
|
procedures: ProcedureInfo[];
|
||||||
functions: FunctionInfo[];
|
functions: FunctionInfo[];
|
||||||
triggers: TriggerInfo[];
|
triggers: TriggerInfo[];
|
||||||
|
schedulerEvents: SchedulerEventInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseInfo extends DatabaseInfoObjects {
|
export interface DatabaseInfo extends DatabaseInfoObjects {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
functions: 'img function',
|
functions: 'img function',
|
||||||
queries: 'img query-data',
|
queries: 'img query-data',
|
||||||
triggers: 'icon trigger',
|
triggers: 'icon trigger',
|
||||||
|
schedulerEvents: 'icon scheduler-event',
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultTabs = {
|
const defaultTabs = {
|
||||||
@@ -87,10 +88,12 @@
|
|||||||
isDropCollection?: boolean;
|
isDropCollection?: boolean;
|
||||||
isRenameCollection?: boolean;
|
isRenameCollection?: boolean;
|
||||||
isDuplicateCollection?: boolean;
|
isDuplicateCollection?: boolean;
|
||||||
|
isDisableEvent?: boolean;
|
||||||
|
isEnableEvent?: boolean;
|
||||||
submenu?: DbObjMenuItem[];
|
submenu?: DbObjMenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMenusCore(objectTypeField, driver): DbObjMenuItem[] {
|
function createMenusCore(objectTypeField, driver, data): DbObjMenuItem[] {
|
||||||
switch (objectTypeField) {
|
switch (objectTypeField) {
|
||||||
case 'tables':
|
case 'tables':
|
||||||
return [
|
return [
|
||||||
@@ -344,6 +347,7 @@
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
case 'functions':
|
case 'functions':
|
||||||
|
return [...defaultDatabaseObjectAppObjectActions['functions']];
|
||||||
case 'triggers':
|
case 'triggers':
|
||||||
return [...defaultDatabaseObjectAppObjectActions['triggers']];
|
return [...defaultDatabaseObjectAppObjectActions['triggers']];
|
||||||
case 'collections':
|
case 'collections':
|
||||||
@@ -383,6 +387,28 @@
|
|||||||
},
|
},
|
||||||
...(driver?.getScriptTemplates?.('collections') || []),
|
...(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
|
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) {
|
} else if (menu.isTruncate) {
|
||||||
const { conid, database } = data;
|
const { conid, database } = data;
|
||||||
const driver = await getDriver();
|
const driver = await getDriver();
|
||||||
@@ -625,8 +681,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMenus(objectTypeField, driver): ReturnType<typeof createMenusCore> {
|
function createMenus(objectTypeField, driver, data): ReturnType<typeof createMenusCore> {
|
||||||
return createMenusCore(objectTypeField, driver).filter(x => {
|
return createMenusCore(objectTypeField, driver, data).filter(x => {
|
||||||
if (x.scriptTemplate) {
|
if (x.scriptTemplate) {
|
||||||
return hasPermission(`dbops/sql-template/${x.scriptTemplate}`);
|
return hasPermission(`dbops/sql-template/${x.scriptTemplate}`);
|
||||||
}
|
}
|
||||||
@@ -844,7 +900,7 @@
|
|||||||
const driver = findEngineDriver(data, getExtensions());
|
const driver = findEngineDriver(data, getExtensions());
|
||||||
|
|
||||||
const { objectTypeField } = data;
|
const { objectTypeField } = data;
|
||||||
return createMenus(objectTypeField, driver)
|
return createMenus(objectTypeField, driver, data)
|
||||||
.filter(x => x)
|
.filter(x => x)
|
||||||
.map(menu => menuItemMapper(menu, data, connection));
|
.map(menu => menuItemMapper(menu, data, connection));
|
||||||
}
|
}
|
||||||
@@ -941,6 +997,15 @@
|
|||||||
if (data.objectTypeField === 'triggers') {
|
if (data.objectTypeField === 'triggers') {
|
||||||
res.push(`${data.tableName}, ${data.triggerTiming?.toLowerCase() ?? ''} ${data.eventType?.toLowerCase() ?? ''}`);
|
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) {
|
if (data.objectComment) {
|
||||||
res.push(data.objectComment);
|
res.push(data.objectComment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,4 +85,12 @@ export const defaultDatabaseObjectAppObjectActions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
schedulerEvents: [
|
||||||
|
{
|
||||||
|
label: 'Show SQL',
|
||||||
|
tab: 'SqlObjectTab',
|
||||||
|
defaultActionId: 'showSql',
|
||||||
|
icon: 'img sql-file',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
'icon add-column': 'mdi mdi-table-column-plus-after',
|
'icon add-column': 'mdi mdi-table-column-plus-after',
|
||||||
'icon parameter': 'mdi mdi-at',
|
'icon parameter': 'mdi mdi-at',
|
||||||
'icon trigger': 'mdi mdi-lightning-bolt',
|
'icon trigger': 'mdi mdi-lightning-bolt',
|
||||||
|
'icon scheduler-event': 'mdi mdi-calendar-blank',
|
||||||
|
|
||||||
'icon window-restore': 'mdi mdi-window-restore',
|
'icon window-restore': 'mdi mdi-window-restore',
|
||||||
'icon window-maximize': 'mdi mdi-window-maximize',
|
'icon window-maximize': 'mdi mdi-window-maximize',
|
||||||
|
|||||||
@@ -173,6 +173,13 @@ export function getSupportedScriptTemplates(objectTypeField: string): { label: s
|
|||||||
scriptTemplate: 'CREATE OBJECT',
|
scriptTemplate: 'CREATE OBJECT',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
case 'schedulerEvents':
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'CREATE SCHEDULER EVENT',
|
||||||
|
scriptTemplate: 'CREATE OBJECT',
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
function generateObjectList(seed = 0) {
|
function generateObjectList(seed = 0) {
|
||||||
const counts = [1000, 1200, 1100, 2100, 720];
|
const counts = [1000, 1200, 1100, 2100, 720];
|
||||||
const schemas = ['A', 'dev', 'public', 'dbo'];
|
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 => ({
|
const res = _.range(1, counts[seed % counts.length]).map(i => ({
|
||||||
pureName: `name ${i}`,
|
pureName: `name ${i}`,
|
||||||
schemaName: schemas[i % schemas.length],
|
schemaName: schemas[i % schemas.length],
|
||||||
@@ -80,11 +80,12 @@
|
|||||||
// $: console.log('OBJECTS', $objects);
|
// $: console.log('OBJECTS', $objects);
|
||||||
|
|
||||||
$: objectList = _.flatten([
|
$: objectList = _.flatten([
|
||||||
...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers'].map(objectTypeField =>
|
...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers', 'schedulerEvents'].map(
|
||||||
_.sortBy(
|
objectTypeField =>
|
||||||
(($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
_.sortBy(
|
||||||
['schemaName', 'pureName']
|
(($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
||||||
)
|
['schemaName', 'pureName']
|
||||||
|
)
|
||||||
),
|
),
|
||||||
...dbApps.map(app =>
|
...dbApps.map(app =>
|
||||||
app.queries.map(query => ({
|
app.queries.map(query => ({
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
this.feedback({ analysingMessage: 'Loading triggers' });
|
this.feedback({ analysingMessage: 'Loading triggers' });
|
||||||
const triggers = await this.analyserQuery('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']);
|
const uniqueNames = await this.analyserQuery('uniqueNames', ['tables']);
|
||||||
this.feedback({ analysingMessage: 'Finalizing DB structure' });
|
this.feedback({ analysingMessage: 'Finalizing DB structure' });
|
||||||
|
|
||||||
@@ -249,6 +252,21 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
tableName: row.tableName,
|
tableName: row.tableName,
|
||||||
createSql: `CREATE TRIGGER ${row.triggerName} ${row.triggerTiming} ${row.eventType} ON ${row.tableName} FOR EACH ROW ${row.definition}`,
|
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,
|
||||||
|
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 });
|
this.feedback({ analysingMessage: null });
|
||||||
return res;
|
return res;
|
||||||
@@ -258,6 +276,7 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
const tableModificationsQueryData = await this.analyserQuery('tableModifications');
|
const tableModificationsQueryData = await this.analyserQuery('tableModifications');
|
||||||
const procedureModificationsQueryData = await this.analyserQuery('procedureModifications');
|
const procedureModificationsQueryData = await this.analyserQuery('procedureModifications');
|
||||||
const functionModificationsQueryData = await this.analyserQuery('functionModifications');
|
const functionModificationsQueryData = await this.analyserQuery('functionModifications');
|
||||||
|
const schedulerEvents = await this.analyserQuery('schedulerEvents');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tables: tableModificationsQueryData.rows
|
tables: tableModificationsQueryData.rows
|
||||||
@@ -285,6 +304,21 @@ class Analyser extends DatabaseAnalyser {
|
|||||||
objectId: x.Name,
|
objectId: x.Name,
|
||||||
pureName: 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,
|
||||||
|
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',
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const uniqueNames = require('./uniqueNames');
|
|||||||
const viewTexts = require('./viewTexts');
|
const viewTexts = require('./viewTexts');
|
||||||
const parameters = require('./parameters');
|
const parameters = require('./parameters');
|
||||||
const triggers = require('./triggers');
|
const triggers = require('./triggers');
|
||||||
|
const schedulerEvents = require('./schedulerEvents.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
columns,
|
columns,
|
||||||
@@ -28,4 +29,5 @@ module.exports = {
|
|||||||
uniqueNames,
|
uniqueNames,
|
||||||
viewTexts,
|
viewTexts,
|
||||||
triggers,
|
triggers,
|
||||||
|
schedulerEvents,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
FROM INFORMATION_SCHEMA.EVENTS
|
||||||
|
WHERE EVENT_SCHEMA = '#DATABASE#' AND EVENT_NAME =OBJECT_ID_CONDITION
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user