diff --git a/integration-tests/__tests__/alter-processor.spec.js b/integration-tests/__tests__/alter-processor.spec.js index db01ec4c5..a25858234 100644 --- a/integration-tests/__tests__/alter-processor.spec.js +++ b/integration-tests/__tests__/alter-processor.spec.js @@ -56,8 +56,8 @@ async function testTableDiff(conn, driver, mangle) { } // const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq']; -const TESTED_COLUMNS = ['col_pk']; -// const TESTED_COLUMNS = ['col_idx']; +// const TESTED_COLUMNS = ['col_pk']; +const TESTED_COLUMNS = ['col_idx']; // const TESTED_COLUMNS = ['col_fk']; // const TESTED_COLUMNS = ['col_std']; @@ -69,15 +69,15 @@ describe('Alter processor', () => { test.each(engines.map(engine => [engine.label, engine]))( 'Add column - %s', testWrapper(async (conn, driver, engine) => { - await testTableDiff(conn, driver, tbl => + await testTableDiff(conn, driver, tbl => { tbl.columns.push({ columnName: 'added', dataType: 'int', pairingId: uuidv1(), notNull: false, autoIncrement: false, - }) - ); + }); + }); }) ); @@ -109,4 +109,13 @@ describe('Alter processor', () => { ); }) ); + + test.each(engines.map(engine => [engine.label, engine]))( + 'Drop index - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(conn, driver, tbl => { + tbl.indexes = []; + }); + }) + ); }); diff --git a/integration-tests/__tests__/table-analyse.spec.js b/integration-tests/__tests__/table-analyse.spec.js index df8e98b81..bdb4bf327 100644 --- a/integration-tests/__tests__/table-analyse.spec.js +++ b/integration-tests/__tests__/table-analyse.spec.js @@ -133,10 +133,10 @@ describe('Table analyse', () => { const structure = await driver.analyseFull(conn); const t2 = structure.tables.find(x => x.pureName == 't2'); - const indexesAndUniques = [...t2.uniques, ...t2.indexes]; - expect(indexesAndUniques.length).toEqual(1); - expect(indexesAndUniques[0].columns.length).toEqual(1); - expect(indexesAndUniques[0].columns[0]).toEqual(expect.objectContaining({ columnName: 'val2' })); + // const indexesAndUniques = [...t2.uniques, ...t2.indexes]; + expect(t2.uniques.length).toEqual(1); + expect(t2.uniques[0].columns.length).toEqual(1); + expect(t2.uniques[0].columns[0]).toEqual(expect.objectContaining({ columnName: 'val2' })); }) ); }); diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 7bccec773..6ecacf285 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -420,8 +420,24 @@ export class SqlDumper implements AlterProcessor { ); } - dropIndex(ix: IndexInfo) {} - createIndex(ix: IndexInfo) {} + dropIndex(ix: IndexInfo) { + this.put('^drop ^index %i', ix.constraintName); + if (this.dialect.dropIndexContainsTableSpec) { + this.put(' ^on %f', ix); + } + this.endCommand(); + } + createIndex(ix: IndexInfo) { + this.put('^create'); + if (ix.indexType) this.put(' %k', ix.indexType); + if (ix.isUnique) this.put(' ^unique'); + this.put(' ^index %i &n^on %f (&>&n', ix.constraintName, ix); + this.putCollection(',&n', ix.columns, col => { + this.put('%i %k', col.columnName, col.isDescending == true ? 'DESC' : 'ASC'); + }); + this.put('&<&n)'); + this.endCommand(); + } dropUnique(uq: UniqueInfo) { this.dropConstraintCore(uq); diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index 56277bca1..b8e12753b 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -207,6 +207,8 @@ export class AlterPlan { ...(this.dialect.dropColumnDependencies?.includes('uniques') ? table.uniques : []), ]).filter(cnt => cnt.columns.find(col => col.columnName == op.oldObject.columnName)); + console.log('deletedConstraints', deletedConstraints); + const res: AlterOperation[] = [ ...[...deletedFks, ...deletedConstraints].map(oldObject => { const opRes: AlterOperation = { @@ -260,11 +262,13 @@ export class AlterPlan { ): AlterOperation[] | null { if (op.operationType == operationType) { if (_.isFunction(isAllowed)) { - if (!isAllowed(op[objectField])) return null; + if (isAllowed(op[objectField])) return null; } else { - if (!isAllowed) return null; + if (isAllowed) return null; } + // console.log('*****************RECREATED NEEDED', op, operationType, isAllowed); + // console.log(this.dialect); const table = this.db.tables.find( x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName ); diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index 32c9d7821..2facb4ff5 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -15,6 +15,8 @@ export interface SqlDialect { dropColumnDependencies?: string[]; changeColumnDependencies?: string[]; + dropIndexContainsTableSpec?: boolean; + createColumn?: boolean; dropColumn?: boolean; createIndex?: boolean; diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index d7d0dc5ad..6076d06d6 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -15,6 +15,7 @@ const dialect = { dropColumnDependencies: ['default', 'dependencies', 'indexes', 'primaryKey'], changeColumnDependencies: ['indexes'], anonymousPrimaryKey: false, + dropIndexContainsTableSpec: true, quoteIdentifier(s) { return `[${s}]`; }, diff --git a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js index 026c8118e..4068aea17 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js @@ -75,6 +75,7 @@ class Analyser extends DatabaseAnalyser { const viewTexts = await this.getViewTexts(views.rows.map(x => x.pureName)); const indexes = await this.driver.query(this.pool, this.createQuery('indexes', ['tables'])); + const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables'])); return { tables: tables.rows.map(table => ({ @@ -84,9 +85,11 @@ class Analyser extends DatabaseAnalyser { columns: columns.rows.filter(col => col.pureName == table.pureName).map(getColumnInfo), primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, pkColumns.rows), foreignKeys: DatabaseAnalyser.extractForeignKeys(table, fkColumns.rows), - uniques: [], indexes: _.uniqBy( - indexes.rows.filter(idx => idx.tableName == table.pureName), + indexes.rows.filter( + idx => + idx.tableName == table.pureName && !uniqueNames.rows.find(x => x.constraintName == idx.constraintName) + ), 'constraintName' ).map(idx => ({ ..._.pick(idx, ['constraintName', 'indexType']), @@ -97,6 +100,21 @@ class Analyser extends DatabaseAnalyser { ..._.pick(col, ['columnName']), })), })), + + uniques: _.uniqBy( + indexes.rows.filter( + idx => + idx.tableName == table.pureName && uniqueNames.rows.find(x => x.constraintName == idx.constraintName) + ), + 'constraintName' + ).map(idx => ({ + ..._.pick(idx, ['constraintName']), + columns: indexes.rows + .filter(col => col.tableName == idx.tableName && col.constraintName == idx.constraintName) + .map(col => ({ + ..._.pick(col, ['columnName']), + })), + })), })), views: views.rows.map(view => ({ ...view, diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js index 3a514ee7b..377713706 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/index.js @@ -8,6 +8,7 @@ const indexes = require('./indexes'); const programmables = require('./programmables'); const procedureModifications = require('./procedureModifications'); const functionModifications = require('./functionModifications'); +const uniqueNames = require('./uniqueNames'); module.exports = { columns, @@ -20,4 +21,5 @@ module.exports = { procedureModifications, functionModifications, indexes, + uniqueNames, }; diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/uniqueNames.js b/plugins/dbgate-plugin-mysql/src/backend/sql/uniqueNames.js new file mode 100644 index 000000000..cef8daecd --- /dev/null +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/uniqueNames.js @@ -0,0 +1,5 @@ +module.exports = ` + select CONSTRAINT_NAME as constraintName + from information_schema.TABLE_CONSTRAINTS + where CONSTRAINT_SCHEMA = '#DATABASE#' and constraint_type = 'UNIQUE' +`; diff --git a/plugins/dbgate-plugin-mysql/src/frontend/drivers.js b/plugins/dbgate-plugin-mysql/src/frontend/drivers.js index 36f50d875..f743e8f72 100644 --- a/plugins/dbgate-plugin-mysql/src/frontend/drivers.js +++ b/plugins/dbgate-plugin-mysql/src/frontend/drivers.js @@ -22,6 +22,7 @@ const dialect = { dropForeignKey: true, createPrimaryKey: true, dropPrimaryKey: true, + dropIndexContainsTableSpec: true, }; const mysqlDriverBase = { diff --git a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js index 7ddc3b094..3148bde82 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-postgres/src/backend/Analyser.js @@ -67,6 +67,7 @@ class Analyser extends DatabaseAnalyser { const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions'])); const indexes = await this.driver.query(this.pool, this.createQuery('indexes', ['tables'])); const indexcols = await this.driver.query(this.pool, this.createQuery('indexcols', ['tables'])); + const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables'])); return { tables: tables.rows.map(table => { @@ -107,7 +108,12 @@ class Analyser extends DatabaseAnalyser { })) ), indexes: indexes.rows - .filter(x => x.table_name == table.pure_name && x.schema_name == table.schema_name) + .filter( + x => + x.table_name == table.pure_name && + x.schema_name == table.schema_name && + !uniqueNames.rows.find(y => y.constraint_name == x.index_name) + ) .map(idx => ({ constraintName: idx.index_name, isUnique: idx.is_unique, @@ -120,7 +126,24 @@ class Analyser extends DatabaseAnalyser { })) ), })), - uniques: [], + uniques: indexes.rows + .filter( + x => + x.table_name == table.pure_name && + x.schema_name == table.schema_name && + uniqueNames.rows.find(y => y.constraint_name == x.index_name) + ) + .map(idx => ({ + constraintName: idx.index_name, + columns: _.compact( + idx.indkey + .split(' ') + .map(colid => indexcols.rows.find(col => col.oid == idx.oid && col.attnum == colid)) + .map(col => ({ + columnName: col.column_name, + })) + ), + })), }; }), views: views.rows.map(view => ({ diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js index 17f5e77e3..1e4a4db9f 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/sql/index.js +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/index.js @@ -12,6 +12,7 @@ const routineModifications = require('./routineModifications'); const matviewColumns = require('./matviewColumns'); const indexes = require('./indexes'); const indexcols = require('./indexcols'); +const uniqueNames = require('./uniqueNames'); module.exports = { columns, @@ -28,4 +29,5 @@ module.exports = { matviewColumns, indexes, indexcols, + uniqueNames, }; diff --git a/plugins/dbgate-plugin-postgres/src/backend/sql/uniqueNames.js b/plugins/dbgate-plugin-postgres/src/backend/sql/uniqueNames.js new file mode 100644 index 000000000..a6eec71b4 --- /dev/null +++ b/plugins/dbgate-plugin-postgres/src/backend/sql/uniqueNames.js @@ -0,0 +1,3 @@ +module.exports = ` + select conname as "constraint_name" from pg_constraint where contype = 'u' +`; diff --git a/plugins/dbgate-plugin-postgres/src/frontend/drivers.js b/plugins/dbgate-plugin-postgres/src/frontend/drivers.js index 2fcc36309..c040812db 100644 --- a/plugins/dbgate-plugin-postgres/src/frontend/drivers.js +++ b/plugins/dbgate-plugin-postgres/src/frontend/drivers.js @@ -15,6 +15,15 @@ const dialect = { return '"' + s + '"'; }, stringAgg: true, + + createColumn: true, + dropColumn: true, + createIndex: true, + dropIndex: true, + createForeignKey: true, + dropForeignKey: true, + createPrimaryKey: true, + dropPrimaryKey: true, }; const postgresDriverBase = { @@ -35,15 +44,6 @@ const postgresDriver = { dialect: { ...dialect, materializedViews: true, - - createColumn: true, - dropColumn: true, - createIndex: true, - dropIndex: true, - createForeignKey: true, - dropForeignKey: true, - createPrimaryKey: true, - dropPrimaryKey: true, }, }; diff --git a/plugins/dbgate-plugin-sqlite/src/frontend/driver.js b/plugins/dbgate-plugin-sqlite/src/frontend/driver.js index d1f0fee2e..d8bbda00f 100644 --- a/plugins/dbgate-plugin-sqlite/src/frontend/driver.js +++ b/plugins/dbgate-plugin-sqlite/src/frontend/driver.js @@ -17,7 +17,7 @@ const dialect = { explicitDropConstraint: true, stringEscapeChar: "'", fallbackDataType: 'nvarchar(max)', - dropColumnDependencies: ['index', 'primaryKey'], + dropColumnDependencies: ['indexes', 'primaryKey'], quoteIdentifier(s) { return `[${s}]`; },