This commit is contained in:
SPRINX0\prochazka
2024-12-13 16:27:17 +01:00
24 changed files with 380 additions and 255 deletions

View File

@@ -1,41 +0,0 @@
name: E2E tests
on:
push:
branches:
- master
- develop
- 'feature/**'
jobs:
e2e-tests:
runs-on: ubuntu-latest
container: node:18
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: yarn install
run: |
yarn install
- name: Build
run: |
yarn prepare:packer
- name: yarn install cypress
run: |
cd e2e-tests
yarn install
- name: Run Cypress tests
run: |
cd e2e-tests
yarn test:ci
services:
mysql:
image: mysql:8.0.18
env:
MYSQL_ROOT_PASSWORD: Pwd2020Db

View File

@@ -12,16 +12,27 @@ jobs:
container: node:18 container: node:18
steps: steps:
- name: Context - name: Install dependencies for cypress
env: run: |
GITHUB_CONTEXT: ${{ toJson(github) }} apt-get update
run: echo "$GITHUB_CONTEXT" apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: yarn install - name: yarn install
run: | run: |
yarn install yarn install
- name: Build packer dist for cypress
run: |
yarn prepare:packer
- name: yarn install cypress
run: |
cd e2e-tests
yarn install
- name: Run Cypress tests
run: |
cd e2e-tests
yarn test:ci
- name: Integration tests - name: Integration tests
run: | run: |
cd integration-tests cd integration-tests
@@ -84,5 +95,10 @@ jobs:
env: env:
CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db
oracle:
image: gvenzl/oracle-xe:21-slim
env:
ORACLE_PASSWORD: Pwd2020Db
# cockroachdb: # cockroachdb:
# image: cockroachdb/cockroach # image: cockroachdb/cockroach

View File

@@ -3,9 +3,15 @@ const _ = require('lodash');
const fp = require('lodash/fp'); const fp = require('lodash/fp');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const engines = require('../engines'); const engines = require('../engines');
const { getAlterDatabaseScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools'); const {
getAlterDatabaseScript,
extendDatabaseInfo,
generateDbPairingId,
formatQueryWithoutParams,
runCommandOnDriver,
} = require('dbgate-tools');
const initSql = ['CREATE TABLE t1 (id int primary key)', 'CREATE TABLE t2 (id int primary key)']; const initSql = ['CREATE TABLE ~t1 (~id int primary key)', 'CREATE TABLE ~t2 (~id int primary key)'];
function flatSource(engineCond = x => !x.skipReferences) { function flatSource(engineCond = x => !x.skipReferences) {
return _.flatten( return _.flatten(
@@ -16,13 +22,14 @@ function flatSource(engineCond = x => !x.skipReferences) {
} }
async function testDatabaseDiff(conn, driver, mangle, createObject = null) { async function testDatabaseDiff(conn, driver, mangle, createObject = null) {
await driver.query(conn, `create table t1 (id int not null primary key)`); await runCommandOnDriver(conn, driver, `create table ~t1 (~id int not null primary key)`);
await driver.query( await runCommandOnDriver(
conn, conn,
`create table t2 ( driver,
id int not null primary key, `create table ~t2 (
t1_id int null references t1(id) ~id int not null primary key,
~t1_id int null references ~t1(~id)
)` )`
); );
@@ -63,7 +70,7 @@ describe('Alter database', () => {
db => { db => {
_.remove(db[type], x => x.pureName == 'obj1'); _.remove(db[type], x => x.pureName == 'obj1');
}, },
object.create1 formatQueryWithoutParams(driver, object.create1)
); );
expect(db[type].length).toEqual(0); expect(db[type].length).toEqual(0);
}) })
@@ -72,9 +79,9 @@ describe('Alter database', () => {
test.each(flatSource(x => x.supportRenameSqlObject))( test.each(flatSource(x => x.supportRenameSqlObject))(
'Rename object - %s - %s', 'Rename object - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => { testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create1);
const structure = extendDatabaseInfo(await driver.analyseFull(conn)); const structure = extendDatabaseInfo(await driver.analyseFull(conn));

View File

@@ -4,7 +4,12 @@ const fp = require('lodash/fp');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const engines = require('../engines'); const engines = require('../engines');
const crypto = require('crypto'); const crypto = require('crypto');
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools'); const {
getAlterTableScript,
extendDatabaseInfo,
generateDbPairingId,
formatQueryWithoutParams,
} = require('dbgate-tools');
function pickImportantTableInfo(engine, table) { function pickImportantTableInfo(engine, table) {
const props = ['columnName', 'defaultValue']; const props = ['columnName', 'defaultValue'];
@@ -15,7 +20,10 @@ function pickImportantTableInfo(engine, table) {
columns: table.columns columns: table.columns
.filter(x => x.columnName != 'rowid') .filter(x => x.columnName != 'rowid')
.map(fp.pick(props)) .map(fp.pick(props))
.map(props => _.omitBy(props, x => x == null)), .map(props => _.omitBy(props, x => x == null))
.map(props =>
_.omitBy(props, (v, k) => k == 'defaultValue' && v == 'NULL' && engine.setNullDefaultInsteadOfDrop)
),
}; };
} }
@@ -25,27 +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, `create table t0 (id int not null primary key)`); await driver.query(conn, formatQueryWithoutParams(driver, `create table ~t0 (~id int not null primary key)`));
await driver.query( await driver.query(
conn, conn,
`create table t1 ( formatQueryWithoutParams(
col_pk int not null primary key, driver,
col_std int, `create table ~t1 (
col_def int default 12, ~col_pk int not null primary key,
${engine.skipReferences ? '' : 'col_fk int references t0(id),'} ~col_std int,
col_idx int, ~col_def int default 12,
col_uq int ${engine.skipUnique ? '' : 'unique'} , ${engine.skipReferences ? '' : '~col_fk int references ~t0(~id),'}
col_ref int ${engine.skipUnique ? '' : 'unique'} ~col_idx int,
~col_uq int ${engine.skipUnique ? '' : 'unique'} ,
~col_ref int ${engine.skipUnique ? '' : 'unique'}
)` )`
)
); );
if (!engine.skipIndexes) { if (!engine.skipIndexes) {
await driver.query(conn, `create index idx1 on t1(col_idx)`); await driver.query(conn, formatQueryWithoutParams(driver, `create index ~idx1 on ~t1(~col_idx)`));
} }
if (!engine.skipReferences) { if (!engine.skipReferences) {
await driver.query(conn, `create table t2 (id int not null primary key, fkval int null references t1(col_ref))`); await driver.query(
conn,
formatQueryWithoutParams(
driver,
`create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))`
)
);
} }
const tget = x => x.tables.find(y => y.pureName == 't1'); const tget = x => x.tables.find(y => y.pureName == 't1');
@@ -175,5 +192,4 @@ describe('Alter table', () => {
// }); // });
// }) // })
// ); // );
}); });

