Compare commits
123 Commits
develop
...
v5.5.3-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
555f30c0b3 | ||
|
|
7549d37a04 | ||
|
|
29072eb71b | ||
|
|
48e9e77be5 | ||
|
|
4dd3f15ba3 | ||
|
|
3603501ae2 | ||
|
|
338180a21a | ||
|
|
28193ed6f3 | ||
|
|
0509710602 | ||
|
|
a4872b4159 | ||
|
|
888f5c6260 | ||
|
|
7a5abb5f47 | ||
|
|
61a9f02899 | ||
|
|
354c4201f7 | ||
|
|
d8340087c5 | ||
|
|
e3249c6d79 | ||
|
|
58e65608e4 | ||
|
|
2b6fdf5a6a | ||
|
|
967d7849ee | ||
|
|
7c476ab2f0 | ||
|
|
caaf35e45a | ||
|
|
6ddc9ee6c5 | ||
|
|
39aa250223 | ||
|
|
031a92db8e | ||
|
|
0c2579897f | ||
|
|
b63479bf45 | ||
|
|
4c89552265 | ||
|
|
517002e079 | ||
|
|
85bfb1986d | ||
|
|
632421eb73 | ||
|
|
21365be411 | ||
|
|
eaa54022fc | ||
|
|
55f7f39efd | ||
|
|
71e709b346 | ||
|
|
1d2d295a45 | ||
|
|
5ed23beff0 | ||
|
|
43a8db55a2 | ||
|
|
0a9cba7bf7 | ||
|
|
02af761bf7 | ||
|
|
6882a146e7 | ||
|
|
c9834f9792 | ||
|
|
21b4baf700 | ||
|
|
d3a24627dd | ||
|
|
8aac9cf59d | ||
|
|
ce70b2e71a | ||
|
|
62a5ef60f6 | ||
|
|
95430e9c11 | ||
|
|
1cee36cc9b | ||
|
|
aa475f81a0 | ||
|
|
1173d5db1d | ||
|
|
f34d0cbb90 | ||
|
|
780d187911 | ||
|
|
48d4374346 | ||
|
|
6b8b511d0d | ||
|
|
c6be115634 | ||
|
|
dadde225f1 | ||
|
|
31dfc1dc28 | ||
|
|
1de163af44 | ||
|
|
2181eada53 | ||
|
|
75e63d2710 | ||
|
|
fbfcdcbc40 | ||
|
|
0238e6a7f1 | ||
|
|
be17301c91 | ||
|
|
b1118c7f43 | ||
|
|
24bf5e5b0c | ||
|
|
122471f81f | ||
|
|
a6136cee25 | ||
|
|
83357ba2cc | ||
|
|
4fe10b26b0 | ||
|
|
485f6c9759 | ||
|
|
732c5b763b | ||
|
|
4431d08a88 | ||
|
|
cb7224ac94 | ||
|
|
66b39c1f80 | ||
|
|
b30f139b5d | ||
|
|
f39ec26c29 | ||
|
|
8c3c32aeba | ||
|
|
9eb27f5e92 | ||
|
|
3e5b45de8f | ||
|
|
e7b4a6ffcc | ||
|
|
e7ec75138d | ||
|
|
6c4679d83b | ||
|
|
5f23b29c4e | ||
|
|
55db98fe1b | ||
|
|
f7c5ffa0ce | ||
|
|
d1e98e5640 | ||
|
|
e785fdf9b7 | ||
|
|
fc0db925c5 | ||
|
|
5ab686b721 | ||
|
|
327d43096f | ||
|
|
1f7b632553 | ||
|
|
592d7987ab | ||
|
|
c429424fda | ||
|
|
0b4709d383 | ||
|
|
336929ff3f | ||
|
|
677f83cc4b | ||
|
|
5c58c35a64 | ||
|
|
b346a458a6 | ||
|
|
226512a4ca | ||
|
|
a0527d78e9 | ||
|
|
3357295d98 | ||
|
|
fc6a43b4fe | ||
|
|
260b2e4b12 | ||
|
|
f080b18d3f | ||
|
|
56f015ffd5 | ||
|
|
fd8a28831e | ||
|
|
503e09ddd1 | ||
|
|
880912806a | ||
|
|
665ce22741 | ||
|
|
e5c9ec7681 | ||
|
|
74fceeec78 | ||
|
|
77d60ccfa5 | ||
|
|
0c2b25f79a | ||
|
|
4065e05013 | ||
|
|
319a7fd003 | ||
|
|
26c01f43f9 | ||
|
|
88d7e07bea | ||
|
|
a9a5a3491e | ||
|
|
d255273368 | ||
|
|
a7846b4adf | ||
|
|
ce431e6e21 | ||
|
|
f8e39a2a5d | ||
|
|
e5135b1a9d |
1
.github/workflows/run-tests.yaml
vendored
1
.github/workflows/run-tests.yaml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- 'feature/**'
|
||||
|
||||
jobs:
|
||||
test-runner:
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -8,10 +8,40 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### 5.5.2
|
||||
- FIXED: MySQL, PostgreSQL readonly conections #900
|
||||
|
||||
### 5.5.1
|
||||
- ADDED: Clickhouse support (#532)
|
||||
- ADDED: MySQL - specify table engine, show table engine in table list
|
||||
- FIXED: Hidden primary key name in PK editor for DB engines with anonymous PK (MySQL)
|
||||
- CHANGED: Import/export dialog is now tacub instead of modal
|
||||
- ADDED: Saving import/export job
|
||||
- REMOVED: Ability to reopen export/import wizard from generated script. This was a bit hack, now you could save import/export job instead
|
||||
- ADDED: Autodetect CSV delimited
|
||||
- FIXED: Import CSV files with spaces around quotes
|
||||
- ADDED: JSON file import
|
||||
- ADDED: JSON export can export objects with ID field used as object key
|
||||
- ADDED: JSON and JSON lines imports supports importing from web URL
|
||||
- FIXED: Editing imported URL in job editor
|
||||
- ADDED: Quick export from table data grid (#892)
|
||||
- CHANGED: Create table workflow is reworked, you can specify schema and table name in table editor
|
||||
- FIXED: After saving new table, table editor is reset to empty state
|
||||
- ADDED: (PostgreSQL, SQL Server) - ability to filter objects by schema
|
||||
- ADDED: (PostgreSQL, SQL Server) - Use separate schemas option - for databases with lot of schemas, only selected schema is loaded
|
||||
- FIXED: Internal refactor of drivers, client objects are not more messed up with auxiliary fields
|
||||
- ADDED: Copy connection error to clipboard after clicking on error icon
|
||||
- FIXED: (MySQL) Fixed importing SQL dump exported from mysqldump (#702)
|
||||
- FIXED: (PostgreSQL) Fixed filtering JSONB fields (#889)
|
||||
- FIXED: OIDC authentication not working anymore (#891)
|
||||
- ADDED: Added tests for import from CSV and JSON
|
||||
- FIXED: multiple shortcuts handling #898
|
||||
- ADDED: (Premium) MS Entra authentization for Azure SQL databases
|
||||
|
||||
### 5.4.4
|
||||
- CHANGED: Improved autoupdate, notification is now in app
|
||||
- CHANGED: Default behaviour of autoupdate, new version is downloaded after click of "Download" button
|
||||
- ADDED: Ability to configure autoupdate (check only, check+download, don't check)\
|
||||
- ADDED: Ability to configure autoupdate (check only, check+download, don't check)
|
||||
- ADDED: Option to run check for new version manually
|
||||
- FIXED: Fixed autoupgrade channel for premium edition
|
||||
- FIXED: Fixes following issues: #886, #865, #782, #375
|
||||
|
||||
@@ -30,6 +30,7 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
* CockroachDB
|
||||
* MariaDB
|
||||
* CosmosDB (Premium)
|
||||
* ClickHouse
|
||||
|
||||
<!-- Learn more about DbGate features at the [DbGate website](https://dbgate.org/), or try our online [demo application](https://demo.dbgate.org) -->
|
||||
|
||||
@@ -69,8 +70,8 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
* Redis tree view, generate script from keys, run Redis script
|
||||
* Runs as application for Windows, Linux and Mac. Or in Docker container on server and in web Browser on client.
|
||||
* Import, export from/to CSV, Excel, JSON, NDJSON, XML
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
* Archives - backup your data in NDJSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* NDJSON data viewer and editor - browse NDJSON data, edit data and structure directly on NDJSON files. Works also for big NDSON files
|
||||
* Charts, export chart to HTML page
|
||||
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Extensible plugin architecture
|
||||
|
||||
@@ -67,3 +67,4 @@ describe('Alter database', () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
151
integration-tests/__tests__/import-formats.spec.js
Normal file
151
integration-tests/__tests__/import-formats.spec.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const dbgateApi = require('dbgate-api/src/shell');
|
||||
// const jsonLinesWriter = require('dbgate-api/src/shell/jsonLinesWriter');
|
||||
const tmp = require('tmp');
|
||||
// const dbgatePluginCsv = require('dbgate-plugin-csv/src/backend');
|
||||
const fs = require('fs');
|
||||
const requirePlugin = require('dbgate-api/src/shell/requirePlugin');
|
||||
|
||||
const CSV_DATA = `Issue Number; Title; Github URL; Labels; State; Created At; Updated At; Reporter; Assignee
|
||||
801; "Does it 'burst' the database on startup or first lUI load ? "; https://github.com/dbgate/dbgate/issues/801; ""; open; 05/23/2024; 05/23/2024; rgarrigue;
|
||||
799; "BUG: latest AppImage crashes on opening in Fedora 39"; https://github.com/dbgate/dbgate/issues/799; ""; open; 05/21/2024; 05/24/2024; BenGraham-Git;
|
||||
798; "MongoDB write operations fail"; https://github.com/dbgate/dbgate/issues/798; "bug,solved"; open; 05/21/2024; 05/24/2024; mahmed0715;
|
||||
797; "BUG: Unable to open SQL files"; https://github.com/dbgate/dbgate/issues/797; "bug"; open; 05/20/2024; 05/21/2024; cesarValdivia;
|
||||
795; "BUG: MS SQL Server connection error (KEY_USAGE_BIT_INCORRECT)"; https://github.com/dbgate/dbgate/issues/795; ""; open; 05/20/2024; 05/20/2024; keskinonur;
|
||||
794; "GLIBC_2.29' not found and i have 2.31"; https://github.com/dbgate/dbgate/issues/794; ""; closed; 05/20/2024; 05/21/2024; MFdanGM;
|
||||
793; "BUG: PostgresSQL doesn't show tables when connected"; https://github.com/dbgate/dbgate/issues/793; ""; open; 05/20/2024; 05/22/2024; stomper013;
|
||||
792; "FEAT: Wayland support"; https://github.com/dbgate/dbgate/issues/792; ""; closed; 05/19/2024; 05/21/2024; VosaXalo;
|
||||
`;
|
||||
|
||||
async function getReaderRows(reader) {
|
||||
const jsonLinesFileName = tmp.tmpNameSync();
|
||||
|
||||
const writer = await dbgateApi.jsonLinesWriter({
|
||||
fileName: jsonLinesFileName,
|
||||
});
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
|
||||
const jsonData = fs.readFileSync(jsonLinesFileName, 'utf-8');
|
||||
const rows = jsonData
|
||||
.split('\n')
|
||||
.filter(x => x.trim() !== '')
|
||||
.map(x => JSON.parse(x));
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
test('csv import test', async () => {
|
||||
const dbgatePluginCsv = requirePlugin('dbgate-plugin-csv');
|
||||
|
||||
const csvFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(csvFileName, CSV_DATA);
|
||||
|
||||
const reader = await dbgatePluginCsv.shellApi.reader({
|
||||
fileName: csvFileName,
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows[0].columns).toEqual([
|
||||
{ columnName: 'Issue Number' },
|
||||
{ columnName: 'Title' },
|
||||
{ columnName: 'Github URL' },
|
||||
{ columnName: 'Labels' },
|
||||
{ columnName: 'State' },
|
||||
{ columnName: 'Created At' },
|
||||
{ columnName: 'Updated At' },
|
||||
{ columnName: 'Reporter' },
|
||||
{ columnName: 'Assignee' },
|
||||
]);
|
||||
expect(rows.length).toEqual(9);
|
||||
expect(rows[1]).toEqual({
|
||||
'Issue Number': '801',
|
||||
Title: "Does it 'burst' the database on startup or first lUI load ? ",
|
||||
'Github URL': 'https://github.com/dbgate/dbgate/issues/801',
|
||||
Labels: '',
|
||||
State: 'open',
|
||||
'Created At': '05/23/2024',
|
||||
'Updated At': '05/23/2024',
|
||||
Reporter: 'rgarrigue',
|
||||
Assignee: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('JSON array import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify([
|
||||
{ id: 1, val: 'v1' },
|
||||
{ id: 2, val: 'v2' },
|
||||
])
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ id: 1, val: 'v1' },
|
||||
{ id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('JSON object import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify({
|
||||
k1: { id: 1, val: 'v1' },
|
||||
k2: { id: 2, val: 'v2' },
|
||||
})
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
jsonStyle: 'object',
|
||||
keyField: 'mykey',
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ mykey: 'k1', id: 1, val: 'v1' },
|
||||
{ mykey: 'k2', id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('JSON filtered object import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify({
|
||||
filtered: {
|
||||
k1: { id: 1, val: 'v1' },
|
||||
k2: { id: 2, val: 'v2' },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
jsonStyle: 'object',
|
||||
keyField: 'mykey',
|
||||
rootField: 'filtered',
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ mykey: 'k1', id: 1, val: 'v1' },
|
||||
{ mykey: 'k2', id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
90
integration-tests/__tests__/schema-tests.spec.js
Normal file
90
integration-tests/__tests__/schema-tests.spec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const _ = require('lodash');
|
||||
const fp = require('lodash/fp');
|
||||
const { testWrapper, extractConnection } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
const { runCommandOnDriver } = require('dbgate-tools');
|
||||
|
||||
async function baseStructure(conn, driver) {
|
||||
await driver.query(conn, `create table t1 (id int not null primary key)`);
|
||||
|
||||
await driver.query(
|
||||
conn,
|
||||
`create table t2 (
|
||||
id int not null primary key,
|
||||
t1_id int
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
describe('Schema tests', () => {
|
||||
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||
'Create schema - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
const schemas1 = await driver.listSchemas(conn);
|
||||
expect(schemas1.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||
const count = schemas1.length;
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
const schemas2 = await driver.listSchemas(conn);
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||
expect(schemas2.length).toEqual(count + 1);
|
||||
expect(schemas2.find(x => x.isDefault).schemaName).toEqual(engine.defaultSchemaName);
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||
'Drop schema - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
const schemas1 = await driver.listSchemas(conn);
|
||||
expect(schemas1.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.dropSchema('myschema'));
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
const schemas2 = await driver.listSchemas(conn);
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.supportSchemas && !x.skipSeparateSchemas).map(engine => [engine.label, engine]))(
|
||||
'Table inside schema - %s',
|
||||
testWrapper(async (handle, driver, engine) => {
|
||||
await baseStructure(handle, driver);
|
||||
await runCommandOnDriver(handle, driver, dmp => dmp.createSchema('myschema'));
|
||||
|
||||
const schemaConnDef = {
|
||||
...extractConnection(engine),
|
||||
database: `${handle.database}::myschema`,
|
||||
};
|
||||
|
||||
const schemaConn = await driver.connect(schemaConnDef);
|
||||
await driver.query(schemaConn, `create table myschema.myt1 (id int not null primary key)`);
|
||||
const structure1 = await driver.analyseFull(schemaConn);
|
||||
expect(structure1.tables.length).toEqual(1);
|
||||
expect(structure1.tables[0].pureName).toEqual('myt1');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('Base analyser test', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Structure without change - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
version: '3'
|
||||
services:
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# restart: always
|
||||
# environment:
|
||||
# POSTGRES_PASSWORD: Pwd2020Db
|
||||
# ports:
|
||||
# - 15000:5432
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Pwd2020Db
|
||||
ports:
|
||||
- 15000:5432
|
||||
|
||||
# mariadb:
|
||||
# image: mariadb
|
||||
@@ -26,13 +26,13 @@ services:
|
||||
# environment:
|
||||
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
|
||||
clickhouse:
|
||||
image: bitnami/clickhouse:24.8.4
|
||||
restart: always
|
||||
ports:
|
||||
- 15005:8123
|
||||
environment:
|
||||
- CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
||||
# clickhouse:
|
||||
# image: bitnami/clickhouse:24.8.4
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15005:8123
|
||||
# environment:
|
||||
# - CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
||||
|
||||
# mssql:
|
||||
# image: mcr.microsoft.com/mssql/server
|
||||
|
||||
@@ -81,6 +81,8 @@ const engines = [
|
||||
drop2: 'DROP FUNCTION obj2',
|
||||
},
|
||||
],
|
||||
supportSchemas: true,
|
||||
defaultSchemaName: 'public',
|
||||
},
|
||||
{
|
||||
label: 'SQL Server',
|
||||
@@ -105,6 +107,9 @@ const engines = [
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
supportSchemas: true,
|
||||
defaultSchemaName: 'dbo',
|
||||
// skipSeparateSchemas: true,
|
||||
},
|
||||
{
|
||||
label: 'SQLite',
|
||||
@@ -113,6 +118,7 @@ const engines = [
|
||||
engine: 'sqlite@dbgate-plugin-sqlite',
|
||||
},
|
||||
objects: [views],
|
||||
skipOnCI: false,
|
||||
},
|
||||
{
|
||||
label: 'CockroachDB',
|
||||
@@ -161,9 +167,9 @@ const filterLocal = [
|
||||
'-MariaDB',
|
||||
'-PostgreSQL',
|
||||
'-SQL Server',
|
||||
'-SQLite',
|
||||
'SQLite',
|
||||
'-CockroachDB',
|
||||
'ClickHouse',
|
||||
'-ClickHouse',
|
||||
];
|
||||
|
||||
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js",
|
||||
"test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest",
|
||||
"test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js",
|
||||
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults",
|
||||
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit",
|
||||
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
|
||||
},
|
||||
"jest": {
|
||||
@@ -22,6 +22,7 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^27.0.1",
|
||||
"pino-pretty": "^11.2.2"
|
||||
"pino-pretty": "^11.2.2",
|
||||
"tmp": "^0.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
};
|
||||
|
||||
const { prettyFactory } = require('pino-pretty');
|
||||
const tmp = require('tmp');
|
||||
|
||||
const pretty = prettyFactory({
|
||||
colorize: true,
|
||||
@@ -20,3 +26,5 @@ global.console = {
|
||||
process.stdout.write(messages.join(' ') + '\n');
|
||||
},
|
||||
};
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
};
|
||||
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const crypto = require('crypto');
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.4.5-beta.6",
|
||||
"version": "5.5.3-beta.3",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -36,6 +36,7 @@
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:web:docker": "yarn workspace dbgate-web build",
|
||||
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||
"build:plugins:backend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:backend",
|
||||
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
|
||||
"storage-json": "dbmodel model-to-json storage-db packages/api/src/storageModel.js --commonjs",
|
||||
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"rimraf": "^3.0.0",
|
||||
"simple-encryptor": "^4.0.0",
|
||||
"ssh2": "^1.11.0",
|
||||
"stream-json": "^1.8.0",
|
||||
"tar": "^6.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -76,6 +76,7 @@ function getPortalCollections() {
|
||||
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
|
||||
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
||||
parent: process.env[`PARENT_${id}`] || undefined,
|
||||
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
|
||||
|
||||
// SSH tunnel
|
||||
useSshTunnel: process.env[`USE_SSH_${id}`],
|
||||
|
||||
@@ -213,6 +213,17 @@ module.exports = {
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
schemaList_meta: true,
|
||||
async schemaList({ conid, database }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.loadDataCore('schemaList', { conid, database });
|
||||
},
|
||||
|
||||
dispatchDatabaseChangedEvent_meta: true,
|
||||
dispatchDatabaseChangedEvent({ event, conid, database }) {
|
||||
socket.emitChanged(event, { conid, database });
|
||||
},
|
||||
|
||||
loadKeys_meta: true,
|
||||
async loadKeys({ conid, database, root, filter }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
|
||||
@@ -18,11 +18,14 @@ function readFirstLine(file) {
|
||||
}
|
||||
if (reader.hasNextLine()) {
|
||||
reader.nextLine((err, line) => {
|
||||
if (err) reject(err);
|
||||
resolve(line);
|
||||
if (err) {
|
||||
reader.close(() => reject(err)); // Ensure reader is closed on error
|
||||
return;
|
||||
}
|
||||
reader.close(() => resolve(line)); // Ensure reader is closed after reading
|
||||
});
|
||||
} else {
|
||||
resolve(null);
|
||||
reader.close(() => resolve(null)); // Properly close if no lines are present
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const { splitQuery } = require('dbgate-query-splitter');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger } = require('dbgate-tools');
|
||||
const {
|
||||
extractBoolSettingsValue,
|
||||
extractIntSettingsValue,
|
||||
getLogger,
|
||||
isCompositeDbName,
|
||||
dbNameLogCategory,
|
||||
} = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
@@ -11,7 +17,7 @@ const { dumpSqlSelect } = require('dbgate-sqltree');
|
||||
|
||||
const logger = getLogger('dbconnProcess');
|
||||
|
||||
let systemConnection;
|
||||
let dbhan;
|
||||
let storedConnection;
|
||||
let afterConnectCallbacks = [];
|
||||
let afterAnalyseCallbacks = [];
|
||||
@@ -28,6 +34,25 @@ function getStatusCounter() {
|
||||
return statusCounter;
|
||||
}
|
||||
|
||||
function extractErrorMessage(err, defaultMessage) {
|
||||
if (!err) {
|
||||
return defaultMessage;
|
||||
}
|
||||
if (err.errors) {
|
||||
try {
|
||||
return err.errors.map(x => x.message).join('\n');
|
||||
} catch (e2) {}
|
||||
}
|
||||
if (err.message) {
|
||||
return err.message;
|
||||
}
|
||||
const s = `${err}`;
|
||||
if (s && (!s.endsWith('Error') || s.includes(' '))) {
|
||||
return s;
|
||||
}
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
async function checkedAsyncCall(promise) {
|
||||
try {
|
||||
const res = await promise;
|
||||
@@ -35,7 +60,7 @@ async function checkedAsyncCall(promise) {
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
message: extractErrorMessage(err, 'Checked call error'),
|
||||
});
|
||||
// console.error(err);
|
||||
setTimeout(() => process.exit(1), 1000);
|
||||
@@ -46,10 +71,16 @@ async function checkedAsyncCall(promise) {
|
||||
let loadingModel = false;
|
||||
|
||||
async function handleFullRefresh() {
|
||||
if (storedConnection.useSeparateSchemas && !isCompositeDbName(dbhan?.database)) {
|
||||
resolveAnalysedPromises();
|
||||
// skip loading DB structure
|
||||
return;
|
||||
}
|
||||
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('loadStructure');
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection, serverVersion));
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(dbhan, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||
process.send({ msgtype: 'structureTime', analysedTime });
|
||||
@@ -60,12 +91,15 @@ async function handleFullRefresh() {
|
||||
}
|
||||
|
||||
async function handleIncrementalRefresh(forceSend) {
|
||||
if (storedConnection.useSeparateSchemas && !isCompositeDbName(dbhan?.database)) {
|
||||
resolveAnalysedPromises();
|
||||
// skip loading DB structure
|
||||
return;
|
||||
}
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('checkStructure');
|
||||
const newStructure = await checkedAsyncCall(
|
||||
driver.analyseIncremental(systemConnection, analysedStructure, serverVersion)
|
||||
);
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(dbhan, analysedStructure, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
if (newStructure != null) {
|
||||
analysedStructure = newStructure;
|
||||
@@ -103,7 +137,8 @@ function setStatusName(name) {
|
||||
|
||||
async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
const version = await driver.getVersion(dbhan);
|
||||
logger.debug(`Got server version: ${version.version}`);
|
||||
process.send({ msgtype: 'version', version });
|
||||
serverVersion = version;
|
||||
}
|
||||
@@ -114,8 +149,13 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
systemConnection.feedback = feedback => setStatus({ feedback });
|
||||
dbhan = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
logger.debug(
|
||||
`Connected to database, driver: ${storedConnection.engine}, separate schemas: ${
|
||||
storedConnection.useSeparateSchemas ? 'YES' : 'NO'
|
||||
}, 'DB: ${dbNameLogCategory(dbhan.database)} }`
|
||||
);
|
||||
dbhan.feedback = feedback => setStatus({ feedback });
|
||||
await checkedAsyncCall(readVersion());
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
@@ -138,7 +178,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
}
|
||||
|
||||
function waitConnected() {
|
||||
if (systemConnection) return Promise.resolve();
|
||||
if (dbhan) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
afterConnectCallbacks.push([resolve, reject]);
|
||||
});
|
||||
@@ -163,10 +203,14 @@ async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
await driver.script(systemConnection, sql, { useTransaction });
|
||||
await driver.script(dbhan, sql, { useTransaction });
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing SQL script'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,10 +219,14 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
await driver.operation(systemConnection, operation, { useTransaction });
|
||||
await driver.operation(dbhan, operation, { useTransaction });
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing DB operation'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +236,14 @@ async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
// console.log(sql);
|
||||
const res = await driver.query(systemConnection, sql);
|
||||
const res = await driver.query(dbhan, sql);
|
||||
process.send({ msgtype: 'response', msgid, ...res });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing SQL script'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,52 +254,64 @@ async function handleSqlSelect({ msgid, select }) {
|
||||
return handleQueryData({ msgid, sql: dmp.s }, true);
|
||||
}
|
||||
|
||||
async function handleDriverDataCore(msgid, callMethod) {
|
||||
async function handleDriverDataCore(msgid, callMethod, { logName }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const result = await callMethod(driver);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
logger.error(err, `Error when handling message ${logName}`);
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: extractErrorMessage(err, 'Error executing DB data') });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSchemaList({ msgid }) {
|
||||
logger.debug('Loading schema list');
|
||||
return handleDriverDataCore(msgid, driver => driver.listSchemas(dbhan), { logName: 'listSchemas' });
|
||||
}
|
||||
|
||||
async function handleCollectionData({ msgid, options }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.readCollection(systemConnection, options));
|
||||
return handleDriverDataCore(msgid, driver => driver.readCollection(dbhan, options), { logName: 'readCollection' });
|
||||
}
|
||||
|
||||
async function handleLoadKeys({ msgid, root, filter }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeys(systemConnection, root, filter));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeys(dbhan, root, filter), { logName: 'loadKeys' });
|
||||
}
|
||||
|
||||
async function handleExportKeys({ msgid, options }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.exportKeys(systemConnection, options));
|
||||
return handleDriverDataCore(msgid, driver => driver.exportKeys(dbhan, options), { logName: 'exportKeys' });
|
||||
}
|
||||
|
||||
async function handleLoadKeyInfo({ msgid, key }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(systemConnection, key));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(dbhan, key), { logName: 'loadKeyInfo' });
|
||||
}
|
||||
|
||||
async function handleCallMethod({ msgid, method, args }) {
|
||||
return handleDriverDataCore(msgid, driver => {
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only, cannot call custom methods');
|
||||
}
|
||||
return handleDriverDataCore(
|
||||
msgid,
|
||||
driver => {
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only, cannot call custom methods');
|
||||
}
|
||||
|
||||
ensureExecuteCustomScript(driver);
|
||||
return driver.callMethod(systemConnection, method, args);
|
||||
});
|
||||
ensureExecuteCustomScript(driver);
|
||||
return driver.callMethod(dbhan, method, args);
|
||||
},
|
||||
{ logName: `callMethod:${method}` }
|
||||
);
|
||||
}
|
||||
|
||||
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(systemConnection, key, cursor, count));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(dbhan, key, cursor, count), {
|
||||
logName: 'loadKeyTableRange',
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLoadFieldValues({ msgid, schemaName, pureName, field, search }) {
|
||||
return handleDriverDataCore(msgid, driver =>
|
||||
driver.loadFieldValues(systemConnection, { schemaName, pureName }, field, search)
|
||||
);
|
||||
return handleDriverDataCore(msgid, driver => driver.loadFieldValues(dbhan, { schemaName, pureName }, field, search), {
|
||||
logName: 'loadFieldValues',
|
||||
});
|
||||
}
|
||||
|
||||
function ensureExecuteCustomScript(driver) {
|
||||
@@ -264,10 +328,10 @@ async function handleUpdateCollection({ msgid, changeSet }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
ensureExecuteCustomScript(driver);
|
||||
const result = await driver.updateCollection(systemConnection, changeSet);
|
||||
const result = await driver.updateCollection(dbhan, changeSet);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: extractErrorMessage(err, 'Error updating collection') });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +341,7 @@ async function handleSqlPreview({ msgid, objects, options }) {
|
||||
|
||||
try {
|
||||
const dmp = driver.createDumper();
|
||||
const generator = new SqlGenerator(analysedStructure, options, objects, dmp, driver, systemConnection);
|
||||
const generator = new SqlGenerator(analysedStructure, options, objects, dmp, driver, dbhan);
|
||||
|
||||
await generator.dump();
|
||||
process.send({ msgtype: 'response', msgid, sql: dmp.s, isTruncated: generator.isTruncated });
|
||||
@@ -288,7 +352,12 @@ async function handleSqlPreview({ msgid, objects, options }) {
|
||||
}, 500);
|
||||
}
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, isError: true, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
isError: true,
|
||||
errorMessage: extractErrorMessage(err, 'Error generating SQL preview'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,14 +366,19 @@ async function handleGenerateDeploySql({ msgid, modelFolder }) {
|
||||
|
||||
try {
|
||||
const res = await generateDeploySql({
|
||||
systemConnection,
|
||||
systemConnection: dbhan,
|
||||
connection: storedConnection,
|
||||
analysedStructure,
|
||||
modelFolder,
|
||||
});
|
||||
process.send({ ...res, msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, isError: true, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
isError: true,
|
||||
errorMessage: extractErrorMessage(err, 'Error generating deploy SQL'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +411,7 @@ const messageHandlers = {
|
||||
loadFieldValues: handleLoadFieldValues,
|
||||
sqlSelect: handleSqlSelect,
|
||||
exportKeys: handleExportKeys,
|
||||
schemaList: handleSchemaList,
|
||||
// runCommand: handleRunCommand,
|
||||
};
|
||||
|
||||
@@ -362,7 +437,7 @@ function start() {
|
||||
await handleMessage(message);
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error in DB connection');
|
||||
process.send({ msgtype: 'error', error: err.message });
|
||||
process.send({ msgtype: 'error', error: extractErrorMessage(err, 'Error processing message') });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class ImportStream extends stream.Transform {
|
||||
}
|
||||
async _transform(chunk, encoding, cb) {
|
||||
try {
|
||||
await this.driver.script(this.pool, chunk);
|
||||
await this.driver.script(this.pool, chunk, { queryOptions: { importSqlDump: true } });
|
||||
} catch (err) {
|
||||
this.emit('error', err.message);
|
||||
}
|
||||
@@ -47,7 +47,9 @@ async function importDatabase({ connection = undefined, systemConnection = undef
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
logger.info(`Connected.`);
|
||||
|
||||
logger.info(`Input file: ${inputFile}`);
|
||||
const downloadedFile = await download(inputFile);
|
||||
logger.info(`Downloaded file: ${downloadedFile}`);
|
||||
|
||||
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
||||
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions('script'));
|
||||
|
||||
@@ -6,7 +6,7 @@ const copyStream = require('./copyStream');
|
||||
const fakeObjectReader = require('./fakeObjectReader');
|
||||
const consoleObjectWriter = require('./consoleObjectWriter');
|
||||
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||
const jsonArrayWriter = require('./jsonArrayWriter');
|
||||
const jsonWriter = require('./jsonWriter');
|
||||
const jsonLinesReader = require('./jsonLinesReader');
|
||||
const sqlDataWriter = require('./sqlDataWriter');
|
||||
const jslDataReader = require('./jslDataReader');
|
||||
@@ -29,6 +29,7 @@ const modifyJsonLinesReader = require('./modifyJsonLinesReader');
|
||||
const dataDuplicator = require('./dataDuplicator');
|
||||
const dbModelToJson = require('./dbModelToJson');
|
||||
const jsonToDbModel = require('./jsonToDbModel');
|
||||
const jsonReader = require('./jsonReader');
|
||||
|
||||
const dbgateApi = {
|
||||
queryReader,
|
||||
@@ -37,8 +38,9 @@ const dbgateApi = {
|
||||
tableReader,
|
||||
copyStream,
|
||||
jsonLinesWriter,
|
||||
jsonArrayWriter,
|
||||
jsonLinesReader,
|
||||
jsonReader,
|
||||
jsonWriter,
|
||||
sqlDataWriter,
|
||||
fakeObjectReader,
|
||||
consoleObjectWriter,
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
const logger = getLogger('jsonArrayWriter');
|
||||
|
||||
class StringifyStream extends stream.Transform {
|
||||
constructor() {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.wasRecord = false;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
let skip = false;
|
||||
|
||||
if (!this.wasHeader) {
|
||||
skip = chunk.__isStreamHeader;
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!skip) {
|
||||
if (!this.wasRecord) {
|
||||
this.push('[\n');
|
||||
} else {
|
||||
this.push(',\n');
|
||||
}
|
||||
this.wasRecord = true;
|
||||
|
||||
this.push(JSON.stringify(chunk));
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
_flush(done) {
|
||||
if (!this.wasRecord) {
|
||||
this.push('[]\n');
|
||||
} else {
|
||||
this.push('\n]\n');
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonArrayWriter({ fileName, encoding = 'utf-8' }) {
|
||||
logger.info(`Writing file ${fileName}`);
|
||||
const stringify = new StringifyStream();
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
return stringify;
|
||||
}
|
||||
|
||||
module.exports = jsonArrayWriter;
|
||||
@@ -2,6 +2,7 @@ const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const byline = require('byline');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const download = require('./download');
|
||||
const logger = getLogger('jsonLinesReader');
|
||||
|
||||
class ParseStream extends stream.Transform {
|
||||
@@ -35,8 +36,10 @@ class ParseStream extends stream.Transform {
|
||||
async function jsonLinesReader({ fileName, encoding = 'utf-8', limitRows = undefined }) {
|
||||
logger.info(`Reading file ${fileName}`);
|
||||
|
||||
const downloadedFile = await download(fileName);
|
||||
|
||||
const fileStream = fs.createReadStream(
|
||||
fileName,
|
||||
downloadedFile,
|
||||
// @ts-ignore
|
||||
encoding
|
||||
);
|
||||
|
||||
84
packages/api/src/shell/jsonReader.js
Normal file
84
packages/api/src/shell/jsonReader.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const byline = require('byline');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { parser } = require('stream-json');
|
||||
const { pick } = require('stream-json/filters/Pick');
|
||||
const { streamArray } = require('stream-json/streamers/StreamArray');
|
||||
const { streamObject } = require('stream-json/streamers/StreamObject');
|
||||
const download = require('./download');
|
||||
|
||||
const logger = getLogger('jsonReader');
|
||||
|
||||
|
||||
class ParseStream extends stream.Transform {
|
||||
constructor({ limitRows, jsonStyle, keyField }) {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.limitRows = limitRows;
|
||||
this.jsonStyle = jsonStyle;
|
||||
this.keyField = keyField || '_key';
|
||||
this.rowsWritten = 0;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (!this.wasHeader) {
|
||||
this.push({
|
||||
__isStreamHeader: true,
|
||||
__isDynamicStructure: true,
|
||||
});
|
||||
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!this.limitRows || this.rowsWritten < this.limitRows) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push({
|
||||
...chunk.value,
|
||||
[this.keyField]: chunk.key,
|
||||
});
|
||||
} else {
|
||||
this.push(chunk.value);
|
||||
}
|
||||
|
||||
this.rowsWritten += 1;
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonReader({
|
||||
fileName,
|
||||
jsonStyle,
|
||||
keyField = '_key',
|
||||
rootField = null,
|
||||
encoding = 'utf-8',
|
||||
limitRows = undefined,
|
||||
}) {
|
||||
logger.info(`Reading file ${fileName}`);
|
||||
|
||||
const downloadedFile = await download(fileName);
|
||||
const fileStream = fs.createReadStream(
|
||||
downloadedFile,
|
||||
// @ts-ignore
|
||||
encoding
|
||||
);
|
||||
const parseJsonStream = parser();
|
||||
fileStream.pipe(parseJsonStream);
|
||||
|
||||
const parseStream = new ParseStream({ limitRows, jsonStyle, keyField });
|
||||
|
||||
const tramsformer = jsonStyle === 'object' ? streamObject() : streamArray();
|
||||
|
||||
if (rootField) {
|
||||
const filterStream = pick({ filter: rootField });
|
||||
parseJsonStream.pipe(filterStream);
|
||||
filterStream.pipe(tramsformer);
|
||||
} else {
|
||||
parseJsonStream.pipe(tramsformer);
|
||||
}
|
||||
|
||||
tramsformer.pipe(parseStream);
|
||||
|
||||
return parseStream;
|
||||
}
|
||||
|
||||
module.exports = jsonReader;
|
||||
97
packages/api/src/shell/jsonWriter.js
Normal file
97
packages/api/src/shell/jsonWriter.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const _ = require('lodash');
|
||||
|
||||
const logger = getLogger('jsonArrayWriter');
|
||||
|
||||
class StringifyStream extends stream.Transform {
|
||||
constructor({ jsonStyle, keyField, rootField }) {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.wasRecord = false;
|
||||
this.jsonStyle = jsonStyle;
|
||||
this.keyField = keyField || '_key';
|
||||
this.rootField = rootField;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
let skip = false;
|
||||
|
||||
if (!this.wasHeader) {
|
||||
skip = chunk.__isStreamHeader;
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!skip) {
|
||||
if (!this.wasRecord) {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push(`{"${this.rootField}": {\n`);
|
||||
} else {
|
||||
this.push(`{"${this.rootField}": [\n`);
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('{\n');
|
||||
} else {
|
||||
this.push('[\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.push(',\n');
|
||||
}
|
||||
this.wasRecord = true;
|
||||
|
||||
if (this.jsonStyle === 'object') {
|
||||
const key = chunk[this.keyField] ?? chunk[Object.keys(chunk)[0]];
|
||||
this.push(`"${key}": ${JSON.stringify(_.omit(chunk, [this.keyField]))}`);
|
||||
} else {
|
||||
this.push(JSON.stringify(chunk));
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
_flush(done) {
|
||||
if (!this.wasRecord) {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push(`{"${this.rootField}": {}}\n`);
|
||||
} else {
|
||||
this.push(`{"${this.rootField}": []}\n`);
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('{}\n');
|
||||
} else {
|
||||
this.push('[]\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('\n}}\n');
|
||||
} else {
|
||||
this.push('\n]}\n');
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('\n}\n');
|
||||
} else {
|
||||
this.push('\n]\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonWriter({ fileName, jsonStyle, keyField = '_key', rootField, encoding = 'utf-8' }) {
|
||||
logger.info(`Writing file ${fileName}`);
|
||||
const stringify = new StringifyStream({ jsonStyle, keyField, rootField });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
return stringify;
|
||||
}
|
||||
|
||||
module.exports = jsonWriter;
|
||||
@@ -66,7 +66,7 @@ class ParseStream extends stream.Transform {
|
||||
...obj,
|
||||
...update.fields,
|
||||
},
|
||||
(v, k) => v.$$undefined$$
|
||||
(v, k) => v?.$$undefined$$
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ async function runScript(func) {
|
||||
await func();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Error running script: ${err.message}`);
|
||||
logger.error({ err }, `Error running script: ${err.message || err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ export abstract class GridDisplay {
|
||||
conditions.push(
|
||||
_.cloneDeepWith(condition, (expr: Expression) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return this.createColumnExpression(column, { alias: column.sourceAlias });
|
||||
return this.createColumnExpression(column, { alias: column.sourceAlias }, undefined, 'filter');
|
||||
}
|
||||
// return {
|
||||
// exprType: 'column',
|
||||
@@ -253,7 +253,7 @@ export abstract class GridDisplay {
|
||||
orCondition.conditions.push(
|
||||
_.cloneDeepWith(condition, (expr: Expression) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return this.createColumnExpression(column, { alias: 'basetbl' });
|
||||
return this.createColumnExpression(column, { alias: 'basetbl' }, undefined, 'filter');
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -278,7 +278,7 @@ export abstract class GridDisplay {
|
||||
|
||||
applySortOnSelect(select: Select, displayedColumnInfo: DisplayedColumnInfo) {
|
||||
if (this.config.sort?.length > 0) {
|
||||
select.orderBy = this.config.sort
|
||||
const orderByColumns = this.config.sort
|
||||
.map(col => ({ ...col, dispInfo: displayedColumnInfo[col.uniqueName] }))
|
||||
.map(col => ({ ...col, expr: select.columns.find(x => x.alias == col.uniqueName) }))
|
||||
.filter(col => col.dispInfo && col.expr)
|
||||
@@ -286,6 +286,10 @@ export abstract class GridDisplay {
|
||||
...col.expr,
|
||||
direction: col.order,
|
||||
}));
|
||||
|
||||
if (orderByColumns.length > 0) {
|
||||
select.orderBy = orderByColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,10 +574,10 @@ export abstract class GridDisplay {
|
||||
|
||||
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {}
|
||||
|
||||
createColumnExpression(col, source, alias?) {
|
||||
createColumnExpression(col, source, alias?, purpose: 'view' | 'filter' = 'view') {
|
||||
let expr = null;
|
||||
if (this.dialect.createColumnViewExpression) {
|
||||
expr = this.dialect.createColumnViewExpression(col.columnName, col.dataType, source, alias);
|
||||
expr = this.dialect.createColumnViewExpression(col.columnName, col.dataType, source, alias, purpose);
|
||||
if (expr) {
|
||||
return expr;
|
||||
}
|
||||
@@ -595,7 +599,7 @@ export abstract class GridDisplay {
|
||||
name: _.pick(name, ['schemaName', 'pureName']),
|
||||
alias: 'basetbl',
|
||||
},
|
||||
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' })),
|
||||
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' }, undefined, 'view')),
|
||||
orderBy: this.driver?.requiresDefaultSortCriteria
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ export class TableGridDisplay extends GridDisplay {
|
||||
? this.table.primaryKey.columns.map(x => x.columnName)
|
||||
: this.table.columns.map(x => x.columnName);
|
||||
}
|
||||
|
||||
|
||||
if (this.config.isFormView) {
|
||||
this.addAllExpandedColumnsToSelected = true;
|
||||
this.hintBaseColumns = this.formColumns;
|
||||
@@ -287,7 +287,7 @@ export class TableGridDisplay extends GridDisplay {
|
||||
for (const column of columns) {
|
||||
if (this.addAllExpandedColumnsToSelected || this.config.addedColumns.includes(column.uniqueName)) {
|
||||
select.columns.push(
|
||||
this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName)
|
||||
this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName, 'view')
|
||||
);
|
||||
displayedColumnInfo[column.uniqueName] = {
|
||||
...column,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { DatabaseInfo, DatabaseModification, EngineDriver, SqlDialect } from 'dbgate-types';
|
||||
import { DatabaseHandle, DatabaseInfo, DatabaseModification, EngineDriver, SqlDialect } from 'dbgate-types';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _pick from 'lodash/pick';
|
||||
import _compact from 'lodash/compact';
|
||||
import { getLogger } from './getLogger';
|
||||
import { type Logger } from 'pinomin';
|
||||
import { dbNameLogCategory, isCompositeDbName, splitCompositeDbName } from './schemaInfoTools';
|
||||
|
||||
const logger = getLogger('dbAnalyser');
|
||||
|
||||
@@ -40,7 +41,7 @@ export class DatabaseAnalyser {
|
||||
dialect: SqlDialect;
|
||||
logger: Logger;
|
||||
|
||||
constructor(public pool, public driver: EngineDriver, version) {
|
||||
constructor(public dbhan: DatabaseHandle, public driver: EngineDriver, version) {
|
||||
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(version)) || driver?.dialect;
|
||||
this.logger = logger;
|
||||
}
|
||||
@@ -68,6 +69,7 @@ export class DatabaseAnalyser {
|
||||
}
|
||||
|
||||
async fullAnalysis() {
|
||||
logger.info(`Performing full analysis, DB=${dbNameLogCategory(this.dbhan.database)}, engine=${this.driver.engine}`);
|
||||
const res = this.addEngineField(await this._runAnalysis());
|
||||
// console.log('FULL ANALYSIS', res);
|
||||
return res;
|
||||
@@ -88,6 +90,7 @@ export class DatabaseAnalyser {
|
||||
}
|
||||
|
||||
async incrementalAnalysis(structure) {
|
||||
logger.info(`Performing incremental analysis, DB=${dbNameLogCategory(this.dbhan.database)}, engine=${this.driver.engine}`);
|
||||
this.structure = structure;
|
||||
|
||||
const modifications = await this.getModifications();
|
||||
@@ -180,8 +183,19 @@ export class DatabaseAnalyser {
|
||||
// return this.createQueryCore('=OBJECT_ID_CONDITION', typeFields) != ' is not null';
|
||||
// }
|
||||
|
||||
getDefaultSchemaNameCondition() {
|
||||
return 'is not null';
|
||||
}
|
||||
|
||||
createQuery(template, typeFields, replacements = {}) {
|
||||
return this.createQueryCore(this.processQueryReplacements(template, replacements), typeFields);
|
||||
let query = this.createQueryCore(this.processQueryReplacements(template, replacements), typeFields);
|
||||
|
||||
const dbname = this.dbhan.database;
|
||||
const schemaCondition = isCompositeDbName(dbname)
|
||||
? `= '${splitCompositeDbName(dbname).schema}' `
|
||||
: ` ${this.getDefaultSchemaNameCondition()} `;
|
||||
|
||||
return query?.replace(/=SCHEMA_NAME_CONDITION/g, schemaCondition);
|
||||
}
|
||||
|
||||
processQueryReplacements(query, replacements) {
|
||||
@@ -242,8 +256,8 @@ export class DatabaseAnalyser {
|
||||
}
|
||||
|
||||
feedback(obj) {
|
||||
if (this.pool.feedback) {
|
||||
this.pool.feedback(obj);
|
||||
if (this.dbhan.feedback) {
|
||||
this.dbhan.feedback(obj);
|
||||
}
|
||||
if (obj && obj.analysingMessage) {
|
||||
logger.debug(obj.analysingMessage);
|
||||
@@ -318,7 +332,7 @@ export class DatabaseAnalyser {
|
||||
};
|
||||
}
|
||||
try {
|
||||
const res = await this.driver.query(this.pool, sql);
|
||||
const res = await this.driver.query(this.dbhan, sql);
|
||||
this.logger.debug({ rows: res.rows.length, template }, `Loaded analyser query`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
@@ -338,7 +352,6 @@ export class DatabaseAnalyser {
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
schemas: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,14 @@ export class SqlDumper implements AlterProcessor {
|
||||
this.putCmd('^drop ^database %i', name);
|
||||
}
|
||||
|
||||
createSchema(name: string) {
|
||||
this.putCmd('^create ^schema %i', name);
|
||||
}
|
||||
|
||||
dropSchema(name: string) {
|
||||
this.putCmd('^drop ^schema %i', name);
|
||||
}
|
||||
|
||||
specialColumnOptions(column) {}
|
||||
|
||||
selectScopeIdentity(table: TableInfo) {}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { prepareTableForImport } from './tableTransforms';
|
||||
|
||||
const logger = getLogger('bulkStreamBase');
|
||||
|
||||
export function createBulkInsertStreamBase(driver: EngineDriver, stream, pool, name, options: WriteTableOptions): any {
|
||||
export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan, name, options: WriteTableOptions): any {
|
||||
const fullNameQuoted = name.schemaName
|
||||
? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}`
|
||||
: driver.dialect.quoteIdentifier(name.pureName);
|
||||
@@ -29,22 +29,22 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, pool, n
|
||||
};
|
||||
|
||||
writable.checkStructure = async () => {
|
||||
let structure = await driver.analyseSingleTable(pool, name);
|
||||
let structure = await driver.analyseSingleTable(dbhan, name);
|
||||
// console.log('ANALYSING', name, structure);
|
||||
if (structure && options.dropIfExists) {
|
||||
logger.info(`Dropping table ${fullNameQuoted}`);
|
||||
await driver.script(pool, `DROP TABLE ${fullNameQuoted}`);
|
||||
await driver.script(dbhan, `DROP TABLE ${fullNameQuoted}`);
|
||||
}
|
||||
if (options.createIfNotExists && (!structure || options.dropIfExists)) {
|
||||
const dmp = driver.createDumper();
|
||||
const createdTableInfo = driver.adaptTableInfo(prepareTableForImport({ ...writable.structure, ...name }));
|
||||
dmp.createTable(createdTableInfo);
|
||||
logger.info({ sql: dmp.s }, `Creating table ${fullNameQuoted}`);
|
||||
await driver.script(pool, dmp.s);
|
||||
structure = await driver.analyseSingleTable(pool, name);
|
||||
await driver.script(dbhan, dmp.s);
|
||||
structure = await driver.analyseSingleTable(dbhan, name);
|
||||
}
|
||||
if (options.truncate) {
|
||||
await driver.script(pool, `TRUNCATE TABLE ${fullNameQuoted}`);
|
||||
await driver.script(dbhan, `TRUNCATE TABLE ${fullNameQuoted}`);
|
||||
}
|
||||
|
||||
writable.columnNames = _intersection(
|
||||
@@ -74,7 +74,7 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, pool, n
|
||||
dmp.putRaw(';');
|
||||
// require('fs').writeFileSync('/home/jena/test.sql', dmp.s);
|
||||
// console.log(dmp.s);
|
||||
await driver.query(pool, dmp.s, { discardResult: true });
|
||||
await driver.query(dbhan, dmp.s, { discardResult: true });
|
||||
} else {
|
||||
for (const row of rows) {
|
||||
const dmp = driver.createDumper();
|
||||
@@ -85,13 +85,13 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, pool, n
|
||||
dmp.putRaw('(');
|
||||
dmp.putCollection(',', writable.columnNames, col => dmp.putValue(row[col as string]));
|
||||
dmp.putRaw(')');
|
||||
await driver.query(pool, dmp.s, { discardResult: true });
|
||||
await driver.query(dbhan, dmp.s, { discardResult: true });
|
||||
}
|
||||
}
|
||||
if (options.commitAfterInsert) {
|
||||
const dmp = driver.createDumper();
|
||||
dmp.commitTransaction();
|
||||
await driver.query(pool, dmp.s, { discardResult: true });
|
||||
await driver.query(dbhan, dmp.s, { discardResult: true });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export const driverBase = {
|
||||
}
|
||||
for (const sqlItem of splitQuery(sql, this.getQuerySplitterOptions('script'))) {
|
||||
try {
|
||||
await this.query(pool, sqlItem, { discardResult: true });
|
||||
await this.query(pool, sqlItem, { discardResult: true, ...options?.queryOptions });
|
||||
} catch (err) {
|
||||
if (options?.useTransaction && this.supportsTransactions) {
|
||||
runCommandOnDriver(pool, this, dmp => dmp.rollbackTransaction());
|
||||
@@ -84,7 +84,17 @@ export const driverBase = {
|
||||
}
|
||||
},
|
||||
async operation(pool, operation, options: RunScriptOptions) {
|
||||
throw new Error('Operation not defined in target driver');
|
||||
const { type } = operation;
|
||||
switch (type) {
|
||||
case 'createSchema':
|
||||
await runCommandOnDriver(pool, this, dmp => dmp.createSchema(operation.schemaName));
|
||||
break;
|
||||
case 'dropSchema':
|
||||
await runCommandOnDriver(pool, this, dmp => dmp.dropSchema(operation.schemaName));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Operation type ${type} not supported`);
|
||||
}
|
||||
},
|
||||
getNewObjectTemplates() {
|
||||
if (this.databaseEngineTypes.includes('sql')) {
|
||||
@@ -180,5 +190,9 @@ export const driverBase = {
|
||||
|
||||
adaptTableInfo(table) {
|
||||
return table;
|
||||
}
|
||||
},
|
||||
|
||||
async listSchemas(pool) {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,3 +23,4 @@ export * from './getLogger';
|
||||
export * from './getConnectionLabel';
|
||||
export * from './detectSqlFilterBehaviour';
|
||||
export * from './filterBehaviours';
|
||||
export * from './schemaInfoTools';
|
||||
|
||||
48
packages/tools/src/schemaInfoTools.ts
Normal file
48
packages/tools/src/schemaInfoTools.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { SchemaInfo, SqlDialect } from 'dbgate-types';
|
||||
|
||||
export function findDefaultSchema(schemaList: SchemaInfo[], dialect: SqlDialect, schemaInStorage: string = null) {
|
||||
if (!schemaList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (schemaInStorage && schemaList.find(x => x.schemaName == schemaInStorage)) {
|
||||
return schemaInStorage;
|
||||
}
|
||||
|
||||
const dynamicDefaultSchema = schemaList.find(x => x.isDefault);
|
||||
if (dynamicDefaultSchema) {
|
||||
return dynamicDefaultSchema.schemaName;
|
||||
}
|
||||
if (dialect?.defaultSchemaName && schemaList.find(x => x.schemaName == dialect.defaultSchemaName)) {
|
||||
return dialect.defaultSchemaName;
|
||||
}
|
||||
return schemaList[0]?.schemaName;
|
||||
}
|
||||
|
||||
export function isCompositeDbName(name: string) {
|
||||
return name?.includes('::');
|
||||
}
|
||||
|
||||
export function splitCompositeDbName(name: string) {
|
||||
if (!isCompositeDbName(name)) return null;
|
||||
const [database, schema] = name.split('::');
|
||||
return { database, schema };
|
||||
}
|
||||
|
||||
export function extractDbNameFromComposite(name: string) {
|
||||
return isCompositeDbName(name) ? splitCompositeDbName(name).database : name;
|
||||
}
|
||||
|
||||
export function extractSchemaNameFromComposite(name: string) {
|
||||
return splitCompositeDbName(name)?.schema;
|
||||
}
|
||||
|
||||
export function dbNameLogCategory(database: string): string {
|
||||
if (isCompositeDbName(database)) {
|
||||
return '~composite';
|
||||
}
|
||||
if (database) {
|
||||
return '~simple';
|
||||
}
|
||||
return '~nodb';
|
||||
}
|
||||
3
packages/types/dbinfo.d.ts
vendored
3
packages/types/dbinfo.d.ts
vendored
@@ -126,6 +126,7 @@ export interface TriggerInfo extends SqlObjectInfo {}
|
||||
export interface SchemaInfo {
|
||||
objectId?: string;
|
||||
schemaName: string;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface DatabaseInfoObjects {
|
||||
@@ -139,7 +140,5 @@ export interface DatabaseInfoObjects {
|
||||
}
|
||||
|
||||
export interface DatabaseInfo extends DatabaseInfoObjects {
|
||||
schemas?: SchemaInfo[];
|
||||
engine?: string;
|
||||
defaultSchema?: string;
|
||||
}
|
||||
|
||||
8
packages/types/dialect.d.ts
vendored
8
packages/types/dialect.d.ts
vendored
@@ -50,7 +50,13 @@ export interface SqlDialect {
|
||||
predefinedDataTypes: string[];
|
||||
|
||||
// create sql-tree expression
|
||||
createColumnViewExpression(columnName: string, dataType: string, source: { alias: string }, alias?: string): any;
|
||||
createColumnViewExpression(
|
||||
columnName: string,
|
||||
dataType: string,
|
||||
source: { alias: string },
|
||||
alias?: string,
|
||||
purpose: 'view' | 'filter' = 'view'
|
||||
): any;
|
||||
|
||||
getTableFormOptions(intent: 'newTableForm' | 'editTableForm' | 'sqlCreateTable' | 'sqlAlterTable'): {
|
||||
name: string;
|
||||
|
||||
75
packages/types/engines.d.ts
vendored
75
packages/types/engines.d.ts
vendored
@@ -11,6 +11,7 @@ import {
|
||||
FunctionInfo,
|
||||
TriggerInfo,
|
||||
CollectionInfo,
|
||||
SchemaInfo,
|
||||
} from './dbinfo';
|
||||
import { FilterBehaviour } from './filter-type';
|
||||
|
||||
@@ -24,10 +25,12 @@ export interface StreamOptions {
|
||||
|
||||
export interface RunScriptOptions {
|
||||
useTransaction: boolean;
|
||||
queryOptions?: QueryOptions;
|
||||
}
|
||||
|
||||
export interface QueryOptions {
|
||||
discardResult?: boolean;
|
||||
importSqlDump?: boolean;
|
||||
}
|
||||
|
||||
export interface WriteTableOptions {
|
||||
@@ -129,6 +132,15 @@ export interface FilterBehaviourProvider {
|
||||
getFilterBehaviour(dataType: string, standardFilterBehaviours: { [id: string]: FilterBehaviour }): FilterBehaviour;
|
||||
}
|
||||
|
||||
export interface DatabaseHandle {
|
||||
client: any;
|
||||
database?: string;
|
||||
feedback?: (message: any) => void;
|
||||
getDatabase?: () => any;
|
||||
connectionType?: string;
|
||||
treeKeySeparator?: string;
|
||||
}
|
||||
|
||||
export interface EngineDriver extends FilterBehaviourProvider {
|
||||
engine: string;
|
||||
title: string;
|
||||
@@ -170,52 +182,52 @@ export interface EngineDriver extends FilterBehaviourProvider {
|
||||
defaultSocketPath?: string;
|
||||
authTypeLabel?: string;
|
||||
importExportArgs?: any[];
|
||||
connect({ server, port, user, password, database }): Promise<any>;
|
||||
close(pool): Promise<any>;
|
||||
query(pool: any, sql: string, options?: QueryOptions): Promise<QueryResult>;
|
||||
stream(pool: any, sql: string, options: StreamOptions);
|
||||
readQuery(pool: any, sql: string, structure?: TableInfo): Promise<stream.Readable>;
|
||||
readJsonQuery(pool: any, query: any, structure?: TableInfo): Promise<stream.Readable>;
|
||||
writeTable(pool: any, name: NamedObjectInfo, options: WriteTableOptions): Promise<stream.Writable>;
|
||||
connect({ server, port, user, password, database }): Promise<DatabaseHandle>;
|
||||
close(dbhan: DatabaseHandle): Promise<any>;
|
||||
query(dbhan: DatabaseHandle, sql: string, options?: QueryOptions): Promise<QueryResult>;
|
||||
stream(dbhan: DatabaseHandle, sql: string, options: StreamOptions);
|
||||
readQuery(dbhan: DatabaseHandle, sql: string, structure?: TableInfo): Promise<stream.Readable>;
|
||||
readJsonQuery(dbhan: DatabaseHandle, query: any, structure?: TableInfo): Promise<stream.Readable>;
|
||||
writeTable(dbhan: DatabaseHandle, name: NamedObjectInfo, options: WriteTableOptions): Promise<stream.Writable>;
|
||||
analyseSingleObject(
|
||||
pool: any,
|
||||
dbhan: DatabaseHandle,
|
||||
name: NamedObjectInfo,
|
||||
objectTypeField: keyof DatabaseInfo
|
||||
): Promise<TableInfo | ViewInfo | ProcedureInfo | FunctionInfo | TriggerInfo>;
|
||||
analyseSingleTable(pool: any, name: NamedObjectInfo): Promise<TableInfo>;
|
||||
getVersion(pool: any): Promise<{ version: string }>;
|
||||
listDatabases(pool: any): Promise<
|
||||
analyseSingleTable(dbhan: DatabaseHandle, name: NamedObjectInfo): Promise<TableInfo>;
|
||||
getVersion(dbhan: DatabaseHandle): Promise<{ version: string }>;
|
||||
listDatabases(dbhan: DatabaseHandle): Promise<
|
||||
{
|
||||
name: string;
|
||||
}[]
|
||||
>;
|
||||
loadKeys(pool, root: string, filter?: string): Promise;
|
||||
exportKeys(pool, options: {}): Promise;
|
||||
loadKeyInfo(pool, key): Promise;
|
||||
loadKeyTableRange(pool, key, cursor, count): Promise;
|
||||
loadFieldValues(pool: any, name: NamedObjectInfo, field: string, search: string): Promise;
|
||||
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
||||
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
||||
loadKeys(dbhan: DatabaseHandle, root: string, filter?: string): Promise;
|
||||
exportKeys(dbhan: DatabaseHandle, options: {}): Promise;
|
||||
loadKeyInfo(dbhan: DatabaseHandle, key): Promise;
|
||||
loadKeyTableRange(dbhan: DatabaseHandle, key, cursor, count): Promise;
|
||||
loadFieldValues(dbhan: DatabaseHandle, name: NamedObjectInfo, field: string, search: string): Promise;
|
||||
analyseFull(dbhan: DatabaseHandle, serverVersion): Promise<DatabaseInfo>;
|
||||
analyseIncremental(dbhan: DatabaseHandle, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
||||
dialect: SqlDialect;
|
||||
dialectByVersion(version): SqlDialect;
|
||||
createDumper(options = null): SqlDumper;
|
||||
createBackupDumper(pool: any, options): Promise<SqlBackupDumper>;
|
||||
createBackupDumper(dbhan: DatabaseHandle, options): Promise<SqlBackupDumper>;
|
||||
getAuthTypes(): EngineAuthType[];
|
||||
readCollection(pool: any, options: ReadCollectionOptions): Promise<any>;
|
||||
updateCollection(pool: any, changeSet: any): Promise<any>;
|
||||
readCollection(dbhan: DatabaseHandle, options: ReadCollectionOptions): Promise<any>;
|
||||
updateCollection(dbhan: DatabaseHandle, changeSet: any): Promise<any>;
|
||||
getCollectionUpdateScript(changeSet: any, collectionInfo: CollectionInfo): string;
|
||||
createDatabase(pool: any, name: string): Promise;
|
||||
dropDatabase(pool: any, name: string): Promise;
|
||||
createDatabase(dbhan: DatabaseHandle, name: string): Promise;
|
||||
dropDatabase(dbhan: DatabaseHandle, name: string): Promise;
|
||||
getQuerySplitterOptions(usage: 'stream' | 'script' | 'editor'): any;
|
||||
script(pool: any, sql: string, options?: RunScriptOptions): Promise;
|
||||
operation(pool: any, operation: {}, options?: RunScriptOptions): Promise;
|
||||
script(dbhan: DatabaseHandle, sql: string, options?: RunScriptOptions): Promise;
|
||||
operation(dbhan: DatabaseHandle, operation: {}, options?: RunScriptOptions): Promise;
|
||||
getNewObjectTemplates(): NewObjectTemplate[];
|
||||
// direct call of pool method, only some methods could be supported, on only some drivers
|
||||
callMethod(pool, method, args);
|
||||
serverSummary(pool): Promise<ServerSummary>;
|
||||
summaryCommand(pool, command, row): Promise<void>;
|
||||
startProfiler(pool, options): Promise<any>;
|
||||
stopProfiler(pool, profiler): Promise<void>;
|
||||
// direct call of dbhan.client method, only some methods could be supported, on only some drivers
|
||||
callMethod(dbhan: DatabaseHandle, method, args);
|
||||
serverSummary(dbhan: DatabaseHandle): Promise<ServerSummary>;
|
||||
summaryCommand(dbhan: DatabaseHandle, command, row): Promise<void>;
|
||||
startProfiler(dbhan: DatabaseHandle, options): Promise<any>;
|
||||
stopProfiler(dbhan: DatabaseHandle, profiler): Promise<void>;
|
||||
getRedirectAuthUrl(connection, options): Promise<{ url: string; sid: string }>;
|
||||
getAuthTokenFromCode(connection, options): Promise<string>;
|
||||
getAccessTokenFromAuth(connection, req): Promise<string | null>;
|
||||
@@ -230,6 +242,7 @@ export interface EngineDriver extends FilterBehaviourProvider {
|
||||
): any[];
|
||||
// adapts table info from different source (import, other database) to be suitable for this database
|
||||
adaptTableInfo(table: TableInfo): TableInfo;
|
||||
listSchemas(dbhan: DatabaseHandle): SchemaInfo[];
|
||||
|
||||
analyserClass?: any;
|
||||
dumperClass?: any;
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function extractRedirectUri() {
|
||||
const res = (location.origin + location.pathname).replace(/\/login.html$/, '/');
|
||||
console.log('Using redirect URI:', res);
|
||||
return res;
|
||||
}
|
||||
|
||||
async function processSingleProvider(provider) {
|
||||
if (provider.workflowType == 'redirect') {
|
||||
await processRedirectLogin(provider.amoid);
|
||||
@@ -86,7 +92,7 @@
|
||||
const resp = await apiCall('auth/redirect', {
|
||||
amoid: amoid,
|
||||
state,
|
||||
redirectUri: location.origin + location.pathname,
|
||||
redirectUri: extractRedirectUri(),
|
||||
});
|
||||
|
||||
const { uri } = resp;
|
||||
@@ -194,9 +200,7 @@
|
||||
// }`
|
||||
// );
|
||||
internalRedirectTo(
|
||||
`/connections/dblogin-web?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||
location.origin + location.pathname
|
||||
}`
|
||||
`/connections/dblogin-web?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${extractRedirectUri()}`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import CheckboxField from '../forms/CheckboxField.svelte';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -16,6 +18,7 @@
|
||||
export let statusIcon = undefined;
|
||||
export let statusIconBefore = undefined;
|
||||
export let statusTitle = undefined;
|
||||
export let statusTitleToCopy = undefined;
|
||||
export let extInfo = undefined;
|
||||
export let menu = undefined;
|
||||
export let expandIcon = undefined;
|
||||
@@ -114,7 +117,16 @@
|
||||
{/if}
|
||||
{#if statusIcon}
|
||||
<span class="status">
|
||||
<FontIcon icon={statusIcon} title={statusTitle} />
|
||||
<FontIcon
|
||||
icon={statusIcon}
|
||||
title={statusTitle}
|
||||
on:click={() => {
|
||||
if (statusTitleToCopy) {
|
||||
copyTextToClipboard(statusTitleToCopy);
|
||||
showSnackbarSuccess('Copied to clipboard');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
{#if extInfo}
|
||||
|
||||
@@ -65,18 +65,18 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
import { getExtensions } from '../stores';
|
||||
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import { exportQuickExportFile, } from '../utility/exportFileTools';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -156,13 +156,19 @@
|
||||
{
|
||||
text: 'Export',
|
||||
onClick: () => {
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: 'archive',
|
||||
sourceArchiveFolder: data.folderName,
|
||||
sourceList: [data.fileName],
|
||||
},
|
||||
openImportExportTab({
|
||||
sourceStorageType: 'archive',
|
||||
sourceArchiveFolder: data.folderName,
|
||||
sourceList: [data.fileName],
|
||||
});
|
||||
|
||||
// showModal(ImportExportModal, {
|
||||
// initialValues: {
|
||||
// sourceStorageType: 'archive',
|
||||
// sourceArchiveFolder: data.folderName,
|
||||
// sourceList: [data.fileName],
|
||||
// },
|
||||
// });
|
||||
},
|
||||
}
|
||||
),
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
export function openConnection(connection) {
|
||||
const config = getCurrentConfig();
|
||||
if (connection.singleDatabase) {
|
||||
currentDatabase.set({ connection, name: connection.defaultDatabase });
|
||||
switchCurrentDatabase({ connection, name: connection.defaultDatabase });
|
||||
apiCall('database-connections/refresh', {
|
||||
conid: connection._id,
|
||||
database: connection.defaultDatabase,
|
||||
@@ -60,7 +60,7 @@
|
||||
if (electron) {
|
||||
apiCall('database-connections/disconnect', { conid, database: currentDb.name });
|
||||
}
|
||||
currentDatabase.set(null);
|
||||
switchCurrentDatabase(null);
|
||||
}
|
||||
closeMultipleTabs(closeCondition);
|
||||
// if (data.unsaved) {
|
||||
@@ -107,6 +107,7 @@
|
||||
import { tick } from 'svelte';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -142,7 +143,7 @@
|
||||
return;
|
||||
}
|
||||
if ($openedSingleDatabaseConnections.includes(data._id)) {
|
||||
currentDatabase.set({ connection: data, name: data.defaultDatabase });
|
||||
switchCurrentDatabase({ connection: data, name: data.defaultDatabase });
|
||||
return;
|
||||
}
|
||||
if ($openedConnections.includes(data._id)) {
|
||||
@@ -322,6 +323,7 @@
|
||||
: _.get($currentDatabase, 'connection._id') == data._id}
|
||||
statusIcon={statusIcon || engineStatusIcon}
|
||||
statusTitle={statusTitle || engineStatusTitle}
|
||||
statusTitleToCopy={statusTitle || engineStatusTitle}
|
||||
statusIconBefore={data.isReadOnly ? 'icon lock' : null}
|
||||
{extInfo}
|
||||
colorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data._id })}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
apiCall('database-connections/disconnect', { conid, database });
|
||||
}
|
||||
if (getCurrentDatabase()?.connection?._id == conid && getCurrentDatabase()?.name == database) {
|
||||
currentDatabase.set(null);
|
||||
switchCurrentDatabase(null);
|
||||
}
|
||||
openedSingleDatabaseConnections.update(list => list.filter(x => x != conid));
|
||||
closeMultipleTabs(closeCondition);
|
||||
@@ -56,27 +56,7 @@
|
||||
};
|
||||
|
||||
const handleNewTable = () => {
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${name}`;
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Table #',
|
||||
tooltip,
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
forceNewTab: true,
|
||||
}
|
||||
);
|
||||
newTable(connection, name);
|
||||
};
|
||||
|
||||
const handleDropDatabase = () => {
|
||||
@@ -98,25 +78,39 @@
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: connection._id,
|
||||
targetDatabaseName: name,
|
||||
},
|
||||
openImportExportTab({
|
||||
sourceStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: connection._id,
|
||||
targetDatabaseName: name,
|
||||
});
|
||||
|
||||
// showModal(ImportExportModal, {
|
||||
// initialValues: {
|
||||
// sourceStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
// targetStorageType: 'database',
|
||||
// targetConnectionId: connection._id,
|
||||
// targetDatabaseName: name,
|
||||
// },
|
||||
// });
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: connection._id,
|
||||
sourceDatabaseName: name,
|
||||
},
|
||||
openImportExportTab({
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: connection._id,
|
||||
sourceDatabaseName: name,
|
||||
});
|
||||
|
||||
// showModal(ImportExportModal, {
|
||||
// initialValues: {
|
||||
// targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
// sourceStorageType: 'database',
|
||||
// sourceConnectionId: connection._id,
|
||||
// sourceDatabaseName: name,
|
||||
// },
|
||||
// });
|
||||
};
|
||||
|
||||
const handleSqlGenerator = () => {
|
||||
@@ -268,6 +262,17 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleRefreshSchemas = () => {
|
||||
const conid = connection._id;
|
||||
const database = name;
|
||||
apiCall('database-connections/dispatch-database-changed-event', {
|
||||
event: 'schema-list-changed',
|
||||
conid,
|
||||
database,
|
||||
});
|
||||
loadSchemaList(conid, database);
|
||||
};
|
||||
|
||||
async function handleConfirmSql(sql) {
|
||||
saveScriptToDatabase({ conid: connection._id, database: name }, sql, false);
|
||||
}
|
||||
@@ -296,11 +301,13 @@
|
||||
onClick: handleNewPerspective,
|
||||
text: 'Design perspective query',
|
||||
},
|
||||
connection.useSeparateSchemas && { onClick: handleRefreshSchemas, text: 'Refresh schemas' },
|
||||
|
||||
{ divider: true },
|
||||
isSqlOrDoc &&
|
||||
!connection.isReadOnly &&
|
||||
hasPermission(`dbops/import`) && { onClick: handleImport, text: 'Import wizard' },
|
||||
isSqlOrDoc && hasPermission(`dbops/export`) && { onClick: handleExport, text: 'Export wizard' },
|
||||
hasPermission(`dbops/import`) && { onClick: handleImport, text: 'Import' },
|
||||
isSqlOrDoc && hasPermission(`dbops/export`) && { onClick: handleExport, text: 'Export' },
|
||||
driver?.databaseEngineTypes?.includes('sql') &&
|
||||
hasPermission(`dbops/sql-dump/import`) &&
|
||||
!connection.isReadOnly && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' },
|
||||
@@ -360,7 +367,6 @@
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
import _, { find } from 'lodash';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
@@ -371,6 +377,7 @@
|
||||
getCurrentDatabase,
|
||||
getExtensions,
|
||||
getOpenedTabs,
|
||||
loadingSchemaLists,
|
||||
openedConnections,
|
||||
openedSingleDatabaseConnections,
|
||||
pinnedDatabases,
|
||||
@@ -381,7 +388,7 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||
import { extractDbNameFromComposite, findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
@@ -390,13 +397,15 @@
|
||||
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
import newQuery from '../query/newQuery';
|
||||
import { exportSqlDump } from '../utility/exportFileTools';
|
||||
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
|
||||
import ExportDatabaseDumpModal from '../modals/ExportDatabaseDumpModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import newTable from '../tableeditor/newTable';
|
||||
import { loadSchemaList, switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -414,6 +423,7 @@
|
||||
|
||||
$: isPinned = !!$pinnedDatabases.find(x => x?.name == data.name && x?.connection?._id == data.connection?._id);
|
||||
$: apps = useUsedApps();
|
||||
$: isLoadingSchemas = $loadingSchemaLists[`${data?.connection?._id}::${data?.name}`];
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
@@ -425,8 +435,8 @@
|
||||
colorMark={passProps?.connectionColorFactory &&
|
||||
passProps?.connectionColorFactory({ conid: _.get(data.connection, '_id'), database: data.name }, null, null, false)}
|
||||
isBold={_.get($currentDatabase, 'connection._id') == _.get(data.connection, '_id') &&
|
||||
_.get($currentDatabase, 'name') == data.name}
|
||||
on:click={() => ($currentDatabase = data)}
|
||||
extractDbNameFromComposite(_.get($currentDatabase, 'name')) == data.name}
|
||||
on:click={() => switchCurrentDatabase(data)}
|
||||
on:dragstart
|
||||
on:dragenter
|
||||
on:dragend
|
||||
@@ -436,6 +446,7 @@
|
||||
.find(x => x.isNewQuery)
|
||||
.onClick();
|
||||
}}
|
||||
statusIcon={isLoadingSchemas ? 'icon loading' : ''}
|
||||
menu={createMenu}
|
||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||
onPin={isPinned ? null : () => pinnedDatabases.update(list => [...list, data])}
|
||||
|
||||
@@ -618,15 +618,22 @@
|
||||
});
|
||||
} else if (menu.isImport) {
|
||||
const { conid, database } = data;
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: getDefaultFileFormat(getExtensions()).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: conid,
|
||||
targetDatabaseName: database,
|
||||
fixedTargetPureName: data.pureName,
|
||||
},
|
||||
openImportExportTab({
|
||||
sourceStorageType: getDefaultFileFormat(getExtensions()).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: conid,
|
||||
targetDatabaseName: database,
|
||||
fixedTargetPureName: data.pureName,
|
||||
});
|
||||
// showModal(ImportExportModal, {
|
||||
// initialValues: {
|
||||
// sourceStorageType: getDefaultFileFormat(getExtensions()).storageType,
|
||||
// targetStorageType: 'database',
|
||||
// targetConnectionId: conid,
|
||||
// targetDatabaseName: database,
|
||||
// fixedTargetPureName: data.pureName,
|
||||
// },
|
||||
// });
|
||||
} else {
|
||||
openDatabaseObjectDetail(
|
||||
menu.tab,
|
||||
@@ -764,31 +771,22 @@
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
// openNewTab(
|
||||
// {
|
||||
// tabComponent: 'ImportExportTab',
|
||||
// title: 'Import/Export',
|
||||
// icon: 'img export',
|
||||
// },
|
||||
// {
|
||||
// editor: {
|
||||
// sourceStorageType: 'database',
|
||||
// sourceConnectionId: data.conid,
|
||||
// sourceDatabaseName: data.database,
|
||||
// sourceSchemaName: data.schemaName,
|
||||
// sourceList: [data.pureName],
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
showModal(ImportExportModal, {
|
||||
initialValues: {
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: data.database,
|
||||
sourceSchemaName: data.schemaName,
|
||||
sourceList: [data.pureName],
|
||||
},
|
||||
openImportExportTab({
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: data.database,
|
||||
sourceSchemaName: data.schemaName,
|
||||
sourceList: [data.pureName],
|
||||
});
|
||||
// showModal(ImportExportModal, {
|
||||
// initialValues: {
|
||||
// sourceStorageType: 'database',
|
||||
// sourceConnectionId: data.conid,
|
||||
// sourceDatabaseName: data.database,
|
||||
// sourceSchemaName: data.schemaName,
|
||||
// sourceList: [data.pureName],
|
||||
// },
|
||||
// });
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -832,7 +830,6 @@
|
||||
import { filterName, generateDbPairingId, getAlterDatabaseScript, getConnectionLabel } from 'dbgate-tools';
|
||||
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
@@ -848,6 +845,7 @@
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -879,7 +877,7 @@
|
||||
{...$$restProps}
|
||||
module={$$props.module}
|
||||
{data}
|
||||
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||
title={data.schemaName && !passProps?.hideSchemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||
icon={databaseObjectIcons[data.objectTypeField]}
|
||||
menu={createMenu}
|
||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||
|
||||
@@ -65,6 +65,14 @@
|
||||
currentConnection: true,
|
||||
};
|
||||
|
||||
const jobs: FileTypeHandler = {
|
||||
icon: 'img export',
|
||||
format: 'json',
|
||||
tabComponent: 'ImportExportTab',
|
||||
folder: 'jobs',
|
||||
currentConnection: false,
|
||||
};
|
||||
|
||||
const perspectives: FileTypeHandler = {
|
||||
icon: 'img perspective',
|
||||
format: 'json',
|
||||
@@ -82,6 +90,7 @@
|
||||
sqlite,
|
||||
diagrams,
|
||||
perspectives,
|
||||
jobs,
|
||||
};
|
||||
|
||||
export const extractKey = data => data.file;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
databaseList.push({
|
||||
text: `${db.name} on ${getConnectionLabel(connection)}`,
|
||||
icon: 'img database',
|
||||
onClick: () => currentDatabase.set({ connection, name: db.name }),
|
||||
onClick: () => switchCurrentDatabase({ connection, name: db.name }),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@
|
||||
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import registerCommand from './registerCommand';
|
||||
import { formatKeyText } from '../utility/common';
|
||||
import { formatKeyText, switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
let domInput;
|
||||
let filter = '';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { currentDatabase, getCurrentDatabase } from '../stores';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import registerCommand from './registerCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
registerCommand({
|
||||
id: 'database.changeState',
|
||||
@@ -40,7 +41,7 @@ registerCommand({
|
||||
onClick: () => {
|
||||
const electron = getElectron();
|
||||
if (electron) apiCall('database-connections/disconnect', dbid);
|
||||
currentDatabase.set(null);
|
||||
switchCurrentDatabase(null);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
||||
import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores';
|
||||
import registerCommand from './registerCommand';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
currentDatabase.subscribe(value => {
|
||||
if (!value) return;
|
||||
@@ -17,7 +18,7 @@ currentDatabase.subscribe(value => {
|
||||
function switchDatabaseCommand(db) {
|
||||
return {
|
||||
text: `${db.name} on ${getConnectionLabel(db?.connection, { allowExplicitDatabase: false })}`,
|
||||
onClick: () => currentDatabase.set(db),
|
||||
onClick: () => switchCurrentDatabase(db),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import registerCommand from './registerCommand';
|
||||
import { get } from 'svelte/store';
|
||||
import AboutModal from '../modals/AboutModal.svelte';
|
||||
import SettingsModal from '../settings/SettingsModal.svelte';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import newQuery, { newDiagram, newPerspective, newQueryDesign } from '../query/newQuery';
|
||||
@@ -36,7 +35,7 @@ import { apiCall } from '../utility/api';
|
||||
import runCommand from './runCommand';
|
||||
import { openWebLink } from '../utility/exportFileTools';
|
||||
import { getSettings } from '../utility/metadataLoaders';
|
||||
import { isMac } from '../utility/common';
|
||||
import { isMac, switchCurrentDatabase } from '../utility/common';
|
||||
import { doLogout, internalRedirectTo } from '../clientAuth';
|
||||
import { disconnectServerConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
import UploadErrorModal from '../modals/UploadErrorModal.svelte';
|
||||
@@ -44,6 +43,8 @@ import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import localforage from 'localforage';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import newTable from '../tableeditor/newTable';
|
||||
|
||||
// function themeCommand(theme: ThemeDefinition) {
|
||||
// return {
|
||||
@@ -253,26 +254,7 @@ registerCommand({
|
||||
const $currentDatabase = get(currentDatabase);
|
||||
const connection = _.get($currentDatabase, 'connection') || {};
|
||||
const database = _.get($currentDatabase, 'name');
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Table #',
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
forceNewTab: true,
|
||||
}
|
||||
);
|
||||
newTable(connection, database);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -365,7 +347,7 @@ registerCommand({
|
||||
onConfirm: async file => {
|
||||
const resp = await apiCall('connections/new-sqlite-database', { file });
|
||||
const connection = resp;
|
||||
currentDatabase.set({ connection, name: `${file}.sqlite` });
|
||||
switchCurrentDatabase({ connection, name: `${file}.sqlite` });
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -483,10 +465,18 @@ registerCommand({
|
||||
toolbar: true,
|
||||
icon: 'icon import',
|
||||
onClick: () =>
|
||||
showModal(ImportExportModal, {
|
||||
importToCurrentTarget: true,
|
||||
initialValues: { sourceStorageType: getDefaultFileFormat(get(extensions)).storageType },
|
||||
}),
|
||||
openImportExportTab(
|
||||
{
|
||||
sourceStorageType: getDefaultFileFormat(get(extensions)).storageType,
|
||||
},
|
||||
{
|
||||
importToCurrentTarget: true,
|
||||
}
|
||||
),
|
||||
// showModal(ImportExportModal, {
|
||||
// importToCurrentTarget: true,
|
||||
// initialValues: { sourceStorageType: getDefaultFileFormat(get(extensions)).storageType },
|
||||
// }),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
@@ -595,8 +585,6 @@ registerCommand({
|
||||
onClick: () => getElectron().send('check-for-updates'),
|
||||
});
|
||||
|
||||
|
||||
|
||||
export function registerFileCommands({
|
||||
idPrefix,
|
||||
category,
|
||||
|
||||
@@ -122,8 +122,6 @@
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import { extractShellConnection } from '../impexp/createImpExpScript';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
@@ -136,6 +134,7 @@
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { mongoFilterBehaviour, standardFilterBehaviours } from 'dbgate-tools';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
|
||||
export let conid;
|
||||
export let display;
|
||||
@@ -207,7 +206,8 @@
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = [pureName];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
showModal(ImportExportModal, { initialValues });
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
|
||||
@@ -45,9 +45,6 @@
|
||||
import _ from 'lodash';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { extensions } from '../stores';
|
||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
@@ -58,7 +55,7 @@
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import RowsArrayGrider from './RowsArrayGrider';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
|
||||
export let jslid;
|
||||
export let display;
|
||||
@@ -152,7 +149,8 @@
|
||||
initialValues.sourceList = ['query-data'];
|
||||
initialValues[`columns_query-data`] = display.getExportColumnMap();
|
||||
}
|
||||
showModal(ImportExportModal, { initialValues });
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
|
||||
@@ -65,13 +65,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { getContext } from 'svelte';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import { extractShellConnection } from '../impexp/createImpExpScript';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
@@ -84,6 +81,7 @@
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
|
||||
export let conid;
|
||||
export let display;
|
||||
@@ -145,7 +143,8 @@
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = display.baseTableOrSimilar ? [display.baseTableOrSimilar.pureName] : [];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
showModal(ImportExportModal, { initialValues });
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
|
||||
30
packages/web/src/elements/ColumnMapColumnDropdown.svelte
Normal file
30
packages/web/src/elements/ColumnMapColumnDropdown.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script alng="ts">
|
||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
|
||||
export let tableInfo;
|
||||
export let onChange;
|
||||
export let value;
|
||||
</script>
|
||||
|
||||
{#if tableInfo}
|
||||
<div class="wrapper">
|
||||
<TextField {...$$restProps} {value} on:input={e => onChange(e.target.value)} />
|
||||
<DropDownButton
|
||||
menu={() => {
|
||||
return tableInfo.columns.map(opt => ({
|
||||
text: opt.columnName,
|
||||
onClick: () => onChange(opt.columnName),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<TextField {value} on:input={e => onChange(e.target.value)} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -4,12 +4,15 @@
|
||||
import FormArgumentList from '../forms/FormArgumentList.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import createRef from '../utility/createRef';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
|
||||
export let title;
|
||||
export let fieldDefinitions;
|
||||
export let values;
|
||||
export let onChangeValues;
|
||||
export let pureNameTitle = null;
|
||||
export let schemaList = null;
|
||||
|
||||
let collapsed = false;
|
||||
|
||||
@@ -32,6 +35,17 @@
|
||||
</div>
|
||||
{#if !collapsed}
|
||||
<FormProviderCore values={valuesStore}>
|
||||
{#if schemaList?.length > 0}
|
||||
<FormSelectField
|
||||
isNative
|
||||
name="schemaName"
|
||||
label="Schema"
|
||||
options={schemaList.map(x => ({ label: x.schemaName, value: x.schemaName }))}
|
||||
/>
|
||||
{/if}
|
||||
{#if pureNameTitle}
|
||||
<FormTextField name="pureName" label={pureNameTitle} />
|
||||
{/if}
|
||||
<FormArgumentList args={fieldDefinitions} />
|
||||
</FormProviderCore>
|
||||
{/if}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export let isMulti = false;
|
||||
export let notSelected = null;
|
||||
export let defaultValue = '';
|
||||
export let selectClass = '';
|
||||
|
||||
let listOpen = false;
|
||||
let isFocused = false;
|
||||
@@ -23,6 +24,7 @@
|
||||
{#if isNative}
|
||||
<select
|
||||
value={options.find(x => x.value == value) ? value : defaultValue}
|
||||
class={selectClass}
|
||||
{...$$restProps}
|
||||
on:change={e => {
|
||||
dispatch('change', e.target['value']);
|
||||
@@ -46,7 +48,7 @@
|
||||
items={options}
|
||||
value={isMulti
|
||||
? _.compact(value?.map(item => options.find(x => x.value == item)) ?? [])
|
||||
: options.find(x => x.value == value) ?? null}
|
||||
: (options.find(x => x.value == value) ?? null)}
|
||||
on:select={e => {
|
||||
if (isMulti) {
|
||||
dispatch(
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
'icon minus-box': 'mdi mdi-minus-box-outline',
|
||||
'icon plus-box': 'mdi mdi-plus-box-outline',
|
||||
'icon plus-thick': 'mdi mdi-plus-thick',
|
||||
'icon minus-thick': 'mdi mdi-minus-thick',
|
||||
'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible',
|
||||
'icon cloud-upload': 'mdi mdi-cloud-upload',
|
||||
'icon import': 'mdi mdi-application-import',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
function extractUrlName(url, values) {
|
||||
const match = url.match(/\/([^/]+)($|\?)/);
|
||||
const match = url.match(/\/([^/\?]+)($|\?)/);
|
||||
if (match) {
|
||||
const res = match[1];
|
||||
if (res.includes('.')) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import { useDatabaseInfo, useDatabaseList } from '../utility/metadataLoaders';
|
||||
import { useSchemaList } from '../utility/metadataLoaders';
|
||||
|
||||
export let conidName;
|
||||
export let databaseName;
|
||||
|
||||
const { values } = getFormContext();
|
||||
$: dbinfo = useDatabaseInfo({ conid: $values[conidName], database: values[databaseName] });
|
||||
$: schemaList = useSchemaList({ conid: $values[conidName], database: values[databaseName] });
|
||||
|
||||
$: schemaOptions = (($dbinfo && $dbinfo.schemas) || []).map(schema => ({
|
||||
$: schemaOptions = (_.isArray($schemaList) ? $schemaList : []).map(schema => ({
|
||||
value: schema.schemaName,
|
||||
label: schema.schemaName,
|
||||
}));
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import Link from '../elements/Link.svelte';
|
||||
import TableControl from '../elements/TableControl.svelte';
|
||||
@@ -67,14 +66,17 @@
|
||||
import SourceName from './SourceName.svelte';
|
||||
|
||||
import SourceTargetConfig from './SourceTargetConfig.svelte';
|
||||
import useEffect from '../utility/useEffect';
|
||||
|
||||
export let uploadedFile = undefined;
|
||||
export let openedFile = undefined;
|
||||
// export let uploadedFile = undefined;
|
||||
// export let openedFile = undefined;
|
||||
export let previewReaderStore;
|
||||
export let isTabActive;
|
||||
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
|
||||
$: targetDbinfo = useDatabaseInfo({ conid: $values.targetConnectionId, database: $values.targetDatabaseName });
|
||||
$: sourceDbinfo = useDatabaseInfo({ conid: $values.sourceConnectionId, database: $values.sourceDatabaseName });
|
||||
$: sourceConnectionInfo = useConnectionInfo({ conid: $values.sourceConnectionId });
|
||||
$: sourceEngine = $sourceConnectionInfo?.engine;
|
||||
$: sourceList = $values.sourceList;
|
||||
@@ -98,7 +100,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = file => {
|
||||
export function addUploadedFile(file) {
|
||||
addFilesToSourceList(
|
||||
$extensions,
|
||||
[
|
||||
@@ -113,36 +115,20 @@
|
||||
previewSource.set
|
||||
);
|
||||
// setFieldValue('sourceList', [...(sourceList || []), file.originalName]);
|
||||
};
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setUploadListener(handleUpload);
|
||||
if (uploadedFile) {
|
||||
handleUpload(uploadedFile);
|
||||
$: effectActiveTab = useEffect(() => {
|
||||
if (isTabActive) {
|
||||
setUploadListener(addUploadedFile);
|
||||
return () => {
|
||||
setUploadListener(null);
|
||||
};
|
||||
} else {
|
||||
return () => {};
|
||||
}
|
||||
if (openedFile) {
|
||||
handleUpload(openedFile);
|
||||
// addFilesToSourceList(
|
||||
// $extensions,
|
||||
// [
|
||||
// {
|
||||
// fileName: openedFile.filePath,
|
||||
// shortName: openedFile.shortName,
|
||||
// },
|
||||
// ],
|
||||
// $values,
|
||||
// values,
|
||||
// !sourceList || sourceList.length == 0 ? openedFile.storageType : null,
|
||||
// previewSource.set
|
||||
// );
|
||||
}
|
||||
|
||||
return () => {
|
||||
setUploadListener(null);
|
||||
};
|
||||
});
|
||||
// engine={sourceEngine}
|
||||
// {setPreviewSource}
|
||||
|
||||
$effectActiveTab;
|
||||
</script>
|
||||
|
||||
<div class="flex1">
|
||||
@@ -234,7 +220,11 @@
|
||||
<Link
|
||||
onClick={() => {
|
||||
showModal(ColumnMapModal, {
|
||||
value: $values[`columns_${row}`],
|
||||
initialValue: $values[`columns_${row}`],
|
||||
sourceTableInfo: $sourceDbinfo?.tables?.find(x => x.pureName?.toLowerCase() == row?.toLowerCase()),
|
||||
targetTableInfo: $targetDbinfo?.tables?.find(
|
||||
x => x.pureName?.toLowerCase() == (values[`targetName_${row}`] || row)?.toLowerCase()
|
||||
),
|
||||
onConfirm: value => setFieldValue(`columns_${row}`, value),
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -196,6 +196,7 @@
|
||||
width: 20vw;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-bottom: var(--dim-large-form-margin);
|
||||
border: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
||||
@@ -192,7 +192,7 @@ export function normalizeExportColumnMap(colmap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default async function createImpExpScript(extensions, values, addEditorInfo = true, forceScript = false) {
|
||||
export default async function createImpExpScript(extensions, values, forceScript = false) {
|
||||
const config = getCurrentConfig();
|
||||
const script =
|
||||
config.allowShellScripting || forceScript
|
||||
@@ -233,10 +233,6 @@ export default async function createImpExpScript(extensions, values, addEditorIn
|
||||
script.copyStream(sourceVar, targetVar, colmapVar);
|
||||
script.endLine();
|
||||
}
|
||||
if (addEditorInfo) {
|
||||
script.comment('@ImportExportConfigurator');
|
||||
script.comment(JSON.stringify(values));
|
||||
}
|
||||
return script.getScript(values.schedule);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
|
||||
export let onConfirm;
|
||||
export let url;
|
||||
|
||||
const handleSubmit = e => {
|
||||
onConfirm(e.detail.url);
|
||||
@@ -15,7 +16,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<FormProvider initialValues={{ url }}>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Download imported file from web</svelte:fragment>
|
||||
|
||||
|
||||
@@ -1,27 +1,67 @@
|
||||
<script lang="ts">
|
||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import ColumnMapColumnDropdown from '../elements/ColumnMapColumnDropdown.svelte';
|
||||
import Link from '../elements/Link.svelte';
|
||||
import TableControl from '../elements/TableControl.svelte';
|
||||
import CheckboxField from '../forms/CheckboxField.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
|
||||
export let header = 'Configure columns';
|
||||
export let onConfirm;
|
||||
export let value = [];
|
||||
|
||||
export let sourceTableInfo;
|
||||
export let targetTableInfo;
|
||||
|
||||
export let initialValue;
|
||||
|
||||
function getResetValue() {
|
||||
if (sourceTableInfo && !targetTableInfo) {
|
||||
return sourceTableInfo.columns.map(x => ({
|
||||
src: x.columnName,
|
||||
dst: x.columnName,
|
||||
skip: false,
|
||||
}));
|
||||
}
|
||||
if (targetTableInfo && !sourceTableInfo) {
|
||||
return targetTableInfo.columns.map(x => ({
|
||||
src: x.columnName,
|
||||
dst: x.columnName,
|
||||
skip: false,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const resetValue = getResetValue();
|
||||
|
||||
function equalValues(v1, v2) {
|
||||
if (!v1 || !v2) return false;
|
||||
if (v1.length != v2.length) return false;
|
||||
for (let i = 0; i < v1.length; i++) {
|
||||
if (v1[i].src != v2[i].src || v1[i].dst != v2[i].dst || !!v1[i].skip != !!v2[i].skip) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$: differentFromReset = !equalValues(value, resetValue);
|
||||
|
||||
let value = initialValue?.length > 0 ? initialValue : resetValue;
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<div slot="header">{header}</div>
|
||||
|
||||
<div class="m-3">
|
||||
When no columns are defined in this mapping, source row is copied to target without any modifications
|
||||
</div>
|
||||
{#if resetValue.length == 0}
|
||||
<div class="m-3">
|
||||
When no columns are defined in this mapping, source row is copied to target without any modifications
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<TableControl
|
||||
columns={[
|
||||
@@ -41,17 +81,19 @@
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="1" let:row let:index>
|
||||
<TextField
|
||||
<ColumnMapColumnDropdown
|
||||
value={row['src']}
|
||||
on:change={e =>
|
||||
(value = (value || []).map((x, i) => (i == index ? { ...x, src: e.target.value, ignore: false } : x)))}
|
||||
onChange={column =>
|
||||
(value = (value || []).map((x, i) => (i == index ? { ...x, src: column, ignore: false } : x)))}
|
||||
tableInfo={sourceTableInfo}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="2" let:row let:index>
|
||||
<TextField
|
||||
<ColumnMapColumnDropdown
|
||||
value={row['dst']}
|
||||
on:change={e =>
|
||||
onChange={e =>
|
||||
(value = (value || []).map((x, i) => (i == index ? { ...x, dst: e.target.value, ignore: false } : x)))}
|
||||
tableInfo={targetTableInfo}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="3" let:index>
|
||||
@@ -68,7 +110,7 @@
|
||||
value="OK"
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
onConfirm(!value || value.length == 0 ? null : value);
|
||||
onConfirm(!value || value.length == 0 || !differentFromReset ? null : value);
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
@@ -82,8 +124,9 @@
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Reset"
|
||||
disabled={!differentFromReset}
|
||||
on:click={() => {
|
||||
value = [];
|
||||
value = resetValue;
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script context="module">
|
||||
<script context="module" lang="ts">
|
||||
export async function saveScriptToDatabase({ conid, database }, sql, syncModel = true) {
|
||||
const resp = await apiCall('database-connections/run-script', {
|
||||
conid,
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
export async function runOperationOnDatabase({ conid, database }, operation, syncModel = true) {
|
||||
export async function runOperationOnDatabase({ conid, database }, operation, syncModel: string | boolean = true) {
|
||||
const resp = await apiCall('database-connections/run-operation', {
|
||||
conid,
|
||||
database,
|
||||
@@ -27,7 +27,11 @@
|
||||
showModal(ErrorMessageModal, { title: 'Error when executing operation', message: errorMessage });
|
||||
} else {
|
||||
showSnackbarSuccess('Saved to database');
|
||||
if (syncModel) apiCall('database-connections/sync-model', { conid, database });
|
||||
if (_.isString(syncModel)) {
|
||||
apiCall('database-connections/dispatch-database-changed-event', { event: syncModel, conid, database });
|
||||
} else if (syncModel) {
|
||||
apiCall('database-connections/sync-model', { conid, database });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
<script lang="ts">
|
||||
import moment from 'moment';
|
||||
import { writable } from 'svelte/store';
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import LargeButton from '../buttons/LargeButton.svelte';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import LargeFormButton from '../forms/LargeFormButton.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import createImpExpScript from '../impexp/createImpExpScript';
|
||||
import ImportExportConfigurator from '../impexp/ImportExportConfigurator.svelte';
|
||||
import PreviewDataGrid from '../impexp/PreviewDataGrid.svelte';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import RunnerOutputFiles from '../query/RunnerOutputFiles.svelte';
|
||||
import SocketMessageView from '../query/SocketMessageView.svelte';
|
||||
import { currentArchive, currentDatabase, extensions, visibleWidgetSideBar, selectedWidget } from '../stores';
|
||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||
import createRef from '../utility/createRef';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
|
||||
let busy = false;
|
||||
let executeNumber = 0;
|
||||
let runnerId = null;
|
||||
|
||||
const previewReaderStore = writable(null);
|
||||
|
||||
export let initialValues;
|
||||
export let uploadedFile = undefined;
|
||||
export let openedFile = undefined;
|
||||
export let importToCurrentTarget = false;
|
||||
|
||||
const refreshArchiveFolderRef = createRef(null);
|
||||
|
||||
function detectCurrentTarget() {
|
||||
if (!importToCurrentTarget) return {};
|
||||
|
||||
if ($currentDatabase && $selectedWidget != 'archive') {
|
||||
return {
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: $currentDatabase?.connection?._id,
|
||||
targetDatabaseName: $currentDatabase?.name,
|
||||
};
|
||||
}
|
||||
|
||||
if ($currentArchive == 'default') {
|
||||
return {
|
||||
targetStorageType: 'archive',
|
||||
targetArchiveFolder: `import-${moment().format('YYYY-MM-DD-hh-mm-ss')}`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
targetStorageType: 'archive',
|
||||
targetArchiveFolder: $currentArchive,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$: effect = useEffect(() => registerRunnerDone(runnerId));
|
||||
|
||||
function registerRunnerDone(rid) {
|
||||
if (rid) {
|
||||
apiOn(`runner-done-${rid}`, handleRunnerDone);
|
||||
return () => {
|
||||
apiOff(`runner-done-${rid}`, handleRunnerDone);
|
||||
};
|
||||
} else {
|
||||
return () => {};
|
||||
}
|
||||
}
|
||||
|
||||
$: $effect;
|
||||
|
||||
const handleRunnerDone = () => {
|
||||
busy = false;
|
||||
if (refreshArchiveFolderRef.get()) {
|
||||
apiCall('archive/refresh-folders', {});
|
||||
apiCall('archive/refresh-files', { folder: refreshArchiveFolderRef.get() });
|
||||
$currentArchive = refreshArchiveFolderRef.get();
|
||||
$selectedWidget = 'archive';
|
||||
$visibleWidgetSideBar = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateScript = async e => {
|
||||
closeCurrentModal();
|
||||
const code = await createImpExpScript($extensions, e.detail, undefined, true);
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Shell #',
|
||||
icon: 'img shell',
|
||||
tabComponent: 'ShellTab',
|
||||
},
|
||||
{ editor: code }
|
||||
);
|
||||
};
|
||||
|
||||
const handleExecute = async e => {
|
||||
if (busy) return;
|
||||
const values = e.detail;
|
||||
busy = true;
|
||||
const script = await createImpExpScript($extensions, values);
|
||||
executeNumber += 1;
|
||||
let runid = runnerId;
|
||||
const resp = await apiCall('runners/start', { script });
|
||||
runid = resp.runid;
|
||||
runnerId = runid;
|
||||
|
||||
if (values.targetStorageType == 'archive') {
|
||||
refreshArchiveFolderRef.set(values.targetArchiveFolder);
|
||||
} else {
|
||||
refreshArchiveFolderRef.set(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
apiCall('runners/cancel', {
|
||||
runid: runnerId,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<FormProvider
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetArchiveFolder: $currentArchive,
|
||||
sourceArchiveFolder: $currentArchive,
|
||||
...detectCurrentTarget(),
|
||||
...initialValues,
|
||||
}}
|
||||
>
|
||||
<ModalBase {...$$restProps} fullScreen>
|
||||
<svelte:fragment slot="header">
|
||||
Import/Export
|
||||
{#if busy}
|
||||
<FontIcon icon="icon loading" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<HorizontalSplitter initialValue="70%">
|
||||
<div class="content" slot="1">
|
||||
<ImportExportConfigurator {uploadedFile} {openedFile} {previewReaderStore} />
|
||||
|
||||
{#if busy}
|
||||
<LoadingInfo wrapper message="Processing import/export ..." />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Output files" name="output" height="20%">
|
||||
<RunnerOutputFiles {runnerId} {executeNumber} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Messages" name="messages">
|
||||
<SocketMessageView
|
||||
eventName={runnerId ? `runner-info-${runnerId}` : null}
|
||||
{executeNumber}
|
||||
showNoMessagesAlert
|
||||
/>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Preview" name="preview" skip={!$previewReaderStore}>
|
||||
<PreviewDataGrid reader={$previewReaderStore} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
|
||||
<FormTextField label="Schedule" name="schedule" />
|
||||
<FormTextField label="Start variable index" name="startVariableIndex" />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<div class="flex m-2">
|
||||
{#if busy}
|
||||
<LargeButton icon="icon stop" on:click={handleCancel}>Stop</LargeButton>
|
||||
{:else}
|
||||
<LargeFormButton on:click={handleExecute} icon="icon run">Run</LargeFormButton>
|
||||
{/if}
|
||||
<LargeFormButton icon="img sql-file" on:click={handleGenerateScript}>Generate script</LargeFormButton>
|
||||
|
||||
<LargeButton on:click={closeCurrentModal} icon="icon close">Close</LargeButton>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,33 @@ const jsonFormat = {
|
||||
storageType: 'json',
|
||||
extension: 'json',
|
||||
name: 'JSON',
|
||||
writerFunc: 'jsonArrayWriter',
|
||||
readerFunc: 'jsonReader',
|
||||
writerFunc: 'jsonWriter',
|
||||
|
||||
args: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'jsonStyle',
|
||||
label: 'JSON style',
|
||||
options: [
|
||||
{ name: 'Array', value: '' },
|
||||
{ name: 'Object', value: 'object' },
|
||||
],
|
||||
apiName: 'jsonStyle',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'keyField',
|
||||
label: 'Key field (only for "Object" style)',
|
||||
apiName: 'keyField',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'rootField',
|
||||
label: 'Root field',
|
||||
apiName: 'rootField',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const sqlFormat = {
|
||||
@@ -38,7 +64,7 @@ const jsonQuickExport = {
|
||||
label: 'JSON',
|
||||
extension: 'json',
|
||||
createWriter: fileName => ({
|
||||
functionName: 'jsonArrayWriter',
|
||||
functionName: 'jsonWriter',
|
||||
props: {
|
||||
fileName,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import { addCompleter, setCompleters } from 'ace-builds/src-noconflict/ext-language_tools';
|
||||
import { getDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { getConnectionInfo, getDatabaseInfo, getSchemaList } from '../utility/metadataLoaders';
|
||||
import analyseQuerySources from './analyseQuerySources';
|
||||
import { getStringSettingsValue } from '../settings/settingsTools';
|
||||
import { findEngineDriver, findDefaultSchema } from 'dbgate-tools';
|
||||
import { getExtensions } from '../stores';
|
||||
|
||||
const COMMON_KEYWORDS = [
|
||||
'select',
|
||||
@@ -24,9 +26,9 @@ const COMMON_KEYWORDS = [
|
||||
'go',
|
||||
];
|
||||
|
||||
function createTableLikeList(dbinfo, schemaCondition) {
|
||||
function createTableLikeList(schemaList, dbinfo, schemaCondition) {
|
||||
return [
|
||||
...(dbinfo.schemas?.map(x => ({
|
||||
...(schemaList?.map(x => ({
|
||||
name: x.schemaName,
|
||||
value: x.schemaName,
|
||||
caption: x.schemaName,
|
||||
@@ -78,6 +80,10 @@ export function mountCodeCompletion({ conid, database, editor, getText }) {
|
||||
const cursor = session.selection.cursor;
|
||||
const line = session.getLine(cursor.row).slice(0, cursor.column);
|
||||
const dbinfo = await getDatabaseInfo({ conid, database });
|
||||
const schemaList = await getSchemaList({ conid, database });
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
const driver = findEngineDriver(connection, getExtensions());
|
||||
const defaultSchema = findDefaultSchema(schemaList, driver.dialect);
|
||||
|
||||
const convertUpper = getStringSettingsValue('sqlEditor.sqlCommandsCase', 'upperCase') == 'upperCase';
|
||||
|
||||
@@ -147,9 +153,9 @@ export function mountCodeCompletion({ conid, database, editor, getText }) {
|
||||
];
|
||||
}
|
||||
} else {
|
||||
const schema = (dbinfo.schemas || []).find(x => x.schemaName == colMatch[1]);
|
||||
const schema = (schemaList || []).find(x => x.schemaName == colMatch[1]);
|
||||
if (schema) {
|
||||
list = createTableLikeList(dbinfo, x => x.schemaName == schema.schemaName);
|
||||
list = createTableLikeList(schemaList, dbinfo, x => x.schemaName == schema.schemaName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -167,7 +173,7 @@ export function mountCodeCompletion({ conid, database, editor, getText }) {
|
||||
} else {
|
||||
list = [
|
||||
...(onlyTables ? [] : list),
|
||||
...createTableLikeList(dbinfo, x => !dbinfo.defaultSchema || dbinfo.defaultSchema == x.schemaName),
|
||||
...createTableLikeList(schemaList, dbinfo, x => !defaultSchema || defaultSchema == x.schemaName),
|
||||
|
||||
...(onlyTables
|
||||
? []
|
||||
|
||||
@@ -58,13 +58,17 @@
|
||||
/>
|
||||
|
||||
{#if driver?.showConnectionField('databaseFile', $values, showConnectionFieldArgs)}
|
||||
<FormElectronFileSelector label="Database file" name="databaseFile" disabled={isConnected || !electron} />
|
||||
<FormElectronFileSelector
|
||||
label="Database file"
|
||||
name="databaseFile"
|
||||
disabled={isConnected || !electron || disabledFields.includes('databaseFile')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('useDatabaseUrl', $values, showConnectionFieldArgs)}
|
||||
<div class="radio">
|
||||
<FormRadioGroupField
|
||||
disabled={isConnected}
|
||||
disabled={isConnected || disabledFields.includes('useDatabaseUrl')}
|
||||
name="useDatabaseUrl"
|
||||
matchValueToOption={(value, option) => !!option.value == !!value}
|
||||
options={[
|
||||
@@ -80,7 +84,7 @@
|
||||
label="Database URL"
|
||||
name="databaseUrl"
|
||||
placeholder={driver?.databaseUrlPlaceholder}
|
||||
disabled={isConnected}
|
||||
disabled={isConnected || disabledFields.includes('databaseUrl')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -101,15 +105,19 @@
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('endpoint', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Endpoint" name="endpoint" disabled={isConnected} />
|
||||
<FormTextField label="Endpoint" name="endpoint" disabled={isConnected || disabledFields.includes('endpoint')} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('endpointKey', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Key" name="endpointKey" disabled={isConnected} />
|
||||
<FormTextField label="Key" name="endpointKey" disabled={isConnected || disabledFields.includes('endpointKey')} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('clientLibraryPath', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Client library path" name="clientLibraryPath" disabled={isConnected} />
|
||||
<FormTextField
|
||||
label="Client library path"
|
||||
name="clientLibraryPath"
|
||||
disabled={isConnected || disabledFields.includes('clientLibraryPath')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('server', $values, showConnectionFieldArgs)}
|
||||
@@ -249,6 +257,14 @@
|
||||
<FormCheckboxField label={`Use only database ${defaultDatabase}`} name="singleDatabase" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('useSeparateSchemas', $values, showConnectionFieldArgs)}
|
||||
<FormCheckboxField
|
||||
label={`Use schemas separately (use this if you have many large schemas)`}
|
||||
name="useSeparateSchemas"
|
||||
disabled={isConnected}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if driver}
|
||||
<div class="row">
|
||||
<div class="col-6 mr-1">
|
||||
|
||||
@@ -8,6 +8,7 @@ import _ from 'lodash';
|
||||
import { safeJsonParse } from 'dbgate-tools';
|
||||
import { apiCall } from './utility/api';
|
||||
import { getOpenedTabsStorageName, isAdminPage } from './utility/pageDefs';
|
||||
import { switchCurrentDatabase } from './utility/common';
|
||||
|
||||
export interface TabDefinition {
|
||||
title: string;
|
||||
@@ -148,6 +149,8 @@ export const loadingPluginStore = writable({
|
||||
loadingPackageName: null,
|
||||
});
|
||||
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
||||
export const appliedCurrentSchema = writable<string>(null);
|
||||
export const loadingSchemaLists = writable({}); // dict [`${conid}::${database}`]: true
|
||||
|
||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||
@@ -295,7 +298,7 @@ export function subscribeApiDependendStores() {
|
||||
currentConfigValue = value;
|
||||
invalidateCommands();
|
||||
if (value.singleDbConnection) {
|
||||
currentDatabase.set(value.singleDbConnection);
|
||||
switchCurrentDatabase(value.singleDbConnection);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -311,3 +314,9 @@ appUpdaterActive.subscribe(value => {
|
||||
appUpdaterActiveValue = value;
|
||||
});
|
||||
export const getAppUpdaterActive = () => appUpdaterActiveValue;
|
||||
|
||||
let appliedCurrentSchemaValue = null;
|
||||
appliedCurrentSchema.subscribe(value => {
|
||||
appliedCurrentSchemaValue = value;
|
||||
});
|
||||
export const getAppliedCurrentSchema = () => appliedCurrentSchemaValue;
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
export let dbInfo;
|
||||
export let driver;
|
||||
export let resetCounter;
|
||||
export let isCreateTable;
|
||||
export let schemaList;
|
||||
|
||||
$: isWritable = !!setTableInfo;
|
||||
|
||||
@@ -165,15 +167,14 @@
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
{#if tableFormOptions}
|
||||
{#if tableInfo && (tableFormOptions || isCreateTable)}
|
||||
{#key resetCounter}
|
||||
<ObjectFieldsEditor
|
||||
title="Table properties"
|
||||
fieldDefinitions={tableFormOptions}
|
||||
values={_.pick(
|
||||
tableInfo,
|
||||
tableFormOptions.map(x => x.name)
|
||||
)}
|
||||
fieldDefinitions={tableFormOptions ?? []}
|
||||
pureNameTitle={isCreateTable ? 'Table name' : null}
|
||||
schemaList={isCreateTable && schemaList?.length >= 0 ? schemaList : null}
|
||||
values={_.pick(tableInfo, ['schemaName', 'pureName', ...(tableFormOptions ?? []).map(x => x.name)])}
|
||||
onChangeValues={vals => {
|
||||
if (!_.isEmpty(vals)) {
|
||||
setTableInfo(tbl => ({ ...tbl, ...vals }));
|
||||
|
||||
47
packages/web/src/tableeditor/newTable.ts
Normal file
47
packages/web/src/tableeditor/newTable.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import _ from 'lodash';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||
import { getAppliedCurrentSchema, getExtensions } from '../stores';
|
||||
|
||||
export default function newTable(connection, database) {
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${database}`;
|
||||
const driver = findEngineDriver(connection, getExtensions());
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Table #',
|
||||
tooltip,
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
current: {
|
||||
pureName: 'new_table',
|
||||
schemaName: getAppliedCurrentSchema() ?? driver?.dialect?.defaultSchemaName,
|
||||
columns: [
|
||||
{
|
||||
columnName: 'id',
|
||||
dataType: 'int',
|
||||
notNull: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [
|
||||
{
|
||||
columnName: 'id',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
forceNewTab: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -285,7 +285,7 @@
|
||||
draggingTabTarget,
|
||||
} from '../stores';
|
||||
import tabs from '../tabs';
|
||||
import { setSelectedTab } from '../utility/common';
|
||||
import { setSelectedTab, switchCurrentDatabase } from '../utility/common';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import { isElectronAvailable } from '../utility/getElectron';
|
||||
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
||||
@@ -420,11 +420,11 @@
|
||||
if (conid) {
|
||||
const connection = await getConnectionInfo({ conid, database });
|
||||
if (connection) {
|
||||
$currentDatabase = { connection, name: database };
|
||||
switchCurrentDatabase({ connection, name: database });
|
||||
return;
|
||||
}
|
||||
}
|
||||
$currentDatabase = null;
|
||||
switchCurrentDatabase(null);
|
||||
};
|
||||
|
||||
async function scrollInViewTab(tabid) {
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('ImportExportTab');
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'job',
|
||||
category: 'Job',
|
||||
getCurrentEditor,
|
||||
folder: 'jobs',
|
||||
format: 'json',
|
||||
fileExtension: 'job',
|
||||
|
||||
// undoRedo: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import moment from 'moment';
|
||||
import { writable } from 'svelte/store';
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import LargeButton from '../buttons/LargeButton.svelte';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import LargeFormButton from '../forms/LargeFormButton.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import createImpExpScript from '../impexp/createImpExpScript';
|
||||
import ImportExportConfigurator from '../impexp/ImportExportConfigurator.svelte';
|
||||
import PreviewDataGrid from '../impexp/PreviewDataGrid.svelte';
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import RunnerOutputFiles from '../query/RunnerOutputFiles.svelte';
|
||||
import SocketMessageView from '../query/SocketMessageView.svelte';
|
||||
import { currentArchive, currentDatabase, extensions, visibleWidgetSideBar, selectedWidget } from '../stores';
|
||||
import {
|
||||
currentArchive,
|
||||
currentDatabase,
|
||||
extensions,
|
||||
visibleWidgetSideBar,
|
||||
selectedWidget,
|
||||
activeTabId,
|
||||
} from '../stores';
|
||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||
import createRef from '../utility/createRef';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
@@ -24,6 +41,15 @@
|
||||
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import { changeTab } from '../utility/common';
|
||||
import _ from 'lodash';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { registerFileCommands } from '../commands/stdCommands';
|
||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
|
||||
|
||||
let busy = false;
|
||||
let executeNumber = 0;
|
||||
@@ -32,17 +58,62 @@
|
||||
const previewReaderStore = writable(null);
|
||||
|
||||
export let tabid;
|
||||
export let initialValues;
|
||||
export let uploadedFile = undefined;
|
||||
export let openedFile = undefined;
|
||||
export let importToCurrentTarget = false;
|
||||
|
||||
export let savedFile;
|
||||
export let savedFilePath;
|
||||
|
||||
const refreshArchiveFolderRef = createRef(null);
|
||||
|
||||
const formValues = writable({});
|
||||
|
||||
let domConfigurator;
|
||||
|
||||
export const activator = createActivator('ImportExportTab', true);
|
||||
|
||||
// const formValues = writable({
|
||||
// sourceStorageType: 'database',
|
||||
// targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
// targetArchiveFolder: $currentArchive,
|
||||
// sourceArchiveFolder: $currentArchive,
|
||||
// ...detectCurrentTarget(),
|
||||
// ...initialValues,
|
||||
// });
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
onInitialData: value => {
|
||||
$formValues = {
|
||||
sourceStorageType: 'database',
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetArchiveFolder: $currentArchive,
|
||||
sourceArchiveFolder: $currentArchive,
|
||||
...detectCurrentTarget(),
|
||||
...value,
|
||||
};
|
||||
|
||||
if (uploadedFile) {
|
||||
domConfigurator.addUploadedFile(uploadedFile);
|
||||
}
|
||||
if (openedFile) {
|
||||
domConfigurator.addUploadedFile(openedFile);
|
||||
}
|
||||
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
props: _.omit(tab.props, ['uploadedFile', 'openedFile', 'importToCurrentTarget']),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
// $: console.log('formValues', $formValues);
|
||||
|
||||
$: setEditorData($formValues);
|
||||
|
||||
$: updateTabTitle($formValues);
|
||||
|
||||
function detectCurrentTarget() {
|
||||
if (!importToCurrentTarget) return {};
|
||||
|
||||
@@ -67,7 +138,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: effect = useEffect(() => registerRunnerDone(runnerId));
|
||||
$: effectRunner = useEffect(() => registerRunnerDone(runnerId));
|
||||
|
||||
function registerRunnerDone(rid) {
|
||||
if (rid) {
|
||||
@@ -80,7 +151,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: $effect;
|
||||
$: $effectRunner;
|
||||
|
||||
const handleRunnerDone = () => {
|
||||
busy = false;
|
||||
@@ -94,7 +165,8 @@
|
||||
};
|
||||
|
||||
const handleGenerateScript = async e => {
|
||||
const code = await createImpExpScript($extensions, e.detail, undefined, true);
|
||||
const values = $formValues as any;
|
||||
const code = await createImpExpScript($extensions, values, undefined, true);
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Shell #',
|
||||
@@ -107,7 +179,7 @@
|
||||
|
||||
const handleExecute = async e => {
|
||||
if (busy) return;
|
||||
const values = e.detail;
|
||||
const values = $formValues as any;
|
||||
busy = true;
|
||||
const script = await createImpExpScript($extensions, values);
|
||||
executeNumber += 1;
|
||||
@@ -128,63 +200,84 @@
|
||||
runid: runnerId,
|
||||
});
|
||||
};
|
||||
|
||||
export function getData() {
|
||||
return $editorState.value || '';
|
||||
}
|
||||
|
||||
function getSourceTargetTitle(prefix, values) {
|
||||
switch (values[`${prefix}StorageType`]) {
|
||||
case 'database':
|
||||
return values[`${prefix}DatabaseName`] || 'DB';
|
||||
case 'archive':
|
||||
return values[`${prefix}ArchiveFolder`] || 'Archive';
|
||||
case 'query':
|
||||
return 'Query';
|
||||
default:
|
||||
return values[`${prefix}StorageType`]?.toUpperCase().replace(/@.*$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
function updateTabTitle(values) {
|
||||
if (savedFile || savedFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
title: `${getSourceTargetTitle('source', values)}->${getSourceTargetTitle('target', values)}(${values.sourceList?.length || 0})`,
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProvider
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
targetStorageType: getDefaultFileFormat($extensions).storageType,
|
||||
targetArchiveFolder: $currentArchive,
|
||||
sourceArchiveFolder: $currentArchive,
|
||||
...detectCurrentTarget(),
|
||||
...initialValues,
|
||||
}}
|
||||
>
|
||||
<HorizontalSplitter initialValue="70%">
|
||||
<div class="content" slot="1">
|
||||
<ImportExportConfigurator {uploadedFile} {openedFile} {previewReaderStore} />
|
||||
<ToolStripContainer>
|
||||
<FormProviderCore values={formValues}>
|
||||
<HorizontalSplitter initialValue="70%">
|
||||
<div class="content" slot="1">
|
||||
<ImportExportConfigurator
|
||||
bind:this={domConfigurator}
|
||||
{previewReaderStore}
|
||||
isTabActive={tabid == $activeTabId}
|
||||
/>
|
||||
|
||||
{#if busy}
|
||||
<LoadingInfo wrapper message="Processing import/export ..." />
|
||||
{/if}
|
||||
</div>
|
||||
{#if busy}
|
||||
<LoadingInfo wrapper message="Processing import/export ..." />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Output files" name="output" height="20%">
|
||||
<RunnerOutputFiles {runnerId} {executeNumber} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Messages" name="messages">
|
||||
<SocketMessageView
|
||||
eventName={runnerId ? `runner-info-${runnerId}` : null}
|
||||
{executeNumber}
|
||||
showNoMessagesAlert
|
||||
/>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Preview" name="preview" skip={!$previewReaderStore}>
|
||||
<PreviewDataGrid reader={$previewReaderStore} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
|
||||
<FormTextField label="Schedule" name="schedule" />
|
||||
<FormTextField label="Start variable index" name="startVariableIndex" />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
|
||||
<!-- <svelte:fragment slot="footer">
|
||||
<div class="flex m-2">
|
||||
{#if busy}
|
||||
<LargeButton icon="icon stop" on:click={handleCancel}>Stop</LargeButton>
|
||||
{:else}
|
||||
<LargeFormButton on:click={handleExecute} icon="icon run">Run</LargeFormButton>
|
||||
{/if}
|
||||
<LargeFormButton icon="img sql-file" on:click={handleGenerateScript}>Generate script</LargeFormButton>
|
||||
|
||||
<LargeButton on:click={closeCurrentModal} icon="icon close">Close</LargeButton>
|
||||
</div>
|
||||
</svelte:fragment> -->
|
||||
</FormProvider>
|
||||
<svelte:fragment slot="2">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Output files" name="output" height="20%">
|
||||
<RunnerOutputFiles {runnerId} {executeNumber} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Messages" name="messages">
|
||||
<SocketMessageView
|
||||
eventName={runnerId ? `runner-info-${runnerId}` : null}
|
||||
{executeNumber}
|
||||
showNoMessagesAlert
|
||||
/>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Preview" name="preview" skip={!$previewReaderStore}>
|
||||
<PreviewDataGrid reader={$previewReaderStore} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
|
||||
<FormTextField label="Schedule" name="schedule" />
|
||||
<FormTextField label="Start variable index" name="startVariableIndex" />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
<svelte:fragment slot="toolstrip">
|
||||
{#if busy}
|
||||
<ToolStripButton icon="icon stop" on:click={handleCancel}>Stop</ToolStripButton>
|
||||
{:else}
|
||||
<ToolStripButton on:click={handleExecute} icon="icon run">Run</ToolStripButton>
|
||||
{/if}
|
||||
<ToolStripButton icon="img sql-file" on:click={handleGenerateScript}>Generate script</ToolStripButton>
|
||||
<ToolStripSaveButton idPrefix="job" />
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
@@ -193,5 +286,6 @@
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--theme-bg-0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,15 +23,6 @@
|
||||
onClick: () => getCurrentEditor().copyNodeScript(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'shell.openWizard',
|
||||
category: 'Shell',
|
||||
name: 'Open wizard',
|
||||
// testEnabled: () => getCurrentEditor()?.openWizardEnabled(),
|
||||
onClick: () => getCurrentEditor().openWizard(),
|
||||
});
|
||||
|
||||
const configRegex = /\s*\/\/\s*@ImportExportConfigurator\s*\n\s*\/\/\s*(\{[^\n]+\})\n/;
|
||||
const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g;
|
||||
const initRegex = /([^\n]+\/\/\s*@init)/g;
|
||||
</script>
|
||||
@@ -47,8 +38,6 @@
|
||||
import { registerFileCommands } from '../commands/stdCommands';
|
||||
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import AceEditor from '../query/AceEditor.svelte';
|
||||
import RunnerOutputPane from '../query/RunnerOutputPane.svelte';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
@@ -60,7 +49,7 @@
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import useTimerLabel from '../utility/useTimerLabel';
|
||||
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
@@ -149,19 +138,6 @@
|
||||
copyTextToClipboard(resp);
|
||||
}
|
||||
|
||||
// export function openWizardEnabled() {
|
||||
// return ($editorValue || '').match(configRegex);
|
||||
// }
|
||||
|
||||
export function openWizard() {
|
||||
const jsonTextMatch = ($editorValue || '').match(configRegex);
|
||||
if (jsonTextMatch) {
|
||||
showModal(ImportExportModal, { initialValues: JSON.parse(jsonTextMatch[1]) });
|
||||
} else {
|
||||
showSnackbarError('No wizard info found');
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveScript() {
|
||||
const selectedText = domEditor.getEditor().getSelectedText();
|
||||
const editorText = $editorValue;
|
||||
@@ -208,7 +184,6 @@
|
||||
return [
|
||||
{ command: 'shell.execute' },
|
||||
{ command: 'shell.kill' },
|
||||
{ command: 'shell.openWizard' },
|
||||
{ divider: true },
|
||||
{ command: 'shell.toggleComment' },
|
||||
{ divider: true },
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon close',
|
||||
testEnabled: () => getCurrentEditor()?.canSave(),
|
||||
testEnabled: () => getCurrentEditor()?.canResetChanges(),
|
||||
onClick: () => getCurrentEditor().reset(),
|
||||
});
|
||||
</script>
|
||||
@@ -39,29 +39,23 @@
|
||||
import _ from 'lodash';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
|
||||
import ColumnLabel from '../elements/ColumnLabel.svelte';
|
||||
import ConstraintLabel from '../elements/ConstraintLabel.svelte';
|
||||
import ForeignKeyObjectListControl from '../elements/ForeignKeyObjectListControl.svelte';
|
||||
|
||||
import { extensions } from '../stores';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import TableEditor from '../tableeditor/TableEditor.svelte';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
|
||||
import { useConnectionInfo, useDatabaseInfo, useDbCore } from '../utility/metadataLoaders';
|
||||
import { useConnectionInfo, useDatabaseInfo, useDbCore, useSchemaList } from '../utility/metadataLoaders';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { changeTab } from '../utility/common';
|
||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { apiCall } from '../utility/api';
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { changeTab } from '../utility/common';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -81,6 +75,7 @@
|
||||
$: tableInfoWithPairingId = $tableInfo ? generateTablePairingId($tableInfo) : null;
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: driver = findEngineDriver($connection, $extensions);
|
||||
$: schemaList = useSchemaList({ conid, database });
|
||||
|
||||
const { editorState, editorValue, setEditorData, clearEditorData } = useEditorData({ tabid });
|
||||
|
||||
@@ -90,30 +85,11 @@
|
||||
return objectTypeField == 'tables' && !!$editorValue && !$connection?.isReadOnly;
|
||||
}
|
||||
|
||||
export function save() {
|
||||
if ($editorValue.base) {
|
||||
doSave(null);
|
||||
} else {
|
||||
showModal(InputTextModal, {
|
||||
header: 'Set table name',
|
||||
value: savedName || 'newTable',
|
||||
label: 'Table name',
|
||||
onConfirm: name => {
|
||||
savedName = name;
|
||||
setEditorData(tbl => ({
|
||||
base: tbl.base,
|
||||
current: {
|
||||
...tbl.current,
|
||||
pureName: name,
|
||||
},
|
||||
}));
|
||||
doSave(name);
|
||||
},
|
||||
});
|
||||
}
|
||||
export function canResetChanges() {
|
||||
return canSave() && !!$editorValue.base;
|
||||
}
|
||||
|
||||
function doSave(createTableName) {
|
||||
export function save() {
|
||||
const { sql, recreates } = getAlterTableScript(
|
||||
$editorValue.base,
|
||||
extendTableInfo(fillConstraintNames($editorValue.current, driver.dialect)),
|
||||
@@ -127,32 +103,30 @@
|
||||
sql,
|
||||
recreates,
|
||||
onConfirm: () => {
|
||||
handleConfirmSql(sql, createTableName);
|
||||
handleConfirmSql(sql);
|
||||
},
|
||||
engine: driver.engine,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleConfirmSql(sql, createTableName) {
|
||||
async function handleConfirmSql(sql) {
|
||||
const resp = await apiCall('database-connections/run-script', { conid, database, sql, useTransaction: true });
|
||||
const { errorMessage } = resp || {};
|
||||
if (errorMessage) {
|
||||
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
|
||||
} else {
|
||||
if (createTableName) {
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
title: createTableName,
|
||||
props: {
|
||||
...tab.props,
|
||||
pureName: createTableName,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
await apiCall('database-connections/sync-model', { conid, database });
|
||||
showSnackbarSuccess('Saved to database');
|
||||
const isCreateTable = $editorValue?.base == null;
|
||||
const tableName = _.pick($editorValue.current, ['pureName', 'schemaName']);
|
||||
clearEditorData();
|
||||
if (isCreateTable) {
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
title: tableName.pureName,
|
||||
props: { ...tab.props, ...tableName },
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +147,10 @@
|
||||
bind:this={domEditor}
|
||||
tableInfo={showTable}
|
||||
dbInfo={$dbInfo}
|
||||
schemaList={$schemaList}
|
||||
{driver}
|
||||
{resetCounter}
|
||||
isCreateTable={objectTypeField == 'tables' && !$editorValue?.base}
|
||||
setTableInfo={objectTypeField == 'tables' && !$connection?.isReadOnly && hasPermission(`dbops/model/edit`)
|
||||
? tableInfoUpdater =>
|
||||
setEditorData(tbl =>
|
||||
@@ -191,7 +167,10 @@
|
||||
: null}
|
||||
/>
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandButton command="tableStructure.save" />
|
||||
<ToolStripCommandButton
|
||||
command="tableStructure.save"
|
||||
buttonLabel={$editorValue?.base ? 'Alter table' : 'Create table'}
|
||||
/>
|
||||
<ToolStripCommandButton command="tableStructure.reset" />
|
||||
<ToolStripCommandButton command="tableEditor.addColumn" />
|
||||
<ToolStripCommandButton command="tableEditor.addIndex" hideDisabled />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { currentDatabase, getCurrentDatabase, getLockedDatabaseMode, openedTabs
|
||||
import { shouldShowTab } from '../tabpanel/TabsPanel.svelte';
|
||||
import { callWhenAppLoaded, getAppLoaded } from './appLoadManager';
|
||||
import { getConnectionInfo } from './metadataLoaders';
|
||||
import { switchCurrentDatabase } from './common';
|
||||
|
||||
let lastCurrentTab = null;
|
||||
|
||||
@@ -20,7 +21,7 @@ openedTabs.subscribe(value => {
|
||||
if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
|
||||
const doWork = async () => {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
currentDatabase.set({
|
||||
switchCurrentDatabase({
|
||||
connection,
|
||||
name: database,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { getOpenedTabs, openedTabs } from '../stores';
|
||||
import { findDefaultSchema, findEngineDriver, isCompositeDbName } from 'dbgate-tools';
|
||||
import { currentDatabase, getExtensions, getOpenedTabs, loadingSchemaLists, openedTabs } from '../stores';
|
||||
import _ from 'lodash';
|
||||
import { getSchemaList } from './metadataLoaders';
|
||||
import { showSnackbarError } from './snackbar';
|
||||
|
||||
export class LoadingToken {
|
||||
isCanceled = false;
|
||||
@@ -59,21 +62,21 @@ export function isMac() {
|
||||
export function formatKeyText(keyText: string): string {
|
||||
if (isMac()) {
|
||||
return keyText
|
||||
.replace('CtrlOrCommand+', '⌘ ')
|
||||
.replace('Shift+', '⇧ ')
|
||||
.replace('Alt+', '⌥ ')
|
||||
.replace('Command+', '⌘ ')
|
||||
.replace('Ctrl+', '⌃ ')
|
||||
.replace('Backspace', '⌫ ');
|
||||
.replace(/CtrlOrCommand\+/g, '⌘ ')
|
||||
.replace(/Shift\+/g, '⇧ ')
|
||||
.replace(/Alt\+/g, '⌥ ')
|
||||
.replace(/Command\+/g, '⌘ ')
|
||||
.replace(/Ctrl\+/g, '⌃ ')
|
||||
.replace(/Backspace/g, '⌫ ');
|
||||
}
|
||||
return keyText.replace('CtrlOrCommand+', 'Ctrl+');
|
||||
return keyText.replace(/CtrlOrCommand\+/g, 'Ctrl+');
|
||||
}
|
||||
|
||||
export function resolveKeyText(keyText: string): string {
|
||||
if (isMac()) {
|
||||
return keyText.replace('CtrlOrCommand+', 'Command+');
|
||||
return keyText.replace(/CtrlOrCommand\+/g, 'Command+');
|
||||
}
|
||||
return keyText.replace('CtrlOrCommand+', 'Ctrl+');
|
||||
return keyText.replace(/CtrlOrCommand\+/g, 'Ctrl+');
|
||||
}
|
||||
|
||||
export function isCtrlOrCommandKey(event) {
|
||||
@@ -82,3 +85,37 @@ export function isCtrlOrCommandKey(event) {
|
||||
}
|
||||
return event.ctrlKey;
|
||||
}
|
||||
|
||||
export async function loadSchemaList(conid, database) {
|
||||
try {
|
||||
loadingSchemaLists.update(x => ({ ...x, [`${conid}::${database}`]: true }));
|
||||
const schemas = await getSchemaList({ conid, database });
|
||||
if (schemas.errorMessage) {
|
||||
showSnackbarError(`Error loading schemas: ${schemas.errorMessage}`);
|
||||
console.error('Error loading schemas', schemas.errorMessage);
|
||||
return;
|
||||
}
|
||||
return schemas;
|
||||
} finally {
|
||||
loadingSchemaLists.update(x => _.omit(x, [`${conid}::${database}`]));
|
||||
}
|
||||
}
|
||||
|
||||
export async function switchCurrentDatabase(data) {
|
||||
if (data?.connection?.useSeparateSchemas && !isCompositeDbName(data.name)) {
|
||||
const conid = data.connection._id;
|
||||
const database = data.name;
|
||||
const storageKey = `selected-schema-${conid}-${database}`;
|
||||
const schemaInStorage = localStorage.getItem(storageKey);
|
||||
const schemas = await loadSchemaList(conid, database);
|
||||
if (!schemas) return;
|
||||
const driver = findEngineDriver(data.connection, getExtensions());
|
||||
const defaultSchema = findDefaultSchema(schemas, driver?.dialect, schemaInStorage);
|
||||
currentDatabase.set({
|
||||
...data,
|
||||
name: `${data.name}::${defaultSchema}`,
|
||||
});
|
||||
} else {
|
||||
currentDatabase.set(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ function processTags(items) {
|
||||
function extractMenuItems(menu, options = null) {
|
||||
let res = [];
|
||||
doExtractMenuItems(menu, res, options);
|
||||
// console.log('BEFORE PROCESS TAGS', res);
|
||||
res = processTags(res);
|
||||
return res;
|
||||
}
|
||||
@@ -142,7 +143,10 @@ export function getContextMenu(): any {
|
||||
|
||||
export function prepareMenuItems(items, options, commandsCustomized) {
|
||||
const extracted = extractMenuItems(items, options);
|
||||
// console.log('EXTRACTED', extracted);
|
||||
const compacted = _.compact(extracted.map(x => mapItem(x, commandsCustomized)));
|
||||
// console.log('COMPACTED', compacted);
|
||||
const filtered = filterMenuItems(compacted);
|
||||
// console.log('FILTERED', filtered);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ import hasPermission from './hasPermission';
|
||||
export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition) => Function, advancedExportMenuItem) {
|
||||
const extensions = getExtensions();
|
||||
return [
|
||||
{
|
||||
text: 'Export advanced...',
|
||||
...advancedExportMenuItem,
|
||||
},
|
||||
{ divider: true },
|
||||
...extensions.quickExports.map(fmt => ({
|
||||
text: fmt.label,
|
||||
onClick: handler(fmt),
|
||||
@@ -25,11 +30,6 @@ export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition)
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: 'More...',
|
||||
...advancedExportMenuItem,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@ export default function createQuickExportMenu(
|
||||
return {
|
||||
text: 'Export',
|
||||
submenu: createQuickExportMenuItems(handler, advancedExportMenuItem),
|
||||
...advancedExportMenuItem,
|
||||
...additionalFields,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ export async function exportSqlDump(outputFile, connection, databaseName, pureFi
|
||||
onOpenResult:
|
||||
pureFileName && !getElectron()
|
||||
? () => {
|
||||
downloadFromApi(`uploads/get?file=${pureFileName}`, 'file.sql');
|
||||
}
|
||||
downloadFromApi(`uploads/get?file=${pureFileName}`, 'file.sql');
|
||||
}
|
||||
: null,
|
||||
openResultLabel: 'Download SQL file',
|
||||
});
|
||||
@@ -226,17 +226,18 @@ export async function downloadFromApi(route: string, donloadName: string) {
|
||||
method: 'GET',
|
||||
headers: resolveApiHeaders(),
|
||||
})
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const objUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
a.download = donloadName;
|
||||
a.href = objUrl;
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(objUrl)
|
||||
})
|
||||
})
|
||||
URL.revokeObjectURL(objUrl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
15
packages/web/src/utility/importExportTools.ts
Normal file
15
packages/web/src/utility/importExportTools.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import openNewTab from './openNewTab';
|
||||
|
||||
export function openImportExportTab(editorProps, additionalProps = {}) {
|
||||
openNewTab(
|
||||
{
|
||||
tabComponent: 'ImportExportTab',
|
||||
title: 'Import/Export',
|
||||
icon: 'img export',
|
||||
props: additionalProps,
|
||||
},
|
||||
{
|
||||
editor: editorProps,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,12 @@ const databaseInfoLoader = ({ conid, database }) => ({
|
||||
transform: extendDatabaseInfo,
|
||||
});
|
||||
|
||||
const schemaListLoader = ({ conid, database }) => ({
|
||||
url: 'database-connections/schema-list',
|
||||
params: { conid, database },
|
||||
reloadTrigger: { key: `schema-list-changed`, conid, database },
|
||||
});
|
||||
|
||||
// const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({
|
||||
// url: 'metadata/table-info',
|
||||
// params: { conid, database, schemaName, pureName },
|
||||
@@ -449,3 +455,9 @@ export function useAuthTypes(args) {
|
||||
// export function useDatabaseKeys(args) {
|
||||
// return useCore(databaseKeysLoader, args);
|
||||
// }
|
||||
export function getSchemaList(args) {
|
||||
return getCore(schemaListLoader, args);
|
||||
}
|
||||
export function useSchemaList(args) {
|
||||
return useCore(schemaListLoader, args);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { get } from 'svelte/store';
|
||||
import newQuery from '../query/newQuery';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import getElectron from './getElectron';
|
||||
import { currentDatabase, extensions, getCurrentDatabase } from '../stores';
|
||||
import { getUploadListener } from './uploadFiles';
|
||||
import {getConnectionLabel, getDatabaseFileLabel } from 'dbgate-tools';
|
||||
import { getConnectionLabel, getDatabaseFileLabel } from 'dbgate-tools';
|
||||
import { apiCall } from './api';
|
||||
import openNewTab from './openNewTab';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
import { SAVED_FILE_HANDLERS } from '../appobj/SavedFileAppObject.svelte';
|
||||
import _ from 'lodash';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import { openImportExportTab } from './importExportTools';
|
||||
import { switchCurrentDatabase } from './common';
|
||||
|
||||
export function canOpenByElectron(file, extensions) {
|
||||
if (!file) return false;
|
||||
@@ -38,7 +39,7 @@ export async function openSqliteFile(filePath) {
|
||||
singleDatabase: true,
|
||||
defaultDatabase,
|
||||
});
|
||||
currentDatabase.set({
|
||||
switchCurrentDatabase({
|
||||
connection: resp,
|
||||
name: getDatabaseFileLabel(filePath),
|
||||
});
|
||||
@@ -178,17 +179,30 @@ export function openElectronFileCore(filePath, extensions) {
|
||||
shortName: parsed.name,
|
||||
});
|
||||
} else {
|
||||
showModal(ImportExportModal, {
|
||||
openedFile: {
|
||||
filePath,
|
||||
storageType: format.storageType,
|
||||
shortName: parsed.name,
|
||||
},
|
||||
importToCurrentTarget: true,
|
||||
initialValues: {
|
||||
openImportExportTab(
|
||||
{
|
||||
sourceStorageType: format.storageType,
|
||||
},
|
||||
});
|
||||
{
|
||||
openedFile: {
|
||||
filePath,
|
||||
storageType: format.storageType,
|
||||
shortName: parsed.name,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// showModal(ImportExportModal, {
|
||||
// openedFile: {
|
||||
// filePath,
|
||||
// storageType: format.storageType,
|
||||
// shortName: parsed.name,
|
||||
// },
|
||||
// importToCurrentTarget: true,
|
||||
// initialValues: {
|
||||
// sourceStorageType: format.storageType,
|
||||
// },
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ function findFreeNumber(numbers: number[]) {
|
||||
// return res;
|
||||
}
|
||||
|
||||
export default async function openNewTab(newTab, initialData = undefined, options = undefined) {
|
||||
export default async function openNewTab(newTab, initialData: any = undefined, options: any = undefined) {
|
||||
const oldTabs = getOpenedTabs();
|
||||
const activeTab = getActiveTab();
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import getElectron from './getElectron';
|
||||
import resolveApi, { resolveApiHeaders } from './resolveApi';
|
||||
import { findFileFormat } from '../plugins/fileformats';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import openNewTab from './openNewTab';
|
||||
import { openImportExportTab } from './importExportTools';
|
||||
|
||||
let uploadListener;
|
||||
|
||||
@@ -79,13 +79,23 @@ export default function uploadFiles(files) {
|
||||
uploadListener(fileData);
|
||||
} else {
|
||||
if (findFileFormat(ext, fileData.storageType)) {
|
||||
showModal(ImportExportModal, {
|
||||
uploadedFile: fileData,
|
||||
importToCurrentTarget: true,
|
||||
initialValues: {
|
||||
openImportExportTab(
|
||||
{
|
||||
sourceStorageType: fileData.storageType,
|
||||
},
|
||||
});
|
||||
{
|
||||
uploadedFile: fileData,
|
||||
importToCurrentTarget: true,
|
||||
}
|
||||
);
|
||||
|
||||
// showModal(ImportExportModal, {
|
||||
// uploadedFile: fileData,
|
||||
// importToCurrentTarget: true,
|
||||
// initialValues: {
|
||||
// sourceStorageType: fileData.storageType,
|
||||
// },
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
const queryFiles = useFiles({ folder: 'query' });
|
||||
const sqliteFiles = useFiles({ folder: 'sqlite' });
|
||||
const diagramFiles = useFiles({ folder: 'diagrams' });
|
||||
const jobFiles = useFiles({ folder: 'jobs' });
|
||||
const perspectiveFiles = useFiles({ folder: 'perspectives' });
|
||||
|
||||
$: files = [
|
||||
@@ -31,11 +32,12 @@
|
||||
...($sqliteFiles || []),
|
||||
...($diagramFiles || []),
|
||||
...($perspectiveFiles || []),
|
||||
...($jobFiles || []),
|
||||
];
|
||||
|
||||
function handleRefreshFiles() {
|
||||
apiCall('files/refresh', {
|
||||
folders: ['sql', 'shell', 'markdown', 'charts', 'query', 'sqlite', 'diagrams', 'perspectives'],
|
||||
folders: ['sql', 'shell', 'markdown', 'charts', 'query', 'sqlite', 'diagrams', 'perspectives', 'jobs'],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
171
packages/web/src/widgets/SchemaSelector.svelte
Normal file
171
packages/web/src/widgets/SchemaSelector.svelte
Normal file
@@ -0,0 +1,171 @@
|
||||
<script lang="ts">
|
||||
import InlineButton from '../buttons/InlineButton.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
|
||||
import _ from 'lodash';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { runOperationOnDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { appliedCurrentSchema, currentDatabase } from '../stores';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { extractDbNameFromComposite, extractSchemaNameFromComposite, findDefaultSchema } from 'dbgate-tools';
|
||||
|
||||
export let schemaList;
|
||||
export let objectList;
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let connection;
|
||||
|
||||
export let negativeMarginTop = false;
|
||||
|
||||
export let driver;
|
||||
|
||||
let selectedSchema = null;
|
||||
|
||||
$: valueStorageKey = `selected-schema-${conid}-${database}`;
|
||||
|
||||
$: {
|
||||
if (selectedSchema != null) {
|
||||
$appliedCurrentSchema = selectedSchema;
|
||||
} else {
|
||||
const usedSchemas = Object.keys(countBySchema);
|
||||
const defaultSchema = findDefaultSchema(schemaList, driver?.dialect);
|
||||
if (usedSchemas.length == 1 && usedSchemas[0] == defaultSchema) {
|
||||
$appliedCurrentSchema = usedSchemas[0];
|
||||
} else {
|
||||
$appliedCurrentSchema = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function computeCountBySchema(list) {
|
||||
const res = {};
|
||||
for (const item of list) {
|
||||
if (!item.schemaName) continue;
|
||||
if (!res[item.schemaName]) res[item.schemaName] = 0;
|
||||
res[item.schemaName] += 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
$: realSchemaList = _.uniq(
|
||||
_.compact([selectedSchema, ...Object.keys(countBySchema), ...(schemaList?.map(x => x.schemaName) ?? [])])
|
||||
);
|
||||
$: countBySchema = computeCountBySchema(objectList ?? []);
|
||||
|
||||
function handleCreateSchema() {
|
||||
showModal(InputTextModal, {
|
||||
header: 'Create schema',
|
||||
value: 'newschema',
|
||||
label: 'Schema name',
|
||||
onConfirm: async name => {
|
||||
const dbid = { conid, database };
|
||||
await runOperationOnDatabase(
|
||||
dbid,
|
||||
{
|
||||
type: 'createSchema',
|
||||
schemaName: name,
|
||||
},
|
||||
'schema-list-changed'
|
||||
);
|
||||
if (selectedSchema) {
|
||||
selectedSchema = name;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
function handleDropSchema() {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really drop schema ${$appliedCurrentSchema}?`,
|
||||
onConfirm: async () => {
|
||||
const dbid = { conid, database };
|
||||
runOperationOnDatabase(
|
||||
dbid,
|
||||
{
|
||||
type: 'dropSchema',
|
||||
schemaName: $appliedCurrentSchema,
|
||||
},
|
||||
'schema-list-changed'
|
||||
);
|
||||
selectedSchema = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$: if (connection?.useSeparateSchemas) {
|
||||
selectedSchema =
|
||||
extractSchemaNameFromComposite($currentDatabase?.name) ?? findDefaultSchema(schemaList, driver?.dialect);
|
||||
} else {
|
||||
selectedSchema = localStorage.getItem(valueStorageKey ?? '');
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if realSchemaList.length > 0}
|
||||
<div class="wrapper" class:negativeMarginTop>
|
||||
<div class="mr-1">Schema:</div>
|
||||
<SelectField
|
||||
isNative
|
||||
options={connection?.useSeparateSchemas
|
||||
? (schemaList?.map(x => ({ label: x.schemaName, value: x.schemaName })) ?? [])
|
||||
: [
|
||||
{ label: `All schemas (${objectList?.length ?? 0})`, value: '' },
|
||||
...realSchemaList.map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
|
||||
]}
|
||||
value={selectedSchema ?? $appliedCurrentSchema ?? ''}
|
||||
on:change={e => {
|
||||
if (connection?.useSeparateSchemas) {
|
||||
switchCurrentDatabase({
|
||||
connection,
|
||||
name: `${extractDbNameFromComposite(database)}::${e.detail}`,
|
||||
});
|
||||
} else {
|
||||
selectedSchema = e.detail;
|
||||
}
|
||||
localStorage.setItem(valueStorageKey, e.detail);
|
||||
}}
|
||||
selectClass="schema-select"
|
||||
/>
|
||||
|
||||
{#if selectedSchema != null}
|
||||
<InlineButton
|
||||
on:click={() => {
|
||||
selectedSchema = null;
|
||||
localStorage.removeItem(valueStorageKey);
|
||||
}}
|
||||
title="Reset to default"
|
||||
>
|
||||
<FontIcon icon="icon close" />
|
||||
</InlineButton>
|
||||
{/if}
|
||||
<InlineButton on:click={handleCreateSchema} title="Add new schema" square>
|
||||
<FontIcon icon="icon plus-thick" />
|
||||
</InlineButton>
|
||||
<InlineButton on:click={handleDropSchema} title="Delete schema" square disabled={!$appliedCurrentSchema}>
|
||||
<FontIcon icon="icon minus-thick" />
|
||||
</InlineButton>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
margin-bottom: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.negativeMarginTop {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
:global(.schema-select) {
|
||||
flex: 1;
|
||||
min-width: 10px;
|
||||
min-height: 22px;
|
||||
width: 10px;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,13 @@
|
||||
import InlineButton from '../buttons/InlineButton.svelte';
|
||||
import SearchInput from '../elements/SearchInput.svelte';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
import { useConnectionInfo, useDatabaseInfo, useDatabaseStatus, useUsedApps } from '../utility/metadataLoaders';
|
||||
import {
|
||||
useConnectionInfo,
|
||||
useDatabaseInfo,
|
||||
useDatabaseStatus,
|
||||
useSchemaList,
|
||||
useUsedApps,
|
||||
} from '../utility/metadataLoaders';
|
||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||
import AppObjectList from '../appobj/AppObjectList.svelte';
|
||||
import _ from 'lodash';
|
||||
@@ -35,6 +41,8 @@
|
||||
import runCommand from '../commands/runCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
import SchemaSelector from './SchemaSelector.svelte';
|
||||
import { appliedCurrentSchema } from '../stores';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -43,6 +51,7 @@
|
||||
|
||||
$: objects = useDatabaseInfo({ conid, database });
|
||||
$: status = useDatabaseStatus({ conid, database });
|
||||
$: schemaList = useSchemaList({ conid, database });
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: driver = findEngineDriver($connection, $extensions);
|
||||
@@ -76,6 +85,7 @@
|
||||
|
||||
const handleRefreshDatabase = () => {
|
||||
apiCall('database-connections/refresh', { conid, database });
|
||||
apiCall('database-connections/dispatch-database-changed-event', { event: 'schema-list-changed', conid, database });
|
||||
};
|
||||
|
||||
function createAddMenu() {
|
||||
@@ -99,6 +109,12 @@
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
$: flatFilteredList = objectList.filter(data => {
|
||||
const matcher = databaseObjectAppObject.createMatcher(data);
|
||||
if (matcher && !matcher(filter)) return false;
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $status && $status.name == 'error'}
|
||||
@@ -107,6 +123,14 @@
|
||||
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
</WidgetsInnerContainer>
|
||||
{:else if objectList.length == 0 && $status && $status.name != 'pending' && $status.name != 'checkStructure' && $status.name != 'loadStructure' && $objects}
|
||||
<SchemaSelector
|
||||
schemaList={_.isArray($schemaList) ? $schemaList : null}
|
||||
objectList={flatFilteredList}
|
||||
connection={$connection}
|
||||
{conid}
|
||||
{database}
|
||||
{driver}
|
||||
/>
|
||||
<WidgetsInnerContainer>
|
||||
<ErrorInfo
|
||||
message={`Database ${database} is empty or structure is not loaded, press Refresh button to reload structure`}
|
||||
@@ -130,16 +154,28 @@
|
||||
<SearchInput placeholder="Search in tables, objects, # prefix in columns" bind:value={filter} />
|
||||
<CloseSearchButton bind:filter />
|
||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list">
|
||||
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<SchemaSelector
|
||||
schemaList={_.isArray($schemaList) ? $schemaList : null}
|
||||
objectList={flatFilteredList}
|
||||
connection={$connection}
|
||||
{conid}
|
||||
{database}
|
||||
{driver}
|
||||
negativeMarginTop
|
||||
/>
|
||||
|
||||
<WidgetsInnerContainer>
|
||||
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
|
||||
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
|
||||
{:else}
|
||||
<AppObjectList
|
||||
list={objectList.map(x => ({ ...x, conid, database }))}
|
||||
list={objectList
|
||||
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||
.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||
subItemsComponent={SubColumnParamList}
|
||||
@@ -147,7 +183,11 @@
|
||||
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
|
||||
expandIconFunc={chevronExpandIcon}
|
||||
{filter}
|
||||
passProps={{ showPinnedInsteadOfUnpin: true, connection: $connection }}
|
||||
passProps={{
|
||||
showPinnedInsteadOfUnpin: true,
|
||||
connection: $connection,
|
||||
hideSchemaName: !!$appliedCurrentSchema,
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
const { DatabaseAnalyser } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
const sql = require('./sql');
|
||||
|
||||
function extractDataType(dataType) {
|
||||
@@ -24,7 +24,7 @@ class Analyser extends DatabaseAnalyser {
|
||||
|
||||
createQuery(resFileName, typeFields, replacements = {}) {
|
||||
let res = sql[resFileName];
|
||||
res = res.replace('#DATABASE#', this.pool._database_name);
|
||||
res = res.replace('#DATABASE#', this.dbhan.database);
|
||||
return super.createQuery(res, typeFields, replacements);
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ class Analyser extends DatabaseAnalyser {
|
||||
async _computeSingleObjectId() {
|
||||
const { pureName } = this.singleObjectFilter;
|
||||
const resId = await this.driver.query(
|
||||
this.pool,
|
||||
`SELECT uuid as id FROM system.tables WHERE database = '${this.pool._database_name}' AND name='${pureName}'`
|
||||
this.dbhan,
|
||||
`SELECT uuid as id FROM system.tables WHERE database = '${this.dbhan.database}' AND name='${pureName}'`
|
||||
);
|
||||
this.singleObjectId = resId.rows[0]?.id;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ const _ = require('lodash');
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createOracleBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, pool, name, options);
|
||||
function createOracleBulkInsertStream(driver, stream, dbhan, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options);
|
||||
|
||||
writable.send = async () => {
|
||||
await pool.insert({
|
||||
await dbhan.client.insert({
|
||||
table: name.pureName,
|
||||
values: writable.buffer,
|
||||
format: 'JSONEachRow',
|
||||
|
||||
@@ -15,16 +15,18 @@ const driver = {
|
||||
url: databaseUrl,
|
||||
username: user,
|
||||
password: password,
|
||||
database: database,
|
||||
database,
|
||||
});
|
||||
|
||||
client._database_name = database;
|
||||
return client;
|
||||
return {
|
||||
client,
|
||||
database,
|
||||
};
|
||||
},
|
||||
// called for retrieve data (eg. browse in data grid) and for update database
|
||||
async query(client, query, options) {
|
||||
async query(dbhan, query, options) {
|
||||
if (options?.discardResult) {
|
||||
await client.command({
|
||||
await dbhan.client.command({
|
||||
query,
|
||||
});
|
||||
return {
|
||||
@@ -32,7 +34,7 @@ const driver = {
|
||||
columns: [],
|
||||
};
|
||||
} else {
|
||||
const resultSet = await client.query({
|
||||
const resultSet = await dbhan.client.query({
|
||||
query,
|
||||
format: 'JSONCompactEachRowWithNamesAndTypes',
|
||||
});
|
||||
@@ -57,10 +59,10 @@ const driver = {
|
||||
}
|
||||
},
|
||||
// called in query console
|
||||
async stream(client, query, options) {
|
||||
async stream(dbhan, query, options) {
|
||||
try {
|
||||
if (!query.match(/^\s*SELECT/i)) {
|
||||
const resp = await client.command({
|
||||
const resp = await dbhan.client.command({
|
||||
query,
|
||||
});
|
||||
// console.log('RESP', resp);
|
||||
@@ -76,7 +78,7 @@ const driver = {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultSet = await client.query({
|
||||
const resultSet = await dbhan.client.query({
|
||||
query,
|
||||
format: 'JSONCompactEachRowWithNamesAndTypes',
|
||||
});
|
||||
@@ -138,13 +140,13 @@ const driver = {
|
||||
}
|
||||
},
|
||||
// called when exporting table or view
|
||||
async readQuery(client, query, structure) {
|
||||
async readQuery(dbhan, query, structure) {
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
});
|
||||
|
||||
const resultSet = await client.query({
|
||||
const resultSet = await dbhan.client.query({
|
||||
query,
|
||||
format: 'JSONCompactEachRowWithNamesAndTypes',
|
||||
});
|
||||
@@ -190,12 +192,12 @@ const driver = {
|
||||
|
||||
return pass;
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
return createBulkInsertStream(this, stream, pool, name, options);
|
||||
async writeTable(dbhan, name, options) {
|
||||
return createBulkInsertStream(this, stream, dbhan, name, options);
|
||||
},
|
||||
// detect server version
|
||||
async getVersion(client) {
|
||||
const resultSet = await client.query({
|
||||
async getVersion(dbhan) {
|
||||
const resultSet = await dbhan.client.query({
|
||||
query: 'SELECT version() as version',
|
||||
format: 'JSONEachRow',
|
||||
});
|
||||
@@ -203,8 +205,8 @@ const driver = {
|
||||
return { version: dataset[0].version };
|
||||
},
|
||||
// list databases on server
|
||||
async listDatabases(client) {
|
||||
const resultSet = await client.query({
|
||||
async listDatabases(dbhan) {
|
||||
const resultSet = await dbhan.client.query({
|
||||
query: `SELECT name
|
||||
FROM system.databases
|
||||
WHERE name NOT IN ('system', 'information_schema', 'information_schema_ro', 'INFORMATION_SCHEMA')`,
|
||||
@@ -214,8 +216,8 @@ const driver = {
|
||||
return dataset;
|
||||
},
|
||||
|
||||
async close(client) {
|
||||
return client.close();
|
||||
async close(dbhan) {
|
||||
return dbhan.client.close();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"csv": "^5.3.2",
|
||||
"csv": "^6.3.10",
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"line-reader": "^0.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,32 @@ const zipObject = require('lodash/zipObject');
|
||||
const csv = require('csv');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const lineReader = require('line-reader');
|
||||
|
||||
let dbgateApi;
|
||||
|
||||
function readFirstLine(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
lineReader.open(file, (err, reader) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (reader.hasNextLine()) {
|
||||
reader.nextLine((err, line) => {
|
||||
if (err) {
|
||||
reader.close(() => reject(err)); // Ensure reader is closed on error
|
||||
return;
|
||||
}
|
||||
reader.close(() => resolve(line)); // Ensure reader is closed after reading
|
||||
});
|
||||
} else {
|
||||
reader.close(() => resolve(null)); // Properly close if no lines are present
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class CsvPrepareStream extends stream.Transform {
|
||||
constructor({ header }) {
|
||||
super({ objectMode: true });
|
||||
@@ -46,13 +70,29 @@ class CsvPrepareStream extends stream.Transform {
|
||||
|
||||
async function reader({ fileName, encoding = 'utf-8', header = true, delimiter, limitRows = undefined }) {
|
||||
console.log(`Reading file ${fileName}`);
|
||||
const downloadedFile = await dbgateApi.download(fileName);
|
||||
|
||||
if (!delimiter) {
|
||||
// auto detect delimiter
|
||||
// read first line from downloadedFile
|
||||
const firstLine = await readFirstLine(downloadedFile);
|
||||
if (firstLine) {
|
||||
const delimiterCounts = {
|
||||
',': firstLine.replace(/[^,]/g, '').length,
|
||||
';': firstLine.replace(/[^;]/g, '').length,
|
||||
'|': firstLine.replace(/[^|]/g, '').length,
|
||||
};
|
||||
|
||||
delimiter = Object.keys(delimiterCounts).reduce((a, b) => (delimiterCounts[a] > delimiterCounts[b] ? a : b), ',');
|
||||
}
|
||||
}
|
||||
const csvStream = csv.parse({
|
||||
// @ts-ignore
|
||||
delimiter,
|
||||
skip_lines_with_error: true,
|
||||
to_line: limitRows ? limitRows + 1 : undefined,
|
||||
ltrim: true,
|
||||
});
|
||||
const downloadedFile = await dbgateApi.download(fileName);
|
||||
const fileStream = fs.createReadStream(downloadedFile, encoding);
|
||||
const csvPrepare = new CsvPrepareStream({ header });
|
||||
fileStream.pipe(csvStream);
|
||||
|
||||
@@ -17,6 +17,7 @@ const fileFormat = {
|
||||
name: 'delimiter',
|
||||
label: 'Delimiter',
|
||||
options: [
|
||||
{ name: 'Auto-detect', value: '' },
|
||||
{ name: 'Comma (,)', value: ',' },
|
||||
{ name: 'Semicolon (;)', value: ';' },
|
||||
{ name: 'Tab', value: '\t' },
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const { DatabaseAnalyser } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
|
||||
class Analyser extends DatabaseAnalyser {
|
||||
constructor(pool, driver, version) {
|
||||
super(pool, driver, version);
|
||||
constructor(dbhan, driver, version) {
|
||||
super(dbhan, driver, version);
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
const collectionsAndViews = await this.pool.__getDatabase().listCollections().toArray();
|
||||
const collectionsAndViews = await this.dbhan.getDatabase().listCollections().toArray();
|
||||
const collections = collectionsAndViews.filter((x) => x.type == 'collection');
|
||||
const views = collectionsAndViews.filter((x) => x.type == 'view');
|
||||
|
||||
@@ -16,8 +16,8 @@ class Analyser extends DatabaseAnalyser {
|
||||
collections
|
||||
.filter((x) => x.type == 'collection')
|
||||
.map((x) =>
|
||||
this.pool
|
||||
.__getDatabase()
|
||||
this.dbhan
|
||||
.getDatabase()
|
||||
.collection(x.name)
|
||||
.aggregate([{ $collStats: { count: {} } }])
|
||||
.toArray()
|
||||
|
||||
@@ -5,9 +5,9 @@ const { EJSON } = require('bson');
|
||||
const logger = getLogger('mongoBulkInsert');
|
||||
|
||||
|
||||
function createBulkInsertStream(driver, stream, pool, name, options) {
|
||||
function createBulkInsertStream(driver, stream, dbhan, name, options) {
|
||||
const collectionName = name.pureName;
|
||||
const db = pool.__getDatabase();
|
||||
const db = dbhan.getDatabase();
|
||||
|
||||
const writable = new stream.Writable({
|
||||
objectMode: true,
|
||||
|
||||
@@ -34,8 +34,8 @@ function findArrayResult(resValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getScriptableDb(pool) {
|
||||
const db = pool.__getDatabase();
|
||||
async function getScriptableDb(dbhan) {
|
||||
const db = dbhan.getDatabase();
|
||||
const collections = await db.listCollections().toArray();
|
||||
for (const collection of collections) {
|
||||
_.set(db, collection.name, db.collection(collection.name));
|
||||
@@ -77,42 +77,43 @@ const driver = {
|
||||
options.tlsInsecure = !ssl.rejectUnauthorized;
|
||||
}
|
||||
|
||||
const pool = new MongoClient(mongoUrl, options);
|
||||
await pool.connect();
|
||||
// const pool = await MongoClient.connect(mongoUrl);
|
||||
pool.__getDatabase = database ? () => pool.db(database) : () => pool.db();
|
||||
pool.__databaseName = database;
|
||||
return pool;
|
||||
const client = new MongoClient(mongoUrl, options);
|
||||
await client.connect();
|
||||
return {
|
||||
client,
|
||||
database,
|
||||
getDatabase: database ? () => client.db(database) : () => client.db(),
|
||||
};
|
||||
},
|
||||
// @ts-ignore
|
||||
async query(pool, sql) {
|
||||
async query(dbhan, sql) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
},
|
||||
async script(pool, sql) {
|
||||
async script(dbhan, sql) {
|
||||
let func;
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
const db = await getScriptableDb(pool);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
const res = func(db, ObjectId.createFromHexString);
|
||||
if (isPromise(res)) await res;
|
||||
},
|
||||
async operation(pool, operation, options) {
|
||||
async operation(dbhan, operation, options) {
|
||||
const { type } = operation;
|
||||
switch (type) {
|
||||
case 'createCollection':
|
||||
await this.script(pool, `db.createCollection('${operation.collection.name}')`);
|
||||
await this.script(dbhan, `db.createCollection('${operation.collection.name}')`);
|
||||
break;
|
||||
case 'dropCollection':
|
||||
await this.script(pool, `db.dropCollection('${operation.collection}')`);
|
||||
await this.script(dbhan, `db.dropCollection('${operation.collection}')`);
|
||||
break;
|
||||
case 'renameCollection':
|
||||
await this.script(pool, `db.renameCollection('${operation.collection}', '${operation.newName}')`);
|
||||
await this.script(dbhan, `db.renameCollection('${operation.collection}', '${operation.newName}')`);
|
||||
break;
|
||||
case 'cloneCollection':
|
||||
await this.script(
|
||||
pool,
|
||||
dbhan,
|
||||
`db.collection('${operation.collection}').aggregate([{$out: '${operation.newName}'}]).toArray()`
|
||||
);
|
||||
break;
|
||||
@@ -121,7 +122,7 @@ const driver = {
|
||||
}
|
||||
// saveScriptToDatabase({ conid: connection._id, database: name }, `db.createCollection('${newCollection}')`);
|
||||
},
|
||||
async stream(pool, sql, options) {
|
||||
async stream(dbhan, sql, options) {
|
||||
let func;
|
||||
try {
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
@@ -134,7 +135,7 @@ const driver = {
|
||||
options.done();
|
||||
return;
|
||||
}
|
||||
const db = await getScriptableDb(pool);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
|
||||
let exprValue;
|
||||
try {
|
||||
@@ -192,8 +193,8 @@ const driver = {
|
||||
|
||||
options.done();
|
||||
},
|
||||
async startProfiler(pool, options) {
|
||||
const db = await getScriptableDb(pool);
|
||||
async startProfiler(dbhan, options) {
|
||||
const db = await getScriptableDb(dbhan);
|
||||
const old = await db.command({ profile: -1 });
|
||||
await db.command({ profile: 2 });
|
||||
const cursor = await db.collection('system.profile').find({
|
||||
@@ -230,12 +231,12 @@ const driver = {
|
||||
old,
|
||||
};
|
||||
},
|
||||
async stopProfiler(pool, { cursor, old }) {
|
||||
async stopProfiler(dbhan, { cursor, old }) {
|
||||
cursor.close();
|
||||
const db = await getScriptableDb(pool);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
await db.command({ profile: old.was, slowms: old.slowms });
|
||||
},
|
||||
async readQuery(pool, sql, structure) {
|
||||
async readQuery(dbhan, sql, structure) {
|
||||
try {
|
||||
const json = JSON.parse(sql);
|
||||
if (json && json.pureName) {
|
||||
@@ -251,7 +252,7 @@ const driver = {
|
||||
// });
|
||||
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
const db = await getScriptableDb(pool);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
exprValue = func(db, ObjectId.createFromHexString);
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
@@ -278,27 +279,27 @@ const driver = {
|
||||
|
||||
// return pass;
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
return createBulkInsertStream(this, stream, pool, name, options);
|
||||
async writeTable(dbhan, name, options) {
|
||||
return createBulkInsertStream(this, stream, dbhan, name, options);
|
||||
},
|
||||
async getVersion(pool) {
|
||||
const status = await pool.__getDatabase().admin().serverInfo();
|
||||
async getVersion(dbhan) {
|
||||
const status = await dbhan.getDatabase().admin().serverInfo();
|
||||
return {
|
||||
...status,
|
||||
versionText: `MongoDB ${status.version}`,
|
||||
};
|
||||
},
|
||||
async listDatabases(pool) {
|
||||
const res = await pool.__getDatabase().admin().listDatabases();
|
||||
async listDatabases(dbhan) {
|
||||
const res = await dbhan.getDatabase().admin().listDatabases();
|
||||
return res.databases;
|
||||
},
|
||||
async readCollection(pool, options) {
|
||||
async readCollection(dbhan, options) {
|
||||
try {
|
||||
const mongoCondition = convertToMongoCondition(options.condition);
|
||||
// console.log('******************* mongoCondition *****************');
|
||||
// console.log(JSON.stringify(mongoCondition, undefined, 2));
|
||||
|
||||
const collection = pool.__getDatabase().collection(options.pureName);
|
||||
const collection = dbhan.getDatabase().collection(options.pureName);
|
||||
if (options.countDocuments) {
|
||||
const count = await collection.countDocuments(convertObjectId(mongoCondition) || {});
|
||||
return { count };
|
||||
@@ -326,7 +327,7 @@ const driver = {
|
||||
return { errorMessage: err.message };
|
||||
}
|
||||
},
|
||||
async updateCollection(pool, changeSet) {
|
||||
async updateCollection(dbhan, changeSet) {
|
||||
const res = {
|
||||
inserted: [],
|
||||
updated: [],
|
||||
@@ -334,7 +335,7 @@ const driver = {
|
||||
replaced: [],
|
||||
};
|
||||
try {
|
||||
const db = pool.__getDatabase();
|
||||
const db = dbhan.getDatabase();
|
||||
for (const insert of changeSet.inserts) {
|
||||
const collection = db.collection(insert.pureName);
|
||||
const document = {
|
||||
@@ -384,19 +385,19 @@ const driver = {
|
||||
}
|
||||
},
|
||||
|
||||
async createDatabase(pool, name) {
|
||||
const db = pool.db(name);
|
||||
async createDatabase(dbhan, name) {
|
||||
const db = dbhan.client.db(name);
|
||||
await db.createCollection('collection1');
|
||||
},
|
||||
|
||||
async dropDatabase(pool, name) {
|
||||
const db = pool.db(name);
|
||||
async dropDatabase(dbhan, name) {
|
||||
const db = dbhan.client.db(name);
|
||||
await db.dropDatabase();
|
||||
},
|
||||
|
||||
async loadFieldValues(pool, name, field, search) {
|
||||
async loadFieldValues(dbhan, name, field, search) {
|
||||
try {
|
||||
const collection = pool.__getDatabase().collection(name.pureName);
|
||||
const collection = dbhan.getDatabase().collection(name.pureName);
|
||||
// console.log('options.condition', JSON.stringify(options.condition, undefined, 2));
|
||||
|
||||
const pipelineMatch = [];
|
||||
@@ -442,10 +443,10 @@ const driver = {
|
||||
}
|
||||
},
|
||||
|
||||
readJsonQuery(pool, select, structure) {
|
||||
readJsonQuery(dbhan, select, structure) {
|
||||
const { collection, condition, sort } = select;
|
||||
|
||||
const db = pool.__getDatabase();
|
||||
const db = dbhan.getDatabase();
|
||||
const res = db
|
||||
.collection(collection)
|
||||
.find(condition || {})
|
||||
@@ -455,23 +456,23 @@ const driver = {
|
||||
return res;
|
||||
},
|
||||
|
||||
async summaryCommand(pool, command, row) {
|
||||
async summaryCommand(dbhan, command, row) {
|
||||
switch (command) {
|
||||
case 'profileOff':
|
||||
await pool.db(row.name).command({ profile: 0 });
|
||||
await dbhan.client.db(row.name).command({ profile: 0 });
|
||||
return;
|
||||
case 'profileFiltered':
|
||||
await pool.db(row.name).command({ profile: 1, slowms: 100 });
|
||||
await dbhan.client.db(row.name).command({ profile: 1, slowms: 100 });
|
||||
return;
|
||||
case 'profileAll':
|
||||
await pool.db(row.name).command({ profile: 2 });
|
||||
await dbhan.client.db(row.name).command({ profile: 2 });
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
async serverSummary(pool) {
|
||||
const res = await pool.__getDatabase().admin().listDatabases();
|
||||
const profiling = await Promise.all(res.databases.map((x) => pool.db(x.name).command({ profile: -1 })));
|
||||
async serverSummary(dbhan) {
|
||||
const res = await dbhan.getDatabase().admin().listDatabases();
|
||||
const profiling = await Promise.all(res.databases.map((x) => dbhan.client.db(x.name).command({ profile: -1 })));
|
||||
|
||||
function formatProfiling(info) {
|
||||
switch (info.was) {
|
||||
|
||||
@@ -63,8 +63,8 @@ function getColumnInfo({
|
||||
}
|
||||
|
||||
class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
constructor(pool, driver, version) {
|
||||
super(pool, driver, version);
|
||||
constructor(dbhan, driver, version) {
|
||||
super(dbhan, driver, version);
|
||||
}
|
||||
|
||||
createQuery(resFileName, typeFields) {
|
||||
@@ -75,7 +75,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
async _computeSingleObjectId() {
|
||||
const { schemaName, pureName, typeField } = this.singleObjectFilter;
|
||||
const fullName = schemaName ? `[${schemaName}].[${pureName}]` : pureName;
|
||||
const resId = await this.driver.query(this.pool, `SELECT OBJECT_ID('${fullName}') AS id`);
|
||||
const resId = await this.driver.query(this.dbhan, `SELECT OBJECT_ID('${fullName}') AS id`);
|
||||
this.singleObjectId = resId.rows[0].id;
|
||||
}
|
||||
|
||||
@@ -88,28 +88,17 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
const pkColumnsRows = await this.analyserQuery('primaryKeys', ['tables']);
|
||||
this.feedback({ analysingMessage: 'Loading foreign keys' });
|
||||
const fkColumnsRows = await this.analyserQuery('foreignKeys', ['tables']);
|
||||
this.feedback({ analysingMessage: 'Loading schemas' });
|
||||
const schemaRows = await this.analyserQuery('getSchemas');
|
||||
this.feedback({ analysingMessage: 'Loading indexes' });
|
||||
const indexesRows = await this.analyserQuery('indexes', ['tables']);
|
||||
this.feedback({ analysingMessage: 'Loading index columns' });
|
||||
const indexcolsRows = await this.analyserQuery('indexcols', ['tables']);
|
||||
this.feedback({ analysingMessage: 'Loading default schema' });
|
||||
const defaultSchemaRows = await this.driver.query(this.pool, 'SELECT SCHEMA_NAME() as name');
|
||||
this.feedback({ analysingMessage: 'Loading table sizes' });
|
||||
const tableSizes = await this.analyserQuery('tableSizes');
|
||||
|
||||
const schemas = schemaRows.rows;
|
||||
|
||||
const tableSizesDict = _.mapValues(_.keyBy(tableSizes.rows, 'objectId'), 'tableRowCount');
|
||||
|
||||
this.feedback({ analysingMessage: 'Loading SQL code' });
|
||||
const sqlCodeRows = await this.analyserQuery('loadSqlCode', [
|
||||
'views',
|
||||
'procedures',
|
||||
'functions',
|
||||
'triggers',
|
||||
]);
|
||||
const sqlCodeRows = await this.analyserQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers']);
|
||||
const getCreateSql = row =>
|
||||
sqlCodeRows.rows
|
||||
.filter(x => x.pureName == row.pureName && x.schemaName == row.schemaName)
|
||||
@@ -182,8 +171,6 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
views,
|
||||
procedures,
|
||||
functions,
|
||||
schemas,
|
||||
defaultSchema: defaultSchemaRows.rows[0] ? defaultSchemaRows.rows[0].name : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const { createBulkInsertStreamBase } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
|
||||
function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
function runBulkInsertBatch(dbhan, tableName, writable, rows) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tableMgr = pool.tableMgr();
|
||||
const tableMgr = dbhan.client.tableMgr();
|
||||
tableMgr.bind(tableName, bulkMgr => {
|
||||
bulkMgr.insertRows(rows, err => {
|
||||
if (err) reject(err);
|
||||
@@ -16,8 +16,8 @@ function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createNativeBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, pool, name, options);
|
||||
function createNativeBulkInsertStream(driver, stream, dbhan, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options);
|
||||
|
||||
const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : name.pureName;
|
||||
|
||||
@@ -25,7 +25,7 @@ function createNativeBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
|
||||
await runBulkInsertBatch(pool, fullName, writable, rows);
|
||||
await runBulkInsertBatch(dbhan, fullName, writable, rows);
|
||||
};
|
||||
|
||||
return writable;
|
||||
|
||||
@@ -3,12 +3,12 @@ const tedious = require('tedious');
|
||||
const getConcreteType = require('./getConcreteType');
|
||||
const _ = require('lodash');
|
||||
|
||||
function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
function runBulkInsertBatch(dbhan, tableName, writable, rows) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var options = { keepNulls: true };
|
||||
|
||||
// instantiate - provide the table where you'll be inserting to, options and a callback
|
||||
var bulkLoad = pool.newBulkLoad(tableName, options, (error, rowCount) => {
|
||||
var bulkLoad = dbhan.client.newBulkLoad(tableName, options, (error, rowCount) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
@@ -40,7 +40,7 @@ function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
);
|
||||
// console.log('IMPORT ROWS', rowsMapped);
|
||||
|
||||
pool.execBulkLoad(bulkLoad, rowsMapped);
|
||||
dbhan.client.execBulkLoad(bulkLoad, rowsMapped);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ function runBulkInsertBatch(pool, tableName, writable, rows) {
|
||||
*
|
||||
* @param {import('dbgate-types').EngineDriver} driver
|
||||
*/
|
||||
function createTediousBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, pool, name, options);
|
||||
function createTediousBulkInsertStream(driver, stream, dbhan, name, options) {
|
||||
const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options);
|
||||
|
||||
const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : name.pureName;
|
||||
|
||||
@@ -59,7 +59,7 @@ function createTediousBulkInsertStream(driver, stream, pool, name, options) {
|
||||
? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}`
|
||||
: driver.dialect.quoteIdentifier(name.pureName);
|
||||
|
||||
const respTemplate = await driver.query(pool, `SELECT * FROM ${fullNameQuoted} WHERE 1=0`, {
|
||||
const respTemplate = await driver.query(dbhan, `SELECT * FROM ${fullNameQuoted} WHERE 1=0`, {
|
||||
addDriverNativeColumn: true,
|
||||
});
|
||||
writable.templateColumns = respTemplate.columns;
|
||||
@@ -68,7 +68,7 @@ function createTediousBulkInsertStream(driver, stream, pool, name, options) {
|
||||
const rows = writable.buffer;
|
||||
writable.buffer = [];
|
||||
|
||||
await runBulkInsertBatch(pool, fullName, writable, rows);
|
||||
await runBulkInsertBatch(dbhan, fullName, writable, rows);
|
||||
};
|
||||
|
||||
return writable;
|
||||
|
||||
@@ -9,6 +9,9 @@ const nativeDriver = require('./nativeDriver');
|
||||
const lock = new AsyncLock();
|
||||
const { tediousConnect, tediousQueryCore, tediousReadQuery, tediousStream } = require('./tediousDriver');
|
||||
const { nativeConnect, nativeQueryCore, nativeReadQuery, nativeStream } = nativeDriver;
|
||||
const { getLogger } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
|
||||
const logger = getLogger('mssqlDriver');
|
||||
|
||||
let requireMsnodesqlv8;
|
||||
let platformInfo;
|
||||
@@ -79,50 +82,53 @@ const driver = {
|
||||
|
||||
async connect(conn) {
|
||||
const { authType } = conn;
|
||||
if (requireMsnodesqlv8 && (authType == 'sspi' || authType == 'sql')) {
|
||||
return nativeConnect(conn);
|
||||
}
|
||||
const connectionType = requireMsnodesqlv8 && (authType == 'sspi' || authType == 'sql') ? 'msnodesqlv8' : 'tedious';
|
||||
const client = connectionType == 'msnodesqlv8' ? await nativeConnect(conn) : await tediousConnect(conn);
|
||||
|
||||
return tediousConnect(conn);
|
||||
return {
|
||||
client,
|
||||
connectionType,
|
||||
database: conn.database,
|
||||
};
|
||||
},
|
||||
async close(pool) {
|
||||
return pool.close();
|
||||
async close(dbhan) {
|
||||
return dbhan.client.close();
|
||||
},
|
||||
async queryCore(pool, sql, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeQueryCore(pool, sql, options);
|
||||
async queryCore(dbhan, sql, options) {
|
||||
if (dbhan.connectionType == 'msnodesqlv8') {
|
||||
return nativeQueryCore(dbhan, sql, options);
|
||||
} else {
|
||||
return tediousQueryCore(pool, sql, options);
|
||||
return tediousQueryCore(dbhan, sql, options);
|
||||
}
|
||||
},
|
||||
async query(pool, sql, options) {
|
||||
async query(dbhan, sql, options) {
|
||||
return lock.acquire('connection', async () => {
|
||||
return this.queryCore(pool, sql, options);
|
||||
return this.queryCore(dbhan, sql, options);
|
||||
});
|
||||
},
|
||||
async stream(pool, sql, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeStream(pool, sql, options);
|
||||
async stream(dbhan, sql, options) {
|
||||
if (dbhan.connectionType == 'msnodesqlv8') {
|
||||
return nativeStream(dbhan, sql, options);
|
||||
} else {
|
||||
return tediousStream(pool, sql, options);
|
||||
return tediousStream(dbhan, sql, options);
|
||||
}
|
||||
},
|
||||
async readQuery(pool, sql, structure) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeReadQuery(pool, sql, structure);
|
||||
async readQuery(dbhan, sql, structure) {
|
||||
if (dbhan.connectionType == 'msnodesqlv8') {
|
||||
return nativeReadQuery(dbhan, sql, structure);
|
||||
} else {
|
||||
return tediousReadQuery(pool, sql, structure);
|
||||
return tediousReadQuery(dbhan, sql, structure);
|
||||
}
|
||||
},
|
||||
async writeTable(pool, name, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return createNativeBulkInsertStream(this, stream, pool, name, options);
|
||||
async writeTable(dbhan, name, options) {
|
||||
if (dbhan.connectionType == 'msnodesqlv8') {
|
||||
return createNativeBulkInsertStream(this, stream, dbhan, name, options);
|
||||
} else {
|
||||
return createTediousBulkInsertStream(this, stream, pool, name, options);
|
||||
return createTediousBulkInsertStream(this, stream, dbhan, name, options);
|
||||
}
|
||||
},
|
||||
async getVersion(pool) {
|
||||
const res = (await this.query(pool, versionQuery)).rows[0];
|
||||
async getVersion(dbhan) {
|
||||
const res = (await this.query(dbhan, versionQuery)).rows[0];
|
||||
|
||||
if (res.productVersion) {
|
||||
const splitted = res.productVersion.split('.');
|
||||
@@ -133,8 +139,8 @@ const driver = {
|
||||
}
|
||||
return res;
|
||||
},
|
||||
async listDatabases(pool) {
|
||||
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
||||
async listDatabases(dbhan) {
|
||||
const { rows } = await this.query(dbhan, 'SELECT name FROM sys.databases order by name');
|
||||
return rows;
|
||||
},
|
||||
getRedirectAuthUrl(connection, options) {
|
||||
@@ -150,6 +156,19 @@ const driver = {
|
||||
getAccessTokenFromAuth: (connection, req) => {
|
||||
return req?.user?.msentraToken;
|
||||
},
|
||||
async listSchemas(dbhan) {
|
||||
const { rows } = await this.query(dbhan, 'select schema_id as objectId, name as schemaName from sys.schemas');
|
||||
|
||||
const defaultSchemaRows = await this.query(dbhan, 'SELECT SCHEMA_NAME() as name');
|
||||
const defaultSchema = defaultSchemaRows.rows[0]?.name;
|
||||
|
||||
logger.debug(`Loaded ${rows.length} mssql schemas`);
|
||||
|
||||
return rows.map(x => ({
|
||||
...x,
|
||||
isDefault: x.schemaName == defaultSchema,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
driver.initialize = dbgateEnv => {
|
||||
|
||||
@@ -2,6 +2,7 @@ const _ = require('lodash');
|
||||
const stream = require('stream');
|
||||
const makeUniqueColumnNames = require('./makeUniqueColumnNames');
|
||||
let requireMsnodesqlv8;
|
||||
const { extractDbNameFromComposite } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
|
||||
// async function nativeQueryCore(pool, sql, options) {
|
||||
// if (sql == null) {
|
||||
@@ -57,13 +58,12 @@ async function connectWithDriver({ server, port, user, password, database, authT
|
||||
connectionString += `;Driver={${driver}}`;
|
||||
if (authType == 'sspi') connectionString += ';Trusted_Connection=Yes';
|
||||
else connectionString += `;UID=${user};PWD=${password}`;
|
||||
if (database) connectionString += `;Database=${database}`;
|
||||
if (database) connectionString += `;Database=${extractDbNameFromComposite(database)}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
getMsnodesqlv8().open(connectionString, (err, conn) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
conn._connectionType = 'msnodesqlv8';
|
||||
resolve(conn);
|
||||
}
|
||||
});
|
||||
@@ -88,7 +88,7 @@ async function nativeConnect(connection) {
|
||||
}
|
||||
}
|
||||
|
||||
async function nativeQueryCore(pool, sql, options) {
|
||||
async function nativeQueryCore(dbhan, sql, options) {
|
||||
if (sql == null) {
|
||||
return Promise.resolve({
|
||||
rows: [],
|
||||
@@ -98,7 +98,7 @@ async function nativeQueryCore(pool, sql, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
const q = dbhan.client.query(sql);
|
||||
const rows = [];
|
||||
|
||||
q.on('meta', meta => {
|
||||
@@ -128,7 +128,7 @@ async function nativeQueryCore(pool, sql, options) {
|
||||
});
|
||||
}
|
||||
|
||||
async function nativeReadQuery(pool, sql, structure) {
|
||||
async function nativeReadQuery(dbhan, sql, structure) {
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
highWaterMark: 100,
|
||||
@@ -136,7 +136,7 @@ async function nativeReadQuery(pool, sql, structure) {
|
||||
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
const q = dbhan.client.query(sql);
|
||||
|
||||
q.on('meta', meta => {
|
||||
columns = extractNativeColumns(meta);
|
||||
@@ -168,7 +168,7 @@ async function nativeReadQuery(pool, sql, structure) {
|
||||
return pass;
|
||||
}
|
||||
|
||||
async function nativeStream(pool, sql, options) {
|
||||
async function nativeStream(dbhan, sql, options) {
|
||||
const handleInfo = info => {
|
||||
const { message, lineNumber, procName } = info;
|
||||
options.info({
|
||||
@@ -192,7 +192,7 @@ async function nativeStream(pool, sql, options) {
|
||||
|
||||
let columns = null;
|
||||
let currentRow = null;
|
||||
const q = pool.query(sql);
|
||||
const q = dbhan.client.query(sql);
|
||||
|
||||
q.on('meta', meta => {
|
||||
if (currentRow) options.row(currentRow);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user