mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 23:45:59 +00:00
Merge branch 'master' of https://github.com/dbgate/dbgate
This commit is contained in:
41
.github/workflows/e2e-tests.yaml
vendored
41
.github/workflows/e2e-tests.yaml
vendored
@@ -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
|
||||
24
.github/workflows/run-tests.yaml
vendored
24
.github/workflows/run-tests.yaml
vendored
@@ -12,16 +12,27 @@ jobs:
|
||||
container: node:18
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- name: Install dependencies for cypress
|
||||
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 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
|
||||
run: |
|
||||
cd integration-tests
|
||||
@@ -84,5 +95,10 @@ jobs:
|
||||
env:
|
||||
CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db
|
||||
|
||||
oracle:
|
||||
image: gvenzl/oracle-xe:21-slim
|
||||
env:
|
||||
ORACLE_PASSWORD: Pwd2020Db
|
||||
|
||||
# cockroachdb:
|
||||
# image: cockroachdb/cockroach
|
||||
|
||||
@@ -3,9 +3,15 @@ const _ = require('lodash');
|
||||
const fp = require('lodash/fp');
|
||||
const { testWrapper } = require('../tools');
|
||||
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) {
|
||||
return _.flatten(
|
||||
@@ -16,13 +22,14 @@ function flatSource(engineCond = x => !x.skipReferences) {
|
||||
}
|
||||
|
||||
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,
|
||||
`create table t2 (
|
||||
id int not null primary key,
|
||||
t1_id int null references t1(id)
|
||||
driver,
|
||||
`create table ~t2 (
|
||||
~id int not null primary key,
|
||||
~t1_id int null references ~t1(~id)
|
||||
)`
|
||||
);
|
||||
|
||||
@@ -63,7 +70,7 @@ describe('Alter database', () => {
|
||||
db => {
|
||||
_.remove(db[type], x => x.pureName == 'obj1');
|
||||
},
|
||||
object.create1
|
||||
formatQueryWithoutParams(driver, object.create1)
|
||||
);
|
||||
expect(db[type].length).toEqual(0);
|
||||
})
|
||||
@@ -72,9 +79,9 @@ describe('Alter database', () => {
|
||||
test.each(flatSource(x => x.supportRenameSqlObject))(
|
||||
'Rename object - %s - %s',
|
||||
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));
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ const fp = require('lodash/fp');
|
||||
const { testWrapper } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
const crypto = require('crypto');
|
||||
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
|
||||
const {
|
||||
getAlterTableScript,
|
||||
extendDatabaseInfo,
|
||||
generateDbPairingId,
|
||||
formatQueryWithoutParams,
|
||||
} = require('dbgate-tools');
|
||||
|
||||
function pickImportantTableInfo(engine, table) {
|
||||
const props = ['columnName', 'defaultValue'];
|
||||
@@ -15,7 +20,10 @@ function pickImportantTableInfo(engine, table) {
|
||||
columns: table.columns
|
||||
.filter(x => x.columnName != 'rowid')
|
||||
.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) {
|
||||
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(
|
||||
conn,
|
||||
`create table t1 (
|
||||
col_pk int not null primary key,
|
||||
col_std int,
|
||||
col_def int 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'}
|
||||
formatQueryWithoutParams(
|
||||
driver,
|
||||
`create table ~t1 (
|
||||
~col_pk int not null primary key,
|
||||
~col_std int,
|
||||
~col_def int 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'}
|
||||
)`
|
||||
)
|
||||
);
|
||||
|
||||
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) {
|
||||
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');
|
||||
@@ -175,5 +192,4 @@ describe('Alter table', () => {
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ const engines = require('../engines');
|
||||
const stream = require('stream');
|
||||
const { testWrapper } = require('../tools');
|
||||
const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator');
|
||||
const { runCommandOnDriver } = require('dbgate-tools');
|
||||
const { runCommandOnDriver, runQueryOnDriver } = require('dbgate-tools');
|
||||
|
||||
describe('Data duplicator', () => {
|
||||
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');
|
||||
|
||||
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');
|
||||
})
|
||||
);
|
||||
@@ -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');
|
||||
|
||||
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');
|
||||
|
||||
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');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ const tableWriter = require('dbgate-api/src/shell/tableWriter');
|
||||
const copyStream = require('dbgate-api/src/shell/copyStream');
|
||||
const importDatabase = require('dbgate-api/src/shell/importDatabase');
|
||||
const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader');
|
||||
const { runQueryOnDriver } = require('dbgate-tools');
|
||||
|
||||
function createImportStream() {
|
||||
const pass = new stream.PassThrough({
|
||||
@@ -37,7 +38,7 @@ describe('DB Import', () => {
|
||||
});
|
||||
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');
|
||||
})
|
||||
);
|
||||
@@ -65,10 +66,10 @@ describe('DB Import', () => {
|
||||
});
|
||||
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');
|
||||
|
||||
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');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ const { testWrapper, testWrapperPrepareOnly } = require('../tools');
|
||||
const _ = require('lodash');
|
||||
const engines = require('../engines');
|
||||
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 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) {
|
||||
const { testEmptyLastScript, finalCheckAgainstModel, markDeleted, allowDropStatements } = options || {};
|
||||
let index = 0;
|
||||
@@ -83,13 +106,13 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
||||
|
||||
for (const loadedDbModel of dbModelsYaml) {
|
||||
if (_.isString(loadedDbModel)) {
|
||||
await driver.script(conn, loadedDbModel);
|
||||
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel));
|
||||
} else {
|
||||
const { sql, isEmpty } = await generateDeploySql({
|
||||
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
||||
connection: conn.isPreparedOnly ? conn : undefined,
|
||||
driver,
|
||||
loadedDbModel,
|
||||
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
|
||||
dbdiffOptionsExtra,
|
||||
});
|
||||
console.debug('Generated deploy script:', sql);
|
||||
@@ -106,7 +129,7 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
||||
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
||||
connection: conn.isPreparedOnly ? conn : undefined,
|
||||
driver,
|
||||
loadedDbModel,
|
||||
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
|
||||
dbdiffOptionsExtra,
|
||||
});
|
||||
}
|
||||
@@ -117,7 +140,12 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
||||
const dbhan = conn.isPreparedOnly ? await connectUtility(driver, conn, 'read') : conn;
|
||||
const structure = await driver.analyseFull(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', () => {
|
||||
@@ -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');
|
||||
})
|
||||
);
|
||||
@@ -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');
|
||||
})
|
||||
);
|
||||
@@ -414,8 +442,8 @@ describe('Deploy database', () => {
|
||||
],
|
||||
]);
|
||||
|
||||
await driver.query(conn, `insert into t1 (id) values (1)`);
|
||||
const res = await driver.query(conn, ` select val from t1 where id = 1`);
|
||||
await runQueryOnDriver(conn, driver, `insert into ~t1 (~id) values (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');
|
||||
})
|
||||
);
|
||||
@@ -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',
|
||||
@@ -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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
})
|
||||
);
|
||||
@@ -525,17 +553,17 @@ describe('Deploy database', () => {
|
||||
|
||||
const V1 = {
|
||||
name: 'v1.view.sql',
|
||||
text: 'create view v1 as select * from t1',
|
||||
text: 'create view ~v1 as select * from ~t1',
|
||||
};
|
||||
|
||||
const V1_VARIANT2 = {
|
||||
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 = {
|
||||
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]))(
|
||||
@@ -682,15 +710,15 @@ describe('Deploy database', () => {
|
||||
[
|
||||
{
|
||||
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();
|
||||
|
||||
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();
|
||||
})
|
||||
);
|
||||
@@ -702,48 +730,53 @@ describe('Deploy database', () => {
|
||||
[
|
||||
{
|
||||
name: 't1.uninstall.sql',
|
||||
text: 'drop table t1',
|
||||
text: 'drop table ~t1',
|
||||
},
|
||||
{
|
||||
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',
|
||||
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',
|
||||
text: 'drop table t1',
|
||||
text: 'drop table ~t1',
|
||||
},
|
||||
{
|
||||
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',
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
const res5 = await driver.query(
|
||||
const res5 = await runQueryOnDriver(
|
||||
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();
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const { testWrapper } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
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() {
|
||||
return _.flatten(
|
||||
@@ -34,9 +35,9 @@ describe('Object analyse', () => {
|
||||
test.each(flatSource())(
|
||||
'Full analysis - %s - %s',
|
||||
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);
|
||||
|
||||
expect(structure[type].length).toEqual(1);
|
||||
@@ -47,11 +48,11 @@ describe('Object analyse', () => {
|
||||
test.each(flatSource())(
|
||||
'Incremental analysis - add - %s - %s',
|
||||
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);
|
||||
await driver.query(conn, object.create1, { discardResult: true });
|
||||
await runCommandOnDriver(conn, driver, object.create1);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2[type].length).toEqual(2);
|
||||
@@ -62,12 +63,12 @@ describe('Object analyse', () => {
|
||||
test.each(flatSource())(
|
||||
'Incremental analysis - drop - %s - %s',
|
||||
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 driver.query(conn, object.create2, { discardResult: true });
|
||||
await runCommandOnDriver(conn, driver, object.create1);
|
||||
await runCommandOnDriver(conn, driver, object.create2);
|
||||
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);
|
||||
|
||||
expect(structure2[type].length).toEqual(1);
|
||||
@@ -78,11 +79,11 @@ describe('Object analyse', () => {
|
||||
test.each(flatSource())(
|
||||
'Create SQL - add - %s - %s',
|
||||
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);
|
||||
await driver.query(conn, object.drop1, { discardResult: true });
|
||||
await runCommandOnDriver(conn, driver, object.drop1);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
expect(structure2[type].length).toEqual(0);
|
||||
|
||||
@@ -98,10 +99,10 @@ describe('Object analyse', () => {
|
||||
test.each(flatSourceParameters())(
|
||||
'Test parameters simple analyse - %s - %s',
|
||||
testWrapper(async (conn, driver, testName, parameter, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
|
||||
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 parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
|
||||
@@ -116,15 +117,15 @@ describe('Object analyse', () => {
|
||||
test.each(flatSourceParameters())(
|
||||
'Test parameters create SQL - %s - %s',
|
||||
testWrapper(async (conn, driver, testName, parameter, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
|
||||
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);
|
||||
await driver.query(conn, parameter.drop, { discardResult: true });
|
||||
await runCommandOnDriver(conn, driver, parameter.drop);
|
||||
|
||||
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 parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const engines = require('../engines');
|
||||
const { splitQuery } = require('dbgate-query-splitter');
|
||||
const { testWrapper } = require('../tools');
|
||||
const { runQueryOnDriver, runCommandOnDriver, formatQueryWithoutParams } = require('dbgate-tools');
|
||||
|
||||
const initSql = [
|
||||
'CREATE TABLE t1 (id int primary key)',
|
||||
'INSERT INTO t1 (id) VALUES (1)',
|
||||
'INSERT INTO t1 (id) VALUES (2)',
|
||||
'CREATE TABLE ~t1 (~id int primary key)',
|
||||
'INSERT INTO ~t1 (~id) VALUES (1)',
|
||||
'INSERT INTO ~t1 (~id) VALUES (2)',
|
||||
];
|
||||
|
||||
expect.extend({
|
||||
@@ -51,7 +52,7 @@ class StreamHandler {
|
||||
function executeStreamItem(driver, conn, sql) {
|
||||
return new Promise(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]))(
|
||||
'Simple query - %s',
|
||||
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.objectContaining({
|
||||
columnName: 'id',
|
||||
@@ -91,8 +94,11 @@ describe('Query', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Simple stream query - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
|
||||
const results = await executeStream(driver, conn, 'SELECT id FROM t1 ORDER BY id');
|
||||
for (const sql of initSql) {
|
||||
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);
|
||||
const res = results[0];
|
||||
|
||||
@@ -104,11 +110,14 @@ describe('Query', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'More queries - %s',
|
||||
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(
|
||||
driver,
|
||||
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);
|
||||
|
||||
@@ -128,7 +137,7 @@ describe('Query', () => {
|
||||
const results = await executeStream(
|
||||
driver,
|
||||
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);
|
||||
|
||||
@@ -144,7 +153,7 @@ describe('Query', () => {
|
||||
const results = await executeStream(
|
||||
driver,
|
||||
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);
|
||||
})
|
||||
@@ -153,16 +162,57 @@ describe('Query', () => {
|
||||
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
|
||||
'Save data query - %s',
|
||||
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(
|
||||
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 }
|
||||
);
|
||||
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);
|
||||
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();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('Schema tests', () => {
|
||||
});
|
||||
|
||||
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',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
|
||||
@@ -3,11 +3,11 @@ const engines = require('../engines');
|
||||
const { testWrapper } = require('../tools');
|
||||
|
||||
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 =>
|
||||
`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 t4Sql = 'CREATE TABLE t4 (id int not null primary key, valdef int not null default 12)';
|
||||
`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 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 txMatch = (engine, tname, vcolname, nextcol, defaultValue) =>
|
||||
@@ -73,7 +73,7 @@ describe('Table analyse', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table add - incremental analysis - %s',
|
||||
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);
|
||||
expect(structure1.tables.length).toEqual(1);
|
||||
@@ -92,13 +92,13 @@ describe('Table analyse', () => {
|
||||
'Table remove - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
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);
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
expect(structure1.tables.find(x => x.pureName == 't1')).toEqual(t1Match(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);
|
||||
|
||||
expect(structure2.tables.length).toEqual(1);
|
||||
@@ -110,14 +110,13 @@ describe('Table analyse', () => {
|
||||
'Table change - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
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);
|
||||
|
||||
if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
|
||||
await driver.query(
|
||||
conn,
|
||||
`ALTER TABLE t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} nextcol varchar(50)`
|
||||
await runCommandOnDriver(conn, driver, dmp =>
|
||||
dmp.put(`ALTER TABLE ~t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} ~nextcol varchar(50)`)
|
||||
);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
@@ -133,7 +132,7 @@ describe('Table analyse', () => {
|
||||
'Index - full analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
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 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]))(
|
||||
'Unique - full analysis - %s',
|
||||
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 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]))(
|
||||
'Foreign key - full analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t2Sql(engine));
|
||||
await driver.query(conn, t3Sql);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t3Sql));
|
||||
// await driver.query(conn, fkSql);
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
@@ -181,7 +180,7 @@ describe('Table analyse', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table structure - default value - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t4Sql);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.put(t4Sql));
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const _ = require('lodash');
|
||||
const fp = require('lodash/fp');
|
||||
const engines = require('../engines');
|
||||
const { testWrapper } = require('../tools');
|
||||
const { extendDatabaseInfo } = require('dbgate-tools');
|
||||
const { extendDatabaseInfo, runCommandOnDriver } = require('dbgate-tools');
|
||||
|
||||
function createExpector(value) {
|
||||
return _.cloneDeepWith(value, x => {
|
||||
@@ -25,7 +25,7 @@ function checkTableStructure2(t1, t2) {
|
||||
}
|
||||
|
||||
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 table1 = {
|
||||
|
||||
@@ -71,8 +71,8 @@ services:
|
||||
# restart: on-failure
|
||||
|
||||
oracle:
|
||||
image: container-registry.oracle.com/database/express:21.3.0-xe
|
||||
image: gvenzl/oracle-xe:21-slim
|
||||
environment:
|
||||
ORACLE_PWD: Pwd2020Db
|
||||
ORACLE_PASSWORD: Pwd2020Db
|
||||
ports:
|
||||
- 15006:1521
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const views = {
|
||||
type: 'views',
|
||||
create1: 'CREATE VIEW obj1 AS SELECT id FROM t1',
|
||||
create2: 'CREATE VIEW obj2 AS SELECT id FROM t2',
|
||||
drop1: 'DROP VIEW obj1',
|
||||
drop2: 'DROP VIEW obj2',
|
||||
create1: 'CREATE VIEW ~obj1 AS SELECT ~id FROM ~t1',
|
||||
create2: 'CREATE VIEW ~obj2 AS SELECT ~id FROM ~t2',
|
||||
drop1: 'DROP VIEW ~obj1',
|
||||
drop2: 'DROP VIEW ~obj2',
|
||||
};
|
||||
const matviews = {
|
||||
type: 'matviews',
|
||||
@@ -414,8 +414,27 @@ end;$$`,
|
||||
server: 'localhost',
|
||||
port: 15006,
|
||||
},
|
||||
skipOnCI: true,
|
||||
skipOnCI: false,
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class DuplicatorItemHolder {
|
||||
this.autoColumn = this.table.columns.find(x => x.autoIncrement)?.columnName;
|
||||
if (
|
||||
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;
|
||||
}
|
||||
@@ -140,6 +140,9 @@ class DuplicatorItemHolder {
|
||||
weakref.foreignKey.columns[0].columnName
|
||||
);
|
||||
});
|
||||
if (this.duplicator.driver.dialect.requireFromDual) {
|
||||
dmp.put(' ^from ^dual');
|
||||
}
|
||||
});
|
||||
const qrow = qres.rows[0];
|
||||
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));
|
||||
}
|
||||
// console.log('IDRES', JSON.stringify(res));
|
||||
// console.log('*********** ENTRIES OF', res?.rows?.[0]);
|
||||
const resId = Object.entries(res?.rows?.[0])?.[0]?.[1];
|
||||
if (resId != null) {
|
||||
this.idMap[chunk[this.autoColumn]] = resId;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _compact from 'lodash/compact';
|
||||
import _isString from 'lodash/isString';
|
||||
import { SqlDumper } from './SqlDumper';
|
||||
import { splitQuery } from 'dbgate-query-splitter';
|
||||
import { dumpSqlSelect } from 'dbgate-sqltree';
|
||||
@@ -26,9 +27,17 @@ const dialect = {
|
||||
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();
|
||||
cmd(dmp as any);
|
||||
if (_isString(cmd)) {
|
||||
dmp.put(cmd);
|
||||
} else {
|
||||
cmd(dmp as any);
|
||||
}
|
||||
// console.log('CMD:', dmp.s);
|
||||
await driver.query(pool, dmp.s, { discardResult: true });
|
||||
}
|
||||
@@ -39,11 +48,21 @@ export async function runQueryOnDriver(
|
||||
cmd: (dmp: SqlDumper) => void
|
||||
): Promise<QueryResult> {
|
||||
const dmp = driver.createDumper();
|
||||
cmd(dmp as any);
|
||||
if (_isString(cmd)) {
|
||||
dmp.put(cmd);
|
||||
} else {
|
||||
cmd(dmp as any);
|
||||
}
|
||||
// console.log('QUERY:', 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 = {
|
||||
analyserClass: null,
|
||||
dumperClass: SqlDumper,
|
||||
|
||||
@@ -132,7 +132,9 @@ class MsSqlDumper extends SqlDumper {
|
||||
} else {
|
||||
this.dropDefault(oldcol);
|
||||
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.columnDefinition(newcol, { includeDefault: false });
|
||||
this.endCommand();
|
||||
|
||||
@@ -32,10 +32,12 @@ class Dumper extends SqlDumper {
|
||||
}
|
||||
|
||||
changeColumn(oldcol, newcol, constraints) {
|
||||
this.fillNewNotNullDefaults({
|
||||
...newcol,
|
||||
columnName: oldcol.columnName,
|
||||
});
|
||||
if (!oldcol.notNull) {
|
||||
this.fillNewNotNullDefaults({
|
||||
...newcol,
|
||||
columnName: oldcol.columnName,
|
||||
});
|
||||
}
|
||||
this.put('^alter ^table %f ^change ^column %i %i ', oldcol, oldcol.columnName, newcol.columnName);
|
||||
this.columnDefinition(newcol);
|
||||
this.inlineConstraints(constraints);
|
||||
|
||||
@@ -23,7 +23,7 @@ function getColumnInfo(
|
||||
columnName: column_name,
|
||||
dataType: fullDataType,
|
||||
notNull: is_nullable == 'N',
|
||||
defaultValue: autoIncrement ? undefined : default_value,
|
||||
defaultValue: autoIncrement ? undefined : default_value?.trim(),
|
||||
autoIncrement,
|
||||
};
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class Analyser extends DatabaseAnalyser {
|
||||
}
|
||||
|
||||
async _computeSingleObjectId() {
|
||||
const { typeField, pureName } = this.singleObjectFilter;
|
||||
const { typeField, pureName } = this.singleObjectFilter;
|
||||
this.singleObjectId = `${typeField}:${pureName}`;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ class Analyser extends DatabaseAnalyser {
|
||||
indexes.rows.filter(
|
||||
idx =>
|
||||
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'
|
||||
).map(idx => ({
|
||||
@@ -141,6 +142,9 @@ class Analyser extends DatabaseAnalyser {
|
||||
..._.pick(col, ['columnName']),
|
||||
})),
|
||||
})),
|
||||
identitySequenceName: (columnsGrouped[columnGroup(table)] || [])
|
||||
.find(x => x?.default_value?.endsWith('.nextval'))
|
||||
?.default_value?.match(/\"([^"]+)\"\.nextval/)?.[1],
|
||||
};
|
||||
}),
|
||||
views: views.rows.map(view => ({
|
||||
@@ -167,14 +171,14 @@ class Analyser extends DatabaseAnalyser {
|
||||
objectId: `procedures:${proc.pure_name}`,
|
||||
pureName: proc.pure_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,
|
||||
})),
|
||||
functions: routines.rows
|
||||
.filter(x => x.object_type == 'FUNCTION')
|
||||
.map(func => ({
|
||||
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,
|
||||
// schemaName: func.schema_name,
|
||||
contentHash: func.hash_code,
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
module.exports = `
|
||||
select
|
||||
routine_name as "pure_name",
|
||||
-- routine_schema as "schema_name",
|
||||
routine_definition as "definition",
|
||||
ora_hash(routine_definition) as "hash_code",
|
||||
routine_type as "object_type",
|
||||
'fixme_data_type' as "data_type",
|
||||
'fixme_external_language' as "language"
|
||||
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
|
||||
SELECT
|
||||
name as "pure_name",
|
||||
type as "object_type",
|
||||
LISTAGG(text, '') WITHIN GROUP (ORDER BY line) AS "source_code",
|
||||
ora_hash(LISTAGG(text, '') WITHIN GROUP (ORDER BY line)) AS "hash_code"
|
||||
FROM all_source
|
||||
WHERE type in ('FUNCTION', 'PROCEDURE') AND OWNER = '$owner'
|
||||
GROUP BY name, type
|
||||
`;
|
||||
|
||||
@@ -4,7 +4,7 @@ from (select
|
||||
view_name as "pure_name",
|
||||
text as "create_sql"
|
||||
from all_views av
|
||||
where owner = 'C##test' and text is not null
|
||||
where owner = '$owner' and text is not null
|
||||
) avv
|
||||
where 'views:' || "pure_name" is not null
|
||||
`;
|
||||
|
||||
@@ -60,9 +60,9 @@ class Dumper extends SqlDumper {
|
||||
// this.putCmd('^alter ^table %f ^rename ^to %i', obj, newname);
|
||||
// }
|
||||
|
||||
// renameColumn(column, newcol) {
|
||||
// this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol);
|
||||
// }
|
||||
renameColumn(column, newcol) {
|
||||
this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol);
|
||||
}
|
||||
|
||||
// dropTable(obj, options = {}) {
|
||||
// this.put('^drop ^table');
|
||||
@@ -87,30 +87,48 @@ class Dumper extends SqlDumper {
|
||||
// super.columnDefinition(col, options);
|
||||
// }
|
||||
|
||||
// changeColumn(oldcol, newcol, constraints) {
|
||||
// if (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 != 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 (oldcol.defaultValue != newcol.defaultValue) {
|
||||
// if (newcol.defaultValue == null) {
|
||||
// this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^default', newcol, newcol.columnName);
|
||||
// } else {
|
||||
// this.putCmd(
|
||||
// '^alter ^table %f ^alter ^column %i ^set ^default %s',
|
||||
// newcol,
|
||||
// newcol.columnName,
|
||||
// newcol.defaultValue
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
changeColumn(oldcol, newcol, constraints) {
|
||||
if (oldcol.columnName != newcol.columnName) {
|
||||
this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', oldcol, oldcol.columnName, newcol.columnName);
|
||||
}
|
||||
|
||||
if (!oldcol.notNull) {
|
||||
this.fillNewNotNullDefaults(newcol);
|
||||
}
|
||||
|
||||
if (!testEqualTypes(oldcol, newcol) || oldcol.notNull != newcol.notNull) {
|
||||
this.putCmd(
|
||||
'^alter ^table %f ^modify (%i %s %k)',
|
||||
newcol,
|
||||
newcol.columnName,
|
||||
newcol.dataType,
|
||||
newcol.notNull ? 'not null' : 'null'
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
// if (value === true) this.putRaw('true');
|
||||
|
||||
@@ -13,7 +13,7 @@ const dialect = {
|
||||
ilike: true,
|
||||
// stringEscapeChar: '\\',
|
||||
stringEscapeChar: "'",
|
||||
fallbackDataType: 'varchar',
|
||||
fallbackDataType: 'varchar(250)',
|
||||
anonymousPrimaryKey: false,
|
||||
enableConstraintsPerTable: true,
|
||||
dropColumnDependencies: ['dependencies'],
|
||||
@@ -22,6 +22,7 @@ const dialect = {
|
||||
},
|
||||
userDatabaseNamePrefix: 'C##',
|
||||
upperCaseAllDbObjectNames: true,
|
||||
requireStandaloneSelectForScopeIdentity: true,
|
||||
|
||||
createColumn: true,
|
||||
dropColumn: true,
|
||||
@@ -36,6 +37,7 @@ const dialect = {
|
||||
dropUnique: true,
|
||||
createCheck: true,
|
||||
dropCheck: true,
|
||||
renameSqlObject: true,
|
||||
|
||||
dropReferencesWhenDropTable: true,
|
||||
requireFromDual: true,
|
||||
|
||||
@@ -89,7 +89,9 @@ class Dumper extends SqlDumper {
|
||||
}
|
||||
}
|
||||
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);
|
||||
else this.putCmd('^alter ^table %f ^alter ^column %i ^drop ^not ^null', newcol, newcol.columnName);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user