View File

@@ -2,7 +2,7 @@ const engines = require('../engines');
const stream = require('stream'); const stream = require('stream');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator'); const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator');
const { runCommandOnDriver } = require('dbgate-tools'); const { runCommandOnDriver, runQueryOnDriver } = require('dbgate-tools');
describe('Data duplicator', () => { describe('Data duplicator', () => {
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))( test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
@@ -84,10 +84,10 @@ describe('Data duplicator', () => {
], ],
}); });
const res1 = await driver.query(conn, `select count(*) as cnt from t1`); const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.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`); const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6'); expect(res2.rows[0].cnt.toString()).toEqual('6');
}) })
); );
@@ -145,13 +145,15 @@ describe('Data duplicator', () => {
}, },
}); });
const res1 = await driver.query(conn, `select count(*) as cnt from t1`); const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('1'); expect(res1.rows[0].cnt.toString()).toEqual('1');
const res2 = await driver.query(conn, `select count(*) as cnt from t2`); const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('2'); expect(res2.rows[0].cnt.toString()).toEqual('2');
const res3 = await driver.query(conn, `select count(*) as cnt from t2 where valfk is not null`); const res3 = await runQueryOnDriver(conn, driver, dmp =>
dmp.put(`select count(*) as ~cnt from ~t2 where ~valfk is not null`)
);
expect(res3.rows[0].cnt.toString()).toEqual('1'); expect(res3.rows[0].cnt.toString()).toEqual('1');
}) })
); );

View File

@@ -5,6 +5,7 @@ const tableWriter = require('dbgate-api/src/shell/tableWriter');
const copyStream = require('dbgate-api/src/shell/copyStream'); const copyStream = require('dbgate-api/src/shell/copyStream');
const importDatabase = require('dbgate-api/src/shell/importDatabase'); const importDatabase = require('dbgate-api/src/shell/importDatabase');
const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader'); const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader');
const { runQueryOnDriver } = require('dbgate-tools');
function createImportStream() { function createImportStream() {
const pass = new stream.PassThrough({ const pass = new stream.PassThrough({
@@ -37,7 +38,7 @@ describe('DB Import', () => {
}); });
await copyStream(reader, writer); await copyStream(reader, writer);
const res = await driver.query(conn, `select count(*) as cnt from t1`); const res = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res.rows[0].cnt.toString()).toEqual('6'); expect(res.rows[0].cnt.toString()).toEqual('6');
}) })
); );
@@ -65,10 +66,10 @@ describe('DB Import', () => {
}); });
await copyStream(reader2, writer2); await copyStream(reader2, writer2);
const res1 = await driver.query(conn, `select count(*) as cnt from t1`); const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.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`); const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6'); expect(res2.rows[0].cnt.toString()).toEqual('6');
}) })
); );

View File

