diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 5687cc612..c178e3e8d 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -1,7 +1,7 @@ const stableStringify = require('json-stable-stringify'); const _ = require('lodash'); const fp = require('lodash/fp'); -const { testWrapper } = require('../tools'); +const { testWrapper, removeNotNull, transformSqlForEngine } = require('../tools'); const engines = require('../engines'); const crypto = require('crypto'); const { @@ -33,36 +33,36 @@ function checkTableStructure(engine, t1, t2) { } async function testTableDiff(engine, conn, driver, mangle) { - await driver.query(conn, formatQueryWithoutParams(driver, `create table ~t0 (~id int not null primary key)`)); + const initQuery = formatQueryWithoutParams(driver, `create table ~t0 (~id int not null primary key)`); + await driver.query(conn, transformSqlForEngine(engine, initQuery)); - await driver.query( - conn, - formatQueryWithoutParams( - driver, - `create table ~t1 ( + const query = formatQueryWithoutParams( + driver, + `create table ~t1 ( ~col_pk int not null primary key, ~col_std int, - ~col_def int default 12, + ~col_def int ${engine.skipDefaultValue ? '' : 'default 12'}, ${engine.skipReferences ? '' : '~col_fk int references ~t0(~id),'} ~col_idx int, ~col_uq int ${engine.skipUnique ? '' : 'unique'} , ~col_ref int ${engine.skipUnique ? '' : 'unique'} )` - ) ); + await driver.query(conn, transformSqlForEngine(engine, query)); + if (!engine.skipIndexes) { - await driver.query(conn, formatQueryWithoutParams(driver, `create index ~idx1 on ~t1(~col_idx)`)); + const query = formatQueryWithoutParams(driver, `create index ~idx1 on ~t1(~col_idx)`); + await driver.query(conn, transformSqlForEngine(engine, query)); } if (!engine.skipReferences) { - await driver.query( - conn, - formatQueryWithoutParams( - driver, - `create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))` - ) + const query = formatQueryWithoutParams( + driver, + `create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))` ); + + await driver.query(conn, transformSqlForEngine(engine, query)); } const tget = x => x.tables.find(y => y.pureName == 't1'); @@ -89,14 +89,12 @@ const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'co // const TESTED_COLUMNS = ['col_std']; // const TESTED_COLUMNS = ['col_ref']; -function engines_columns_source() { +function create_engines_columns_source(engines) { return _.flatten( engines.map(engine => - TESTED_COLUMNS.filter(col => !col.endsWith('_pk') || !engine.skipPkColumnTesting).map(column => [ - engine.label, - column, - engine, - ]) + TESTED_COLUMNS.filter(col => col.endsWith('_pk') || !engine.skipNonPkRename) + .filter(col => !col.endsWith('_pk') || !engine.skipPkColumnTesting) + .map(column => [engine.label, column, engine]) ) ); } @@ -117,26 +115,45 @@ describe('Alter table', () => { }) ); - test.each(engines_columns_source())( - 'Drop column - %s - %s', - testWrapper(async (conn, driver, column, engine) => { - await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column))); - }) + const columnsSource = create_engines_columns_source(engines); + const dropableColumnsSrouce = columnsSource.filter( + ([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk') ); + const hasDropableColumns = dropableColumnsSrouce.length > 0; - test.each(engines_columns_source())( - 'Change nullability - %s - %s', - testWrapper(async (conn, driver, column, engine) => { - await testTableDiff( - engine, - conn, - driver, - tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x))) - ); - }) - ); + if (hasDropableColumns) { + test.each(dropableColumnsSrouce)( + 'Drop column - %s - %s', + testWrapper(async (conn, driver, column, engine) => { + await testTableDiff( + engine, + conn, + driver, + tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)) + ); + }) + ); + } - test.each(engines_columns_source())( + const hasEnginesWithNullable = engines.filter(x => !x.skipNullable).length > 0; + + if (hasEnginesWithNullable) { + const source = create_engines_columns_source(engines.filter(x => !x.skipNullable)); + + test.each(source)( + 'Change nullability - %s - %s', + testWrapper(async (conn, driver, column, engine) => { + await testTableDiff( + engine, + conn, + driver, + tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x))) + ); + }) + ); + } + + test.each(columnsSource)( 'Rename column - %s - %s', testWrapper(async (conn, driver, column, engine) => { await testTableDiff( @@ -157,32 +174,37 @@ describe('Alter table', () => { }) ); - test.each(engines.map(engine => [engine.label, engine]))( - 'Add default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123'; - }); - }) - ); + const enginesWithDefault = engines.filter(x => !x.skipDefaultValue); + const hasEnginesWithDefault = enginesWithDefault.length > 0; - test.each(engines.map(engine => [engine.label, engine]))( - 'Unset default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined; - }); - }) - ); + if (hasEnginesWithDefault) { + test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + 'Add default value - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(engine, conn, driver, tbl => { + tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123'; + }); + }) + ); - test.each(engines.map(engine => [engine.label, engine]))( - 'Change default value - %s', - testWrapper(async (conn, driver, engine) => { - await testTableDiff(engine, conn, driver, tbl => { - tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567'; - }); - }) - ); + test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + 'Unset default value - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(engine, conn, driver, tbl => { + tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined; + }); + }) + ); + + test.each(enginesWithDefault.map(engine => [engine.label, engine]))( + 'Change default value - %s', + testWrapper(async (conn, driver, engine) => { + await testTableDiff(engine, conn, driver, tbl => { + tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567'; + }); + }) + ); + } // test.each(engines.map(engine => [engine.label, engine]))( // 'Change autoincrement - %s', diff --git a/integration-tests/__tests__/db-import-export.spec.js b/integration-tests/__tests__/db-import-export.spec.js index 5c2a5557e..16a228a69 100644 --- a/integration-tests/__tests__/db-import-export.spec.js +++ b/integration-tests/__tests__/db-import-export.spec.js @@ -85,31 +85,36 @@ describe('DB Import/export', () => { }) ); - test.each(engines.filter(x => x.dumpFile).map(engine => [engine.label, engine]))( - 'Import SQL dump - %s', - testWrapper(async (conn, driver, engine) => { - // const reader = await fakeObjectReader({ delay: 10 }); - // const reader = await fakeObjectReader(); - await importDatabase({ - systemConnection: conn, - driver, - inputFile: engine.dumpFile, - }); + const enginesWithDumpFile = engines.filter(x => x.dumpFile); + const hasEnginesWithDumpFile = enginesWithDumpFile.length > 0; - const structure = await driver.analyseFull(conn); + if (hasEnginesWithDumpFile) { + test.each(enginesWithDumpFile.filter(x => x.dumpFile).map(engine => [engine.label, engine]))( + 'Import SQL dump - %s', + testWrapper(async (conn, driver, engine) => { + // const reader = await fakeObjectReader({ delay: 10 }); + // const reader = await fakeObjectReader(); + await importDatabase({ + systemConnection: conn, + driver, + inputFile: engine.dumpFile, + }); - for (const check of engine.dumpChecks || []) { - const res = await driver.query(conn, check.sql); - expect(res.rows[0].res.toString()).toEqual(check.res); - } + const structure = await driver.analyseFull(conn); - // const res1 = await driver.query(conn, `select count(*) as cnt from t1`); - // expect(res1.rows[0].cnt.toString()).toEqual('6'); + for (const check of engine.dumpChecks || []) { + const res = await driver.query(conn, check.sql); + expect(res.rows[0].res.toString()).toEqual(check.res); + } - // const res2 = await driver.query(conn, `select count(*) as cnt from t2`); - // expect(res2.rows[0].cnt.toString()).toEqual('6'); - }) - ); + // const res1 = await driver.query(conn, `select count(*) as cnt from t1`); + // expect(res1.rows[0].cnt.toString()).toEqual('6'); + + // const res2 = await driver.query(conn, `select count(*) as cnt from t2`); + // expect(res2.rows[0].cnt.toString()).toEqual('6'); + }) + ); + } test.each(engines.map(engine => [engine.label, engine]))( 'Export one table - %s', diff --git a/integration-tests/__tests__/deploy-database.spec.js b/integration-tests/__tests__/deploy-database.spec.js index 438f7d554..011907de6 100644 --- a/integration-tests/__tests__/deploy-database.spec.js +++ b/integration-tests/__tests__/deploy-database.spec.js @@ -448,7 +448,7 @@ describe('Deploy database', () => { }) ); - test.each(engines.filter(x => !x.skipChangeColumn).map(engine => [engine.label, engine]))( + test.each(engines.filter(x => !x.skipChangeColumn || x.skipNullability).map(engine => [engine.label, engine]))( 'Change column to NOT NULL column with default - %s', testWrapper(async (conn, driver, engine) => { await testDatabaseDeploy(engine, conn, driver, [ diff --git a/integration-tests/engines.js b/integration-tests/engines.js index d2617d2f6..908a2cbcd 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -1,3 +1,4 @@ +// @ts-check const views = { type: 'views', create1: 'CREATE VIEW ~obj1 AS SELECT ~id FROM ~t1', @@ -13,6 +14,7 @@ const matviews = { drop2: 'DROP MATERIALIZED VIEW obj2', }; +/** @type {import('dbgate-types').TestEngineInfo} */ const mysqlEngine = { label: 'MySQL', connection: { @@ -160,6 +162,7 @@ const mysqlEngine = { ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const mariaDbEngine = { label: 'MariaDB', connection: { @@ -180,6 +183,7 @@ const mariaDbEngine = { ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const postgreSqlEngine = { label: 'PostgreSQL', connection: { @@ -352,6 +356,7 @@ $$ LANGUAGE plpgsql;`, ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const sqlServerEngine = { label: 'SQL Server', connection: { @@ -465,6 +470,7 @@ const sqlServerEngine = { ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const sqliteEngine = { label: 'SQLite', generateDbFile: true, @@ -500,6 +506,7 @@ const sqliteEngine = { ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const cockroachDbEngine = { label: 'CockroachDB', connection: { @@ -511,6 +518,7 @@ const cockroachDbEngine = { objects: [views, matviews], }; +/** @type {import('dbgate-types').TestEngineInfo} */ const clickhouseEngine = { label: 'ClickHouse', connection: { @@ -533,6 +541,7 @@ const clickhouseEngine = { skipChangeColumn: true, }; +/** @type {import('dbgate-types').TestEngineInfo} */ const oracleEngine = { label: 'Oracle', connection: { @@ -592,6 +601,30 @@ const oracleEngine = { ], }; +/** @type {import('dbgate-types').TestEngineInfo} */ +const cassandraEngine = { + label: 'Cassandra', + connection: { + server: 'localhost:15942', + engine: 'cassandra@dbgate-plugin-cassandra', + }, + removeNotNull: true, + + alterTableAddColumnSyntax: false, + skipOnCI: false, + skipReferences: true, + // dbSnapshotBySeconds: true, + // setNullDefaultInsteadOfDrop: true, + skipIncrementalAnalysis: true, + skipNonPkRename: true, + skipPkDrop: true, + skipDefaultValue: true, + skipNullability: true, + skipUnique: true, + skipIndexes: true, + // objects: [], +}; + const enginesOnCi = [ // all engines, which would be run on GitHub actions mysqlEngine, @@ -606,16 +639,18 @@ const enginesOnCi = [ const enginesOnLocal = [ // all engines, which would be run on local test - mysqlEngine, + cassandraEngine, + // mysqlEngine, // mariaDbEngine, // postgreSqlEngine, // sqlServerEngine, - sqliteEngine, + // sqliteEngine, // cockroachDbEngine, // clickhouseEngine, // oracleEngine, ]; +/** @type {any} */ module.exports = process.env.CITEST ? enginesOnCi : enginesOnLocal; module.exports.mysqlEngine = mysqlEngine; @@ -626,3 +661,4 @@ module.exports.sqliteEngine = sqliteEngine; module.exports.cockroachDbEngine = cockroachDbEngine; module.exports.clickhouseEngine = clickhouseEngine; module.exports.oracleEngine = oracleEngine; +module.exports.cassandraEngine = cassandraEngine; diff --git a/integration-tests/tools.js b/integration-tests/tools.js index 5f39398c2..7b13a2657 100644 --- a/integration-tests/tools.js +++ b/integration-tests/tools.js @@ -1,3 +1,4 @@ +// @ts-check const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver'); const crypto = require('crypto'); @@ -81,9 +82,27 @@ const testWrapperPrepareOnly = await body(conn, driver, ...other); }; +/** @param {string} sql + * @returns {string} */ +const removeNotNull = sql => sql.replace(/not null/gi, ''); + +/** @param {import('dbgate-types').TestEngineInfo} engine + * @param {string} sql + * @returns {string} */ +const transformSqlForEngine = (engine, sql) => { + let result = sql; + + if (engine.removeNotNull) { + result = removeNotNull(result); + } + + return result; +}; + module.exports = { randomDbName, connect, testWrapper, testWrapperPrepareOnly, + transformSqlForEngine, };