diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml deleted file mode 100644 index f5ecce628..000000000 --- a/.github/workflows/e2e-tests.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index b8ceda796..c813d251e 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -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 diff --git a/integration-tests/__tests__/alter-database.spec.js b/integration-tests/__tests__/alter-database.spec.js index dae829f25..490de76a9 100644 --- a/integration-tests/__tests__/alter-database.spec.js +++ b/integration-tests/__tests__/alter-database.spec.js @@ -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)); diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index afe1c9a61..5687cc612 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -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', () => { // }); // }) // ); - }); diff --git a/integration-tests/__tests__/data-duplicator.spec.js b/integration-tests/__tests__/data-duplicator.spec.js index 67023aa50..3c7791b80 100644 --- a/integration-tests/__tests__/data-duplicator.spec.js +++ b/integration-tests/__tests__/data-duplicator.spec.js @@ -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'); }) ); diff --git a/integration-tests/__tests__/db-import.spec.js b/integration-tests/__tests__/db-import.spec.js index 78393f7c5..6177ae3a3 100644 --- a/integration-tests/__tests__/db-import.spec.js +++ b/integration-tests/__tests__/db-import.spec.js @@ -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'); }) ); diff --git a/integration-tests/__tests__/deploy-database.spec.js b/integration-tests/__tests__/deploy-database.spec.js index c4db435a6..b1c9faf21 100644 --- a/integration-tests/__tests__/deploy-database.spec.js +++ b/integration-tests/__tests__/deploy-database.spec.js @@ -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(); }) diff --git a/integration-tests/__tests__/object-analyse.spec.js b/integration-tests/__tests__/object-analyse.spec.js index f94223b69..f924f2137 100644 --- a/integration-tests/__tests__/object-analyse.spec.js +++ b/integration-tests/__tests__/object-analyse.spec.js @@ -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; diff --git a/integration-tests/__tests__/query.spec.js b/integration-tests/__tests__/query.spec.js index d01648286..b843f7c30 100644 --- a/integration-tests/__tests__/query.spec.js +++ b/integration-tests/__tests__/query.spec.js @@ -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(); + }) + ); }); diff --git a/integration-tests/__tests__/schema-tests.spec.js b/integration-tests/__tests__/schema-tests.spec.js index 116b15b17..d31fcb13b 100644 --- a/integration-tests/__tests__/schema-tests.spec.js +++ b/integration-tests/__tests__/schema-tests.spec.js @@ -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); diff --git a/integration-tests/__tests__/table-analyse.spec.js b/integration-tests/__tests__/table-analyse.spec.js index f8404ec13..43c3c439f 100644 --- a/integration-tests/__tests__/table-analyse.spec.js +++ b/integration-tests/__tests__/table-analyse.spec.js @@ -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); diff --git a/integration-tests/__tests__/table-create.spec.js b/integration-tests/__tests__/table-create.spec.js index 471f5e544..606811aec 100644 --- a/integration-tests/__tests__/table-create.spec.js +++ b/integration-tests/__tests__/table-create.spec.js @@ -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 = { diff --git a/integration-tests/docker-compose.yaml b/integration-tests/docker-compose.yaml index 2ec611827..29cfb9371 100644 --- a/integration-tests/docker-compose.yaml +++ b/integration-tests/docker-compose.yaml @@ -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 diff --git a/integration-tests/engines.js b/integration-tests/engines.js index b1174f11e..da452af83 100644 --- a/integration-tests/engines.js +++ b/integration-tests/engines.js @@ -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', + }, + ], }, ]; diff --git a/packages/datalib/src/DataDuplicator.ts b/packages/datalib/src/DataDuplicator.ts index 50c0b6571..55c56984e 100644 --- a/packages/datalib/src/DataDuplicator.ts +++ b/packages/datalib/src/DataDuplicator.ts @@ -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; diff --git a/packages/tools/src/driverBase.ts b/packages/tools/src/driverBase.ts index 6c1391efb..06820f7d5 100644 --- a/packages/tools/src/driverBase.ts +++ b/packages/tools/src/driverBase.ts @@ -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 { +export async function runCommandOnDriver( + pool, + driver: EngineDriver, + cmd: (dmp: SqlDumper) => void | string +): Promise { 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 { 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, diff --git a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js index 4cfdaf2eb..bb9bad57e 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/MsSqlDumper.js @@ -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(); diff --git a/plugins/dbgate-plugin-mysql/src/frontend/Dumper.js b/plugins/dbgate-plugin-mysql/src/frontend/Dumper.js index 7878e1aa5..8853ed2ad 100644 --- a/plugins/dbgate-plugin-mysql/src/frontend/Dumper.js +++ b/plugins/dbgate-plugin-mysql/src/frontend/Dumper.js @@ -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); diff --git a/plugins/dbgate-plugin-oracle/src/backend/Analyser.js b/plugins/dbgate-plugin-oracle/src/backend/Analyser.js index bbb851f28..397f7aa5f 100644 --- a/plugins/dbgate-plugin-oracle/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-oracle/src/backend/Analyser.js @@ -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, diff --git a/plugins/dbgate-plugin-oracle/src/backend/sql/routines.js b/plugins/dbgate-plugin-oracle/src/backend/sql/routines.js index 2f290386c..a16f21dee 100644 --- a/plugins/dbgate-plugin-oracle/src/backend/sql/routines.js +++ b/plugins/dbgate-plugin-oracle/src/backend/sql/routines.js @@ -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 `; diff --git a/plugins/dbgate-plugin-oracle/src/backend/sql/views.js b/plugins/dbgate-plugin-oracle/src/backend/sql/views.js index facf4731f..f0ff95c1d 100644 --- a/plugins/dbgate-plugin-oracle/src/backend/sql/views.js +++ b/plugins/dbgate-plugin-oracle/src/backend/sql/views.js @@ -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 `; diff --git a/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js b/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js index cbe0becdd..15e387972 100644 --- a/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js +++ b/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js @@ -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'); diff --git a/plugins/dbgate-plugin-oracle/src/frontend/driver.js b/plugins/dbgate-plugin-oracle/src/frontend/driver.js index d52d49aac..8e34381dd 100644 --- a/plugins/dbgate-plugin-oracle/src/frontend/driver.js +++ b/plugins/dbgate-plugin-oracle/src/frontend/driver.js @@ -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, diff --git a/plugins/dbgate-plugin-postgres/src/frontend/Dumper.js b/plugins/dbgate-plugin-postgres/src/frontend/Dumper.js index 898f6167d..127db3656 100644 --- a/plugins/dbgate-plugin-postgres/src/frontend/Dumper.js +++ b/plugins/dbgate-plugin-postgres/src/frontend/Dumper.js @@ -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); }