@@ -4,7 +4,7 @@ const { testWrapper, testWrapperPrepareOnly } = require('../tools');
const _ = require('lodash'); const _ = require('lodash');
const engines = require('../engines'); const engines = require('../engines');
const deployDb = require('dbgate-api/src/shell/deployDb'); const deployDb = require('dbgate-api/src/shell/deployDb');
const { databaseInfoFromYamlModel } = require('dbgate-tools'); const { databaseInfoFromYamlModel, runQueryOnDriver, formatQueryWithoutParams } = require('dbgate-tools');
const generateDeploySql = require('dbgate-api/src/shell/generateDeploySql'); const generateDeploySql = require('dbgate-api/src/shell/generateDeploySql');
const connectUtility = require('dbgate-api/src/utility/connectUtility'); const connectUtility = require('dbgate-api/src/utility/connectUtility');
@@ -69,6 +69,29 @@ function checkStructure(
} }
} }
// function convertObjectText(text, driver) {
// if (!text) return undefined;
// text = formatQueryWithoutParams(driver, text);
// if (driver.dialect.requireFromDual && text.startsWith('create view ') && !text.includes('from')) {
// text = text + ' from dual';
// }
// return text;
// }
// function convertModelToEngine(model, driver) {
// return model.map(x => ({
// ...x,
// text: convertObjectText(x.text, driver),
// }));
// }
function convertModelToEngine(model, driver) {
return model.map(x => ({
...x,
text: x.text ? formatQueryWithoutParams(driver, x.text) : undefined,
}));
}
async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) { async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
const { testEmptyLastScript, finalCheckAgainstModel, markDeleted, allowDropStatements } = options || {}; const { testEmptyLastScript, finalCheckAgainstModel, markDeleted, allowDropStatements } = options || {};
let index = 0; let index = 0;
@@ -83,13 +106,13 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
for (const loadedDbModel of dbModelsYaml) { for (const loadedDbModel of dbModelsYaml) {
if (_.isString(loadedDbModel)) { if (_.isString(loadedDbModel)) {
await driver.script(conn, loadedDbModel); await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel));
} else { } else {
const { sql, isEmpty } = await generateDeploySql({ const { sql, isEmpty } = await generateDeploySql({
systemConnection: conn.isPreparedOnly ? undefined : conn, systemConnection: conn.isPreparedOnly ? undefined : conn,
connection: conn.isPreparedOnly ? conn : undefined, connection: conn.isPreparedOnly ? conn : undefined,
driver, driver,
loadedDbModel, loadedDbModel: convertModelToEngine(loadedDbModel, driver),
dbdiffOptionsExtra, dbdiffOptionsExtra,
}); });
console.debug('Generated deploy script:', sql); console.debug('Generated deploy script:', sql);
@@ -106,7 +129,7 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
systemConnection: conn.isPreparedOnly ? undefined : conn, systemConnection: conn.isPreparedOnly ? undefined : conn,
connection: conn.isPreparedOnly ? conn : undefined, connection: conn.isPreparedOnly ? conn : undefined,
driver, driver,
loadedDbModel, loadedDbModel: convertModelToEngine(loadedDbModel, driver),
dbdiffOptionsExtra, dbdiffOptionsExtra,
}); });
} }
@@ -117,7 +140,12 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
const dbhan = conn.isPreparedOnly ? await connectUtility(driver, conn, 'read') : conn; const dbhan = conn.isPreparedOnly ? await connectUtility(driver, conn, 'read') : conn;
const structure = await driver.analyseFull(dbhan); const structure = await driver.analyseFull(dbhan);
if (conn.isPreparedOnly) await driver.close(dbhan); if (conn.isPreparedOnly) await driver.close(dbhan);
checkStructure(engine, structure, finalCheckAgainstModel ?? _.findLast(dbModelsYaml, x => _.isArray(x)), options); checkStructure(
engine,
structure,
convertModelToEngine(finalCheckAgainstModel ?? _.findLast(dbModelsYaml, x => _.isArray(x)), driver),
options
);
} }
describe('Deploy database', () => { describe('Deploy database', () => {
@@ -339,7 +367,7 @@ describe('Deploy database', () => {
], ],
]); ]);
const res = await driver.query(conn, `select count(*) as cnt from t1`); const res = await runQueryOnDriver(conn, driver, `select count(*) as ~cnt from ~t1`);
expect(res.rows[0].cnt.toString()).toEqual('3'); expect(res.rows[0].cnt.toString()).toEqual('3');
}) })
); );
@@ -386,7 +414,7 @@ describe('Deploy database', () => {
], ],
]); ]);
const res = await driver.query(conn, `select val from t1 where id = 2`); const res = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 2`);
expect(res.rows[0].val.toString()).toEqual('5'); expect(res.rows[0].val.toString()).toEqual('5');
}) })
); );
@@ -414,8 +442,8 @@ describe('Deploy database', () => {
], ],
]); ]);
await driver.query(conn, `insert into t1 (id) values (1)`); await runQueryOnDriver(conn, driver, `insert into ~t1 (~id) values (1)`);
const res = await driver.query(conn, ` select val from t1 where id = 1`); const res = await runQueryOnDriver(conn, driver, ` select ~val from ~t1 where ~id = 1`);
expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20'); expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20');
}) })
); );
@@ -438,7 +466,7 @@ describe('Deploy database', () => {
}, },
}, },
], ],
'insert into t1 (id, val) values (1, 1); insert into t1 (id) values (2)', 'insert into ~t1 (~id, ~val) values (1, 1); insert into ~t1 (~id) values (2)',
[ [
{ {
name: 't1.table.yaml', name: 't1.table.yaml',
@@ -452,16 +480,16 @@ describe('Deploy database', () => {
}, },
}, },
], ],
'insert into t1 (id) values (3);', 'insert into ~t1 (~id) values (3);',
]); ]);
const res1 = await driver.query(conn, `select val from t1 where id = 1`); const res1 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 1`);
expect(res1.rows[0].val).toEqual(1); expect(res1.rows[0].val).toEqual(1);
const res2 = await driver.query(conn, `select val from t1 where id = 2`); const res2 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 2`);
expect(res2.rows[0].val).toEqual(20); expect(res2.rows[0].val).toEqual(20);
const res3 = await driver.query(conn, `select val from t1 where id = 3`); const res3 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 3`);
expect(res2.rows[0].val).toEqual(20); expect(res2.rows[0].val).toEqual(20);
}) })
); );
@@ -525,17 +553,17 @@ describe('Deploy database', () => {
const V1 = { const V1 = {
name: 'v1.view.sql', name: 'v1.view.sql',
text: 'create view v1 as select * from t1', text: 'create view ~v1 as select * from ~t1',
}; };
const V1_VARIANT2 = { const V1_VARIANT2 = {
name: 'v1.view.sql', name: 'v1.view.sql',
text: 'create view v1 as select 1 as c1', text: 'create view ~v1 as select ~id + ~id ~idsum from ~t1',
}; };
const V1_DELETED = { const V1_DELETED = {
name: '_deleted_v1.view.sql', name: '_deleted_v1.view.sql',
text: 'create view _deleted_v1 as select * from t1', text: 'create view ~_deleted_v1 as select * from ~t1',
}; };
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
@@ -682,15 +710,15 @@ describe('Deploy database', () => {
[ [
{ {
name: '1.predeploy.sql', name: '1.predeploy.sql',
text: 'create table t1 (id int primary key); insert into t1 (id) values (1);', text: 'create table ~t1 (~id int primary key); insert into ~t1 (~id) values (1);',
}, },
], ],
]); ]);
const res1 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t1'); const res1 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~t1');
expect(res1.rows[0].cnt == 1).toBeTruthy(); expect(res1.rows[0].cnt == 1).toBeTruthy();
const res2 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM dbgate_deploy_journal'); const res2 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~dbgate_deploy_journal');
expect(res2.rows[0].cnt == 1).toBeTruthy(); expect(res2.rows[0].cnt == 1).toBeTruthy();
}) })
); );
@@ -702,48 +730,53 @@ describe('Deploy database', () => {
[ [
{ {
name: 't1.uninstall.sql', name: 't1.uninstall.sql',
text: 'drop table t1', text: 'drop table ~t1',
}, },
{ {
name: 't1.install.sql', name: 't1.install.sql',
text: 'create table t1 (id int primary key); insert into t1 (id) values (1)', text: 'create table ~t1 (~id int primary key); insert into ~t1 (~id) values (1)',
}, },
{ {
name: 't2.once.sql', name: 't2.once.sql',
text: 'create table t2 (id int primary key); insert into t2 (id) values (1)', text: 'create table ~t2 (~id int primary key); insert into ~t2 (~id) values (1)',
}, },
], ],
[ [
{ {
name: 't1.uninstall.sql', name: 't1.uninstall.sql',
text: 'drop table t1', text: 'drop table ~t1',
}, },
{ {
name: 't1.install.sql', name: 't1.install.sql',
text: 'create table t1 (id int primary key, val int); insert into t1 (id, val) values (1, 11)', text: 'create table ~t1 (~id int primary key, ~val int); insert into ~t1 (~id, ~val) values (1, 11)',
}, },
{ {
name: 't2.once.sql', name: 't2.once.sql',
text: 'insert into t2 (id) values (2)', text: 'insert into ~t2 (~id) values (2)',
}, },
], ],
]); ]);
const res1 = await driver.query(conn, 'SELECT val from t1 where id = 1'); const res1 = await runQueryOnDriver(conn, driver, 'SELECT ~val from ~t1 where ~id = 1');
expect(res1.rows[0].val == 11).toBeTruthy(); expect(res1.rows[0].val == 11).toBeTruthy();
const res2 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t2'); const res2 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~t2');
expect(res2.rows[0].cnt == 1).toBeTruthy(); expect(res2.rows[0].cnt == 1).toBeTruthy();
const res3 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM dbgate_deploy_journal'); const res3 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~dbgate_deploy_journal');
expect(res3.rows[0].cnt == 3).toBeTruthy(); expect(res3.rows[0].cnt == 3).toBeTruthy();
const res4 = await driver.query(conn, "SELECT run_count from dbgate_deploy_journal where name = 't2.once.sql'"); const res4 = await runQueryOnDriver(
conn,
driver,
"SELECT ~run_count from ~dbgate_deploy_journal where ~name = 't2.once.sql'"
);
expect(res4.rows[0].run_count == 1).toBeTruthy(); expect(res4.rows[0].run_count == 1).toBeTruthy();
const res5 = await driver.query( const res5 = await runQueryOnDriver(
conn, conn,
"SELECT run_count from dbgate_deploy_journal where name = 't1.install.sql'" driver,
"SELECT ~run_count from ~dbgate_deploy_journal where ~name = 't1.install.sql'"
); );
expect(res5.rows[0].run_count == 2).toBeTruthy(); expect(res5.rows[0].run_count == 2).toBeTruthy();
}) })

