Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e4eb39a19 | |||
| b6399c8271 | |||
| 27188eb2c5 | |||
| 57a997adc3 | |||
| a72a03cc3a | |||
| bb185d9e9f | |||
| 0298660714 | |||
| 1a4009a6b2 | |||
| e40357c052 | |||
| 222ea07cf2 | |||
| 81cea4c0f2 | |||
| 21702f1593 | |||
| 32ddb9c4c7 | |||
| 10fc62ceb7 | |||
| d3018a3136 | |||
| 5c7d2bfd85 | |||
| 0eca5dd95d | |||
| 352e426e17 | |||
| 666122f265 | |||
| a66dc03b99 | |||
| cf5ecb3150 | |||
| 46c365c5cd | |||
| ea76751e4a | |||
| 0bef3f8e71 | |||
| c26c9fae12 | |||
| 446c615bb8 | |||
| c516873541 | |||
| d4326de087 | |||
| eca966bb90 | |||
| 262b4732e3 | |||
| 7f9a30f568 | |||
| a89d2e1365 | |||
| fcd6f6c8fc | |||
| 8313d7f9f1 | |||
| 3a12601103 | |||
| 926949dc89 | |||
| 6b155083ef | |||
| 2b2ecac3ab | |||
| 35e9ff607d | |||
| ae037834f2 | |||
| 3ac24436ba | |||
| 2ca17e826c | |||
| 4fb6128499 | |||
| c359332746 | |||
| 1cd8e8e376 | |||
| 48ec2bdac8 | |||
| 2283e91532 | |||
| 647894ad60 | |||
| 574573abbb | |||
| a735a03cd7 | |||
| 83881a0dac | |||
| c04c6bbd2c | |||
| 42bbbc7ff4 | |||
| 1ecffeda71 | |||
| 92992d1e95 | |||
| bc9df9750f | |||
| 27e70e8031 | |||
| c823b8d19a | |||
| 170ff77eec | |||
| c241f5c562 | |||
| e06d964de4 | |||
| dfdb86de6f | |||
| a37b74f693 | |||
| 398d9f15df | |||
| ab7c2d7a31 | |||
| 090549ff91 | |||
| 10330c6597 | |||
| da2fe6a891 | |||
| 01b88221c5 | |||
| 46d25710b8 | |||
| a40ec7e66b | |||
| c8d2031d24 | |||
| 4f838e0ae3 | |||
| c7cc1b7611 | |||
| bf67a5f13d | |||
| e6bbe66873 | |||
| 1a930acf0a | |||
| a497467137 | |||
| b463416633 | |||
| ccf6240d65 | |||
| 5ccc12019d | |||
| f57fa9aee9 | |||
| 80e841a43d | |||
| fd6df055c0 | |||
| 669d0b9dac | |||
| b9f9501e67 | |||
| 4b1c021871 | |||
| 1f79627dbe | |||
| dc18be07ce | |||
| c6cd865663 | |||
| 86186072ed | |||
| 1216bcf9bf | |||
| 788ea70d32 | |||
| 18de37c4e4 | |||
| aeb81bd97f | |||
| a68660f1ab | |||
| 5abfa85a0e | |||
| 794f43d9ae | |||
| 5db8f11fd6 | |||
| 598674a7e0 | |||
| af17eceb27 | |||
| 90946c582d | |||
| d619e0f961 | |||
| 2c0b76fb3f |
@@ -0,0 +1,33 @@
|
||||
const directory = process.argv[2];
|
||||
const fs = require('fs');
|
||||
|
||||
const volatilePackages = require('./volatilePackages');
|
||||
const apiPackageJson = JSON.parse(fs.readFileSync(`packages/api/package.json`, { encoding: 'utf-8' }));
|
||||
|
||||
const dependencies = {};
|
||||
const optionalDependencies = {};
|
||||
for (const pkg of volatilePackages) {
|
||||
if (pkg == 'msnodesqlv8' && process.platform != 'win32') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (apiPackageJson.dependencies[pkg]) {
|
||||
dependencies[pkg] = apiPackageJson.dependencies[pkg];
|
||||
}
|
||||
if (apiPackageJson.optionalDependencies[pkg]) {
|
||||
optionalDependencies[pkg] = apiPackageJson.optionalDependencies[pkg];
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
`${directory}/package.json`,
|
||||
JSON.stringify(
|
||||
{
|
||||
dependencies,
|
||||
optionalDependencies,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
'utf-8'
|
||||
);
|
||||
@@ -10,6 +10,14 @@ function flatSource() {
|
||||
);
|
||||
}
|
||||
|
||||
function flatSourceParameters() {
|
||||
return _.flatten(
|
||||
engines.map(engine =>
|
||||
(engine.parameters || []).map(parameter => [engine.label, parameter.testName, parameter, engine])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const obj1Match = expect.objectContaining({
|
||||
pureName: 'obj1',
|
||||
});
|
||||
@@ -78,7 +86,7 @@ describe('Object analyse', () => {
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
expect(structure2[type].length).toEqual(0);
|
||||
|
||||
await driver.query(conn, structure1[type][0].createSql, { discardResult: true });
|
||||
await driver.script(conn, structure1[type][0].createSql);
|
||||
|
||||
const structure3 = await driver.analyseIncremental(conn, structure2);
|
||||
|
||||
@@ -86,4 +94,45 @@ describe('Object analyse', () => {
|
||||
expect(structure3[type][0]).toEqual(type.includes('views') ? view1Match : obj1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(flatSourceParameters())(
|
||||
'Test parameters simple analyse - %s - %s',
|
||||
testWrapper(async (conn, driver, testName, parameter, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
|
||||
|
||||
await driver.query(conn, parameter.create, { discardResult: true });
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
|
||||
|
||||
expect(parameters.length).toEqual(parameter.list.length);
|
||||
for (let i = 0; i < parameters.length; i += 1) {
|
||||
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
test.each(flatSourceParameters())(
|
||||
'Test parameters create SQL - %s - %s',
|
||||
testWrapper(async (conn, driver, testName, parameter, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
|
||||
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
|
||||
|
||||
await driver.query(conn, parameter.create, { discardResult: true });
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
await driver.query(conn, parameter.drop, { discardResult: true });
|
||||
|
||||
const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1');
|
||||
await driver.script(conn, obj.createSql);
|
||||
|
||||
const structure2 = await driver.analyseFull(conn);
|
||||
const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
|
||||
|
||||
expect(parameters.length).toEqual(parameter.list.length);
|
||||
for (let i = 0; i < parameters.length; i += 1) {
|
||||
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,16 @@ const engines = [
|
||||
port: 15001,
|
||||
},
|
||||
// skipOnCI: true,
|
||||
objects: [views],
|
||||
objects: [
|
||||
views,
|
||||
{
|
||||
type: 'procedures',
|
||||
create1: 'CREATE PROCEDURE obj1() BEGIN SELECT * FROM t1; END',
|
||||
create2: 'CREATE PROCEDURE obj2() BEGIN SELECT * FROM t2; END',
|
||||
drop1: 'DROP PROCEDURE obj1',
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
dbSnapshotBySeconds: true,
|
||||
dumpFile: 'data/chinook-mysql.sql',
|
||||
dumpChecks: [
|
||||
@@ -37,6 +46,68 @@ const engines = [
|
||||
res: '25',
|
||||
},
|
||||
],
|
||||
parametersOtherSql: ['CREATE PROCEDURE obj2(a int, b int) BEGIN SELECT * FROM t1; END'],
|
||||
parameters: [
|
||||
{
|
||||
testName: 'simple',
|
||||
create: 'CREATE PROCEDURE obj1(a int) BEGIN SELECT * FROM t1; END',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'int',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'paramTypes',
|
||||
create: 'CREATE PROCEDURE obj1(a int, b varchar(50), c numeric(10,2)) BEGIN SELECT * FROM t1; END',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'int',
|
||||
},
|
||||
{
|
||||
parameterName: 'b',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'varchar(50)',
|
||||
},
|
||||
{
|
||||
parameterName: 'c',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'decimal(10,2)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'paramModes',
|
||||
create: 'CREATE PROCEDURE obj1(IN a int, OUT b int, INOUT c int) BEGIN SELECT * FROM t1; END',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'int',
|
||||
},
|
||||
{
|
||||
parameterName: 'b',
|
||||
parameterMode: 'OUT',
|
||||
dataType: 'int',
|
||||
},
|
||||
{
|
||||
parameterName: 'c',
|
||||
parameterMode: 'INOUT',
|
||||
dataType: 'int',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'MariaDB',
|
||||
@@ -105,6 +176,94 @@ const engines = [
|
||||
res: '25',
|
||||
},
|
||||
],
|
||||
|
||||
parametersOtherSql: ['CREATE PROCEDURE obj2(a integer, b integer) LANGUAGE SQL AS $$ select * from t1 $$'],
|
||||
parameters: [
|
||||
{
|
||||
testName: 'simple',
|
||||
create: 'CREATE PROCEDURE obj1(a integer) LANGUAGE SQL AS $$ select * from t1 $$',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'integer',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'dataTypes',
|
||||
create:
|
||||
'CREATE PROCEDURE obj1(a integer, b varchar(20), c numeric(18,2)) LANGUAGE SQL AS $$ select * from t1 $$',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'integer',
|
||||
},
|
||||
{
|
||||
parameterName: 'b',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'varchar',
|
||||
},
|
||||
{
|
||||
parameterName: 'c',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'numeric',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'paramModes',
|
||||
create: 'CREATE PROCEDURE obj1(IN a integer, INOUT b integer) LANGUAGE SQL AS $$ select * from t1 $$',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'a',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'integer',
|
||||
},
|
||||
{
|
||||
parameterName: 'b',
|
||||
parameterMode: 'INOUT',
|
||||
dataType: 'integer',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'paramModesFunction',
|
||||
objectTypeField: 'functions',
|
||||
create: `
|
||||
create or replace function obj1(
|
||||
out min_len int,
|
||||
out max_len int)
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
select min(id),
|
||||
max(id)
|
||||
into min_len, max_len
|
||||
from t1;
|
||||
end;$$`,
|
||||
drop: 'DROP FUNCTION obj1',
|
||||
list: [
|
||||
{
|
||||
parameterName: 'min_len',
|
||||
parameterMode: 'OUT',
|
||||
dataType: 'integer',
|
||||
},
|
||||
{
|
||||
parameterName: 'max_len',
|
||||
parameterMode: 'OUT',
|
||||
dataType: 'integer',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SQL Server',
|
||||
@@ -129,6 +288,63 @@ const engines = [
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
parametersOtherSql: ['CREATE PROCEDURE obj2 (@p1 int, @p2 int) AS SELECT id from t1'],
|
||||
parameters: [
|
||||
{
|
||||
testName: 'simple',
|
||||
create: 'CREATE PROCEDURE obj1 (@param1 int) AS SELECT id from t1',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: '@param1',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'int',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'dataTypes',
|
||||
create: 'CREATE PROCEDURE obj1 (@p1 bit, @p2 nvarchar(20), @p3 decimal(18,2), @p4 float) AS SELECT id from t1',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: '@p1',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'bit',
|
||||
},
|
||||
{
|
||||
parameterName: '@p2',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'nvarchar(20)',
|
||||
},
|
||||
{
|
||||
parameterName: '@p3',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'decimal(18,2)',
|
||||
},
|
||||
{
|
||||
parameterName: '@p4',
|
||||
parameterMode: 'IN',
|
||||
dataType: 'float',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'outputParam',
|
||||
create: 'CREATE PROCEDURE obj1 (@p1 int OUTPUT) AS SELECT id from t1',
|
||||
drop: 'DROP PROCEDURE obj1',
|
||||
objectTypeField: 'procedures',
|
||||
list: [
|
||||
{
|
||||
parameterName: '@p1',
|
||||
parameterMode: 'OUT',
|
||||
dataType: 'int',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
supportSchemas: true,
|
||||
supportRenameSqlObject: true,
|
||||
defaultSchemaName: 'dbo',
|
||||
@@ -188,10 +404,10 @@ const engines = [
|
||||
|
||||
const filterLocal = [
|
||||
// filter local testing
|
||||
'-MySQL',
|
||||
'MySQL',
|
||||
'-MariaDB',
|
||||
'-PostgreSQL',
|
||||
'SQL Server',
|
||||
'-SQL Server',
|
||||
'-SQLite',
|
||||
'-CockroachDB',
|
||||
'-ClickHouse',
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.5.7-alpha.60",
|
||||
"version": "5.5.7-beta.66",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -53,7 +53,7 @@
|
||||
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||
"copy:packer:build": "copyfiles packages/api/dist/* packer/build -f && copyfiles packages/web/public/* packer/build -u 2 && copyfiles \"packages/web/public/**/*\" packer/build -u 2 && copyfiles \"plugins/dist/**/*\" packer/build/plugins -u 2 && copyfiles packer/install-packages.sh packer/build -f",
|
||||
"install:drivers:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && yarn add oracledb && cd ..",
|
||||
"install:drivers:docker": "node common/defineVolatileDependencies.js docker && cd docker && yarn install && cd ..",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
|
||||
"prepare:packer": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:packer:build",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
|
||||
Vendored
+29
-42
@@ -1,60 +1,47 @@
|
||||
DEVMODE=1
|
||||
|
||||
CONNECTIONS=mysql,postgres,postgres1,mongo,mongo2,mysqlssh,sqlite,relational
|
||||
CONNECTIONS=mysql,postgres,mongo,redis,mssql,oracle
|
||||
|
||||
LABEL_mysql=MySql localhost
|
||||
SERVER_mysql=localhost
|
||||
LABEL_mysql=MySql
|
||||
SERVER_mysql=dbgatedckstage1.sprinx.cz
|
||||
USER_mysql=root
|
||||
PASSWORD_mysql=test
|
||||
PORT_mysql=3307
|
||||
PASSWORD_mysql=Pwd2020Db
|
||||
PORT_mysql=3306
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
|
||||
LABEL_postgres=Postgres localhost
|
||||
SERVER_postgres=localhost
|
||||
LABEL_postgres=Postgres
|
||||
SERVER_postgres=dbgatedckstage1.sprinx.cz
|
||||
USER_postgres=postgres
|
||||
PASSWORD_postgres=Pwd2020Db
|
||||
PORT_postgres=5432
|
||||
ENGINE_postgres=postgres@dbgate-plugin-postgres
|
||||
|
||||
LABEL_postgres1=Postgres localhost test DB
|
||||
SERVER_postgres1=localhost
|
||||
USER_postgres1=postgres
|
||||
PASSWORD_postgres1=Pwd2020Db
|
||||
PORT_postgres1=5432
|
||||
ENGINE_postgres1=postgres@dbgate-plugin-postgres
|
||||
DATABASE_postgres1=test
|
||||
|
||||
LABEL_mongo=Mongo URL
|
||||
URL_mongo=mongodb://localhost:27017
|
||||
LABEL_mongo=Mongo
|
||||
SERVER_mongo=dbgatedckstage1.sprinx.cz
|
||||
USER_mongo=root
|
||||
PASSWORD_mongo=Pwd2020Db
|
||||
PORT_mongo=27017
|
||||
ENGINE_mongo=mongo@dbgate-plugin-mongo
|
||||
|
||||
LABEL_mongo2=Mongo Server
|
||||
SERVER_mongo2=localhost
|
||||
ENGINE_mongo2=mongo@dbgate-plugin-mongo
|
||||
LABEL_redis=Redis
|
||||
SERVER_redis=dbgatedckstage1.sprinx.cz
|
||||
ENGINE_redis=redis@dbgate-plugin-redis
|
||||
PORT_redis=6379
|
||||
|
||||
LABEL_mysqlssh=MySql SSH
|
||||
SERVER_mysqlssh=localhost
|
||||
USER_mysqlssh=root
|
||||
PASSWORD_mysqlssh=xxx
|
||||
PORT_mysqlssh=3316
|
||||
ENGINE_mysqlssh=mysql@dbgate-plugin-mysql
|
||||
USE_SSH_mysqlssh=1
|
||||
SSH_HOST_mysqlssh=demo.dbgate.org
|
||||
SSH_PORT_mysqlssh=22
|
||||
SSH_MODE_mysqlssh=userPassword
|
||||
SSH_LOGIN_mysqlssh=root
|
||||
SSH_PASSWORD_mysqlssh=xxx
|
||||
LABEL_mssql=SQL Server
|
||||
SERVER_mssql=dbgatedckstage1.sprinx.cz
|
||||
USER_mssql=sa
|
||||
PASSWORD_mssql=Pwd2020Db
|
||||
PORT_mssql=1433
|
||||
ENGINE_mssql=mssql@dbgate-plugin-mssql
|
||||
|
||||
LABEL_sqlite=sqlite
|
||||
FILE_sqlite=/home/jena/.dbgate/files/sqlite/feeds.sqlite
|
||||
ENGINE_sqlite=sqlite@dbgate-plugin-sqlite
|
||||
|
||||
LABEL_relational=Relational dataset repo
|
||||
SERVER_relational=relational.fit.cvut.cz
|
||||
USER_relational=guest
|
||||
PASSWORD_relational=relational
|
||||
ENGINE_relational=mariadb@dbgate-plugin-mysql
|
||||
READONLY_relational=1
|
||||
LABEL_oracle=Oracle
|
||||
SERVER_oracle=dbgatedckstage1.sprinx.cz
|
||||
USER_oracle=system
|
||||
PASSWORD_oracle=Pwd2020Db
|
||||
PORT_oracle=1521
|
||||
ENGINE_oracle=oracle@dbgate-plugin-oracle
|
||||
SERVICE_NAME_oracle=xe
|
||||
|
||||
# SETTINGS_dataGrid.showHintColumns=1
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -12,6 +12,6 @@ DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
|
||||
|
||||
|
||||
SINGLE_CONNECTION=mysql
|
||||
SINGLE_DATABASE=Chinook
|
||||
# SINGLE_DATABASE=Chinook
|
||||
|
||||
PERMISSIONS=files/charts/read
|
||||
|
||||
@@ -111,7 +111,7 @@ module.exports = {
|
||||
const scriptFile = path.join(uploadsdir(), runid + '.js');
|
||||
fs.writeFileSync(`${scriptFile}`, scriptText);
|
||||
fs.mkdirSync(directory);
|
||||
const pluginNames = _.union(fs.readdirSync(pluginsdir()), packagedPluginList);
|
||||
const pluginNames = extractPlugins(scriptText);
|
||||
logger.info({ scriptFile }, 'Running script');
|
||||
// const subprocess = fork(scriptFile, ['--checkParent', '--max-old-space-size=8192'], {
|
||||
const subprocess = fork(
|
||||
|
||||
@@ -52,7 +52,7 @@ module.exports = {
|
||||
if (existing) return existing;
|
||||
const connection = await connections.getCore({ conid });
|
||||
if (!connection) {
|
||||
throw new Error(`Connection with conid="${conid}" not fund`);
|
||||
throw new Error(`Connection with conid="${conid}" not found`);
|
||||
}
|
||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||
|
||||
@@ -36,6 +36,9 @@ export function extractShellApiFunctionName(functionName) {
|
||||
}
|
||||
|
||||
export function findEngineDriver(connection, extensions: ExtensionsDirectory): EngineDriver {
|
||||
if (!extensions) {
|
||||
return null;
|
||||
}
|
||||
if (_isString(connection)) {
|
||||
return extensions.drivers.find(x => x.engine == connection);
|
||||
}
|
||||
|
||||
Vendored
+14
-2
@@ -35,7 +35,7 @@ export interface IndexInfo extends ColumnsConstraintInfo {
|
||||
isUnique: boolean;
|
||||
// indexType: 'normal' | 'clustered' | 'xml' | 'spatial' | 'fulltext';
|
||||
indexType?: string;
|
||||
// condition for filtered index (SQL Server)
|
||||
// condition for filtered index (SQL Server)
|
||||
filterDefinition?: string;
|
||||
}
|
||||
|
||||
@@ -118,9 +118,21 @@ export interface ViewInfo extends SqlObjectInfo {
|
||||
columns: ColumnInfo[];
|
||||
}
|
||||
|
||||
export interface ProcedureInfo extends SqlObjectInfo {}
|
||||
export type ParameterMode = 'IN' | 'OUT' | 'INOUT' | 'RETURN';
|
||||
|
||||
export interface ParameterInfo {
|
||||
schemaName: string;
|
||||
parameterName?: string;
|
||||
pureName: string;
|
||||
dataType: string;
|
||||
parameterMode?: ParameterMode;
|
||||
}
|
||||
export interface ProcedureInfo extends SqlObjectInfo {
|
||||
parameters?: ParameterInfo[];
|
||||
}
|
||||
|
||||
export interface FunctionInfo extends SqlObjectInfo {
|
||||
parameters?: ParameterInfo[];
|
||||
// returnDataType?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
export let module = null;
|
||||
|
||||
export let isBold = false;
|
||||
export let isChoosed = false;
|
||||
export let isBusy = false;
|
||||
export let statusIcon = undefined;
|
||||
export let statusIconBefore = undefined;
|
||||
@@ -66,11 +67,21 @@
|
||||
}
|
||||
|
||||
// $: console.log(title, indentLevel);
|
||||
let domDiv;
|
||||
|
||||
$: if (isBold && domDiv) {
|
||||
domDiv.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
}
|
||||
|
||||
$: if (isChoosed && domDiv) {
|
||||
domDiv.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="main"
|
||||
class:isBold
|
||||
class:isChoosed
|
||||
draggable={true}
|
||||
on:click={handleClick}
|
||||
on:mouseup={handleMouseUp}
|
||||
@@ -83,6 +94,7 @@
|
||||
on:dragenter
|
||||
on:dragend
|
||||
on:drop
|
||||
bind:this={domDiv}
|
||||
>
|
||||
{#if checkedObjectsStore}
|
||||
<CheckboxField
|
||||
@@ -180,6 +192,12 @@
|
||||
.isBold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.isChoosed {
|
||||
background-color: var(--theme-bg-3);
|
||||
}
|
||||
:global(.app-object-list-focused) .isChoosed {
|
||||
background-color: var(--theme-bg-selected);
|
||||
}
|
||||
.status {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
export let emptyGroupNames = [];
|
||||
|
||||
export let collapsedGroupNames = writable([]);
|
||||
export let onChangeFilteredList;
|
||||
|
||||
$: filtered = !groupFunc
|
||||
? list.filter(data => {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<svelte:component
|
||||
this={module.default}
|
||||
{data}
|
||||
on:click={handleExpand}
|
||||
on:dblclick={handleExpand}
|
||||
on:expand={handleExpandButton}
|
||||
expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)}
|
||||
{checkedObjectsStore}
|
||||
@@ -61,7 +61,13 @@
|
||||
|
||||
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
|
||||
<div class="subitems">
|
||||
<svelte:component this={subItemsComponent} {data} {filter} {passProps} />
|
||||
<svelte:component
|
||||
this={subItemsComponent(data)}
|
||||
{data}
|
||||
{filter}
|
||||
{passProps}
|
||||
isExpandedOnlyBySearch={isExpandedBySearch && !isExpanded}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -11,7 +11,17 @@
|
||||
const databases = getLocalStorage(`database_list_${_id}`) || [];
|
||||
return filterName(filter, ...databases.map(x => x.name));
|
||||
};
|
||||
export function openConnection(connection) {
|
||||
export function openConnection(connection, disableExpand = false) {
|
||||
if (connection.singleDatabase) {
|
||||
if (getOpenedSingleDatabaseConnections().includes(connection._id)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (getOpenedConnections().includes(connection._id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const config = getCurrentConfig();
|
||||
if (connection.singleDatabase) {
|
||||
switchCurrentDatabase({ connection, name: connection.defaultDatabase });
|
||||
@@ -27,7 +37,10 @@
|
||||
conid: connection._id,
|
||||
keepOpen: true,
|
||||
});
|
||||
expandedConnections.update(x => _.uniq([...x, connection._id]));
|
||||
|
||||
if (!disableExpand) {
|
||||
expandedConnections.update(x => _.uniq([...x, connection._id]));
|
||||
}
|
||||
|
||||
// if (!config.runAsPortal && getCurrentSettings()['defaultAction.connectionClick'] != 'connect') {
|
||||
// expandedConnections.update(x => _.uniq([...x, connection._id]));
|
||||
@@ -83,10 +96,12 @@
|
||||
currentDatabase,
|
||||
expandedConnections,
|
||||
extensions,
|
||||
focusedConnectionOrDatabase,
|
||||
getCurrentConfig,
|
||||
getCurrentDatabase,
|
||||
getCurrentSettings,
|
||||
getOpenedConnections,
|
||||
getOpenedSingleDatabaseConnections,
|
||||
getOpenedTabs,
|
||||
openedConnections,
|
||||
openedSingleDatabaseConnections,
|
||||
@@ -120,8 +135,8 @@
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleConnect = () => {
|
||||
openConnection(data);
|
||||
const handleConnect = (disableExpand = false) => {
|
||||
openConnection(data, disableExpand);
|
||||
};
|
||||
|
||||
const handleOpenConnectionTab = () => {
|
||||
@@ -135,13 +150,14 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal) {
|
||||
await tick();
|
||||
handleConnect();
|
||||
return;
|
||||
}
|
||||
const handleDoubleClick = async () => {
|
||||
// const config = getCurrentConfig();
|
||||
// if (config.runAsPortal) {
|
||||
// await tick();
|
||||
// handleConnect(true);
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ($openedSingleDatabaseConnections.includes(data._id)) {
|
||||
switchCurrentDatabase({ connection: data, name: data.defaultDatabase });
|
||||
return;
|
||||
@@ -149,12 +165,35 @@
|
||||
if ($openedConnections.includes(data._id)) {
|
||||
return;
|
||||
}
|
||||
await tick();
|
||||
handleConnect(true);
|
||||
|
||||
if (getCurrentSettings()['defaultAction.connectionClick'] == 'openDetails') {
|
||||
handleOpenConnectionTab();
|
||||
} else {
|
||||
await tick();
|
||||
handleConnect();
|
||||
// if (getCurrentSettings()['defaultAction.connectionClick'] == 'openDetails') {
|
||||
// handleOpenConnectionTab();
|
||||
// } else {
|
||||
// await tick();
|
||||
// handleConnect();
|
||||
// }
|
||||
};
|
||||
|
||||
const handleClick = async e => {
|
||||
focusedConnectionOrDatabase.set({
|
||||
conid: data?._id,
|
||||
connection: data,
|
||||
database: data.singleDatabase ? data.defaultDatabase : null,
|
||||
});
|
||||
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal == false && !config.storageDatabase) {
|
||||
openNewTab({
|
||||
title: getConnectionLabel(data),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
conid: data._id,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -319,8 +358,8 @@
|
||||
title={getConnectionLabel(data, { showUnsaved: true })}
|
||||
icon={data.singleDatabase ? 'img database' : 'img server'}
|
||||
isBold={data.singleDatabase
|
||||
? _.get($currentDatabase, 'connection._id') == data._id && _.get($currentDatabase, 'name') == data.defaultDatabase
|
||||
: _.get($currentDatabase, 'connection._id') == data._id}
|
||||
? $currentDatabase?.connection?._id == data._id && $currentDatabase?.name == data.defaultDatabase
|
||||
: $currentDatabase?.connection?._id == data._id}
|
||||
statusIcon={statusIcon || engineStatusIcon}
|
||||
statusTitle={statusTitle || engineStatusTitle}
|
||||
statusTitleToCopy={statusTitle || engineStatusTitle}
|
||||
@@ -329,12 +368,16 @@
|
||||
colorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data._id })}
|
||||
menu={getContextMenu}
|
||||
on:click={handleClick}
|
||||
on:click
|
||||
on:dblclick
|
||||
on:expand
|
||||
on:dblclick={handleConnect}
|
||||
on:dblclick={handleDoubleClick}
|
||||
on:middleclick={() => {
|
||||
_.flattenDeep(getContextMenu())
|
||||
.find(x => x.isNewQuery)
|
||||
.onClick();
|
||||
}}
|
||||
isChoosed={data._id == $focusedConnectionOrDatabase?.conid &&
|
||||
(data.singleDatabase
|
||||
? $focusedConnectionOrDatabase?.database == data.defaultDatabase
|
||||
: !$focusedConnectionOrDatabase?.database)}
|
||||
/>
|
||||
|
||||
@@ -446,12 +446,15 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
currentArchive,
|
||||
currentDatabase,
|
||||
extensions,
|
||||
focusedConnectionOrDatabase,
|
||||
getCurrentDatabase,
|
||||
getExtensions,
|
||||
getOpenedTabs,
|
||||
loadingSchemaLists,
|
||||
lockedDatabaseMode,
|
||||
openedConnections,
|
||||
openedSingleDatabaseConnections,
|
||||
openedTabs,
|
||||
pinnedDatabases,
|
||||
selectedWidget,
|
||||
visibleWidgetSideBar,
|
||||
@@ -482,6 +485,7 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
import ExportDbModelModal from '../modals/ExportDbModelModal.svelte';
|
||||
import ChooseArchiveFolderModal from '../modals/ChooseArchiveFolderModal.svelte';
|
||||
import { extractShellConnection } from '../impexp/createImpExpScript';
|
||||
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -509,10 +513,17 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
extInfo={data.extInfo}
|
||||
icon="img database"
|
||||
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') &&
|
||||
extractDbNameFromComposite(_.get($currentDatabase, 'name')) == data.name}
|
||||
on:click={() => switchCurrentDatabase(data)}
|
||||
passProps?.connectionColorFactory({ conid: data?.connection?._id, database: data.name }, null, null, false)}
|
||||
isBold={$currentDatabase?.connection?._id == data?.connection?._id &&
|
||||
extractDbNameFromComposite($currentDatabase?.name) == data.name}
|
||||
on:dblclick={() => {
|
||||
switchCurrentDatabase(data);
|
||||
// passProps?.onFocusSqlObjectList?.();
|
||||
}}
|
||||
on:click={() => {
|
||||
// switchCurrentDatabase(data);
|
||||
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
|
||||
}}
|
||||
on:dragstart
|
||||
on:dragenter
|
||||
on:dragend
|
||||
@@ -522,7 +533,15 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
.find(x => x.isNewQuery)
|
||||
.onClick();
|
||||
}}
|
||||
statusIcon={isLoadingSchemas ? 'icon loading' : ''}
|
||||
statusIcon={isLoadingSchemas
|
||||
? 'icon loading'
|
||||
: $lockedDatabaseMode
|
||||
? getNumberIcon(
|
||||
$openedTabs.filter(
|
||||
x => !x.closedTime && x.props.conid == data?.connection?._id && x.props.database == data?.name
|
||||
).length
|
||||
)
|
||||
: ''}
|
||||
menu={createMenu}
|
||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||
onPin={isPinned ? null : () => pinnedDatabases.update(list => [...list, data])}
|
||||
@@ -532,4 +551,6 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
list.filter(x => x?.name != data?.name || x?.connection?._id != data?.connection?._id)
|
||||
)
|
||||
: null}
|
||||
isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid &&
|
||||
data.name == $focusedConnectionOrDatabase?.database}
|
||||
/>
|
||||
|
||||
@@ -29,12 +29,18 @@
|
||||
views: 'ViewDataTab',
|
||||
matviews: 'ViewDataTab',
|
||||
queries: 'QueryDataTab',
|
||||
procedures: 'SqlObjectTab',
|
||||
functions: 'SqlObjectTab',
|
||||
};
|
||||
|
||||
function createMenusCore(
|
||||
objectTypeField,
|
||||
driver
|
||||
): {
|
||||
function createScriptTemplatesSubmenu(objectTypeField) {
|
||||
return {
|
||||
label: 'SQL template',
|
||||
submenu: getSupportedScriptTemplates(objectTypeField),
|
||||
};
|
||||
}
|
||||
|
||||
interface DbObjMenuItem {
|
||||
label?: string;
|
||||
tab?: string;
|
||||
forceNewTab?: boolean;
|
||||
@@ -53,34 +59,22 @@
|
||||
isExport?: boolean;
|
||||
isImport?: boolean;
|
||||
isActiveChart?: boolean;
|
||||
isShowSql?: boolean;
|
||||
scriptTemplate?: string;
|
||||
sqlGeneratorProps?: any;
|
||||
isDropCollection?: boolean;
|
||||
isRenameCollection?: boolean;
|
||||
isDuplicateCollection?: boolean;
|
||||
}[] {
|
||||
submenu?: DbObjMenuItem[];
|
||||
}
|
||||
|
||||
function createMenusCore(objectTypeField, driver): DbObjMenuItem[] {
|
||||
switch (objectTypeField) {
|
||||
case 'tables':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['tables'],
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'TableDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open form',
|
||||
tab: 'TableDataTab',
|
||||
forceNewTab: true,
|
||||
initialData: {
|
||||
grid: {
|
||||
isFormView: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
icon: 'img table-structure',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Design query',
|
||||
@@ -93,6 +87,33 @@
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
},
|
||||
createScriptTemplatesSubmenu('tables'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE TABLE',
|
||||
sqlGeneratorProps: {
|
||||
createTables: true,
|
||||
createIndexes: true,
|
||||
createForeignKeys: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'DROP TABLE',
|
||||
sqlGeneratorProps: {
|
||||
dropTables: true,
|
||||
dropReferences: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'INSERT',
|
||||
sqlGeneratorProps: {
|
||||
insert: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
@@ -142,50 +163,12 @@
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE TABLE',
|
||||
sqlGeneratorProps: {
|
||||
createTables: true,
|
||||
createIndexes: true,
|
||||
createForeignKeys: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP TABLE',
|
||||
sqlGeneratorProps: {
|
||||
dropTables: true,
|
||||
dropReferences: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: INSERT',
|
||||
sqlGeneratorProps: {
|
||||
insert: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
case 'views':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['views'],
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'ViewDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
icon: 'img view-structure',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Design query',
|
||||
@@ -197,6 +180,27 @@
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
},
|
||||
createScriptTemplatesSubmenu('views'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE VIEW',
|
||||
sqlGeneratorProps: {
|
||||
createViews: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'DROP VIEW',
|
||||
sqlGeneratorProps: {
|
||||
dropViews: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop view',
|
||||
isDrop: true,
|
||||
@@ -219,48 +223,12 @@
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: ALTER VIEW',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE VIEW',
|
||||
sqlGeneratorProps: {
|
||||
createViews: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP VIEW',
|
||||
sqlGeneratorProps: {
|
||||
dropViews: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
case 'matviews':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['matviews'],
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'ViewDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop view',
|
||||
@@ -272,10 +240,31 @@
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Query designer',
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
createScriptTemplatesSubmenu('matviews'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
createMatviews: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'DROP MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
dropMatviews: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
@@ -288,37 +277,6 @@
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE MATERIALIZED VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: ALTER MATERIALIZED VIEW',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
createMatviews: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
dropMatviews: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
case 'queries':
|
||||
return [
|
||||
@@ -330,6 +288,10 @@
|
||||
];
|
||||
case 'procedures':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['procedures'],
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop procedure',
|
||||
isDrop: true,
|
||||
@@ -340,33 +302,31 @@
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
createScriptTemplatesSubmenu('procedures'),
|
||||
{
|
||||
label: 'SQL: CREATE PROCEDURE',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: ALTER PROCEDURE',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: EXECUTE',
|
||||
scriptTemplate: 'EXECUTE PROCEDURE',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE PROCEDURE',
|
||||
sqlGeneratorProps: {
|
||||
createProcedures: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP PROCEDURE',
|
||||
sqlGeneratorProps: {
|
||||
dropProcedures: true,
|
||||
},
|
||||
label: 'SQL generator',
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE PROCEDURE',
|
||||
sqlGeneratorProps: {
|
||||
createProcedures: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'DROP PROCEDURE',
|
||||
sqlGeneratorProps: {
|
||||
dropProcedures: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
case 'functions':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['functions'],
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop function',
|
||||
isDrop: true,
|
||||
@@ -377,43 +337,30 @@
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
createScriptTemplatesSubmenu('functions'),
|
||||
{
|
||||
label: 'SQL: CREATE FUNCTION',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: ALTER FUNCTION',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE FUNCTION',
|
||||
sqlGeneratorProps: {
|
||||
createFunctions: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP FUNCTION',
|
||||
sqlGeneratorProps: {
|
||||
dropFunctions: true,
|
||||
},
|
||||
label: 'SQL generator',
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE FUNCTION',
|
||||
sqlGeneratorProps: {
|
||||
createFunctions: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'DROP FUNCTION',
|
||||
sqlGeneratorProps: {
|
||||
dropFunctions: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
case 'collections':
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['collections'],
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'CollectionDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open JSON',
|
||||
tab: 'CollectionDataTab',
|
||||
forceNewTab: true,
|
||||
initialData: {
|
||||
grid: {
|
||||
isJsonView: true,
|
||||
},
|
||||
},
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Design perspective query',
|
||||
@@ -659,15 +606,30 @@
|
||||
// fixedTargetPureName: data.pureName,
|
||||
// },
|
||||
// });
|
||||
// } else if (menu.isShowSql) {
|
||||
// openNewTab({
|
||||
// title: data.pureName,
|
||||
// icon: 'img sql-file',
|
||||
// tabComponent: 'SqlObjectTab',
|
||||
// tabPreviewMode: true,
|
||||
// props: {
|
||||
// conid: data.conid,
|
||||
// database: data.database,
|
||||
// schemaName: data.schemaName,
|
||||
// pureName: data.pureName,
|
||||
// objectTypeField: data.objectTypeField,
|
||||
// },
|
||||
// });
|
||||
} else {
|
||||
openDatabaseObjectDetail(
|
||||
menu.tab,
|
||||
menu.scriptTemplate,
|
||||
data,
|
||||
{ ...data, defaultActionId: menu.defaultActionId },
|
||||
menu.forceNewTab,
|
||||
menu.initialData,
|
||||
menu.icon,
|
||||
data
|
||||
data,
|
||||
!!menu.defaultActionId
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -697,11 +659,12 @@
|
||||
export async function openDatabaseObjectDetail(
|
||||
tabComponent,
|
||||
scriptTemplate,
|
||||
{ schemaName, pureName, conid, database, objectTypeField },
|
||||
{ schemaName, pureName, conid, database, objectTypeField, defaultActionId },
|
||||
forceNewTab?,
|
||||
initialData?,
|
||||
icon?,
|
||||
appObjectData?
|
||||
appObjectData?,
|
||||
tabPreviewMode?
|
||||
) {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${database}\n${fullDisplayName({
|
||||
@@ -711,12 +674,16 @@
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: scriptTemplate ? 'Query #' : getObjectTitle(connection, schemaName, pureName),
|
||||
// title: getObjectTitle(connection, schemaName, pureName),
|
||||
title: tabComponent ? getObjectTitle(connection, schemaName, pureName) : 'Query #',
|
||||
tooltip,
|
||||
icon: icon || (scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
|
||||
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
|
||||
icon:
|
||||
icon ||
|
||||
(scriptTemplate || tabComponent == 'SqlObjectTab' ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
|
||||
tabComponent: tabComponent ?? 'QueryTab',
|
||||
appObject: 'DatabaseObjectAppObject',
|
||||
appObjectData,
|
||||
tabPreviewMode,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
@@ -724,38 +691,79 @@
|
||||
database,
|
||||
objectTypeField,
|
||||
initialArgs: scriptTemplate ? { scriptTemplate } : null,
|
||||
defaultActionId,
|
||||
},
|
||||
},
|
||||
initialData,
|
||||
{ forceNewTab }
|
||||
);
|
||||
|
||||
if (tabPreviewMode && defaultActionId && getBoolSettingsValue('defaultAction.useLastUsedAction', true)) {
|
||||
lastUsedDefaultActions.update(actions => ({
|
||||
...actions,
|
||||
[objectTypeField]: defaultActionId,
|
||||
}));
|
||||
// apiCall('config/update-settings', {
|
||||
// [`defaultAction.dbObjectClick.${objectTypeField}`]: defaultActionId,
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDatabaseObjectClick(data, forceNewTab = false) {
|
||||
export function handleDatabaseObjectClick(
|
||||
data,
|
||||
{ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}
|
||||
) {
|
||||
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
||||
const driver = findEngineDriver(data, getExtensions());
|
||||
|
||||
const configuredAction = getCurrentSettings()[`defaultAction.dbObjectClick.${objectTypeField}`];
|
||||
const overrideMenu = createMenus(objectTypeField, driver).find(x => x.label && x.label == configuredAction);
|
||||
if (overrideMenu) {
|
||||
databaseObjectMenuClickHandler(data, overrideMenu);
|
||||
const activeTab = getActiveTab();
|
||||
const activeTabProps = activeTab?.props || {};
|
||||
// const activeDefaultActionId = activeTab?.props?.defaultActionId;
|
||||
|
||||
if (matchDatabaseObjectAppObject(data, activeTabProps)) {
|
||||
if (!tabPreviewMode) {
|
||||
openedTabs.update(tabs => {
|
||||
return tabs.map(tab => ({
|
||||
...tab,
|
||||
tabPreviewMode: tab.tabid == activeTab.tabid ? false : tab.tabPreviewMode,
|
||||
focused: focusTab && tab.tabid == activeTab.tabid ? true : tab.focused,
|
||||
}));
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const availableDefaultActions = defaultDatabaseObjectAppObjectActions[objectTypeField];
|
||||
|
||||
const configuredActionId = getLastUsedDefaultActions()[objectTypeField];
|
||||
const prefferedAction =
|
||||
// availableDefaultActions.find(x => x.defaultActionId == activeDefaultActionId) ??
|
||||
availableDefaultActions.find(x => x.defaultActionId == configuredActionId) ?? availableDefaultActions[0];
|
||||
|
||||
// console.log('activeTab', activeTab);
|
||||
|
||||
// const overrideMenu = createMenus(objectTypeField, driver).find(x => x.label && x.label == configuredAction);
|
||||
// if (overrideMenu) {
|
||||
// databaseObjectMenuClickHandler(data, overrideMenu);
|
||||
// return;
|
||||
// }
|
||||
|
||||
openDatabaseObjectDetail(
|
||||
defaultTabs[objectTypeField],
|
||||
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||
prefferedAction.tab,
|
||||
activeTabProps?.scriptTemplate,
|
||||
{
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: prefferedAction.defaultActionId,
|
||||
},
|
||||
forceNewTab,
|
||||
null,
|
||||
null,
|
||||
data
|
||||
data,
|
||||
tabPreviewMode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -769,64 +777,84 @@
|
||||
);
|
||||
}
|
||||
|
||||
function menuItemMapper(menu, data, connection) {
|
||||
if (menu.divider) return menu;
|
||||
|
||||
if (menu.isExport) {
|
||||
return createQuickExportMenu(
|
||||
fmt => async () => {
|
||||
const coninfo = await getConnectionInfo(data);
|
||||
exportQuickExportFile(
|
||||
data.pureName,
|
||||
{
|
||||
functionName: menu.functionName,
|
||||
props: {
|
||||
connection: extractShellConnection(coninfo, data.database),
|
||||
..._.pick(data, ['pureName', 'schemaName']),
|
||||
},
|
||||
},
|
||||
fmt
|
||||
);
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
openImportExportTab({
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: extractDbNameFromComposite(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],
|
||||
// },
|
||||
// });
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (connection?.isReadOnly && menu.requiresWriteAccess) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (menu.submenu) {
|
||||
return {
|
||||
...menu,
|
||||
submenu: menu.submenu.map(x => menuItemMapper(x, data, connection)),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
text: menu.label,
|
||||
onClick: () => {
|
||||
databaseObjectMenuClickHandler(data, menu);
|
||||
},
|
||||
iconAlt: menu.defaultActionId ? 'icon open-in-new' : null,
|
||||
onClickAlt: menu.defaultActionId
|
||||
? () => {
|
||||
databaseObjectMenuClickHandler(data, { ...menu, forceNewTab: true, defaultActionId: null });
|
||||
}
|
||||
: null,
|
||||
isBold:
|
||||
data.objectTypeField &&
|
||||
menu.defaultActionId &&
|
||||
getLastUsedDefaultActions()[data.objectTypeField] == menu.defaultActionId,
|
||||
};
|
||||
}
|
||||
|
||||
export function createDatabaseObjectMenu(data, connection = null) {
|
||||
const driver = findEngineDriver(data, getExtensions());
|
||||
|
||||
const { objectTypeField } = data;
|
||||
return createMenus(objectTypeField, driver)
|
||||
.filter(x => x)
|
||||
.map(menu => {
|
||||
if (menu.divider) return menu;
|
||||
|
||||
if (menu.isExport) {
|
||||
return createQuickExportMenu(
|
||||
fmt => async () => {
|
||||
const coninfo = await getConnectionInfo(data);
|
||||
exportQuickExportFile(
|
||||
data.pureName,
|
||||
{
|
||||
functionName: menu.functionName,
|
||||
props: {
|
||||
connection: extractShellConnection(coninfo, data.database),
|
||||
..._.pick(data, ['pureName', 'schemaName']),
|
||||
},
|
||||
},
|
||||
fmt
|
||||
);
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
openImportExportTab({
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: extractDbNameFromComposite(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],
|
||||
// },
|
||||
// });
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (connection?.isReadOnly && menu.requiresWriteAccess) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
text: menu.label,
|
||||
onClick: () => {
|
||||
databaseObjectMenuClickHandler(data, menu);
|
||||
},
|
||||
};
|
||||
});
|
||||
.map(menu => menuItemMapper(menu, data, connection));
|
||||
}
|
||||
|
||||
function formatRowCount(value) {
|
||||
@@ -838,6 +866,10 @@
|
||||
export function createAppObjectMenu(data) {
|
||||
return createDatabaseObjectMenu(data);
|
||||
}
|
||||
|
||||
export function handleObjectClick(data, { forceNewTab = false, tabPreviewMode = false, focusTab = false }) {
|
||||
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -846,10 +878,15 @@
|
||||
import {
|
||||
currentDatabase,
|
||||
extensions,
|
||||
getActiveTab,
|
||||
getCurrentSettings,
|
||||
getExtensions,
|
||||
getLastUsedDefaultActions,
|
||||
lastUsedDefaultActions,
|
||||
openedConnections,
|
||||
openedTabs,
|
||||
pinnedTables,
|
||||
selectedDatabaseObjectAppObject,
|
||||
} from '../stores';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import {
|
||||
@@ -877,12 +914,16 @@
|
||||
import { getDefaultFileFormat } from '../plugins/fileformats';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
|
||||
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
function handleClick(forceNewTab = false) {
|
||||
handleDatabaseObjectClick(data, forceNewTab);
|
||||
function handleClick({ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}) {
|
||||
$selectedDatabaseObjectAppObject = _.pick(data, ['conid', 'database', 'objectTypeField', 'pureName', 'schemaName']);
|
||||
handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
@@ -915,8 +956,10 @@
|
||||
onPin={isPinned ? null : () => pinnedTables.update(list => [...list, data])}
|
||||
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
|
||||
extInfo={getExtInfo(data)}
|
||||
on:click={() => handleClick()}
|
||||
on:middleclick={() => handleClick(true)}
|
||||
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
|
||||
on:click={() => handleClick({ tabPreviewMode: true })}
|
||||
on:middleclick={() => handleClick({ forceNewTab: true })}
|
||||
on:dblclick={() => handleClick({ tabPreviewMode: false, focusTab: true })}
|
||||
on:expand
|
||||
on:dragstart
|
||||
on:dragenter
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" context="module">
|
||||
export const extractKey = ({ columnName }) => columnName;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
{data}
|
||||
title={data.parameterName}
|
||||
extInfo={data.parameterMode && data.parameterMode !== 'IN' ? `${data.dataType} ${data.parameterMode}` : data.dataType}
|
||||
icon={'icon parameter'}
|
||||
disableHover
|
||||
/>
|
||||
@@ -1,21 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||
import _ from 'lodash';
|
||||
import { useDatabaseList } from '../utility/metadataLoaders';
|
||||
import AppObjectList from './AppObjectList.svelte';
|
||||
import * as databaseAppObject from './DatabaseAppObject.svelte';
|
||||
import { volatileConnectionMapStore } from '../utility/api';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
|
||||
export let filter;
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
$: databases = useDatabaseList({ conid: data._id });
|
||||
export let isExpandedOnlyBySearch;
|
||||
|
||||
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
|
||||
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
|
||||
</script>
|
||||
|
||||
<AppObjectList
|
||||
list={_.sortBy(
|
||||
($databases || []).filter(x => filterName(filter, x.name)),
|
||||
dbList.filter(x => filterName(filter, x.name, data.displayName, data.server)),
|
||||
x => x.sortOrder ?? x.name
|
||||
).map(db => ({ ...db, connection: data }))}
|
||||
module={databaseAppObject}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import AppObjectList from './AppObjectList.svelte';
|
||||
import * as parameterAppObject from './ParameterAppObject.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<AppObjectList
|
||||
list={(data.parameters || []).map(parameter => ({
|
||||
...data,
|
||||
...parameter,
|
||||
}))}
|
||||
module={parameterAppObject}
|
||||
/>
|
||||
@@ -0,0 +1,80 @@
|
||||
export function matchDatabaseObjectAppObject(obj1, obj2) {
|
||||
return (
|
||||
obj1?.objectTypeField == obj2?.objectTypeField &&
|
||||
obj1?.conid == obj2?.conid &&
|
||||
obj1?.database == obj2?.database &&
|
||||
obj1?.pureName == obj2?.pureName &&
|
||||
obj1?.schemaName == obj2?.schemaName
|
||||
);
|
||||
}
|
||||
|
||||
function getTableLikeActions(dataTab) {
|
||||
return [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: dataTab,
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
{
|
||||
label: 'Open form',
|
||||
tab: dataTab,
|
||||
initialData: {
|
||||
grid: {
|
||||
isFormView: true,
|
||||
},
|
||||
},
|
||||
defaultActionId: 'openForm',
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
icon: 'img table-structure',
|
||||
defaultActionId: 'openStructure',
|
||||
},
|
||||
{
|
||||
label: 'Show SQL',
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const defaultDatabaseObjectAppObjectActions = {
|
||||
tables: getTableLikeActions('TableDataTab'),
|
||||
views: getTableLikeActions('ViewDataTab'),
|
||||
matviews: getTableLikeActions('ViewDataTab'),
|
||||
procedures: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
},
|
||||
],
|
||||
functions: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
},
|
||||
],
|
||||
collections: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'CollectionDataTab',
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
{
|
||||
label: 'Open JSON',
|
||||
tab: 'CollectionDataTab',
|
||||
defaultActionId: 'openJson',
|
||||
initialData: {
|
||||
grid: {
|
||||
isJsonView: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -7,6 +7,8 @@
|
||||
export let disabled = false;
|
||||
export let value;
|
||||
export let title = null;
|
||||
export let skipWidth = false;
|
||||
export let outline = false;
|
||||
|
||||
function handleClick() {
|
||||
if (!disabled) dispatch('click');
|
||||
@@ -19,27 +21,53 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<input {type} {value} {title} class:disabled {...$$restProps} on:click={handleClick} bind:this={domButton} />
|
||||
<input
|
||||
{type}
|
||||
{value}
|
||||
{title}
|
||||
class:disabled
|
||||
{...$$restProps}
|
||||
on:click={handleClick}
|
||||
bind:this={domButton}
|
||||
class:skipWidth
|
||||
class:outline
|
||||
/>
|
||||
|
||||
<style>
|
||||
input {
|
||||
border: 1px solid var(--theme-bg-button-inv-2);
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
width: 100px;
|
||||
background-color: var(--theme-bg-button-inv);
|
||||
color: var(--theme-font-inv-1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:hover:not(.disabled) {
|
||||
input:not(.skipWidth) {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
input:hover:not(.disabled):not(.outline) {
|
||||
background-color: var(--theme-bg-button-inv-2);
|
||||
}
|
||||
input:active:not(.disabled) {
|
||||
input:active:not(.disabled):not(.outline) {
|
||||
background-color: var(--theme-bg-button-inv-3);
|
||||
}
|
||||
input.disabled {
|
||||
background-color: var(--theme-bg-button-inv-3);
|
||||
color: var(--theme-font-inv-3);
|
||||
}
|
||||
|
||||
input.outline {
|
||||
background-color: transparent;
|
||||
color: var(--theme-font-2);
|
||||
border: 1px solid var(--theme-bg-button-inv-2);
|
||||
}
|
||||
|
||||
input.outline:hover:not(.disabled) {
|
||||
color: var(--theme-bg-button-inv-3);
|
||||
border: 2px solid var(--theme-bg-button-inv-3);
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -942,6 +942,14 @@ registerCommand({
|
||||
onClick: () => showModal(UploadErrorModal),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.unsetCurrentDatabase',
|
||||
category: 'Application',
|
||||
name: 'Unset current database',
|
||||
testEnabled: () => getCurrentDatabase() != null,
|
||||
onClick: () => currentDatabase.set(null),
|
||||
});
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
||||
|
||||
@@ -142,6 +142,8 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let isColumnManagerFocused = false;
|
||||
</script>
|
||||
|
||||
{#if allowChangeChangeSetStructure}
|
||||
@@ -207,9 +209,13 @@
|
||||
bind:this={domFocusField}
|
||||
on:keydown={handleKeyDown}
|
||||
on:focus={() => {
|
||||
isColumnManagerFocused = true;
|
||||
// activator.activate();
|
||||
// invalidateCommands();
|
||||
}}
|
||||
on:blur={() => {
|
||||
isColumnManagerFocused = false;
|
||||
}}
|
||||
on:copy={copyToClipboard}
|
||||
/>
|
||||
|
||||
@@ -224,6 +230,7 @@
|
||||
{database}
|
||||
{tableInfo}
|
||||
{setTableInfo}
|
||||
{isColumnManagerFocused}
|
||||
columnInfo={tableInfo?.columns?.[columnIndex]}
|
||||
{columnIndex}
|
||||
{allowChangeChangeSetStructure}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
export let columnInfo = null;
|
||||
export let columnIndex = -1;
|
||||
|
||||
export let isColumnManagerFocused = false;
|
||||
|
||||
export let allowChangeChangeSetStructure = false;
|
||||
$: addDataCommand = allowChangeChangeSetStructure;
|
||||
|
||||
@@ -49,6 +51,7 @@
|
||||
else display.focusColumns([column.uniqueName]);
|
||||
}}
|
||||
class:isSelected
|
||||
class:isFocused={isColumnManagerFocused}
|
||||
on:click
|
||||
on:mousedown
|
||||
on:mousemove
|
||||
@@ -123,6 +126,10 @@
|
||||
}
|
||||
|
||||
.row.isSelected {
|
||||
background: var(--theme-bg-3);
|
||||
}
|
||||
|
||||
.row.isSelected.isFocused {
|
||||
background: var(--theme-bg-selected);
|
||||
}
|
||||
|
||||
|
||||
@@ -173,6 +173,9 @@
|
||||
background: var(--theme-bg-volcano);
|
||||
}
|
||||
td.isSelected {
|
||||
background: var(--theme-bg-3);
|
||||
}
|
||||
:global(.data-grid-focused) td.isSelected {
|
||||
background: var(--theme-bg-selected);
|
||||
}
|
||||
td.isDeleted {
|
||||
|
||||
@@ -472,7 +472,7 @@
|
||||
export let dataEditorTypesBehaviourOverride = null;
|
||||
|
||||
const wheelRowCount = 5;
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
let containerHeight = 0;
|
||||
let containerWidth = 0;
|
||||
@@ -492,6 +492,8 @@
|
||||
let autofillSelectedCells = emptyCellArray;
|
||||
const domFilterControlsRef = createRef({});
|
||||
|
||||
let isGridFocused=false;
|
||||
|
||||
const tabid = getContext('tabid');
|
||||
|
||||
let unsubscribeDbRefresh;
|
||||
@@ -1041,7 +1043,6 @@
|
||||
return !hideGridLeftColumn;
|
||||
}
|
||||
|
||||
|
||||
$: autofillMarkerCell =
|
||||
selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1
|
||||
? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))]
|
||||
@@ -1134,7 +1135,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($tabVisible && domFocusField && focusOnVisible) {
|
||||
$: if ($tabFocused && domFocusField && focusOnVisible) {
|
||||
domFocusField.focus();
|
||||
}
|
||||
|
||||
@@ -1863,6 +1864,7 @@
|
||||
{:else}
|
||||
<div
|
||||
class="container"
|
||||
class:data-grid-focused={isGridFocused}
|
||||
bind:clientWidth={containerWidth}
|
||||
bind:clientHeight={containerHeight}
|
||||
use:contextMenu={buildMenu}
|
||||
@@ -1877,10 +1879,14 @@
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
invalidateCommands();
|
||||
isGridFocused = true;
|
||||
}}
|
||||
on:blur
|
||||
on:paste={handlePaste}
|
||||
on:copy={copyToClipboard}
|
||||
on:blur={handleBlur}
|
||||
on:blur={() => {
|
||||
isGridFocused = false;
|
||||
}}
|
||||
/>
|
||||
<table
|
||||
class="table"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
$: searchValue = value || '';
|
||||
export let isDebounced = false;
|
||||
export let onFocusFilteredList = null;
|
||||
|
||||
let domInput;
|
||||
|
||||
@@ -14,9 +15,21 @@
|
||||
if (e.keyCode == keycodes.escape) {
|
||||
value = '';
|
||||
}
|
||||
if (e.keyCode == keycodes.downArrow || e.keyCode == keycodes.pageDown || e.keyCode == keycodes.enter) {
|
||||
onFocusFilteredList?.();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedSet = _.debounce(x => (value = x), 500);
|
||||
|
||||
export function focus(text) {
|
||||
domInput.focus();
|
||||
if (text) {
|
||||
domInput.value = text;
|
||||
value = text;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
|
||||
@@ -217,12 +217,12 @@
|
||||
$: rowHeight = $dataGridRowHeight;
|
||||
let currentCell = [0, 0];
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
const domCells = {};
|
||||
|
||||
let domFocusField;
|
||||
|
||||
$: if ($tabVisible && domFocusField && focusOnVisible) {
|
||||
$: if ($tabFocused && domFocusField && focusOnVisible) {
|
||||
domFocusField.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
'icon open-in-new': 'mdi mdi-open-in-new',
|
||||
'icon add-folder': 'mdi mdi-folder-plus-outline',
|
||||
'icon add-column': 'mdi mdi-table-column-plus-after',
|
||||
'icon parameter': 'mdi mdi-at',
|
||||
|
||||
'icon window-restore': 'mdi mdi-window-restore',
|
||||
'icon window-maximize': 'mdi mdi-window-maximize',
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
if (item.onClick) item.onClick();
|
||||
}
|
||||
|
||||
function handleClickAlt(e, item) {
|
||||
if (item.disabled) return;
|
||||
dispatchClose();
|
||||
if (onCloseParent) onCloseParent();
|
||||
if (item.onClickAlt) item.onClickAlt();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fixPopupPlacement(element);
|
||||
});
|
||||
@@ -123,11 +130,16 @@
|
||||
changeActiveSubmenu();
|
||||
}}
|
||||
>
|
||||
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled}>
|
||||
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
|
||||
{item.text || item.label}
|
||||
{#if item.keyText}
|
||||
<span class="keyText">{formatKeyText(item.keyText)}</span>
|
||||
{/if}
|
||||
{#if item.iconAlt}
|
||||
<span class="alt-icon" on:click={e => handleClickAlt(e, item)}>
|
||||
<FontIcon icon={item.iconAlt} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if item.submenu}
|
||||
<div class="menu-right">
|
||||
<FontIcon icon="icon menu-right" />
|
||||
@@ -166,7 +178,7 @@
|
||||
cursor: default;
|
||||
white-space: nowrap;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100% - 20px)
|
||||
max-height: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.keyText {
|
||||
@@ -189,6 +201,10 @@
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
|
||||
a.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover:not(.disabled) {
|
||||
background-color: var(--theme-bg-1);
|
||||
text-decoration: none;
|
||||
@@ -205,4 +221,12 @@
|
||||
position: relative;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.alt-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alt-icon:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
|
||||
--theme-border: #555;
|
||||
|
||||
--theme-bg-hover: #112a45;
|
||||
--theme-bg-selected: #15395b; /* blue-3 */
|
||||
--theme-bg-hover: #555;
|
||||
--theme-bg-selected: #1d4d7e; /* blue-4 */
|
||||
--theme-bg-selected-point: #1765ad; /* blue-5 */
|
||||
|
||||
--theme-bg-statusbar-inv: #0050b3;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
--theme-border: #ccc;
|
||||
|
||||
--theme-bg-hover: #bae7ff;
|
||||
--theme-bg-hover: #ccc;
|
||||
--theme-bg-selected: #91d5ff; /* blue-3 */
|
||||
--theme-bg-selected-point: #40a9ff; /* blue-5 */
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function newQuery({
|
||||
tooltip,
|
||||
tabComponent,
|
||||
multiTabIndex,
|
||||
focused: true,
|
||||
props: {
|
||||
...props,
|
||||
conid: connection._id,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { defaultDatabaseObjectAppObjectActions } from '../appobj/appObjectTools';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import { lastUsedDefaultActions } from '../stores';
|
||||
|
||||
export let label;
|
||||
export let objectTypeField;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<FormFieldTemplateLarge {label} type="combo">
|
||||
<SelectField
|
||||
{label}
|
||||
isNative
|
||||
{disabled}
|
||||
defaultValue={defaultDatabaseObjectAppObjectActions[objectTypeField][0]?.defaultActionId}
|
||||
options={defaultDatabaseObjectAppObjectActions[objectTypeField].map(x => ({
|
||||
value: x.defaultActionId,
|
||||
label: x.label,
|
||||
}))}
|
||||
value={$lastUsedDefaultActions[objectTypeField]}
|
||||
on:change={e => {
|
||||
$lastUsedDefaultActions = {
|
||||
...$lastUsedDefaultActions,
|
||||
[objectTypeField]: e.detail.value,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
@@ -38,6 +38,7 @@
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { derived } from 'svelte/store';
|
||||
import { safeFormatDate } from 'dbgate-tools';
|
||||
import FormDefaultActionField from './FormDefaultActionField.svelte';
|
||||
|
||||
const electron = getElectron();
|
||||
let restartWarning = false;
|
||||
@@ -277,66 +278,32 @@ ORDER BY
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="4">
|
||||
<div class="heading">Default actions</div>
|
||||
<FormSelectField
|
||||
label="Connection click"
|
||||
name="defaultAction.connectionClick"
|
||||
isNative
|
||||
defaultValue="connect"
|
||||
options={[
|
||||
{ value: 'openDetails', label: 'Edit / open details' },
|
||||
{ value: 'connect', label: 'Connect' },
|
||||
]}
|
||||
/>
|
||||
<FormCheckboxField name="defaultAction.useLastUsedAction" label="Use last used action" defaultValue={true} />
|
||||
|
||||
<FormSelectField
|
||||
<FormDefaultActionField
|
||||
label="Table click"
|
||||
name="defaultAction.dbObjectClick.tables"
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{ value: '', label: 'Open data, or open existing' },
|
||||
{ value: 'Open data', label: 'Open data (always new tab)' },
|
||||
{ value: 'Open form', label: 'Open form (always new tab)' },
|
||||
{ value: 'Open structure', label: 'Open structure' },
|
||||
{ value: 'SQL: CREATE TABLE', label: 'SQL: CREATE' },
|
||||
{ value: 'SQL: SELECT', label: 'SQL: SELECT' },
|
||||
]}
|
||||
objectTypeField="tables"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
<FormDefaultActionField
|
||||
label="View click"
|
||||
name="defaultAction.dbObjectClick.views"
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{ value: '', label: 'Open data, or open existing' },
|
||||
{ value: 'Open data', label: 'Open data (always new tab)' },
|
||||
{ value: 'SQL: CREATE VIEW', label: 'SQL: CREATE' },
|
||||
]}
|
||||
objectTypeField="views"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
<FormDefaultActionField
|
||||
label="Materialized view click"
|
||||
name="defaultAction.dbObjectClick.matviews"
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{ value: '', label: 'Open data, or open existing' },
|
||||
{ value: 'Open data', label: 'Open data (always new tab)' },
|
||||
{ value: 'SQL: CREATE MATERIALIZED VIEW', label: 'SQL: CREATE' },
|
||||
]}
|
||||
objectTypeField="matviews"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
<FormDefaultActionField
|
||||
label="Procedure click"
|
||||
name="defaultAction.dbObjectClick.procedures"
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{ value: '', label: 'SQL: CREATE' },
|
||||
{ value: 'SQL: EXECUTE', label: 'SQL: EXECUTE' },
|
||||
// { value: 'SQL: CREATE PROCEDURE', label: 'SQL: CREATE' },
|
||||
]}
|
||||
objectTypeField="procedures"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label="Function click"
|
||||
objectTypeField="functions"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="5">
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface TabDefinition {
|
||||
tabOrder?: number;
|
||||
multiTabIndex?: number;
|
||||
unsaved?: boolean;
|
||||
tabPreviewMode?: boolean;
|
||||
focused?: boolean;
|
||||
}
|
||||
|
||||
export function writableWithStorage<T>(defaultValue: T, storageName) {
|
||||
@@ -91,7 +93,7 @@ export const openedConnections = writable([]);
|
||||
export const temporaryOpenedConnections = writable([]);
|
||||
export const openedSingleDatabaseConnections = writable([]);
|
||||
export const expandedConnections = writable([]);
|
||||
export const currentDatabase = writable(null);
|
||||
export const currentDatabase = writableWithForage(null, 'currentDatabase');
|
||||
export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]);
|
||||
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
|
||||
export const extensions = writable<ExtensionsDirectory>(null);
|
||||
@@ -154,6 +156,10 @@ export const loadingPluginStore = writable({
|
||||
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
||||
export const appliedCurrentSchema = writable<string>(null);
|
||||
export const loadingSchemaLists = writable({}); // dict [`${conid}::${database}`]: true
|
||||
export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultActions');
|
||||
|
||||
export const selectedDatabaseObjectAppObject = writable(null);
|
||||
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(null);
|
||||
|
||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||
@@ -324,8 +330,32 @@ appliedCurrentSchema.subscribe(value => {
|
||||
});
|
||||
export const getAppliedCurrentSchema = () => appliedCurrentSchemaValue;
|
||||
|
||||
let selectedDatabaseObjectAppObjectValue = null;
|
||||
selectedDatabaseObjectAppObject.subscribe(value => {
|
||||
selectedDatabaseObjectAppObjectValue = value;
|
||||
});
|
||||
export const getSelectedDatabaseObjectAppObject = () => selectedDatabaseObjectAppObjectValue;
|
||||
|
||||
let openedModalsValue = [];
|
||||
openedModals.subscribe(value => {
|
||||
openedModalsValue = value;
|
||||
});
|
||||
export const getOpenedModals = () => openedModalsValue;
|
||||
|
||||
let focusedConnectionOrDatabaseValue = null;
|
||||
focusedConnectionOrDatabase.subscribe(value => {
|
||||
focusedConnectionOrDatabaseValue = value;
|
||||
});
|
||||
export const getFocusedConnectionOrDatabase = () => focusedConnectionOrDatabaseValue;
|
||||
|
||||
let openedSingleDatabaseConnectionsValue = [];
|
||||
openedSingleDatabaseConnections.subscribe(value => {
|
||||
openedSingleDatabaseConnectionsValue = value;
|
||||
});
|
||||
export const getOpenedSingleDatabaseConnections = () => openedSingleDatabaseConnectionsValue;
|
||||
|
||||
let lastUsedDefaultActionsValue = {};
|
||||
lastUsedDefaultActions.subscribe(value => {
|
||||
lastUsedDefaultActionsValue = value;
|
||||
});
|
||||
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
|
||||
@@ -4,17 +4,23 @@
|
||||
|
||||
export let tabid;
|
||||
export let tabVisible;
|
||||
export let tabFocused;
|
||||
export let tabPreviewMode;
|
||||
export let tabComponent;
|
||||
|
||||
const tabVisibleStore = writable(tabVisible);
|
||||
setContext('tabid', tabid);
|
||||
setContext('tabVisible', tabVisibleStore);
|
||||
|
||||
const tabVisibleStore = writable(tabVisible);
|
||||
setContext('tabVisible', tabVisibleStore);
|
||||
$: tabVisibleStore.set(tabVisible);
|
||||
|
||||
const tabFocusedStore = writable(tabFocused);
|
||||
setContext('tabFocused', tabFocusedStore);
|
||||
$: tabFocusedStore.set(tabFocused);
|
||||
</script>
|
||||
|
||||
<div class:tabVisible>
|
||||
<svelte:component this={tabComponent} {...$$restProps} {tabid} {tabVisible} />
|
||||
<svelte:component this={tabComponent} {...$$restProps} {tabid} {tabVisible} {tabFocused} {tabPreviewMode} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -52,5 +52,7 @@
|
||||
{tabid}
|
||||
unsaved={openedTabsByTabId[tabid]?.unsaved}
|
||||
tabVisible={tabid == shownTab?.tabid}
|
||||
tabFocused={tabid == shownTab?.tabid && shownTab?.focused}
|
||||
tabPreviewMode={tabid == shownTab?.tabid && shownTab?.tabPreviewMode}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -155,6 +155,19 @@
|
||||
);
|
||||
const closeOthersInMultiTab = multiTabIndex =>
|
||||
closeTabFunc((x, active) => x.tabid != active.tabid && (x.multiTabIndex || 0) == multiTabIndex);
|
||||
const reopenClosedTab = () => {
|
||||
const lastClosedTabId = getOpenedTabs()
|
||||
.filter(x => x.closedTime)
|
||||
.sort((a, b) => b.closedTime - a.closedTime)[0]?.tabid;
|
||||
|
||||
if (!lastClosedTabId) return;
|
||||
|
||||
openedTabs.update(x =>
|
||||
x.map(tab =>
|
||||
tab.tabid === lastClosedTabId ? { ...tab, selected: true, closedTime: null } : { ...tab, selected: false }
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
function getTabDbName(tab, connectionList) {
|
||||
if (tab.tabComponent == 'ConnectionTab') return 'Connections';
|
||||
@@ -223,12 +236,12 @@
|
||||
id: 'tabs.closeTab',
|
||||
category: 'Tabs',
|
||||
name: 'Close tab',
|
||||
keyText: isElectronAvailable() ? 'CtrlOrCommand+W' : null,
|
||||
keyText: isElectronAvailable() ? 'CtrlOrCommand+W' : 'CtrlOrCommand+Shift+W',
|
||||
testEnabled: () => {
|
||||
const hasAnyOtherTab = getOpenedTabs().filter(x => !x.closedTime).length >= 1;
|
||||
const hasAnyModalOpen = getOpenedModals().length > 0;
|
||||
|
||||
return hasAnyOtherTab && !hasConfirmModalOpen;
|
||||
return hasAnyOtherTab && !hasAnyModalOpen;
|
||||
},
|
||||
onClick: closeCurrentTab,
|
||||
});
|
||||
@@ -249,6 +262,15 @@
|
||||
onClick: closeTabsButCurrentDb,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.reopenClosedTab',
|
||||
category: 'Tabs',
|
||||
name: 'Reopen closed tab',
|
||||
keyText: 'CtrlOrCommand+Shift+T',
|
||||
testEnabled: () => getOpenedTabs().filter(x => x.closedTime).length >= 1,
|
||||
onClick: reopenClosedTab,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.addToFavorites',
|
||||
category: 'Tabs',
|
||||
@@ -301,6 +323,7 @@
|
||||
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
||||
import SwitchDatabaseModal from '../modals/SwitchDatabaseModal.svelte';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { handleAfterTabClick } from '../utility/changeCurrentDbByTab';
|
||||
|
||||
export let multiTabIndex;
|
||||
export let shownTab;
|
||||
@@ -340,6 +363,7 @@
|
||||
return;
|
||||
}
|
||||
setSelectedTab(tabid);
|
||||
handleAfterTabClick();
|
||||
};
|
||||
|
||||
const handleMouseDown = (e, tabid) => {
|
||||
@@ -355,6 +379,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleDoubleClick = (e, tabid) => {
|
||||
e.preventDefault();
|
||||
|
||||
openedTabs.update(tabs =>
|
||||
tabs.map(x => ({
|
||||
...x,
|
||||
tabPreviewMode: x.tabid == tabid ? false : x.tabPreviewMode,
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
const getContextMenu = tab => () => {
|
||||
const { tabid, props, tabComponent, appObject, appObjectData } = tab;
|
||||
|
||||
@@ -574,9 +609,11 @@
|
||||
class:selected={$draggingTab || $draggingDbGroup
|
||||
? tab.tabid == $draggingTabTarget?.tabid
|
||||
: tab.tabid == shownTab?.tabid}
|
||||
class:preview={!!tab.tabPreviewMode}
|
||||
on:click={e => handleTabClick(e, tab.tabid)}
|
||||
on:mousedown={e => handleMouseDown(e, tab.tabid)}
|
||||
on:mouseup={e => handleMouseUp(e, tab.tabid)}
|
||||
on:dblclick={e => handleDoubleClick(e, tab.tabid)}
|
||||
use:contextMenu={getContextMenu(tab)}
|
||||
draggable={true}
|
||||
on:dragstart={async e => {
|
||||
@@ -720,6 +757,9 @@
|
||||
.file-tab-item.selected {
|
||||
background-color: var(--theme-bg-0);
|
||||
}
|
||||
.file-tab-item.preview {
|
||||
font-style: italic;
|
||||
}
|
||||
.file-name {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
export let tabid;
|
||||
export let savedFile;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('FavoriteEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('JsonEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
|
||||
let jslid = null;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('JsonLinesEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('MarkdownEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
},
|
||||
];
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
const timerLabel = useTimerLabel();
|
||||
|
||||
let busy = false;
|
||||
@@ -182,7 +182,7 @@
|
||||
invalidateCommands();
|
||||
}
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<AceEditor
|
||||
mode={driver?.editorMode || 'text'}
|
||||
mode={driver?.editorMode || 'sql'}
|
||||
value={$editorState.value || ''}
|
||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||
options={{
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
const timerLabel = useTimerLabel();
|
||||
|
||||
let runnerId;
|
||||
@@ -82,7 +82,7 @@
|
||||
invalidateCommands();
|
||||
}
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('SqlObjectTab');
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlObject.find',
|
||||
category: 'SQL Object',
|
||||
name: 'Find',
|
||||
keyText: 'CtrlOrCommand+F',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().find(),
|
||||
});
|
||||
|
||||
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName', 'objectTypeField'];
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
import AceEditor from '../query/AceEditor.svelte';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { extensions } from '../stores';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import applyScriptTemplate, { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import { changeTab } from '../utility/common';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { template } from 'lodash';
|
||||
|
||||
export let tabid;
|
||||
export let appObjectData;
|
||||
export let scriptTemplate;
|
||||
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let objectTypeField;
|
||||
|
||||
$: appObjectData = {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
};
|
||||
|
||||
$: defaultScriptTemplate = getSupportedScriptTemplates(appObjectData.objectTypeField)?.[0]?.scriptTemplate;
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: driver = findEngineDriver($connection, $extensions);
|
||||
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('SqlObjectTab', false);
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
export function find() {
|
||||
domEditor.getEditor().execCommand('find');
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
return [{ command: 'sqlObject.find' }];
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
{#await applyScriptTemplate(scriptTemplate ?? defaultScriptTemplate, $extensions, appObjectData)}
|
||||
<LoadingInfo message="Loading script..." />
|
||||
{:then sql}
|
||||
<AceEditor
|
||||
value={sql || ''}
|
||||
readOnly
|
||||
menu={createMenu()}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
mode={driver?.editorMode || 'sql'}
|
||||
/>
|
||||
{/await}
|
||||
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<SelectField
|
||||
isNative
|
||||
value={scriptTemplate ?? defaultScriptTemplate}
|
||||
options={getSupportedScriptTemplates(appObjectData.objectTypeField).map(x => ({
|
||||
label: x.label,
|
||||
value: x.scriptTemplate,
|
||||
}))}
|
||||
on:change={e => {
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
props: {
|
||||
...tab.props,
|
||||
scriptTemplate: e.detail,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
{#if objectTypeField == 'tables' || objectTypeField == 'views' || objectTypeField == 'matviews'}
|
||||
<ToolStripButton
|
||||
icon="icon structure"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'openStructure',
|
||||
},
|
||||
});
|
||||
}}>Open structure</ToolStripButton
|
||||
>
|
||||
<ToolStripButton
|
||||
icon="icon table"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: objectTypeField == 'tables' ? 'img table' : 'img view',
|
||||
tabComponent: objectTypeField == 'tables' ? 'TableDataTab' : 'ViewDataTab',
|
||||
objectTypeField,
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
});
|
||||
}}>Open data</ToolStripButton
|
||||
>
|
||||
{/if}
|
||||
{#each getSupportedScriptTemplates(appObjectData.objectTypeField) as template}
|
||||
<ToolStripButton
|
||||
icon="img sql-file"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
objectTypeField: appObjectData.objectTypeField,
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
schemaName,
|
||||
pureName,
|
||||
objectTypeField,
|
||||
initialArgs: { scriptTemplate: template.scriptTemplate },
|
||||
},
|
||||
});
|
||||
}}>{template.label}</ToolStripButton
|
||||
>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
@@ -298,17 +298,39 @@
|
||||
title: pureName,
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField: 'tables',
|
||||
defaultActionId: 'openStructure',
|
||||
},
|
||||
});
|
||||
}}>Open structure</ToolStripButton
|
||||
>
|
||||
|
||||
<ToolStripButton
|
||||
icon="img sql-file"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'SqlObjectTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField: 'tables',
|
||||
defaultActionId: 'showSql',
|
||||
},
|
||||
});
|
||||
}}>Table SQL</ToolStripButton
|
||||
>
|
||||
|
||||
<ToolStripButton
|
||||
icon={$collapsedLeftColumnStore ? 'icon columns-outline' : 'icon columns'}
|
||||
on:click={() => collapsedLeftColumnStore.update(x => !x)}>View columns</ToolStripButton
|
||||
|
||||
@@ -175,24 +175,43 @@
|
||||
<ToolStripCommandButton command="tableEditor.addColumn" />
|
||||
<ToolStripCommandButton command="tableEditor.addIndex" hideDisabled />
|
||||
|
||||
{#if objectTypeField == 'tables'}
|
||||
<ToolStripButton
|
||||
icon="icon table"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img table',
|
||||
tabComponent: 'TableDataTab',
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField: 'tables',
|
||||
},
|
||||
});
|
||||
}}>Open data</ToolStripButton
|
||||
>
|
||||
{/if}
|
||||
<ToolStripButton
|
||||
icon={'icon table'}
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: objectTypeField == 'tables' ? 'img table' : 'img view',
|
||||
tabComponent: objectTypeField == 'tables' ? 'TableDataTab' : 'ViewDataTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
});
|
||||
}}>Open data</ToolStripButton
|
||||
>
|
||||
<ToolStripButton
|
||||
icon="img sql-file"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'SqlObjectTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'showSql',
|
||||
},
|
||||
});
|
||||
}}>Show SQL</ToolStripButton
|
||||
>
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
|
||||
@@ -18,16 +18,24 @@
|
||||
import SqlFormView from '../formview/SqlFormView.svelte';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { extensions } from '../stores';
|
||||
import { useConnectionInfo, useDatabaseInfo, useDatabaseServerVersion, useViewInfo } from '../utility/metadataLoaders';
|
||||
import {
|
||||
useConnectionInfo,
|
||||
useDatabaseInfo,
|
||||
useDatabaseServerVersion,
|
||||
useViewInfo,
|
||||
} from '../utility/metadataLoaders';
|
||||
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
|
||||
import useGridConfig from '../utility/useGridConfig';
|
||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let objectTypeField;
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: viewInfo = useViewInfo({ conid, database, schemaName, pureName });
|
||||
@@ -76,6 +84,46 @@
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandButton command="dataGrid.refresh" />
|
||||
<ToolStripExportButton {quickExportHandlerRef} />
|
||||
|
||||
<ToolStripButton
|
||||
icon="icon structure"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img table-structure',
|
||||
tabComponent: 'TableStructureTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'openStructure',
|
||||
},
|
||||
});
|
||||
}}>Open structure</ToolStripButton
|
||||
>
|
||||
|
||||
<ToolStripButton
|
||||
icon="img sql-file"
|
||||
on:click={() => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'SqlObjectTab',
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
defaultActionId: 'showSql',
|
||||
},
|
||||
});
|
||||
}}>View SQL</ToolStripButton
|
||||
>
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
{/if}
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
const tabFocused: any = getContext('tabFocused');
|
||||
|
||||
export const activator = createActivator('YamlEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
$: if ($tabFocused && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import * as ServerSummaryTab from './ServerSummaryTab.svelte';
|
||||
import * as ProfilerTab from './ProfilerTab.svelte';
|
||||
import * as DataDuplicatorTab from './DataDuplicatorTab.svelte';
|
||||
import * as ImportExportTab from './ImportExportTab.svelte';
|
||||
import * as SqlObjectTab from './SqlObjectTab.svelte';
|
||||
|
||||
import protabs from './index-pro';
|
||||
|
||||
@@ -62,5 +63,6 @@ export default {
|
||||
ProfilerTab,
|
||||
DataDuplicatorTab,
|
||||
ImportExportTab,
|
||||
SqlObjectTab,
|
||||
...protabs,
|
||||
};
|
||||
|
||||
@@ -63,3 +63,81 @@ export default async function applyScriptTemplate(scriptTemplate, extensions, pr
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getSupportedScriptTemplates(objectTypeField: string): { label: string; scriptTemplate: string }[] {
|
||||
switch (objectTypeField) {
|
||||
case 'tables':
|
||||
return [
|
||||
{ label: 'CREATE TABLE', scriptTemplate: 'CREATE TABLE' },
|
||||
{ label: 'SELECT', scriptTemplate: 'SELECT' },
|
||||
];
|
||||
case 'views':
|
||||
return [
|
||||
{
|
||||
label: 'CREATE VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'ALTER VIEW',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
];
|
||||
case 'matviews':
|
||||
return [
|
||||
{
|
||||
label: 'CREATE MATERIALIZED VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'ALTER MATERIALIZED VIEW',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
];
|
||||
|
||||
case 'procedures':
|
||||
return [
|
||||
{
|
||||
label: 'CREATE PROCEDURE',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'ALTER PROCEDURE',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'EXECUTE',
|
||||
scriptTemplate: 'EXECUTE PROCEDURE',
|
||||
},
|
||||
];
|
||||
|
||||
case 'functions':
|
||||
return [
|
||||
{
|
||||
label: 'CREATE FUNCTION',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: ' ALTER FUNCTION',
|
||||
scriptTemplate: 'ALTER OBJECT',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,37 +1,94 @@
|
||||
import _ from 'lodash';
|
||||
import { currentDatabase, getCurrentDatabase, getLockedDatabaseMode, openedTabs } from '../stores';
|
||||
import {
|
||||
currentDatabase,
|
||||
focusedConnectionOrDatabase,
|
||||
getActiveTab,
|
||||
getCurrentDatabase,
|
||||
getLockedDatabaseMode,
|
||||
openedTabs,
|
||||
selectedDatabaseObjectAppObject,
|
||||
} from '../stores';
|
||||
import { shouldShowTab } from '../tabpanel/TabsPanel.svelte';
|
||||
import { callWhenAppLoaded, getAppLoaded } from './appLoadManager';
|
||||
import { getConnectionInfo } from './metadataLoaders';
|
||||
import { switchCurrentDatabase } from './common';
|
||||
|
||||
let lastCurrentTab = null;
|
||||
// let lastCurrentTab = null;
|
||||
|
||||
openedTabs.subscribe(value => {
|
||||
const newCurrentTab = (value || []).find(x => x.selected);
|
||||
if (newCurrentTab == lastCurrentTab) return;
|
||||
if (getLockedDatabaseMode() && getCurrentDatabase()) return;
|
||||
// openedTabs.subscribe(value => {
|
||||
// const newCurrentTab = (value || []).find(x => x.selected);
|
||||
// if (newCurrentTab == lastCurrentTab) return;
|
||||
// if (getLockedDatabaseMode() && getCurrentDatabase()) return;
|
||||
|
||||
const lastTab = lastCurrentTab;
|
||||
lastCurrentTab = newCurrentTab;
|
||||
// if (lastTab?.tabComponent == 'ConnectionTab') return;
|
||||
// const lastTab = lastCurrentTab;
|
||||
// lastCurrentTab = newCurrentTab;
|
||||
// // if (lastTab?.tabComponent == 'ConnectionTab') return;
|
||||
|
||||
if (newCurrentTab) {
|
||||
const { conid, database } = newCurrentTab.props || {};
|
||||
if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
|
||||
const doWork = async () => {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
switchCurrentDatabase({
|
||||
connection,
|
||||
name: database,
|
||||
});
|
||||
};
|
||||
callWhenAppLoaded(doWork);
|
||||
}
|
||||
// if (newCurrentTab) {
|
||||
// const { conid, database } = newCurrentTab.props || {};
|
||||
// if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
|
||||
// const doWork = async () => {
|
||||
// const connection = await getConnectionInfo({ conid });
|
||||
// switchCurrentDatabase({
|
||||
// connection,
|
||||
// name: database,
|
||||
// });
|
||||
// };
|
||||
// callWhenAppLoaded(doWork);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
export async function handleAfterTabClick() {
|
||||
const currentTab = getActiveTab();
|
||||
const { conid, database, objectTypeField, pureName, schemaName, defaultActionId } = currentTab?.props || {};
|
||||
const db = getCurrentDatabase();
|
||||
if (conid && database && (conid != db?.connection?._id || database != db?.name)) {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
switchCurrentDatabase({
|
||||
connection,
|
||||
name: database,
|
||||
});
|
||||
// const doWork = async () => {
|
||||
// const connection = await getConnectionInfo({ conid });
|
||||
// switchCurrentDatabase({
|
||||
// connection,
|
||||
// name: database,
|
||||
// });
|
||||
// };
|
||||
// callWhenAppLoaded(doWork);
|
||||
}
|
||||
});
|
||||
|
||||
if (conid && database && objectTypeField && pureName && defaultActionId) {
|
||||
selectedDatabaseObjectAppObject.set({
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
pureName,
|
||||
schemaName,
|
||||
});
|
||||
}
|
||||
|
||||
// focus current tab
|
||||
openedTabs.update(tabs => {
|
||||
return tabs.map(tab => ({
|
||||
...tab,
|
||||
focused: !!tab.selected && !tab.closedTime,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
currentDatabase.subscribe(currentDb => {
|
||||
if (currentDb) {
|
||||
focusedConnectionOrDatabase.set({
|
||||
conid: currentDb.connection?._id,
|
||||
database: currentDb.name,
|
||||
connection: currentDb.connection,
|
||||
});
|
||||
} else {
|
||||
focusedConnectionOrDatabase.set(null);
|
||||
}
|
||||
|
||||
if (!getLockedDatabaseMode()) return;
|
||||
if (!currentDb && !getAppLoaded()) return;
|
||||
openedTabs.update(tabs => {
|
||||
|
||||
@@ -23,17 +23,32 @@ export function changeTab(tabid, changeFunc) {
|
||||
export function markTabUnsaved(tabid) {
|
||||
const tab = getOpenedTabs().find(x => x.tabid == tabid);
|
||||
if (tab.unsaved) return;
|
||||
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: true } : tab)));
|
||||
openedTabs.update(files =>
|
||||
files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: true, tabPreviewMode: false } : tab))
|
||||
);
|
||||
}
|
||||
|
||||
export function markTabSaved(tabid) {
|
||||
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: false } : tab)));
|
||||
}
|
||||
|
||||
export function setSelectedTabFunc(files, tabid) {
|
||||
export function setSelectedTabFunc(files, tabid, additionalProps = {}) {
|
||||
return [
|
||||
...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })),
|
||||
...(files || []).filter(x => x.tabid == tabid).map(x => ({ ...x, selected: true })),
|
||||
...(files || [])
|
||||
.filter(x => x.tabid != tabid)
|
||||
.map(x => ({
|
||||
...x,
|
||||
selected: false,
|
||||
focused: false,
|
||||
})),
|
||||
...(files || [])
|
||||
.filter(x => x.tabid == tabid)
|
||||
.map(x => ({
|
||||
...x,
|
||||
selected: true,
|
||||
focused: false,
|
||||
...additionalProps,
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
|
||||
let existing = null;
|
||||
|
||||
const { savedFile, savedFolder, savedFilePath } = newTab.props || {};
|
||||
const { tabPreviewMode } = newTab;
|
||||
if (savedFile || savedFilePath) {
|
||||
existing = oldTabs.find(
|
||||
x =>
|
||||
@@ -49,7 +50,9 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
openedTabs.update(tabs => setSelectedTabFunc(tabs, existing.tabid));
|
||||
openedTabs.update(tabs =>
|
||||
setSelectedTabFunc(tabs, existing.tabid, !tabPreviewMode ? { tabPreviewMode: false } : {})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,8 +95,14 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
|
||||
items.push(newItem);
|
||||
}
|
||||
|
||||
const filesFiltered = tabPreviewMode ? (files || []).filter(x => !x.tabPreviewMode) : files;
|
||||
|
||||
return [
|
||||
...(files || []).map(x => ({ ...x, selected: false, tabOrder: _.findIndex(items, y => y.tabid == x.tabid) })),
|
||||
...(filesFiltered || []).map(x => ({
|
||||
...x,
|
||||
selected: false,
|
||||
tabOrder: _.findIndex(items, y => y.tabid == x.tabid),
|
||||
})),
|
||||
{
|
||||
...newTab,
|
||||
tabid,
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import keycodes from '../utility/keycodes';
|
||||
import _ from 'lodash';
|
||||
import { sleep } from '../utility/common';
|
||||
|
||||
export let list;
|
||||
export let selectedObjectStore;
|
||||
export let getSelectedObject;
|
||||
export let selectedObjectMatcher;
|
||||
export let handleObjectClick;
|
||||
|
||||
export let onScrollTop = null;
|
||||
export let onFocusFilterBox = null;
|
||||
export let getDefaultFocusedItem = null;
|
||||
|
||||
let isListFocused = false;
|
||||
let domDiv = null;
|
||||
export let hideContent = false;
|
||||
|
||||
function handleKeyDown(ev) {
|
||||
const listInstance = _.isFunction(list) ? list() : list;
|
||||
|
||||
function selectByDiff(diff) {
|
||||
const selected = getSelectedObject();
|
||||
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
|
||||
|
||||
if (index == 0 && diff < 0) {
|
||||
onFocusFilterBox?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
let newIndex = index + diff;
|
||||
if (newIndex >= listInstance.length) {
|
||||
newIndex = listInstance.length - 1;
|
||||
}
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
}
|
||||
|
||||
if (listInstance[newIndex]) {
|
||||
selectedObjectStore.set(listInstance[newIndex]);
|
||||
handleObjectClick?.(listInstance[newIndex], { tabPreviewMode: true });
|
||||
}
|
||||
|
||||
if (newIndex == 0) {
|
||||
onScrollTop?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ev.keyCode == keycodes.upArrow) {
|
||||
selectByDiff(-1);
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.downArrow) {
|
||||
selectByDiff(1);
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.enter) {
|
||||
handleObjectClick?.(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.pageDown) {
|
||||
selectByDiff(10);
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.pageUp) {
|
||||
selectByDiff(-10);
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.home) {
|
||||
if (listInstance[0]) {
|
||||
selectedObjectStore.set(listInstance[0]);
|
||||
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
|
||||
onScrollTop?.();
|
||||
}
|
||||
}
|
||||
if (ev.keyCode == keycodes.end) {
|
||||
if (listInstance[listInstance.length - 1]) {
|
||||
selectedObjectStore.set(listInstance[listInstance.length - 1]);
|
||||
handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!ev.ctrlKey &&
|
||||
!ev.altKey &&
|
||||
!ev.metaKey &&
|
||||
((ev.keyCode >= keycodes.a && ev.keyCode <= keycodes.z) ||
|
||||
(ev.keyCode >= keycodes.n0 && ev.keyCode <= keycodes.n9) ||
|
||||
(ev.keyCode >= keycodes.numPad0 && ev.keyCode <= keycodes.numPad9) ||
|
||||
ev.keyCode == keycodes.dash)
|
||||
) {
|
||||
const text = ev.key;
|
||||
onFocusFilterBox?.(text);
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
export function focusFirst() {
|
||||
const listInstance = _.isFunction(list) ? list() : list;
|
||||
|
||||
domDiv?.focus();
|
||||
if (listInstance[0]) {
|
||||
selectedObjectStore.set(listInstance[0]);
|
||||
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
|
||||
onScrollTop?.();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFocus() {
|
||||
isListFocused = true;
|
||||
// await tick();
|
||||
await sleep(100);
|
||||
// console.log('ON FOCUS AFTER SLEEP');
|
||||
const listInstance = _.isFunction(list) ? list() : list;
|
||||
const selected = getSelectedObject();
|
||||
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
|
||||
if (index < 0) {
|
||||
const focused = getDefaultFocusedItem?.();
|
||||
if (focused) {
|
||||
const index2 = _.findIndex(listInstance, x => selectedObjectMatcher(x, focused));
|
||||
if (index2 >= 0) {
|
||||
selectedObjectStore.set(focused);
|
||||
handleObjectClick?.(focused, { tabPreviewMode: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
focusFirst();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
tabindex="0"
|
||||
on:keydown={handleKeyDown}
|
||||
class="wrapper"
|
||||
class:app-object-list-focused={isListFocused}
|
||||
on:focus={handleFocus}
|
||||
on:blur={() => {
|
||||
isListFocused = false;
|
||||
}}
|
||||
bind:this={domDiv}
|
||||
class:hideContent
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hideContent {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -16,9 +16,13 @@
|
||||
openedTabs,
|
||||
emptyConnectionGroupNames,
|
||||
collapsedConnectionGroupNames,
|
||||
focusedConnectionOrDatabase,
|
||||
getFocusedConnectionOrDatabase,
|
||||
currentDatabase,
|
||||
getCurrentConfig,
|
||||
} from '../stores';
|
||||
import runCommand from '../commands/runCommand';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
@@ -29,11 +33,21 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import AppObjectListHandler from './AppObjectListHandler.svelte';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
|
||||
const connections = useConnectionList();
|
||||
const serverStatus = useServerStatus();
|
||||
|
||||
export let passProps: any = {};
|
||||
|
||||
let filter = '';
|
||||
let domListHandler;
|
||||
let domContainer = null;
|
||||
let domFilter = null;
|
||||
|
||||
$: connectionsWithStatus =
|
||||
$connections && $serverStatus
|
||||
@@ -47,12 +61,53 @@
|
||||
x => !x.unsaved || $openedConnections.includes(x._id) || $openedSingleDatabaseConnections.includes(x._id)
|
||||
);
|
||||
|
||||
$: connectionsWithParent = connectionsWithStatusFiltered
|
||||
? connectionsWithStatusFiltered?.filter(x => x.parent !== undefined && x.parent !== null && x.parent.length !== 0)
|
||||
: [];
|
||||
$: connectionsWithoutParent = connectionsWithStatusFiltered
|
||||
? connectionsWithStatusFiltered?.filter(x => x.parent === undefined || x.parent === null || x.parent.length === 0)
|
||||
: [];
|
||||
$: connectionsWithParent = _.sortBy(
|
||||
connectionsWithStatusFiltered
|
||||
? connectionsWithStatusFiltered?.filter(x => x.parent !== undefined && x.parent !== null && x.parent.length !== 0)
|
||||
: [],
|
||||
connection => (getConnectionLabel(connection) || '').toUpperCase()
|
||||
);
|
||||
$: connectionsWithoutParent = _.sortBy(
|
||||
connectionsWithStatusFiltered
|
||||
? connectionsWithStatusFiltered?.filter(x => x.parent === undefined || x.parent === null || x.parent.length === 0)
|
||||
: [],
|
||||
connection => (getConnectionLabel(connection) || '').toUpperCase()
|
||||
);
|
||||
|
||||
function getFocusFlatList() {
|
||||
const expanded = $expandedConnections;
|
||||
const opened = $openedConnections;
|
||||
|
||||
const res = [];
|
||||
for (const con of [...connectionsWithParent, ...connectionsWithoutParent]) {
|
||||
const databases = getLocalStorage(`database_list_${con._id}`) || [];
|
||||
if (!filterName(filter, con.displayName, con.server, ...databases.map(x => x.name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.push({
|
||||
connection: con,
|
||||
conid: con._id,
|
||||
database: con.singleDatabase ? con.defaultDatabase : null,
|
||||
});
|
||||
|
||||
if ((expanded.includes(con._id) && opened.includes(con._id)) || filter) {
|
||||
for (const db of _.sortBy(databases, x => x.sortOrder ?? x.name)) {
|
||||
if (!filterName(filter, con.displayName, con.server, db.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.push({
|
||||
conid: con._id,
|
||||
database: db.name,
|
||||
connection: con,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const handleRefreshConnections = () => {
|
||||
for (const conid of $openedConnections) {
|
||||
@@ -112,7 +167,14 @@
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search connection or database" bind:value={filter} />
|
||||
<SearchInput
|
||||
placeholder="Search connection or database"
|
||||
bind:value={filter}
|
||||
bind:this={domFilter}
|
||||
onFocusFilteredList={() => {
|
||||
domListHandler?.focusFirst();
|
||||
}}
|
||||
/>
|
||||
<CloseSearchButton bind:filter />
|
||||
{#if $commandsCustomized['new.connection']?.enabled}
|
||||
<InlineButton on:click={() => runCommand('new.connection')} title="Add new connection">
|
||||
@@ -127,6 +189,7 @@
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer
|
||||
bind:this={domContainer}
|
||||
on:drop={e => {
|
||||
var data = e.dataTransfer.getData('app_object_drag_data');
|
||||
if (data) {
|
||||
@@ -134,43 +197,95 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={_.sortBy(connectionsWithParent, connection => (getConnectionLabel(connection) || '').toUpperCase())}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
list={getFocusFlatList}
|
||||
selectedObjectStore={focusedConnectionOrDatabase}
|
||||
getSelectedObject={getFocusedConnectionOrDatabase}
|
||||
selectedObjectMatcher={(o1, o2) => o1?.conid == o2?.conid && o1?.database == o2?.database}
|
||||
getDefaultFocusedItem={() =>
|
||||
$currentDatabase
|
||||
? {
|
||||
conid: $currentDatabase?.connection?._id,
|
||||
database: $currentDatabase?.name,
|
||||
connection: $currentDatabase?.connection,
|
||||
}
|
||||
: null}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
groupIconFunc={chevronExpandIcon}
|
||||
groupFunc={data => data.parent}
|
||||
expandIconFunc={plusExpandIcon}
|
||||
onDropOnGroup={handleDropOnGroup}
|
||||
emptyGroupNames={$emptyConnectionGroupNames}
|
||||
sortGroups
|
||||
groupContextMenu={createGroupContextMenu}
|
||||
collapsedGroupNames={collapsedConnectionGroupNames}
|
||||
/>
|
||||
{#if (connectionsWithParent?.length > 0 && connectionsWithoutParent?.length > 0) || ($emptyConnectionGroupNames.length > 0 && connectionsWithoutParent?.length > 0)}
|
||||
<div class="br" />
|
||||
{/if}
|
||||
<AppObjectList
|
||||
list={_.sortBy(connectionsWithoutParent, connection => (getConnectionLabel(connection) || '').toUpperCase())}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
/>
|
||||
handleObjectClick={(data, options) => {
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
if ($openedSingleDatabaseConnections.includes(data.conid)) {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
} else {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
// console.log('FOCUSING DB', passProps);
|
||||
// passProps?.onFocusSqlObjectList?.();
|
||||
}
|
||||
} else {
|
||||
if (options.focusTab) {
|
||||
openConnection(data.connection);
|
||||
} else {
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal == false && !config.storageDatabase) {
|
||||
openNewTab({
|
||||
title: getConnectionLabel(data.connection),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
tabPreviewMode: options.tabPreviewMode,
|
||||
props: {
|
||||
conid: data.conid,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={connectionsWithParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={() => SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
groupIconFunc={chevronExpandIcon}
|
||||
groupFunc={data => data.parent}
|
||||
expandIconFunc={plusExpandIcon}
|
||||
onDropOnGroup={handleDropOnGroup}
|
||||
emptyGroupNames={$emptyConnectionGroupNames}
|
||||
sortGroups
|
||||
groupContextMenu={createGroupContextMenu}
|
||||
collapsedGroupNames={collapsedConnectionGroupNames}
|
||||
/>
|
||||
{#if (connectionsWithParent?.length > 0 && connectionsWithoutParent?.length > 0) || ($emptyConnectionGroupNames.length > 0 && connectionsWithoutParent?.length > 0)}
|
||||
<div class="br" />
|
||||
{/if}
|
||||
<AppObjectList
|
||||
list={connectionsWithoutParent}
|
||||
module={connectionAppObject}
|
||||
subItemsComponent={() => SubDatabaseList}
|
||||
expandOnClick
|
||||
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
|
||||
{filter}
|
||||
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
|
||||
}}
|
||||
/>
|
||||
</AppObjectListHandler>
|
||||
{#if $connections && !$connections.find(x => !x.unsaved) && $openedConnections.length == 0 && $commandsCustomized['new.connection']?.enabled && !$openedTabs.find(x => !x.closedTime && x.tabComponent == 'ConnectionTab' && !x.props?.conid)}
|
||||
<LargeButton icon="icon new-connection" on:click={() => runCommand('new.connection')} fillHorizontal
|
||||
>Add new connection</LargeButton
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { currentDatabase, extensions, pinnedDatabases, pinnedTables } from '../stores';
|
||||
import {
|
||||
currentDatabase,
|
||||
extensions,
|
||||
pinnedDatabases,
|
||||
pinnedTables,
|
||||
} from '../stores';
|
||||
import { useConfig, useConnectionInfo } from '../utility/metadataLoaders';
|
||||
|
||||
import ConnectionList from './ConnectionList.svelte';
|
||||
@@ -14,8 +19,10 @@
|
||||
import DbKeysTree from './DbKeysTree.svelte';
|
||||
import SingleConnectionDatabaseList from './SingleConnectionDatabaseList.svelte';
|
||||
import _ from 'lodash';
|
||||
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
|
||||
|
||||
export let hidden = false;
|
||||
let domSqlObjectList = null;
|
||||
|
||||
$: conid = $currentDatabase?.connection?._id;
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
@@ -32,7 +39,7 @@
|
||||
</WidgetColumnBarItem>
|
||||
{:else if !$config?.singleDbConnection}
|
||||
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
|
||||
<ConnectionList />
|
||||
<ConnectionList passProps={{ onFocusSqlObjectList: () => domSqlObjectList.focus() }} />
|
||||
</WidgetColumnBarItem>
|
||||
{/if}
|
||||
<WidgetColumnBarItem
|
||||
@@ -48,7 +55,7 @@
|
||||
|
||||
<WidgetColumnBarItem
|
||||
title={driver?.databaseEngineTypes?.includes('document')
|
||||
? driver?.collectionPluralLabel ?? 'Collections/containers'
|
||||
? (driver?.collectionPluralLabel ?? 'Collections/containers')
|
||||
: 'Tables, views, functions'}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
@@ -58,7 +65,7 @@
|
||||
(driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document'))
|
||||
)}
|
||||
>
|
||||
<SqlObjectList {conid} {database} />
|
||||
<SqlObjectList {conid} {database} bind:this={domSqlObjectList} />
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
<WidgetColumnBarItem
|
||||
@@ -77,6 +84,8 @@
|
||||
skip={conid && (database || singleDatabase)}
|
||||
>
|
||||
<WidgetsInnerContainer>
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
|
||||
<ErrorInfo message="Database not selected" icon="img alert" />
|
||||
</WidgetsInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import AddDbKeyModal from '../modals/AddDbKeyModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { currentDatabase, getExtensions } from '../stores';
|
||||
import { currentDatabase, focusedConnectionOrDatabase, getExtensions } from '../stores';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
||||
|
||||
import DbKeysSubTree from './DbKeysSubTree.svelte';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -50,6 +51,13 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$: differentFocusedDb =
|
||||
$focusedConnectionOrDatabase &&
|
||||
($focusedConnectionOrDatabase.conid != conid ||
|
||||
($focusedConnectionOrDatabase?.database && $focusedConnectionOrDatabase?.database != database));
|
||||
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
@@ -62,6 +70,9 @@
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
{#if differentFocusedDb}
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
{/if}
|
||||
<WidgetsInnerContainer hideContent={differentFocusedDb}>
|
||||
<DbKeysSubTree {conid} {database} root="" {reloadToken} connection={$currentDatabase?.connection} {filter} />
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { focusedConnectionOrDatabase, openedConnections } from '../stores';
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let connection;
|
||||
</script>
|
||||
|
||||
<div class="no-focused-info">
|
||||
{#if $focusedConnectionOrDatabase?.database}
|
||||
{#if database}
|
||||
<div class="m-1">Current database:</div>
|
||||
<div class="m-1 ml-3 mb-3">
|
||||
<b>{database}</b>
|
||||
</div>
|
||||
{/if}
|
||||
<FormStyledButton
|
||||
value={`Switch to ${$focusedConnectionOrDatabase?.database}`}
|
||||
skipWidth
|
||||
on:click={() =>
|
||||
switchCurrentDatabase({
|
||||
connection: $focusedConnectionOrDatabase?.connection,
|
||||
name: $focusedConnectionOrDatabase?.database,
|
||||
})}
|
||||
/>
|
||||
{#if database}
|
||||
<FormStyledButton
|
||||
value={`Show ${database}`}
|
||||
skipWidth
|
||||
outline
|
||||
on:click={() => {
|
||||
$focusedConnectionOrDatabase = {
|
||||
conid,
|
||||
database,
|
||||
connection,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if connection}
|
||||
<div class="m-1">Current connection:</div>
|
||||
<div class="m-1 ml-3 mb-3">
|
||||
<b>{getConnectionLabel(connection)}</b>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !$openedConnections.includes($focusedConnectionOrDatabase?.conid) && $focusedConnectionOrDatabase?.conid}
|
||||
<FormStyledButton
|
||||
value={`Connect to ${getConnectionLabel($focusedConnectionOrDatabase?.connection)}`}
|
||||
skipWidth
|
||||
on:click={() => openConnection($focusedConnectionOrDatabase?.connection)}
|
||||
/>
|
||||
{/if}
|
||||
{#if connection}
|
||||
<FormStyledButton
|
||||
value={`Show ${getConnectionLabel(connection)}`}
|
||||
skipWidth
|
||||
outline
|
||||
on:click={() => {
|
||||
$focusedConnectionOrDatabase = {
|
||||
conid,
|
||||
connection,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.no-focused-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -5,28 +5,90 @@
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||
import SubDatabaseList from '../appobj/SubDatabaseList.svelte';
|
||||
import { openedConnections } from '../stores';
|
||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import AppObjectListHandler from './AppObjectListHandler.svelte';
|
||||
import { useDatabaseList } from '../utility/metadataLoaders';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { currentDatabase, focusedConnectionOrDatabase, getFocusedConnectionOrDatabase } from '../stores';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
|
||||
export let connection;
|
||||
|
||||
let filter = '';
|
||||
|
||||
let domListHandler;
|
||||
let domContainer = null;
|
||||
let domFilter = null;
|
||||
|
||||
$: databases = useDatabaseList({ conid: connection?._id });
|
||||
|
||||
const handleRefreshDatabases = () => {
|
||||
apiCall('server-connections/refresh', { conid: connection._id });
|
||||
};
|
||||
|
||||
function getFocusFlatList() {
|
||||
const res = [];
|
||||
for (const db of $databases) {
|
||||
if (!filterName(filter, db.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.push({
|
||||
connection,
|
||||
conid: connection._id,
|
||||
database: db.name,
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search connection or database" bind:value={filter} />
|
||||
<SearchInput
|
||||
placeholder="Search connection or database"
|
||||
bind:value={filter}
|
||||
bind:this={domFilter}
|
||||
onFocusFilteredList={() => {
|
||||
domListHandler?.focusFirst();
|
||||
}}
|
||||
/>
|
||||
<CloseSearchButton bind:filter />
|
||||
<InlineButton on:click={handleRefreshDatabases} title="Refresh database list">
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
<SubDatabaseList data={connection} {filter} passProps={{}} />
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
list={getFocusFlatList}
|
||||
selectedObjectStore={focusedConnectionOrDatabase}
|
||||
getSelectedObject={getFocusedConnectionOrDatabase}
|
||||
selectedObjectMatcher={(o1, o2) => o1?.conid == o2?.conid && o1?.database == o2?.database}
|
||||
getDefaultFocusedItem={() =>
|
||||
$currentDatabase
|
||||
? {
|
||||
conid: $currentDatabase?.connection?._id,
|
||||
database: $currentDatabase?.name,
|
||||
connection: $currentDatabase?.connection,
|
||||
}
|
||||
: null}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleObjectClick={(data, options) => {
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SubDatabaseList data={connection} {filter} passProps={{}} />
|
||||
</AppObjectListHandler>
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -36,18 +36,31 @@
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { currentDatabase, extensions } from '../stores';
|
||||
import {
|
||||
currentDatabase,
|
||||
extensions,
|
||||
focusedConnectionOrDatabase,
|
||||
getSelectedDatabaseObjectAppObject,
|
||||
selectedDatabaseObjectAppObject,
|
||||
} from '../stores';
|
||||
import newQuery from '../query/newQuery';
|
||||
import runCommand from '../commands/runCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
import SchemaSelector from './SchemaSelector.svelte';
|
||||
import { appliedCurrentSchema } from '../stores';
|
||||
import AppObjectListHandler from './AppObjectListHandler.svelte';
|
||||
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
|
||||
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
|
||||
import SubProcedureParamList from '../appobj/SubProcedureParamList.svelte';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
|
||||
let filter = '';
|
||||
let domContainer = null;
|
||||
let domFilter = null;
|
||||
let domListHandler;
|
||||
|
||||
$: objects = useDatabaseInfo({ conid, database });
|
||||
$: status = useDatabaseStatus({ conid, database });
|
||||
@@ -115,10 +128,23 @@
|
||||
if (matcher && !matcher(filter)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
export function focus() {
|
||||
domListHandler?.focusFirst();
|
||||
}
|
||||
|
||||
$: differentFocusedDb =
|
||||
$focusedConnectionOrDatabase &&
|
||||
($focusedConnectionOrDatabase.conid != conid ||
|
||||
($focusedConnectionOrDatabase?.database && $focusedConnectionOrDatabase?.database != database));
|
||||
</script>
|
||||
|
||||
{#if $status && $status.name == 'error'}
|
||||
<WidgetsInnerContainer>
|
||||
{#if differentFocusedDb}
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
{/if}
|
||||
|
||||
<WidgetsInnerContainer hideContent={differentFocusedDb}>
|
||||
<ErrorInfo message={$status.message} icon="img error" />
|
||||
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
</WidgetsInnerContainer>
|
||||
@@ -131,7 +157,11 @@
|
||||
{database}
|
||||
{driver}
|
||||
/>
|
||||
<WidgetsInnerContainer>
|
||||
{#if differentFocusedDb}
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
{/if}
|
||||
|
||||
<WidgetsInnerContainer hideContent={differentFocusedDb}>
|
||||
<ErrorInfo
|
||||
message={`Database ${database} is empty or structure is not loaded, press Refresh button to reload structure`}
|
||||
icon="img alert"
|
||||
@@ -151,7 +181,14 @@
|
||||
</WidgetsInnerContainer>
|
||||
{:else}
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search in tables, objects, # prefix in columns" bind:value={filter} />
|
||||
<SearchInput
|
||||
placeholder="Search in tables, objects, # prefix in columns"
|
||||
bind:value={filter}
|
||||
bind:this={domFilter}
|
||||
onFocusFilteredList={() => {
|
||||
domListHandler?.focusFirst();
|
||||
}}
|
||||
/>
|
||||
<CloseSearchButton bind:filter />
|
||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
|
||||
@@ -168,27 +205,53 @@
|
||||
negativeMarginTop
|
||||
/>
|
||||
|
||||
<WidgetsInnerContainer>
|
||||
{#if differentFocusedDb}
|
||||
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
|
||||
{/if}
|
||||
|
||||
<WidgetsInnerContainer bind:this={domContainer} hideContent={differentFocusedDb}>
|
||||
{#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
|
||||
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||
.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||
subItemsComponent={SubColumnParamList}
|
||||
isExpandable={data =>
|
||||
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
|
||||
expandIconFunc={chevronExpandIcon}
|
||||
{filter}
|
||||
passProps={{
|
||||
showPinnedInsteadOfUnpin: true,
|
||||
connection: $connection,
|
||||
hideSchemaName: !!$appliedCurrentSchema,
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
list={flatFilteredList.map(x => ({ ...x, conid, database }))}
|
||||
selectedObjectStore={selectedDatabaseObjectAppObject}
|
||||
getSelectedObject={getSelectedDatabaseObjectAppObject}
|
||||
selectedObjectMatcher={matchDatabaseObjectAppObject}
|
||||
handleObjectClick={(data, options) => databaseObjectAppObject.handleObjectClick(data, options)}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
/>
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
>
|
||||
<AppObjectList
|
||||
list={objectList
|
||||
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||
.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||
subItemsComponent={data =>
|
||||
data.objectTypeField == 'procedures' || data.objectTypeField == 'functions'
|
||||
? SubProcedureParamList
|
||||
: SubColumnParamList}
|
||||
isExpandable={data =>
|
||||
data.objectTypeField == 'tables' ||
|
||||
data.objectTypeField == 'views' ||
|
||||
data.objectTypeField == 'matviews' ||
|
||||
((data.objectTypeField == 'procedures' || data.objectTypeField == 'functions') &&
|
||||
!!data.parameters?.length)}
|
||||
expandIconFunc={chevronExpandIcon}
|
||||
{filter}
|
||||
passProps={{
|
||||
showPinnedInsteadOfUnpin: true,
|
||||
connection: $connection,
|
||||
hideSchemaName: !!$appliedCurrentSchema,
|
||||
}}
|
||||
/>
|
||||
</AppObjectListHandler>
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<div on:drop><slot /></div>
|
||||
<script lang="ts">
|
||||
let domDiv;
|
||||
|
||||
export let hideContent = false;
|
||||
|
||||
export function scrollTop() {
|
||||
domDiv.scrollTop = 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div on:drop bind:this={domDiv} class:hideContent><slot /></div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
@@ -7,4 +17,8 @@
|
||||
overflow-y: auto;
|
||||
width: var(--dim-left-panel-width);
|
||||
}
|
||||
|
||||
.hideContent {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,18 @@ function simplifyComutedExpression(expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
function getFullDataTypeName({ dataType, charMaxLength, numericScale, numericPrecision }) {
|
||||
let fullDataType = dataType;
|
||||
if (charMaxLength && isTypeString(dataType)) {
|
||||
fullDataType = `${dataType}(${charMaxLength < 0 ? 'MAX' : charMaxLength})`;
|
||||
}
|
||||
if (numericPrecision && numericScale && isTypeNumeric(dataType)) {
|
||||
fullDataType = `${dataType}(${numericPrecision},${numericScale})`;
|
||||
}
|
||||
|
||||
return fullDataType;
|
||||
}
|
||||
|
||||
function getColumnInfo({
|
||||
isNullable,
|
||||
isIdentity,
|
||||
@@ -43,13 +55,12 @@ function getColumnInfo({
|
||||
defaultConstraint,
|
||||
computedExpression,
|
||||
}) {
|
||||
let fullDataType = dataType;
|
||||
if (charMaxLength && isTypeString(dataType)) {
|
||||
fullDataType = `${dataType}(${charMaxLength < 0 ? 'MAX' : charMaxLength})`;
|
||||
}
|
||||
if (numericPrecision && numericScale && isTypeNumeric(dataType)) {
|
||||
fullDataType = `${dataType}(${numericPrecision},${numericScale})`;
|
||||
}
|
||||
const fullDataType = getFullDataTypeName({
|
||||
dataType,
|
||||
charMaxLength,
|
||||
numericPrecision,
|
||||
numericScale,
|
||||
});
|
||||
|
||||
if (defaultValue) {
|
||||
defaultValue = defaultValue.trim();
|
||||
@@ -116,7 +127,11 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
this.feedback({ analysingMessage: 'Loading views' });
|
||||
const viewsRows = await this.analyserQuery('views', ['views']);
|
||||
this.feedback({ analysingMessage: 'Loading procedures & functions' });
|
||||
|
||||
const programmableRows = await this.analyserQuery('programmables', ['procedures', 'functions']);
|
||||
const procedureParameterRows = await this.analyserQuery('proceduresParameters');
|
||||
const functionParameterRows = await this.analyserQuery('functionParameters');
|
||||
|
||||
this.feedback({ analysingMessage: 'Loading view columns' });
|
||||
const viewColumnRows = await this.analyserQuery('viewColumns', ['views']);
|
||||
|
||||
@@ -157,20 +172,46 @@ class MsSqlAnalyser extends DatabaseAnalyser {
|
||||
columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
|
||||
}));
|
||||
|
||||
const procedureParameter = procedureParameterRows.rows.map(row => ({
|
||||
...row,
|
||||
dataType: getFullDataTypeName(row),
|
||||
}));
|
||||
|
||||
const prodceureToParameters = procedureParameter.reduce((acc, parameter) => {
|
||||
if (!acc[parameter.parentObjectId]) acc[parameter.parentObjectId] = [];
|
||||
acc[parameter.parentObjectId].push(parameter);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const procedures = programmableRows.rows
|
||||
.filter(x => x.sqlObjectType.trim() == 'P')
|
||||
.map(row => ({
|
||||
...row,
|
||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
||||
createSql: getCreateSql(row),
|
||||
parameters: prodceureToParameters[row.objectId],
|
||||
}));
|
||||
|
||||
const functionParameters = functionParameterRows.rows.map(row => ({
|
||||
...row,
|
||||
dataType: getFullDataTypeName(row),
|
||||
}));
|
||||
|
||||
const functionToParameters = functionParameters.reduce((acc, parameter) => {
|
||||
if (!acc[parameter.parentObjectId]) acc[parameter.parentObjectId] = [];
|
||||
|
||||
acc[parameter.parentObjectId].push(parameter);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const functions = programmableRows.rows
|
||||
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
|
||||
.map(row => ({
|
||||
...row,
|
||||
contentHash: row.modifyDate && row.modifyDate.toISOString(),
|
||||
createSql: getCreateSql(row),
|
||||
parameters: functionToParameters[row.objectId],
|
||||
}));
|
||||
|
||||
this.feedback({ analysingMessage: null });
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
o.object_id as parentObjectId,
|
||||
p.object_id AS parameterObjectId,
|
||||
o.name as pureName,
|
||||
CASE
|
||||
WHEN p.name IS NULL OR LTRIM(RTRIM(p.name)) = '' THEN
|
||||
'@Output'
|
||||
ELSE
|
||||
p.name
|
||||
END AS parameterName,
|
||||
TYPE_NAME(p.user_type_id) AS dataType,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) = 'nvarchar' THEN p.max_length / 2
|
||||
ELSE p.max_length
|
||||
END AS charMaxLength,
|
||||
CASE
|
||||
WHEN p.is_output = 1 THEN 'OUT'
|
||||
ELSE 'IN'
|
||||
END AS parameterMode,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.precision
|
||||
ELSE NULL
|
||||
END AS numericPrecision,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.scale
|
||||
ELSE NULL
|
||||
END AS numericScale,
|
||||
CASE
|
||||
WHEN p.is_output = 1 THEN 'OUT'
|
||||
ELSE 'IN'
|
||||
END AS parameterMode,
|
||||
p.parameter_id AS parameterIndex,
|
||||
s.name as schemaName
|
||||
FROM
|
||||
sys.objects o
|
||||
JOIN
|
||||
sys.parameters p ON o.object_id = p.object_id
|
||||
INNER JOIN
|
||||
sys.schemas s ON s.schema_id=o.schema_id
|
||||
WHERE
|
||||
o.type IN ('FN', 'IF', 'TF')
|
||||
and o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION
|
||||
ORDER BY
|
||||
p.object_id,
|
||||
p.parameter_id;
|
||||
`;
|
||||
@@ -7,6 +7,8 @@ const modifications = require('./modifications');
|
||||
const loadSqlCode = require('./loadSqlCode');
|
||||
const views = require('./views');
|
||||
const programmables = require('./programmables');
|
||||
const proceduresParameters = require('./proceduresParameters');
|
||||
const functionParameters = require('./functionParameters');
|
||||
const viewColumns = require('./viewColumns');
|
||||
const indexes = require('./indexes');
|
||||
const indexcols = require('./indexcols');
|
||||
@@ -20,6 +22,8 @@ module.exports = {
|
||||
loadSqlCode,
|
||||
views,
|
||||
programmables,
|
||||
proceduresParameters,
|
||||
functionParameters,
|
||||
viewColumns,
|
||||
indexes,
|
||||
indexcols,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
o.object_id as parentObjectId,
|
||||
p.object_id as objectId,
|
||||
o.name as pureName,
|
||||
p.name AS parameterName,
|
||||
TYPE_NAME(p.user_type_id) AS dataType,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) = 'nvarchar' THEN p.max_length / 2
|
||||
ELSE p.max_length
|
||||
END AS charMaxLength,
|
||||
CASE
|
||||
WHEN p.is_output = 1 THEN 'OUT'
|
||||
ELSE 'IN'
|
||||
END AS parameterMode,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.precision
|
||||
ELSE NULL
|
||||
END AS numericPrecision,
|
||||
CASE
|
||||
WHEN TYPE_NAME(p.user_type_id) IN ('numeric', 'decimal') THEN p.scale
|
||||
ELSE NULL
|
||||
END AS numericScale,
|
||||
p.parameter_id AS parameterIndex,
|
||||
s.name as schemaName
|
||||
FROM
|
||||
sys.objects o
|
||||
JOIN
|
||||
sys.parameters p ON o.object_id = p.object_id
|
||||
INNER JOIN
|
||||
sys.schemas s ON s.schema_id=o.schema_id
|
||||
WHERE
|
||||
o.type = 'P'
|
||||
and o.object_id =OBJECT_ID_CONDITION and s.name =SCHEMA_NAME_CONDITION
|
||||
ORDER BY
|
||||
o.object_id,
|
||||
p.parameter_id;
|
||||
`;
|
||||
@@ -15,6 +15,11 @@ function quoteDefaultValue(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeTypeName(typeName) {
|
||||
if (/int\(\d+\)/.test(typeName)) return 'int';
|
||||
return typeName;
|
||||
}
|
||||
|
||||
function getColumnInfo(
|
||||
{
|
||||
isNullable,
|
||||
@@ -60,6 +65,18 @@ function getColumnInfo(
|
||||
};
|
||||
}
|
||||
|
||||
function getParametersSqlString(parameters = []) {
|
||||
if (!parameters?.length) return '';
|
||||
|
||||
return parameters
|
||||
.map(i => {
|
||||
const mode = i.parameterMode ? `${i.parameterMode} ` : '';
|
||||
const dataType = i.dataType ? ` ${i.dataType.toUpperCase()}` : '';
|
||||
return mode + i.parameterName + dataType;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
class Analyser extends DatabaseAnalyser {
|
||||
constructor(dbhan, driver, version) {
|
||||
super(dbhan, driver, version);
|
||||
@@ -114,6 +131,30 @@ class Analyser extends DatabaseAnalyser {
|
||||
this.feedback({ analysingMessage: 'Loading programmables' });
|
||||
const programmables = await this.analyserQuery('programmables', ['procedures', 'functions']);
|
||||
|
||||
const parameters = await this.analyserQuery('parameters', ['procedures', 'functions']);
|
||||
|
||||
const functionParameters = parameters.rows.filter(x => x.routineType == 'FUNCTION');
|
||||
const functionNameToParameters = functionParameters.reduce((acc, row) => {
|
||||
if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
|
||||
|
||||
acc[`${row.schemaName}.${row.pureName}`].push({
|
||||
...row,
|
||||
dataType: normalizeTypeName(row.dataType),
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const procedureParameters = parameters.rows.filter(x => x.routineType == 'PROCEDURE');
|
||||
const procedureNameToParameters = procedureParameters.reduce((acc, row) => {
|
||||
if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
|
||||
|
||||
acc[`${row.schemaName}.${row.pureName}`].push({
|
||||
...row,
|
||||
dataType: normalizeTypeName(row.dataType),
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.feedback({ analysingMessage: 'Loading view texts' });
|
||||
const viewTexts = await this.getViewTexts(views.rows.map(x => x.pureName));
|
||||
this.feedback({ analysingMessage: 'Loading indexes' });
|
||||
@@ -174,20 +215,26 @@ class Analyser extends DatabaseAnalyser {
|
||||
.map(x => _.omit(x, ['objectType']))
|
||||
.map(x => ({
|
||||
...x,
|
||||
createSql: `DELIMITER //\n\nCREATE PROCEDURE \`${x.pureName}\`()\n${x.routineDefinition}\n\nDELIMITER ;\n`,
|
||||
createSql: `DELIMITER //\n\nCREATE PROCEDURE \`${x.pureName}\`(${getParametersSqlString(
|
||||
procedureNameToParameters[`${x.schemaName}.${x.pureName}`]
|
||||
)})\n${x.routineDefinition}\n\nDELIMITER ;\n`,
|
||||
objectId: x.pureName,
|
||||
contentHash: _.isDate(x.modifyDate) ? x.modifyDate.toISOString() : x.modifyDate,
|
||||
parameters: procedureNameToParameters[`${x.schemaName}.${x.pureName}`],
|
||||
})),
|
||||
functions: programmables.rows
|
||||
.filter(x => x.objectType == 'FUNCTION')
|
||||
.map(x => _.omit(x, ['objectType']))
|
||||
.map(x => ({
|
||||
...x,
|
||||
createSql: `CREATE FUNCTION \`${x.pureName}\`()\nRETURNS ${x.returnDataType} ${
|
||||
x.isDeterministic == 'YES' ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'
|
||||
}\n${x.routineDefinition}`,
|
||||
createSql: `CREATE FUNCTION \`${x.pureName}\`(${getParametersSqlString(
|
||||
functionNameToParameters[`${x.schemaName}.${x.pureName}`]?.filter(i => i.parameterMode !== 'RETURN')
|
||||
)})\nRETURNS ${x.returnDataType} ${x.isDeterministic == 'YES' ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}\n${
|
||||
x.routineDefinition
|
||||
}`,
|
||||
objectId: x.pureName,
|
||||
contentHash: _.isDate(x.modifyDate) ? x.modifyDate.toISOString() : x.modifyDate,
|
||||
parameters: functionNameToParameters[`${x.schemaName}.${x.pureName}`],
|
||||
})),
|
||||
};
|
||||
this.feedback({ analysingMessage: null });
|
||||
|
||||
@@ -10,6 +10,7 @@ const procedureModifications = require('./procedureModifications');
|
||||
const functionModifications = require('./functionModifications');
|
||||
const uniqueNames = require('./uniqueNames');
|
||||
const viewTexts = require('./viewTexts');
|
||||
const parameters = require('./parameters');
|
||||
|
||||
module.exports = {
|
||||
columns,
|
||||
@@ -19,6 +20,7 @@ module.exports = {
|
||||
tableModifications,
|
||||
views,
|
||||
programmables,
|
||||
parameters,
|
||||
procedureModifications,
|
||||
functionModifications,
|
||||
indexes,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
r.ROUTINE_SCHEMA AS schemaName,
|
||||
r.SPECIFIC_NAME AS pureName,
|
||||
CASE
|
||||
WHEN COALESCE(NULLIF(PARAMETER_MODE, ''), 'RETURN') = 'RETURN' THEN 'Return'
|
||||
ELSE PARAMETER_NAME
|
||||
END AS parameterName,
|
||||
p.CHARACTER_MAXIMUM_LENGTH AS charMaxLength,
|
||||
p.NUMERIC_PRECISION AS numericPrecision,
|
||||
p.NUMERIC_SCALE AS numericScale,
|
||||
p.DTD_IDENTIFIER AS dataType,
|
||||
COALESCE(NULLIF(PARAMETER_MODE, ''), 'RETURN') AS parameterMode,
|
||||
r.ROUTINE_TYPE AS routineType, -- Function or Procedure
|
||||
p.ORDINAL_POSITION AS ordinalPosition
|
||||
FROM
|
||||
information_schema.PARAMETERS p
|
||||
JOIN
|
||||
information_schema.ROUTINES r
|
||||
ON
|
||||
p.SPECIFIC_NAME = r.SPECIFIC_NAME AND r.ROUTINE_SCHEMA = p.SPECIFIC_SCHEMA
|
||||
WHERE
|
||||
r.ROUTINE_SCHEMA = '#DATABASE#' AND r.ROUTINE_NAME =OBJECT_ID_CONDITION
|
||||
ORDER BY
|
||||
r.ROUTINE_SCHEMA, r.SPECIFIC_NAME, p.ORDINAL_POSITION
|
||||
`;
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = `
|
||||
select
|
||||
ROUTINE_SCHEMA AS schemaName,
|
||||
ROUTINE_NAME as pureName,
|
||||
ROUTINE_TYPE as objectType,
|
||||
COALESCE(LAST_ALTERED, CREATED) as modifyDate,
|
||||
|
||||
@@ -49,6 +49,19 @@ function getColumnInfo(
|
||||
};
|
||||
}
|
||||
|
||||
function getParametersSqlString(parameters = []) {
|
||||
if (!parameters?.length) return '';
|
||||
|
||||
return parameters
|
||||
.map(i => {
|
||||
const mode = i.parameterMode ? `${i.parameterMode} ` : '';
|
||||
const dataType = i.dataType ? ` ${i.dataType.toUpperCase()}` : '';
|
||||
const parameterName = i.parameterName ?? '';
|
||||
return `${mode}${parameterName}${dataType}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
class Analyser extends DatabaseAnalyser {
|
||||
constructor(dbhan, driver, version) {
|
||||
super(dbhan, driver, version);
|
||||
@@ -144,6 +157,9 @@ class Analyser extends DatabaseAnalyser {
|
||||
this.feedback({ analysingMessage: 'Loading routines' });
|
||||
const routines = await this.analyserQuery('routines', ['procedures', 'functions']);
|
||||
|
||||
this.feedback({ analysingMessage: 'Loading routine parameters' });
|
||||
const routineParametersRows = await this.analyserQuery('proceduresParameters');
|
||||
|
||||
this.feedback({ analysingMessage: 'Loading indexes' });
|
||||
const indexes = this.driver.__analyserInternals.skipIndexes
|
||||
? { rows: [] }
|
||||
@@ -191,6 +207,40 @@ class Analyser extends DatabaseAnalyser {
|
||||
columnName: x.column_name,
|
||||
}));
|
||||
|
||||
const procedureParameters = routineParametersRows.rows
|
||||
.filter(i => i.routine_type == 'PROCEDURE')
|
||||
.map(i => ({
|
||||
pureName: i.pure_name,
|
||||
parameterName: i.parameter_name,
|
||||
dataType: normalizeTypeName(i.data_type),
|
||||
parameterMode: i.parameter_mode,
|
||||
schemaName: i.schema_name,
|
||||
}));
|
||||
|
||||
const procedureNameToParameters = procedureParameters.reduce((acc, row) => {
|
||||
if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
|
||||
acc[`${row.schemaName}.${row.pureName}`].push(row);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const functionParameters = routineParametersRows.rows
|
||||
.filter(i => i.routine_type == 'FUNCTION')
|
||||
.map(i => ({
|
||||
pureName: i.pure_name,
|
||||
parameterName: i.parameter_name,
|
||||
dataType: normalizeTypeName(i.data_type),
|
||||
parameterMode: i.parameter_mode,
|
||||
schemaName: i.schema_name,
|
||||
}));
|
||||
|
||||
const functionNameToParameters = functionParameters.reduce((acc, row) => {
|
||||
if (!acc[`${row.schemaName}.${row.pureName}`]) acc[`${row.schemaName}.${row.pureName}`] = [];
|
||||
acc[`${row.schemaName}.${row.pureName}`].push(row);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const res = {
|
||||
tables: tables.rows.map(table => {
|
||||
const newTable = {
|
||||
@@ -279,17 +329,23 @@ class Analyser extends DatabaseAnalyser {
|
||||
objectId: `procedures:${proc.schema_name}.${proc.pure_name}`,
|
||||
pureName: proc.pure_name,
|
||||
schemaName: proc.schema_name,
|
||||
createSql: `CREATE PROCEDURE "${proc.schema_name}"."${proc.pure_name}"() LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`,
|
||||
createSql: `CREATE PROCEDURE "${proc.schema_name}"."${proc.pure_name}"(${getParametersSqlString(
|
||||
procedureNameToParameters[`${proc.schema_name}.${proc.pure_name}`]
|
||||
)}) LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`,
|
||||
contentHash: proc.hash_code,
|
||||
parameters: procedureNameToParameters[`${proc.schema_name}.${proc.pure_name}`],
|
||||
})),
|
||||
functions: routines.rows
|
||||
.filter(x => x.object_type == 'FUNCTION')
|
||||
.map(func => ({
|
||||
objectId: `functions:${func.schema_name}.${func.pure_name}`,
|
||||
createSql: `CREATE FUNCTION "${func.schema_name}"."${func.pure_name}"() RETURNS ${func.data_type} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`,
|
||||
createSql: `CREATE FUNCTION "${func.schema_name}"."${func.pure_name}"(${getParametersSqlString(
|
||||
functionNameToParameters[`${func.schema_name}.${func.pure_name}`]
|
||||
)}) RETURNS ${func.data_type.toUpperCase()} LANGUAGE ${func.language}\nAS\n$$\n${func.definition}\n$$`,
|
||||
pureName: func.pure_name,
|
||||
schemaName: func.schema_name,
|
||||
contentHash: func.hash_code,
|
||||
parameters: functionNameToParameters[`${func.schema_name}.${func.pure_name}`],
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const indexcols = require('./indexcols');
|
||||
const uniqueNames = require('./uniqueNames');
|
||||
const geometryColumns = require('./geometryColumns');
|
||||
const geographyColumns = require('./geographyColumns');
|
||||
const proceduresParameters = require('./proceduresParameters');
|
||||
|
||||
const fk_keyColumnUsage = require('./fk_key_column_usage');
|
||||
const fk_referentialConstraints = require('./fk_referential_constraints');
|
||||
@@ -39,4 +40,5 @@ module.exports = {
|
||||
uniqueNames,
|
||||
geometryColumns,
|
||||
geographyColumns,
|
||||
proceduresParameters,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
module.exports = `
|
||||
SELECT
|
||||
proc.specific_schema AS schema_name,
|
||||
proc.routine_name AS pure_name,
|
||||
proc.routine_type as routine_type,
|
||||
args.parameter_name AS parameter_name,
|
||||
args.parameter_mode,
|
||||
args.data_type AS data_type,
|
||||
args.ordinal_position AS parameter_index,
|
||||
args.parameter_mode AS parameter_mode
|
||||
FROM
|
||||
information_schema.routines proc
|
||||
LEFT JOIN
|
||||
information_schema.parameters args
|
||||
ON proc.specific_schema = args.specific_schema
|
||||
AND proc.specific_name = args.specific_name
|
||||
WHERE
|
||||
proc.specific_schema NOT IN ('pg_catalog', 'information_schema') -- Exclude system schemas
|
||||
AND args.parameter_name IS NOT NULL
|
||||
AND proc.routine_type IN ('PROCEDURE', 'FUNCTION') -- Filter for procedures
|
||||
AND proc.specific_schema !~ '^_timescaledb_'
|
||||
AND proc.specific_schema =SCHEMA_NAME_CONDITION
|
||||
AND (
|
||||
(routine_type = 'PROCEDURE' AND ('procedures:' || proc.specific_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
|
||||
OR
|
||||
(routine_type = 'FUNCTION' AND ('functions:' || proc.specific_schema || '.' || routine_name) =OBJECT_ID_CONDITION)
|
||||
)
|
||||
ORDER BY
|
||||
schema_name,
|
||||
args.ordinal_position;
|
||||
`;
|
||||
@@ -2,7 +2,7 @@ const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
const Dumper = require('./Dumper');
|
||||
const { postgreSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
||||
|
||||
const spatialTypes = ['GEOGRAPHY'];
|
||||
const spatialTypes = ['GEOGRAPHY','GEOMETRY'];
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
const dialect = {
|
||||
|
||||
Reference in New Issue
Block a user