Compare commits

..

104 Commits

Author SHA1 Message Date
Nybkox 4e4eb39a19 fix: remove distinct from mysql params query 2024-12-03 16:19:57 +01:00
Jan Prochazka b6399c8271 try to fix test 2024-12-03 16:05:23 +01:00
Nybkox 27188eb2c5 fix: normalize type name for mysql params 2024-12-03 15:39:22 +01:00
Nybkox 57a997adc3 fix: expect decimal for numeric params in mysql tests 2024-12-03 15:39:05 +01:00
Nybkox a72a03cc3a fix: add null safety for mysql function w/o params 2024-12-03 15:33:11 +01:00
Nybkox bb185d9e9f fix: add join on schema for mysql params query 2024-12-03 15:32:33 +01:00
Nybkox 0298660714 fix: add shchema to mysql programables query 2024-12-03 15:32:21 +01:00
Nybkox 1a4009a6b2 chore: remove unused query results 2024-12-03 14:13:31 +01:00
Nybkox e40357c052 fix: replace fullDataType with dataType in mssql params 2024-12-03 14:11:26 +01:00
Nybkox 222ea07cf2 fix: update mssql params queries 2024-12-03 14:11:04 +01:00
Nybkox 81cea4c0f2 fix: pssql params to string helper 2024-12-03 13:07:50 +01:00
Nybkox 21702f1593 fix: do not fetch pssql params w/o name 2024-12-03 13:03:05 +01:00
Nybkox 32ddb9c4c7 fix: normalize pssql parameters datatype 2024-12-03 13:02:46 +01:00
Nybkox 10fc62ceb7 fix: do not order pssql params by name 2024-12-03 13:02:40 +01:00
Nybkox d3018a3136 fix: correct function name in tests 2024-12-03 13:02:39 +01:00
Nybkox 5c7d2bfd85 fix: update parameter group keys 2024-12-03 11:01:47 +01:00
Nybkox 0eca5dd95d fix: typos 2024-12-03 10:37:36 +01:00
Jan Prochazka 352e426e17 fix 2024-12-03 09:48:56 +01:00
Jan Prochazka 666122f265 add mysql create/drop procedure test 2024-12-03 09:46:12 +01:00
Jan Prochazka a66dc03b99 params test - added object for test parent object match 2024-11-29 10:48:15 +01:00
Jan Prochazka cf5ecb3150 test create SQL 2024-11-29 10:44:09 +01:00
Jan Prochazka 46c365c5cd mysql parameter types 2024-11-29 10:29:12 +01:00
Jan Prochazka ea76751e4a postgres function tests 2024-11-29 10:06:34 +01:00
Jan Prochazka 0bef3f8e71 postgre procedure type tests 2024-11-29 09:57:46 +01:00
Jan Prochazka c26c9fae12 mssql parameters test 2024-11-29 09:35:15 +01:00
Jan Prochazka 446c615bb8 typo 2024-11-29 08:47:13 +01:00
Nybkox c516873541 feat: add parameters to mysql 2024-11-28 21:35:26 +01:00
Nybkox d4326de087 fix: proceduresParameters mssql query 2024-11-28 21:07:59 +01:00
Nybkox eca966bb90 fix: add null safety to getParametersSqlString 2024-11-28 21:07:59 +01:00
Nybkox 262b4732e3 feat: use parameterMode instead of isOutputParameter 2024-11-28 21:07:59 +01:00
Nybkox 7f9a30f568 fix: add missing conditions for pssql parameters query 2024-11-28 21:07:59 +01:00
Nybkox a89d2e1365 fix: add missing conditions for mssql parameters queries 2024-11-28 21:07:59 +01:00
Nybkox fcd6f6c8fc fix: correcttly map parameters to object list 2024-11-28 21:07:59 +01:00
Nybkox 8313d7f9f1 fix: update mssql parameters query to match ParameterInfo 2024-11-28 21:07:59 +01:00
Nybkox 3a12601103 feat: add parameters to createSql for pssql 2024-11-28 21:07:59 +01:00
Nybkox 926949dc89 fix: remove redundant field from ParameterInfo 2024-11-28 21:07:59 +01:00
Nybkox 6b155083ef feat: stored procedures and funciton parameters support for pssql 2024-11-28 21:07:59 +01:00
Nybkox 2b2ecac3ab feat: stored procedures and funciton parameters support for mssql 2024-11-28 21:07:59 +01:00
Jan Prochazka 35e9ff607d v5.5.7-beta.66 2024-11-28 18:47:35 +01:00
Jan Prochazka ae037834f2 connections UX 2024-11-28 18:45:39 +01:00
Jan Prochazka 3ac24436ba focused connection widget 2024-11-28 18:17:22 +01:00
Jan Prochazka 2ca17e826c connection tree UX 2024-11-28 17:53:22 +01:00
SPRINX0\prochazka 4fb6128499 v5.5.7-premium-beta.65 2024-11-28 15:12:52 +01:00
SPRINX0\prochazka c359332746 v5.5.7-beta.64 2024-11-28 15:11:54 +01:00
SPRINX0\prochazka 1cd8e8e376 typo 2024-11-28 15:02:27 +01:00
SPRINX0\prochazka 48ec2bdac8 connections UX 2024-11-28 14:59:19 +01:00
SPRINX0\prochazka 2283e91532 v5.5.7-beta.63 2024-11-27 18:30:05 +01:00
SPRINX0\prochazka 647894ad60 fix 2024-11-27 18:28:45 +01:00
SPRINX0\prochazka 574573abbb fix 2024-11-27 18:28:17 +01:00
SPRINX0\prochazka a735a03cd7 fix 2024-11-27 18:27:11 +01:00
SPRINX0\prochazka 83881a0dac docker build fix 2024-11-27 18:26:05 +01:00
SPRINX0\prochazka c04c6bbd2c v5.5.7-beta.62 2024-11-26 16:20:53 +01:00
SPRINX0\prochazka 42bbbc7ff4 fix job 2024-11-26 16:20:29 +01:00
SPRINX0\prochazka 1ecffeda71 default action improved & configurable 2024-11-26 15:19:39 +01:00
SPRINX0\prochazka 92992d1e95 ctx menu - open in new window 2024-11-26 14:19:20 +01:00
SPRINX0\prochazka bc9df9750f single connection mode list handler 2024-11-26 12:47:36 +01:00
SPRINX0\prochazka 27e70e8031 new UX - fixed support for singledb connections 2024-11-26 12:29:16 +01:00
SPRINX0\prochazka c823b8d19a UX 2024-11-26 09:30:37 +01:00
SPRINX0\prochazka 170ff77eec Merge branch 'feature/connection-keyboard-browse' 2024-11-26 09:11:23 +01:00
SPRINX0\prochazka c241f5c562 focused DB UX 2024-11-26 09:04:13 +01:00
SPRINX0\prochazka e06d964de4 connections UX WIP 2024-11-25 16:34:09 +01:00
SPRINX0\prochazka dfdb86de6f db widget UX 2024-11-25 15:52:26 +01:00
SPRINX0\prochazka a37b74f693 focusable databases WIP 2024-11-25 13:46:16 +01:00
SPRINX0\prochazka 398d9f15df focus connection or database WIP 2024-11-25 11:00:41 +01:00
SPRINX0\prochazka ab7c2d7a31 set tab preview mode off in markTabUnsaved 2024-11-25 08:31:24 +01:00
Jan Prochazka 090549ff91 v5.5.7-beta.61 2024-11-22 16:20:00 +01:00
Jan Prochazka 10330c6597 Merge branch 'master' of github.com:dbgate/dbgate 2024-11-22 16:19:34 +01:00
Jan Prochazka da2fe6a891 table & view preview mode switch 2024-11-22 16:19:29 +01:00
Jan Prochazka 01b88221c5 Merge pull request #945 from dbgate/feature-close-tab-hotkey-for-web
feat: add close tab hotkey for web
2024-11-22 15:26:43 +01:00
Nybkox 46d25710b8 feat: add close tab hotkey for web 2024-11-22 15:20:01 +01:00
Jan Prochazka a40ec7e66b show all databases for filtered connections 2024-11-22 11:40:51 +01:00
Jan Prochazka c8d2031d24 fix - don't call for DB list when searching in connections list 2024-11-22 11:25:46 +01:00
Jan Prochazka 4f838e0ae3 Merge branch 'feature/tab-preview-mode' 2024-11-22 10:43:24 +01:00
Jan Prochazka c7cc1b7611 theme changes (hover bg, dark selection bg) 2024-11-22 10:22:38 +01:00
Jan Prochazka bf67a5f13d UX 2024-11-22 10:15:43 +01:00
Jan Prochazka e6bbe66873 use keyboard for navigation between searchbox and table list 2024-11-22 10:03:49 +01:00
Jan Prochazka 1a930acf0a tables keyboard navigation 2024-11-22 09:45:01 +01:00
Jan Prochazka a497467137 fix 2024-11-22 08:45:30 +01:00
Jan Prochazka b463416633 Merge branch 'master' into feature/tab-preview-mode 2024-11-22 08:35:28 +01:00
Jan Prochazka ccf6240d65 Merge pull request #914 from dataspun/feature/geometry-autodetect-map
Update postgres plugin drivers.js
2024-11-22 08:28:40 +01:00
Jan Prochazka 5ccc12019d Merge pull request #943 from dbgate/feature-reopen-closed-tab
Feature reopen closed tab
2024-11-22 08:22:48 +01:00
Jan Prochazka f57fa9aee9 Merge pull request #942 from dbgate/feature-prevent-spawning-same-modal
feat: prevent spawning the same modal if one instance is opened
2024-11-22 08:21:00 +01:00
Nybkox 80e841a43d feat: add reopen closed tab command 2024-11-22 02:19:41 +01:00
SPRINX0\prochazka fd6df055c0 focus tab by enter 2024-11-21 17:07:18 +01:00
SPRINX0\prochazka 669d0b9dac correctly focusing tabs 2024-11-21 16:53:46 +01:00
SPRINX0\prochazka b9f9501e67 handle tab focus 2024-11-21 16:49:56 +01:00
SPRINX0\prochazka 4b1c021871 Revert "don't focus in tabs"
This reverts commit 90946c582d.
2024-11-21 16:12:27 +01:00
SPRINX0\prochazka 1f79627dbe change selected object when switching tab 2024-11-21 15:47:46 +01:00
SPRINX0\prochazka dc18be07ce #938 current database is not changed after closing tab 2024-11-21 15:03:43 +01:00
SPRINX0\prochazka c6cd865663 default action handling 2024-11-21 13:46:19 +01:00
SPRINX0\prochazka 86186072ed db app obj WIP 2024-11-20 17:12:54 +01:00
SPRINX0\prochazka 1216bcf9bf sql object tab refactor 2024-11-20 15:37:01 +01:00
SPRINX0\prochazka 788ea70d32 db app objc refactors 2024-11-20 15:30:47 +01:00
SPRINX0\prochazka 18de37c4e4 sql object tab 2024-11-20 14:53:06 +01:00
SPRINX0\prochazka aeb81bd97f sql object tab - ability to show template 2024-11-20 14:33:05 +01:00
SPRINX0\prochazka a68660f1ab sql object tab 2024-11-20 14:24:37 +01:00
SPRINX0\prochazka 5abfa85a0e script templates refactor 2024-11-20 13:51:18 +01:00
SPRINX0\prochazka 794f43d9ae call click when changing table by arrow 2024-11-20 11:25:07 +01:00
SPRINX0\prochazka 5db8f11fd6 dbappobj highlight 2024-11-20 08:16:13 +01:00
SPRINX0\prochazka 598674a7e0 show focus in data grid 2024-11-19 16:35:55 +01:00
SPRINX0\prochazka af17eceb27 table keyboard navigation WIP 2024-11-19 15:54:18 +01:00
SPRINX0\prochazka 90946c582d don't focus in tabs 2024-11-19 14:13:58 +01:00
SPRINX0\prochazka d619e0f961 tab preview mode - basic concept #767 2024-11-19 14:10:41 +01:00
Jeremy 2c0b76fb3f Update drivers.js
add geometry to spatial types
2024-10-09 14:20:44 -07:00
75 changed files with 2523 additions and 564 deletions
+33
View File
@@ -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]));
}
})
);
});
+219 -3
View File
@@ -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
View File
@@ -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\"",
+29 -42
View File
@@ -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
+1 -1
View File
@@ -12,6 +12,6 @@ DBCONFIG_mysql=[{"name":"Chinook","connectionColor":"cyan"}]
SINGLE_CONNECTION=mysql
SINGLE_DATABASE=Chinook
# SINGLE_DATABASE=Chinook
PERMISSIONS=files/charts/read
+1 -1
View File
@@ -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 });
+3
View File
@@ -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);
}
+14 -2
View File
@@ -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}
/>
+80
View File
@@ -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>
+8
View File
@@ -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 {
+10 -4
View File
@@ -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
+2 -2
View File
@@ -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();
}
+1
View File
@@ -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',
+26 -2
View File
@@ -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>
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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 */
+1
View File
@@ -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>
+19 -52
View File
@@ -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">
+31 -1
View File
@@ -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;
+9 -3
View File
@@ -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}
+42 -2
View File
@@ -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();
}
+2 -2
View File
@@ -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();
}
+3 -3
View File
@@ -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={{
+2 -2
View File
@@ -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();
}
+178
View File
@@ -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>
+22
View File
@@ -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
+38 -19
View File
@@ -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>
+49 -1
View File
@@ -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}
+2 -2
View File
@@ -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();
}
+2
View File
@@ -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 => {
+19 -4
View File
@@ -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,
})),
];
}
+11 -2
View File
@@ -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>
+158 -43
View File
@@ -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
+13 -4
View File
@@ -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>
+13 -2
View File
@@ -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>
+84 -21
View File
@@ -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 = {