View File

@@ -1,8 +1,9 @@
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const engines = require('../engines'); const engines = require('../engines');
const _ = require('lodash'); const _ = require('lodash');
const { formatQueryWithoutParams, runCommandOnDriver } = require('dbgate-tools');
const initSql = ['CREATE TABLE t1 (id int primary key)', 'CREATE TABLE t2 (id int primary key)']; const initSql = ['CREATE TABLE ~t1 (~id int primary key)', 'CREATE TABLE ~t2 (~id int primary key)'];
function flatSource() { function flatSource() {
return _.flatten( return _.flatten(
@@ -34,9 +35,9 @@ describe('Object analyse', () => {
test.each(flatSource())( test.each(flatSource())(
'Full analysis - %s - %s', 'Full analysis - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => { testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create1);
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
expect(structure[type].length).toEqual(1); expect(structure[type].length).toEqual(1);
@@ -47,11 +48,11 @@ describe('Object analyse', () => {
test.each(flatSource())( test.each(flatSource())(
'Incremental analysis - add - %s - %s', 'Incremental analysis - add - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => { testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create2, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create2);
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.create1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create1);
const structure2 = await driver.analyseIncremental(conn, structure1); const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(2); expect(structure2[type].length).toEqual(2);
@@ -62,12 +63,12 @@ describe('Object analyse', () => {
test.each(flatSource())( test.each(flatSource())(
'Incremental analysis - drop - %s - %s', 'Incremental analysis - drop - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => { testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create1);
await driver.query(conn, object.create2, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create2);
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.drop2, { discardResult: true }); await runCommandOnDriver(conn, driver, object.drop2);
const structure2 = await driver.analyseIncremental(conn, structure1); const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(1); expect(structure2[type].length).toEqual(1);
@@ -78,11 +79,11 @@ describe('Object analyse', () => {
test.each(flatSource())( test.each(flatSource())(
'Create SQL - add - %s - %s', 'Create SQL - add - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => { testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.create1);
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.drop1, { discardResult: true }); await runCommandOnDriver(conn, driver, object.drop1);
const structure2 = await driver.analyseIncremental(conn, structure1); const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(0); expect(structure2[type].length).toEqual(0);
@@ -98,10 +99,10 @@ describe('Object analyse', () => {
test.each(flatSourceParameters())( test.each(flatSourceParameters())(
'Test parameters simple analyse - %s - %s', 'Test parameters simple analyse - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => { testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of engine.parametersOtherSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, parameter.create, { discardResult: true }); await runCommandOnDriver(conn, driver, parameter.create);
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters; const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
@@ -116,15 +117,15 @@ describe('Object analyse', () => {
test.each(flatSourceParameters())( test.each(flatSourceParameters())(
'Test parameters create SQL - %s - %s', 'Test parameters create SQL - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => { testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of engine.parametersOtherSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, parameter.create, { discardResult: true }); await runCommandOnDriver(conn, driver, parameter.create);
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
await driver.query(conn, parameter.drop, { discardResult: true }); await runCommandOnDriver(conn, driver, parameter.drop);
const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1'); const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1');
await driver.script(conn, obj.createSql); await driver.script(conn, obj.createSql, { discardResult: true });
const structure2 = await driver.analyseFull(conn); const structure2 = await driver.analyseFull(conn);
const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters; const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;

View File

@@ -1,11 +1,12 @@
const engines = require('../engines'); const engines = require('../engines');
const { splitQuery } = require('dbgate-query-splitter'); const { splitQuery } = require('dbgate-query-splitter');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const { runQueryOnDriver, runCommandOnDriver, formatQueryWithoutParams } = require('dbgate-tools');
const initSql = [ const initSql = [
'CREATE TABLE t1 (id int primary key)', 'CREATE TABLE ~t1 (~id int primary key)',
'INSERT INTO t1 (id) VALUES (1)', 'INSERT INTO ~t1 (~id) VALUES (1)',
'INSERT INTO t1 (id) VALUES (2)', 'INSERT INTO ~t1 (~id) VALUES (2)',
]; ];
expect.extend({ expect.extend({
@@ -51,7 +52,7 @@ class StreamHandler {
function executeStreamItem(driver, conn, sql) { function executeStreamItem(driver, conn, sql) {
return new Promise(resolve => { return new Promise(resolve => {
const handler = new StreamHandler(resolve); const handler = new StreamHandler(resolve);
driver.stream(conn, sql, handler); driver.stream(conn, formatQueryWithoutParams(driver, sql), handler);
}); });
} }
@@ -68,9 +69,11 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Simple query - %s', 'Simple query - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const res = await driver.query(conn, 'SELECT id FROM t1 ORDER BY id'); const res = await runQueryOnDriver(conn, driver, dmp => dmp.put('SELECT ~id FROM ~t1 ORDER BY ~id'));
expect(res.columns).toEqual([ expect(res.columns).toEqual([
expect.objectContaining({ expect.objectContaining({
columnName: 'id', columnName: 'id',
@@ -91,8 +94,11 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Simple stream query - %s', 'Simple stream query - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) {
const results = await executeStream(driver, conn, 'SELECT id FROM t1 ORDER BY id'); await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const results = await executeStream(driver, conn, 'SELECT ~id FROM ~t1 ORDER BY ~id');
expect(results.length).toEqual(1); expect(results.length).toEqual(1);
const res = results[0]; const res = results[0];
@@ -104,11 +110,14 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'More queries - %s', 'More queries - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const results = await executeStream( const results = await executeStream(
driver, driver,
conn, conn,
'SELECT id FROM t1 ORDER BY id; SELECT id FROM t1 ORDER BY id DESC' 'SELECT ~id FROM ~t1 ORDER BY ~id; SELECT ~id FROM ~t1 ORDER BY ~id DESC'
); );
expect(results.length).toEqual(2); expect(results.length).toEqual(2);
@@ -128,7 +137,7 @@ describe('Query', () => {
const results = await executeStream( const results = await executeStream(
driver, driver,
conn, conn,
'CREATE TABLE t1 (id int primary key); INSERT INTO t1 (id) VALUES (1); INSERT INTO t1 (id) VALUES (2); SELECT id FROM t1 ORDER BY id; ' 'CREATE TABLE ~t1 (~id int primary key); INSERT INTO ~t1 (~id) VALUES (1); INSERT INTO ~t1 (~id) VALUES (2); SELECT ~id FROM ~t1 ORDER BY ~id; '
); );
expect(results.length).toEqual(1); expect(results.length).toEqual(1);
@@ -144,7 +153,7 @@ describe('Query', () => {
const results = await executeStream( const results = await executeStream(
driver, driver,
conn, conn,
'CREATE TABLE t1 (id int); INSERT INTO t1 (id) VALUES (1); INSERT INTO t1 (id) VALUES (2) ' 'CREATE TABLE ~t1 (~id int); INSERT INTO ~t1 (~id) VALUES (1); INSERT INTO ~t1 (~id) VALUES (2) '
); );
expect(results.length).toEqual(0); expect(results.length).toEqual(0);
}) })
@@ -153,16 +162,57 @@ describe('Query', () => {
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))( test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
'Save data query - %s', 'Save data query - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true }); for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
await driver.script( await driver.script(
conn, conn,
'INSERT INTO t1 (id) VALUES (3);INSERT INTO t1 (id) VALUES (4);UPDATE t1 SET id=10 WHERE id=1;DELETE FROM t1 WHERE id=2;', formatQueryWithoutParams(
driver,
'INSERT INTO ~t1 (~id) VALUES (3);INSERT INTO ~t1 (~id) VALUES (4);UPDATE ~t1 SET ~id=10 WHERE ~id=1;DELETE FROM ~t1 WHERE ~id=2;'
),
{ discardResult: true } { discardResult: true }
); );
const res = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t1'); const res = await runQueryOnDriver(conn, driver, dmp => dmp.put('SELECT COUNT(*) AS ~cnt FROM ~t1'));
// console.log(res); // console.log(res);
expect(res.rows[0].cnt == 3).toBeTruthy(); expect(res.rows[0].cnt == 3).toBeTruthy();
}) })
); );
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
'Select scope identity - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', notNull: true, autoIncrement: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
const structure = await driver.analyseFull(conn);
const table = structure.tables.find(x => x.pureName == 't1');
let res;
if (driver.dialect.requireStandaloneSelectForScopeIdentity) {
await runCommandOnDriver(conn, driver, dmp => dmp.put("INSERT INTO ~t1 (~val) VALUES ('aaa')"));
res = await runQueryOnDriver(conn, driver, dmp => dmp.selectScopeIdentity(table));
} else {
res = await runQueryOnDriver(conn, driver, dmp => {
dmp.putCmd("INSERT INTO ~t1 (~val) VALUES ('aaa')");
dmp.selectScopeIdentity(table);
});
}
const row = res.rows[0];
const keys = Object.keys(row);
expect(keys.length).toEqual(1);
expect(row[keys[0]] == 1).toBeTruthy();
})
);
}); });

View File

@@ -76,7 +76,7 @@ describe('Schema tests', () => {
}); });
describe('Base analyser test', () => { describe('Base analyser test', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
'Structure without change - %s', 'Structure without change - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await baseStructure(conn, driver); await baseStructure(conn, driver);

View File

@@ -3,11 +3,11 @@ const engines = require('../engines');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const t1Sql = 'CREATE TABLE ~t1 (~id int not null primary key, ~val1 varchar(50))'; const t1Sql = 'CREATE TABLE ~t1 (~id int not null primary key, ~val1 varchar(50))';
const ix1Sql = 'CREATE index ix1 ON t1(val1, id)'; const ix1Sql = 'CREATE index ~ix1 ON ~t1(~val1, ~id)';
const t2Sql = engine => const t2Sql = engine =>
`CREATE TABLE t2 (id int not null primary key, val2 varchar(50) ${engine.skipUnique ? '' : 'unique'})`; `CREATE TABLE ~t2 (~id int not null primary key, ~val2 varchar(50) ${engine.skipUnique ? '' : 'unique'})`;
const t3Sql = 'CREATE TABLE t3 (id int not null primary key, valfk int, foreign key (valfk) references t2(id))'; const t3Sql = 'CREATE TABLE ~t3 (~id int not null primary key, ~valfk int, foreign key (~valfk) references ~t2(~id))';
const t4Sql = 'CREATE TABLE t4 (id int not null primary key, valdef int not null default 12)'; const t4Sql = 'CREATE TABLE ~t4 (~id int not null primary key, ~valdef int default 12 not null)';
// const fkSql = 'ALTER TABLE t3 ADD FOREIGN KEY (valfk) REFERENCES t2(id)' // const fkSql = 'ALTER TABLE t3 ADD FOREIGN KEY (valfk) REFERENCES t2(id)'
const txMatch = (engine, tname, vcolname, nextcol, defaultValue) => const txMatch = (engine, tname, vcolname, nextcol, defaultValue) =>
@@ -73,7 +73,7 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Table add - incremental analysis - %s', 'Table add - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
expect(structure1.tables.length).toEqual(1); expect(structure1.tables.length).toEqual(1);
@@ -92,13 +92,13 @@ describe('Table analyse', () => {
'Table remove - incremental analysis - %s', 'Table remove - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await driver.query(conn, t2Sql(engine)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
expect(structure1.tables.length).toEqual(2); expect(structure1.tables.length).toEqual(2);
expect(structure1.tables.find(x => x.pureName == 't1')).toEqual(t1Match(engine)); expect(structure1.tables.find(x => x.pureName == 't1')).toEqual(t1Match(engine));
expect(structure1.tables.find(x => x.pureName == 't2')).toEqual(t2Match(engine)); expect(structure1.tables.find(x => x.pureName == 't2')).toEqual(t2Match(engine));
await driver.query(conn, 'DROP TABLE t2'); await runCommandOnDriver(conn, driver, dmp => dmp.put('DROP TABLE ~t2'));
const structure2 = await driver.analyseIncremental(conn, structure1); const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2.tables.length).toEqual(1); expect(structure2.tables.length).toEqual(1);
@@ -110,14 +110,13 @@ describe('Table analyse', () => {
'Table change - incremental analysis - %s', 'Table change - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await driver.query(conn, t2Sql(engine)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn); const structure1 = await driver.analyseFull(conn);
if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100)); if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100));
await driver.query( await runCommandOnDriver(conn, driver, dmp =>
conn, dmp.put(`ALTER TABLE ~t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} ~nextcol varchar(50)`)
`ALTER TABLE t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} nextcol varchar(50)`
); );
const structure2 = await driver.analyseIncremental(conn, structure1); const structure2 = await driver.analyseIncremental(conn, structure1);
@@ -133,7 +132,7 @@ describe('Table analyse', () => {
'Index - full analysis - %s', 'Index - full analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await driver.query(conn, ix1Sql); await runCommandOnDriver(conn, driver, dmp => dmp.put(ix1Sql));
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
const t1 = structure.tables.find(x => x.pureName == 't1'); const t1 = structure.tables.find(x => x.pureName == 't1');
@@ -147,7 +146,7 @@ describe('Table analyse', () => {
test.each(engines.filter(x => !x.skipUnique).map(engine => [engine.label, engine]))( test.each(engines.filter(x => !x.skipUnique).map(engine => [engine.label, engine]))(
'Unique - full analysis - %s', 'Unique - full analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
const t2 = structure.tables.find(x => x.pureName == 't2'); const t2 = structure.tables.find(x => x.pureName == 't2');
@@ -161,8 +160,8 @@ describe('Table analyse', () => {
test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))( test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))(
'Foreign key - full analysis - %s', 'Foreign key - full analysis - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine)); await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
await driver.query(conn, t3Sql); await runCommandOnDriver(conn, driver, dmp => dmp.put(t3Sql));
// await driver.query(conn, fkSql); // await driver.query(conn, fkSql);
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
@@ -181,7 +180,7 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Table structure - default value - %s', 'Table structure - default value - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t4Sql); await runCommandOnDriver(conn, driver, dmp => dmp.put(t4Sql));
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);

View File

@@ -2,7 +2,7 @@ const _ = require('lodash');
const fp = require('lodash/fp'); const fp = require('lodash/fp');
const engines = require('../engines'); const engines = require('../engines');
const { testWrapper } = require('../tools'); const { testWrapper } = require('../tools');
const { extendDatabaseInfo } = require('dbgate-tools'); const { extendDatabaseInfo, runCommandOnDriver } = require('dbgate-tools');
function createExpector(value) { function createExpector(value) {
return _.cloneDeepWith(value, x => { return _.cloneDeepWith(value, x => {
@@ -25,7 +25,7 @@ function checkTableStructure2(t1, t2) {
} }
async function testTableCreate(conn, driver, table) { async function testTableCreate(conn, driver, table) {
await driver.query(conn, `create table t0 (id int not null primary key)`); await runCommandOnDriver(conn, driver, dmp => dmp.put('create table ~t0 (~id int not null primary key)'));
const dmp = driver.createDumper(); const dmp = driver.createDumper();
const table1 = { const table1 = {

View File

@@ -71,8 +71,8 @@ services:
# restart: on-failure # restart: on-failure
oracle: oracle:
image: container-registry.oracle.com/database/express:21.3.0-xe image: gvenzl/oracle-xe:21-slim
environment: environment:
ORACLE_PWD: Pwd2020Db ORACLE_PASSWORD: Pwd2020Db
ports: ports:
- 15006:1521 - 15006:1521

View File

@@ -1,9 +1,9 @@
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',
create2: 'CREATE VIEW obj2 AS SELECT id FROM t2', create2: 'CREATE VIEW ~obj2 AS SELECT ~id FROM ~t2',
drop1: 'DROP VIEW obj1', drop1: 'DROP VIEW ~obj1',
drop2: 'DROP VIEW obj2', drop2: 'DROP VIEW ~obj2',
}; };
const matviews = { const matviews = {
type: 'matviews', type: 'matviews',
@@ -414,8 +414,27 @@ end;$$`,
server: 'localhost', server: 'localhost',
port: 15006, port: 15006,
}, },
skipOnCI: true, skipOnCI: false,
dbSnapshotBySeconds: true, dbSnapshotBySeconds: true,
setNullDefaultInsteadOfDrop: true,
skipIncrementalAnalysis: true,
objects: [
views,
{
type: 'procedures',
create1: 'CREATE PROCEDURE ~obj1 AS BEGIN SELECT ~id FROM ~t1 END',
create2: 'CREATE PROCEDURE ~obj2 AS BEGIN SELECT ~id FROM ~t2 END',
drop1: 'DROP PROCEDURE ~obj1',
drop2: 'DROP PROCEDURE ~obj2',
},
{
type: 'functions',
create1: 'CREATE FUNCTION ~obj1 RETURN NUMBER IS v_count NUMBER; \n BEGIN SELECT COUNT(*) INTO v_count FROM ~t1;\n RETURN v_count;\n END ~obj1',
create2: 'CREATE FUNCTION ~obj2 RETURN NUMBER IS v_count NUMBER; \n BEGIN SELECT COUNT(*) INTO v_count FROM ~t2;\n RETURN v_count;\n END ~obj2',
drop1: 'DROP FUNCTION ~obj1',
drop2: 'DROP FUNCTION ~obj2',
},
],
}, },
]; ];

View File

@@ -66,7 +66,7 @@ class DuplicatorItemHolder {
this.autoColumn = this.table.columns.find(x => x.autoIncrement)?.columnName; this.autoColumn = this.table.columns.find(x => x.autoIncrement)?.columnName;
if ( if (
this.table.primaryKey?.columns?.length != 1 || this.table.primaryKey?.columns?.length != 1 ||
this.table.primaryKey?.columns?.[0].columnName != this.autoColumn this.table.primaryKey?.columns?.[0]?.columnName != this.autoColumn
) { ) {
this.autoColumn = null; this.autoColumn = null;
} }
@@ -140,6 +140,9 @@ class DuplicatorItemHolder {
weakref.foreignKey.columns[0].columnName weakref.foreignKey.columns[0].columnName
); );
}); });
if (this.duplicator.driver.dialect.requireFromDual) {
dmp.put(' ^from ^dual');
}
}); });
const qrow = qres.rows[0]; const qrow = qres.rows[0];
return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName); return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName);
@@ -194,6 +197,7 @@ class DuplicatorItemHolder {
res = await runQueryOnDriver(pool, driver, dmp => dmp.selectScopeIdentity(this.table)); res = await runQueryOnDriver(pool, driver, dmp => dmp.selectScopeIdentity(this.table));
} }
// console.log('IDRES', JSON.stringify(res)); // console.log('IDRES', JSON.stringify(res));
// console.log('*********** ENTRIES OF', res?.rows?.[0]);
const resId = Object.entries(res?.rows?.[0])?.[0]?.[1]; const resId = Object.entries(res?.rows?.[0])?.[0]?.[1];
if (resId != null) { if (resId != null) {
this.idMap[chunk[this.autoColumn]] = resId; this.idMap[chunk[this.autoColumn]] = resId;

View File

@@ -1,4 +1,5 @@
import _compact from 'lodash/compact'; import _compact from 'lodash/compact';
import _isString from 'lodash/isString';
import { SqlDumper } from './SqlDumper'; import { SqlDumper } from './SqlDumper';
import { splitQuery } from 'dbgate-query-splitter'; import { splitQuery } from 'dbgate-query-splitter';
import { dumpSqlSelect } from 'dbgate-sqltree'; import { dumpSqlSelect } from 'dbgate-sqltree';
@@ -26,9 +27,17 @@ const dialect = {
defaultSchemaName: null, defaultSchemaName: null,
}; };
export async function runCommandOnDriver(pool, driver: EngineDriver, cmd: (dmp: SqlDumper) => void): Promise<void> { export async function runCommandOnDriver(
pool,
driver: EngineDriver,
cmd: (dmp: SqlDumper) => void | string
): Promise<void> {
const dmp = driver.createDumper(); const dmp = driver.createDumper();
cmd(dmp as any); if (_isString(cmd)) {
dmp.put(cmd);
} else {
cmd(dmp as any);
}
// console.log('CMD:', dmp.s); // console.log('CMD:', dmp.s);
await driver.query(pool, dmp.s, { discardResult: true }); await driver.query(pool, dmp.s, { discardResult: true });
} }
@@ -39,11 +48,21 @@ export async function runQueryOnDriver(
cmd: (dmp: SqlDumper) => void cmd: (dmp: SqlDumper) => void
): Promise<QueryResult> { ): Promise<QueryResult> {
const dmp = driver.createDumper(); const dmp = driver.createDumper();
cmd(dmp as any); if (_isString(cmd)) {
dmp.put(cmd);
} else {
cmd(dmp as any);
}
// console.log('QUERY:', dmp.s); // console.log('QUERY:', dmp.s);
return await driver.query(pool, dmp.s); return await driver.query(pool, dmp.s);
} }
export function formatQueryWithoutParams(driver: EngineDriver, sql: string) {
const dmp = driver.createDumper();
dmp.put(sql);
return dmp.s;
}
export const driverBase = { export const driverBase = {
analyserClass: null, analyserClass: null,
dumperClass: SqlDumper, dumperClass: SqlDumper,

View File

@@ -132,7 +132,9 @@ class MsSqlDumper extends SqlDumper {
} else { } else {
this.dropDefault(oldcol); this.dropDefault(oldcol);
if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName); if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName);
this.fillNewNotNullDefaults(newcol); if (!oldcol.notNull) {
this.fillNewNotNullDefaults(newcol);
}
this.put('^alter ^table %f ^alter ^column %i ', oldcol, oldcol.columnName, newcol.columnName); this.put('^alter ^table %f ^alter ^column %i ', oldcol, oldcol.columnName, newcol.columnName);
this.columnDefinition(newcol, { includeDefault: false }); this.columnDefinition(newcol, { includeDefault: false });
this.endCommand(); this.endCommand();

View File

@@ -32,10 +32,12 @@ class Dumper extends SqlDumper {
} }
changeColumn(oldcol, newcol, constraints) { changeColumn(oldcol, newcol, constraints) {
this.fillNewNotNullDefaults({ if (!oldcol.notNull) {
...newcol, this.fillNewNotNullDefaults({
columnName: oldcol.columnName, ...newcol,
}); columnName: oldcol.columnName,
});
}
this.put('^alter ^table %f ^change ^column %i %i ', oldcol, oldcol.columnName, newcol.columnName); this.put('^alter ^table %f ^change ^column %i %i ', oldcol, oldcol.columnName, newcol.columnName);
this.columnDefinition(newcol); this.columnDefinition(newcol);
this.inlineConstraints(constraints); this.inlineConstraints(constraints);

View File

@@ -23,7 +23,7 @@ function getColumnInfo(
columnName: column_name, columnName: column_name,
dataType: fullDataType, dataType: fullDataType,
notNull: is_nullable == 'N', notNull: is_nullable == 'N',
defaultValue: autoIncrement ? undefined : default_value, defaultValue: autoIncrement ? undefined : default_value?.trim(),
autoIncrement, autoIncrement,
}; };
} }
@@ -40,7 +40,7 @@ class Analyser extends DatabaseAnalyser {
} }
async _computeSingleObjectId() { async _computeSingleObjectId() {
const { typeField, pureName } = this.singleObjectFilter; const { typeField, pureName } = this.singleObjectFilter;
this.singleObjectId = `${typeField}:${pureName}`; this.singleObjectId = `${typeField}:${pureName}`;
} }
@@ -114,7 +114,8 @@ class Analyser extends DatabaseAnalyser {
indexes.rows.filter( indexes.rows.filter(
idx => idx =>
idx.tableName == newTable.pureName && idx.tableName == newTable.pureName &&
!uniqueNames.rows.find(x => x.constraintName == idx.constraintName) !uniqueNames.rows.find(x => x.constraintName == idx.constraintName) &&
!idx.constraintName.startsWith('SYS_C')
), ),
'constraintName' 'constraintName'
).map(idx => ({ ).map(idx => ({
@@ -141,6 +142,9 @@ class Analyser extends DatabaseAnalyser {
..._.pick(col, ['columnName']), ..._.pick(col, ['columnName']),
})), })),
})), })),
identitySequenceName: (columnsGrouped[columnGroup(table)] || [])
.find(x => x?.default_value?.endsWith('.nextval'))
?.default_value?.match(/\"([^"]+)\"\.nextval/)?.[1],
}; };
}), }),
views: views.rows.map(view => ({ views: views.rows.map(view => ({
@@ -167,14 +171,14 @@ class Analyser extends DatabaseAnalyser {
objectId: `procedures:${proc.pure_name}`, objectId: `procedures:${proc.pure_name}`,
pureName: proc.pure_name, pureName: proc.pure_name,
// schemaName: proc.schema_name, // schemaName: proc.schema_name,
createSql: `CREATE PROCEDURE "${proc.pure_name}"() LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`, createSql: `SET SQLTERMINATOR "/"\nCREATE ${proc.source_code}\n/\n`,
contentHash: proc.hash_code, contentHash: proc.hash_code,
})), })),
functions: routines.rows functions: routines.rows
.filter(x => x.object_type == 'FUNCTION') .filter(x => x.object_type == 'FUNCTION')
.map(func => ({ .map(func => ({
objectId: `functions:${func.pure_name}`, objectId: `functions:${func.pure_name}`,
createSql: `CREATE FUNCTION "${func.pure_name}"() RETURNS ${func.data_type} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`, createSql: `SET SQLTERMINATOR "/"\nCREATE ${func.source_code}\n/\n`,
pureName: func.pure_name, pureName: func.pure_name,
// schemaName: func.schema_name, // schemaName: func.schema_name,
contentHash: func.hash_code, contentHash: func.hash_code,

View File

@@ -1,41 +1,10 @@
module.exports = ` module.exports = `
select SELECT
routine_name as "pure_name", name as "pure_name",
-- routine_schema as "schema_name", type as "object_type",
routine_definition as "definition", LISTAGG(text, '') WITHIN GROUP (ORDER BY line) AS "source_code",
ora_hash(routine_definition) as "hash_code", ora_hash(LISTAGG(text, '') WITHIN GROUP (ORDER BY line)) AS "hash_code"
routine_type as "object_type", FROM all_source
'fixme_data_type' as "data_type", WHERE type in ('FUNCTION', 'PROCEDURE') AND OWNER = '$owner'
'fixme_external_language' as "language" GROUP BY name, type
from (select
sys_context('userenv', 'DB_NAME') routine_catalog,
sys_context('userenv', 'DB_NAME') specific_catalog,
ap.owner specific_schema,
ap.owner routine_schema,
decode( ap.procedure_name, null, ap.object_name || ap.procedure_name, ap.procedure_name ) specific_name,
decode( ap.procedure_name, null, ap.object_name || ap.procedure_name, ap.procedure_name ) routine_name,
ao.object_type routine_type,
decode(impltypeowner, null, to_char(null), SYS_CONTEXT('userenv', 'DB_NAME')) type_udt_catalog,
--to_clob(get_proc_text(ap.owner, ap.object_name, ao.object_type, 32767)) routine_body,
'fixme_routine_body.' || ap.owner || '.' || decode( ap.procedure_name, null, ap.object_name || ap.procedure_name, ap.procedure_name ) routine_body,
--to_clob(get_proc_text(ap.owner, ap.object_name, ao.object_type, 4000)) routine_definition,
'fixme_routine_definition.' || ap.owner || '.' || decode( ap.procedure_name, null, ap.object_name || ap.procedure_name, ap.procedure_name ) routine_definition,
sys_context('userenv', 'DB_NAME') character_set_catalog,
'SYS' character_set_schema,
sys_context('userenv', 'DB_NAME') collation_catalog,
'SYS' collation_schema,
deterministic is_deterministic,
pipelined is_pipelined ,
aggregate is_aggregate,
authid is_definer
from
all_procedures ap,
all_objects ao
where
ap.owner = '$owner' and
ap.owner = ao.owner and
ap.object_name = ao.object_name and
ao.object_type in ('PACKAGE', 'PROCEDURE', 'FUNCTION')
and ao.object_name =OBJECT_ID_CONDITION
) routines
`; `;

View File

@@ -4,7 +4,7 @@ from (select
view_name as "pure_name", view_name as "pure_name",
text as "create_sql" text as "create_sql"
from all_views av from all_views av
where owner = 'C##test' and text is not null where owner = '$owner' and text is not null
) avv ) avv
where 'views:' || "pure_name" is not null where 'views:' || "pure_name" is not null
`; `;

View File

@@ -60,9 +60,9 @@ class Dumper extends SqlDumper {
// this.putCmd('^alter ^table %f ^rename ^to %i', obj, newname); // this.putCmd('^alter ^table %f ^rename ^to %i', obj, newname);
// } // }
// renameColumn(column, newcol) { renameColumn(column, newcol) {
// this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol); this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol);
// } }
// dropTable(obj, options = {}) { // dropTable(obj, options = {}) {
// this.put('^drop ^table'); // this.put('^drop ^table');
@@ -87,30 +87,48 @@ class Dumper extends SqlDumper {
// super.columnDefinition(col, options); // super.columnDefinition(col, options);
// } // }
// changeColumn(oldcol, newcol, constraints) { changeColumn(oldcol, newcol, constraints) {
// if (oldcol.columnName != newcol.columnName) { if (oldcol.columnName != newcol.columnName) {
// this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', oldcol, oldcol.columnName, newcol.columnName); this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', oldcol, oldcol.columnName, newcol.columnName);
// } }
// if (!testEqualTypes(oldcol, newcol)) {
// this.putCmd('^alter ^table %f ^alter ^column %i ^type %s', oldcol, newcol.columnName, newcol.dataType); if (!oldcol.notNull) {
// } this.fillNewNotNullDefaults(newcol);
// if (oldcol.notNull != newcol.notNull) { }
// if (newcol.notNull) this.putCmd('^alter ^table %f ^alter ^column %i ^set ^not ^null', newcol, newcol.columnName);
// else this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^not ^null', newcol, newcol.columnName); if (!testEqualTypes(oldcol, newcol) || oldcol.notNull != newcol.notNull) {
// } this.putCmd(
// if (oldcol.defaultValue != newcol.defaultValue) { '^alter ^table %f ^modify (%i %s %k)',
// if (newcol.defaultValue == null) { newcol,
// this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^default', newcol, newcol.columnName); newcol.columnName,
// } else { newcol.dataType,
// this.putCmd( newcol.notNull ? 'not null' : 'null'
// '^alter ^table %f ^alter ^column %i ^set ^default %s', );
// newcol, }
// newcol.columnName,
// newcol.defaultValue if (oldcol.defaultValue != newcol.defaultValue) {
// ); if (newcol.defaultValue?.trim()) {
// } this.putCmd('^alter ^table %f ^modify (%i ^default %s)', newcol, newcol.columnName, newcol.defaultValue);
// } } else {
// } this.putCmd('^alter ^table %f ^modify (%i ^default ^null)', newcol, newcol.columnName);
}
}
}
selectScopeIdentity(table) {
const sequence = table.identitySequenceName;
if (sequence) {
this.put('^select %i.CURRVAL FROM DUAL', sequence);
}
}
renameTable(obj, newname) {
this.putCmd('^alter ^table %f ^rename ^to %i', obj, newname);
}
renameSqlObject(obj, newname) {
this.putCmd('^rename %f ^to %i', obj, newname);
}
// putValue(value) { // putValue(value) {
// if (value === true) this.putRaw('true'); // if (value === true) this.putRaw('true');

View File

@@ -13,7 +13,7 @@ const dialect = {
ilike: true, ilike: true,
// stringEscapeChar: '\\', // stringEscapeChar: '\\',
stringEscapeChar: "'", stringEscapeChar: "'",
fallbackDataType: 'varchar', fallbackDataType: 'varchar(250)',
anonymousPrimaryKey: false, anonymousPrimaryKey: false,
enableConstraintsPerTable: true, enableConstraintsPerTable: true,
dropColumnDependencies: ['dependencies'], dropColumnDependencies: ['dependencies'],
@@ -22,6 +22,7 @@ const dialect = {
}, },
userDatabaseNamePrefix: 'C##', userDatabaseNamePrefix: 'C##',
upperCaseAllDbObjectNames: true, upperCaseAllDbObjectNames: true,
requireStandaloneSelectForScopeIdentity: true,
createColumn: true, createColumn: true,
dropColumn: true, dropColumn: true,
@@ -36,6 +37,7 @@ const dialect = {
dropUnique: true, dropUnique: true,
createCheck: true, createCheck: true,
dropCheck: true, dropCheck: true,
renameSqlObject: true,
dropReferencesWhenDropTable: true, dropReferencesWhenDropTable: true,
requireFromDual: true, requireFromDual: true,

View File

@@ -89,7 +89,9 @@ class Dumper extends SqlDumper {
} }
} }
if (oldcol.notNull != newcol.notNull) { if (oldcol.notNull != newcol.notNull) {
this.fillNewNotNullDefaults(newcol); if (!oldcol.notNull) {
this.fillNewNotNullDefaults(newcol);
}
if (newcol.notNull) this.putCmd('^alter ^table %f ^alter ^column %i ^set ^not ^null', newcol, newcol.columnName); if (newcol.notNull) this.putCmd('^alter ^table %f ^alter ^column %i ^set ^not ^null', newcol, newcol.columnName);
else this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^not ^null', newcol, newcol.columnName); else this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^not ^null', newcol, newcol.columnName);
} }