feat: basic cassandra tests setup

This commit is contained in:
Nybkox
2025-01-28 20:45:22 +01:00
parent 516393856d
commit 8359746f47
5 changed files with 169 additions and 87 deletions

View File

@@ -1,7 +1,7 @@
const stableStringify = require('json-stable-stringify'); const stableStringify = require('json-stable-stringify');
const _ = require('lodash'); const _ = require('lodash');
const fp = require('lodash/fp'); const fp = require('lodash/fp');
const { testWrapper } = require('../tools'); const { testWrapper, removeNotNull, transformSqlForEngine } = require('../tools');
const engines = require('../engines'); const engines = require('../engines');
const crypto = require('crypto'); const crypto = require('crypto');
const { const {
@@ -33,36 +33,36 @@ function checkTableStructure(engine, t1, t2) {
} }
async function testTableDiff(engine, conn, driver, mangle) { 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( const query = formatQueryWithoutParams(
conn, driver,
formatQueryWithoutParams( `create table ~t1 (
driver,
`create table ~t1 (
~col_pk int not null primary key, ~col_pk int not null primary key,
~col_std int, ~col_std int,
~col_def int default 12, ~col_def int ${engine.skipDefaultValue ? '' : 'default 12'},
${engine.skipReferences ? '' : '~col_fk int references ~t0(~id),'} ${engine.skipReferences ? '' : '~col_fk int references ~t0(~id),'}
~col_idx int, ~col_idx int,
~col_uq int ${engine.skipUnique ? '' : 'unique'} , ~col_uq int ${engine.skipUnique ? '' : 'unique'} ,
~col_ref int ${engine.skipUnique ? '' : 'unique'} ~col_ref int ${engine.skipUnique ? '' : 'unique'}
)` )`
)
); );
await driver.query(conn, transformSqlForEngine(engine, query));
if (!engine.skipIndexes) { 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) { if (!engine.skipReferences) {
await driver.query( const query = formatQueryWithoutParams(
conn, driver,
formatQueryWithoutParams( `create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))`
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'); 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_std'];
// const TESTED_COLUMNS = ['col_ref']; // const TESTED_COLUMNS = ['col_ref'];
function engines_columns_source() { function create_engines_columns_source(engines) {
return _.flatten( return _.flatten(
engines.map(engine => engines.map(engine =>
TESTED_COLUMNS.filter(col => !col.endsWith('_pk') || !engine.skipPkColumnTesting).map(column => [ TESTED_COLUMNS.filter(col => col.endsWith('_pk') || !engine.skipNonPkRename)
engine.label, .filter(col => !col.endsWith('_pk') || !engine.skipPkColumnTesting)
column, .map(column => [engine.label, column, engine])
engine,
])
) )
); );
} }
@@ -117,26 +115,45 @@ describe('Alter table', () => {
}) })
); );
test.each(engines_columns_source())( const columnsSource = create_engines_columns_source(engines);
'Drop column - %s - %s', const dropableColumnsSrouce = columnsSource.filter(
testWrapper(async (conn, driver, column, engine) => { ([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')
await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)));
})
); );
const hasDropableColumns = dropableColumnsSrouce.length > 0;
test.each(engines_columns_source())( if (hasDropableColumns) {
'Change nullability - %s - %s', test.each(dropableColumnsSrouce)(
testWrapper(async (conn, driver, column, engine) => { 'Drop column - %s - %s',
await testTableDiff( testWrapper(async (conn, driver, column, engine) => {
engine, await testTableDiff(
conn, engine,
driver, conn,
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x))) 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', 'Rename column - %s - %s',
testWrapper(async (conn, driver, column, engine) => { testWrapper(async (conn, driver, column, engine) => {
await testTableDiff( await testTableDiff(
@@ -157,32 +174,37 @@ describe('Alter table', () => {
}) })
); );
test.each(engines.map(engine => [engine.label, engine]))( const enginesWithDefault = engines.filter(x => !x.skipDefaultValue);
'Add default value - %s', const hasEnginesWithDefault = enginesWithDefault.length > 0;
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]))( if (hasEnginesWithDefault) {
'Unset default value - %s', test.each(enginesWithDefault.map(engine => [engine.label, engine]))(
testWrapper(async (conn, driver, engine) => { 'Add default value - %s',
await testTableDiff(engine, conn, driver, tbl => { testWrapper(async (conn, driver, engine) => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined; await testTableDiff(engine, conn, driver, tbl => {
}); tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123';
}) });
); })
);
test.each(engines.map(engine => [engine.label, engine]))( test.each(enginesWithDefault.map(engine => [engine.label, engine]))(
'Change default value - %s', 'Unset default value - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => { await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567'; 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]))( // test.each(engines.map(engine => [engine.label, engine]))(
// 'Change autoincrement - %s', // 'Change autoincrement - %s',

View File

@@ -85,31 +85,36 @@ describe('DB Import/export', () => {
}) })
); );
test.each(engines.filter(x => x.dumpFile).map(engine => [engine.label, engine]))( const enginesWithDumpFile = engines.filter(x => x.dumpFile);
'Import SQL dump - %s', const hasEnginesWithDumpFile = enginesWithDumpFile.length > 0;
testWrapper(async (conn, driver, engine) => {
// const reader = await fakeObjectReader({ delay: 10 });
// const reader = await fakeObjectReader();
await importDatabase({
systemConnection: conn,
driver,
inputFile: engine.dumpFile,
});
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 structure = await driver.analyseFull(conn);
const res = await driver.query(conn, check.sql);
expect(res.rows[0].res.toString()).toEqual(check.res);
}
// const res1 = await driver.query(conn, `select count(*) as cnt from t1`); for (const check of engine.dumpChecks || []) {
// expect(res1.rows[0].cnt.toString()).toEqual('6'); 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`); // const res1 = await driver.query(conn, `select count(*) as cnt from t1`);
// expect(res2.rows[0].cnt.toString()).toEqual('6'); // 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]))( test.each(engines.map(engine => [engine.label, engine]))(
'Export one table - %s', 'Export one table - %s',

View File

@@ -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', 'Change column to NOT NULL column with default - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(engine, conn, driver, [ await testDatabaseDeploy(engine, conn, driver, [

View File

@@ -1,3 +1,4 @@
// @ts-check
const views = { const views = {
type: 'views', type: 'views',
create1: 'CREATE VIEW ~obj1 AS SELECT ~id FROM ~t1', create1: 'CREATE VIEW ~obj1 AS SELECT ~id FROM ~t1',
@@ -13,6 +14,7 @@ const matviews = {
drop2: 'DROP MATERIALIZED VIEW obj2', drop2: 'DROP MATERIALIZED VIEW obj2',
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const mysqlEngine = { const mysqlEngine = {
label: 'MySQL', label: 'MySQL',
connection: { connection: {
@@ -160,6 +162,7 @@ const mysqlEngine = {
], ],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const mariaDbEngine = { const mariaDbEngine = {
label: 'MariaDB', label: 'MariaDB',
connection: { connection: {
@@ -180,6 +183,7 @@ const mariaDbEngine = {
], ],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const postgreSqlEngine = { const postgreSqlEngine = {
label: 'PostgreSQL', label: 'PostgreSQL',
connection: { connection: {
@@ -352,6 +356,7 @@ $$ LANGUAGE plpgsql;`,
], ],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const sqlServerEngine = { const sqlServerEngine = {
label: 'SQL Server', label: 'SQL Server',
connection: { connection: {
@@ -465,6 +470,7 @@ const sqlServerEngine = {
], ],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const sqliteEngine = { const sqliteEngine = {
label: 'SQLite', label: 'SQLite',
generateDbFile: true, generateDbFile: true,
@@ -500,6 +506,7 @@ const sqliteEngine = {
], ],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const cockroachDbEngine = { const cockroachDbEngine = {
label: 'CockroachDB', label: 'CockroachDB',
connection: { connection: {
@@ -511,6 +518,7 @@ const cockroachDbEngine = {
objects: [views, matviews], objects: [views, matviews],
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const clickhouseEngine = { const clickhouseEngine = {
label: 'ClickHouse', label: 'ClickHouse',
connection: { connection: {
@@ -533,6 +541,7 @@ const clickhouseEngine = {
skipChangeColumn: true, skipChangeColumn: true,
}; };
/** @type {import('dbgate-types').TestEngineInfo} */
const oracleEngine = { const oracleEngine = {
label: 'Oracle', label: 'Oracle',
connection: { 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 = [ const enginesOnCi = [
// all engines, which would be run on GitHub actions // all engines, which would be run on GitHub actions
mysqlEngine, mysqlEngine,
@@ -606,16 +639,18 @@ 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, cassandraEngine,
// mysqlEngine,
// mariaDbEngine, // mariaDbEngine,
// postgreSqlEngine, // postgreSqlEngine,
// sqlServerEngine, // sqlServerEngine,
sqliteEngine, // sqliteEngine,
// cockroachDbEngine, // cockroachDbEngine,
// clickhouseEngine, // clickhouseEngine,
// oracleEngine, // oracleEngine,
]; ];
/** @type {any} */
module.exports = process.env.CITEST ? enginesOnCi : enginesOnLocal; module.exports = process.env.CITEST ? enginesOnCi : enginesOnLocal;
module.exports.mysqlEngine = mysqlEngine; module.exports.mysqlEngine = mysqlEngine;
@@ -626,3 +661,4 @@ module.exports.sqliteEngine = sqliteEngine;
module.exports.cockroachDbEngine = cockroachDbEngine; module.exports.cockroachDbEngine = cockroachDbEngine;
module.exports.clickhouseEngine = clickhouseEngine; module.exports.clickhouseEngine = clickhouseEngine;
module.exports.oracleEngine = oracleEngine; module.exports.oracleEngine = oracleEngine;
module.exports.cassandraEngine = cassandraEngine;

View File

@@ -1,3 +1,4 @@
// @ts-check
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver'); const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
const crypto = require('crypto'); const crypto = require('crypto');
@@ -81,9 +82,27 @@ const testWrapperPrepareOnly =
await body(conn, driver, ...other); 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 = { module.exports = {
randomDbName, randomDbName,
connect, connect,
testWrapper, testWrapper,
testWrapperPrepareOnly, testWrapperPrepareOnly,
transformSqlForEngine,
}; };