diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index 1882f6293..f8bc629e3 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -4,7 +4,7 @@ import { NamedObjectInfo, DatabaseInfo } from '@dbgate/types'; export interface ChangeSetItem { pureName: string; - schemaName: string; + schemaName?: string; insertedRowIndex?: number; condition?: { [column: string]: string }; fields?: { [column: string]: string }; diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index 1d93bd387..15b600c81 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -40,7 +40,7 @@ export class TableGridDisplay extends GridDisplay { } } - findTable({ schemaName, pureName }) { + findTable({ schemaName = undefined, pureName }) { return ( this.dbinfo && this.dbinfo.tables && diff --git a/packages/engines/mssql/MsSqlAnalyser.js b/packages/engines/mssql/MsSqlAnalyser.js index 510c51904..6464da000 100644 --- a/packages/engines/mssql/MsSqlAnalyser.js +++ b/packages/engines/mssql/MsSqlAnalyser.js @@ -52,19 +52,19 @@ class MsSqlAnalyser extends DatabaseAnalyser { this.singleObjectId = null; } - createQuery(resFileName, filterIdObjects) { + createQuery(resFileName, typeFields) { let res = sql[resFileName]; if (this.singleObjectFilter) { const { typeField } = this.singleObjectFilter; if (!this.singleObjectId) return null; - if (!filterIdObjects || !filterIdObjects.includes(typeField)) return null; + if (!typeFields || !typeFields.includes(typeField)) return null; return res.replace('=[OBJECT_ID_CONDITION]', ` = ${this.singleObjectId}`); } - if (!this.modifications || !filterIdObjects || this.modifications.length == 0) { + if (!this.modifications || !typeFields || this.modifications.length == 0) { res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null'); } else { const filterIds = this.modifications - .filter((x) => filterIdObjects.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) + .filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) .map((x) => x.objectId); if (filterIds.length == 0) { res = res.replace('=[OBJECT_ID_CONDITION]', ' = 0'); @@ -77,8 +77,7 @@ class MsSqlAnalyser extends DatabaseAnalyser { async getSingleObjectId() { if (this.singleObjectFilter) { - const { name, typeField } = this.singleObjectFilter; - const { schemaName, pureName } = name; + const { schemaName, pureName, typeField } = this.singleObjectFilter; const fullName = schemaName ? `[${schemaName}].[${pureName}]` : pureName; const resId = await this.driver.query(this.pool, `SELECT OBJECT_ID('${fullName}') AS id`); this.singleObjectId = resId.rows[0].id; diff --git a/packages/engines/mssql/index.js b/packages/engines/mssql/index.js index b38b5eb42..0261b1909 100644 --- a/packages/engines/mssql/index.js +++ b/packages/engines/mssql/index.js @@ -206,7 +206,7 @@ const driver = { }, async analyseSingleObject(pool, name, typeField = 'tables') { const analyser = new MsSqlAnalyser(pool, this); - analyser.singleObjectFilter = { name, typeField }; + analyser.singleObjectFilter = { ...name, typeField }; const res = await analyser.fullAnalysis(); return res.tables[0]; }, diff --git a/packages/engines/mysql/MySqlAnalyser.js b/packages/engines/mysql/MySqlAnalyser.js index abca6d1cd..ae6ab2068 100644 --- a/packages/engines/mysql/MySqlAnalyser.js +++ b/packages/engines/mysql/MySqlAnalyser.js @@ -28,17 +28,40 @@ function getColumnInfo({ }; } +function objectTypeToField(type) { + if (type == 'VIEW') return 'views'; + if (type == 'BASE TABLE') return 'tables'; + return null; +} + class MySqlAnalyser extends DatabaseAnalayser { constructor(pool, driver) { super(pool, driver); } - createQuery(resFileName, tables = false, views = false, procedures = false, functions = false, triggers = false) { + createQuery(resFileName, typeFields) { let res = sql[resFileName]; - res = res.replace('=[OBJECT_NAME_CONDITION]', ' is not null'); + if (this.singleObjectFilter) { + const { typeField, pureName } = this.singleObjectFilter; + if (!typeFields || !typeFields.includes(typeField)) return null; + return res.replace('=[OBJECT_NAME_CONDITION]', ` = ${pureName}`).replace('#DATABASE#', this.pool._database_name); + } + if (!this.modifications || !typeFields || this.modifications.length == 0) { + res = res.replace('=[OBJECT_NAME_CONDITION]', ' is not null'); + } else { + const filterNames = this.modifications + .filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) + .map((x) => x.objectId); + if (filterNames.length == 0) { + res = res.replace('=[OBJECT_NAME_CONDITION]', ' IS NULL'); + } else { + res = res.replace('=[OBJECT_NAME_CONDITION]', ` in (${filterNames.map((x) => `'${x}'`).join(',')})`); + } + } res = res.replace('#DATABASE#', this.pool._database_name); return res; } + async _runAnalysis() { const tables = await this.driver.query(this.pool, this.createQuery('tables')); const columns = await this.driver.query(this.pool, this.createQuery('columns')); @@ -62,6 +85,89 @@ class MySqlAnalyser extends DatabaseAnalayser { functions: programmables.rows.filter((x) => x.objectType == 'FUNCTION').map(fp.omit(['objectType'])), }); } + + getDeletedObjectsForField(nameArray, objectTypeField) { + return this.structure[objectTypeField] + .filter((x) => !nameArray.includes(x.pureName)) + .map((x) => ({ + oldName: _.pick(x, ['pureName']), + action: 'remove', + objectTypeField, + })); + } + + getDeletedObjects(nameArray) { + return [ + ...this.getDeletedObjectsForField(nameArray, 'tables'), + ...this.getDeletedObjectsForField(nameArray, 'views'), + ...this.getDeletedObjectsForField(nameArray, 'procedures'), + ...this.getDeletedObjectsForField(nameArray, 'functions'), + ...this.getDeletedObjectsForField(nameArray, 'triggers'), + ]; + } + + async getModifications() { + const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications')); + const procedureModificationsQueryData = await this.driver.query( + this.pool, + this.createQuery('procedureModifications') + ); + const functionModificationsQueryData = await this.driver.query( + this.pool, + this.createQuery('functionModifications') + ); + + const allModifications = _.compact([ + ...tableModificationsQueryData.rows.map((x) => { + if (x.objectType == 'BASE TABLE') return { ...x, objectTypeField: 'tables' }; + if (x.objectType == 'VIEW') return { ...x, objectTypeField: 'views' }; + return null; + }), + procedureModificationsQueryData.rows.map((x) => ({ + objectTypeField: 'procedures', + modifyDate: x.Modified, + pureName: x.Name, + })), + functionModificationsQueryData.rows.map((x) => ({ + objectTypeField: 'functions', + modifyDate: x.Modified, + pureName: x.Name, + })), + ]); + // console.log('MOD - SRC', modifications); + // console.log( + // 'MODs', + // this.structure.tables.map((x) => x.modifyDate) + // ); + const modifications = allModifications.map((x) => { + const { objectType, modifyDate, pureName } = x; + const field = objectTypeToField(objectType); + + if (!field || !this.structure[field]) return null; + // @ts-ignore + const obj = this.structure[field].find((x) => x.pureName == pureName); + + // object not modified + if (obj && Math.abs(new Date(modifyDate).getTime() - new Date(obj.modifyDate).getTime()) < 1000) return null; + + /** @type {import('@dbgate/types').DatabaseModification} */ + const action = obj + ? { + newName: { pureName }, + oldName: _.pick(obj, ['pureName']), + action: 'change', + objectTypeField: field, + } + : { + newName: { pureName }, + action: 'add', + objectTypeField: field, + }; + return action; + }); + + return [..._.compact(modifications), ...this.getDeletedObjects([...allModifications.map((x) => x.pureName)])]; + } } module.exports = MySqlAnalyser; diff --git a/packages/engines/mysql/sql/functionModifications.js b/packages/engines/mysql/sql/functionModifications.js new file mode 100644 index 000000000..0ccbd49f8 --- /dev/null +++ b/packages/engines/mysql/sql/functionModifications.js @@ -0,0 +1,3 @@ +module.exports = ` +SHOW PROCEDURE STATUS WHERE Db = '#DATABASE#' +`; diff --git a/packages/engines/mysql/sql/index.js b/packages/engines/mysql/sql/index.js index 3bf884adc..f5a4992ec 100644 --- a/packages/engines/mysql/sql/index.js +++ b/packages/engines/mysql/sql/index.js @@ -5,6 +5,8 @@ const foreignKeys = require('./foreignKeys'); const tableModifications = require('./tableModifications'); const views = require('./views'); const programmables = require('./programmables'); +const procedureModifications = require('./procedureModifications'); +const functionModifications = require('./functionModifications'); module.exports = { columns, @@ -14,4 +16,6 @@ module.exports = { tableModifications, views, programmables, + procedureModifications, + functionModifications, }; diff --git a/packages/engines/mysql/sql/procedureModifications.js b/packages/engines/mysql/sql/procedureModifications.js new file mode 100644 index 000000000..0ccbd49f8 --- /dev/null +++ b/packages/engines/mysql/sql/procedureModifications.js @@ -0,0 +1,3 @@ +module.exports = ` +SHOW PROCEDURE STATUS WHERE Db = '#DATABASE#' +`; diff --git a/packages/engines/mysql/sql/tableModifications.js b/packages/engines/mysql/sql/tableModifications.js index a0011fdef..6382ea5ae 100644 --- a/packages/engines/mysql/sql/tableModifications.js +++ b/packages/engines/mysql/sql/tableModifications.js @@ -1,7 +1,8 @@ module.exports = ` select - TABLE_NAME, - case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as ALTER_TIME + TABLE_NAME as pureName, + TABLE_TYPE as objectType, + case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate from information_schema.tables where TABLE_SCHEMA = '#DATABASE#' `; diff --git a/packages/engines/mysql/sql/tables.js b/packages/engines/mysql/sql/tables.js index 38b2d974d..eed928431 100644 --- a/packages/engines/mysql/sql/tables.js +++ b/packages/engines/mysql/sql/tables.js @@ -1,7 +1,7 @@ module.exports = ` select TABLE_NAME as pureName, - case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as alterTime + case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate from information_schema.tables where TABLE_SCHEMA = '#DATABASE#' and TABLE_TYPE='BASE TABLE' and TABLE_NAME =[OBJECT_NAME_CONDITION]; `; diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index 5ec0616b9..5c55ae9e8 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -1,6 +1,6 @@ export interface NamedObjectInfo { pureName: string; - schemaName: string; + schemaName?: string; } export interface ColumnReference { diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index cba11d3b3..3dba19c56 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -48,7 +48,7 @@ export interface EngineDriver { export interface DatabaseModification { oldName?: NamedObjectInfo; newName?: NamedObjectInfo; - objectId: string; + objectId?: string; action: 'add' | 'remove' | 'change'; objectTypeField: keyof DatabaseInfo; }