Compare commits

..

116 Commits

Author SHA1 Message Date
Jan Prochazka 3895c6bb47 tests 2021-09-06 18:26:12 +02:00
Jan Prochazka 2e03056a15 tests 2021-09-06 18:25:34 +02:00
Jan Prochazka eaa5970a0f drop column with default works 2021-09-05 11:18:44 +02:00
Jan Prochazka e79e19c614 drop column ref works 2021-09-05 11:01:52 +02:00
Jan Prochazka 0ef5ac04d8 drop unique column works 2021-09-05 10:55:53 +02:00
Jan Prochazka 2cb3a6b446 create table dumper 2021-09-05 09:44:40 +02:00
Jan Prochazka d75397d793 sql dumper - create index 2021-09-05 09:38:38 +02:00
Jan Prochazka 5b58ed9c26 create table test 2021-09-05 08:42:56 +02:00
Jan Prochazka f4c39bbf3c cockroach fk analyse fix 2021-09-04 21:54:12 +02:00
Jan Prochazka e2ce349a30 alter processor fixes 2021-09-04 21:06:42 +02:00
Jan Prochazka 04a6540890 postgre, mysql uniques, recreate table WIP, drop index works 2021-09-04 18:43:59 +02:00
Jan Prochazka b3b7d021c5 Merge branch 'master' into tableeditor2 2021-09-04 09:41:10 +02:00
Jan Prochazka b396c8f820 v4.2.8-beta.3 2021-09-04 09:35:02 +02:00
Jan Prochazka 8cc8f9f0b9 #149 2021-09-04 09:34:10 +02:00
Jan Prochazka 3bbe06a55b recreate table WIP 2021-08-26 16:29:28 +02:00
Jan Prochazka dfe37496f2 unique analysis 2021-08-26 13:00:38 +02:00
Jan Prochazka 3fe13f0443 alter table WIP 2021-08-26 11:45:44 +02:00
Jan Prochazka a5b5f36298 sqlite index analysis 2021-08-25 19:57:11 +02:00
Jan Prochazka b9e2e51bd7 index column analysingh works for both postgres and cockroach 2021-08-25 18:43:08 +02:00
Jan Prochazka 10e63f3e77 index column analysis - postgres is OK, still fails for cockroach 2021-08-20 22:24:31 +02:00
Jan Prochazka 820044b489 postgre sql indexes analysis - partialy working 2021-08-20 22:17:02 +02:00
Jan Prochazka 89c904abc1 mysql index analysis works 2021-08-19 16:08:27 +02:00
Jan Prochazka c5a3ee01ee sql server index analyser works 2021-08-19 15:14:27 +02:00
Jan Prochazka a5cc99005a Merge branch 'master' into tableeditor2 2021-08-19 13:39:45 +02:00
Jan Prochazka 77ebf0051c v4.2.8-beta.2 2021-08-19 13:35:19 +02:00
Jan Prochazka d5cee7b35b fix 2021-08-19 13:35:07 +02:00
Jan Prochazka 3ac96c4ae4 v4.2.8-beta.1 2021-08-19 13:32:20 +02:00
Jan Prochazka 60545674c5 sql export with correct dialect 2021-08-19 13:30:57 +02:00
Jan Prochazka b5c313e517 SQL file export 2021-08-19 12:41:43 +02:00
Jan Prochazka e3bdad6d77 fixed messages table display 2021-08-19 11:53:08 +02:00
Jan Prochazka 7453afa684 #160 json view dark mode colors 2021-08-19 11:09:05 +02:00
Jan Prochazka 86eca6bc7e Merge pull request #152 from cschreier/master
#150 Enable horizontal scrolling with touchpad
2021-08-14 09:37:12 +02:00
Jan Prochazka 3c0bc69662 mssql indexes analyse WIP 2021-08-14 09:36:22 +02:00
Christian Schreier 2baf9a1446 enable scroll in whole table container 2021-07-31 12:37:14 +02:00
Christian Schreier 296038a3de enable horizontal scroll with touchpad 2021-07-31 12:35:48 +02:00
Jan Prochazka 71e1ea5736 Merge branch 'master' into tableeditor2 2021-07-18 07:59:31 +02:00
Jan Prochazka 32d05edb6a fixed popup menu for columns 2021-07-18 07:57:25 +02:00
Jan Prochazka ba608ff438 Merge pull request #146 from cschreier/master
#145 open inplace editor on num pad key down
2021-07-18 07:20:14 +02:00
cschreier 5d79b687d5 open inplace editor on num pad key down 2021-07-16 10:57:22 +02:00
Jan Prochazka ad1ec70e94 v4.2.7 2021-07-15 21:37:48 +02:00
Jan Prochazka 7e73f81d4e v4.2.7-beta.2 2021-07-14 21:05:14 +02:00
Jan Prochazka 9a0ae06c87 #143 2021-07-14 21:04:51 +02:00
Jan Prochazka 902fbbf29a #142 2021-07-14 20:58:42 +02:00
Jan Prochazka a0df43484a samll refactor 2021-07-14 20:54:52 +02:00
Jan Prochazka d2317ab908 new query on middle mouse click 2021-07-14 20:46:26 +02:00
Jan Prochazka fa733e2285 implemented middle click in connection tree 2021-07-14 18:07:29 +02:00
Jan Prochazka 134737d4a8 Merge pull request #141 from tumregels/tumregels-patch-1
fix a typo in README.md
2021-07-12 22:33:24 +02:00
Lev def3141b6d Update README.md 2021-07-07 15:52:40 -04:00
Jan Prochazka 057afe2bd5 v4.2.7-beta.1 2021-07-02 09:41:04 +02:00
Jan Prochazka 40203f2823 #140 2021-07-01 13:41:48 +02:00
Jan Prochazka 9d711e45f9 display datetime cells #137 2021-07-01 13:35:19 +02:00
Jan Prochazka 35eb5716a5 change column nullablility - works (without SQLite - table recreate needed) 2021-07-01 12:01:24 +02:00
Jan Prochazka 7a10b85b4c rename column works 2021-07-01 10:50:41 +02:00
Jan Prochazka 7a2e86246d columns cross-tests 2021-07-01 10:23:13 +02:00
Jan Prochazka 331b275105 alter processor tests 2021-07-01 09:41:28 +02:00
Jan Prochazka 3791fd568c alter processor - works add column tests 2021-07-01 09:12:15 +02:00
Jan Prochazka 6ed9bb0258 v4.2.6 2021-06-30 16:45:10 +02:00
Jan Prochazka e66f2fcd2d changelog 2021-06-30 16:43:14 +02:00
Jan Prochazka 1250f5d25a v4.2.6-beta.3 2021-06-30 16:21:47 +02:00
Jan Prochazka 4a3ef70979 Merge branch 'master' into tableeditor2 2021-06-30 16:20:48 +02:00
Jan Prochazka bf29ee430e typo 2021-06-30 16:20:26 +02:00
Jan Prochazka c7f1e75d22 v4.2.6-beta.2 2021-06-30 16:18:25 +02:00
Jan Prochazka 0886712714 typo 2021-06-30 16:17:50 +02:00
Jan Prochazka f415c8bfe9 typo 2021-06-30 16:15:13 +02:00
Jan Prochazka 4c1ac0757c create column test 2021-06-28 08:00:28 +02:00
Jan Prochazka 67a793038b alter processor 2021-06-27 20:44:02 +02:00
Jan Prochazka 05a65dab3c alter plan 2021-06-25 17:04:01 +02:00
Jan Prochazka 0d61e43431 new table command 2021-06-25 15:12:46 +02:00
Jan Prochazka b6195603e8 FK on update,, on delete actions 2021-06-25 14:59:55 +02:00
Jan Prochazka 6db306cb0c fk editor 2021-06-24 17:19:25 +02:00
Jan Prochazka 48140348e0 Merge branch 'master' into tableeditor2 2021-06-24 11:49:35 +02:00
Jan Prochazka 989574bb52 using ilike instead of like for postgres 2021-06-24 11:49:02 +02:00
Jan Prochazka 8f3c479642 fk editor 2021-06-24 11:42:26 +02:00
Jan Prochazka 4db464772e fk editor 2021-06-24 11:27:58 +02:00
Jan Prochazka 039d3b4058 Merge branch 'master' into tableeditor2 2021-06-24 09:44:05 +02:00
Jan Prochazka b99e3ed177 #136 configurable thousands separator in grid 2021-06-24 09:22:12 +02:00
Jan Prochazka 6519ba95bc table editor - add pk command 2021-06-17 15:00:52 +02:00
Jan Prochazka 6f22932b16 table editor 2021-06-17 14:37:59 +02:00
Jan Prochazka bf725dd563 table editor 2021-06-17 14:29:45 +02:00
Jan Prochazka dea6700a25 pk editor 2021-06-17 13:58:21 +02:00
Jan Prochazka b8ccae570e PK editor iun column editor 2021-06-17 11:13:28 +02:00
Jan Prochazka 17fc6ccc2e table editor WIP 2021-06-17 11:09:26 +02:00
Jan Prochazka 112f310d13 data type editor 2021-06-17 08:54:11 +02:00
Jan Prochazka 8874589ed0 column editor 2021-06-17 08:22:41 +02:00
Jan Prochazka f7621ae336 Merge pull request #134 from ArnoNuyts/patch-1
Fix importing into a mongo db
2021-06-17 07:33:04 +02:00
Jan Prochazka b4cc211763 renamed groupId => pairingId 2021-06-17 07:22:44 +02:00
ArnoNuyts 38263de9f1 Update driver.js
Fix issue where createBulkInsertStream isn't found
2021-06-16 12:04:33 +02:00
Jan Prochazka 260e3c50df v4.2.6-beta.1 2021-06-14 07:13:07 +02:00
Jan Prochazka 57327623d1 Revert "v4.2.6-beta.1"
This reverts commit 661775d5af.
2021-06-14 07:12:39 +02:00
Jan Prochazka ff9f1d85ab Merge branch 'master' of github.com:dbgate/dbgate 2021-06-13 20:42:45 +02:00
Jan Prochazka 661775d5af v4.2.6-beta.1 2021-06-13 20:42:06 +02:00
Jan Prochazka dd1088d02d Merge pull request #130 from knixeur/fix/115-icon-with-deb
fix: install correct size for icons in .deb package
2021-06-13 20:41:40 +02:00
Jan Prochazka 8d265ad6d2 changelog 2021-06-13 20:32:53 +02:00
Jan Prochazka 346c530f76 v4.2.5 2021-06-13 20:30:00 +02:00
Jan Prochazka 870e3ad666 column editor 2021-06-10 15:57:37 +02:00
Jan Prochazka e31ff5960c v4.2.5-beta.4 2021-06-10 14:11:58 +02:00
Jan Prochazka 3fa71cc94a postgre function analyser 2021-06-10 14:11:26 +02:00
Jan Prochazka f5ea87da7b testing code for #125 2021-06-10 13:37:24 +02:00
Jan Prochazka 643695bd2b #125 2021-06-10 13:37:09 +02:00
Jan Prochazka 697a9438c6 column editor dialog 2021-06-10 12:48:03 +02:00
Jan Prochazka 3ad665f80b fix 2021-06-10 11:10:45 +02:00
Jan Prochazka 7847eaa64d table editor WIP 2021-06-10 10:51:30 +02:00
Guillermo Bonvehí bb870ec90f fix: install correct size for icons in .deb package
Credits to @AulonSal that suggested reviewing
https://github.com/sindresorhus/caprine/pull/1420

Fix #115
2021-06-10 03:47:20 -03:00
Jan Prochazka 9959e61b35 v4.2.5-beta.2 2021-06-10 08:43:00 +02:00
Jan Prochazka 92ead26873 #125 2021-06-10 08:42:22 +02:00
Jan Prochazka afb27ec989 v4.2.5-beta.1 2021-06-09 20:19:50 +02:00
Jan Prochazka ff30b6511c fixed build 2021-06-09 20:13:05 +02:00
Jan Prochazka 56306aeaec app objecty list - remove keys 2021-06-09 20:11:02 +02:00
Jan Prochazka c2c354044a Merge pull request #126 from knixeur/bugfix/mysql_no_altered_created_date
fix(mysql): contentHash return null if modifyDate is not set
2021-06-09 18:47:31 +02:00
Guillermo Bonvehí ceb216df8c fix(mysql): contentHash return null if modifyDate is not set 2021-06-08 09:20:54 -03:00
Jan Prochazka b2994ede8c changelog 2021-06-07 18:10:34 +02:00
Jan Prochazka 0b87c5085c v4.2.4 2021-06-07 18:07:43 +02:00
Jan Prochazka 2305d086cc commented testing code 2021-06-07 18:06:49 +02:00
Jan Prochazka 6c3c1b377e v4.2.4-beta.4 2021-06-06 18:41:11 +02:00
Jan Prochazka 07c4c89720 Merge branch 'quick-export' 2021-06-06 18:40:44 +02:00
Jan Prochazka 87efb92f07 v4.2.4-beta.3 2021-06-03 20:59:38 +02:00
100 changed files with 3036 additions and 269 deletions
+1
View File
@@ -3,6 +3,7 @@ on:
push:
branches:
- master
- develop
jobs:
test-runner:
+14 -1
View File
@@ -1,6 +1,19 @@
# ChangeLog
### 4.2.4 - to be released
### 4.2.6
- Fixed MongoDB import
- Configurable thousands separator #136
- Using case insensitive text search in postgres
### 4.2.5
- FIXED: Fixed crash when using large model on some installations
- FIXED: Postgre SQL CREATE function
- FIXED: Analysing of MySQL when modifyDate is not known
### 4.2.4
- ADDED: Query history
- ADDED: One-click exports in desktop app
- ADDED: JSON array export
- FIXED: Procedures in PostgreSQL #122
- ADDED: Support of materialized views for PostgreSQL #123
- ADDED: Integration tests
+1 -1
View File
@@ -64,7 +64,7 @@ There are many database managers now, so why DbGate?
* Backend - NodeJs, ExpressJs, socket.io, database connection drivers
* JavaScript + TypeScript
* App - electron
* Platform independed - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
* Platform independent - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
## Plugins
Plugins are standard NPM packages published on [npmjs.com](https://www.npmjs.com).
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

+2 -2
View File
@@ -45,7 +45,7 @@
]
}
],
"icon": "icon.png",
"icon": "icons/",
"category": "Development",
"synopsis": "Database manager for SQL Server, MySQL, PostgreSQL, MongoDB and SQLite",
"publish": [
@@ -103,4 +103,4 @@
"optionalDependencies": {
"msnodesqlv8": "^2.0.10"
}
}
}
@@ -0,0 +1,123 @@
const stableStringify = require('json-stable-stringify');
const _ = require('lodash');
const fp = require('lodash/fp');
const uuidv1 = require('uuid/v1');
const { testWrapper } = require('../tools');
const engines = require('../engines');
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
function pickImportantTableInfo(table) {
return {
pureName: table.pureName,
columns: table.columns
.filter(x => x.columnName != 'rowid')
.map(fp.pick(['columnName', 'notNull', 'autoIncrement'])),
};
}
function checkTableStructure(t1, t2) {
// expect(t1.pureName).toEqual(t2.pureName)
expect(pickImportantTableInfo(t1)).toEqual(pickImportantTableInfo(t2));
}
async function testTableDiff(conn, driver, mangle) {
await driver.query(conn, `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 null,
col_def int null default 12,
col_fk int null references t0(id),
col_idx int null,
col_uq int null unique,
col_ref int null unique
)`
);
await driver.query(conn, `create index idx1 on t1(col_idx)`);
await driver.query(conn, `create table t2 (id int not null primary key, fkval int null references t1(col_ref))`);
const tget = x => x.tables.find(y => y.pureName == 't1');
const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn)));
let structure2 = _.cloneDeep(structure1);
mangle(tget(structure2));
structure2 = extendDatabaseInfo(structure2);
const sql = getAlterTableScript(tget(structure1), tget(structure2), {}, structure2, driver);
console.log('RUNNING ALTER SQL', driver.engine, ':', sql);
await driver.script(conn, sql);
const structure2Real = extendDatabaseInfo(await driver.analyseFull(conn));
checkTableStructure(tget(structure2Real), tget(structure2));
// expect(stableStringify(structure2)).toEqual(stableStringify(structure2Real));
}
const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq'];
// const TESTED_COLUMNS = ['col_pk'];
// const TESTED_COLUMNS = ['col_idx'];
// const TESTED_COLUMNS = ['col_def'];
// const TESTED_COLUMNS = ['col_std'];
function engines_columns_source() {
return _.flatten(engines.map(engine => TESTED_COLUMNS.map(column => [engine.label, column, engine])));
}
describe('Alter processor', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Add column - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(conn, driver, tbl => {
tbl.columns.push({
columnName: 'added',
dataType: 'int',
pairingId: uuidv1(),
notNull: false,
autoIncrement: false,
});
});
})
);
test.each(engines_columns_source())(
'Drop column - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)));
})
);
test.each(engines_columns_source())(
'Change nullability - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
conn,
driver,
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x)))
);
})
);
test.each(engines_columns_source())(
'Rename column - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
conn,
driver,
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x)))
);
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Drop index - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(conn, driver, tbl => {
tbl.indexes = [];
});
})
);
});
@@ -2,7 +2,10 @@ const engines = require('../engines');
const { testWrapper } = require('../tools');
const t1Sql = 'CREATE TABLE t1 (id int not null primary key, val1 varchar(50) null)';
const t2Sql = 'CREATE TABLE t2 (id int not null primary key, val2 varchar(50) null)';
const ix1Sql = 'CREATE index ix1 ON t1(val1, id)';
const t2Sql = 'CREATE TABLE t2 (id int not null primary key, val2 varchar(50) null unique)';
const t3Sql = 'CREATE TABLE t3 (id int not null primary key, valfk int, foreign key (valfk) references t2(id))';
// const fkSql = 'ALTER TABLE t3 ADD FOREIGN KEY (valfk) REFERENCES t2(id)'
const txMatch = (tname, vcolname, nextcol) =>
expect.objectContaining({
@@ -98,7 +101,7 @@ describe('Table analyse', () => {
const structure1 = await driver.analyseFull(conn);
if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100));
await driver.query(conn, 'ALTER TABLE t2 ADD nextcol varchar(50)');
const structure2 = await driver.analyseIncremental(conn, structure1);
@@ -109,4 +112,53 @@ describe('Table analyse', () => {
expect(structure2.tables.find(x => x.pureName == 't2')).toEqual(t2NextColMatch);
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Index - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t1Sql);
await driver.query(conn, ix1Sql);
const structure = await driver.analyseFull(conn);
const t1 = structure.tables.find(x => x.pureName == 't1');
expect(t1.indexes.length).toEqual(1);
expect(t1.indexes[0].columns.length).toEqual(2);
expect(t1.indexes[0].columns[0]).toEqual(expect.objectContaining({ columnName: 'val1' }));
expect(t1.indexes[0].columns[1]).toEqual(expect.objectContaining({ columnName: 'id' }));
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Unique - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql);
const structure = await driver.analyseFull(conn);
const t2 = structure.tables.find(x => x.pureName == 't2');
// const indexesAndUniques = [...t2.uniques, ...t2.indexes];
expect(t2.uniques.length).toEqual(1);
expect(t2.uniques[0].columns.length).toEqual(1);
expect(t2.uniques[0].columns[0]).toEqual(expect.objectContaining({ columnName: 'val2' }));
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Foreign key - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql);
await driver.query(conn, t3Sql);
// await driver.query(conn, fkSql);
const structure = await driver.analyseFull(conn);
const t3 = structure.tables.find(x => x.pureName == 't3');
console.log('T3', t3.foreignKeys[0].columns);
expect(t3.foreignKeys.length).toEqual(1);
expect(t3.foreignKeys[0].columns.length).toEqual(1);
expect(t3.foreignKeys[0]).toEqual(expect.objectContaining({ refTableName: 't2' }));
expect(t3.foreignKeys[0].columns[0]).toEqual(
expect.objectContaining({ columnName: 'valfk', refColumnName: 'id' })
);
})
);
});
@@ -0,0 +1,153 @@
const _ = require('lodash');
const fp = require('lodash/fp');
const engines = require('../engines');
const { testWrapper } = require('../tools');
const { extendDatabaseInfo } = require('dbgate-tools');
function createExpector(value) {
return _.cloneDeepWith(value, x => {
if (_.isPlainObject(x)) {
return expect.objectContaining(_.mapValues(x, y => createExpector(y)));
}
});
}
function omitTableSpecificInfo(table) {
return {
...table,
columns: table.columns.map(fp.omit(['dataType'])),
};
}
function checkTableStructure2(t1, t2) {
// expect(t1.pureName).toEqual(t2.pureName)
expect(t2).toEqual(createExpector(omitTableSpecificInfo(t1)));
}
async function testTableCreate(conn, driver, table) {
await driver.query(conn, `create table t0 (id int not null primary key)`);
const dmp = driver.createDumper();
const table1 = {
...table,
pureName: 'tested',
};
dmp.createTable(table1);
console.log('RUNNING CREATE SQL', driver.engine, ':', dmp.s);
await driver.script(conn, dmp.s);
const db = extendDatabaseInfo(await driver.analyseFull(conn));
const table2 = db.tables.find(x => x.pureName == 'tested');
checkTableStructure2(table1, table2);
}
describe('Table create', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Simple table - %s',
testWrapper(async (conn, driver, engine) => {
await testTableCreate(conn, driver, {
columns: [
{
columnName: 'col1',
dataType: 'int',
notNull: true,
},
],
primaryKey: {
columns: [{ columnName: 'col1' }],
},
});
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Table with index - %s',
testWrapper(async (conn, driver, engine) => {
await testTableCreate(conn, driver, {
columns: [
{
columnName: 'col1',
dataType: 'int',
notNull: true,
},
{
columnName: 'col2',
dataType: 'int',
notNull: true,
},
],
primaryKey: {
columns: [{ columnName: 'col1' }],
},
indexes: [
{
constraintName: 'ix1',
pureName: 'tested',
columns: [{ columnName: 'col2' }],
},
],
});
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Table with foreign key - %s',
testWrapper(async (conn, driver, engine) => {
await testTableCreate(conn, driver, {
columns: [
{
columnName: 'col1',
dataType: 'int',
notNull: true,
},
{
columnName: 'col2',
dataType: 'int',
notNull: true,
},
],
primaryKey: {
columns: [{ columnName: 'col1' }],
},
foreignKeys: [
{
pureName: 'tested',
refTableName: 't0',
columns: [{ columnName: 'col2', refColumnName: 'id' }],
},
],
});
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Table with unique - %s',
testWrapper(async (conn, driver, engine) => {
await testTableCreate(conn, driver, {
columns: [
{
columnName: 'col1',
dataType: 'int',
notNull: true,
},
{
columnName: 'col2',
dataType: 'int',
notNull: true,
},
],
primaryKey: {
columns: [{ columnName: 'col1' }],
},
uniques: [
{
pureName: 'tested',
columns: [{ columnName: 'col2' }],
},
],
});
})
);
});
+21 -1
View File
@@ -54,6 +54,15 @@ const engines = [
drop1: 'DROP PROCEDURE obj1',
drop2: 'DROP PROCEDURE obj2',
},
{
type: 'functions',
create1:
'CREATE FUNCTION obj1() returns int LANGUAGE plpgsql AS $$ declare res integer; begin select count(*) into res from t1; return res; end; $$',
create2:
'CREATE FUNCTION obj2() returns int LANGUAGE plpgsql AS $$ declare res integer; begin select count(*) into res from t2; return res; end; $$',
drop1: 'DROP FUNCTION obj1',
drop2: 'DROP FUNCTION obj2',
},
],
},
{
@@ -105,4 +114,15 @@ const engines = [
},
];
module.exports = process.env.CITEST ? engines.filter(x => !x.skipOnCI) : engines;
const filterLocal = [
// filter local testing
'MySQL',
'PostgreSQL',
'SQL Server',
'SQLite',
'CockroachDB',
];
module.exports = process.env.CITEST
? engines.filter(x => !x.skipOnCI)
: engines.filter(x => filterLocal.find(y => x.label == y));
+3
View File
@@ -17,6 +17,9 @@
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
},
"jest": {
"testTimeout": 5000
},
"devDependencies": {
"cross-env": "^7.0.3",
"jest": "^27.0.1"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "4.2.4-beta.2",
"version": "4.2.8-beta.3",
"name": "dbgate-all",
"workspaces": [
"packages/*",
+2
View File
@@ -8,6 +8,7 @@ const consoleObjectWriter = require('./consoleObjectWriter');
const jsonLinesWriter = require('./jsonLinesWriter');
const jsonArrayWriter = require('./jsonArrayWriter');
const jsonLinesReader = require('./jsonLinesReader');
const sqlDataWriter = require('./sqlDataWriter');
const jslDataReader = require('./jslDataReader');
const archiveWriter = require('./archiveWriter');
const archiveReader = require('./archiveReader');
@@ -29,6 +30,7 @@ const dbgateApi = {
jsonLinesWriter,
jsonArrayWriter,
jsonLinesReader,
sqlDataWriter,
fakeObjectReader,
consoleObjectWriter,
jslDataReader,
+54
View File
@@ -0,0 +1,54 @@
const fs = require('fs');
const stream = require('stream');
const path = require('path');
const { driverBase } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
class SqlizeStream extends stream.Transform {
constructor({ fileName }) {
super({ objectMode: true });
this.wasHeader = false;
this.tableName = path.parse(fileName).name;
this.driver = driverBase;
}
_transform(chunk, encoding, done) {
let skip = false;
if (!this.wasHeader) {
if (
chunk.__isStreamHeader ||
// TODO remove isArray test
Array.isArray(chunk.columns)
) {
skip = true;
this.tableName = chunk.pureName;
if (chunk.engine) {
// @ts-ignore
this.driver = requireEngineDriver(chunk.engine) || driverBase;
}
}
this.wasHeader = true;
}
if (!skip) {
const dmp = this.driver.createDumper();
dmp.put(
'^insert ^into %f (%,i) ^values (%,v);\n',
{ pureName: this.tableName },
Object.keys(chunk),
Object.values(chunk)
);
this.push(dmp.s);
}
done();
}
}
async function sqlDataWriter({ fileName, driver, encoding = 'utf-8' }) {
console.log(`Writing file ${fileName}`);
const stringify = new SqlizeStream({ fileName });
const fileStream = fs.createWriteStream(fileName, encoding);
stringify.pipe(fileStream);
stringify['finisher'] = fileStream;
return stringify;
}
module.exports = sqlDataWriter;
+1
View File
@@ -75,6 +75,7 @@ export abstract class GridDisplay {
}
changeSetKeyFields: string[] = null;
sortable = false;
groupable = false;
filterable = false;
editable = false;
isLoadedCorrectly = true;
+1
View File
@@ -35,6 +35,7 @@ export class TableGridDisplay extends GridDisplay {
this.columns = this.getDisplayColumns(this.table, []);
this.filterable = true;
this.sortable = true;
this.groupable = true;
this.editable = true;
this.supportsReload = true;
this.baseTable = this.table;
+1
View File
@@ -17,6 +17,7 @@ export class ViewGridDisplay extends GridDisplay {
this.columns = this.getDisplayColumns(view);
this.filterable = true;
this.sortable = true;
this.groupable = true;
this.editable = false;
this.supportsReload = true;
}
+1 -1
View File
@@ -39,7 +39,7 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
break;
case 'like':
dumpSqlExpression(dmp, condition.left);
dmp.put(' ^like ');
dmp.put(dmp.dialect.ilike ? ' ^ilike ' : ' ^like ');
dumpSqlExpression(dmp, condition.right);
break;
case 'notLike':
+3 -2
View File
@@ -31,6 +31,7 @@
"typescript": "^3.7.5"
},
"dependencies": {
"lodash": "^4.17.21"
"lodash": "^4.17.21",
"uuid": "^3.4.0"
}
}
}
+20 -5
View File
@@ -4,7 +4,10 @@ import _groupBy from 'lodash/groupBy';
import _pick from 'lodash/pick';
import _compact from 'lodash/compact';
const STRUCTURE_FIELDS = ['tables', 'collections', 'views', 'matviews', 'functions', 'procedures', 'triggers'];
const fp_pick = arg => array => _pick(array, arg);
export class DatabaseAnalyser {
structure: DatabaseInfo;
modifications: DatabaseModification[];
@@ -23,8 +26,20 @@ export class DatabaseAnalyser {
async _computeSingleObjectId() {}
addEngineField(db: DatabaseInfo) {
if (!this.driver?.engine) return;
for (const field of STRUCTURE_FIELDS) {
if (!db[field]) continue;
for (const item of db[field]) {
item.engine = this.driver.engine;
}
}
db.engine = this.driver.engine;
return db;
}
async fullAnalysis() {
const res = await this._runAnalysis();
const res = this.addEngineField(await this._runAnalysis());
// console.log('FULL ANALYSIS', res);
return res;
}
@@ -33,7 +48,7 @@ export class DatabaseAnalyser {
// console.log('Analysing SINGLE OBJECT', name, typeField);
this.singleObjectFilter = { ...name, typeField };
await this._computeSingleObjectId();
const res = await this._runAnalysis();
const res = this.addEngineField(await this._runAnalysis());
// console.log('SINGLE OBJECT RES', res);
const obj =
res[typeField]?.length == 1
@@ -50,11 +65,11 @@ export class DatabaseAnalyser {
if (this.modifications == null) {
// modifications not implemented, perform full analysis
this.structure = null;
return this._runAnalysis();
return this.addEngineField(await this._runAnalysis());
}
if (this.modifications.length == 0) return null;
console.log('DB modifications detected:', this.modifications);
return this.mergeAnalyseResult(await this._runAnalysis());
return this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis()));
}
mergeAnalyseResult(newlyAnalysed) {
@@ -66,7 +81,7 @@ export class DatabaseAnalyser {
}
const res = {};
for (const field of ['tables', 'collections', 'views', 'matviews', 'functions', 'procedures', 'triggers']) {
for (const field of STRUCTURE_FIELDS) {
const removedIds = this.modifications
.filter(x => x.action == 'remove' && x.objectTypeField == field)
.map(x => x.objectId);
+140 -33
View File
@@ -15,12 +15,14 @@ import {
IndexInfo,
UniqueInfo,
CheckInfo,
AlterProcessor,
} from 'dbgate-types';
import _isString from 'lodash/isString';
import _isNumber from 'lodash/isNumber';
import _isDate from 'lodash/isDate';
import uuidv1 from 'uuid/v1';
export class SqlDumper {
export class SqlDumper implements AlterProcessor {
s = '';
driver: EngineDriver;
dialect: SqlDialect;
@@ -227,30 +229,25 @@ export class SqlDumper {
table.primaryKey.columns.map(x => x.columnName)
);
}
if (table.foreignKeys) {
table.foreignKeys.forEach(fk => {
this.put(',&n');
this.createForeignKeyFore(fk);
});
}
// foreach (var cnt in table.Uniques)
// {
// if (!first) this.put(", &n");
// first = false;
// CreateUniqueCore(cnt);
// }
// foreach (var cnt in table.Checks)
// {
// if (!first) this.put(", &n");
// first = false;
// CreateCheckCore(cnt);
// }
(table.foreignKeys || []).forEach(fk => {
this.put(',&n');
this.createForeignKeyFore(fk);
});
(table.uniques || []).forEach(uq => {
this.put(',&n');
this.createUniqueCore(uq);
});
(table.checks || []).forEach(chk => {
this.put(',&n');
this.createCheckCore(chk);
});
this.put('&<&n)');
this.endCommand();
// foreach (var ix in table.Indexes)
// {
// CreateIndex(ix);
// }
(table.indexes || []).forEach(ix => {
this.createIndex(ix);
});
}
createForeignKeyFore(fk: ForeignKeyInfo) {
@@ -349,14 +346,52 @@ export class SqlDumper {
changeTriggerSchema(obj: TriggerInfo, newSchema: string) {}
renameTrigger(obj: TriggerInfo, newSchema: string) {}
dropConstraint(cnt: ConstraintInfo) {
dropConstraintCore(cnt: ConstraintInfo) {
this.putCmd('^alter ^table %f ^drop ^constraint %i', cnt, cnt.constraintName);
}
dropConstraint(cnt: ConstraintInfo) {
switch (cnt.constraintType) {
case 'primaryKey':
this.dropPrimaryKey(cnt as PrimaryKeyInfo);
break;
case 'foreignKey':
this.dropForeignKey(cnt as ForeignKeyInfo);
break;
case 'unique':
this.dropUnique(cnt as UniqueInfo);
break;
case 'check':
this.dropCheck(cnt as CheckInfo);
break;
case 'index':
this.dropIndex(cnt as IndexInfo);
break;
}
}
createConstraint(cnt: ConstraintInfo) {
switch (cnt.constraintType) {
case 'primaryKey':
this.createPrimaryKey(cnt as PrimaryKeyInfo);
break;
case 'foreignKey':
this.createForeignKey(cnt as ForeignKeyInfo);
break;
case 'unique':
this.createUnique(cnt as UniqueInfo);
break;
case 'check':
this.createCheck(cnt as CheckInfo);
break;
case 'index':
this.createIndex(cnt as IndexInfo);
break;
}
}
dropForeignKey(fk: ForeignKeyInfo) {
if (this.dialect.explicitDropConstraint) {
this.putCmd('^alter ^table %f ^drop ^foreign ^key %i', fk, fk.constraintName);
} else {
this.dropConstraint(fk);
this.dropConstraintCore(fk);
}
}
createForeignKey(fk: ForeignKeyInfo) {
@@ -368,7 +403,7 @@ export class SqlDumper {
if (this.dialect.explicitDropConstraint) {
this.putCmd('^alter ^table %f ^drop ^primary ^key', pk);
} else {
this.dropConstraint(pk);
this.dropConstraintCore(pk);
}
}
createPrimaryKey(pk: PrimaryKeyInfo) {
@@ -380,11 +415,26 @@ export class SqlDumper {
);
}
dropIndex(ix: IndexInfo) {}
createIndex(ix: IndexInfo) {}
dropIndex(ix: IndexInfo) {
this.put('^drop ^index %i', ix.constraintName);
if (this.dialect.dropIndexContainsTableSpec) {
this.put(' ^on %f', ix);
}
this.endCommand();
}
createIndex(ix: IndexInfo) {
this.put('^create');
if (ix.isUnique) this.put(' ^unique');
this.put(' ^index %i &n^on %f (&>&n', ix.constraintName, ix);
this.putCollection(',&n', ix.columns, col => {
this.put('%i %k', col.columnName, col.isDescending == true ? 'DESC' : 'ASC');
});
this.put('&<&n)');
this.endCommand();
}
dropUnique(uq: UniqueInfo) {
this.dropConstraint(uq);
this.dropConstraintCore(uq);
}
createUniqueCore(uq: UniqueInfo) {
this.put(
@@ -401,7 +451,7 @@ export class SqlDumper {
}
dropCheck(ch: CheckInfo) {
this.dropConstraint(ch);
this.dropConstraintCore(ch);
}
createCheckCore(ch: CheckInfo) {
@@ -416,8 +466,8 @@ export class SqlDumper {
renameConstraint(constraint: ConstraintInfo, newName: string) {}
createColumn(table: TableInfo, column: ColumnInfo, constraints: ConstraintInfo[]) {
this.put('^alter ^table %f ^add %i ', table, column.columnName);
createColumn(column: ColumnInfo, constraints: ConstraintInfo[]) {
this.put('^alter ^table %f ^add %i ', column, column.columnName);
this.columnDefinition(column);
this.inlineConstraints(constraints);
this.endCommand();
@@ -443,7 +493,7 @@ export class SqlDumper {
changeColumn(oldcol: ColumnInfo, newcol: ColumnInfo, constraints: ConstraintInfo[]) {}
dropTable(obj: TableInfo, { testIfExists = false }) {
dropTable(obj: TableInfo, { testIfExists = false } = {}) {
this.putCmd('^drop ^table %f', obj);
}
@@ -469,4 +519,61 @@ export class SqlDumper {
truncateTable(name: NamedObjectInfo) {
this.putCmd('^delete ^from %f', name);
}
dropConstraints(table: TableInfo, dropReferences = false) {
if (dropReferences && this.dialect.dropForeignKey) {
table.dependencies.forEach(cnt => this.dropConstraint(cnt));
}
if (this.dialect.dropIndex) {
table.indexes.forEach(cnt => this.dropIndex(cnt));
}
if (this.dialect.dropForeignKey) {
table.foreignKeys.forEach(cnt => this.dropForeignKey(cnt));
}
if (this.dialect.dropPrimaryKey && table.primaryKey) {
this.dropPrimaryKey(table.primaryKey);
}
}
recreateTable(oldTable: TableInfo, newTable: TableInfo) {
if (oldTable.pairingId != newTable.pairingId) {
throw new Error('Recreate is not possible: oldTable.paringId != newTable.paringId');
}
const tmpTable = `temp_${uuidv1()}`;
const columnPairs = oldTable.columns
.map(oldcol => ({
oldcol,
newcol: newTable.columns.find(x => x.pairingId == oldcol.pairingId),
}))
.filter(x => x.newcol);
this.dropConstraints(oldTable, true);
this.renameTable(oldTable, tmpTable);
this.createTable(newTable);
const autoinc = newTable.columns.find(x => x.autoIncrement);
if (autoinc) {
this.allowIdentityInsert(newTable, true);
}
this.putCmd(
'^insert ^into %f (%,i) select %,s ^from %f',
newTable,
columnPairs.map(x => x.newcol.columnName),
columnPairs.map(x => x.oldcol.columnName),
{ ...oldTable, pureName: tmpTable }
);
if (autoinc) {
this.allowIdentityInsert(newTable, false);
}
if (this.dialect.dropForeignKey) {
newTable.dependencies.forEach(cnt => this.createConstraint(cnt));
}
this.dropTable({ ...oldTable, pureName: tmpTable });
}
}
+380
View File
@@ -0,0 +1,380 @@
import _ from 'lodash';
import {
AlterProcessor,
ColumnInfo,
ConstraintInfo,
DatabaseInfo,
NamedObjectInfo,
SqlDialect,
TableInfo,
} from '../../types';
import { DatabaseInfoAlterProcessor } from './database-info-alter-processor';
import { DatabaseAnalyser } from './DatabaseAnalyser';
interface AlterOperation_CreateTable {
operationType: 'createTable';
newObject: TableInfo;
}
interface AlterOperation_DropTable {
operationType: 'dropTable';
oldObject: TableInfo;
}
interface AlterOperation_RenameTable {
operationType: 'renameTable';
object: TableInfo;
newName: string;
}
interface AlterOperation_CreateColumn {
operationType: 'createColumn';
newObject: ColumnInfo;
}
interface AlterOperation_ChangeColumn {
operationType: 'changeColumn';
oldObject: ColumnInfo;
newObject: ColumnInfo;
}
interface AlterOperation_RenameColumn {
operationType: 'renameColumn';
object: ColumnInfo;
newName: string;
}
interface AlterOperation_DropColumn {
operationType: 'dropColumn';
oldObject: ColumnInfo;
}
interface AlterOperation_CreateConstraint {
operationType: 'createConstraint';
newObject: ConstraintInfo;
}
interface AlterOperation_ChangeConstraint {
operationType: 'changeConstraint';
oldObject: ConstraintInfo;
newObject: ConstraintInfo;
}
interface AlterOperation_DropConstraint {
operationType: 'dropConstraint';
oldObject: ConstraintInfo;
}
interface AlterOperation_RenameConstraint {
operationType: 'renameConstraint';
object: ConstraintInfo;
newName: string;
}
interface AlterOperation_RecreateTable {
operationType: 'recreateTable';
table: TableInfo;
operations: AlterOperation[];
}
type AlterOperation =
| AlterOperation_CreateColumn
| AlterOperation_ChangeColumn
| AlterOperation_DropColumn
| AlterOperation_CreateConstraint
| AlterOperation_ChangeConstraint
| AlterOperation_DropConstraint
| AlterOperation_CreateTable
| AlterOperation_DropTable
| AlterOperation_RenameTable
| AlterOperation_RenameColumn
| AlterOperation_RenameConstraint
| AlterOperation_RecreateTable;
export class AlterPlan {
public operations: AlterOperation[] = [];
constructor(public db: DatabaseInfo, public dialect: SqlDialect) {}
createTable(table: TableInfo) {
this.operations.push({
operationType: 'createTable',
newObject: table,
});
}
dropTable(table: TableInfo) {
this.operations.push({
operationType: 'dropTable',
oldObject: table,
});
}
createColumn(column: ColumnInfo) {
this.operations.push({
operationType: 'createColumn',
newObject: column,
});
}
changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo) {
this.operations.push({
operationType: 'changeColumn',
oldObject: oldColumn,
newObject: newColumn,
});
}
dropColumn(column: ColumnInfo) {
this.operations.push({
operationType: 'dropColumn',
oldObject: column,
});
}
createConstraint(constraint: ConstraintInfo) {
this.operations.push({
operationType: 'createConstraint',
newObject: constraint,
});
}
changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo) {
this.operations.push({
operationType: 'changeConstraint',
oldObject: oldConstraint,
newObject: newConstraint,
});
}
dropConstraint(constraint: ConstraintInfo) {
this.operations.push({
operationType: 'dropConstraint',
oldObject: constraint,
});
}
renameTable(table: TableInfo, newName: string) {
this.operations.push({
operationType: 'renameTable',
object: table,
newName,
});
}
renameColumn(column: ColumnInfo, newName: string) {
this.operations.push({
operationType: 'renameColumn',
object: column,
newName,
});
}
renameConstraint(constraint: ConstraintInfo, newName: string) {
this.operations.push({
operationType: 'renameConstraint',
object: constraint,
newName,
});
}
recreateTable(table: TableInfo, operations: AlterOperation[]) {
this.operations.push({
operationType: 'recreateTable',
table,
operations,
});
}
run(processor: AlterProcessor) {
for (const op of this.operations) {
runAlterOperation(op, processor);
}
}
_addLogicalDependencies(): AlterOperation[] {
const lists = this.operations.map(op => {
if (op.operationType == 'dropColumn') {
const table = this.db.tables.find(
x => x.pureName == op.oldObject.pureName && x.schemaName == op.oldObject.schemaName
);
const deletedFks = this.dialect.dropColumnDependencies?.includes('dependencies')
? table.dependencies.filter(fk => fk.columns.find(col => col.refColumnName == op.oldObject.columnName))
: [];
const deletedConstraints = _.compact([
this.dialect.dropColumnDependencies?.includes('primaryKey') ? table.primaryKey : null,
...(this.dialect.dropColumnDependencies?.includes('foreignKeys') ? table.foreignKeys : []),
...(this.dialect.dropColumnDependencies?.includes('indexes') ? table.indexes : []),
...(this.dialect.dropColumnDependencies?.includes('uniques') ? table.uniques : []),
]).filter(cnt => cnt.columns.find(col => col.columnName == op.oldObject.columnName));
// console.log('deletedConstraints', deletedConstraints);
const res: AlterOperation[] = [
...[...deletedFks, ...deletedConstraints].map(oldObject => {
const opRes: AlterOperation = {
operationType: 'dropConstraint',
oldObject,
};
return opRes;
}),
op,
];
return res;
}
return [op];
});
return _.flatten(lists);
}
_transformToImplementedOps(): AlterOperation[] {
const lists = this.operations.map(op => {
return (
this._testTableRecreate(op, 'createColumn', this.dialect.createColumn, 'newObject') ||
this._testTableRecreate(op, 'dropColumn', this.dialect.dropColumn, 'oldObject') ||
this._testTableRecreate(op, 'createConstraint', obj => this._canCreateConstraint(obj), 'newObject') ||
this._testTableRecreate(op, 'dropConstraint', obj => this._canDropConstraint(obj), 'oldObject') || [op]
);
});
return _.flatten(lists);
}
_canCreateConstraint(cnt: ConstraintInfo) {
if (cnt.constraintType == 'primaryKey') return this.dialect.createPrimaryKey;
if (cnt.constraintType == 'foreignKey') return this.dialect.createForeignKey;
if (cnt.constraintType == 'index') return this.dialect.createIndex;
if (cnt.constraintType == 'unique') return this.dialect.createUnique;
if (cnt.constraintType == 'check') return this.dialect.createCheck;
return null;
}
_canDropConstraint(cnt: ConstraintInfo) {
if (cnt.constraintType == 'primaryKey') return this.dialect.dropPrimaryKey;
if (cnt.constraintType == 'foreignKey') return this.dialect.dropForeignKey;
if (cnt.constraintType == 'index') return this.dialect.dropIndex;
if (cnt.constraintType == 'unique') return this.dialect.dropUnique;
if (cnt.constraintType == 'check') return this.dialect.dropCheck;
return null;
}
_testTableRecreate(
op: AlterOperation,
operationType: string,
isAllowed: boolean | Function,
objectField: string
): AlterOperation[] | null {
if (op.operationType == operationType) {
if (_.isFunction(isAllowed)) {
if (isAllowed(op[objectField])) return null;
} else {
if (isAllowed) return null;
}
// console.log('*****************RECREATED NEEDED', op, operationType, isAllowed);
// console.log(this.dialect);
const table = this.db.tables.find(
x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName
);
return [
{
operationType: 'recreateTable',
table,
operations: [op],
},
];
}
return null;
}
_groupTableRecreations(): AlterOperation[] {
const res = [];
const recreates = {};
for (const op of this.operations) {
if (op.operationType == 'recreateTable') {
const recreate = {
...op,
operations: [...op.operations],
};
res.push(recreate);
recreates[`${op.table.schemaName}||${op.table.pureName}`] = recreate;
} else {
// @ts-ignore
const oldObject: TableInfo = op.oldObject;
if (oldObject) {
const recreated = recreates[`${oldObject.schemaName}||${oldObject.pureName}`];
if (recreated) {
recreated.operations.push(op);
continue;
}
}
res.push(op);
}
}
return res;
}
transformPlan() {
// console.log('*****************OPERATIONS0', this.operations);
this.operations = this._addLogicalDependencies();
// console.log('*****************OPERATIONS1', this.operations);
this.operations = this._transformToImplementedOps();
// console.log('*****************OPERATIONS2', this.operations);
this.operations = this._groupTableRecreations();
// console.log('*****************OPERATIONS3', this.operations);
}
}
export function runAlterOperation(op: AlterOperation, processor: AlterProcessor) {
switch (op.operationType) {
case 'createTable':
processor.createTable(op.newObject);
break;
case 'changeColumn':
processor.changeColumn(op.oldObject, op.newObject);
break;
case 'createColumn':
processor.createColumn(op.newObject, []);
break;
case 'dropColumn':
processor.dropColumn(op.oldObject);
break;
case 'changeConstraint':
processor.changeConstraint(op.oldObject, op.newObject);
break;
case 'createConstraint':
processor.createConstraint(op.newObject);
break;
case 'dropConstraint':
processor.dropConstraint(op.oldObject);
break;
case 'renameColumn':
processor.renameColumn(op.object, op.newName);
break;
case 'renameTable':
processor.renameTable(op.object, op.newName);
break;
case 'renameConstraint':
processor.renameConstraint(op.object, op.newName);
break;
case 'recreateTable':
{
const newTable = _.cloneDeep(op.table);
const newDb = DatabaseAnalyser.createEmptyStructure();
newDb.tables.push(newTable);
// console.log('////////////////////////////newTable1', newTable);
op.operations.forEach(child => runAlterOperation(child, new DatabaseInfoAlterProcessor(newDb)));
// console.log('////////////////////////////op.operations', op.operations);
// console.log('////////////////////////////op.table', op.table);
// console.log('////////////////////////////newTable2', newTable);
processor.recreateTable(op.table, newTable);
}
break;
}
}
@@ -0,0 +1,101 @@
import {
ColumnInfo,
ConstraintInfo,
DatabaseInfo,
ForeignKeyInfo,
PrimaryKeyInfo,
TableInfo,
IndexInfo,
CheckInfo,
UniqueInfo,
} from '../../types';
export class DatabaseInfoAlterProcessor {
constructor(public db: DatabaseInfo) {}
createTable(table: TableInfo) {
this.db.tables.push(table);
}
dropTable(table: TableInfo) {
this.db.tables = this.db.tables.filter(x => x.pureName != table.pureName && x.schemaName != table.schemaName);
}
createColumn(column: ColumnInfo) {
const table = this.db.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName);
table.columns.push(column);
}
changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo) {
const table = this.db.tables.find(x => x.pureName == oldColumn.pureName && x.schemaName == oldColumn.schemaName);
table.columns = table.columns.map(x => (x.columnName == oldColumn.columnName ? newColumn : x));
}
dropColumn(column: ColumnInfo) {
const table = this.db.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName);
table.columns = table.columns.filter(x => x.columnName != column.columnName);
}
createConstraint(constraint: ConstraintInfo) {
const table = this.db.tables.find(x => x.pureName == constraint.pureName && x.schemaName == constraint.schemaName);
switch (constraint.constraintType) {
case 'primaryKey':
table.primaryKey = constraint as PrimaryKeyInfo;
break;
case 'foreignKey':
table.foreignKeys.push(constraint as ForeignKeyInfo);
break;
case 'index':
table.indexes.push(constraint as IndexInfo);
break;
case 'unique':
table.uniques.push(constraint as UniqueInfo);
break;
case 'check':
table.checks.push(constraint as CheckInfo);
break;
}
}
changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo) {
const table = this.db.tables.find(
x => x.pureName == oldConstraint.pureName && x.schemaName == oldConstraint.schemaName
);
}
dropConstraint(constraint: ConstraintInfo) {
const table = this.db.tables.find(x => x.pureName == constraint.pureName && x.schemaName == constraint.schemaName);
switch (constraint.constraintType) {
case 'primaryKey':
table.primaryKey = null;
break;
case 'foreignKey':
table.foreignKeys = table.foreignKeys.filter(x => x.constraintName != constraint.constraintName);
break;
case 'index':
table.indexes = table.indexes.filter(x => x.constraintName != constraint.constraintName);
break;
case 'unique':
table.uniques = table.uniques.filter(x => x.constraintName != constraint.constraintName);
break;
case 'check':
table.checks = table.checks.filter(x => x.constraintName != constraint.constraintName);
break;
}
}
renameTable(table: TableInfo, newName: string) {
this.db.tables.find(x => x.pureName == table.pureName && x.schemaName == table.schemaName).pureName = newName;
}
renameColumn(column: ColumnInfo, newName: string) {
const table = this.db.tables.find(x => x.pureName == column.pureName && x.schemaName == column.schemaName);
table.columns.find(x => x.columnName == column.columnName).columnName = newName;
}
renameConstraint(constraint: ConstraintInfo, newName: string) {}
recreateTable(oldTable: TableInfo, newTable: TableInfo) {
throw new Error('recreateTable not implemented for DatabaseInfoAlterProcessor');
}
}
+299
View File
@@ -0,0 +1,299 @@
import { ColumnInfo, DatabaseInfo, EngineDriver, NamedObjectInfo, TableInfo } from 'dbgate-types';
import uuidv1 from 'uuid/v1';
import { AlterPlan } from './alterPlan';
type DbDiffSchemaMode = 'strict' | 'ignore' | 'ignoreImplicit';
export interface DbDiffOptions {
allowRecreateTable?: boolean;
allowRecreateConstraint?: boolean;
allowRecreateSpecificObject?: boolean;
allowPairRenamedTables?: boolean;
ignoreCase?: boolean;
schemaMode?: DbDiffSchemaMode;
leftImplicitSchema?: string;
rightImplicitSchema?: string;
}
export function generateTablePairingId(table: TableInfo): TableInfo {
if (!table) return table;
if (!table.pairingId) {
return {
...table,
columns: table.columns.map(col => ({
...col,
pairingId: col.pairingId || uuidv1(),
})),
foreignKeys: table.foreignKeys.map(cnt => ({
...cnt,
pairingId: cnt.pairingId || uuidv1(),
})),
checks: table.checks.map(cnt => ({
...cnt,
pairingId: cnt.pairingId || uuidv1(),
})),
indexes: table.indexes.map(cnt => ({
...cnt,
pairingId: cnt.pairingId || uuidv1(),
})),
pairingId: table.pairingId || uuidv1(),
};
}
return table;
}
export function generateDbPairingId(db: DatabaseInfo): DatabaseInfo {
if (!db) return db;
return {
...db,
tables: (db.tables || []).map(generateTablePairingId),
};
}
function testEqualNames(a: string, b: string, opts: DbDiffOptions) {
if (opts.ignoreCase) return a.toLowerCase() == b.toLowerCase();
return a == b;
}
function testEqualSchemas(lschema: string, rschema: string, opts: DbDiffOptions) {
if (opts.schemaMode == 'ignore') lschema = null;
if (opts.schemaMode == 'ignoreImplicit' && lschema == opts.leftImplicitSchema) lschema = null;
if (opts.schemaMode == 'ignore') rschema = null;
if (opts.schemaMode == 'ignoreImplicit' && rschema == opts.rightImplicitSchema) rschema = null;
return testEqualNames(lschema, rschema, opts);
}
function testEqualFullNames(lft: NamedObjectInfo, rgt: NamedObjectInfo, opts: DbDiffOptions) {
if (lft == null || rgt == null) return lft == rgt;
return testEqualSchemas(lft.schemaName, rgt.schemaName, opts) && testEqualNames(lft.pureName, rgt.pureName, opts);
}
export function testEqualColumns(
a: ColumnInfo,
b: ColumnInfo,
checkName: boolean,
checkDefault: boolean,
opts: DbDiffOptions = {}
) {
if (checkName && !testEqualNames(a.columnName, b.columnName, opts)) {
// opts.DiffLogger.Trace("Column, different name: {0}; {1}", a, b);
return false;
}
//if (!DbDiffTool.EqualFullNames(a.Domain, b.Domain, opts))
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different domain: {2}; {3}", a, b, a.Domain, b.Domain);
// return false;
//}
if (a.computedExpression != b.computedExpression) {
// opts.DiffLogger.Trace(
// 'Column {0}, {1}: different computed expression: {2}; {3}',
// a,
// b,
// a.ComputedExpression,
// b.ComputedExpression
// );
return false;
}
if (a.computedExpression != null) {
return true;
}
if (checkDefault) {
if (a.defaultValue == null) {
if (a.defaultValue != b.defaultValue) {
// opts.DiffLogger.Trace(
// 'Column {0}, {1}: different default values: {2}; {3}',
// a,
// b,
// a.DefaultValue,
// b.DefaultValue
// );
return false;
}
} else {
if (a.defaultValue != b.defaultValue) {
// opts.DiffLogger.Trace(
// 'Column {0}, {1}: different default values: {2}; {3}',
// a,
// b,
// a.DefaultValue,
// b.DefaultValue
// );
return false;
}
}
if (a.defaultConstraint != b.defaultConstraint) {
// opts.DiffLogger.Trace(
// 'Column {0}, {1}: different default constraint names: {2}; {3}',
// a,
// b,
// a.DefaultConstraint,
// b.DefaultConstraint
// );
return false;
}
}
if (a.notNull != b.notNull) {
// opts.DiffLogger.Trace('Column {0}, {1}: different nullable: {2}; {3}', a, b, a.NotNull, b.NotNull);
return false;
}
if (a.autoIncrement != b.autoIncrement) {
// opts.DiffLogger.Trace('Column {0}, {1}: different autoincrement: {2}; {3}', a, b, a.AutoIncrement, b.AutoIncrement);
return false;
}
if (a.isSparse != b.isSparse) {
// opts.DiffLogger.Trace('Column {0}, {1}: different is_sparse: {2}; {3}', a, b, a.IsSparse, b.IsSparse);
return false;
}
if (!testEqualTypes(a, b, opts)) {
return false;
}
//var btype = b.DataType;
//var atype = a.DataType;
//if (pairing != null && pairing.Target != null && pairing.Source.Dialect != null)
//{
// btype = pairing.Source.Dialect.MigrateDataType(b, btype, pairing.Source.Dialect.GetDefaultMigrationProfile(), null);
// btype = pairing.Source.Dialect.GenericTypeToSpecific(btype).ToGenericType();
// // normalize type
// atype = pairing.Source.Dialect.GenericTypeToSpecific(atype).ToGenericType();
//}
//if (!EqualTypes(atype, btype, opts))
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different types: {2}; {3}", a, b, a.DataType, b.DataType);
// return false;
//}
//if (!opts.IgnoreColumnCollation && a.Collation != b.Collation)
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different collations: {2}; {3}", a, b, a.Collation, b.Collation);
// return false;
//}
//if (!opts.IgnoreColumnCharacterSet && a.CharacterSet != b.CharacterSet)
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different character sets: {2}; {3}", a, b, a.CharacterSet, b.CharacterSet);
// return false;
//}
return true;
}
export function testEqualTypes(a: ColumnInfo, b: ColumnInfo, opts: DbDiffOptions = {}) {
if (a.dataType != b.dataType) {
// opts.DiffLogger.Trace("Column {0}, {1}: different types: {2}; {3}", a, b, a.DataType, b.DataType);
return false;
}
//if (a.Length != b.Length)
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different lengths: {2}; {3}", a, b, a.Length, b.Length);
// return false;
//}
//if (a.Precision != b.Precision)
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different lengths: {2}; {3}", a, b, a.Precision, b.Precision);
// return false;
//}
//if (a.Scale != b.Scale)
//{
// opts.DiffLogger.Trace("Column {0}, {1}: different scale: {2}; {3}", a, b, a.Scale, b.Scale);
// return false;
//}
return true;
}
function getTableConstraints(table: TableInfo) {
const res = [];
if (table.primaryKey) res.push(table.primaryKey);
if (table.foreignKeys) res.push(...table.foreignKeys);
if (table.indexes) res.push(...table.indexes);
if (table.checks) res.push(...table.checks);
return res;
}
function createPairs(oldList, newList, additionalCondition = null) {
const res = [];
for (const a of oldList) {
const b = newList.find(x => x.pairingId == a.pairingId || (additionalCondition && additionalCondition(a, x)));
if (b) {
res.push([a, b]);
} else {
res.push([a, null]);
}
}
for (const b of newList) {
if (!res.find(x => x[1] == b)) {
res.push([null, b]);
}
}
return res;
}
function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInfo, opts: DbDiffOptions) {
// if (oldTable.primaryKey)
const columnPairs = createPairs(oldTable.columns, newTable.columns);
const constraintPairs = createPairs(
getTableConstraints(oldTable),
getTableConstraints(newTable),
(a, b) => a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey'
);
constraintPairs.filter(x => x[1] == null).forEach(x => plan.dropConstraint(x[0]));
columnPairs.filter(x => x[1] == null).forEach(x => plan.dropColumn(x[0]));
if (!testEqualFullNames(oldTable, newTable, opts)) {
plan.renameTable(oldTable, newTable.pureName);
}
columnPairs.filter(x => x[0] == null).forEach(x => plan.createColumn(x[1]));
columnPairs
.filter(x => x[0] && x[1])
.forEach(x => {
if (!testEqualColumns(x[0], x[1], true, true, opts)) {
if (testEqualColumns(x[0], x[1], false, true, opts)) {
// console.log('PLAN RENAME COLUMN')
plan.renameColumn(x[0], x[1].columnName);
} else {
// console.log('PLAN CHANGE COLUMN')
plan.changeColumn(x[0], x[1]);
}
}
});
constraintPairs.filter(x => x[0] == null).forEach(x => plan.createConstraint(x[1]));
}
export function createAlterTablePlan(
oldTable: TableInfo,
newTable: TableInfo,
opts: DbDiffOptions,
db: DatabaseInfo,
driver: EngineDriver
): AlterPlan {
const plan = new AlterPlan(db, driver.dialect);
if (oldTable == null) {
plan.createTable(newTable);
} else {
planAlterTable(plan, oldTable, newTable, opts);
}
plan.transformPlan();
return plan;
}
export function getAlterTableScript(
oldTable: TableInfo,
newTable: TableInfo,
opts: DbDiffOptions,
db: DatabaseInfo,
driver: EngineDriver
): string {
const plan = createAlterTablePlan(oldTable, newTable, opts, db, driver);
const dmp = driver.createDumper();
plan.run(dmp);
return dmp.s;
}
+2
View File
@@ -11,3 +11,5 @@ export * from './SqlGenerator';
export * from './structureTools';
export * from './settingsExtractors';
export * from './filterName';
export * from './diffTools';
export * from './schemaEditorTools';
+7
View File
@@ -21,6 +21,13 @@ export function fullNameToString({ schemaName, pureName }) {
return pureName;
}
export function fullNameToLabel({ schemaName, pureName }) {
if (schemaName) {
return `${schemaName}.${pureName}`;
}
return pureName;
}
export function quoteFullName(dialect, { schemaName, pureName }) {
if (schemaName) return `${dialect.quoteIdentifier(schemaName)}.${dialect.quoteIdentifier(pureName)}`;
return `${dialect.quoteIdentifier(pureName)}`;
+158
View File
@@ -0,0 +1,158 @@
import uuidv1 from 'uuid/v1';
import _omit from 'lodash/omit';
import { ColumnInfo, ConstraintInfo, ForeignKeyInfo, PrimaryKeyInfo, TableInfo } from 'dbgate-types';
export interface EditorColumnInfo extends ColumnInfo {
isPrimaryKey?: boolean;
}
export function fillEditorColumnInfo(column: ColumnInfo, table: TableInfo): EditorColumnInfo {
return {
isPrimaryKey: !!(table.primaryKey && table.primaryKey.columns.find(x => x.columnName == column.columnName)),
...column,
};
}
function processPrimaryKey(table: TableInfo, oldColumn: EditorColumnInfo, newColumn: EditorColumnInfo): TableInfo {
if (!oldColumn?.isPrimaryKey && newColumn?.isPrimaryKey) {
let primaryKey = table.primaryKey;
if (!primaryKey) {
primaryKey = {
constraintType: 'primaryKey',
pureName: table.pureName,
schemaName: table.schemaName,
columns: [],
};
}
return {
...table,
primaryKey: {
...primaryKey,
columns: [
...primaryKey.columns,
{
columnName: newColumn.columnName,
},
],
},
};
}
if (oldColumn?.isPrimaryKey && !newColumn?.isPrimaryKey) {
let primaryKey = table.primaryKey;
if (primaryKey) {
primaryKey = {
...primaryKey,
columns: table.primaryKey.columns.filter(x => x.columnName != oldColumn.columnName),
};
if (primaryKey.columns.length == 0) {
return {
...table,
primaryKey: null,
};
}
return {
...table,
primaryKey,
};
}
}
return table;
}
export function editorAddColumn(table: TableInfo, column: EditorColumnInfo): TableInfo {
let res = {
...table,
columns: [...table.columns, { ...column, pairingId: uuidv1() }],
};
res = processPrimaryKey(res, null, column);
return res;
}
export function editorModifyColumn(table: TableInfo, column: EditorColumnInfo): TableInfo {
const oldColumn = table?.columns?.find(x => x.pairingId == column.pairingId);
let res = {
...table,
columns: table.columns.map(col => (col.pairingId == column.pairingId ? _omit(column, ['isPrimaryKey']) : col)),
};
res = processPrimaryKey(res, fillEditorColumnInfo(oldColumn, table), column);
return res;
}
export function editorDeleteColumn(table: TableInfo, column: EditorColumnInfo): TableInfo {
let res = {
...table,
columns: table.columns.filter(col => col.pairingId != column.pairingId),
};
res = processPrimaryKey(res, column, null);
return res;
}
export function editorAddConstraint(table: TableInfo, constraint: ConstraintInfo): TableInfo {
const res = {
...table,
};
if (constraint.constraintType == 'primaryKey') {
res.primaryKey = {
pairingId: uuidv1(),
...constraint,
} as PrimaryKeyInfo;
}
if (constraint.constraintType == 'foreignKey') {
res.foreignKeys = [
...(res.foreignKeys || []),
{
pairingId: uuidv1(),
...constraint,
} as ForeignKeyInfo,
];
}
return res;
}
export function editorModifyConstraint(table: TableInfo, constraint: ConstraintInfo): TableInfo {
const res = {
...table,
};
if (constraint.constraintType == 'primaryKey') {
res.primaryKey = {
...res.primaryKey,
...constraint,
};
}
if (constraint.constraintType == 'foreignKey') {
res.foreignKeys = table.foreignKeys.map(fk =>
fk.pairingId == constraint.pairingId ? { ...fk, ...constraint } : fk
);
}
return res;
}
export function editorDeleteConstraint(table: TableInfo, constraint: ConstraintInfo): TableInfo {
const res = {
...table,
};
if (constraint.constraintType == 'primaryKey') {
res.primaryKey = null;
}
if (constraint.constraintType == 'foreignKey') {
res.foreignKeys = table.foreignKeys.filter(x => x.pairingId != constraint.pairingId);
}
return res;
}
+16
View File
@@ -0,0 +1,16 @@
import { ColumnInfo, ConstraintInfo, TableInfo } from './dbinfo';
export interface AlterProcessor {
createTable(table: TableInfo);
dropTable(table: TableInfo);
createColumn(column: ColumnInfo, constraints: ConstraintInfo[]);
changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo, constraints?: ConstraintInfo[]);
dropColumn(column: ColumnInfo);
createConstraint(constraint: ConstraintInfo);
changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo);
dropConstraint(constraint: ConstraintInfo);
renameTable(table: TableInfo, newName: string);
renameColumn(column: ColumnInfo, newName: string);
renameConstraint(constraint: ConstraintInfo, newName: string);
recreateTable(oldTable: TableInfo, newTable: TableInfo);
}
+7 -1
View File
@@ -6,10 +6,13 @@ export interface NamedObjectInfo {
export interface ColumnReference {
columnName: string;
refColumnName?: string;
isIncludedColumn?: boolean;
isDescending?: boolean;
}
export interface ConstraintInfo extends NamedObjectInfo {
constraintName: string;
pairingId?: string;
constraintName?: string;
constraintType: 'primaryKey' | 'foreignKey' | 'index' | 'check' | 'unique';
}
@@ -38,6 +41,7 @@ export interface CheckInfo extends ConstraintInfo {
}
export interface ColumnInfo extends NamedObjectInfo {
pairingId?: string;
columnName: string;
notNull: boolean;
autoIncrement: boolean;
@@ -53,6 +57,7 @@ export interface ColumnInfo extends NamedObjectInfo {
}
export interface DatabaseObjectInfo extends NamedObjectInfo {
pairingId?: string;
objectId?: string;
createDate?: string;
modifyDate?: string;
@@ -103,4 +108,5 @@ export interface DatabaseInfoObjects {
export interface DatabaseInfo extends DatabaseInfoObjects {
schemas: SchemaInfo[];
engine?: string;
}
+19
View File
@@ -1,6 +1,7 @@
export interface SqlDialect {
rangeSelect?: boolean;
limitSelect?: boolean;
ilike?: boolean;
rowNumberOverPaging?: boolean;
stringEscapeChar: string;
offsetFetchRangeSyntax?: boolean;
@@ -10,4 +11,22 @@ export interface SqlDialect {
anonymousPrimaryKey?: boolean;
enableConstraintsPerTable?: boolean;
nosql?: boolean; // mongo
dropColumnDependencies?: string[];
changeColumnDependencies?: string[];
dropIndexContainsTableSpec?: boolean;
createColumn?: boolean;
dropColumn?: boolean;
createIndex?: boolean;
dropIndex?: boolean;
createForeignKey?: boolean;
dropForeignKey?: boolean;
createPrimaryKey?: boolean;
dropPrimaryKey?: boolean;
createUnique?: boolean;
dropUnique?: boolean;
createCheck?: boolean;
dropCheck?: boolean;
}
+1
View File
@@ -42,3 +42,4 @@ export * from './dialect';
export * from './dumper';
export * from './dbtypes';
export * from './extensions';
export * from './alter-processor';
+16
View File
@@ -55,6 +55,10 @@ body {
position: relative;
}
.col-10 {
flex-basis: 83.3333%;
max-width: 83.3333%;
}
.col-9 {
flex-basis: 75%;
max-width: 75%;
@@ -63,10 +67,18 @@ body {
flex-basis: 66.6667%;
max-width: 66.6667%;
}
.col-7 {
flex-basis: 58.3333%;
max-width: 58.3333%;
}
.col-6 {
flex-basis: 50%;
max-width: 50%;
}
.col-5 {
flex-basis: 41.6667%;
max-width: 41.6667%;
}
.col-4 {
flex-basis: 33.3333%;
max-width: 33.3333%;
@@ -75,6 +87,10 @@ body {
flex-basis: 25%;
max-width: 25%;
}
.col-2 {
flex-basis: 16.6666%;
max-width: 16.6666%;
}
.largeFormMarker input[type='text'] {
width: 100%;
@@ -38,6 +38,14 @@
}
}
function handleMouseUp(e) {
if (e.button == 1) {
dispatch('middleclick');
e.preventDefault();
e.stopPropagation();
}
}
function setChecked(value) {
if (!value && isChecked) {
checkedObjectsStore.update(x => x.filter(y => module.extractKey(data) != module.extractKey(y)));
@@ -53,6 +61,7 @@
class:isBold
draggable={true}
on:click={handleClick}
on:mouseup={handleMouseUp}
use:contextMenu={disableContextMenu ? null : menu}
on:dragstart={e => {
e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data));
@@ -52,7 +52,7 @@
</div>
{/if}
{#each filtered as item (module.extractKey(item.data))}
{#each filtered as item}
<AppObjectListItem
{...$$restProps}
{module}
+2 -2
View File
@@ -38,7 +38,7 @@
</script>
{#if groupFunc}
{#each _.keys(groups) as group (group)}
{#each _.keys(groups) as group}
<AppObjectGroup
{group}
{module}
@@ -52,7 +52,7 @@
/>
{/each}
{:else}
{#each filtered as data (module.extractKey(data))}
{#each filtered as data}
<AppObjectListItem
{module}
{subItemsComponent}
@@ -123,7 +123,7 @@
text: 'Connect',
onClick: handleConnect,
},
{ onClick: handleNewQuery, text: 'New query' },
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
$openedConnections.includes(data._id) &&
data.status && {
text: 'Refresh',
@@ -190,4 +190,9 @@
on:click={handleConnect}
on:click
on:expand
on:middleclick={() => {
_.flattenDeep(getContextMenu())
.find(x => x.isNewQuery)
.onClick();
}}
/>
@@ -54,7 +54,7 @@
};
return [
{ onClick: handleNewQuery, text: 'New query' },
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
{ onClick: handleImport, text: 'Import' },
{ onClick: handleExport, text: 'Export' },
{ onClick: handleSqlGenerator, text: 'SQL Generator' },
@@ -68,7 +68,7 @@
<script lang="ts">
import getConnectionLabel from '../utility/getConnectionLabel';
import _ from 'lodash';
import _, { find } from 'lodash';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
@@ -93,5 +93,10 @@
isBold={_.get($currentDatabase, 'connection._id') == _.get(data.connection, '_id') &&
_.get($currentDatabase, 'name') == data.name}
on:click={() => ($currentDatabase = data)}
on:middleclick={() => {
createMenu()
.find(x => x.isNewQuery)
.onClick();
}}
menu={createMenu}
/>
@@ -328,7 +328,6 @@
{ forceNewTab }
);
}
</script>
<script lang="ts">
@@ -351,7 +350,7 @@
export let data;
function handleClick() {
function handleClick(forceNewTab = false) {
const { schemaName, pureName, conid, database, objectTypeField } = data;
openDatabaseObjectDetail(
@@ -364,7 +363,7 @@
database,
objectTypeField,
},
false,
forceNewTab,
null
);
@@ -508,7 +507,6 @@
};
});
}
</script>
<AppObjectCore
@@ -518,6 +516,7 @@
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
icon={icons[data.objectTypeField]}
menu={createMenu}
on:click={handleClick}
on:click={() => handleClick()}
on:middleclick={() => handleClick(true)}
on:expand
/>
@@ -51,11 +51,11 @@
}
:global(.theme-type-dark) .inner {
--json-tree-string-color: #efc5c5;
--json-tree-symbol-color: #efc5c5;
--json-tree-boolean-color: #a6b3f5;
--json-tree-function-color: #a6b3f5;
--json-tree-number-color: #bfbdf2;
--json-tree-string-color: #ffc5c5;
--json-tree-symbol-color: #ffc5c5;
--json-tree-boolean-color: #b6c3ff;
--json-tree-function-color: #b6c3ff;
--json-tree-number-color: #bfbdff;
--json-tree-label-color: #e9aaed;
--json-tree-arrow-color: #d4d4d4;
--json-tree-null-color: #dcdcdc;
+33 -1
View File
@@ -1,4 +1,4 @@
import { currentTheme, extensions, getVisibleToolbar, visibleToolbar } from '../stores';
import { currentDatabase, currentTheme, extensions, getVisibleToolbar, visibleToolbar } from '../stores';
import registerCommand from './registerCommand';
import { derived, get } from 'svelte/store';
import { ThemeDefinition } from 'dbgate-types';
@@ -17,6 +17,7 @@ import { getDefaultFileFormat } from '../plugins/fileformats';
import { getCurrentConfig, getCurrentDatabase } from '../stores';
import './recentDatabaseSwitch';
import hasPermission from '../utility/hasPermission';
import _ from 'lodash';
const electron = getElectron();
@@ -104,6 +105,37 @@ registerCommand({
},
});
registerCommand({
id: 'new.table',
category: 'New',
icon: 'img table',
name: 'Table',
toolbar: true,
toolbarName: 'New table',
onClick: () => {
const $currentDatabase = get(currentDatabase);
const connection = _.get($currentDatabase, 'connection') || {};
const database = _.get($currentDatabase, 'name');
openNewTab(
{
title: 'Table #',
icon: 'img table',
tabComponent: 'TableStructureTab',
props: {
conid: connection._id,
database,
},
},
{
editor: {
columns: [],
},
}
);
},
});
registerCommand({
id: 'new.markdown',
category: 'New',
@@ -6,6 +6,7 @@
import ColumnLabel from '../elements/ColumnLabel.svelte';
import { isTypeDateTime } from 'dbgate-tools';
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
import { copyTextToClipboard } from '../utility/clipboard';
export let column;
export let conid = undefined;
@@ -27,19 +28,20 @@
function getMenu() {
return [
{ onClick: () => setSort('ASC'), text: 'Sort ascending' },
{ onClick: () => setSort('DESC'), text: 'Sort descending' },
setSort && { onClick: () => setSort('ASC'), text: 'Sort ascending' },
setSort && { onClick: () => setSort('DESC'), text: 'Sort descending' },
{ onClick: () => copyTextToClipboard(column.columnName), text: 'Copy column name' },
column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }],
{ divider: true },
{ onClick: () => setGrouping('GROUP'), text: 'Group by' },
{ onClick: () => setGrouping('MAX'), text: 'MAX' },
{ onClick: () => setGrouping('MIN'), text: 'MIN' },
{ onClick: () => setGrouping('SUM'), text: 'SUM' },
{ onClick: () => setGrouping('AVG'), text: 'AVG' },
{ onClick: () => setGrouping('COUNT'), text: 'COUNT' },
{ onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
setGrouping && { divider: true },
setGrouping && { onClick: () => setGrouping('GROUP'), text: 'Group by' },
setGrouping && { onClick: () => setGrouping('MAX'), text: 'MAX' },
setGrouping && { onClick: () => setGrouping('MIN'), text: 'MIN' },
setGrouping && { onClick: () => setGrouping('SUM'), text: 'SUM' },
setGrouping && { onClick: () => setGrouping('AVG'), text: 'AVG' },
setGrouping && { onClick: () => setGrouping('COUNT'), text: 'COUNT' },
setGrouping && { onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
isTypeDateTime(column.dataType) && [
{ divider: true },
@@ -66,8 +66,6 @@
import _ from 'lodash';
import registerCommand from '../commands/registerCommand';
import { registerMenu } from '../utility/contextMenu';
import { useSettings } from '../utility/metadataLoaders';
import { getCurrentSettings } from '../stores';
import { getBoolSettingsValue } from '../settings/settingsTools';
export let config;
+27 -26
View File
@@ -12,14 +12,31 @@
return value;
}
const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/;
// const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/;
const dateTimeRegex = /^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|()|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
function formatNumber(value) {
if (value >= 10000 || value <= -10000) {
if (getBoolSettingsValue('dataGrid.thousandsSeparator', false)) {
return value.toLocaleString();
} else {
return value.toString();
}
}
return value.toString();
}
function formatDateTime(testedString) {
const m = testedString.match(dateTimeRegex);
return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}:${m[6]}`;
}
</script>
<script lang="ts">
import moment from 'moment';
import _ from 'lodash';
import { isTypeLogical } from 'dbgate-tools';
import ShowFormButton from '../formview/ShowFormButton.svelte';
import { getBoolSettingsValue } from '../settings/settingsTools';
export let rowIndex;
export let col;
@@ -66,34 +83,18 @@
{:else if value === undefined}
<span class="null">(No field)</span>
{:else if _.isDate(value)}
{moment(value).format('YYYY-MM-DD HH:mm:ss')}
{value.toString()}
{:else if value === true}
{#if isDynamicStructure}
<span class="value">true</span>
{:else}
1
{/if}
<span class="value">true</span>
{:else if value === false}
{#if isDynamicStructure}
<span class="value">false</span>
{:else}
0
{/if}
<span class="value">false</span>
{:else if _.isNumber(value)}
{#if value >= 10000 || value <= -10000}
{#if isDynamicStructure}
<span class="value">{value.toLocaleString()}</span>
{:else}
{value.toLocaleString()}
{/if}
{:else if isDynamicStructure}
<span class="value">{value.toString()}</span>
{:else}
{value.toString()}
{/if}
<span class="value">{formatNumber(value)}</span>
{:else if _.isString(value)}
{#if dateTimeRegex.test(value)}
{moment(value).format('YYYY-MM-DD HH:mm:ss')}
<span class="value">
{formatDateTime(value)}
</span>
{:else}
{highlightSpecialCharacters(value)}
{/if}
+54 -42
View File
@@ -54,7 +54,7 @@
name: 'Set NULL',
keyText: 'Ctrl+0',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
onClick: () => getCurrentDataGrid().setNull(),
onClick: () => getCurrentDataGrid().setFixedValue(null),
});
registerCommand({
@@ -171,7 +171,6 @@
if (_.isPlainObject(value) || _.isArray(value)) return JSON.stringify(value);
return value;
}
</script>
<script lang="ts">
@@ -310,10 +309,10 @@
}
}
export function setNull() {
export function setFixedValue(value) {
grider.beginUpdate();
selectedCells.filter(isRegularCell).forEach(cell => {
setCellValue(cell, null);
setCellValue(cell, value);
});
grider.endUpdate();
}
@@ -666,43 +665,52 @@
function handleGridWheel(event) {
if (event.shiftKey) {
let newFirstVisibleColumnScrollIndex = firstVisibleColumnScrollIndex;
if (event.deltaY > 0) {
newFirstVisibleColumnScrollIndex++;
}
if (event.deltaY < 0) {
newFirstVisibleColumnScrollIndex--;
}
if (newFirstVisibleColumnScrollIndex > maxScrollColumn) {
newFirstVisibleColumnScrollIndex = maxScrollColumn;
}
if (newFirstVisibleColumnScrollIndex < 0) {
newFirstVisibleColumnScrollIndex = 0;
}
firstVisibleColumnScrollIndex = newFirstVisibleColumnScrollIndex;
domHorizontalScroll.scroll(newFirstVisibleColumnScrollIndex);
scrollHorizontal(event.deltaY, event.deltaX);
} else {
let newFirstVisibleRowScrollIndex = firstVisibleRowScrollIndex;
if (event.deltaY > 0) {
newFirstVisibleRowScrollIndex += wheelRowCount;
}
if (event.deltaY < 0) {
newFirstVisibleRowScrollIndex -= wheelRowCount;
}
let rowCount = grider.rowCount;
if (newFirstVisibleRowScrollIndex + visibleRowCountLowerBound > rowCount) {
newFirstVisibleRowScrollIndex = rowCount - visibleRowCountLowerBound + 1;
}
if (newFirstVisibleRowScrollIndex < 0) {
newFirstVisibleRowScrollIndex = 0;
}
firstVisibleRowScrollIndex = newFirstVisibleRowScrollIndex;
domVerticalScroll.scroll(newFirstVisibleRowScrollIndex);
scrollHorizontal(event.deltaX, event.deltaY);
scrollVertical(event.deltaX, event.deltaY);
}
}
function scrollVertical(deltaX, deltaY) {
let newFirstVisibleRowScrollIndex = firstVisibleRowScrollIndex;
if (deltaY > 0 && deltaX === -0) {
newFirstVisibleRowScrollIndex += wheelRowCount;
} else if (deltaY < 0 && deltaX === -0) {
newFirstVisibleRowScrollIndex -= wheelRowCount;
}
let rowCount = grider.rowCount;
if (newFirstVisibleRowScrollIndex + visibleRowCountLowerBound > rowCount) {
newFirstVisibleRowScrollIndex = rowCount - visibleRowCountLowerBound + 1;
}
if (newFirstVisibleRowScrollIndex < 0) {
newFirstVisibleRowScrollIndex = 0;
}
firstVisibleRowScrollIndex = newFirstVisibleRowScrollIndex;
domVerticalScroll.scroll(newFirstVisibleRowScrollIndex);
}
function scrollHorizontal(deltaX, deltaY) {
let newFirstVisibleColumnScrollIndex = firstVisibleColumnScrollIndex;
if (deltaX > 0 && deltaY === -0) {
newFirstVisibleColumnScrollIndex++;
} else if (deltaX < 0 && deltaY === -0) {
newFirstVisibleColumnScrollIndex--;
}
if (newFirstVisibleColumnScrollIndex > maxScrollColumn) {
newFirstVisibleColumnScrollIndex = maxScrollColumn;
}
if (newFirstVisibleColumnScrollIndex < 0) {
newFirstVisibleColumnScrollIndex = 0;
}
firstVisibleColumnScrollIndex = newFirstVisibleColumnScrollIndex;
domHorizontalScroll.scroll(newFirstVisibleColumnScrollIndex);
}
function getSelectedRowIndexes() {
if (selectedCells.find(x => x[0] == 'header')) return _.range(0, grider.rowCount);
return _.uniq((selectedCells || []).map(x => x[0])).filter(x => _.isNumber(x));
@@ -733,6 +741,7 @@
!event.altKey &&
((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) ||
(event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) ||
(event.keyCode >= keycodes.numPad0 && event.keyCode <= keycodes.numPad9) ||
event.keyCode == keycodes.dash)
) {
// @ts-ignore
@@ -979,7 +988,6 @@
);
const menu = getContextMenu();
</script>
{#if !display || (!isDynamicStructure && (!columns || columns.length == 0))}
@@ -1000,7 +1008,13 @@
{/each}
</div>
{:else}
<div class="container" bind:clientWidth={containerWidth} bind:clientHeight={containerHeight} use:contextMenu={menu}>
<div
class="container"
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
use:contextMenu={menu}
on:wheel={handleGridWheel}
>
<input
type="text"
class="focus-field"
@@ -1018,7 +1032,6 @@
on:mousedown={handleGridMouseDown}
on:mousemove={handleGridMouseMove}
on:mouseup={handleGridMouseUp}
on:wheel={handleGridWheel}
>
<thead>
<tr>
@@ -1050,7 +1063,7 @@
// @ts-ignore
display.resizeColumn(col.uniqueName, col.width, e.detail);
}}
setGrouping={display.sortable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
setGrouping={display.groupable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
grouping={display.getGrouping(col.uniqueName)}
/>
</td>
@@ -1186,5 +1199,4 @@
right: 40px;
bottom: 20px;
}
</style>
@@ -7,11 +7,14 @@
export let collection;
export let title;
export let clickable;
</script>
<ObjectListControl
{collection}
{title}
{clickable}
on:clickrow
columns={[
{
fieldName: 'baseColumns',
@@ -2,10 +2,10 @@
import TableControl from './TableControl.svelte';
export let title;
export let nameComponent;
export let collection;
export let columns;
export let showIfEmpty = false;
export let clickable;
</script>
@@ -25,6 +25,8 @@
},
...columns,
]}
{clickable}
on:clickrow
>
<svelte:fragment slot="-1" let:row>
<slot name="name" {row} />
@@ -76,4 +78,5 @@
.body {
margin: 20px;
}
</style>
@@ -4,10 +4,11 @@
export let label;
export let name;
export let disabled;
export let disabled = false;
export let templateProps = {};
const { template, setFieldValue, values } = getFormContext();
</script>
<svelte:component
@@ -0,0 +1,14 @@
<script lang="ts">
import { getFormContext } from './FormProviderCore.svelte';
import FormDropDownTextFieldRaw from './FormDropDownTextFieldRaw.svelte';
export let label;
export let name;
export let templateProps = {};
const { template } = getFormContext();
</script>
<svelte:component this={template} type="text" {label} {...templateProps}>
<FormDropDownTextFieldRaw {name} {...$$restProps} />
</svelte:component>
@@ -0,0 +1,31 @@
<script lang="ts">
import FontIcon from '../icons/FontIcon.svelte';
import InlineButton from '../elements/InlineButton.svelte';
import { getFormContext } from './FormProviderCore.svelte';
import TextField from './TextField.svelte';
import DropDownButton from '../elements/DropDownButton.svelte';
export let name;
export let disabled = false;
export let defaultValue;
export let menu;
const { values, setFieldValue } = getFormContext();
let showPassword = false;
$: value = $values[name];
$: isCrypted = value && value.startsWith('crypt:');
</script>
<div class="flex">
<TextField
{...$$restProps}
value={$values[name] ?? defaultValue}
on:input={e => setFieldValue(name, e.target['value'])}
/>
<DropDownButton {menu} {disabled} />
</div>
@@ -17,6 +17,7 @@
const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), {
defaultPath: values[name],
properties: ['showHiddenFiles'],
filters: [{ name: 'All Files', extensions: ['*'] }],
});
const filePath = filePaths && filePaths[0];
if (filePath) setFieldValue(name, filePath);
@@ -9,6 +9,7 @@
export let value;
export let isNative = false;
export let isMulti = false;
export let notSelected = null;
let listOpen = false;
let isFocused = false;
@@ -25,6 +26,11 @@
dispatch('change', e.target['value']);
}}
>
{#if notSelected}
<option value="" selected={!value}>
{_.isString(notSelected) ? notSelected : '(not selected)'}
</option>
{/if}
{#each _.compact(options) as x (x.value)}
<option value={x.value} selected={value == x.value}>
{x.label}
+3 -3
View File
@@ -49,7 +49,7 @@
name: 'Set NULL',
keyText: 'Ctrl+0',
testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().setNull(),
onClick: () => getCurrentDataForm().setFixedValue(null),
});
registerCommand({
@@ -247,9 +247,9 @@
// if (onSave) onSave();
// }
export function setNull() {
export function setFixedValue(value) {
if (isDataCell(currentCell)) {
setCellValue(currentCell, null);
setCellValue(currentCell, value);
}
}
+3
View File
@@ -70,6 +70,8 @@
'icon menu-right': 'mdi mdi-menu-right',
'icon plugin': 'mdi mdi-toy-brick',
'icon menu': 'mdi mdi-menu',
'icon add-column': 'mdi mdi-table-column-plus-after',
'icon add-key': 'mdi mdi-key-plus',
'img ok': 'mdi mdi-check-circle color-icon-green',
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
@@ -113,6 +115,7 @@
'img filter': 'mdi mdi-filter',
'img group': 'mdi mdi-group',
};
</script>
<span class={iconNames[icon] || icon} {title} on:click />
+9 -1
View File
@@ -4,6 +4,7 @@
import clickOutside from '../utility/clickOutside';
import keycodes from '../utility/keycodes';
import { onMount } from 'svelte';
import { currentDropDownMenu } from '../stores';
export let fullScreen = false;
export let noPadding = false;
@@ -16,6 +17,11 @@
}
}
function handleClickOutside() {
if ($currentDropDownMenu) return;
handleCloseModal();
}
function handleEscape(e) {
if (e.keyCode == keycodes.escape) {
handleCloseModal();
@@ -28,12 +34,13 @@
if (oldFocus) oldFocus.focus();
};
});
</script>
<!-- The Modal -->
<div id="myModal" class="bglayer">
<!-- Modal content -->
<div class="window" class:fullScreen class:simple use:clickOutside on:clickOutside={handleCloseModal}>
<div class="window" class:fullScreen class:simple use:clickOutside on:clickOutside={handleClickOutside}>
{#if $$slots.header}
<div class="header" class:fullScreen>
<div><slot name="header" /></div>
@@ -153,4 +160,5 @@
border-top: 1px solid var(--theme-border);
background-color: var(--theme-bg-modalheader);
}
</style>
+20 -2
View File
@@ -15,6 +15,13 @@ const jsonFormat = {
writerFunc: 'jsonArrayWriter',
};
const sqlFormat = {
storageType: 'sql',
extension: 'sql',
name: 'SQL',
writerFunc: 'sqlDataWriter',
};
const jsonlQuickExport = {
label: 'JSON lines',
extension: 'jsonl',
@@ -37,8 +44,19 @@ const jsonQuickExport = {
}),
};
const sqlQuickExport = {
label: 'SQL',
extension: 'sql',
createWriter: fileName => ({
functionName: 'sqlDataWriter',
props: {
fileName,
},
}),
};
export function buildFileFormats(plugins): FileFormatDefinition[] {
const res = [jsonlFormat, jsonFormat];
const res = [jsonlFormat, jsonFormat, sqlFormat];
for (const { content } of plugins) {
const { fileFormats } = content;
if (fileFormats) res.push(...fileFormats);
@@ -47,7 +65,7 @@ export function buildFileFormats(plugins): FileFormatDefinition[] {
}
export function buildQuickExports(plugins): QuickExportDefinition[] {
const res = [jsonQuickExport, jsonlQuickExport];
const res = [jsonQuickExport, jsonlQuickExport, sqlQuickExport];
for (const { content } of plugins) {
if (content.quickExports) res.push(...content.quickExports);
}
+6 -1
View File
@@ -69,11 +69,16 @@
.main {
flex: 1;
display: flex;
position: relative;
overflow-y: scroll;
background-color: var(--theme-bg-0);
}
table {
flex: 1;
position: absolute;
left: 0;
right: 0;
top: 0;
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
@@ -46,6 +46,8 @@
/>
<FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} />
<FormCheckboxField name="dataGrid.thousandsSeparator" label="Use thousands separator for numbers" />
<div class="heading">Connection</div>
<FormCheckboxField
name="connection.autoRefresh"
@@ -0,0 +1,77 @@
<script lang="ts">
import uuidv1 from 'uuid/v1';
import FormStyledButton from '../elements/FormStyledButton.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import ModalBase from '../modals/ModalBase.svelte';
import { closeCurrentModal } from '../modals/modalTools';
import ElectronFilesInput from '../impexp/ElectronFilesInput.svelte';
import DropDownButton from '../elements/DropDownButton.svelte';
import DataTypeEditor from './DataTypeEditor.svelte';
import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools';
export let columnInfo;
export let setTableInfo;
export let tableInfo;
export let onAddNext;
</script>
<FormProvider initialValues={fillEditorColumnInfo(columnInfo, tableInfo)}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header"
>{columnInfo ? 'Edit column' : `Add column ${(tableInfo?.columns || []).length + 1}`}</svelte:fragment
>
<FormTextField name="columnName" label="Column name" focused />
<DataTypeEditor />
<FormCheckboxField name="notNull" label="NOT NULL" />
<FormCheckboxField name="isPrimaryKey" label="Is Primary Key" />
<FormCheckboxField name="autoIncrement" label="Is Autoincrement" />
<FormTextField name="defaultValue" label="Default value" />
<FormTextField name="computedExpression" label="Computed expression" />
<svelte:fragment slot="footer">
<FormSubmit
value={columnInfo ? 'Save' : 'Save and next'}
on:click={e => {
closeCurrentModal();
if (columnInfo) {
setTableInfo(tbl => editorModifyColumn(tbl, e.detail));
} else {
setTableInfo(tbl => editorAddColumn(tbl, e.detail));
if (onAddNext) onAddNext();
}
}}
/>
{#if !columnInfo}
<FormStyledButton
type="button"
value="Save"
on:click={e => {
closeCurrentModal();
setTableInfo(tbl => editorAddColumn(tbl, e.detail));
}}
/>
{/if}
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
{#if columnInfo}
<FormStyledButton
type="button"
value="Remove"
on:click={() => {
closeCurrentModal();
setTableInfo(tbl => editorDeleteColumn(tbl, columnInfo));
}}
/>
{/if}
</svelte:fragment>
</ModalBase>
</FormProvider>
@@ -0,0 +1,165 @@
<script lang="ts">
import FormStyledButton from '../elements/FormStyledButton.svelte';
import uuidv1 from 'uuid/v1';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import ModalBase from '../modals/ModalBase.svelte';
import { closeCurrentModal } from '../modals/modalTools';
import ElectronFilesInput from '../impexp/ElectronFilesInput.svelte';
import DropDownButton from '../elements/DropDownButton.svelte';
import DataTypeEditor from './DataTypeEditor.svelte';
import { editorAddConstraint, editorDeleteConstraint, editorModifyConstraint } from 'dbgate-tools';
import TextField from '../forms/TextField.svelte';
import SelectField from '../forms/SelectField.svelte';
export let constraintInfo;
export let setTableInfo;
export let tableInfo;
export let constraintLabel;
export let constraintType;
let constraintName = constraintInfo?.constraintName;
let columns = constraintInfo?.columns || [];
function getConstraint() {
return {
pairingId: uuidv1(),
...constraintInfo,
columns,
pureName: tableInfo.pureName,
schemaName: tableInfo.schemaName,
constraintName,
constraintType,
};
}
</script>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header"
>{constraintInfo ? `Edit ${constraintLabel}` : `Add ${constraintLabel}`}</svelte:fragment
>
<div class="largeFormMarker">
<div class="row">
<div class="label col-3">Constraint name</div>
<div class="col-9">
<TextField value={constraintName} on:input={e => (constraintName = e.target['value'])} focused />
</div>
</div>
{#each columns as column, index}
<div class="row">
<div class="label col-3">Column {index + 1}</div>
<div class="col-6">
{#key column.columnName}
<SelectField
value={column.columnName}
isNative
options={tableInfo.columns.map(col => ({
label: col.columnName,
value: col.columnName,
}))}
on:change={e => {
if (e.detail) {
columns = columns.map((col, i) => (i == index ? { columnName: e.detail } : col));
}
}}
/>
{/key}
</div>
<div class="col-3 button">
<FormStyledButton
value="Delete"
on:click={e => {
const x = [...columns];
x.splice(index, 1);
columns = x;
}}
/>
</div>
</div>
{/each}
<div class="row">
<div class="label col-3">Add new column</div>
<div class="col-9">
{#key columns.length}
<SelectField
placeholder="Select column"
value={''}
on:change={e => {
if (e.detail)
columns = [
...columns,
{
columnName: e.detail,
},
];
}}
isNative
options={[
{
label: 'Choose column',
value: '',
},
...tableInfo.columns.map(col => ({
label: col.columnName,
value: col.columnName,
})),
]}
/>
{/key}
</div>
</div>
</div>
<svelte:fragment slot="footer">
<FormStyledButton
value={'Save'}
on:click={() => {
closeCurrentModal();
if (constraintInfo) {
setTableInfo(tbl => editorModifyConstraint(tbl, getConstraint()));
} else {
setTableInfo(tbl => editorAddConstraint(tbl, getConstraint()));
}
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
{#if constraintInfo}
<FormStyledButton
type="button"
value="Remove"
on:click={() => {
closeCurrentModal();
setTableInfo(tbl => editorDeleteConstraint(tbl, constraintInfo));
}}
/>
{/if}
</svelte:fragment>
</ModalBase>
<style>
.row {
margin: var(--dim-large-form-margin);
display: flex;
}
.row .label {
white-space: nowrap;
align-self: center;
}
.button {
align-self: center;
text-align: right;
}
</style>
@@ -0,0 +1,18 @@
<script lang="ts">
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
import { getFormContext } from '../forms/FormProviderCore.svelte';
const { values, setFieldValue } = getFormContext();
$: dataTypes = ['int', 'nvarchar(250)', 'datetime', 'numeric(10,2)', 'float'];
function createDataTypesMenu() {
return dataTypes.map(type => ({
text: type,
onClick: () => setFieldValue('dataType', type),
}));
}
</script>
<FormDropDownTextField name="dataType" label="Data type" menu={createDataTypesMenu} />
@@ -0,0 +1,239 @@
<script lang="ts">
import FormStyledButton from '../elements/FormStyledButton.svelte';
import uuidv1 from 'uuid/v1';
import FormSelectField from '../forms/FormSelectField.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import ModalBase from '../modals/ModalBase.svelte';
import { closeCurrentModal } from '../modals/modalTools';
import ElectronFilesInput from '../impexp/ElectronFilesInput.svelte';
import DropDownButton from '../elements/DropDownButton.svelte';
import DataTypeEditor from './DataTypeEditor.svelte';
import {
editorAddConstraint,
editorDeleteConstraint,
editorModifyConstraint,
fullNameFromString,
fullNameToLabel,
fullNameToString,
} from 'dbgate-tools';
import TextField from '../forms/TextField.svelte';
import SelectField from '../forms/SelectField.svelte';
import _ from 'lodash';
export let constraintInfo;
export let setTableInfo;
export let tableInfo;
export let dbInfo;
const foreignKeyActions = ['noAction', 'cascade', 'restrict', 'setNull'];
const foreignKeyActionsOptions = foreignKeyActions.map(x => ({
label: _.startCase(x),
value: x,
}));
let constraintName = constraintInfo?.constraintName;
let columns = constraintInfo?.columns || [];
let refTableName = constraintInfo?.refTableName;
let refSchemaName = constraintInfo?.refSchemaName;
let deleteAction = constraintInfo?.deleteAction ? _.camelCase(constraintInfo?.deleteAction) : null;
let updateAction = constraintInfo?.updateAction ? _.camelCase(constraintInfo?.updateAction) : null;
$: refTableInfo = dbInfo?.tables?.find(x => x.pureName == refTableName && x.schemaName == refSchemaName);
function getConstraint() {
return {
pairingId: uuidv1(),
...constraintInfo,
columns,
pureName: tableInfo.pureName,
schemaName: tableInfo.schemaName,
constraintName,
constraintType: 'foreignKey',
refTableName,
refSchemaName,
deleteAction: _.startCase(deleteAction).toUpperCase(),
updateAction: _.startCase(updateAction).toUpperCase(),
};
}
</script>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">{constraintInfo ? `Edit foreign key` : `Add foreign key`}</svelte:fragment>
<div class="largeFormMarker">
<div class="row">
<div class="label col-3">Constraint name</div>
<div class="col-9">
<TextField value={constraintName} on:input={e => (constraintName = e.target['value'])} focused />
</div>
</div>
<div class="row">
<div class="label col-3">Referenced table</div>
<div class="col-9">
<SelectField
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
isNative
notSelected
options={(dbInfo?.tables || []).map(tbl => ({
label: fullNameToLabel(tbl),
value: fullNameToString(tbl),
}))}
on:change={e => {
if (e.detail) {
const name = fullNameFromString(e.detail);
refTableName = name.pureName;
refSchemaName = name.schemaName;
}
}}
/>
</div>
</div>
<div class="row">
<div class="label col-3">On update action</div>
<div class="col-9">
<SelectField
value={updateAction}
isNative
notSelected
options={foreignKeyActionsOptions}
on:change={e => {
updateAction = e.detail || null;
}}
/>
</div>
</div>
<div class="row">
<div class="label col-3">On delete action</div>
<div class="col-9">
<SelectField
value={deleteAction}
isNative
notSelected
options={foreignKeyActionsOptions}
on:change={e => {
deleteAction = e.detail || null;
}}
/>
</div>
</div>
<div class="row">
<div class="col-5 mr-1">
Base column - {tableInfo.pureName}
</div>
<div class="col-5 ml-1">
Ref column - {refTableName || '(table not set)'}
</div>
</div>
{#each columns as column, index}
<div class="row">
<div class="col-5 mr-1">
{#key column.columnName}
<SelectField
value={column.columnName}
isNative
notSelected
options={tableInfo.columns.map(col => ({
label: col.columnName,
value: col.columnName,
}))}
on:change={e => {
if (e.detail) {
columns = columns.map((col, i) => (i == index ? { ...col, columnName: e.detail } : col));
}
}}
/>
{/key}
</div>
<div class="col-5 ml-1">
{#key column.refColumnName}
<SelectField
value={column.refColumnName}
isNative
notSelected
options={(refTableInfo?.columns || []).map(col => ({
label: col.columnName,
value: col.columnName,
}))}
on:change={e => {
if (e.detail) {
columns = columns.map((col, i) => (i == index ? { ...col, refColumnName: e.detail } : col));
}
}}
/>
{/key}
</div>
<div class="col-2 button">
<FormStyledButton
value="Delete"
on:click={e => {
const x = [...columns];
x.splice(index, 1);
columns = x;
}}
/>
</div>
</div>
{/each}
<FormStyledButton
type="button"
value="Add column"
on:click={() => {
columns = [...columns, {}];
}}
/>
</div>
<svelte:fragment slot="footer">
<FormStyledButton
value={'Save'}
on:click={() => {
closeCurrentModal();
if (constraintInfo) {
setTableInfo(tbl => editorModifyConstraint(tbl, getConstraint()));
} else {
setTableInfo(tbl => editorAddConstraint(tbl, getConstraint()));
}
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
{#if constraintInfo}
<FormStyledButton
type="button"
value="Remove"
on:click={() => {
closeCurrentModal();
setTableInfo(tbl => editorDeleteConstraint(tbl, constraintInfo));
}}
/>
{/if}
</svelte:fragment>
</ModalBase>
<style>
.row {
margin: var(--dim-large-form-margin);
display: flex;
}
.row .label {
white-space: nowrap;
align-self: center;
}
.button {
align-self: center;
text-align: right;
}
</style>
@@ -0,0 +1,17 @@
<script lang="ts">
import ColumnsConstraintEditorModal from './ColumnsConstraintEditorModal.svelte';
export let constraintInfo;
export let setTableInfo;
export let tableInfo;
</script>
<ColumnsConstraintEditorModal
{...$$restProps}
constraintLabel="primary key"
constraintType="primaryKey"
{constraintInfo}
{setTableInfo}
{tableInfo}
/>
@@ -0,0 +1,195 @@
<script lang="ts" context="module">
const getCurrentEditor = () => getActiveComponent('TableEditor');
registerCommand({
id: 'tableEditor.addColumn',
category: 'Table editor',
name: 'Add column',
icon: 'icon add-column',
toolbar: true,
isRelatedToTab: true,
testEnabled: () => getCurrentEditor()?.writable(),
onClick: () => getCurrentEditor().addColumn(),
});
registerCommand({
id: 'tableEditor.addPrimaryKey',
category: 'Table editor',
name: 'Add primary key',
icon: 'icon add-key',
toolbar: true,
isRelatedToTab: true,
testEnabled: () => getCurrentEditor()?.allowAddPrimaryKey(),
onClick: () => getCurrentEditor().addPrimaryKey(),
});
registerCommand({
id: 'tableEditor.addForeignKey',
category: 'Table editor',
name: 'Add foreign key',
icon: 'icon add-key',
toolbar: true,
isRelatedToTab: true,
testEnabled: () => getCurrentEditor()?.writable(),
onClick: () => getCurrentEditor().addForeignKey(),
});
</script>
<script lang="ts">
import _ from 'lodash';
import { tick } from 'svelte';
import invalidateCommands from '../commands/invalidateCommands';
import registerCommand from '../commands/registerCommand';
import ColumnLabel from '../elements/ColumnLabel.svelte';
import ConstraintLabel from '../elements/ConstraintLabel.svelte';
import ForeignKeyObjectListControl from '../elements/ForeignKeyObjectListControl.svelte';
import ObjectListControl from '../elements/ObjectListControl.svelte';
import { showModal } from '../modals/modalTools';
import useEditorData from '../query/useEditorData';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import { useDbCore } from '../utility/metadataLoaders';
import ColumnEditorModal from './ColumnEditorModal.svelte';
import ForeignKeyEditorModal from './ForeignKeyEditorModal.svelte';
import PrimaryKeyEditorModal from './PrimaryKeyEditorModal.svelte';
export const activator = createActivator('TableEditor', true);
export let tableInfo;
export let setTableInfo;
export let dbInfo;
export function writable() {
return !!setTableInfo;
}
export function addColumn() {
showModal(ColumnEditorModal, {
setTableInfo,
tableInfo,
onAddNext: async () => {
await tick();
addColumn();
},
});
}
export function allowAddPrimaryKey() {
return writable() && !tableInfo.primaryKey;
}
export function addPrimaryKey() {
showModal(PrimaryKeyEditorModal, {
setTableInfo,
tableInfo,
});
}
export function addForeignKey() {
showModal(ForeignKeyEditorModal, {
setTableInfo,
tableInfo,
dbInfo,
});
}
$: columns = tableInfo?.columns;
$: primaryKey = tableInfo?.primaryKey;
$: foreignKeys = tableInfo?.foreignKeys;
$: dependencies = tableInfo?.dependencies;
$: {
tableInfo;
invalidateCommands();
}
$: console.log('tableInfo', tableInfo);
</script>
<div class="wrapper">
<ObjectListControl
collection={columns?.map((x, index) => ({ ...x, ordinal: index + 1 }))}
title="Columns"
clickable={writable()}
on:clickrow={e => showModal(ColumnEditorModal, { columnInfo: e.detail, tableInfo, setTableInfo })}
columns={[
{
fieldName: 'notNull',
header: 'Not NULL',
sortable: true,
slot: 0,
},
{
fieldName: 'dataType',
header: 'Data Type',
sortable: true,
},
{
fieldName: 'defaultValue',
header: 'Default value',
sortable: true,
},
{
fieldName: 'isSparse',
header: 'Is Sparse',
sortable: true,
slot: 1,
},
{
fieldName: 'computedExpression',
header: 'Computed Expression',
sortable: true,
},
{
fieldName: 'isPersisted',
header: 'Is Persisted',
sortable: true,
slot: 2,
},
]}
>
<svelte:fragment slot="0" let:row>{row?.notNull ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="1" let:row>{row?.isSparse ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="2" let:row>{row?.isPersisted ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="name" let:row><ColumnLabel {...row} forceIcon /></svelte:fragment>
</ObjectListControl>
<ObjectListControl
collection={_.compact([primaryKey])}
title="Primary key"
clickable={writable()}
on:clickrow={e => showModal(PrimaryKeyEditorModal, { constraintInfo: e.detail, tableInfo, setTableInfo })}
columns={[
{
fieldName: 'columns',
header: 'Columns',
slot: 0,
},
]}
>
<svelte:fragment slot="name" let:row><ConstraintLabel {...row} /></svelte:fragment>
<svelte:fragment slot="0" let:row>{row?.columns.map(x => x.columnName).join(', ')}</svelte:fragment>
</ObjectListControl>
<ForeignKeyObjectListControl
collection={foreignKeys}
title="Foreign keys"
clickable={writable()}
on:clickrow={e => showModal(ForeignKeyEditorModal, { constraintInfo: e.detail, tableInfo, setTableInfo, dbInfo })}
/>
<ForeignKeyObjectListControl collection={dependencies} title="Dependencies" />
</div>
<style>
.wrapper {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: var(--theme-bg-0);
overflow: auto;
}
</style>
+21 -81
View File
@@ -4,6 +4,8 @@
</script>
<script lang="ts">
import { generateTablePairingId } from 'dbgate-tools';
import _ from 'lodash';
import ColumnLabel from '../elements/ColumnLabel.svelte';
@@ -11,8 +13,10 @@
import ForeignKeyObjectListControl from '../elements/ForeignKeyObjectListControl.svelte';
import ObjectListControl from '../elements/ObjectListControl.svelte';
import useEditorData from '../query/useEditorData';
import TableEditor from '../tableeditor/TableEditor.svelte';
import { useDbCore } from '../utility/metadataLoaders';
import { useDatabaseInfo, useDbCore } from '../utility/metadataLoaders';
export let tabid;
export let conid;
@@ -22,86 +26,22 @@
export let objectTypeField = 'tables';
$: tableInfo = useDbCore({ conid, database, schemaName, pureName, objectTypeField });
$: dbInfo = useDatabaseInfo({ conid, database });
$: tableInfoWithPairingId = $tableInfo ? generateTablePairingId($tableInfo) : null;
$: columns = $tableInfo?.columns;
$: primaryKey = $tableInfo?.primaryKey;
$: foreignKeys = $tableInfo?.foreignKeys;
$: dependencies = $tableInfo?.dependencies;
const { editorState, editorValue, setEditorData } = useEditorData({ tabid });
$: showTable = $editorValue || tableInfoWithPairingId;
</script>
<div class="wrapper">
<ObjectListControl
collection={columns?.map((x, index) => ({ ...x, ordinal: index + 1 }))}
title="Columns"
columns={[
{
fieldName: 'notNull',
header: 'Not NULL',
sortable: true,
slot: 0,
},
{
fieldName: 'dataType',
header: 'Data Type',
sortable: true,
},
{
fieldName: 'defaultValue',
header: 'Default value',
sortable: true,
},
{
fieldName: 'isSparse',
header: 'Is Sparse',
sortable: true,
slot: 1,
},
{
fieldName: 'computedExpression',
header: 'Computed Expression',
sortable: true,
},
{
fieldName: 'isPersisted',
header: 'Is Persisted',
sortable: true,
slot: 2,
},
]}
>
<svelte:fragment slot="0" let:row>{row?.notNull ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="1" let:row>{row?.isSparse ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="2" let:row>{row?.isPersisted ? 'YES' : 'NO'}</svelte:fragment>
<svelte:fragment slot="name" let:row><ColumnLabel {...row} forceIcon /></svelte:fragment>
</ObjectListControl>
<ObjectListControl
collection={_.compact([primaryKey])}
title="Primary key"
columns={[
{
fieldName: 'columns',
header: 'Columns',
slot: 0,
},
]}
>
<svelte:fragment slot="name" let:row><ConstraintLabel {...row} /></svelte:fragment>
<svelte:fragment slot="0" let:row>{row?.columns.map(x => x.columnName).join(', ')}</svelte:fragment>
</ObjectListControl>
<ForeignKeyObjectListControl collection={foreignKeys} title="Foreign keys" />
<ForeignKeyObjectListControl collection={dependencies} title="Dependencies" />
</div>
<style>
.wrapper {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: var(--theme-bg-0);
overflow: auto;
}
</style>
<TableEditor
tableInfo={showTable}
dbInfo={$dbInfo}
setTableInfo={objectTypeField == 'tables'
? tableInfoUpdater =>
setEditorData(tbl => {
if (tbl) return tableInfoUpdater(tbl);
return tableInfoUpdater(tableInfoWithPairingId);
})
: null}
/>
+5 -5
View File
@@ -63,11 +63,11 @@ export function closeSnackbar(snackId: string) {
// message: 'Test snackbar',
// allowClose: true,
// });
showSnackbar({
icon: 'img ok',
message: 'Auto close',
autoClose: true,
});
// showSnackbar({
// icon: 'img ok',
// message: 'Auto close',
// autoClose: true,
// });
// showSnackbar({
// icon: 'img warn',
// message: 'Buttons',
+19 -1
View File
@@ -1,3 +1,17 @@
<script lang="ts" context="module">
function generateObjectList(seed = 0) {
const counts = [1000, 1200, 1100, 2100, 720];
const schemas = ['A', 'dev', 'public', 'dbo'];
const types = ['tables', 'views', 'functions', 'procedures', 'matviews', 'triggers'];
const res = _.range(1, counts[seed % counts.length]).map(i => ({
pureName: `name ${i}`,
schemaName: schemas[i % schemas.length],
objectTypeField: types[i % types.length],
}));
return res;
}
</script>
<script lang="ts">
import InlineButton from '../elements/InlineButton.svelte';
import SearchInput from '../elements/SearchInput.svelte';
@@ -12,7 +26,7 @@
import ErrorInfo from '../elements/ErrorInfo.svelte';
import axiosInstance from '../utility/axiosInstance';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import { getObjectTypeFieldLabel } from '../utility/common';
import { getObjectTypeFieldLabel } from '../utility/common';
export let conid;
export let database;
@@ -33,6 +47,10 @@ import { getObjectTypeFieldLabel } from '../utility/common';
)
);
// let generateIndex = 0;
// setInterval(() => (generateIndex += 1), 2000);
// $: objectList = generateObjectList(generateIndex);
const handleRefreshDatabase = () => {
axiosInstance.post('database-connections/refresh', { conid, database });
};
+1 -1
View File
@@ -49,7 +49,7 @@
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
if (tab.props && tab.props.conid) {
const connection = connectionList?.find(x => x._id == tab.props.conid);
if (connection) return getConnectionLabel(connection.displayName, { allowExplicitDatabase: false });
if (connection) return getConnectionLabel(connection, { allowExplicitDatabase: false });
return '???';
}
if (tab.props && tab.props.archiveFolder) return tab.props.archiveFolder;
+13
View File
@@ -11,3 +11,16 @@ index ee20a17..7b6fff8 100644
}
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
diff --git a/node_modules/svelte/internal/index.mjs b/node_modules/svelte/internal/index.mjs
index 4146e56..f5f00c5 100644
--- a/node_modules/svelte/internal/index.mjs
+++ b/node_modules/svelte/internal/index.mjs
@@ -196,7 +196,7 @@ function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
}
function detach(node) {
- node.parentNode.removeChild(node);
+ if (node.parentNode) node.parentNode.removeChild(node);
}
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
@@ -6,7 +6,7 @@ const Analyser = require('./Analyser');
const MongoClient = require('mongodb').MongoClient;
const ObjectId = require('mongodb').ObjectId;
const Cursor = require('mongodb').Cursor;
const { createBulkInsertStream } = require('./createBulkInsertStream');
const createBulkInsertStream = require('./createBulkInsertStream');
function readCursor(cursor, options) {
return new Promise((resolve) => {
@@ -1,3 +1,4 @@
const _isString = require('lodash/isString');
const { driverBase } = global.DBGATE_TOOLS;
const Dumper = require('./Dumper');
const { noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options');
@@ -5,7 +6,7 @@ const { noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options');
const mongoIdRegex = /^[0-9a-f]{24}$/;
function getConditionPreview(condition) {
if (condition && _.isString(condition._id) && condition._id.match(mongoIdRegex)) {
if (condition && _isString(condition._id) && condition._id.match(mongoIdRegex)) {
return `{ _id: ObjectId('${condition._id}') }`;
}
return JSON.stringify(condition);
@@ -32,6 +32,8 @@ function getColumnInfo({
charMaxLength,
numericPrecision,
numericScale,
defaultValue,
defaultConstraint,
}) {
let fullDataType = dataType;
if (charMaxLength && isTypeString(dataType)) fullDataType = `${dataType}(${charMaxLength})`;
@@ -42,6 +44,8 @@ function getColumnInfo({
dataType: fullDataType,
notNull: !isNullable,
autoIncrement: !!isIdentity,
defaultValue,
defaultConstraint,
};
}
@@ -51,6 +55,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
}
createQuery(resFileName, typeFields) {
if (!sql[resFileName]) throw new Error(`Missing analyse file ${resFileName}`);
return super.createQuery(sql[resFileName], typeFields);
}
@@ -67,6 +72,8 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const pkColumnsRows = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
const fkColumnsRows = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
const schemaRows = await this.driver.query(this.pool, this.createQuery('getSchemas'));
const indexesRows = await this.driver.query(this.pool, this.createQuery('indexes', ['tables']));
const indexcolsRows = await this.driver.query(this.pool, this.createQuery('indexcols', ['tables']));
const schemas = schemaRows.rows;
@@ -88,15 +95,35 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const tables = tablesRows.rows.map(row => ({
...row,
contentHash: row.modifyDate.toISOString(),
contentHash: row.modifyDate && row.modifyDate.toISOString(),
columns: columnsRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
indexes: indexesRows.rows
.filter(idx => idx.object_id == row.objectId && !idx.is_unique_constraint)
.map(idx => ({
..._.pick(idx, ['constraintName', 'indexType', 'isUnique']),
columns: indexcolsRows.rows
.filter(col => col.object_id == idx.object_id && col.index_id == idx.index_id)
.map(col => ({
..._.pick(col, ['columnName', 'isDescending', 'isIncludedColumn']),
})),
})),
uniques: indexesRows.rows
.filter(idx => idx.object_id == row.objectId && idx.is_unique_constraint)
.map(idx => ({
..._.pick(idx, ['constraintName']),
columns: indexcolsRows.rows
.filter(col => col.object_id == idx.object_id && col.index_id == idx.index_id)
.map(col => ({
..._.pick(col, ['columnName']),
})),
})),
}));
const views = viewsRows.rows.map(row => ({
...row,
contentHash: row.modifyDate.toISOString(),
contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row),
columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
}));
@@ -105,7 +132,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => x.sqlObjectType.trim() == 'P')
.map(row => ({
...row,
contentHash: row.modifyDate.toISOString(),
contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row),
}));
@@ -113,7 +140,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
.map(row => ({
...row,
contentHash: row.modifyDate.toISOString(),
contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row),
}));
@@ -137,7 +164,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
res[field].push({
objectId,
contentHash: modifyDate.toISOString(),
contentHash: modifyDate && modifyDate.toISOString(),
schemaName,
pureName,
});
@@ -8,6 +8,8 @@ const views = require('./views');
const programmables = require('./programmables');
const viewColumns = require('./viewColumns');
const getSchemas = require('./getSchemas');
const indexes = require('./indexes');
const indexcols = require('./indexcols');
module.exports = {
columns,
@@ -20,4 +22,6 @@ module.exports = {
programmables,
viewColumns,
getSchemas,
indexes,
indexcols,
};
@@ -0,0 +1,10 @@
module.exports = `
select
c.object_id, c.index_id, c.column_id,
col.name as columnName,
c.is_descending_key as isDescending, c.is_included_column as isIncludedColumn
from sys.index_columns c
inner join sys.columns col on c.object_id = col.object_id and c.column_id = col.column_id
where c.object_id =OBJECT_ID_CONDITION
order by c.key_ordinal
`;
@@ -0,0 +1,14 @@
module.exports = `
select i.object_id, i.name as constraintName, i.type_desc as indexType, i.is_unique as isUnique,i.index_id, i.is_unique_constraint from sys.indexes i
where i.is_primary_key=0
and i.is_hypothetical=0 and indexproperty(i.object_id, i.name, 'IsStatistics') = 0
and objectproperty(i.object_id, 'IsUserTable') = 1
and i.index_id between 1 and 254
--and i.name not in
-- (select o.name from sysobjects o
-- where o.parent_obj = i.object_id
-- and objectproperty(o.id, N'isConstraint') = 1.0)
and i.object_id =OBJECT_ID_CONDITION
`;
@@ -1,4 +1,4 @@
const { SqlDumper } = global.DBGATE_TOOLS;
const { SqlDumper, testEqualColumns } = global.DBGATE_TOOLS;
class MsSqlDumper extends SqlDumper {
autoIncrement() {
@@ -65,6 +65,13 @@ class MsSqlDumper extends SqlDumper {
super.dropTable(obj, options);
}
dropColumn(column) {
if (column.defaultConstraint) {
this.putCmd('^alter ^table %f ^drop ^constraint %i', column, column.defaultConstraint);
}
super.dropColumn(column);
}
dropDefault(col) {
if (col.defaultConstraint) {
this.putCmd('^alter ^table %f ^drop ^constraint %i', col, col.defaultConstraint);
@@ -88,6 +95,21 @@ class MsSqlDumper extends SqlDumper {
this.putCmd("^execute sp_rename '%f.%i', '%s', 'COLUMN'", column, column.columnName, newcol);
}
changeColumn(oldcol, newcol, constraints) {
if (testEqualColumns(oldcol, newcol, false, false)) {
this.dropDefault(oldcol);
if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName);
this.createDefault(oldcol);
} else {
this.dropDefault(oldcol);
if (oldcol.columnName != newcol.columnName) this.renameColumn(oldcol, newcol.columnName);
this.put('^alter ^table %f ^alter ^column %i ', oldcol, oldcol.columnName, newcol.columnName);
this.columnDefinition(newcol, { includeDefault: false });
this.endCommand();
this.createDefault(oldcol);
}
}
renameConstraint(cnt, newname) {
if (cnt.constraintType == 'index')
this.putCmd("^execute sp_rename '%f.%i', '%s', 'INDEX'", cnt, cnt.constraintName, newname);
@@ -12,10 +12,26 @@ const dialect = {
fallbackDataType: 'nvarchar(max)',
explicitDropConstraint: false,
enableConstraintsPerTable: true,
dropColumnDependencies: ['default', 'dependencies', 'indexes', 'primaryKey', 'foreignKeys', 'uniques'],
changeColumnDependencies: ['indexes'],
anonymousPrimaryKey: false,
dropIndexContainsTableSpec: true,
quoteIdentifier(s) {
return `[${s}]`;
},
createColumn: true,
dropColumn: true,
createIndex: true,
dropIndex: true,
createForeignKey: true,
dropForeignKey: true,
createPrimaryKey: true,
dropPrimaryKey: true,
createUnique: true,
dropUnique: true,
createCheck: true,
dropCheck: true,
};
/** @type {import('dbgate-types').EngineDriver} */
@@ -21,7 +21,7 @@ function getColumnInfo({
fullDataType = `${dataType}(${numericPrecision},${numericScale})`;
return {
notNull: !isNullable || isNullable == 'NO' || isNullable == 'no',
autoIncrement: extra && extra.toLowerCase().includes('auto_increment'),
autoIncrement: !!(extra && extra.toLowerCase().includes('auto_increment')),
columnName,
dataType: fullDataType,
defaultValue,
@@ -74,20 +74,52 @@ class Analyser extends DatabaseAnalyser {
);
const viewTexts = await this.getViewTexts(views.rows.map(x => x.pureName));
const indexes = await this.driver.query(this.pool, this.createQuery('indexes', ['tables']));
const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables']));
return {
tables: tables.rows.map(table => ({
...table,
objectId: table.pureName,
contentHash: table.modifyDate.toISOString(),
contentHash: table.modifyDate && table.modifyDate.toISOString(),
columns: columns.rows.filter(col => col.pureName == table.pureName).map(getColumnInfo),
primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, pkColumns.rows),
foreignKeys: DatabaseAnalyser.extractForeignKeys(table, fkColumns.rows),
indexes: _.uniqBy(
indexes.rows.filter(
idx =>
idx.tableName == table.pureName && !uniqueNames.rows.find(x => x.constraintName == idx.constraintName)
),
'constraintName'
).map(idx => ({
..._.pick(idx, ['constraintName', 'indexType']),
isUnique: !idx.nonUnique,
columns: indexes.rows
.filter(col => col.tableName == idx.tableName && col.constraintName == idx.constraintName)
.map(col => ({
..._.pick(col, ['columnName']),
})),
})),
uniques: _.uniqBy(
indexes.rows.filter(
idx =>
idx.tableName == table.pureName && uniqueNames.rows.find(x => x.constraintName == idx.constraintName)
),
'constraintName'
).map(idx => ({
..._.pick(idx, ['constraintName']),
columns: indexes.rows
.filter(col => col.tableName == idx.tableName && col.constraintName == idx.constraintName)
.map(col => ({
..._.pick(col, ['columnName']),
})),
})),
})),
views: views.rows.map(view => ({
...view,
objectId: view.pureName,
contentHash: view.modifyDate.toISOString(),
contentHash: view.modifyDate && view.modifyDate.toISOString(),
columns: columns.rows.filter(col => col.pureName == view.pureName).map(getColumnInfo),
createSql: viewTexts[view.pureName],
requiresFormat: true,
@@ -98,7 +130,7 @@ class Analyser extends DatabaseAnalyser {
.map(x => ({
...x,
objectId: x.pureName,
contentHash: x.modifyDate.toISOString(),
contentHash: x.modifyDate && x.modifyDate.toISOString(),
})),
functions: programmables.rows
.filter(x => x.objectType == 'FUNCTION')
@@ -106,7 +138,7 @@ class Analyser extends DatabaseAnalyser {
.map(x => ({
...x,
objectId: x.pureName,
contentHash: x.modifyDate.toISOString(),
contentHash: x.modifyDate && x.modifyDate.toISOString(),
})),
};
}
@@ -128,14 +160,14 @@ class Analyser extends DatabaseAnalyser {
.map(x => ({
...x,
objectId: x.pureName,
contentHash: x.modifyDate.toISOString(),
contentHash: x.modifyDate && x.modifyDate.toISOString(),
})),
views: tableModificationsQueryData.rows
.filter(x => x.objectType == 'VIEW')
.map(x => ({
...x,
objectId: x.pureName,
contentHash: x.modifyDate.toISOString(),
contentHash: x.modifyDate && x.modifyDate.toISOString(),
})),
procedures: procedureModificationsQueryData.rows.map(x => ({
contentHash: x.Modified,
@@ -4,9 +4,11 @@ const primaryKeys = require('./primaryKeys');
const foreignKeys = require('./foreignKeys');
const tableModifications = require('./tableModifications');
const views = require('./views');
const indexes = require('./indexes');
const programmables = require('./programmables');
const procedureModifications = require('./procedureModifications');
const functionModifications = require('./functionModifications');
const uniqueNames = require('./uniqueNames');
module.exports = {
columns,
@@ -18,4 +20,6 @@ module.exports = {
programmables,
procedureModifications,
functionModifications,
indexes,
uniqueNames,
};
@@ -0,0 +1,11 @@
module.exports = `
SELECT
INDEX_NAME AS constraintName,
TABLE_NAME AS tableName,
COLUMN_NAME AS columnName,
INDEX_TYPE AS indexType,
NON_UNIQUE AS nonUnique
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = '#DATABASE#' AND TABLE_NAME =OBJECT_ID_CONDITION AND INDEX_NAME != 'PRIMARY'
ORDER BY SEQ_IN_INDEX
`;
@@ -0,0 +1,5 @@
module.exports = `
select CONSTRAINT_NAME as constraintName
from information_schema.TABLE_CONSTRAINTS
where CONSTRAINT_SCHEMA = '#DATABASE#' and constraint_type = 'UNIQUE'
`;
@@ -32,7 +32,7 @@ class Dumper extends SqlDumper {
changeColumn(oldcol, newcol, constraints) {
this.put('^alter ^table %f ^change ^column %i %i ', oldcol, oldcol.columnName, newcol.columnName);
this.columnDefinition(newcol, true, true, true);
this.columnDefinition(newcol);
this.inlineConstraints(constraints);
this.endCommand();
}
@@ -13,6 +13,20 @@ const dialect = {
quoteIdentifier(s) {
return '`' + s + '`';
},
createColumn: true,
dropColumn: true,
createIndex: true,
dropIndex: true,
createForeignKey: true,
dropForeignKey: true,
createPrimaryKey: true,
dropPrimaryKey: true,
dropIndexContainsTableSpec: true,
createUnique: true,
dropUnique: true,
createCheck: true,
dropCheck: true,
};
const mysqlDriverBase = {
@@ -25,11 +25,13 @@ function getColumnInfo({
if (char_max_length && isTypeString(normDataType)) fullDataType = `${normDataType}(${char_max_length})`;
if (numeric_precision && numeric_ccale && isTypeNumeric(normDataType))
fullDataType = `${normDataType}(${numeric_precision},${numeric_ccale})`;
const autoIncrement = !!(default_value && default_value.startsWith('nextval('));
return {
columnName: column_name,
dataType: fullDataType,
notNull: !is_nullable || is_nullable == 'NO' || is_nullable == 'no',
defaultValue: default_value,
defaultValue: autoIncrement ? undefined : default_value,
autoIncrement,
};
}
@@ -39,7 +41,9 @@ class Analyser extends DatabaseAnalyser {
}
createQuery(resFileName, typeFields) {
return super.createQuery(sql[resFileName], typeFields);
return super
.createQuery(sql[resFileName], typeFields)
.replace('#REFTABLECOND#', this.driver.__analyserInternals.refTableCond);
}
async _computeSingleObjectId() {
@@ -63,6 +67,9 @@ class Analyser extends DatabaseAnalyser {
? await this.driver.query(this.pool, this.createQuery('matviewColumns', ['matviews']))
: null;
const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions']));
const indexes = await this.driver.query(this.pool, this.createQuery('indexes', ['tables']));
const indexcols = await this.driver.query(this.pool, this.createQuery('indexcols', ['tables']));
const uniqueNames = await this.driver.query(this.pool, this.createQuery('uniqueNames', ['tables']));
return {
tables: tables.rows.map(table => {
@@ -102,6 +109,43 @@ class Analyser extends DatabaseAnalyser {
refSchemaName: x.ref_schema_name,
}))
),
indexes: indexes.rows
.filter(
x =>
x.table_name == table.pure_name &&
x.schema_name == table.schema_name &&
!uniqueNames.rows.find(y => y.constraint_name == x.index_name)
)
.map(idx => ({
constraintName: idx.index_name,
isUnique: idx.is_unique,
columns: _.compact(
idx.indkey
.split(' ')
.map(colid => indexcols.rows.find(col => col.oid == idx.oid && col.attnum == colid))
.map(col => ({
columnName: col.column_name,
}))
),
})),
uniques: indexes.rows
.filter(
x =>
x.table_name == table.pure_name &&
x.schema_name == table.schema_name &&
uniqueNames.rows.find(y => y.constraint_name == x.index_name)
)
.map(idx => ({
constraintName: idx.index_name,
columns: _.compact(
idx.indkey
.split(' ')
.map(colid => indexcols.rows.find(col => col.oid == idx.oid && col.attnum == colid))
.map(col => ({
columnName: col.column_name,
}))
),
})),
};
}),
views: views.rows.map(view => ({
@@ -139,6 +183,7 @@ class Analyser extends DatabaseAnalyser {
.filter(x => x.object_type == 'FUNCTION')
.map(func => ({
objectId: `functions:${func.schema_name}.${func.pure_name}`,
createSql: `CREATE FUNCTION "${func.schema_name}"."${func.pure_name}"() RETURNS ${func.data_type} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`,
pureName: func.pure_name,
schemaName: func.schema_name,
contentHash: func.hash_code,
@@ -74,7 +74,7 @@ const drivers = driverBases.map(driverBase => ({
}
const res = await client.query({ text: sql, rowMode: 'array' });
const columns = extractPostgresColumns(res);
return { rows: res.rows.map(row => zipDataRow(row, columns)), columns };
return { rows: (res.rows || []).map(row => zipDataRow(row, columns)), columns };
},
stream(client, sql, options) {
const query = new pg.Query({
@@ -12,7 +12,7 @@ select
refcol.column_name as "ref_column_name"
from information_schema.referential_constraints fk
inner join information_schema.table_constraints base on fk.constraint_name = base.constraint_name and fk.constraint_schema = base.constraint_schema
inner join information_schema.table_constraints ref on fk.unique_constraint_name = ref.constraint_name and fk.unique_constraint_schema = ref.constraint_schema
inner join information_schema.table_constraints ref on fk.unique_constraint_name = ref.constraint_name and fk.unique_constraint_schema = ref.constraint_schema #REFTABLECOND#
inner join information_schema.key_column_usage basecol on base.table_name = basecol.table_name and base.constraint_name = basecol.constraint_name
inner join information_schema.key_column_usage refcol on ref.table_name = refcol.table_name and ref.constraint_name = refcol.constraint_name and basecol.ordinal_position = refcol.ordinal_position
where
@@ -10,6 +10,9 @@ const matviews = require('./matviews');
const routines = require('./routines');
const routineModifications = require('./routineModifications');
const matviewColumns = require('./matviewColumns');
const indexes = require('./indexes');
const indexcols = require('./indexcols');
const uniqueNames = require('./uniqueNames');
module.exports = {
columns,
@@ -24,4 +27,7 @@ module.exports = {
matviews,
matviewModifications,
matviewColumns,
indexes,
indexcols,
uniqueNames,
};
@@ -0,0 +1,24 @@
module.exports = `
select
a.attname as "column_name",
a.attnum as "attnum",
a.attrelid as "oid"
from
pg_class t,
pg_class i,
pg_attribute a,
pg_index ix,
pg_namespace c
where
t.oid = ix.indrelid
and a.attnum = ANY(ix.indkey)
and a.attrelid = t.oid
and i.oid = ix.indexrelid
and t.relkind = 'r'
and ix.indisprimary = false
and t.relnamespace = c.oid
and c.nspname != 'pg_catalog'
and ('tables:' || c.nspname || '.' || t.relname) =OBJECT_ID_CONDITION
order by
t.relname
`;
@@ -0,0 +1,25 @@
module.exports = `
select
t.relname as "table_name",
c.nspname as "schema_name",
i.relname as "index_name",
ix.indisprimary as "is_primary",
ix.indisunique as "is_unique",
ix.indkey as "indkey",
t.oid as "oid"
from
pg_class t,
pg_class i,
pg_index ix,
pg_namespace c
where
t.oid = ix.indrelid
and i.oid = ix.indexrelid
and t.relkind = 'r'
and ix.indisprimary = false
and t.relnamespace = c.oid
and c.nspname != 'pg_catalog'
and ('tables:' || c.nspname || '.' || t.relname) =OBJECT_ID_CONDITION
order by
t.relname
`;
@@ -5,6 +5,7 @@ select
routine_definition as "definition",
md5(routine_definition) as "hash_code",
routine_type as "object_type",
data_type as "data_type",
external_language as "language"
from
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
@@ -0,0 +1,3 @@
module.exports = `
select conname as "constraint_name" from pg_constraint where contype = 'u'
`;
@@ -1,4 +1,4 @@
const { SqlDumper } = global.DBGATE_TOOLS;
const { SqlDumper, testEqualTypes } = global.DBGATE_TOOLS;
class Dumper extends SqlDumper {
/** @param type {import('dbgate-types').TransformType} */
@@ -41,7 +41,7 @@ class Dumper extends SqlDumper {
dropTable(obj, options = {}) {
this.put('^drop ^table');
if (options.testIfExists) this.put(' ^if ^exists');
this.put(' %f', obj.FullName);
this.put(' %f', obj);
this.endCommand();
}
@@ -61,6 +61,37 @@ 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, oldcol.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
);
}
}
}
putValue(value) {
if (value === true) this.putRaw('true');
else if (value === false) this.putRaw('false');
else super.putValue(value);
}
}
module.exports = Dumper;
@@ -5,15 +5,30 @@ const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options');
/** @type {import('dbgate-types').SqlDialect} */
const dialect = {
rangeSelect: true,
ilike: true,
// stringEscapeChar: '\\',
stringEscapeChar: "'",
fallbackDataType: 'varchar',
anonymousPrimaryKey: true,
enableConstraintsPerTable: true,
dropColumnDependencies: ['dependencies'],
quoteIdentifier(s) {
return '"' + s + '"';
},
stringAgg: true,
createColumn: true,
dropColumn: true,
createIndex: true,
dropIndex: true,
createForeignKey: true,
dropForeignKey: true,
createPrimaryKey: true,
dropPrimaryKey: true,
createUnique: true,
dropUnique: true,
createCheck: true,
dropCheck: true,
};
const postgresDriverBase = {
@@ -23,6 +38,10 @@ const postgresDriverBase = {
showConnectionField: (field, values) =>
['server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase'].includes(field),
getQuerySplitterOptions: () => postgreSplitterOptions,
__analyserInternals: {
refTableCond: '',
},
};
/** @type {import('dbgate-types').EngineDriver} */
@@ -46,6 +65,11 @@ const cockroachDriver = {
dialect: {
...dialect,
materializedViews: true,
dropColumnDependencies: ['primaryKey', 'dependencies'],
dropPrimaryKey: false,
},
__analyserInternals: {
refTableCond: 'and fk.referenced_table_name = ref.table_name',
},
};
@@ -1,6 +1,20 @@
const _ = require('lodash');
const { DatabaseAnalyser } = require('dbgate-tools');
const indexcolsQuery = `
SELECT
m.name as tableName,
il.name as constraintName,
il."unique" as isUnique,
ii.name as columnName,
il.origin
FROM sqlite_schema AS m,
pragma_index_list(m.name) AS il,
pragma_index_info(il.name) AS ii
WHERE m.type='table' AND il.origin <> 'pk'
ORDER BY ii.seqno
`;
class Analyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
@@ -51,6 +65,8 @@ class Analyser extends DatabaseAnalyser {
createSql: x.sql,
}));
const indexcols = await this.driver.query(this.pool, indexcolsQuery);
for (const tableName of this.getRequestedObjectPureNames(
'tables',
tables.map((x) => x.name)
@@ -67,6 +83,29 @@ class Analyser extends DatabaseAnalyser {
autoIncrement: tableSqls[tableName].toLowerCase().includes('autoincrement') && !!col.pk,
}));
const indexNames = _.uniq(
indexcols.rows.filter((x) => x.tableName == tableName && x.origin == 'c').map((x) => x.constraintName)
);
tableObj.indexes = indexNames.map((idx) => ({
constraintName: idx,
isUnique: !!indexcols.rows.find((x) => x.tableName == tableName && x.constraintName == idx).isUnique,
columns: indexcols.rows
.filter((x) => x.tableName == tableName && x.constraintName == idx)
.map(({ columnName }) => ({ columnName })),
}));
const uniqueNames = _.uniq(
indexcols.rows.filter((x) => x.tableName == tableName && x.origin == 'u').map((x) => x.constraintName)
);
tableObj.uniques = uniqueNames.map((idx) => ({
constraintName: idx,
columns: indexcols.rows
.filter((x) => x.tableName == tableName && x.constraintName == idx)
.map(({ columnName }) => ({ columnName })),
}));
const pkColumns = info.rows
.filter((x) => x.pk)
.map((col) => ({
@@ -95,8 +134,6 @@ class Analyser extends DatabaseAnalyser {
};
return fk;
});
// console.log(info);
}
for (const viewName of this.getRequestedObjectPureNames(
@@ -1,5 +1,13 @@
const { SqlDumper } = global.DBGATE_TOOLS;
class Dumper extends SqlDumper {}
class Dumper extends SqlDumper {
renameColumn(column, newcol) {
this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol);
}
renameTable(obj, newname) {
this.putCmd('^alter ^table %f ^rename ^to %i', obj, newname);
}
}
module.exports = Dumper;
@@ -14,11 +14,22 @@ const dialect = {
limitSelect: true,
rangeSelect: true,
offsetFetchRangeSyntax: false,
explicitDropConstraint: true,
stringEscapeChar: "'",
fallbackDataType: 'nvarchar(max)',
dropColumnDependencies: ['indexes', 'primaryKey', 'uniques'],
quoteIdentifier(s) {
return `[${s}]`;
},
createColumn: true,
dropColumn: true,
createIndex: true,
dropIndex: true,
createForeignKey: false,
dropForeignKey: false,
createPrimaryKey: false,
dropPrimaryKey: false,
};
/** @type {import('dbgate-types').EngineDriver} */