Compare commits

...

858 Commits

Author SHA1 Message Date
Pavel
1e1837953b v6.6.1-beta.17 2025-08-05 22:20:08 +02:00
Pavel
1584471695 fix: ser error mode on preprocess 2025-08-05 22:19:48 +02:00
Pavel
4f2d94069b v6.6.1-beta.16 2025-08-05 14:34:34 +02:00
Pavel
ba303c5b41 feat: error mode warn 2025-08-05 14:34:16 +02:00
Pavel
74c4586be3 feat: svelte5 2025-08-05 14:10:16 +02:00
Pavel
d4e8d1026d v6.6.1-beta.15 2025-08-05 12:46:15 +02:00
Pavel
3d6722ebfa fix: wrap <tr> into <tbody> 2025-08-01 01:31:14 +02:00
Pavel
2d28046149 feat: updgrade svlete to v4 2025-08-01 01:24:59 +02:00
Pavel
0e91437e59 feat: add add comment to table teest 2025-07-31 18:35:30 +02:00
Pavel
c67ca42f57 feat: add add comment to column test 2025-07-31 18:30:20 +02:00
Pavel
c391a675f0 feat: add test for table creation with comments 2025-07-31 17:47:12 +02:00
Pavel
d5d182a9db fix:fix: removeu duplicate method, simplify changeColumnComment 2025-07-31 17:46:51 +02:00
Pavel
63de984d76 fix: correctly parse table comment when creating a table 2025-07-31 16:32:21 +02:00
Pavel
8779285408 feat: ms_description for tables, upd columns 2025-07-31 15:01:45 +02:00
Pavel
c20aec23a2 feat: change ms_description when columnComment changes 2025-07-31 15:01:22 +02:00
Pavel
a9cff01579 fix: show columnComment in structure ui 2025-07-31 15:01:22 +02:00
Pavel
6af56a61b8 feat: add ignoreComments options testEqualColumns 2025-07-31 15:01:22 +02:00
Pavel
252db191a6 feat: add ms_description to mssqldumper table options 2025-07-31 15:01:22 +02:00
Pavel
5e2776f264 feat: add MS_Description to mssql analyzer columns 2025-07-31 15:01:22 +02:00
SPRINX0\prochazka
38ebb2d06a Revert "update all packages"
This reverts commit c765bfc946.
2025-07-30 15:42:23 +02:00
SPRINX0\prochazka
c765bfc946 update all packages 2025-07-30 15:41:18 +02:00
CI workflows
b00ac75f33 chore: auto-update github workflows 2025-07-30 10:40:36 +00:00
SPRINX0\prochazka
36730168c0 SYNC: upgraded mongo for E2E tests 2025-07-30 10:40:19 +00:00
SPRINX0\prochazka
4339ece6f6 v6.6.1-beta.7 2025-07-30 11:17:38 +02:00
SPRINX0\prochazka
041c997e59 v6.6.1-premium-beta.6 2025-07-30 11:17:26 +02:00
SPRINX0\prochazka
098ebb38dc SYNC: fixed BE for premium 2025-07-30 09:05:11 +00:00
CI workflows
b99c38a070 chore: auto-update github workflows 2025-07-30 08:46:03 +00:00
SPRINX0\prochazka
07b42d8e74 Merge branch 'feature/mongosh' 2025-07-30 10:45:02 +02:00
SPRINX0\prochazka
062d168c97 windows arm build for community is back 2025-07-30 10:44:33 +02:00
SPRINX0\prochazka
c9be5fb125 v6.6.1-premium-beta.5 2025-07-30 10:20:20 +02:00
SPRINX0\prochazka
06c6716ee1 v6.6.1-beta.4 2025-07-30 10:20:09 +02:00
SPRINX0\prochazka
e856d8fddf mongosh support only for premium 2025-07-30 10:19:57 +02:00
SPRINX0\prochazka
20339f70c1 text 2025-07-30 08:42:30 +02:00
SPRINX0\prochazka
c2e6cf1eb0 time-limited offer info 2025-07-30 08:19:26 +02:00
SPRINX0\prochazka
8dfdca97cd readme 2025-07-30 07:38:17 +02:00
Pavel
6e8cdc24a3 feat: use getColleciton syntax for mongo 2025-07-29 16:33:39 +02:00
Pavel
461f1e39fa fix: use correct dbName in connection string 2025-07-29 16:12:56 +02:00
Pavel
4892dbce5e fix: use [''] syntax for mongosh col calls 2025-07-29 15:31:13 +02:00
SPRINX0\prochazka
9da32a13de v6.6.1-beta.3 2025-07-29 10:55:18 +02:00
SPRINX0\prochazka
9c6908da77 reverted change 2025-07-29 10:55:07 +02:00
SPRINX0\prochazka
ac081e6c86 v6.6.1-beta.2 2025-07-29 10:50:18 +02:00
SPRINX0\prochazka
cc9744156c removed windows ARM build 2025-07-29 10:50:09 +02:00
SPRINX0\prochazka
72a874c7f4 v6.6.1-beta.1 2025-07-29 09:58:42 +02:00
SPRINX0\prochazka
2809324b35 Merge branch 'master' into feature/mongosh 2025-07-29 09:57:20 +02:00
SPRINX0\prochazka
58e6c45c73 mongosh volatile packages 2025-07-29 09:56:25 +02:00
SPRINX0\prochazka
43aaf192a2 promo widget 2025-07-25 13:04:21 +02:00
SPRINX0\prochazka
c0574bc738 readme 2025-07-25 12:25:02 +02:00
SPRINX0\prochazka
27e5d639ef changelog 2025-07-25 09:27:45 +02:00
SPRINX0\prochazka
aa9fdd4fc9 changelog 2025-07-25 09:24:22 +02:00
SPRINX0\prochazka
7af6d9b2ce test wait 2025-07-25 09:22:55 +02:00
SPRINX0\prochazka
0e06d28335 v6.6.0 2025-07-25 09:21:25 +02:00
SPRINX0\prochazka
e3b86e4d41 v6.5.7-premium-beta.6 2025-07-25 08:39:12 +02:00
SPRINX0\prochazka
5b1bfe7379 v6.5.7-beta.5 2025-07-25 08:39:02 +02:00
CI workflows
76d07b967e chore: auto-update github workflows 2025-07-25 06:30:06 +00:00
CI workflows
4b1932fe52 Update pro ref 2025-07-25 06:29:48 +00:00
CI workflows
a56de91b1e chore: auto-update github workflows 2025-07-25 06:23:26 +00:00
CI workflows
6b4fb616bc Update pro ref 2025-07-25 06:23:11 +00:00
SPRINX0\prochazka
d24670e14e SYNC: chat & chart permission 2025-07-25 06:23:00 +00:00
CI workflows
13b3ae35ed chore: auto-update github workflows 2025-07-25 06:04:12 +00:00
CI workflows
6860e1f085 Update pro ref 2025-07-25 06:03:56 +00:00
SPRINX0\prochazka
74fa1c6628 SYNC: store connection definition in storagedb 2025-07-25 06:03:44 +00:00
SPRINX0\prochazka
85f847a4f3 SYNC: fix 2025-07-25 06:01:37 +00:00
CI workflows
39df72d163 chore: auto-update github workflows 2025-07-25 05:54:02 +00:00
CI workflows
5ca8786802 Update pro ref 2025-07-25 05:53:44 +00:00
SPRINX0\prochazka
ca145967dc SYNC: Merge branch 'feature/firestore' 2025-07-25 05:53:34 +00:00
Pavel
06a3ce7486 feat: update mongo getCollectionExportQueryScript 2025-07-24 20:55:24 +02:00
Pavel
9f85b6154d feat: look into cursor's firstBatch in mongosh res 2025-07-24 18:48:58 +02:00
SPRINX0\prochazka
732763689a v6.5.7-beta.4 2025-07-24 17:28:19 +02:00
SPRINX0\prochazka
0ea75f25f1 process workflows - upgrade node 2025-07-24 17:21:28 +02:00
SPRINX0\prochazka
eab27ce0bb node version 22 2025-07-24 17:20:52 +02:00
SPRINX0\prochazka
29fd381989 Merge branch 'master' into feature/mongosh 2025-07-24 17:14:59 +02:00
Pavel
06a845697a feat: mongosh for script and stream methods 2025-07-24 15:52:59 +02:00
CI workflows
b12587626d chore: auto-update github workflows 2025-07-24 10:11:39 +00:00
SPRINX0\prochazka
b49988032e firestore plugin build 2025-07-24 12:11:21 +02:00
SPRINX0\prochazka
a9b4152553 v6.5.7-premium-beta.3 2025-07-24 12:08:00 +02:00
CI workflows
63720045f1 chore: auto-update github workflows 2025-07-24 10:07:04 +00:00
CI workflows
aa7529192e Update pro ref 2025-07-24 10:06:46 +00:00
SPRINX0\prochazka
a162a15a27 v6.5.7-premium-beta.2 2025-07-24 11:01:28 +02:00
CI workflows
457a73efae chore: auto-update github workflows 2025-07-24 09:00:52 +00:00
CI workflows
91c3dd982b Update pro ref 2025-07-24 09:00:35 +00:00
Jan Prochazka
c171f93c93 SYNC: Merge pull request #5 from dbgate/feature/firestore 2025-07-24 09:00:23 +00:00
SPRINX0\prochazka
0cf9ddb1cd SYNC: try to fix test 2025-07-24 07:13:39 +00:00
SPRINX0\prochazka
2322537350 v6.5.7-premium-beta.1 2025-07-24 08:58:19 +02:00
SPRINX0\prochazka
6ce50109da gw server API 2025-07-24 08:55:21 +02:00
SPRINX0\prochazka
abe7fdf34d SYNC: simplker chat test 2025-07-24 06:52:58 +00:00
CI workflows
ecf2f5ed8c chore: auto-update github workflows 2025-07-24 06:50:30 +00:00
CI workflows
98b4934dd5 Update pro ref 2025-07-24 06:50:16 +00:00
SPRINX0\prochazka
0bc7c544ad SYNC: chat UX 2025-07-24 06:50:05 +00:00
SPRINX0\prochazka
1f7ad9d418 SYNC: fix 2025-07-24 06:31:57 +00:00
CI workflows
4b9d3b3dbc Update pro ref 2025-07-24 06:31:44 +00:00
SPRINX0\prochazka
571e332ed5 SYNC: fixed test 2025-07-24 06:31:34 +00:00
CI workflows
d78d22b188 chore: auto-update github workflows 2025-07-24 06:11:23 +00:00
CI workflows
d37638240a Update pro ref 2025-07-24 06:11:05 +00:00
SPRINX0\prochazka
ae7fd3f87b SYNC: open db chat ctx menu 2025-07-24 06:11:00 +00:00
Jan Prochazka
e82e63b288 SYNC: adidtional test 2025-07-24 06:10:57 +00:00
Jan Prochazka
0149d4e27b SYNC: database chat test 2025-07-24 06:10:54 +00:00
CI workflows
9fc9c71b6f chore: auto-update github workflows 2025-07-23 13:46:29 +00:00
CI workflows
b264f690d1 Update pro ref 2025-07-23 13:46:13 +00:00
Jan Prochazka
c07e19c898 SYNC: Merge pull request #6 from dbgate/feature/ai-assistant 2025-07-23 13:46:01 +00:00
Pavel
b8e50737d2 feat: basic mongosh support 2025-07-23 04:40:52 +02:00
SPRINX0\prochazka
082d0aa02f translations 2025-07-18 10:03:58 +02:00
SPRINX0\prochazka
ca26d0e450 translations 2025-07-18 10:01:24 +02:00
SPRINX0\prochazka
8cbe021ffc v6.5.6 2025-07-17 09:04:06 +02:00
SPRINX0\prochazka
7b39d8025b changelog 2025-07-17 09:03:18 +02:00
SPRINX0\prochazka
47bd35b151 fixed failing test 2025-07-17 08:44:22 +02:00
SPRINX0\prochazka
d7add54a3c v6.5.6-premium-beta.5 2025-07-17 08:19:37 +02:00
SPRINX0\prochazka
d3c937569b SYNC: anonymized cloud instance 2025-07-17 06:18:50 +00:00
SPRINX0\prochazka
94ca613201 v6.5.6-premium-beta.4 2025-07-16 15:52:09 +02:00
SPRINX0\prochazka
30f2f635be v6.5.6-premium-beta.3 2025-07-16 15:51:20 +02:00
SPRINX0\prochazka
57f4d31c21 SYNC: fix 2025-07-16 13:24:47 +00:00
SPRINX0\prochazka
90e4fd7ff5 SYNC: disable splitting queries with blank lines? #1162 2025-07-16 13:16:02 +00:00
SPRINX0\prochazka
17835832f2 v6.5.6-beta.2 2025-07-16 14:51:55 +02:00
SPRINX0\prochazka
949817f597 SYNC: SKIP_ALL_AUTH support 2025-07-16 12:48:38 +00:00
SPRINX0\prochazka
23065f2c4b SYNC: test fix 2025-07-16 12:26:08 +00:00
SPRINX0\prochazka
b623b06cf0 SYNC: bugfix 2025-07-16 12:02:19 +00:00
CI workflows
55c86d8ec7 chore: auto-update github workflows 2025-07-16 11:43:35 +00:00
CI workflows
e955617aa1 Update pro ref 2025-07-16 11:43:18 +00:00
SPRINX0\prochazka
6304610713 SYNC: hard limit for pie chart 2025-07-16 11:43:08 +00:00
SPRINX0\prochazka
47d20928e0 SYNC: try to fix tests 2025-07-16 11:24:06 +00:00
SPRINX0\prochazka
c9a4d02e0d SYNC: new object window screenshot 2025-07-16 11:03:07 +00:00
CI workflows
6513dfb42a chore: auto-update github workflows 2025-07-16 10:52:10 +00:00
CI workflows
3f0412453f Update pro ref 2025-07-16 10:51:29 +00:00
SPRINX0\prochazka
dcba319071 SYNC: disabled messages in new object modal 2025-07-16 10:51:19 +00:00
SPRINX0\prochazka
d19851fc0c SYNC: compare database in new object modal 2025-07-16 10:51:17 +00:00
SPRINX0\prochazka
d6eb06cb72 SYNC: export db window 2025-07-16 10:51:16 +00:00
SPRINX0\prochazka
473080d7ee SYNC: typo 2025-07-16 10:51:15 +00:00
SPRINX0\prochazka
c98a6adb09 SYNC: new object modal testid 2025-07-16 10:51:13 +00:00
SPRINX0\prochazka
2cd56d5041 SYNC: new object button refactor + diagram accesibility 2025-07-16 10:51:12 +00:00
SPRINX0\prochazka
982098672e SYNC: new object modal 2025-07-16 10:51:10 +00:00
SPRINX0\prochazka
445ecea3e6 SYNC: new object modal WIP 2025-07-16 10:51:09 +00:00
SPRINX0\prochazka
db977dfba4 SYNC: next screenshots 2025-07-15 08:32:33 +00:00
CI workflows
a3c12ab9f5 chore: auto-update github workflows 2025-07-15 08:22:58 +00:00
CI workflows
0f7e152650 Update pro ref 2025-07-15 08:22:41 +00:00
SPRINX0\prochazka
b55c7ba9a1 v6.5.6-premium-beta.1 2025-07-15 09:16:08 +02:00
CI workflows
8256c9f7ad chore: auto-update github workflows 2025-07-15 07:12:36 +00:00
CI workflows
59727d7b0b Update pro ref 2025-07-15 07:12:20 +00:00
SPRINX0\prochazka
2dd2210a73 SYNC: separate schemas mode usable for administration 2025-07-15 07:12:08 +00:00
SPRINX0\prochazka
25aafdbebc SYNC: chart screenshots for tutorial 2025-07-15 06:46:01 +00:00
SPRINX0\prochazka
cd5717169c login checker dummy implementation 2025-07-14 15:23:09 +02:00
CI workflows
a38ad5a11e chore: auto-update github workflows 2025-07-14 13:22:34 +00:00
CI workflows
66d9b56976 Update pro ref 2025-07-14 13:22:21 +00:00
SPRINX0\prochazka
ac40bd1e17 SYNC: checking logged users 2025-07-14 13:22:10 +00:00
SPRINX0\prochazka
16d2a9bf99 SYNC: renew license from set license page 2025-07-14 11:41:55 +00:00
SPRINX0\prochazka
b7e6838d26 refresh license fake 2025-07-14 12:28:49 +02:00
CI workflows
21d23b5baa chore: auto-update github workflows 2025-07-14 10:22:13 +00:00
CI workflows
69a2941d57 Update pro ref 2025-07-14 10:21:59 +00:00
SPRINX0\prochazka
3cc2abf8b9 SYNC: better handling of expired license in electron app 2025-07-14 10:21:49 +00:00
Jan Prochazka
6f4173650a v6.5.5 2025-07-04 09:08:49 +02:00
Jan Prochazka
0fcb8bdc0a SYNC: changelog 2025-07-04 07:05:28 +00:00
CI workflows
c0937cf412 chore: auto-update github workflows 2025-07-04 06:44:51 +00:00
CI workflows
d9ab3aab0f Update pro ref 2025-07-04 06:44:36 +00:00
CI workflows
c8652de78b chore: auto-update github workflows 2025-07-04 06:34:19 +00:00
CI workflows
86dc4e2bd5 Update pro ref 2025-07-04 06:34:04 +00:00
Jan Prochazka
1b9c56a9b9 SYNC: fixed data replicator test 2025-07-04 06:33:54 +00:00
Jan Prochazka
08ab504fac SYNC: fix 2025-07-04 06:33:52 +00:00
CI workflows
21c0842fae chore: auto-update github workflows 2025-07-04 06:11:12 +00:00
CI workflows
8d10feaa68 Update pro ref 2025-07-04 06:10:53 +00:00
Jan Prochazka
df2171f253 SYNC: fixed disabling/enabling auth methods for team premium 2025-07-04 06:10:43 +00:00
SPRINX0\prochazka
f5fcd94faf SYNC: fix 2025-07-03 15:36:58 +00:00
CI workflows
15c5dbef00 chore: auto-update github workflows 2025-07-03 15:28:56 +00:00
CI workflows
79df56c096 Update pro ref 2025-07-03 15:28:39 +00:00
SPRINX0\prochazka
d3fffd9530 SYNC: missing audit logs 2025-07-03 15:28:28 +00:00
SPRINX0\prochazka
527c9c8e6e loginchecker placeholder 2025-07-03 16:58:27 +02:00
CI workflows
d285be45cb chore: auto-update github workflows 2025-07-03 14:51:25 +00:00
CI workflows
0dda9c73f6 Update pro ref 2025-07-03 14:51:10 +00:00
SPRINX0\prochazka
d07bf270e7 SYNC: logi checker refactor 2025-07-03 14:50:59 +00:00
CI workflows
eb24dd5d9e chore: auto-update github workflows 2025-07-03 13:25:47 +00:00
CI workflows
ce693c7cd5 Update pro ref 2025-07-03 13:25:32 +00:00
CI workflows
3198890269 chore: auto-update github workflows 2025-07-03 13:11:26 +00:00
CI workflows
eacc93de43 Update pro ref 2025-07-03 13:11:07 +00:00
SPRINX0\prochazka
9795740257 SYNC: check licensed user count 2025-07-03 13:10:55 +00:00
SPRINX0\prochazka
4548f5d8aa fix 2025-07-03 14:09:29 +02:00
SPRINX0\prochazka
8dfd2fb519 markUserAsActive dummy method 2025-07-03 14:07:04 +02:00
CI workflows
83a40f83e1 chore: auto-update github workflows 2025-07-03 11:50:05 +00:00
CI workflows
5b2fcb3c6c Update pro ref 2025-07-03 11:49:50 +00:00
Jan Prochazka
bcd9adb66d Merge pull request #1159 from dbgate/feature/firebird-always-use-text-for-file
feat: add useServerDatabaseFile for firebird
2025-07-03 13:30:21 +02:00
Pavel
5e2dc114ab feat: add useServerDatabaseFile for firebird 2025-07-03 13:27:22 +02:00
SPRINX0\prochazka
1ced4531be auditlog dummy methods 2025-07-03 13:20:07 +02:00
CI workflows
05fe39c0ae chore: auto-update github workflows 2025-07-03 11:18:54 +00:00
CI workflows
3769b2b3ea Update pro ref 2025-07-03 11:18:38 +00:00
Jan Prochazka
f4d5480f6f SYNC: try to fix test 2025-07-02 14:09:25 +00:00
Jan Prochazka
ddf3c0810b SYNC: charts auto detect 2025-07-02 13:49:39 +00:00
Jan Prochazka
6afd6d0aa0 v6.5.5-premium-beta.5 2025-07-02 13:42:41 +02:00
CI workflows
59fe92eb04 chore: auto-update github workflows 2025-07-02 11:41:41 +00:00
CI workflows
0550f32434 Update pro ref 2025-07-02 11:41:25 +00:00
Jan Prochazka
b702cad549 SYNC: fixed chart test 2025-07-02 11:41:15 +00:00
Jan Prochazka
aa5c4d3c5e changelog 2025-07-02 13:29:37 +02:00
CI workflows
6a99445d97 chore: auto-update github workflows 2025-07-02 11:22:53 +00:00
CI workflows
c9880ef47d Update pro ref 2025-07-02 11:22:38 +00:00
CI workflows
c16452dfcb chore: auto-update github workflows 2025-07-02 11:12:38 +00:00
CI workflows
af802c02fc Update pro ref 2025-07-02 11:12:20 +00:00
Jan Prochazka
8028aafeff SYNC: split too different ydefs 2025-07-02 11:12:09 +00:00
Jan Prochazka
b7469062a1 SYNC: charts autodetector test 2025-07-02 08:57:39 +00:00
Jan Prochazka
33b707aa68 SYNC: chart autodetection improved 2025-07-02 08:50:33 +00:00
Jan Prochazka
cd3a1bebff SYNC: autodetect - with grouping field 2025-07-02 08:23:12 +00:00
Jan Prochazka
794dd5a797 SYNC: refactor 2025-07-02 08:23:10 +00:00
CI workflows
a1465432e8 chore: auto-update github workflows 2025-07-02 06:54:14 +00:00
CI workflows
e1f8af0909 Update pro ref 2025-07-02 06:53:57 +00:00
Jan Prochazka
88918be329 SYNC: chart - detect data types 2025-07-02 06:53:47 +00:00
CI workflows
a3fc1dbff0 chore: auto-update github workflows 2025-07-02 06:20:20 +00:00
CI workflows
626c9825cc Update pro ref 2025-07-02 06:20:02 +00:00
Jan Prochazka
c10a84fc79 SYNC: timeline chart type 2025-07-02 06:19:49 +00:00
SPRINX0\prochazka
f14e4fe197 SYNC: month match 2025-07-01 14:53:27 +00:00
CI workflows
6eb218db5e chore: auto-update github workflows 2025-07-01 14:30:39 +00:00
CI workflows
0e77e053b0 Update pro ref 2025-07-01 14:30:23 +00:00
SPRINX0\prochazka
b9a4128a3d SYNC: charts - grouping field support 2025-07-01 14:30:12 +00:00
CI workflows
16f480e1f3 chore: auto-update github workflows 2025-07-01 12:23:46 +00:00
CI workflows
7c42511133 Update pro ref 2025-07-01 12:23:32 +00:00
CI workflows
1b252a84c2 chore: auto-update github workflows 2025-07-01 12:14:57 +00:00
CI workflows
bf833cadff Update pro ref 2025-07-01 12:14:42 +00:00
CI workflows
b6f872882a chore: auto-update github workflows 2025-07-01 12:12:56 +00:00
CI workflows
a18d6fb441 Update pro ref 2025-07-01 12:12:39 +00:00
CI workflows
922e703e81 chore: auto-update github workflows 2025-07-01 10:59:50 +00:00
CI workflows
d7f5817b8b Update pro ref 2025-07-01 10:59:32 +00:00
SPRINX0\prochazka
92a8a4bfa6 SYNC: chart improvements 2025-07-01 10:59:20 +00:00
CI workflows
b480151fc3 chore: auto-update github workflows 2025-07-01 10:35:08 +00:00
CI workflows
37bdbc1bd5 Update pro ref 2025-07-01 10:34:52 +00:00
CI workflows
8eb669139b chore: auto-update github workflows 2025-07-01 10:11:57 +00:00
CI workflows
b485e8cacc Update pro ref 2025-07-01 10:11:39 +00:00
CI workflows
c4bab61c47 chore: auto-update github workflows 2025-07-01 08:45:43 +00:00
CI workflows
72be417ff1 Update pro ref 2025-07-01 08:45:27 +00:00
CI workflows
9be483d7a6 chore: auto-update github workflows 2025-07-01 08:35:12 +00:00
CI workflows
910f2cee2c Update pro ref 2025-07-01 08:34:48 +00:00
SPRINX0\prochazka
1e47ace527 SYNC: fixed diagram zoom GL#57 2025-07-01 07:14:55 +00:00
Jan Prochazka
912b06b145 v6.5.5-premium-beta.4 2025-06-30 14:35:30 +02:00
CI workflows
87d878e287 chore: auto-update github workflows 2025-06-30 12:33:45 +00:00
CI workflows
be886d6bce Update pro ref 2025-06-30 12:33:29 +00:00
Jan Prochazka
0683deb47e v6.5.5-premium-beta.3 2025-06-30 13:53:39 +02:00
CI workflows
114bb22e27 chore: auto-update github workflows 2025-06-30 11:46:31 +00:00
CI workflows
c327ebc3df Update pro ref 2025-06-30 11:46:18 +00:00
Jan Prochazka
92cbd1c69c SYNC: config fixed 2025-06-30 11:46:05 +00:00
CI workflows
7242515e48 chore: auto-update github workflows 2025-06-30 10:32:57 +00:00
CI workflows
401d1a0ac2 Update pro ref 2025-06-30 10:32:41 +00:00
Jan Prochazka
863e042a37 SYNC: fixed exporting chart for electron 2025-06-30 10:32:31 +00:00
Jan Prochazka
39e6c45ec6 v6.5.5-premium-beta.2 2025-06-30 09:25:54 +02:00
CI workflows
0d364d18c7 chore: auto-update github workflows 2025-06-30 06:56:17 +00:00
CI workflows
61444ea390 Update pro ref 2025-06-30 06:56:01 +00:00
CI workflows
106a935efb chore: auto-update github workflows 2025-06-30 06:28:20 +00:00
CI workflows
d175d8a853 Update pro ref 2025-06-30 06:28:04 +00:00
Jan Prochazka
ce6d19a77a SYNC: call adapt db info 2025-06-30 06:27:54 +00:00
CI workflows
0a29273924 chore: auto-update github workflows 2025-06-29 18:32:57 +00:00
CI workflows
5ede64de58 Update pro ref 2025-06-29 18:32:40 +00:00
Jan Prochazka
224c6ad798 SYNC: try to fix oracle test 2025-06-29 18:27:07 +00:00
Jan Prochazka
57b3a0dbe7 v6.5.5-premium-beta.1 2025-06-28 12:00:16 +02:00
CI workflows
f381f708e0 chore: auto-update github workflows 2025-06-27 13:27:16 +00:00
CI workflows
63bf149546 Update pro ref 2025-06-27 13:27:02 +00:00
SPRINX0\prochazka
cb5e671259 SYNC: audit log test 2025-06-27 13:26:51 +00:00
CI workflows
3e38173c4e chore: auto-update github workflows 2025-06-27 13:01:02 +00:00
CI workflows
efacb643fc Update pro ref 2025-06-27 13:00:45 +00:00
SPRINX0\prochazka
1bd153ea0b SYNC: audit log UX 2025-06-27 13:00:32 +00:00
CI workflows
bac3dc5f4c chore: auto-update github workflows 2025-06-27 11:08:55 +00:00
CI workflows
959a853d77 Update pro ref 2025-06-27 11:08:36 +00:00
SPRINX0\prochazka
90bbdd563b SYNC: Merge branch 'feature/audit-logs' 2025-06-27 11:08:23 +00:00
SPRINX0\prochazka
e3c6d05a0a SYNC: try to fix test 2025-06-27 10:32:01 +00:00
SPRINX0\prochazka
930b3d4538 SYNC: cloud test - use test login 2025-06-27 08:51:36 +00:00
SPRINX0\prochazka
74b78141b4 fake method 2025-06-27 10:02:33 +02:00
SPRINX0\prochazka
aa1108cd5b SYNC: try to fix build 2025-06-27 07:42:42 +00:00
SPRINX0\prochazka
f24b1a9db3 audit log fake methods 2025-06-26 16:49:10 +02:00
SPRINX0\prochazka
71b191e740 SYNC: try to fix test 2025-06-25 07:24:42 +00:00
SPRINX0\prochazka
8f6341b903 SYNC: removed baseUrl config 2025-06-25 07:02:07 +00:00
SPRINX0\prochazka
161586db7e SYNC: try to fix test 2025-06-24 15:13:32 +00:00
SPRINX0\prochazka
052262bef9 SYNC: try to fix test 2025-06-24 13:05:38 +00:00
SPRINX0\prochazka
a5a7144707 SYNC: try to fix test 2025-06-24 10:57:54 +00:00
SPRINX0\prochazka
d945e0426d SYNC: try to fix test 2025-06-24 10:29:47 +00:00
SPRINX0\prochazka
926970c4eb SYNC: added missing script 2025-06-24 09:23:53 +00:00
CI workflows
cce36e0f28 chore: auto-update github workflows 2025-06-24 08:33:21 +00:00
CI workflows
48c6dc5be5 Update pro ref 2025-06-24 08:33:05 +00:00
SPRINX0\prochazka
c641830825 SYNC: private cloud test 2025-06-24 08:32:52 +00:00
SPRINX0\prochazka
eba16cc15d SYNC: dbgate cloud redirect workflow 2025-06-24 07:22:43 +00:00
SPRINX0\prochazka
bd88b8411e SYNC: private cloud test 2025-06-23 14:59:05 +00:00
SPRINX0\prochazka
fc121e8750 missing file 2025-06-23 16:51:42 +02:00
CI workflows
d4142fe56a chore: auto-update github workflows 2025-06-23 14:31:30 +00:00
CI workflows
f76a3e72bb Update pro ref 2025-06-23 14:31:11 +00:00
SPRINX0\prochazka
2d400ae7eb SYNC: folder administration modal 2025-06-23 14:30:58 +00:00
SPRINX0\prochazka
edf1632cab SYNC: fixed connection for scripts 2025-06-23 11:29:04 +00:00
SPRINX0\prochazka
a648f1ee67 SYNC: SQL fixed database WIP 2025-06-23 11:10:46 +00:00
SPRINX0\prochazka
d004e6e86c SYNC: new cloud file 2025-06-23 09:05:42 +00:00
SPRINX0\prochazka
fa321d3e8d SYNC: create query on cloud shortcut 2025-06-23 08:52:07 +00:00
SPRINX0\prochazka
e1e53d323f SYNC: dbgate cloud menu refactor 2025-06-23 07:40:46 +00:00
SPRINX0\prochazka
ccb18ca302 v6.5.4 2025-06-20 17:03:39 +02:00
SPRINX0\prochazka
e170f36bc6 v6.5.4-premium-beta.1 2025-06-20 16:46:18 +02:00
SPRINX0\prochazka
4bd9cc51ee SYNC: try to fix e2e test 2025-06-20 14:41:03 +00:00
SPRINX0\prochazka
43ffbda1a4 SYNC: removed vorgotten test.only 2025-06-20 13:32:20 +00:00
SPRINX0\prochazka
8240485fd1 changelog 2025-06-20 15:05:18 +02:00
SPRINX0\prochazka
7f053c0567 v6.5.3 2025-06-20 15:01:47 +02:00
SPRINX0\prochazka
d2922eb0b7 SYNC: cloud connections fix 2025-06-20 12:57:09 +00:00
SPRINX0\prochazka
fec10d453f license detection fix 2025-06-20 14:36:00 +02:00
SPRINX0\prochazka
162040545d SYNC: improved about modal 2025-06-20 12:29:11 +00:00
CI workflows
f14577f8bf chore: auto-update github workflows 2025-06-20 11:57:27 +00:00
CI workflows
e5720bd1be Update pro ref 2025-06-20 11:57:15 +00:00
CI workflows
6d4959bac8 Update pro ref 2025-06-20 11:57:11 +00:00
SPRINX0\prochazka
d668128a34 SYNC: private cloud UX + fixes 2025-06-20 11:46:04 +00:00
SPRINX0\prochazka
f2af38da4c v6.5.3-premium-beta.1 2025-06-19 18:00:13 +02:00
CI workflows
4776d18fd7 chore: auto-update github workflows 2025-06-19 15:57:59 +00:00
CI workflows
cdd0be7b78 Update pro ref 2025-06-19 15:57:43 +00:00
SPRINX0\prochazka
cd505abb22 SYNC: charts fix 2025-06-19 15:57:31 +00:00
SPRINX0\prochazka
28439c010f SYNC: fixed all search column settings for alternative grids #1118 2025-06-19 12:43:15 +00:00
CI workflows
e85f43beb1 chore: auto-update github workflows 2025-06-19 12:08:31 +00:00
CI workflows
a06cbc0840 Update pro ref 2025-06-19 12:08:18 +00:00
SPRINX0\prochazka
adef9728f8 SYNC: charts UX, error handling, bucket count limit 2025-06-19 12:08:06 +00:00
CI workflows
ff1b688b6e chore: auto-update github workflows 2025-06-19 08:56:09 +00:00
CI workflows
3e7574a927 Update pro ref 2025-06-19 08:55:55 +00:00
SPRINX0\prochazka
f852ea90ad changelog 2025-06-18 11:31:53 +02:00
SPRINX0\prochazka
d8f6247c32 v6.5.2 2025-06-18 11:00:46 +02:00
SPRINX0\prochazka
9dc28393a5 links added 2025-06-18 10:50:44 +02:00
SPRINX0\prochazka
c442c98ecf SYNC: fixed test 2025-06-18 08:43:56 +00:00
SPRINX0\prochazka
71e0109927 v6.5.2-premium-beta.1 2025-06-18 10:21:57 +02:00
SPRINX0\prochazka
9c7dd5ed1c SYNC: close chart fix 2025-06-18 08:17:58 +00:00
CI workflows
83620848f2 chore: auto-update github workflows 2025-06-18 08:13:39 +00:00
CI workflows
d548a5b4f3 Update pro ref 2025-06-18 08:13:25 +00:00
CI workflows
b6e5307755 chore: auto-update github workflows 2025-06-18 08:05:11 +00:00
CI workflows
4c5dc5a145 Update pro ref 2025-06-18 08:04:54 +00:00
SPRINX0\prochazka
69ed9172b8 SYNC: chart UX 2025-06-18 08:04:41 +00:00
CI workflows
68551ae176 chore: auto-update github workflows 2025-06-18 07:48:21 +00:00
CI workflows
c97d9d35ba Update pro ref 2025-06-18 07:48:07 +00:00
SPRINX0\prochazka
e86cc97cdf SYNC: changed chart logic 2025-06-18 07:47:56 +00:00
SPRINX0\prochazka
9bff8608c1 SYNC: auto-detect charts is disabled by default #1145 2025-06-18 07:30:16 +00:00
SPRINX0\prochazka
a10fe6994a v6.5.1 2025-06-17 16:08:07 +02:00
SPRINX0\prochazka
67e6a37b59 v6.5.1-beta.1 2025-06-17 15:42:26 +02:00
SPRINX0\prochazka
3075a56735 fixed cloud login 2025-06-17 15:42:16 +02:00
SPRINX0\prochazka
7e4a862cc3 v6.5.0 2025-06-17 10:02:43 +02:00
SPRINX0\prochazka
ed2078ee3b handle license errors 2025-06-17 09:55:59 +02:00
SPRINX0\prochazka
f99c23a622 v6.4.3-premium-beta.10 2025-06-17 09:08:40 +02:00
SPRINX0\prochazka
41e7317764 v6.4.3-beta.9 2025-06-17 09:08:18 +02:00
SPRINX0\prochazka
e0a78c2399 equal behaviour for premium and community 2025-06-17 09:06:08 +02:00
SPRINX0\prochazka
95ad39d2d4 admin premium widget 2025-06-17 09:03:57 +02:00
CI workflows
b831f827b1 chore: auto-update github workflows 2025-06-16 11:25:21 +00:00
SPRINX0\prochazka
c5d8413d9c SYNC: fix 2025-06-16 11:25:08 +00:00
CI workflows
4648ea3424 Update pro ref 2025-06-16 11:25:04 +00:00
SPRINX0\prochazka
e05bd6f231 SYNC: front matter in chart screenshot 2025-06-16 11:24:49 +00:00
CI workflows
caadee7901 chore: auto-update github workflows 2025-06-16 10:58:30 +00:00
CI workflows
18c524117d Update pro ref 2025-06-16 10:58:11 +00:00
SPRINX0\prochazka
ad30fb8b04 SYNC: query result chart screenshot 2025-06-16 10:57:58 +00:00
SPRINX0\prochazka
a8077965a9 icon fix 2025-06-16 10:36:57 +02:00
SPRINX0\prochazka
532ab85ebb public cloud improvements 2025-06-16 10:32:49 +02:00
SPRINX0\prochazka
546227eb37 changelog 2025-06-16 09:55:15 +02:00
CI workflows
7ec3b262d3 chore: auto-update github workflows 2025-06-16 07:24:20 +00:00
CI workflows
c435000d24 Update pro ref 2025-06-16 07:24:03 +00:00
SPRINX0\prochazka
eaa60c281e SYNC: chart screenshot 2025-06-16 07:23:51 +00:00
Jan Prochazka
cd7cf63144 SYNC: copy to cloud folder works for connected DB 2025-06-15 08:22:58 +00:00
CI workflows
6a704aa079 chore: auto-update github workflows 2025-06-15 07:25:21 +00:00
CI workflows
d6b5a1cec8 Update pro ref 2025-06-15 07:25:03 +00:00
Jan Prochazka
9a24ad31cc SYNC: pie chart out labels 2025-06-15 07:24:50 +00:00
CI workflows
9331630b54 chore: auto-update github workflows 2025-06-15 06:58:43 +00:00
CI workflows
904e869d7f Update pro ref 2025-06-15 06:58:29 +00:00
Jan Prochazka
307fa4f5e6 SYNC: trim license 2025-06-15 06:58:15 +00:00
SPRINX0\prochazka
131d16d3ea v6.4.3-beta.8 2025-06-13 16:35:05 +02:00
SPRINX0\prochazka
dbc54c45dd v6.4.3-premium-beta.7 2025-06-13 16:34:50 +02:00
SPRINX0\prochazka
a96a84d509 SYNC: grayed scripts for non active database 2025-06-13 14:31:53 +00:00
SPRINX0\prochazka
3eb8863f67 SYNC: cloud connections in tab names 2025-06-13 13:25:34 +00:00
SPRINX0\prochazka
8737ab077b SYNC: fixed status bar color 2025-06-13 13:14:16 +00:00
SPRINX0\prochazka
50bb6a1d19 SYNC: prod cloud 2025-06-13 13:12:09 +00:00
SPRINX0\prochazka
4181b75af7 SYNC: connection color for cloud connections 2025-06-13 13:08:56 +00:00
CI workflows
620705c87a chore: auto-update github workflows 2025-06-13 12:14:41 +00:00
CI workflows
50b7b93529 Update pro ref 2025-06-13 12:14:22 +00:00
SPRINX0\prochazka
6f18f6bd5c SYNC: debug config 2025-06-13 12:14:07 +00:00
SPRINX0\prochazka
01d256eeee SYNC: don't show private cloud for web app 2025-06-13 11:52:03 +00:00
SPRINX0\prochazka
a1405412a8 debug config 2025-06-13 11:49:19 +02:00
SPRINX0\prochazka
58589b3a15 v6.4.3-premium-beta.6 2025-06-13 09:44:49 +02:00
SPRINX0\prochazka
2983266fdf Merge branch 'master' of https://github.com/dbgate/dbgate 2025-06-12 16:55:58 +02:00
SPRINX0\prochazka
e33df8f12d cloud content refactor 2025-06-12 16:55:55 +02:00
CI workflows
0e0e8e9d18 chore: auto-update github workflows 2025-06-12 13:30:21 +00:00
Jan Prochazka
37f8b54752 Merge pull request #1130 from dbgate/feature/firebird
Feature/firebird
2025-06-12 15:29:58 +02:00
SPRINX0\prochazka
e9a086ad23 SYNC: refresh cloud files improvements 2025-06-12 12:26:35 +00:00
SPRINX0\prochazka
7c06a8ac41 SYNC: fix refresh publis files 2025-06-12 12:13:33 +00:00
SPRINX0\prochazka
70801d958e SYNC: security rename 2025-06-12 11:51:22 +00:00
SPRINX0\prochazka
cf3f95c952 SYNC: security fixes 2025-06-12 11:49:53 +00:00
Pavel
d708616a6a feat: add createFirebirdInsertStream with datetime fields transform 2025-06-12 13:44:24 +02:00
Pavel
17711bc5c9 Revert "feat: transform rows suport for json lines reader"
This reverts commit b74b6b3284.
2025-06-12 13:29:48 +02:00
Pavel
1e2474921b Revert "feat: transform firebird model rows"
This reverts commit 5760ada3b4.
2025-06-12 13:29:46 +02:00
SPRINX0\prochazka
3f37b2b728 security fixes 2025-06-12 10:58:46 +02:00
SPRINX0\prochazka
18b11df672 security: prevent file traversal in uploads 2025-06-12 10:43:27 +02:00
SPRINX0\prochazka
c34f2d4da7 better UX when logging in in electron 2025-06-11 17:25:14 +02:00
SPRINX0\prochazka
d61792581a Merge branch 'master' of https://github.com/dbgate/dbgate 2025-06-11 17:02:40 +02:00
SPRINX0\prochazka
76d9a511b8 sql generate aliases automatically #1122 2025-06-11 17:02:38 +02:00
CI workflows
4248326697 chore: auto-update github workflows 2025-06-11 12:41:59 +00:00
SPRINX0\prochazka
a540b38151 don't generate artifacts for check build 2025-06-11 14:41:34 +02:00
SPRINX0\prochazka
8fb5ef0c1d v6.4.3-premium-beta.5 2025-06-11 14:39:00 +02:00
SPRINX0\prochazka
2da4979e59 UX fix 2025-06-11 14:29:59 +02:00
SPRINX0\prochazka
0146e4a1dd #1111 mssql - handle timestamp and computed columns in clonerows 2025-06-11 11:28:46 +02:00
SPRINX0\prochazka
34bdb72ffd #1118 2025-06-11 11:09:07 +02:00
SPRINX0\prochazka
2ef7c63047 copy column names #1119 2025-06-11 10:34:22 +02:00
SPRINX0\prochazka
95f5417761 fixed grid performance problem - limited length of cell string 2025-06-11 09:30:58 +02:00
SPRINX0\prochazka
4922ec4499 Merge branch 'master' into feature/firebird 2025-06-11 08:09:28 +02:00
SPRINX0\prochazka
20d947a199 readme for firebird 2025-06-10 16:46:16 +02:00
Pavel
c9444c5318 Merge branch 'master' into feature/firebird 2025-06-10 14:57:26 +02:00
SPRINX0\prochazka
871dc90ee4 fixed community build (missing JslChart.svelte) 2025-06-09 16:32:57 +02:00
Jan Prochazka
d1925945b4 skipped failed tests 2025-06-09 14:43:55 +02:00
Jan Prochazka
6625080fde Merge pull request #1139 from dbgate/feature/duckdb-1132
Feature/duckdb 1132
2025-06-09 10:52:58 +02:00
CI workflows
1110609e39 chore: auto-update github workflows 2025-06-09 08:39:51 +00:00
CI workflows
f21d2c7253 Update pro ref 2025-06-09 08:39:32 +00:00
SPRINX0\prochazka
9c1d330945 SYNC: data label formatter 2025-06-09 08:39:21 +00:00
SPRINX0\prochazka
cd7800056c SYNC: commented out charts test 2025-06-09 08:25:54 +00:00
CI workflows
7ff4bec3bc chore: auto-update github workflows 2025-06-09 08:16:27 +00:00
CI workflows
d305cf2167 Update pro ref 2025-06-09 08:16:07 +00:00
SPRINX0\prochazka
e77b83bd92 SYNC: chart labels for pie chart 2025-06-09 08:15:54 +00:00
CI workflows
171d58658a chore: auto-update github workflows 2025-06-09 07:16:15 +00:00
CI workflows
a6f6bc4c0a Update pro ref 2025-06-09 07:15:57 +00:00
Jan Prochazka
f03cffe3f8 SYNC: Merge pull request #4 from dbgate/feature/charts 2025-06-09 07:15:45 +00:00
Pavel
809dca184e chore: add start:api:watch script 2025-06-05 20:26:41 +02:00
Pavel
ecda226949 fix: correctly map DuckDBTimeValue to string 2025-06-05 20:26:33 +02:00
Pavel
ff1b58ebd8 fix: correctly map DuckDBDateValue to string 2025-06-05 20:26:17 +02:00
Pavel
5760ada3b4 feat: transform firebird model rows 2025-06-05 16:31:17 +02:00
Pavel
b74b6b3284 feat: transform rows suport for json lines reader 2025-06-05 16:28:50 +02:00
Pavel
e4cc4b6f58 fix: process blob values, update firebird dialect 2025-06-05 16:27:28 +02:00
Pavel
dd90851477 fix: update collumns object id 2025-06-05 14:24:33 +02:00
Pavel
da9b127468 fix: group indexes for firebird 2025-06-05 12:31:36 +02:00
Pavel
d6b05e44cb fix: skip all table renames for firebird 2025-06-05 11:51:17 +02:00
Pavel
58c1b5b98d fix:divide field length by 4 2025-06-05 11:35:00 +02:00
Pavel
c270cba8d6 fix: apply skipRenameTable filter correctly 2025-06-05 10:37:04 +02:00
Pavel
58f1f749fc fix: respect implicitNullDeclaration in alter database queries 2025-06-03 18:15:07 +02:00
Pavel
38d87a7c8f fix: update column deps for firebird 2025-06-03 17:39:49 +02:00
Pavel
4e13598708 fix: add custom create index to firebird dumper 2025-06-03 17:39:40 +02:00
Pavel
4177448d32 fix: correctly processing script outside of transactions for firebird 2025-06-03 17:19:02 +02:00
Pavel
e4911a6f82 feat: discard result support for firebird query 2025-06-03 17:18:44 +02:00
Pavel
159224700f fix: skip table rename for firebird 2025-06-03 16:52:51 +02:00
Pavel
696d4e7342 feat: skip change column for firebird 2025-06-03 16:36:40 +02:00
Pavel
ffb6cfaa4a feat: add writeTable to firebird 2025-06-03 14:20:54 +02:00
Pavel
b8899fcafa fix: always pass runDeployInTransaction to scipt 2025-06-03 14:11:01 +02:00
Pavel
aba829c991 feat: skip data modifiaciton for firebird 2025-06-03 13:56:24 +02:00
Pavel
8f4c61c259 fix: correct transaction syntax for firebird 2025-06-03 13:56:13 +02:00
Pavel
a19648a6e8 fix: correct runSqlInTransaction 2025-06-03 13:07:46 +02:00
Pavel
d5c0f7045e fix: add arguments to runSqlInTransaction 2025-06-03 12:44:04 +02:00
SPRINX0\prochazka
6f69205818 v6.4.3-premium-beta.4 2025-05-30 13:21:10 +02:00
SPRINX0\prochazka
8166da548c db2 config 2025-05-30 10:56:18 +02:00
SPRINX0\prochazka
d54f7293b7 db2 test container config 2025-05-30 08:16:18 +02:00
Pavel
225520a765 feat: add useTransaction option to deployDb 2025-05-29 22:52:38 +02:00
CI workflows
af1eccde8e chore: auto-update github workflows 2025-05-29 16:11:09 +00:00
CI workflows
5d37280643 Update pro ref 2025-05-29 16:10:50 +00:00
SPRINX0\prochazka
80597039f5 SYNC: charts 2025-05-29 16:10:38 +00:00
Pavel
2766aedc01 fix: correct databaseFile for engines with databseFileLocationOnServer 2025-05-29 15:26:58 +02:00
Pavel
da3e12cb7e feat: add indexes to firebird 2025-05-29 15:20:51 +02:00
Pavel
8baff1b0d2 fix: add object id condtion to firbeird uniques 2025-05-29 15:20:39 +02:00
Pavel
4ff5f9204e fix: correctly skip inc analysis 2025-05-29 15:15:57 +02:00
Pavel
515339bbd8 fix: add changeColumnDependencies to firebird 2025-05-29 14:57:38 +02:00
SPRINX0\prochazka
943634b0e2 Merge branch 'master' of https://github.com/dbgate/dbgate 2025-05-29 14:56:12 +02:00
SPRINX0\prochazka
212b26b960 temporatily disable MognoDB profiler support 2025-05-29 14:56:07 +02:00
CI workflows
c0b41987aa chore: auto-update github workflows 2025-05-29 12:55:11 +00:00
CI workflows
b4ef640052 Update pro ref 2025-05-29 12:54:50 +00:00
SPRINX0\prochazka
db6b7f52eb removed charts & profiler 2025-05-29 14:54:08 +02:00
Pavel
55b4b9e02a feat: add script method to firebird driver 2025-05-29 14:53:54 +02:00
Pavel
4e6ae93b13 fix: add dropColumnDependencies to firebird 2025-05-29 14:53:34 +02:00
Pavel
c9a5fe5676 fix: make firebird singledatabase 2025-05-29 14:53:09 +02:00
Pavel
a5adfb7c7f feat: add uniques to firebird 2025-05-29 14:52:44 +02:00
CI workflows
1794b86041 chore: auto-update github workflows 2025-05-29 12:40:42 +00:00
SPRINX0\prochazka
f405124ce4 fix 2025-05-29 14:40:20 +02:00
CI workflows
25060c1477 chore: auto-update github workflows 2025-05-29 12:31:31 +00:00
SPRINX0\prochazka
6ad218f354 upload artifacts forr check build 2025-05-29 14:31:08 +02:00
CI workflows
d1c52548b0 chore: auto-update github workflows 2025-05-29 12:05:29 +00:00
SPRINX0\prochazka
9dc847b72f fix 2025-05-29 14:05:10 +02:00
CI workflows
5b04adb21f chore: auto-update github workflows 2025-05-29 12:00:15 +00:00
SPRINX0\prochazka
356d25e548 build app check 2025-05-29 13:59:53 +02:00
SPRINX0\prochazka
a9958af818 v6.4.3-beta.3 2025-05-29 13:21:17 +02:00
Jan Prochazka
fb359b7f87 Merge pull request #1120 from ProjectInfinity/command-palette-redesign
feat: redesign CommandPalette
2025-05-29 12:50:18 +02:00
SPRINX0\prochazka
7f087819a6 Merge branch 'feature/cloud' 2025-05-29 12:49:01 +02:00
SPRINX0\prochazka
e836fa3d38 show license - better UX 2025-05-29 12:44:31 +02:00
Pavel
9a69f1108d fix: skipIncrementalAnalysis for firebird 2025-05-29 11:22:16 +02:00
Pavel
2c5c58dc90 fix: map datatype to lowerase variants in firebird 2025-05-29 10:23:26 +02:00
SPRINX0\prochazka
cb50d2838a license limit modal 2025-05-28 17:37:55 +02:00
SPRINX0\prochazka
aff7125914 Revert "tmp change"
This reverts commit 45d82dce04.
2025-05-28 16:44:58 +02:00
SPRINX0\prochazka
45d82dce04 tmp change 2025-05-28 15:55:53 +02:00
SPRINX0\prochazka
7a3b27227a stats fixed 2025-05-28 13:21:52 +02:00
SPRINX0\prochazka
7b50a19b2c cloud file, folder operations 2025-05-28 10:46:35 +02:00
SPRINX0\prochazka
741b942dea cloud files WIP 2025-05-28 08:25:10 +02:00
Pavel
f7ca64a49d fix: remove container_name from workflows 2025-05-27 19:16:21 +02:00
Pavel
2f7b3455e5 feat: add views to firebird 2025-05-27 19:05:02 +02:00
Pavel
1568dfc183 feat: add object ids to firebird queries 2025-05-27 18:49:31 +02:00
SPRINX0\prochazka
d3a5df0007 delete conn on cloud, save file to cloud WIP 2025-05-27 16:46:53 +02:00
Pavel
c20cac621a fix: update firebird workflows 2025-05-27 12:52:46 +02:00
SPRINX0\prochazka
74560c3289 duplicate cloud connection 2025-05-26 17:59:03 +02:00
SPRINX0\prochazka
f94bf3f8ce cloud fixes 2025-05-26 17:24:13 +02:00
SPRINX0\prochazka
d26db7096d refactor - handle cloud listeners 2025-05-26 17:02:09 +02:00
SPRINX0\prochazka
afde0a7423 cloud connection save 2025-05-26 16:46:04 +02:00
SPRINX0\prochazka
cc930a3ff9 cloud connections expansion fix 2025-05-26 15:50:48 +02:00
SPRINX0\prochazka
60ecdadc74 cloud account in statusbar 2025-05-26 15:43:14 +02:00
SPRINX0\prochazka
82fc1850cf API rename 2025-05-26 14:43:10 +02:00
SPRINX0\prochazka
88f937f73e save new connection on cloud 2025-05-26 14:41:41 +02:00
SPRINX0\prochazka
b3497c7306 database content UX 2025-05-26 12:58:20 +02:00
SPRINX0\prochazka
366ab2e0cd refresh public cloud files only on session start 2025-05-26 12:23:38 +02:00
SPRINX0\prochazka
98e4fabd2e cloud fixes 2025-05-26 12:06:33 +02:00
SPRINX0\prochazka
716c3573fd fixed scroll 2025-05-26 09:58:25 +02:00
Pavel
842d8dd780 feat: add triggers to firebird tests 2025-05-23 01:00:18 +02:00
Pavel
c767dfb22e fix: add createSql to firebird triggers 2025-05-23 00:59:34 +02:00
Pavel
f94901c3b2 feat: handle implicit null declation in alter-table 2025-05-23 00:28:27 +02:00
SPRINX0\prochazka
c9638aefe9 show cloud API errors 2025-05-22 17:30:24 +02:00
SPRINX0\prochazka
8bd4721686 delete, rename folders 2025-05-22 17:09:25 +02:00
Pavel
808f7504c3 feat: add changeColumn, renameColumn, dropColumn to firebird dumper 2025-05-22 16:49:29 +02:00
Pavel
8ea7d3d5e8 fix: correctly show default value for firebird 2025-05-22 16:48:11 +02:00
Pavel
d4931890ae fix: use table name as pureName for firebird column 2025-05-22 15:50:40 +02:00
SPRINX0\prochazka
f4a879a452 encrypting cloud content 2025-05-22 15:48:59 +02:00
Pavel
78521ffdb4 fix: remove schemaName from firebird columns query 2025-05-22 15:44:15 +02:00
Pavel
8ea3f80b97 feat: add firebird to github workflows 2025-05-22 15:40:43 +02:00
Pavel
0d8d87857c fix: skip autoIncrement for firedb tests 2025-05-22 15:40:23 +02:00
Pavel
3a3a261d9c fix: correct firedb columns notNull query 2025-05-22 15:40:07 +02:00
Pavel
2e00daf63c fix: correct implicitNullDeclaration usage in dumper 2025-05-22 15:39:46 +02:00
SPRINX0\prochazka
1b8bb0c1fd fixes 2025-05-22 13:20:39 +02:00
SPRINX0\prochazka
5c33579544 cloud content fixes 2025-05-22 13:07:45 +02:00
SPRINX0\prochazka
f8081ff09e cloud database content 2025-05-22 10:43:46 +02:00
SPRINX0\prochazka
01b7eeeecf cloud widgets refactor 2025-05-22 10:29:36 +02:00
SPRINX0\prochazka
d2f4c374a9 smaller widget icon panel 2025-05-22 10:12:48 +02:00
SPRINX0\prochazka
07073eebe9 connecting to cloud database 2025-05-22 09:46:07 +02:00
SPRINX0\prochazka
590a4ae476 show cloud content 2025-05-21 17:09:16 +02:00
SPRINX0\prochazka
b553a81d47 load cloud folders 2025-05-21 14:48:28 +02:00
SPRINX0\prochazka
7d4e53e413 content cloud WIP 2025-05-20 16:43:06 +02:00
SPRINX0\prochazka
839b0f6f5e public cloud search 2025-05-20 13:04:57 +02:00
CI workflows
893c5da4ef chore: auto-update github workflows 2025-05-20 08:52:30 +00:00
CI workflows
f9b893edfa Update pro ref 2025-05-20 08:52:14 +00:00
SPRINX0\prochazka
b4fadb39bf cloud files - opening 2025-05-19 16:59:56 +02:00
SPRINX0\prochazka
310f8bf6f7 public cloud widget 2025-05-19 16:33:04 +02:00
CI workflows
903a26a330 chore: auto-update github workflows 2025-05-19 13:24:23 +00:00
CI workflows
41ebd39810 Update pro ref 2025-05-19 13:24:06 +00:00
SPRINX0\prochazka
281de5196e update cloud files 2025-05-19 10:39:35 +02:00
CI workflows
a9ab864cbb chore: auto-update github workflows 2025-05-19 08:11:23 +00:00
CI workflows
f3ff910821 Update pro ref 2025-05-19 08:11:00 +00:00
SPRINX0\prochazka
ba5179f1e8 Merge branch 'master' into feature/cloud 2025-05-16 13:56:34 +02:00
SPRINX0\prochazka
05e8f6ed78 v6.4.3-alpha.1 2025-05-16 13:55:45 +02:00
SPRINX0\prochazka
23150815a0 use default target schema in dbDeploy 2025-05-16 13:55:14 +02:00
SPRINX0\prochazka
a50f223fe3 cloud icons WIP 2025-05-16 13:54:08 +02:00
SPRINX0\prochazka
9329345d98 basic cloud signin workflow 2025-05-16 12:19:26 +02:00
SPRINX0\prochazka
c71c32b363 readme 2025-05-16 08:10:20 +02:00
SPRINX0\prochazka
5590aa7234 feedback URL 2025-05-16 08:04:44 +02:00
SPRINX0\prochazka
4a3491e0b5 feedback menu link 2025-05-16 08:03:08 +02:00
SPRINX0\prochazka
e8cb87ae3d feedback link 2025-05-16 08:00:33 +02:00
Pavel
2f6427af32 feat: set implicitNullDeclaration to true for firebird 2025-05-15 17:00:02 +02:00
CI workflows
5564047001 chore: auto-update github workflows 2025-05-15 14:08:12 +00:00
CI workflows
22577c5f87 Update pro ref 2025-05-15 14:07:52 +00:00
Jan Prochazka
4dc2627da2 cloud login WIP 2025-05-15 16:01:51 +02:00
Pavel
05aaf0de9f feat: add firebird to test engines 2025-05-15 15:05:44 +02:00
Pavel
951bfa23f3 feat: firebird use attachOrCreate on connect, add dbFileExtension and locaiton on server to tests 2025-05-15 15:04:54 +02:00
Pavel
7ac6cfcf25 feat: offsetFirstSkipRangeSyntax support 2025-05-15 13:32:07 +02:00
Pavel
06055a7c4c fix: remove schema from firebird 2025-05-15 13:22:01 +02:00
ProjectInfinity
b33198d1bf feat: redesign CommandPalette 2025-05-14 19:56:49 +02:00
SPRINX0\prochazka
f826b9eb6e 6.4.2 changelog 2025-05-14 14:59:49 +02:00
SPRINX0\prochazka
2b58121552 v6.4.2 2025-05-14 14:53:20 +02:00
SPRINX0\prochazka
762547d0e9 v4.6.2-beta.4 2025-05-14 13:53:26 +02:00
SPRINX0\prochazka
727523eb3f ts fix 2025-05-14 13:41:22 +02:00
SPRINX0\prochazka
5bbdb66eb2 v6.4.2-premium-beta.3 2025-05-14 13:32:29 +02:00
Jan Prochazka
c6eff4f90d Merge pull request #1105 from mbrevda/docker-lables
Add source label to container
2025-05-14 13:31:55 +02:00
Jan Prochazka
2559173c2c Merge pull request #1116 from dbgate/feature/fix-mysql-triggers-snapshot
fix: add triggers to mysql snapshot
2025-05-14 13:31:16 +02:00
SPRINX0\prochazka
8adea132ef Merge branch 'feature/scan-redis-keys' 2025-05-14 13:28:29 +02:00
SPRINX0\prochazka
9d924f8d1c optimalized loading redis keys info - using pipeline 2025-05-14 13:27:19 +02:00
SPRINX0\prochazka
1b297fed90 fix sorting 2025-05-14 13:00:08 +02:00
SPRINX0\prochazka
2c2a93c440 key count limit 2025-05-14 12:53:51 +02:00
SPRINX0\prochazka
bb076cce5d redis load key refactor 2025-05-14 12:44:53 +02:00
SPRINX0\prochazka
b16b02c3f1 tree loader 2025-05-14 10:32:42 +02:00
Nybkox
3e0f834796 feat: firebird FKs, PKs, procedures, funcs 2025-05-13 19:40:51 +02:00
SPRINX0\prochazka
0af38c6e0e redis load key refactor #1062 2025-05-13 17:38:56 +02:00
Nybkox
85f7011e03 fix: remove empty getFastSnapshot 2025-05-13 17:18:17 +02:00
SPRINX0\prochazka
e8d5412e14 survey link 2025-05-13 16:22:13 +02:00
Nybkox
170cf4753e fix: remove object conditions from modification queries 2025-05-13 14:10:00 +02:00
Nybkox
660e76145e fix: add schedulerEventsModifications query 2025-05-13 13:04:10 +02:00
Nybkox
31a6f7b621 fix: add triggersModifications query 2025-05-13 13:03:59 +02:00
Nybkox
f6699ad93b fix: add triggers to mysql snapshot 2025-05-13 12:35:24 +02:00
SPRINX0\prochazka
6e508e4454 Merge branch 'master' of https://github.com/dbgate/dbgate 2025-05-13 09:51:09 +02:00
SPRINX0\prochazka
2b101844e9 Shell: Run script 2025-05-13 09:51:00 +02:00
SPRINX0\prochazka
fb036935e6 SYNC: Limit query result rows #1098 2025-05-12 13:55:53 +00:00
SPRINX0\prochazka
c3e09ddab0 SYNC: pgsql: added notice detail #1108 2025-05-12 11:06:16 +00:00
SPRINX0\prochazka
36ae07074d SYNC: View PostgreSQL server output #1108 2025-05-12 11:02:21 +00:00
SPRINX0\prochazka
a4518ce261 SYNC: handled double quote strings in MySQL #1107 2025-05-12 10:47:40 +00:00
SPRINX0\prochazka
861ea7ef94 v6.4.2-premium-beta.2 2025-05-12 10:31:39 +02:00
SPRINX0\prochazka
541af0b77e fixed loading keys in redis 2025-05-12 10:28:28 +02:00
Nybkox
3fd3de1828 feat: add firebird triggers 2025-05-07 00:29:42 +02:00
Nybkox
3e2840ca15 fix: add schema to tables 2025-05-07 00:13:40 +02:00
Nybkox
839ec9a456 feat: basic firebird analyser 2025-05-07 00:00:34 +02:00
Nybkox
bac8bd0006 feat: add firebird to to tests compose 2025-05-06 15:51:49 +02:00
SPRINX0\prochazka
8c1b51b7e9 v6.4.2-beta.1 2025-05-06 14:04:29 +02:00
SPRINX0\prochazka
a71c4fe7ec mognoDB bigint support 2025-05-06 12:51:37 +02:00
SPRINX0\prochazka
b9d4197b5c mongoDB - bigint support WIP 2025-05-05 17:16:46 +02:00
SPRINX0\prochazka
ce7559087e duckdb - fixed bigint processing 2025-05-05 16:24:55 +02:00
SPRINX0\prochazka
110d87e512 bigint support #1087 2025-05-05 16:04:21 +02:00
Moshe Brevda
cd817714cd Add source label to container 2025-05-05 12:39:30 +03:00
Jan Prochazka
23db345756 v6.4.1 2025-05-03 19:09:19 +02:00
Jan Prochazka
16990bd0c3 changelog fix 2025-05-03 19:03:06 +02:00
Jan Prochazka
2e3b770bea v6.4.1-beta.2 2025-05-03 17:30:48 +02:00
Jan Prochazka
28f62623bf SYNC: Added option to dump CREATE/DROP database in mysql dump #1103 2025-05-03 07:59:55 +00:00
Jan Prochazka
c9f3e8cb9f Update README.md 2025-05-01 07:27:03 +02:00
SPRINX0\prochazka
6b751eb715 SYNC: export connections modal screenshot 2025-04-30 08:49:34 +00:00
SPRINX0\prochazka
5d953da267 v6.4.0 2025-04-30 09:55:35 +02:00
SPRINX0\prochazka
e87ae31a51 changelog 2025-04-30 09:54:56 +02:00
SPRINX0\prochazka
9f029b892b SYNC: fixed opening data deployer from dataabase 2025-04-30 07:41:15 +00:00
SPRINX0\prochazka
40a9ced0f7 changelog 2025-04-30 09:30:54 +02:00
SPRINX0\prochazka
87fbd7e5da v6.3.4-premium-beta.5 2025-04-30 08:49:58 +02:00
SPRINX0\prochazka
bca5514a76 v6.3.4-beta.4 2025-04-30 08:46:18 +02:00
SPRINX0\prochazka
62ddbb20ac mongodb - filter by objectId imrpoved 2025-04-30 08:36:56 +02:00
SPRINX0\prochazka
9d376961f4 API: queryReader accepts systemConnection (fixes duckDb export filtered data) 2025-04-29 17:15:04 +02:00
SPRINX0\prochazka
e77aa00bcd duckdb doesn't support readonly sessions 2025-04-29 14:58:25 +02:00
SPRINX0\prochazka
465330820d upgraded tedious driver 2025-04-29 14:57:20 +02:00
SPRINX0\prochazka
ec9cbba67e v6.3.4-premium-beta.3 2025-04-29 14:36:53 +02:00
SPRINX0\prochazka
8961ea6fc9 Cancel script execution after first error #1070 2025-04-29 14:05:13 +02:00
SPRINX0\prochazka
ca7ca9da81 fixed JSON-to-Grid only works if there is no newline #1085 2025-04-29 13:26:20 +02:00
SPRINX0\prochazka
2a234f14df fixed: Selection rectangle remains visible after closing JSONB edit cell value form #1031 2025-04-29 13:14:58 +02:00
Jan Prochazka
3ff97bf628 Merge pull request #1101 from dbgate/feature/duckdb-2
Feature/duckdb 2
2025-04-29 12:57:48 +02:00
SPRINX0\prochazka
416d6f2aef SYNC: data deployer menu conditions 2025-04-29 10:55:29 +00:00
Nybkox
ecaafaca69 feat: use straming api for duckdb driver's readQuery 2025-04-29 11:11:21 +02:00
Nybkox
fdb14cd49b fix: typo in jsdoc types 2025-04-29 01:58:25 +02:00
Nybkox
070e955b89 fix: remove duplicated typecheck 2025-04-29 01:57:42 +02:00
Nybkox
9390ab3c6c fix: do not skip non-returnin statements 2025-04-29 01:32:55 +02:00
Nybkox
f67221ee01 feat: use streaming api for duckdb driver's stream method 2025-04-29 01:02:09 +02:00
Nybkox
e09294d9aa feat: add normalization to duck db types 2025-04-29 00:15:50 +02:00
SPRINX0\prochazka
5675acb71a v6.3.4-beta.2 2025-04-28 16:46:52 +02:00
SPRINX0\prochazka
0305a5dcef close tab with confirmation on double ctrl+W 2025-04-28 16:45:21 +02:00
SPRINX0\prochazka
5f03340454 fixed copy (NULL) value 2025-04-28 16:31:10 +02:00
SPRINX0\prochazka
0f69ba46c5 #1089 split queries using blank lines 2025-04-28 15:55:08 +02:00
SPRINX0\prochazka
71ecb6bd4e upgraded dbgate-query-splitter 2025-04-28 15:45:36 +02:00
Jan Prochazka
2b712cc808 Merge pull request #1091 from dbgate/feature/duckdb-2
Feature/duckdb 2
2025-04-28 15:44:26 +02:00
SPRINX0\prochazka
3a68b7b554 handler message errors 2025-04-28 15:14:09 +02:00
SPRINX0\prochazka
e41727a1fc fix 2025-04-28 14:19:48 +02:00
Jan Prochazka
857d0f3316 skip drop references test 2025-04-28 14:12:07 +02:00
Jan Prochazka
1f68f62689 duckdb analyser fixes 2025-04-28 14:07:42 +02:00
Jan Prochazka
09b43a8e95 fixed incorrect naming 2025-04-28 13:31:01 +02:00
Jan Prochazka
b8d765d229 duckdb: rename column + skiptests 2025-04-28 13:06:26 +02:00
Jan Prochazka
06a919ff8d alter table - flat tests 2025-04-28 12:36:18 +02:00
Jan Prochazka
ea1e7769b1 indexes tests 2025-04-28 10:48:23 +02:00
SPRINX0\prochazka
d5e6f99819 duckdb index columns 2025-04-28 10:47:27 +02:00
SPRINX0\prochazka
a5ab9726dd CRW fixes 2025-04-28 10:15:47 +02:00
SPRINX0\prochazka
6464fc56d8 reverted test commented 2025-04-28 10:01:37 +02:00
SPRINX0\prochazka
156e1b928c quick export - works for DuckDB 2025-04-28 09:37:44 +02:00
Jan Prochazka
edf17b8100 use JSON format form quick-exports 2025-04-25 16:45:51 +02:00
Jan Prochazka
5ab980ce1a export from duckdb works 2025-04-25 16:06:10 +02:00
SPRINX0\prochazka
f1d80fadc4 duckdb imports/exports WIP 2025-04-24 18:44:10 +02:00
SPRINX0\prochazka
d331d48ca2 scriptWriterEval 2025-04-24 16:36:07 +02:00
SPRINX0\prochazka
e740db11ed refgactor, code cleanup 2025-04-24 15:45:46 +02:00
SPRINX0\prochazka
6cff7b3c30 duckdb: catch syntax error in query 2025-04-24 15:16:39 +02:00
SPRINX0\prochazka
88cdd2fcbf not start server connection for single-database 2025-04-24 15:10:18 +02:00
SPRINX0\prochazka
55896be694 changed query workflow for duckdb 2025-04-24 14:56:25 +02:00
SPRINX0\prochazka
c4f17e42e1 handleQueryStream refactor 2025-04-24 13:53:41 +02:00
SPRINX0\prochazka
e8b11bd42a copied from master 2025-04-24 13:04:16 +02:00
SPRINX0\prochazka
a566fb3988 typo + bigint converted to numbers 2025-04-24 11:02:44 +02:00
SPRINX0\prochazka
de071b37eb duckdb fixes 2025-04-24 10:54:14 +02:00
SPRINX0\prochazka
7b4d408733 duckdb - removed dummy fast snapshot 2025-04-24 10:31:39 +02:00
SPRINX0\prochazka
06478d89ea Merge branch 'master' into feature/duckdb-2 2025-04-24 10:05:51 +02:00
CI workflows
7cedf4c620 chore: auto-update github workflows 2025-04-24 08:05:50 +00:00
CI workflows
ef1ea5eeee Update pro ref 2025-04-24 08:05:35 +00:00
SPRINX0\prochazka
96f222db94 cloud upgrade moved to pro repo 2025-04-24 10:03:14 +02:00
Jan Prochazka
065eb9b878 duckdb fixes 2025-04-24 09:52:30 +02:00
Jan Prochazka
ed583d80a3 test fix 2025-04-24 09:33:45 +02:00
SPRINX0\prochazka
7752db3916 Merge branch 'master' into feature/duckdb-2 2025-04-24 09:25:45 +02:00
SPRINX0\prochazka
e18254e4d8 SYNC: fixed e2e test 2025-04-24 07:21:26 +00:00
CI workflows
555fc97e8f chore: auto-update github workflows 2025-04-23 15:08:28 +00:00
CI workflows
f135adfdba Update pro ref 2025-04-23 15:08:04 +00:00
SPRINX0\prochazka
72b766f32a SYNC: test fix WIP 2025-04-23 15:07:50 +00:00
Jan Prochazka
5278861ccd fixed replicator for oracle 2025-04-23 15:54:02 +02:00
SPRINX0\prochazka
ebcf88070c test fixes 2025-04-23 15:29:11 +02:00
SPRINX0\prochazka
273811bbb8 changelog 2025-04-23 15:14:15 +02:00
CI workflows
26b53e725d chore: auto-update github workflows 2025-04-23 12:54:39 +00:00
CI workflows
5434ec85e2 Update pro ref 2025-04-23 12:54:18 +00:00
SPRINX0\prochazka
91f438aeff Merge branch 'master' of https://github.com/dbgate/dbgate 2025-04-23 14:53:32 +02:00
SPRINX0\prochazka
501f67ebe7 added storageModel 2025-04-23 14:53:26 +02:00
SPRINX0\prochazka
be52fef0bb SYNC: removed console log 2025-04-23 11:23:48 +00:00
CI workflows
2c0cf40a15 chore: auto-update github workflows 2025-04-23 11:18:49 +00:00
CI workflows
8566070d9b Update pro ref 2025-04-23 11:18:34 +00:00
Jan Prochazka
8f4118a6b8 SYNC: Merge pull request #3 from dbgate/feature/zip 2025-04-23 11:18:20 +00:00
SPRINX0\prochazka
54c53f0b56 v6.3.4-premium-beta.1 2025-04-15 10:21:20 +02:00
SPRINX0\prochazka
eafcee8c67 handled error when getting DB sever version 2025-04-15 10:20:31 +02:00
Jan Prochazka
5c8e1e0f4a export conns base 2025-04-11 10:34:13 +02:00
SPRINX0\prochazka
76043d5876 Merge branch 'master' of https://github.com/dbgate/dbgate 2025-04-10 17:43:32 +02:00
SPRINX0\prochazka
936f3b0752 new storage fake method 2025-04-10 17:43:29 +02:00
SPRINX0\prochazka
9380608781 SYNC: archive UX 2025-04-09 14:30:40 +00:00
SPRINX0\prochazka
544b75bcd5 SYNC: fixed margin-right + in showing JSONL data 2025-04-09 12:19:14 +00:00
SPRINX0\prochazka
2ae63d2323 SYNC: text 2025-04-09 11:42:51 +00:00
SPRINX0\prochazka
f24717a3a3 SYNC: changelog 2025-04-09 11:26:30 +00:00
SPRINX0\prochazka
f0f5558f3e SYNC: pgdump custom args 2025-04-09 11:13:42 +00:00
SPRINX0\prochazka
3d8c732258 SYNC: #1092 - mysql backup arguments 2025-04-09 11:12:14 +00:00
SPRINX0\prochazka
f312237bd5 v6.3.3 2025-04-09 12:27:45 +02:00
SPRINX0\prochazka
952dde3fef changelog 2025-04-09 10:59:35 +02:00
CI workflows
e5387f6d06 chore: auto-update github workflows 2025-04-09 08:57:04 +00:00
CI workflows
93d6697b4e Update pro ref 2025-04-09 08:56:47 +00:00
SPRINX0\prochazka
bad2f3415a health status in sprinx format 2025-04-09 10:41:43 +02:00
SPRINX0\prochazka
d2f5d97282 v6.3.3-premium-beta.2 2025-04-09 10:36:41 +02:00
SPRINX0\prochazka
f8c3fef839 changelog 2025-04-09 10:31:26 +02:00
SPRINX0\prochazka
14b47a929f SYNC: handle subprocess errors 2025-04-09 07:40:54 +00:00
SPRINX0\prochazka
4b3c0466eb SYNC: fixed allocating port for native backup & restore #1092 2025-04-09 07:28:21 +00:00
Nybkox
041397b137 chore: remove test db file 2025-04-08 20:24:24 +02:00
Nybkox
6548c286a6 chore: remove unused code 2025-04-08 20:23:23 +02:00
Nybkox
ccf78285b6 feat: file open duckdb 2025-04-08 20:23:16 +02:00
Nybkox
5e9366fa92 feat: cli open duckdb 2025-04-08 20:22:41 +02:00
Nybkox
0e30cb1439 fix: set multipleSchema to true for duck db 2025-04-08 19:19:23 +02:00
Nybkox
b8d86518e7 feat: add new duck db command 2025-04-08 18:56:17 +02:00
SPRINX0\prochazka
725399ac7c SYNC: admin users screenshot 2025-04-08 16:09:13 +00:00
Nybkox
ca18994092 fix: update duckdb dialects 2025-04-08 18:07:22 +02:00
Nybkox
caefc438b9 chore: remove unused imports 2025-04-08 18:07:14 +02:00
Nybkox
0ece8c7dec feat: add listSchemas to duckdb 2025-04-08 18:07:09 +02:00
Nybkox
e7c42f3623 fix: remove temp duckdb connection cache 2025-04-08 18:06:46 +02:00
Nybkox
18faf89b89 fix: update listSchemas typing 2025-04-08 18:06:09 +02:00
SPRINX0\prochazka
3a750ae6a2 SYNC: changelog 2025-04-08 15:52:03 +00:00
SPRINX0\prochazka
48c614d8c3 SYNC: fixed map export 2025-04-08 15:37:30 +00:00
Jan Prochazka
c37d502c27 Merge pull request #1090 from dataspun/leaflet-map-scale
show scale bar on map
2025-04-08 17:30:05 +02:00
SPRINX0\prochazka
3fed7a081d v6.3.3-premium-beta.1 2025-04-08 17:28:45 +02:00
CI workflows
e75497d03b chore: auto-update github workflows 2025-04-08 15:24:36 +00:00
CI workflows
6d1421f1b7 Update pro ref 2025-04-08 15:24:21 +00:00
SPRINX0\prochazka
dd210be037 SYNC: encrypt superadmin password 2025-04-08 15:24:09 +00:00
CI workflows
7c7d6ad548 chore: auto-update github workflows 2025-04-08 15:12:13 +00:00
CI workflows
2f471c0e3f Update pro ref 2025-04-08 15:11:06 +00:00
SPRINX0\prochazka
cff219674f SYNC: fixed backup & restore under SSH tunnel #1092 2025-04-08 15:10:51 +00:00
SPRINX0\prochazka
e35f9eb75b SYNC: send encryption key to child processes 2025-04-08 14:30:36 +00:00
SPRINX0\prochazka
ef1eff2ecb SYNC: fixed testing compose 2025-04-08 12:58:23 +00:00
SPRINX0\prochazka
244ff61fb3 SYNC: fix 2025-04-08 12:24:15 +00:00
CI workflows
50e2623f19 chore: auto-update github workflows 2025-04-08 11:55:10 +00:00
CI workflows
fd0b997c13 Update pro ref 2025-04-08 11:54:49 +00:00
SPRINX0\prochazka
13d057e4f7 SYNC: filterable table control 2025-04-08 11:54:35 +00:00
Nybkox
d1a6be6ca6 fix: parse res to int in tests 2025-04-08 11:05:23 +02:00
Nybkox
edece02c13 feat: enable duckdb test on ci 2025-04-08 11:05:23 +02:00
Nybkox
d68cf4e44d fix: remove test throw 2025-04-08 11:05:23 +02:00
Nybkox
9d85a58634 fix: skipDeploy tests for duckdb 2025-04-08 11:05:23 +02:00
Nybkox
c4a4cd0957 fix: parse cnt as int 2025-04-08 11:05:23 +02:00
Nybkox
5af7615054 fix: convert dataType to stirng 2025-04-08 11:05:23 +02:00
Nybkox
a68a1334fc feat(duckdb): add rename sql object 2025-04-08 11:05:23 +02:00
Nybkox
dd3e38355c chore: remove test logging 2025-04-08 11:05:23 +02:00
SPRINX0\prochazka
890461bcf8 duckDb: singleConnection query 2025-04-08 11:05:23 +02:00
SPRINX0\prochazka
750265cb79 removed incorrect dataType of duckdb result 2025-04-08 11:05:23 +02:00
SPRINX0\prochazka
004de824ba Merge branch 'master' into feature/duckdb-2 2025-04-08 11:05:23 +02:00
SPRINX0\prochazka
fc43b35628 SYNC: fixed e2e tests 2025-04-08 08:42:31 +00:00
CI workflows
696653f945 chore: auto-update github workflows 2025-04-08 08:10:26 +00:00
CI workflows
157dca50e9 Update pro ref 2025-04-08 08:10:10 +00:00
Jan Prochazka
7d2130b229 SYNC: Merge pull request #2 from dbgate/feature/admin-ui 2025-04-08 08:09:56 +00:00
Jeremy
2cc81211af show scale bar on map 2025-04-07 12:48:31 -07:00
SPRINX0\prochazka
ea9a5b0eb0 column map modal validation 2025-04-03 17:16:02 +02:00
SPRINX0\prochazka
e60cee6a73 UX fix 2025-04-03 15:22:07 +02:00
SPRINX0\prochazka
a3ee60a464 fixed column map modal 2025-04-03 15:09:21 +02:00
Jan Prochazka
28595cbeb3 Merge pull request #1075 from dbgate/feature/json-wrap
feat: add jsonPreviewWrap option to settings
2025-04-03 14:49:54 +02:00
Jan Prochazka
dbdbf5210e Merge pull request #1082 from hansemschnokeloch/patch-1
Fix typo in Analyser.js
2025-04-03 12:35:02 +02:00
SPRINX0\prochazka
a4876f6f14 v6.3.2 2025-04-02 14:08:24 +02:00
SPRINX0\prochazka
9835ae7e50 v6.3.2-premium-beta.4 2025-04-02 13:35:11 +02:00
SPRINX0\prochazka
59f763162c SYNC: fixed zoom 2025-04-02 11:34:18 +00:00
SPRINX0\prochazka
a9412b418f changelog 2025-04-02 12:58:58 +02:00
SPRINX0\prochazka
742c5b76fe v6.3.2-premium-beta.3 2025-04-02 12:45:53 +02:00
SPRINX0\prochazka
7a85fc0179 fixed startup theme 2025-04-02 12:36:42 +02:00
SPRINX0\prochazka
73e73ffe58 SYNC: column search test+screenshot 2025-04-02 10:17:41 +00:00
SPRINX0\prochazka
3c5523894d v6.3.2-beta.2 2025-04-02 11:59:27 +02:00
SPRINX0\prochazka
4b9c76d3db Merge branch 'master' of https://github.com/dbgate/dbgate 2025-04-02 11:59:01 +02:00
SPRINX0\prochazka
0a694eea8a SYNC: fix 2025-04-02 09:58:53 +00:00
SPRINX0\prochazka
87f48dad79 v6.3.2-beta.1 2025-04-02 10:54:58 +02:00
SPRINX0\prochazka
68ef50ca46 real-time system theme switch #1084 2025-04-02 10:52:17 +02:00
SPRINX0\prochazka
8b1da33ffe ability to use dark mode from system #1084 2025-04-02 10:44:40 +02:00
SPRINX0\prochazka
9a9b18a3ef clickhouse: load views for users with limited permissions #1076 2025-04-02 09:48:35 +02:00
Nybkox
83f69d89ff WIP 2025-04-01 17:32:24 +02:00
SPRINX0\prochazka
0bd7d23114 SYNC: highlight columne header search controls 2025-04-01 12:55:33 +00:00
SPRINX0\prochazka
9b860a6aa6 SYNC: uniqied form and grid search in columns 2025-04-01 12:51:20 +00:00
SPRINX0\prochazka
4812519a4c SYNC: search in columns - show filtered column in grid 2025-04-01 12:41:35 +00:00
SPRINX0\prochazka
f3ce6ad467 SYNC: #1077 set custom connection prop from ENV vars 2025-04-01 10:47:43 +00:00
SPRINX0\prochazka
78d9b48854 SYNC: scrollable exported diagram 2025-04-01 10:37:15 +00:00
SPRINX0\prochazka
b37d0eba04 diagram export scirpt 2025-04-01 12:16:27 +02:00
CI workflows
5289b3f54c chore: auto-update github workflows 2025-04-01 08:41:47 +00:00
CI workflows
5c08fe0611 Update pro ref 2025-04-01 08:41:28 +00:00
SPRINX0\prochazka
4bc9b70882 SYNC: configurable export watermark 2025-04-01 08:41:16 +00:00
CI workflows
3f98f9ff39 chore: auto-update github workflows 2025-04-01 08:02:15 +00:00
CI workflows
e78a7bfbf1 Update pro ref 2025-04-01 08:01:56 +00:00
SPRINX0\prochazka
b8b1412bf8 SYNC: diagram improvements 2025-04-01 08:01:44 +00:00
CI workflows
a81d66ace3 chore: auto-update github workflows 2025-03-31 15:04:42 +00:00
CI workflows
dcfd72b7e7 Update pro ref 2025-03-31 15:04:25 +00:00
SPRINX0\prochazka
6467db4a21 SYNC: show diagram table counts WIP 2025-03-31 15:04:11 +00:00
SPRINX0\prochazka
f2570c97f3 SYNC: diagram - persist scroll when zooming 2025-03-31 12:42:43 +00:00
SPRINX0\prochazka
98ad518b5d SYNC: designer right-mouse drag scroll implementation 2025-03-31 12:20:01 +00:00
SPRINX0\prochazka
7a5e17a345 SYNC: fixed selecting in diagrams with zoom 2025-03-31 11:29:53 +00:00
SPRINX0\prochazka
fde257c722 SYNC: fixes 2025-03-31 11:14:59 +00:00
SPRINX0\prochazka
97357f082d removed diagramsettings 2025-03-31 12:24:12 +02:00
CI workflows
6875bd82fc chore: auto-update github workflows 2025-03-31 10:23:04 +00:00
CI workflows
b1f6cad741 Update pro ref 2025-03-31 10:22:44 +00:00
CI workflows
fb28fec60d chore: auto-update github workflows 2025-03-31 10:18:36 +00:00
CI workflows
500130be59 Update pro ref 2025-03-31 10:18:15 +00:00
SPRINX0\prochazka
101173c87c SYNC: diagram settings in premium 2025-03-31 10:16:12 +00:00
SPRINX0\prochazka
4fb1b0dbd1 SYNC: zoom diagram by wheel 2025-03-31 10:12:28 +00:00
SPRINX0\prochazka
4270d5e8ec SYNC: most important tables diagram setting 2025-03-31 08:37:23 +00:00
hansemschnokeloch
fe9f1146ce Fix typo in Analyser.js 2025-03-31 08:28:04 +02:00
SPRINX0\prochazka
6fce43a122 SYNC: fixed diagram references when using zoom 2025-03-28 15:24:33 +00:00
CI workflows
a8a8f9b3e5 chore: auto-update github workflows 2025-03-28 14:43:05 +00:00
SPRINX0\prochazka
0dd0125e9f SYNC: test for parseName + fix 2025-03-28 14:42:43 +00:00
SPRINX0\prochazka
d3d97b5924 SYNC: support OR in filterName 2025-03-28 14:09:05 +00:00
SPRINX0\prochazka
c8462fb50b SYNC: diagram column filter 2025-03-28 13:29:09 +00:00
SPRINX0\prochazka
c99ca9edcc SYNC: diagram style 2025-03-28 11:59:29 +00:00
CI workflows
fc7e96feb9 chore: auto-update github workflows 2025-03-28 11:50:55 +00:00
CI workflows
31dd7be296 Update pro ref 2025-03-28 11:50:38 +00:00
SPRINX0\prochazka
2899373e42 SYNC: diagram settings more visible 2025-03-28 11:50:28 +00:00
CI workflows
7640c3d0ef chore: auto-update github workflows 2025-03-28 08:57:26 +00:00
CI workflows
af7772f617 Update pro ref 2025-03-28 08:57:09 +00:00
SPRINX0\prochazka
a1670caf06 fix export diagram - body - overflow - scroll 2025-03-28 09:31:45 +01:00
SPRINX0\prochazka
61e35b9773 Redis - ability to skip SETNAME #1077 2025-03-28 08:55:01 +01:00
SPRINX0\prochazka
e4671ffdb3 exit connectProcess after connection test 2025-03-28 08:44:56 +01:00
SPRINX0\prochazka
3035f923cb Merge branch 'master' of https://github.com/dbgate/dbgate 2025-03-28 08:22:02 +01:00
SPRINX0\prochazka
97e83def48 end tab preview mode when change grid config 2025-03-28 08:21:57 +01:00
SPRINX0\prochazka
ba31deaebf SYNC: missing gitkeep file 2025-03-27 15:24:27 +00:00
SPRINX0\prochazka
bd571dc93d SYNC: slightly better dropdownmanu-layout algorithm 2025-03-26 15:13:31 +00:00
SPRINX0\prochazka
c1da8321ac themeshot name 2025-03-26 13:09:11 +01:00
SPRINX0\prochazka
0a1972b854 themeshot name 2025-03-26 12:59:08 +01:00
SPRINX0\prochazka
96f387e90b SEO friendly screenshot names 2025-03-26 12:46:33 +01:00
SPRINX0\prochazka
e034fc1019 SYNC: new themeshot 2025-03-26 09:08:26 +00:00
SPRINX0\prochazka
d539912762 SYNC: fragment shots 2025-03-26 08:13:02 +00:00
SPRINX0\prochazka
85b72af3bb Merge branch 'master' of https://github.com/dbgate/dbgate 2025-03-26 08:00:21 +01:00
SPRINX0\prochazka
3004f4b583 malformed changelog 2025-03-26 08:00:18 +01:00
SPRINX0\prochazka
78dbd7eacd SYNC: screenshot 2025-03-25 16:13:02 +00:00
SPRINX0\prochazka
add73e6f16 SYNC: filter - error 2025-03-25 15:53:59 +00:00
SPRINX0\prochazka
2c5a7d103d premium info in community 2025-03-25 15:10:59 +01:00
SPRINX0\prochazka
f8f855d5d2 changed web 2025-03-25 14:02:44 +01:00
Jan Prochazka
f2c109116c Merge branch 'master' of github.com:dbgate/dbgate 2025-03-24 17:34:42 +01:00
Jan Prochazka
41c63fab96 readme 2025-03-24 17:34:38 +01:00
Jan Prochazka
de94651fca SYNC: data duplicator screenshot+test 2025-03-24 13:07:40 +00:00
Jan Prochazka
611c468b70 changed URLs 2025-03-24 10:30:14 +01:00
CI workflows
fd2207e39e chore: auto-update github workflows 2025-03-23 13:37:52 +00:00
Jan Prochazka
10564442e2 try to fix process templates 2025-03-23 14:37:31 +01:00
Jan Prochazka
a44307ca2d SYNC: fix 2025-03-23 11:57:11 +00:00
CI workflows
cfea2ed954 Update pro ref 2025-03-23 11:57:05 +00:00
Jan Prochazka
62e59605ed SYNC: tableyaml screenshot 2025-03-23 11:56:53 +00:00
CI workflows
2644b1b7ac chore: auto-update github workflows 2025-03-23 10:18:52 +00:00
Jan Prochazka
6e4c6d0c54 condition for pushing e2e screenshots 2025-03-23 11:18:36 +01:00
Jan Prochazka
2b7d950a9f SYNC: next screenshots 2025-03-23 09:06:51 +00:00
CI workflows
5ece8d67f9 chore: auto-update github workflows 2025-03-23 08:31:04 +00:00
Jan Prochazka
8511b67811 Merge branch 'master' of github.com:dbgate/dbgate 2025-03-23 09:30:46 +01:00
Jan Prochazka
a0a444e476 ammend commit in images 2025-03-23 09:30:43 +01:00
Jan Prochazka
6fec92926d SYNC: ai assistant test - try to increase timeout 2025-03-23 07:47:24 +00:00
Jan Prochazka
2cbefb261b skip ai assistant test 2025-03-22 15:01:17 +01:00
CI workflows
4bebbb158c chore: auto-update github workflows 2025-03-22 13:34:42 +00:00
Jan Prochazka
8fe2172c0a Merge branch 'master' of github.com:dbgate/dbgate 2025-03-22 14:34:28 +01:00
Jan Prochazka
f52ef2d57e push to image repo 2025-03-22 14:34:25 +01:00
Jan Prochazka
3f1c005548 SYNC: switch on all tests 2025-03-22 10:47:58 +00:00
Jan Prochazka
821bd2b2d8 SYNC: filter themeshot 2025-03-22 09:11:13 +00:00
Nybkox
d8405feab3 feat: add jsonPreviewWrap option to settings 2025-03-18 20:54:22 +01:00
SPRINX0\prochazka
bd94437c05 v6.3.1 2025-03-18 13:49:28 +01:00
SPRINX0\prochazka
b290bdb473 v6.3.1-packer-beta.1 2025-03-18 13:42:48 +01:00
SPRINX0\prochazka
f8e2af4fd4 SYNC: set current version for azure build 2025-03-18 12:39:46 +00:00
SPRINX0\prochazka
97c8dfa5b9 changelog 2025-03-18 12:56:09 +01:00
SPRINX0\prochazka
37cce68e2e v6.3.0 2025-03-18 12:13:15 +01:00
SPRINX0\prochazka
1f87a71b98 changelog 2025-03-18 12:12:08 +01:00
SPRINX0\prochazka
5511ea1887 dark theme detect, startup with dark theme 2025-03-17 15:43:33 +01:00
SPRINX0\prochazka
e371081a1d Merge branch 'master' of https://github.com/dbgate/dbgate 2025-03-17 15:07:10 +01:00
SPRINX0\prochazka
70816b48e8 changelog 2025-03-17 15:07:08 +01:00
SPRINX0\prochazka
2c9fc7b4a7 SYNC: health 2025-03-17 13:11:59 +00:00
SPRINX0\prochazka
3bb4652a49 SYNC: health status 2025-03-17 12:57:47 +00:00
SPRINX0\prochazka
d5f8e01dd8 changelog for 6.3.0 2025-03-17 10:50:17 +01:00
CI workflows
27e0517f9a chore: auto-update github workflows 2025-03-14 14:44:48 +00:00
CI workflows
f59aeda28e Update pro ref 2025-03-14 14:44:35 +00:00
Jan Prochazka
a8a69f8c36 v6.2.2-beta.10 2025-03-14 15:33:23 +01:00
Jan Prochazka
4ae6b8328e v6.2.2-premium-beta.9 2025-03-14 15:33:09 +01:00
Jan Prochazka
6d4337e4ce Merge branch 'master' of github.com:dbgate/dbgate 2025-03-14 15:31:35 +01:00
Jan Prochazka
3df928caf4 deleted obsolete file 2025-03-14 15:31:31 +01:00
Jan Prochazka
faeb33fdc0 SYNC: libsql volatile package 2025-03-14 14:30:00 +00:00
Jan Prochazka
bacdeb20eb libSQL - premium only 2025-03-14 15:26:28 +01:00
Jan Prochazka
781c426b2f libSQL - targetType 2025-03-14 15:25:15 +01:00
Jan Prochazka
d5c9fb8dec refactor 2025-03-14 15:04:53 +01:00
CI workflows
78c372b9d8 chore: auto-update github workflows 2025-03-14 13:52:44 +00:00
Jan Prochazka
ea31069609 fix 2025-03-14 14:52:28 +01:00
CI workflows
3f9a3e8f64 chore: auto-update github workflows 2025-03-14 13:45:28 +00:00
Jan Prochazka
2f02715efd Merge pull request #1069 from dbgate/feature/libsql
feat: libsql basic support
2025-03-14 14:45:14 +01:00
Jan Prochazka
c94fa64a33 v6.2.2-premium-beta.8 2025-03-14 14:40:58 +01:00
CI workflows
e2cb666b3c chore: auto-update github workflows 2025-03-14 13:40:22 +00:00
CI workflows
9f24e0145b Update pro ref 2025-03-14 13:40:10 +00:00
Jan Prochazka
41413471aa v6.2.2-premium-beta.7 2025-03-14 14:26:07 +01:00
CI workflows
f8a11166f0 chore: auto-update github workflows 2025-03-14 13:25:42 +00:00
CI workflows
27517b3b47 Update pro ref 2025-03-14 13:25:29 +00:00
Jan Prochazka
834c0d92b4 v6.2.2-premium-beta.6 2025-03-14 14:01:39 +01:00
Jan Prochazka
e04215f0e1 commented out localization 2025-03-14 14:01:20 +01:00
Jan Prochazka
9fa8d98ae8 Merge branch 'master' of github.com:dbgate/dbgate 2025-03-14 12:09:31 +01:00
Jan Prochazka
31621f273b SYNC: commented dump tests 2025-03-14 11:07:13 +00:00
Jan Prochazka
41f158dac1 v6.2.2-premium-beta.5 2025-03-14 11:47:11 +01:00
CI workflows
e5687f3a7c chore: auto-update github workflows 2025-03-14 10:44:44 +00:00
CI workflows
1a5b684e1f Update pro ref 2025-03-14 10:44:30 +00:00
CI workflows
bf70c487ca chore: auto-update github workflows 2025-03-14 10:38:22 +00:00
CI workflows
4e1d34ba77 Update pro ref 2025-03-14 10:38:10 +00:00
Jan Prochazka
3a75ad61f3 SYNC: Merge branch 'feature/backup-restore' 2025-03-14 10:37:58 +00:00
460 changed files with 23645 additions and 6148 deletions

View File

@@ -92,6 +92,7 @@ jobs:
cp app/dist/*win*.exe artifacts/dbgate-beta.exe || true
cp app/dist/*win_x64.zip artifacts/dbgate-windows-beta.zip || true
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-beta-arm64.zip || true
cp app/dist/*win_arm64.exe artifacts/dbgate-windows-beta-arm64.exe || true
cp app/dist/*-mac_universal.dmg artifacts/dbgate-beta.dmg || true
cp app/dist/*-mac_x64.dmg artifacts/dbgate-beta-x64.dmg || true
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-beta-arm64.dmg || true

112
.github/workflows/build-app-check.yaml vendored Normal file
View File

@@ -0,0 +1,112 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Electron app check build
'on':
push:
tags:
- check-[0-9]+-[0-9]+-[0-9]+.[0-9]+
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- macos-14
- windows-2022
- ubuntu-22.04
steps:
- name: Install python 3.11 (MacOS)
if: matrix.os == 'macos-14'
run: |
brew install python@3.11
echo "PYTHON=/opt/homebrew/bin/python3.11" >> $GITHUB_ENV
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 22.x
- name: adjustPackageJson
run: |
node adjustPackageJson --community
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
env:
GIST_UPLOAD_SECRET: ${{secrets.GIST_UPLOAD_SECRET}}
- name: fillPackagedPlugins
run: |
yarn fillPackagedPlugins
- name: Install Snapcraft
if: matrix.os == 'ubuntu-22.04'
uses: samuelmeuli/action-snapcraft@v1
- name: Publish
run: |
yarn run build:app
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
CSC_LINK: ${{ secrets.APPLECERT_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLECERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
- name: Copy artifacts
run: |
mkdir artifacts
cp app/dist/*.deb artifacts/dbgate-check.deb || true
cp app/dist/*x86*.AppImage artifacts/dbgate-check.AppImage || true
cp app/dist/*arm64*.AppImage artifacts/dbgate-check-arm64.AppImage || true
cp app/dist/*armv7l*.AppImage artifacts/dbgate-check-armv7l.AppImage || true
cp app/dist/*win*.exe artifacts/dbgate-check.exe || true
cp app/dist/*win_x64.zip artifacts/dbgate-windows-check.zip || true
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-check-arm64.zip || true
cp app/dist/*win_arm64.exe artifacts/dbgate-windows-check-arm64.exe || true
cp app/dist/*-mac_universal.dmg artifacts/dbgate-check.dmg || true
cp app/dist/*-mac_x64.dmg artifacts/dbgate-check-x64.dmg || true
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-check-arm64.dmg || true
mv app/dist/*.snap artifacts/dbgate-check.snap || true
mv app/dist/*.exe artifacts/ || true
mv app/dist/*.zip artifacts/ || true
mv app/dist/*.tar.gz artifacts/ || true
mv app/dist/*.AppImage artifacts/ || true
mv app/dist/*.deb artifacts/ || true
mv app/dist/*.snap artifacts/ || true
mv app/dist/*.dmg artifacts/ || true
mv app/dist/*.blockmap artifacts/ || true
mv app/dist/*.yml artifacts/ || true
rm artifacts/builder-debug.yml
- name: Print content of notarization-error.log
if: failure() && matrix.os == 'macos-14'
run: |
find . -type f -name "notarization-error.log" -exec echo "=== Start of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \;

View File

@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -123,6 +123,7 @@ jobs:
cp ../dbgate-merged/app/dist/*win*.exe artifacts/dbgate-premium-beta.exe || true
cp ../dbgate-merged/app/dist/*win_x64.zip artifacts/dbgate-windows-premium-beta.zip || true
cp ../dbgate-merged/app/dist/*win_arm64.zip artifacts/dbgate-windows-premium-beta-arm64.zip || true
cp ../dbgate-merged/app/dist/*win_arm64.exe artifacts/dbgate-windows-premium-beta-arm64.exe || true
cp ../dbgate-merged/app/dist/*-mac_universal.dmg artifacts/dbgate-premium-beta.dmg || true
cp ../dbgate-merged/app/dist/*-mac_x64.dmg artifacts/dbgate-premium-beta-x64.dmg || true
cp ../dbgate-merged/app/dist/*-mac_arm64.dmg artifacts/dbgate-premium-beta-arm64.dmg || true

View File

@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -123,6 +123,7 @@ jobs:
cp ../dbgate-merged/app/dist/*win*.exe artifacts/dbgate-premium-latest.exe || true
cp ../dbgate-merged/app/dist/*win_x64.zip artifacts/dbgate-windows-premium-latest.zip || true
cp ../dbgate-merged/app/dist/*win_arm64.zip artifacts/dbgate-windows-premium-latest-arm64.zip || true
cp ../dbgate-merged/app/dist/*win_arm64.exe artifacts/dbgate-windows-premium-latest-arm64.exe || true
cp ../dbgate-merged/app/dist/*-mac_universal.dmg artifacts/dbgate-premium-latest.dmg || true
cp ../dbgate-merged/app/dist/*-mac_x64.dmg artifacts/dbgate-premium-latest-x64.dmg || true
cp ../dbgate-merged/app/dist/*-mac_arm64.dmg artifacts/dbgate-premium-latest-arm64.dmg || true

View File

@@ -91,6 +91,7 @@ jobs:
cp app/dist/*win*.exe artifacts/dbgate-latest.exe || true
cp app/dist/*win_x64.zip artifacts/dbgate-windows-latest.zip || true
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-latest-arm64.zip || true
cp app/dist/*win_arm64.exe artifacts/dbgate-windows-latest-arm64.exe || true
cp app/dist/*-mac_universal.dmg artifacts/dbgate-latest.dmg || true
cp app/dist/*-mac_x64.dmg artifacts/dbgate-latest-x64.dmg || true
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-latest-arm64.dmg || true

View File

@@ -22,10 +22,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 22.x
- name: Install jq
run: |
sudo apt-get install jq -y
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro

View File

@@ -44,7 +44,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro

View File

@@ -22,17 +22,17 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 22.x
- name: Checkout dbgate/dbgate-pro
uses: actions/checkout@v2
with:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -98,3 +98,8 @@ jobs:
cd ..
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
npm publish
- name: Publish dbgate-plugin-firestore
run: |
cd ..
cd dbgate-merged/plugins/dbgate-plugin-firestore
npm publish

View File

@@ -22,10 +22,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 22.x
- name: Configure NPM token
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -13,10 +13,10 @@ jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 22.x
- uses: actions/checkout@v3
with:
fetch-depth: 1
@@ -26,7 +26,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
ref: 36b6ce878c3c0a0c9623163c8a8b3bdeefc7da53
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -69,6 +69,17 @@ jobs:
with:
name: screenshots
path: screenshots
- name: Push E2E screenshots
if: ${{ github.ref_name == 'master' }}
run: |
git config --global user.email "info@dbgate.info"
git config --global user.name "GitHub Actions"
git clone https://${{ secrets.DIFLOW_GIT_SECRET }}@github.com/dbgate/dbgate-img.git
cp ../dbgate-merged/e2e-tests/screenshots/*.png dbgate-img/static/img
cd dbgate-img/static/img
git add .
git commit --amend --no-edit
git push --force
services:
postgres-cypress:
image: postgres
@@ -96,7 +107,7 @@ jobs:
ports:
- '16009:5556'
mongo:
image: mongo:4.0.12
image: mongo:6.0.25
env:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: Pwd2020Db

View File

@@ -16,6 +16,9 @@ jobs:
uses: actions/checkout@v3
with:
token: ${{ secrets.WORKFLOW_CHANGE_ACCESS_TOKEN }}
- name: git pull
run: |
git pull
- name: Set up Node
uses: actions/setup-node@v3
with:

View File

@@ -13,10 +13,10 @@ jobs:
all-tests:
runs-on: ubuntu-latest
steps:
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 18.x
node-version: 22.x
- uses: actions/checkout@v3
with:
fetch-depth: 1
@@ -37,6 +37,11 @@ jobs:
run: |
cd packages/datalib
yarn test:ci
- name: Tools tests
if: always()
run: |
cd packages/tools
yarn test:ci
- uses: tanmen/jest-reporter@v1
if: always()
with:
@@ -93,3 +98,18 @@ jobs:
image: cassandra:5.0.2
ports:
- '15942:9042'
libsql:
image: ghcr.io/tursodatabase/libsql-server:latest
ports:
- '8080:8080'
firebird:
image: firebirdsql/firebird:latest
env:
FIREBIRD_DATABASE: mydatabase.fdb
FIREBIRD_USER: dbuser
FIREBIRD_PASSWORD: dbpassword
ISC_PASSWORD: masterkey
FIREBIRD_TRACE: false
FIREBIRD_USE_LEGACY_AUTH: true
ports:
- '3050:3050'

2
.nvmrc
View File

@@ -1 +1 @@
v21.7.3
v24.4.1

59
.vscode/launch.json vendored
View File

@@ -1,20 +1,41 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch API",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/packages/api/src/index.js",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}
"version": "0.2.0",
"configurations": [
{
"name": "Debug App",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/api/src/index.js",
"envFile": "${workspaceFolder}/packages/api/.env",
"args": ["--listen-api"],
"console": "integratedTerminal",
"restart": true,
"runtimeExecutable": "node",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Debug App (Break on Start)",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/api/src/index.js",
"args": ["--listen-api"],
"envFile": "${workspaceFolder}/.env",
"console": "integratedTerminal",
"restart": true,
"runtimeExecutable": "node",
"skipFiles": ["<node_internals>/**"],
"stopOnEntry": true
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
}
]
}

View File

@@ -8,6 +8,131 @@ Builds:
- linux - application for linux
- win - application for Windows
## 6.6.0
- ADDED: Database chat - AI powered chatbot, which knows your database (Premium)
- ADDED: Firestore support (Premium)
- REMOVED: Query AI assistant (replaced by Database Chat) (Premium)
- FIXED: Chart permissions were ignored (Premium)
## 6.5.6
- ADDED: New object window - quick access to most common functions
- ADDED: Possibility to disable split query by empty line #1162
- ADDED: Possibility to opt out authentication #1152
- FIXED: Separate schema mode now works in Team Premium edition
- FIXED: Handled situation, when user enters expired license, which is already prolonged
- FIXED: Fixed some minor problems of charts
## 6.5.5
- ADDED: Administer cloud folder window
- CHANGED: Cloud menu redesign
- ADDED: Audit log (for Team Premium edition)
- ADDED: Added new timeline chart type (line chart with time axis)
- ADDED: Chart grouping (more measure determined from data)
- CHANGED: Improved chart autodetection - string X axis (with bar type), COUNT as measure, split different measures
- ADDED: Added chart data type detection
- FIXED: Fixed chart displaying problems
- FIXED: Fixed exporting chart to HTML
- CHANGED: Choose COUNT measure without selecting underlying ID field (use virtual __count)
- FIXED: Problems with authentification administration, especially for Postgres storage
- CHANGED: Anonymous autentification (in Team Premium) is now by default disabled
## 6.5.3
- CHANGED: Improved DbGate Cloud sign-in workflow
- FIXED: Some fixes and error handling in new charts engine
- ADDED: Charts - ability to choose aggregate function
- CHANGED: Improved About window
## 6.5.2
- CHANGED: Autodetecting charts is disabled by default #1145
- CHANGED: Improved chart displaying workflow
- ADDED: Ability to close chart
## 6.5.1
- FIXED: DbGate Cloud e-mail sign-in method for desktop clients
## 6.5.0
- ADDED: DbGate cloud - online storage for connections, SQL scripts and other objects
- ADDED: Public knowledge base - common SQL scripts for specific DB engines (table sizes, index stats etc.)
- ADDED: Query results could be visualised in charts (Premium)
- REMOVED: Chart from selection, active charts - replaced by query result charts
- ADDED: FirebirdSQL support
- ADDED: SQL front matter - properties of SQL script
- ADDED: Auto-execute SQL script on open (saved in SQL front matter)
- CHANGED: Smaller widget icon panel
- CHANGED: Applications and Single-connection mode removed from widget icon panel
- CHANGED: Temporarily disabled MongoDB profiler support
- FIXED: Pie chart distorted if settings change #838
- FIXED: SQL server generated insert statement should exclude computed and timestamp columns #1111
- ADDED: Added option "Show all columns when searching" #1118
- ADDED: Copy cells/rows (e.g. column names) from Structure view #1119
- ADDED: Setting "Show table aliases in code completion" #1122
- FIXED: Vulnerability - check file paths in web version
- FIXED: Very slow render of tables with very log cells
## 6.4.2
- ADDED: Source label to docker container #1105
- FIXED: DbGate restart needed to take effect after trigger is created/deleted on mariadb #1112
- ADDED: View PostgreSQL query console output #1108
- FIXED: Single quote generete MySql error #1107
- ADDED: Ability to limit query result count #1098
- CHANGED: Correct processing of bigint columns #1087 #1055 #583
- CHANGED: Improved and optimalized algorithm of loading redis keys #1062, #1034
- FIXED: Fixed loading Redis keys with :: in key name
## 6.4.0
- ADDED: DuckDB support
- ADDED: Data deployer (Premium)
- ADDED: Compare data between JSON lines file in archive and database table
- CHANGED: Data Duplicator => Data Replicator (suitable for update, create and delete data, much more customizable)
- REMOVED: Data duplicator GUI (replaced with Data Deployer)
- ADDED: Exporting to ZIP file
- ADDED: Download SQL and SQLite files
- ADDED: Upload SQLite files
- ADDED: Upload archive as ZIP folder (Premium)
- ADDED: Compress, uncompress archive folder (Premium)
- ADDED: Export connections and settings #357
- ADDED: Filtering by MongoDB ObjectId works now also without ObjectId(...) wrapper
- ADDED: Split queries using blank lines #1089
- FIXED: JSON-to-Grid only works if there is no newline #1085
- CHANGED: When running multiple commands in script, stop execution after first error #1070
- FIXED: Selection rectangle remains visible after closing JSONB edit cell value form #1031
- FIXED: Diplaying numeric FK column with right alignement #1021
- ADDED: Additional arguments for MySQL and PostgreSQL backup #1092
- CHANGED: Amazon and Azure instalations are not auto-upgraded by default
## 6.3.3
- CHANGED: New administration UI, redesigned administration of users, connections and roles
- ADDED: Encrypting passwords in team-premium edition
- ADDED: Show scale bar on map #1090
- FIXED: Fixed native backup/restore for MySQL+PostgreSQL over SSH tunnel #1092
- CHANGED: Column mapping dialog - fixes and improvements for copying from one existing table into another
- ADDED: Search in columns in table editor
- ADDED: Line Wrap for JSON viewer #768
### 6.3.2
- ADDED: "Use system theme" switch, use changed system theme without restart #1084
- ADDED: "Skip SETNAME instruction" option for Redis #1077
- FIXED: Clickhouse views are now available even for user with limited permissions #1076
- ADDED: Multiple-token search delimited with comma (=OR) in structure search boxes
- CHANGED: When filtering columns in data browser, data view shows only filtered columns
- ADDED: Advanced settings for diagrams (Premium)
- ADDED: Diagrams - zoom with Ctrl+mouse wheel
- FIXED: Scrollable diagram exports + scroll by mouse drag
- FIXED: Fixed many problems in diagrams when zoom is applied
- FIXED: Correctly end connection process after succesful/unsuccesful connect
### 6.3.0
- ADDED: Support for libSQL and Turso (Premium)
- ADDED: Native backup and restore database for MySQL and PostgreSQL (Premium)
- REMOVED: DbGate internal dump export for MySQL (replaced with call of mysqldump)
- REMOVED: Import SQL dump with internal DbGate capabilities (replaced by calling of mysql and psql utilities)
- FIXED: Many fixes in stream processing (imoprt/export), especialy for MongoDB
- ADDED: Indicating progress of import/export tasks, better error reporting
- CHANGED: #1060 - Changed shortcut for AI assistant
- ADDED: /health endpoint with diagnostic info
- FIXED: Linux Appimage crash => A JavaScript error occurred in the main process #1065 , #1067
### 6.2.1
- ADDED: Commit/rollback and autocommit in scripts #1039
- FIXED: Doesn't import all the records from MongoDB #1044
@@ -18,7 +143,7 @@ Builds:
- FIXED: Scroll in XML cell view, XML view respect themes
- REMOVED: armv7l build for Linux (because of problems with glibc compatibility)
- CHANGED: Upgraded to node:22 for docker builds
- CHANGED: Upgraded SQLite engine version (better-sqlite3@11.8.1)
- CHANGED: Upgraded SQLite engine version
### 6.2.0
- ADDED: Query AI Assistant (Premium)

View File

@@ -15,10 +15,13 @@ But there are also many advanced features like schema compare, visual query desi
DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
* Try it online - [demo.dbgate.org](https://demo.dbgate.org) - online demo application
* **Download** application for Windows, Linux or Mac from [dbgate.org](https://dbgate.org/download/)
* **Download** application for Windows, Linux or Mac from [dbgate.io](https://dbgate.io/download/)
* Looking for DbGate Community? **Download** from [dbgate.org](https://dbgate.org/download/)
* Run web version as [NPM package](https://www.npmjs.com/package/dbgate-serve) or as [docker image](https://hub.docker.com/r/dbgate/dbgate)
* Use nodeJs [scripting interface](https://dbgate.org/docs/scripting) ([API documentation](https://dbgate.org/docs/apidoc))
* Use nodeJs [scripting interface](https://docs.dbgate.io/scripting) ([API documentation](https://docs.dbgate.io/apidoc))
* [Recommend DbGate](https://testimonial.to/dbgate) | [Rate on G2](https://www.g2.com/products/dbgate/reviews)
* [Give us feedback](https://dbgate.org/feedback) - it will help us to decide, how to improve DbGate in future
* We [offer 2-year PREMIUM license](https://dbgate.org/review/) for any honest review on these platforms (time-limited offer)
## Supported databases
* MySQL
@@ -34,8 +37,10 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
* CosmosDB (Premium)
* ClickHouse
* Apache Cassandra
<!-- Learn more about DbGate features at the [DbGate website](https://dbgate.org/), or try our online [demo application](https://demo.dbgate.org) -->
* libSQL/Turso (Premium)
* DuckDB
* Firebird
* Firestore (Premium)
<a href="https://raw.githubusercontent.com/dbgate/dbgate/master/img/screenshot1.png">
@@ -78,6 +83,7 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
* Archives - backup your data in NDJSON files on local filesystem (or on DbGate server, when using web application)
* NDJSON data viewer and editor - browse NDJSON data, edit data and structure directly on NDJSON files. Works also for big NDSON files
* Charts, export chart to HTML page
* AI powered database chat
* Show GEO data on map, export map to HTML page
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
* Extensible plugin architecture
@@ -86,11 +92,13 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
Any contributions are welcome. If you want to contribute without coding, consider following:
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
* Write review on [Slant.co](https://www.slant.co/improve/options/41086/~dbgate-review) or [G2](https://www.g2.com/products/dbgate/reviews)
* Purchase a [DbGate Premium](https://dbgate.io/purchase/premium/) liocense
* Write review on [Product Hunt](https://www.producthunt.com/products/dbgate) or [G2](https://www.g2.com/products/dbgate/reviews) - we offer [2-year PREMIUM license](https://dbgate.org/review/) for reviewers (time limited offer)
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues
* Create some tutorial video on [youtube](https://www.youtube.com/playlist?list=PLCo7KjCVXhr0RfUSjM9wJMsp_ShL1q61A)
* Become a backer on [GitHub sponsors](https://github.com/sponsors/dbgate) or [Open collective](https://opencollective.com/dbgate)
* Where a small coding is acceptable for you, you could [create plugin](https://dbgate.org/docs/plugin-development). Plugins for new themes can be created actually without JS coding
* Add a SQL script to [Public Knowledge Base](https://github.com/dbgate/dbgate-knowledge-base)
* Where a small coding is acceptable for you, you could [create plugin](https://docs.dbgate.io/plugin-development). Plugins for new themes can be created actually without JS coding
Thank you!
@@ -185,4 +193,4 @@ yarn plugin # this compiles plugin and copies it into existing DbGate installati
After restarting DbGate, you could use your new plugin from DbGate.
## Logging
DbGate uses [pinomin logger](https://github.com/dbgate/pinomin). So by default, it produces JSON log messages into console and log files. If you want to see formatted logs, please use [pino-pretty](https://github.com/pinojs/pino-pretty) log formatter.
DbGate uses [pinomin logger](https://github.com/dbgate/pinomin). So by default, it produces JSON log messages into console and log files. If you want to see formatted logs, please use [pino-pretty](https://github.com/pinojs/pino-pretty) log formatter.

View File

@@ -43,6 +43,8 @@ function adjustFile(file, isApp = false) {
if (process.argv.includes('--community')) {
delete json.optionalDependencies['mongodb-client-encryption'];
delete json.dependencies['@mongosh/service-provider-node-driver'];
delete json.dependencies['@mongosh/browser-runtime-electron'];
}
if (isApp && process.argv.includes('--premium')) {

View File

@@ -357,6 +357,7 @@ function createWindow() {
title: isProApp() ? 'DbGate Premium' : 'DbGate',
frame: useNativeMenu,
titleBarStyle: useNativeMenu ? undefined : 'hidden',
backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#111111' : undefined,
...bounds,
icon: os.platform() == 'win32' ? 'icon.ico' : path.resolve(__dirname, '../icon.png'),
partition: isProApp() ? 'persist:dbgate-premium' : 'persist:dbgate',

View File

@@ -4,6 +4,7 @@ module.exports = ({ editMenu, isMac }) => [
submenu: [
{ command: 'new.connection', hideDisabled: true },
{ command: 'new.sqliteDatabase', hideDisabled: true },
{ command: 'new.duckdbDatabase', hideDisabled: true },
{ divider: true },
{ command: 'new.query', hideDisabled: true },
{ command: 'new.queryDesign', hideDisabled: true },
@@ -87,6 +88,9 @@ module.exports = ({ editMenu, isMac }) => [
{ command: 'folder.showData', hideDisabled: true },
{ command: 'new.gist', hideDisabled: true },
{ command: 'app.resetSettings', hideDisabled: true },
{ divider: true },
{ command: 'app.exportConnections', hideDisabled: true },
{ command: 'app.importConnections', hideDisabled: true },
],
},
...(isMac
@@ -104,6 +108,7 @@ module.exports = ({ editMenu, isMac }) => [
{ command: 'app.openWeb', hideDisabled: true },
{ command: 'app.openIssue', hideDisabled: true },
{ command: 'app.openSponsoring', hideDisabled: true },
{ command: 'app.giveFeedback', hideDisabled: true },
{ divider: true },
{ command: 'settings.commands', hideDisabled: true },
{ command: 'tabs.changelog', hideDisabled: true },

View File

@@ -1,9 +0,0 @@
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
const content = {};
content['better-sqlite3'] = () => require('better-sqlite3');
content['oracledb'] = () => require('oracledb');
module.exports = content;

View File

@@ -14,12 +14,16 @@ const volatilePackages = [
'ioredis',
'node-redis-dump2',
'better-sqlite3',
'libsql',
'@azure/cosmos',
'@aws-sdk/rds-signer',
'activedirectory2',
'axios',
'ssh2',
'wkx',
'@duckdb/node-api',
'@mongosh/browser-runtime-electron',
'@mongosh/service-provider-node-driver',
];
module.exports = volatilePackages;

View File

@@ -1,5 +1,7 @@
FROM node:22
LABEL org.opencontainers.image.source="https://github.com/dbgate/dbgate"
RUN apt-get update && apt-get install -y \
iputils-ping \
iproute2 \

View File

@@ -1,5 +1,7 @@
FROM node:18-alpine
LABEL org.opencontainers.image.source="https://github.com/dbgate/dbgate"
WORKDIR /home/dbgate-docker
RUN apk --no-cache upgrade \

View File

@@ -7,7 +7,9 @@ const path = require('path');
module.exports = defineConfig({
e2e: {
// baseUrl: 'http://localhost:3000',
// trashAssetsBeforeRuns: false,
chromeWebSecurity: false,
setupNodeEvents(on, config) {
// implement node event listeners here
@@ -40,6 +42,12 @@ module.exports = defineConfig({
case 'multi-sql':
serverProcess = exec('yarn start:multi-sql');
break;
case 'cloud':
serverProcess = exec('yarn start:cloud');
break;
case 'charts':
serverProcess = exec('yarn start:charts');
break;
}
await waitOn({ resources: ['http://localhost:3000'] });

View File

@@ -13,16 +13,22 @@ describe('Add connection', () => {
it('adds connection', () => {
// cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');
cy.themeshot('connection');
cy.themeshot('new-connection');
cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root');
cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('Pwd2020Db');
cy.get('[data-testid=ConnectionDriverFields_port]').clear().type('16004');
cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-1');
// test connection
cy.get('[data-testid=ConnectionTab_buttonTest]').click();
cy.testid('ConnectionTab_buttonTest').click();
cy.contains('Connected:');
cy.testid('ConnectionTab_tabSshTunnel').click();
cy.testid('ConnectionTab_tabControlContent').themeshot('connection-sshtunnel-window', { padding: 50 });
cy.testid('ConnectionTab_tabSsl').click();
cy.testid('ConnectionTab_tabControlContent').themeshot('connection-ssl-window', { padding: 50 });
// save and connect
cy.get('[data-testid=ConnectionTab_buttonSave]').click();
cy.get('[data-testid=ConnectionTab_buttonConnect]').click();
@@ -106,4 +112,11 @@ describe('Add connection', () => {
cy.contains('performance_schema');
});
it('export connections', () => {
cy.testid('WidgetIconPanel_menu').click();
cy.contains('Tools').click();
cy.contains('Export connections').click();
cy.themeshot('export-connections');
});
});

View File

@@ -15,18 +15,23 @@ beforeEach(() => {
describe('Data browser data', () => {
it('Export window', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').rightclick();
cy.contains('MyChinook').click();
cy.contains('Album').rightclick();
cy.contains('Export').click();
cy.contains('Export advanced').click();
cy.wait(1000);
// cy.testid('SourceTargetConfig_buttonCurrentArchive_target').click();
cy.testid('FormTablesSelect_buttonAll_tables').click();
// cy.testid('FormTablesSelect_buttonAll_tables').click();
// cy.testid('SourceTargetConfig_tablesSelect_source').click();
// cy.find('.listContainer').contains('Album').click();
// cy.find('.listContainer').contains('Track').click();
// cy.wait(4000);
// cy.contains('All tables').click();
cy.contains('Run').click();
cy.contains('Finished job script');
cy.contains('Album.csv');
cy.testid('WidgetIconPanel_database').click();
cy.themeshot('exportcsv');
cy.themeshot('configure-export-csv');
});
it('Data archive editor - macros', () => {
@@ -37,7 +42,7 @@ describe('Data browser data', () => {
cy.contains('Out Of Exile').click({ shiftKey: true });
cy.contains('Change text case').click();
cy.contains('AUDIOSLAVE');
cy.themeshot('freetable');
cy.themeshot('data-archive-macros');
});
it('Load table data', () => {
@@ -76,11 +81,25 @@ describe('Data browser data', () => {
cy.contains('Aerosmith').should('not.exist');
});
it('Data filter', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Album').click();
cy.testid('DataFilterControl_input_Title').type('Rock{enter}');
cy.contains('Rows: 7');
cy.testid('DataFilterControl_input_AlbumId').type('>10xxx{enter}');
cy.contains('Rows: 7');
cy.testid('DataFilterControl_filtermenu_Title').click();
// hide what is not needed
cy.testid('WidgetIconPanel_database').click();
cy.testid('DataGrid_itemReferences').click();
cy.themeshot('data-browser-filter');
cy.testid('DataGridCore_button_clearFilters').click();
cy.contains('Rows: 347');
});
it('Data grid screenshots', () => {
cy.contains('MySql-connection').click();
cy.window().then(win => {
win.__changeCurrentTheme('theme-dark');
});
cy.contains('MyChinook').click();
@@ -95,16 +114,25 @@ describe('Data browser data', () => {
cy.contains('PgChinook').click();
cy.contains('customer').click();
cy.contains('Leonie').click();
cy.themeshot('datagrid');
cy.themeshot('common-data-browser');
cy.contains('invoice').click();
cy.contains('invoice_line (invoice_id)').click();
cy.themeshot('masterdetail');
cy.themeshot('data-browser-master-detail');
cy.contains('9, Place Louis Barthou').click();
cy.contains('Switch to form').click();
cy.contains('Switch to table'); // test that we are in form view
cy.themeshot('formview');
cy.themeshot('data-browser-form-view');
});
it('Column search', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Customer').click();
cy.testid('ColumnManager_searchColumns').clear().type('name,id{enter}');
cy.contains('Company').should('not.exist');
cy.themeshot('data-browser-column-search');
});
it('SQL Gen', () => {
@@ -112,7 +140,7 @@ describe('Data browser data', () => {
cy.contains('PgChinook').rightclick();
cy.contains('SQL Generator').click();
cy.contains('Check all').click();
cy.themeshot('sqlgen');
cy.themeshot('sql-generator');
});
it('Macros in DB', () => {
@@ -127,7 +155,7 @@ describe('Data browser data', () => {
cy.testid('DataGrid_itemMacros').click();
cy.contains('Change text case').click();
cy.contains('NIELSEN');
cy.themeshot('macros');
cy.themeshot('data-browser-macros');
});
it('Perspectives', () => {
@@ -143,7 +171,7 @@ describe('Data browser data', () => {
// check track is loaded
cy.contains('Put The Finger On You');
cy.themeshot('perspective1');
cy.themeshot('perspective-designer');
});
it('Query editor - code completion', () => {
@@ -152,24 +180,26 @@ describe('Data browser data', () => {
cy.contains('Customer').rightclick();
cy.contains('SQL template').click();
cy.contains('CREATE TABLE').click();
cy.wait(1000);
cy.get('body').realPress('PageDown');
cy.get('body').realType('select * from Album where Album.');
// code completion
cy.contains('ArtistId');
cy.themeshot('query');
cy.themeshot('query-editor-code-completion');
});
it('Query editor - join wizard', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewQuery').click();
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_query').click();
cy.wait(1000);
cy.get('body').realType('select * from Invoice');
cy.get('body').realPress('{enter}');
cy.get('body').realPress(['Control', 'j']);
// JOIN wizard
cy.contains('INNER JOIN Customer ON Invoice.CustomerId = Customer.CustomerId');
cy.themeshot('joinwizard');
cy.themeshot('query-editor-join-wizard');
});
it('Mongo JSON data view', () => {
@@ -186,7 +216,7 @@ describe('Data browser data', () => {
cy.testid('WidgetIconPanel_cell-data').click();
// test JSON view
cy.contains('Country: "Brazil"');
cy.themeshot('mongoquery');
cy.themeshot('mongo-query-json-view');
});
it('SQL preview', () => {
@@ -196,7 +226,7 @@ describe('Data browser data', () => {
cy.contains('Show SQL').click();
// index should be part of create script
cy.contains('CREATE INDEX `IFK_CustomerSupportRepId`');
cy.themeshot('sqlpreview');
cy.themeshot('sql-preview-create-index');
});
it('Query designer', () => {
@@ -205,7 +235,7 @@ describe('Data browser data', () => {
cy.testid('WidgetIconPanel_file').click();
cy.contains('customer').click();
// cy.contains('left join').rightclick();
cy.themeshot('querydesigner');
cy.themeshot('query-designer');
});
it('Database diagram', () => {
@@ -216,24 +246,24 @@ describe('Data browser data', () => {
cy.testid('WidgetIconPanel_file').click();
// check diagram is shown
cy.contains('MediaTypeId');
cy.themeshot('diagram');
cy.themeshot('database-diagram');
});
it('Charts', () => {
cy.testid('WidgetIconPanel_file').click();
cy.contains('pie-chart').click();
cy.contains('line-chart').click();
cy.testid('TabsPanel_buttonSplit').click();
cy.testid('WidgetIconPanel_file').click();
cy.themeshot('charts');
});
// it('Charts', () => {
// cy.testid('WidgetIconPanel_file').click();
// cy.contains('pie-chart').click();
// cy.contains('line-chart').click();
// cy.testid('TabsPanel_buttonSplit').click();
// cy.testid('WidgetIconPanel_file').click();
// cy.themeshot('view-split-charts');
// });
it('Keyboard configuration', () => {
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Keyboard shortcuts').click();
cy.contains('dataForm.refresh').click();
cy.testid('CommandModal_keyboardButton').click();
cy.themeshot('keyboard');
cy.themeshot('keyboard-configuration');
});
it('Command palette', () => {
@@ -244,7 +274,7 @@ describe('Data browser data', () => {
// cy.realPress('F1');
cy.realPress('PageDown');
cy.realPress('PageDown');
cy.testid('CommandPalette_main').themeshot('commandpalette', { padding: 50 });
cy.testid('CommandPalette_main').themeshot('command-palette', { padding: 50 });
});
it('Show map', () => {
@@ -257,7 +287,7 @@ describe('Data browser data', () => {
cy.contains('13.9').click({ shiftKey: true });
cy.testid('WidgetIconPanel_cell-data').click();
cy.wait(2000);
cy.themeshot('map');
cy.themeshot('cell-map-view');
});
it('Search in connections', () => {
@@ -269,17 +299,18 @@ describe('Data browser data', () => {
cy.contains('Album').click();
cy.testid('SqlObjectList_searchMenuDropDown').click();
cy.contains('Column name').click();
cy.themeshot('connsearch');
cy.themeshot('search-in-connections');
});
it('Plugin tab', () => {
cy.testid('WidgetIconPanel_plugins').click();
cy.testid('WidgetIconPanel_settings').click();
cy.contains('Manage plugins').click();
cy.contains('dbgate-plugin-theme-total-white').click();
// text from plugin markdown
cy.contains('Total white theme');
// wait for load logos
cy.wait(2000);
cy.themeshot('plugin');
cy.themeshot('view-plugin-tab');
});
it('Edit mongo data JSON', () => {
@@ -306,7 +337,7 @@ describe('Data browser data', () => {
cy.contains('Helena').rightclick();
cy.contains('Delete document').click();
cy.contains('Save').click();
cy.themeshot('mongosave');
cy.themeshot('save-changes-mongodb');
});
it('Edit mongo data JSON', () => {
@@ -320,7 +351,7 @@ describe('Data browser data', () => {
cy.testid('ColumnManagerRow_checkbox__id').click();
cy.testid('DataFilterControl_input_countries.1').type('EXISTS{enter}');
cy.testid('WidgetIconPanel_cell-data').click();
cy.themeshot('collection');
cy.themeshot('mongodb-json-cell-view');
});
it('Table structure editor', () => {
@@ -329,10 +360,10 @@ describe('Data browser data', () => {
cy.contains('Customer').rightclick();
cy.contains('Open structure').click();
cy.contains('varchar(40)');
cy.themeshot('structure');
cy.themeshot('table-structure-editor');
cy.contains('EmployeeId').click();
cy.contains('Ref column - Employee');
cy.themeshot('fkeditor');
cy.themeshot('foreign-key-editor');
});
it('Compare database', () => {
@@ -344,25 +375,31 @@ describe('Data browser data', () => {
cy.testid('CompareModelTab_gridObjects_Customer_Customer').click();
cy.testid('WidgetIconPanel_database').click();
cy.testid('CompareModelTab_tabDdl').click();
cy.themeshot('dbcompare');
cy.themeshot('compare-database-models');
cy.contains('Settings').click();
cy.testid('CompareModelTab_tabOperations').click();
cy.themeshot('comparesettings');
cy.themeshot('compare-database-settings');
});
it.skip('Query editor - AI assistant', () => {
it('Database chat', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewQuery').click();
cy.testid('QueryTab_switchAiAssistantButton').click();
cy.testid('QueryAiAssistant_allowSendToAiServiceButton').click();
cy.testid('ConfirmModal_okButton').click();
cy.testid('QueryAiAssistant_promptInput').type('album names');
cy.testid('QueryAiAssistant_queryFromQuestionButton').click();
cy.contains('Use this').click();
cy.testid('QueryTab_executeButton').click();
cy.contains('Balls to the Wall');
cy.themeshot('aiassistant');
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_databaseChat').click();
cy.wait(1000);
cy.get('body').realType('find most popular artist');
cy.get('body').realPress('{enter}');
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 20000 }).click();
cy.wait(20000);
// cy.contains('Iron Maiden');
cy.themeshot('database-chat');
// cy.testid('DatabaseChatTab_promptInput').click();
// cy.get('body').realType('I need top 10 songs with the biggest income');
// cy.get('body').realPress('{enter}');
// cy.contains('Hot Girl', { timeout: 20000 });
// cy.wait(1000);
// cy.themeshot('database-chat');
});
it('Modify data', () => {
@@ -388,7 +425,7 @@ describe('Data browser data', () => {
cy.contains('INSERT INTO `Employee`');
cy.contains("SET `FirstName`='Jane'");
cy.contains('DELETE FROM `Employee`');
cy.themeshot('modifydata');
cy.themeshot('data-browser-save-changes');
// cy.testid('ConfirmSqlModal_okButton').click();
// cy.contains('Cannot delete or update a parent row')
@@ -403,4 +440,60 @@ describe('Data browser data', () => {
cy.contains('Novak');
cy.contains('Rows: 8');
});
it('Export menu', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Album').click();
cy.testid('DataFilterControl_input_ArtistId').type('22{enter}');
// cy.contains('Presence').rightclick();
// cy.contains('Coda').rightclick();
// cy.testid('DropDownMenu-container-0').contains('Export').click();
cy.contains('Export').click();
// cy.wait(1000);
cy.themeshot('data-browser-export-menu');
});
it('MySQL native backup', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').rightclick();
cy.contains('Create database backup').click();
cy.contains('Customer');
cy.themeshot('mysql-backup-configuration');
});
it('View table YAML model', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').rightclick();
cy.contains('Export DB model').click();
cy.testid('ExportDbModelModal_archiveFolder').select('(Create new)');
cy.testid('InputTextModal_value').clear().type('test-model');
cy.testid('InputTextModal_ok').click();
cy.testid('ModalBase_window').themeshot('export-database-model-window', { padding: 50 });
cy.testid('ExportDbModelModal_exportButton').click();
cy.contains('Album').click();
cy.contains('autoIncrement');
cy.themeshot('database-model-table-yaml');
});
it('Data replicator', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('WidgetIconPanel_archive').click();
cy.contains('chinook-archive').rightclick();
cy.contains('Data deployer').click();
cy.contains('Dry run').click();
cy.testid('TableControl_row_2_checkbox').click();
cy.testid('TableControl_row_2').click();
cy.testid('DataDeploySettings_find_checkbox').click();
cy.testid('DataDeploySettings_create_checkbox').click();
cy.testid('WidgetIconPanel_archive').click();
cy.themeshot('data-deployer');
cy.testid('DataDeployTab_importIntoDb').click();
cy.testid('ConfirmDataDeployModal_okButton').click();
cy.contains('Replicated Customer, inserted 59 rows');
cy.contains('Finished job script');
cy.testid('DataDeployTab_importIntoDb').click();
cy.themeshot('data-replicator');
});
});

View File

@@ -0,0 +1,112 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
});
describe('Charts', () => {
it('Auto detect chart', () => {
cy.contains('MySql-connection').click();
cy.contains('charts_sample').click();
cy.testid('WidgetIconPanel_file').click();
cy.contains('chart1').click();
cy.contains('department_name');
// cy.testid('QueryTab_executeButton').click();
// cy.testid('QueryTab_openChartButton').click();
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('choose-detected-chart');
});
it('Two line charts', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_query').click();
cy.wait(1000);
cy.get('body').realType('SELECT InvoiceDate, Total from Invoice');
cy.contains('Execute').click();
cy.contains('Open chart').click();
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('two-line-charts');
});
it('Invoice naive autodetection', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_query').click();
cy.wait(1000);
cy.get('body').realType('SELECT * from Invoice');
cy.contains('Execute').click();
cy.contains('Open chart').click();
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('chart-naive-autodetection');
});
it('Invoice by country - grouped chart', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_query').click();
cy.wait(1000);
cy.get('body').realType(
"SELECT InvoiceDate, Total, BillingCountry from Invoice where BillingCountry in ('USA', 'Canada', 'Brazil', 'France', 'Germany')"
);
cy.contains('Execute').click();
cy.contains('Open chart').click();
cy.testid('ChartSelector_chart_1').click();
cy.testid('JslChart_customizeButton').click();
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('chart-grouped-autodetected');
cy.testid('ChartDefinitionEditor_chartTypeSelect').select('Bar');
cy.testid('ChartDefinitionEditor_xAxisTransformSelect').select('Date (Year)');
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('chart-grouped-bars');
});
it('Public Knowledge base - show chart', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.testid('WidgetIconPanel_cloud-public').click();
cy.testid('public-cloud-file-tag-mysql/folder-MySQL/tag-premium/top-tables-row-count.sql').click();
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('public-knowledge-base-tables-sizes');
});
it('Auto detect chart', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Invoice').rightclick();
cy.contains('SQL template').click();
cy.contains('SELECT').click();
cy.testid('QueryTab_detectChartButton').click();
cy.testid('QueryTab_executeButton').click();
cy.contains('Chart 1').click();
cy.testid('ChartSelector_chart_0').click();
cy.testid('JslChart_customizeButton').click();
cy.testid('ChartDefinitionEditor_chartTypeSelect').select('Bar');
cy.testid('ChartDefinitionEditor_chartTypeSelect').select('Line');
cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
cy.themeshot('query-result-chart');
});
it('New object window', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Invoice').click();
cy.testid('WidgetIconPanel_addButton').click();
cy.contains('Compare database');
cy.themeshot('new-object-window');
});
});

View File

@@ -0,0 +1,56 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
});
describe('Cloud tests', () => {
it('Private cloud', () => {
cy.testid('WidgetIconPanel_cloudAccount');
cy.window().then(win => {
win.__loginToCloudTest('dbgate.test@gmail.com');
});
cy.contains('dbgate.test@gmail.com');
// cy.testid('WidgetIconPanel_cloudAccount').click();
// cy.origin('https://identity.dbgate.io', () => {
// cy.contains('Sign in with GitHub').click();
// });
// cy.origin('https://github.com', () => {
// cy.get('#login_field').type('dbgatetest');
// cy.get('#password').type('Pwd2020Db');
// cy.get('input[type="submit"]').click();
// });
// cy.wait(3000);
// cy.location('origin').then(origin => {
// if (origin === 'https://github.com') {
// // Still on github.com → an authorization step is waiting
// cy.origin('https://github.com', () => {
// // Try once, don't wait the full default timeout
// cy.get('button[data-octo-click="oauth_application_authorization"]', { timeout: 500, log: false }).click(); // if the button exists it will be clicked
// // if not, the short timeout elapses and we drop out
// });
// } else {
// // Already back on localhost nothing to authorize
// cy.log('OAuth redirect skipped the Authorize screen');
// }
// });
cy.contains('Testing Connections').rightclick();
cy.contains('Administrate access').click();
cy.contains('User email');
cy.themeshot('administer-shared-folder');
});
});

View File

@@ -59,7 +59,8 @@ describe('Transactions', () => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.testid('TabsPanel_buttonNewQuery').click();
cy.testid('TabsPanel_buttonNewObject').click();
cy.testid('NewObjectModal_query').click();
cy.wait(1000);
cy.get('body').type(
formatQueryWithoutParams(driver, "INSERT INTO ~categories (~category_id, ~category_name) VALUES (5, 'test');")

View File

@@ -45,36 +45,36 @@ describe('Run as portal', () => {
cy.get('[data-testid=InputTextModal_ok]').click();
});
it('Import Chinook MySQL', () => {
cy.visit('http://localhost:3000');
cy.contains('MySql-connection').click();
cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
cy.contains('Chinook').rightclick();
cy.contains('Restore/import SQL dump').click();
cy.get('#uploadFileButton').selectFile('data/chinook-mysql.sql', { force: true });
cy.wait(500);
cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
cy.contains('Importing database');
cy.contains('Finished job script');
cy.get('[data-testid=RunScriptModal_close]').click();
cy.contains('Chinook').click();
cy.contains('Album');
});
// it('Import Chinook MySQL', () => {
// cy.visit('http://localhost:3000');
// cy.contains('MySql-connection').click();
// cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
// cy.contains('Chinook').rightclick();
// cy.contains('Restore/import SQL dump').click();
// cy.get('#uploadFileButton').selectFile('data/chinook-mysql.sql', { force: true });
// cy.wait(500);
// cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
// cy.contains('Importing database');
// cy.contains('Finished job script');
// cy.get('[data-testid=RunScriptModal_close]').click();
// cy.contains('Chinook').click();
// cy.contains('Album');
// });
it('Import Chinook Postgres', () => {
cy.visit('http://localhost:3000');
cy.contains('Postgres-connection').click();
cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
cy.contains('Restore/import SQL dump').click();
cy.get('#uploadFileButton').selectFile('data/chinook-postgres.sql', { force: true });
cy.wait(500);
cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
cy.contains('Importing database');
cy.contains('Finished job script');
cy.get('[data-testid=RunScriptModal_close]').click();
cy.contains('Chinook').click();
cy.contains('album');
});
// it('Import Chinook Postgres', () => {
// cy.visit('http://localhost:3000');
// cy.contains('Postgres-connection').click();
// cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
// cy.contains('Restore/import SQL dump').click();
// cy.get('#uploadFileButton').selectFile('data/chinook-postgres.sql', { force: true });
// cy.wait(500);
// cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
// cy.contains('Importing database');
// cy.contains('Finished job script');
// cy.get('[data-testid=RunScriptModal_close]').click();
// cy.contains('Chinook').click();
// cy.contains('album');
// });
it('Open ask pwd connection', () => {
cy.visit('http://localhost:3000');
@@ -83,7 +83,7 @@ describe('Run as portal', () => {
cy.testid('DatabaseLoginModal_password').clear().type('Pwd2020Db');
cy.testid('DatabaseLoginModal_connect').click();
cy.contains('Chinook').click();
cy.contains('album');
// cy.contains('album');
});
// it('import chinook DB', () => {

View File

@@ -1,3 +1,12 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if the error message matches the one about WorkerGlobalScope importScripts
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'")) {
// return false to let Cypress know we intentionally want to ignore this error
return false;
}
// otherwise let Cypress throw the error
});
beforeEach(() => {
cy.visit('http://localhost:3000');
cy.viewport(1250, 900);
@@ -11,21 +20,23 @@ describe('Team edition tests', () => {
cy.testid('AdminMenuWidget_itemConnections').click();
cy.contains('New connection').click();
cy.contains('New connection').click();
cy.contains('New connection').click();
cy.testid('ConnectionDriverFields_connectionType').select('PostgreSQL');
cy.themeshot('connadmin');
cy.themeshot('connection-administration');
cy.testid('AdminMenuWidget_itemRoles').click();
cy.contains('Permissions').click();
cy.themeshot('roleadmin');
cy.contains('logged-user').click();
cy.themeshot('role-administration');
cy.testid('AdminMenuWidget_itemUsers').click();
cy.contains('New user').click();
cy.themeshot('user-administration');
cy.testid('AdminMenuWidget_itemAuthentication').click();
cy.contains('Add authentication').click();
cy.contains('Use database login').click();
cy.contains('Add authentication').click();
cy.contains('OAuth 2.0').click();
cy.themeshot('authadmin');
cy.themeshot('authentication-administration');
});
it('OAuth authentication', () => {
@@ -77,6 +88,35 @@ describe('Team edition tests', () => {
cy.testid('LoginPage_submitLogin').click();
cy.testid('AdminMenuWidget_itemUsers').click();
cy.contains('test@example.com');
cy.contains('Rows: 1');
});
it('Audit logging', () => {
cy.testid('LoginPage_linkAdmin').click();
cy.testid('LoginPage_password').type('adminpwd');
cy.testid('LoginPage_submitLogin').click();
cy.testid('AdminMenuWidget_itemAuditLog').click();
cy.contains('Audit log is not enabled');
cy.testid('AdminMenuWidget_itemSettings').click();
cy.testid('AdminSettingsTab_auditLogCheckbox').click();
cy.testid('AdminMenuWidget_itemAuditLog').click();
cy.contains('No data for selected date');
cy.testid('AdminMenuWidget_itemConnections').click();
cy.contains('Open table').click();
cy.contains('displayName');
cy.get('.toolstrip').contains('Export').click();
cy.contains('CSV file').click();
cy.testid('AdminMenuWidget_itemUsers').click();
cy.contains('Open table').click();
cy.contains('login');
cy.get('.toolstrip').contains('Export').click();
cy.contains('XML file').click();
cy.testid('AdminMenuWidget_itemAuditLog').click();
cy.testid('AdminAuditLogTab_refreshButton').click();
cy.contains('Exporting query').click();
cy.themeshot('auditlog');
});
});

View File

@@ -42,3 +42,11 @@ beforeEach(() => {
});
});
});
// Cypress.Screenshot.defaults({
// onBeforeScreenshot() {
// if (window.Chart) {
// Object.values(window.Chart.instances).forEach(c => c.resize());
// }
// },
// });

View File

@@ -0,0 +1,6 @@
{"__isStreamHeader":true,"pureName":"departments","schemaName":"dbo","objectId":1205579333,"createDate":"2025-06-12T10:30:34.083Z","modifyDate":"2025-06-12T10:30:34.120Z","contentHash":"2025-06-12T10:30:34.120Z","columns":[{"columnName":"id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"name","dataType":"varchar(100)","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false}],"primaryKey":{"constraintName":"PK__departme__3213E83FE8E7043D","schemaName":"dbo","pureName":"departments","constraintType":"primaryKey","columns":[{"columnName":"id"}]},"foreignKeys":[],"indexes":[],"uniques":[],"engine":"mssql@dbgate-plugin-mssql"}
{"id":1,"name":"IT"}
{"id":2,"name":"Marketing"}
{"id":3,"name":"Finance"}
{"id":4,"name":"Human Resources"}
{"id":5,"name":"Research and Development"}

View File

@@ -0,0 +1,12 @@
name: departments
columns:
- name: id
type: int
default: null
notNull: true
- name: name
type: varchar(100)
default: null
notNull: true
primaryKey:
- id

View File

@@ -0,0 +1,39 @@
{"__isStreamHeader":true,"pureName":"employee_project","schemaName":"dbo","objectId":1333579789,"createDate":"2025-06-12T10:30:34.133Z","modifyDate":"2025-06-12T10:30:34.133Z","contentHash":"2025-06-12T10:30:34.133Z","columns":[{"columnName":"employee_id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"project_id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"role","dataType":"varchar(50)","notNull":false,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false}],"primaryKey":{"constraintName":"PK__employee__2EE9924949ED9668","schemaName":"dbo","pureName":"employee_project","constraintType":"primaryKey","columns":[{"columnName":"employee_id"},{"columnName":"project_id"}]},"foreignKeys":[{"constraintName":"FK__employee___emplo__5165187F","constraintType":"foreignKey","schemaName":"dbo","pureName":"employee_project","refSchemaName":"dbo","refTableName":"employees","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"employee_id","refColumnName":"id"}]},{"constraintName":"FK__employee___proje__52593CB8","constraintType":"foreignKey","schemaName":"dbo","pureName":"employee_project","refSchemaName":"dbo","refTableName":"projects","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"project_id","refColumnName":"id"}]}],"indexes":[],"uniques":[],"engine":"mssql@dbgate-plugin-mssql"}
{"employee_id":1,"project_id":6,"role":"Manager"}
{"employee_id":1,"project_id":8,"role":"Developer"}
{"employee_id":2,"project_id":7,"role":"Tester"}
{"employee_id":2,"project_id":8,"role":"Developer"}
{"employee_id":3,"project_id":4,"role":"Analyst"}
{"employee_id":3,"project_id":6,"role":"Developer"}
{"employee_id":4,"project_id":2,"role":"Manager"}
{"employee_id":4,"project_id":4,"role":"Analyst"}
{"employee_id":4,"project_id":5,"role":"Analyst"}
{"employee_id":5,"project_id":5,"role":"Tester"}
{"employee_id":6,"project_id":1,"role":"Analyst"}
{"employee_id":6,"project_id":6,"role":"Tester"}
{"employee_id":6,"project_id":9,"role":"Manager"}
{"employee_id":7,"project_id":8,"role":"Manager"}
{"employee_id":8,"project_id":10,"role":"Analyst"}
{"employee_id":9,"project_id":2,"role":"Analyst"}
{"employee_id":9,"project_id":6,"role":"Analyst"}
{"employee_id":9,"project_id":7,"role":"Developer"}
{"employee_id":10,"project_id":2,"role":"Manager"}
{"employee_id":10,"project_id":6,"role":"Analyst"}
{"employee_id":11,"project_id":1,"role":"Tester"}
{"employee_id":12,"project_id":4,"role":"Tester"}
{"employee_id":13,"project_id":2,"role":"Developer"}
{"employee_id":13,"project_id":3,"role":"Analyst"}
{"employee_id":13,"project_id":7,"role":"Developer"}
{"employee_id":14,"project_id":3,"role":"Developer"}
{"employee_id":14,"project_id":9,"role":"Manager"}
{"employee_id":15,"project_id":1,"role":"Developer"}
{"employee_id":15,"project_id":5,"role":"Manager"}
{"employee_id":16,"project_id":3,"role":"Tester"}
{"employee_id":16,"project_id":5,"role":"Developer"}
{"employee_id":17,"project_id":6,"role":"Analyst"}
{"employee_id":18,"project_id":1,"role":"Tester"}
{"employee_id":18,"project_id":5,"role":"Tester"}
{"employee_id":18,"project_id":6,"role":"Manager"}
{"employee_id":19,"project_id":6,"role":"Analyst"}
{"employee_id":20,"project_id":2,"role":"Developer"}
{"employee_id":20,"project_id":4,"role":"Developer"}

View File

@@ -0,0 +1,18 @@
name: employee_project
columns:
- name: employee_id
type: int
default: null
notNull: true
references: employees
- name: project_id
type: int
default: null
notNull: true
references: projects
- name: role
type: varchar(50)
default: null
primaryKey:
- employee_id
- project_id

View File

@@ -0,0 +1,21 @@
{"__isStreamHeader":true,"pureName":"employees","schemaName":"dbo","objectId":1237579447,"createDate":"2025-06-12T10:30:34.113Z","modifyDate":"2025-06-12T12:35:22.140Z","contentHash":"2025-06-12T12:35:22.140Z","columns":[{"columnName":"id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"name","dataType":"varchar(100)","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"email","dataType":"varchar(100)","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"hire_date","dataType":"date","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"department_id","dataType":"int","notNull":false,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false}],"primaryKey":{"constraintName":"PK__employee__3213E83FE576E55A","schemaName":"dbo","pureName":"employees","constraintType":"primaryKey","columns":[{"columnName":"id"}]},"foreignKeys":[{"constraintName":"FK__employees__depar__4CA06362","constraintType":"foreignKey","schemaName":"dbo","pureName":"employees","refSchemaName":"dbo","refTableName":"departments","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"department_id","refColumnName":"id"}]}],"indexes":[],"uniques":[{"constraintName":"UQ__employee__AB6E6164E18D883F","columns":[{"columnName":"email"}]}],"engine":"mssql@dbgate-plugin-mssql"}
{"id":1,"name":"John Smith","email":"john.smith@example.com","hire_date":"2018-07-09T00:00:00.000Z","department_id":2}
{"id":2,"name":"Jane Garcia","email":"jane.garcia@example.com","hire_date":"2019-10-13T00:00:00.000Z","department_id":5}
{"id":3,"name":"Grace Smith","email":"grace.smith@example.com","hire_date":"2019-03-16T00:00:00.000Z","department_id":1}
{"id":4,"name":"Charlie Williams","email":"charlie.williams@example.com","hire_date":"2020-10-18T00:00:00.000Z","department_id":2}
{"id":5,"name":"Eve Brown","email":"eve.brown@example.com","hire_date":"2018-04-12T00:00:00.000Z","department_id":4}
{"id":6,"name":"Alice Moore","email":"alice.moore@example.com","hire_date":"2019-04-20T00:00:00.000Z","department_id":2}
{"id":7,"name":"Eve Williams","email":"eve.williams@example.com","hire_date":"2020-04-26T00:00:00.000Z","department_id":4}
{"id":8,"name":"Eve Jones","email":"eve.jones@example.com","hire_date":"2022-10-04T00:00:00.000Z","department_id":3}
{"id":9,"name":"Diana Miller","email":"diana.miller@example.com","hire_date":"2021-03-28T00:00:00.000Z","department_id":1}
{"id":10,"name":"Diana Smith","email":"diana.smith@example.com","hire_date":"2018-04-12T00:00:00.000Z","department_id":2}
{"id":11,"name":"Hank Johnson","email":"hank.johnson@example.com","hire_date":"2020-09-16T00:00:00.000Z","department_id":2}
{"id":12,"name":"Frank Miller","email":"frank.miller@example.com","hire_date":"2023-01-12T00:00:00.000Z","department_id":4}
{"id":13,"name":"Jane Brown","email":"jane.brown@example.com","hire_date":"2023-05-07T00:00:00.000Z","department_id":3}
{"id":14,"name":"Grace Davis","email":"grace.davis@example.com","hire_date":"2019-08-22T00:00:00.000Z","department_id":3}
{"id":15,"name":"Jane Black","email":"jane.black@example.com","hire_date":"2019-04-28T00:00:00.000Z","department_id":2}
{"id":16,"name":"Charlie Smith","email":"charlie.smith@example.com","hire_date":"2019-06-12T00:00:00.000Z","department_id":5}
{"id":17,"name":"Eve Johnson","email":"eve.johnson@example.com","hire_date":"2020-11-07T00:00:00.000Z","department_id":5}
{"id":18,"name":"Jane Johnson","email":"jane.johnson@example.com","hire_date":"2020-04-06T00:00:00.000Z","department_id":2}
{"id":19,"name":"Hank Brown","email":"hank.brown@example.com","hire_date":"2023-05-10T00:00:00.000Z","department_id":2}
{"id":20,"name":"Frank Jones","email":"frank.jones@example.com","hire_date":"2020-10-26T00:00:00.000Z","department_id":1}

View File

@@ -0,0 +1,28 @@
name: employees
columns:
- name: id
type: int
default: null
notNull: true
- name: name
type: varchar(100)
default: null
notNull: true
- name: email
type: varchar(100)
default: null
notNull: true
- name: hire_date
type: date
default: null
notNull: true
- name: department_id
type: int
default: null
references: departments
primaryKey:
- id
uniques:
- name: UQ__employee__AB6E6164E18D883F
columns:
- email

View File

@@ -0,0 +1,141 @@
{"__isStreamHeader":true,"pureName":"finance_reports","schemaName":"dbo","objectId":338100245,"createDate":"2025-06-23T12:15:08.727Z","modifyDate":"2025-06-23T12:15:08.750Z","contentHash":"2025-06-23T12:15:08.750Z","columns":[{"columnName":"id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"date","dataType":"date","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"profit","dataType":"money","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false}],"foreignKeys":[{"constraintName":"project_id","constraintType":"foreignKey","schemaName":"dbo","pureName":"finance_reports","refSchemaName":"dbo","refTableName":"projects","updateAction":"NO ACTION","deleteAction":"NO ACTION","columns":[{"columnName":"id","refColumnName":"id"}]}],"indexes":[],"uniques":[],"engine":"mssql@dbgate-plugin-mssql"}
{"id":1,"date":"2022-01-01T00:00:00.000Z","profit":73923.4}
{"id":1,"date":"2022-01-31T00:00:00.000Z","profit":21837.75}
{"id":1,"date":"2022-03-02T00:00:00.000Z","profit":67859.8}
{"id":1,"date":"2022-04-01T00:00:00.000Z","profit":77403.3}
{"id":1,"date":"2022-05-01T00:00:00.000Z","profit":84083.19}
{"id":1,"date":"2022-05-31T00:00:00.000Z","profit":30040.55}
{"id":1,"date":"2022-06-30T00:00:00.000Z","profit":50947.14}
{"id":1,"date":"2022-07-30T00:00:00.000Z","profit":63345.62}
{"id":1,"date":"2022-08-29T00:00:00.000Z","profit":23819.45}
{"id":1,"date":"2022-09-28T00:00:00.000Z","profit":-25919.19}
{"id":1,"date":"2022-10-28T00:00:00.000Z","profit":27967.6}
{"id":1,"date":"2022-11-27T00:00:00.000Z","profit":-37402.36}
{"id":1,"date":"2022-12-27T00:00:00.000Z","profit":94528.8}
{"id":1,"date":"2023-01-26T00:00:00.000Z","profit":29491.03}
{"id":1,"date":"2023-02-25T00:00:00.000Z","profit":81541.29}
{"id":2,"date":"2022-01-01T00:00:00.000Z","profit":18070.94}
{"id":2,"date":"2022-01-31T00:00:00.000Z","profit":-40609.87}
{"id":2,"date":"2022-03-02T00:00:00.000Z","profit":42435.51}
{"id":2,"date":"2022-04-01T00:00:00.000Z","profit":-11915.15}
{"id":2,"date":"2022-05-01T00:00:00.000Z","profit":-37417.4}
{"id":2,"date":"2022-05-31T00:00:00.000Z","profit":23028.66}
{"id":2,"date":"2022-06-30T00:00:00.000Z","profit":-6895.49}
{"id":2,"date":"2022-07-30T00:00:00.000Z","profit":63114.54}
{"id":2,"date":"2022-08-29T00:00:00.000Z","profit":94646.99}
{"id":2,"date":"2022-09-28T00:00:00.000Z","profit":99560.77}
{"id":2,"date":"2022-10-28T00:00:00.000Z","profit":62216.22}
{"id":2,"date":"2022-11-27T00:00:00.000Z","profit":85094.32}
{"id":2,"date":"2022-12-27T00:00:00.000Z","profit":-23378.37}
{"id":2,"date":"2023-01-26T00:00:00.000Z","profit":47635.86}
{"id":2,"date":"2023-02-25T00:00:00.000Z","profit":33727.72}
{"id":3,"date":"2022-01-01T00:00:00.000Z","profit":33088.03}
{"id":3,"date":"2022-01-31T00:00:00.000Z","profit":66668.91}
{"id":3,"date":"2022-03-02T00:00:00.000Z","profit":5344.27}
{"id":3,"date":"2022-04-01T00:00:00.000Z","profit":22122.99}
{"id":3,"date":"2022-05-01T00:00:00.000Z","profit":27342.01}
{"id":3,"date":"2022-05-31T00:00:00.000Z","profit":55479.42}
{"id":3,"date":"2022-06-30T00:00:00.000Z","profit":35956.11}
{"id":3,"date":"2022-07-30T00:00:00.000Z","profit":9667.12}
{"id":3,"date":"2022-08-29T00:00:00.000Z","profit":63430.18}
{"id":3,"date":"2022-09-28T00:00:00.000Z","profit":-4883.41}
{"id":3,"date":"2022-10-28T00:00:00.000Z","profit":38902.8}
{"id":3,"date":"2022-11-27T00:00:00.000Z","profit":-25500.13}
{"id":3,"date":"2022-12-27T00:00:00.000Z","profit":65074.21}
{"id":3,"date":"2023-01-26T00:00:00.000Z","profit":12570.27}
{"id":3,"date":"2023-02-25T00:00:00.000Z","profit":35418.36}
{"id":4,"date":"2022-01-01T00:00:00.000Z","profit":68282.98}
{"id":4,"date":"2022-01-31T00:00:00.000Z","profit":77778.99}
{"id":4,"date":"2022-03-02T00:00:00.000Z","profit":95490.49}
{"id":4,"date":"2022-04-01T00:00:00.000Z","profit":-44466.37}
{"id":4,"date":"2022-05-01T00:00:00.000Z","profit":40215.71}
{"id":4,"date":"2022-05-31T00:00:00.000Z","profit":-31228.87}
{"id":4,"date":"2022-06-30T00:00:00.000Z","profit":60667.69}
{"id":4,"date":"2022-07-30T00:00:00.000Z","profit":71439.16}
{"id":4,"date":"2022-08-29T00:00:00.000Z","profit":-25077.4}
{"id":4,"date":"2022-09-28T00:00:00.000Z","profit":-36128.2}
{"id":4,"date":"2022-10-28T00:00:00.000Z","profit":36727.68}
{"id":4,"date":"2022-11-27T00:00:00.000Z","profit":-24207.2}
{"id":4,"date":"2022-12-27T00:00:00.000Z","profit":63846.96}
{"id":5,"date":"2022-01-01T00:00:00.000Z","profit":21648.3}
{"id":5,"date":"2022-01-31T00:00:00.000Z","profit":59263.22}
{"id":5,"date":"2022-03-02T00:00:00.000Z","profit":49154.51}
{"id":5,"date":"2022-04-01T00:00:00.000Z","profit":34787.48}
{"id":5,"date":"2022-05-01T00:00:00.000Z","profit":-24120.19}
{"id":5,"date":"2022-05-31T00:00:00.000Z","profit":98437.86}
{"id":5,"date":"2022-06-30T00:00:00.000Z","profit":18614.77}
{"id":5,"date":"2022-07-30T00:00:00.000Z","profit":17680.34}
{"id":5,"date":"2022-08-29T00:00:00.000Z","profit":74406.86}
{"id":5,"date":"2022-09-28T00:00:00.000Z","profit":61845.3}
{"id":5,"date":"2022-10-28T00:00:00.000Z","profit":-37889.59}
{"id":5,"date":"2022-11-27T00:00:00.000Z","profit":76651.05}
{"id":5,"date":"2022-12-27T00:00:00.000Z","profit":58739.6}
{"id":5,"date":"2023-01-26T00:00:00.000Z","profit":82605.85}
{"id":6,"date":"2022-01-01T00:00:00.000Z","profit":-5206.8}
{"id":6,"date":"2022-01-31T00:00:00.000Z","profit":27498.27}
{"id":6,"date":"2022-03-02T00:00:00.000Z","profit":-2939.84}
{"id":6,"date":"2022-04-01T00:00:00.000Z","profit":-37261.08}
{"id":6,"date":"2022-05-01T00:00:00.000Z","profit":37069.04}
{"id":6,"date":"2022-05-31T00:00:00.000Z","profit":524.88}
{"id":6,"date":"2022-06-30T00:00:00.000Z","profit":-29620.85}
{"id":6,"date":"2022-07-30T00:00:00.000Z","profit":35540.81}
{"id":6,"date":"2022-08-29T00:00:00.000Z","profit":20608.94}
{"id":6,"date":"2022-09-28T00:00:00.000Z","profit":34809.33}
{"id":6,"date":"2022-10-28T00:00:00.000Z","profit":-44949.05}
{"id":6,"date":"2022-11-27T00:00:00.000Z","profit":-22524.26}
{"id":6,"date":"2022-12-27T00:00:00.000Z","profit":37841.58}
{"id":7,"date":"2022-01-01T00:00:00.000Z","profit":6903.17}
{"id":7,"date":"2022-01-31T00:00:00.000Z","profit":58480.84}
{"id":7,"date":"2022-03-02T00:00:00.000Z","profit":48217.34}
{"id":7,"date":"2022-04-01T00:00:00.000Z","profit":73592.44}
{"id":7,"date":"2022-05-01T00:00:00.000Z","profit":-21831.18}
{"id":7,"date":"2022-05-31T00:00:00.000Z","profit":-40926.16}
{"id":7,"date":"2022-06-30T00:00:00.000Z","profit":62299.5}
{"id":7,"date":"2022-07-30T00:00:00.000Z","profit":95376.53}
{"id":7,"date":"2022-08-29T00:00:00.000Z","profit":-13317.36}
{"id":7,"date":"2022-09-28T00:00:00.000Z","profit":81565.05}
{"id":7,"date":"2022-10-28T00:00:00.000Z","profit":77420.52}
{"id":7,"date":"2022-11-27T00:00:00.000Z","profit":-12052.47}
{"id":7,"date":"2022-12-27T00:00:00.000Z","profit":37742.07}
{"id":7,"date":"2023-01-26T00:00:00.000Z","profit":-8057.99}
{"id":8,"date":"2022-01-01T00:00:00.000Z","profit":27213.73}
{"id":8,"date":"2022-01-31T00:00:00.000Z","profit":34271.75}
{"id":8,"date":"2022-03-02T00:00:00.000Z","profit":-44549.47}
{"id":8,"date":"2022-04-01T00:00:00.000Z","profit":15236.34}
{"id":8,"date":"2022-05-01T00:00:00.000Z","profit":-27759.81}
{"id":8,"date":"2022-05-31T00:00:00.000Z","profit":7955.12}
{"id":8,"date":"2022-06-30T00:00:00.000Z","profit":-34484.38}
{"id":8,"date":"2022-07-30T00:00:00.000Z","profit":-49758.7}
{"id":8,"date":"2022-08-29T00:00:00.000Z","profit":-41990.86}
{"id":8,"date":"2022-09-28T00:00:00.000Z","profit":58123.01}
{"id":8,"date":"2022-10-28T00:00:00.000Z","profit":30128.78}
{"id":8,"date":"2022-11-27T00:00:00.000Z","profit":-10151.17}
{"id":8,"date":"2022-12-27T00:00:00.000Z","profit":54048.33}
{"id":8,"date":"2023-01-26T00:00:00.000Z","profit":-43123.17}
{"id":9,"date":"2022-01-01T00:00:00.000Z","profit":61031.83}
{"id":9,"date":"2022-01-31T00:00:00.000Z","profit":68577.58}
{"id":9,"date":"2022-03-02T00:00:00.000Z","profit":88698.97}
{"id":9,"date":"2022-04-01T00:00:00.000Z","profit":8906.03}
{"id":9,"date":"2022-05-01T00:00:00.000Z","profit":28824.73}
{"id":9,"date":"2022-05-31T00:00:00.000Z","profit":88280.34}
{"id":9,"date":"2022-06-30T00:00:00.000Z","profit":35266.09}
{"id":9,"date":"2022-07-30T00:00:00.000Z","profit":-38025.36}
{"id":9,"date":"2022-08-29T00:00:00.000Z","profit":-12118.53}
{"id":9,"date":"2022-09-28T00:00:00.000Z","profit":-27265.86}
{"id":9,"date":"2022-10-28T00:00:00.000Z","profit":56870.57}
{"id":9,"date":"2022-11-27T00:00:00.000Z","profit":88078.95}
{"id":9,"date":"2022-12-27T00:00:00.000Z","profit":-24059.67}
{"id":9,"date":"2023-01-26T00:00:00.000Z","profit":-13301.43}
{"id":10,"date":"2022-01-01T00:00:00.000Z","profit":-22479.23}
{"id":10,"date":"2022-01-31T00:00:00.000Z","profit":8106.27}
{"id":10,"date":"2022-03-02T00:00:00.000Z","profit":69372.19}
{"id":10,"date":"2022-04-01T00:00:00.000Z","profit":-11895.74}
{"id":10,"date":"2022-05-01T00:00:00.000Z","profit":-33206.5}
{"id":10,"date":"2022-05-31T00:00:00.000Z","profit":56073.34}
{"id":10,"date":"2022-06-30T00:00:00.000Z","profit":67488.3}
{"id":10,"date":"2022-07-30T00:00:00.000Z","profit":48529.23}
{"id":10,"date":"2022-08-29T00:00:00.000Z","profit":28680.2}
{"id":10,"date":"2022-09-28T00:00:00.000Z","profit":59311.16}
{"id":10,"date":"2022-10-28T00:00:00.000Z","profit":25315.78}
{"id":10,"date":"2022-11-27T00:00:00.000Z","profit":36116.38}
{"id":10,"date":"2022-12-27T00:00:00.000Z","profit":-42040.4}

View File

@@ -0,0 +1,15 @@
name: finance_reports
columns:
- name: id
type: int
default: null
notNull: true
references: projects
- name: date
type: date
default: null
notNull: true
- name: profit
type: money
default: null
notNull: true

View File

@@ -0,0 +1,11 @@
{"__isStreamHeader":true,"pureName":"projects","schemaName":"dbo","objectId":1301579675,"createDate":"2025-06-12T10:30:34.127Z","modifyDate":"2025-06-23T12:15:08.750Z","contentHash":"2025-06-23T12:15:08.750Z","columns":[{"columnName":"id","dataType":"int","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"name","dataType":"varchar(100)","notNull":true,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"start_date","dataType":"date","notNull":false,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false},{"columnName":"end_date","dataType":"date","notNull":false,"autoIncrement":false,"defaultValue":null,"defaultConstraint":null,"computedExpression":null,"hasAutoValue":false}],"primaryKey":{"constraintName":"PK__projects__3213E83F26A7ED11","schemaName":"dbo","pureName":"projects","constraintType":"primaryKey","columns":[{"columnName":"id"}]},"foreignKeys":[],"indexes":[],"uniques":[],"engine":"mssql@dbgate-plugin-mssql"}
{"id":1,"name":"Apollo Upgrade","start_date":"2020-04-27T00:00:00.000Z","end_date":"2020-10-19T00:00:00.000Z"}
{"id":2,"name":"Market Expansion","start_date":"2022-08-04T00:00:00.000Z","end_date":"2023-06-20T00:00:00.000Z"}
{"id":3,"name":"AI Integration","start_date":"2020-05-11T00:00:00.000Z","end_date":"2021-07-10T00:00:00.000Z"}
{"id":4,"name":"Cost Reduction","start_date":"2022-01-08T00:00:00.000Z","end_date":"2022-07-12T00:00:00.000Z"}
{"id":5,"name":"Cloud Migration","start_date":"2021-01-11T00:00:00.000Z","end_date":"2021-05-27T00:00:00.000Z"}
{"id":6,"name":"Customer Portal","start_date":"2021-07-13T00:00:00.000Z","end_date":"2022-09-22T00:00:00.000Z"}
{"id":7,"name":"Data Lake","start_date":"2021-02-25T00:00:00.000Z","end_date":"2021-08-21T00:00:00.000Z"}
{"id":8,"name":"UX Overhaul","start_date":"2021-05-20T00:00:00.000Z","end_date":"2022-09-10T00:00:00.000Z"}
{"id":9,"name":"Security Hardening","start_date":"2021-05-28T00:00:00.000Z","end_date":"2022-07-28T00:00:00.000Z"}
{"id":10,"name":"Mobile App Revamp","start_date":"2021-11-17T00:00:00.000Z","end_date":"2022-06-04T00:00:00.000Z"}

View File

@@ -0,0 +1,18 @@
name: projects
columns:
- name: id
type: int
default: null
notNull: true
- name: name
type: varchar(100)
default: null
notNull: true
- name: start_date
type: date
default: null
- name: end_date
type: date
default: null
primaryKey:
- id

View File

@@ -0,0 +1,23 @@
-- >>>
-- autoExecute: true
-- splitterInitialValue: 20%
-- selected-chart: 1
-- <<<
SELECT
d.name AS department_name,
FORMAT(fr.date, 'yyyy-MM') AS month,
SUM(fr.profit) AS total_monthly_profit
FROM
departments d
JOIN
employees e ON d.id = e.department_id
JOIN
employee_project ep ON e.id = ep.employee_id
JOIN
finance_reports fr ON ep.project_id = fr.id
GROUP BY
d.name, FORMAT(fr.date, 'yyyy-MM')
ORDER BY
d.name, month;

View File

@@ -21,8 +21,8 @@ services:
build: containers/mysql-ssh-login
restart: always
ports:
- 16005:3306
- "16015:22"
- 16017:3306
- "16012:22"
mysql-ssh-keyfile:
build: containers/mysql-ssh-keyfile

8
e2e-tests/env/charts/.env vendored Normal file
View File

@@ -0,0 +1,8 @@
CONNECTIONS=mysql
LABEL_mysql=MySql-connection
SERVER_mysql=localhost
USER_mysql=root
PASSWORD_mysql=Pwd2020Db
PORT_mysql=16004
ENGINE_mysql=mysql@dbgate-plugin-mysql

2
e2e-tests/env/cloud/.env vendored Normal file
View File

@@ -0,0 +1,2 @@
ALLOW_DBGATE_PRIVATE_CLOUD=1
REDIRECT_TO_DBGATE_CLOUD_LOGIN=1

View File

@@ -195,6 +195,11 @@ async function run() {
path.join(baseDir, 'archive-e2etests', 'default')
);
await copyFolder(
path.resolve(path.join(__dirname, '../data/chinook-jsonl')),
path.join(baseDir, 'archive-e2etests', 'chinook-archive')
);
await copyFolder(
path.resolve(path.join(__dirname, '../data/files/query')),
path.join(baseDir, 'files-e2etests', 'query')

96
e2e-tests/init/charts.js Normal file
View File

@@ -0,0 +1,96 @@
const path = require('path');
const os = require('os');
const fs = require('fs');
const baseDir = path.join(os.homedir(), '.dbgate');
const dbgateApi = require('dbgate-api');
dbgateApi.initializeApiEnvironment();
const dbgatePluginMysql = require('dbgate-plugin-mysql');
dbgateApi.registerPlugins(dbgatePluginMysql);
async function copyFolder(source, target) {
if (!fs.existsSync(target)) {
fs.mkdirSync(target, { recursive: true });
}
for (const file of fs.readdirSync(source)) {
fs.copyFileSync(path.join(source, file), path.join(target, file));
}
}
async function initMySqlDatabase(dbname, inputFile) {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_mysql,
user: process.env.USER_mysql,
password: process.env.PASSWORD_mysql,
port: process.env.PORT_mysql,
engine: 'mysql@dbgate-plugin-mysql',
},
sql: `drop database if exists ${dbname}`,
});
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_mysql,
user: process.env.USER_mysql,
password: process.env.PASSWORD_mysql,
port: process.env.PORT_mysql,
engine: 'mysql@dbgate-plugin-mysql',
},
sql: `create database ${dbname}`,
});
await dbgateApi.importDatabase({
connection: {
server: process.env.SERVER_mysql,
user: process.env.USER_mysql,
password: process.env.PASSWORD_mysql,
port: process.env.PORT_mysql,
database: dbname,
engine: 'mysql@dbgate-plugin-mysql',
},
inputFile,
});
}
async function run() {
const connection = {
server: process.env.SERVER_mysql,
user: process.env.USER_mysql,
password: process.env.PASSWORD_mysql,
port: process.env.PORT_mysql,
engine: 'mysql@dbgate-plugin-mysql',
};
try {
await dbgateApi.executeQuery({
connection,
sql: 'drop database if exists charts_sample',
});
} catch (err) {
console.error('Failed to drop database', err);
}
await dbgateApi.executeQuery({
connection,
sql: 'create database charts_sample',
});
await dbgateApi.importDbFromFolder({
connection: {
...connection,
database: 'charts_sample',
},
folder: path.resolve(path.join(__dirname, '../data/charts-sample')),
});
await copyFolder(
path.resolve(path.join(__dirname, '../data/files/sql')),
path.join(baseDir, 'files-e2etests', 'sql')
);
await initMySqlDatabase('MyChinook', path.resolve(path.join(__dirname, '../data/chinook-mysql.sql')));
}
dbgateApi.runScript(run);

View File

@@ -21,6 +21,8 @@
"cy:run:browse-data": "cypress run --spec cypress/e2e/browse-data.cy.js",
"cy:run:team": "cypress run --spec cypress/e2e/team.cy.js",
"cy:run:multi-sql": "cypress run --spec cypress/e2e/multi-sql.cy.js",
"cy:run:cloud": "cypress run --spec cypress/e2e/cloud.cy.js",
"cy:run:charts": "cypress run --spec cypress/e2e/charts.cy.js",
"start:add-connection": "node clearTestingData && cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/portal/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
@@ -28,6 +30,8 @@
"start:browse-data": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/browse-data/.env node e2e-tests/init/browse-data.js && env-cmd -f e2e-tests/env/browse-data/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:team": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/team/.env node e2e-tests/init/team.js && env-cmd -f e2e-tests/env/team/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:multi-sql": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/multi-sql/.env node e2e-tests/init/multi-sql.js && env-cmd -f e2e-tests/env/multi-sql/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:cloud": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/cloud/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:charts": "node clearTestingData && cd .. && env-cmd -f e2e-tests/env/charts/.env node e2e-tests/init/charts.js && env-cmd -f e2e-tests/env/charts/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"test:add-connection": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection",
"test:portal": "start-server-and-test start:portal http://localhost:3000 cy:run:portal",
@@ -35,8 +39,10 @@
"test:browse-data": "start-server-and-test start:browse-data http://localhost:3000 cy:run:browse-data",
"test:team": "start-server-and-test start:team http://localhost:3000 cy:run:team",
"test:multi-sql": "start-server-and-test start:multi-sql http://localhost:3000 cy:run:multi-sql",
"test:cloud": "start-server-and-test start:cloud http://localhost:3000 cy:run:cloud",
"test:charts": "start-server-and-test start:charts http://localhost:3000 cy:run:charts",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql",
"test": "yarn test:add-connection && yarn test:portal && yarn test:oauth && yarn test:browse-data && yarn test:team && yarn test:multi-sql && yarn test:cloud && yarn test:charts",
"test:ci": "yarn test"
},
"dependencies": {}

View File

View File

@@ -1 +0,0 @@
Folder with screenshots

View File

@@ -29,7 +29,7 @@ async function testDatabaseDiff(conn, driver, mangle, createObject = null) {
driver,
`create table ~t2 (
~id int not null primary key,
~t1_id int null references ~t1(~id)
~t1_id int ${driver.dialect.implicitNullDeclaration ? '' : 'null'} references ~t1(~id)
)`
);
@@ -52,7 +52,7 @@ async function testDatabaseDiff(conn, driver, mangle, createObject = null) {
}
describe('Alter database', () => {
test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipReferences && !x.skipDropReferences).map(engine => [engine.label, engine]))(
'Drop referenced table - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDiff(conn, driver, db => {

View File

@@ -60,7 +60,9 @@ async function testTableDiff(engine, conn, driver, mangle) {
if (!engine.skipReferences) {
const query = formatQueryWithoutParams(
driver,
`create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))`
`create table ~t2 (~id int not null primary key, ~fkval int ${
driver.dialect.implicitNullDeclaration ? '' : 'null'
} references ~t1(~col_ref))`
);
await driver.query(conn, transformSqlForEngine(engine, query));
@@ -90,7 +92,7 @@ const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'co
// const TESTED_COLUMNS = ['col_std'];
// const TESTED_COLUMNS = ['col_ref'];
function create_engines_columns_source(engines) {
function createEnginesColumnsSource(engines) {
return _.flatten(
engines.map(engine =>
TESTED_COLUMNS.filter(col => col.endsWith('_pk') || !engine.skipNonPkRename)
@@ -116,45 +118,55 @@ describe('Alter table', () => {
})
);
const columnsSource = create_engines_columns_source(engines);
const dropableColumnsSrouce = columnsSource.filter(
([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')
test.each(engines.filter(i => i.supportTableComments).map(engine => [engine.label, engine]))(
'Add comment to table - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.objectComment = 'Added table comment';
});
})
);
const hasDropableColumns = dropableColumnsSrouce.length > 0;
if (hasDropableColumns) {
test.each(dropableColumnsSrouce)(
'Drop column - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
engine,
conn,
driver,
tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column))
);
})
);
}
test.each(engines.filter(i => i.supportColumnComments).map(engine => [engine.label, engine]))(
'Add comment to column - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.push({
columnName: 'added',
columnComment: 'Added column comment',
dataType: 'int',
pairingId: crypto.randomUUID(),
notNull: false,
autoIncrement: false,
});
});
})
);
const hasEnginesWithNullable = engines.filter(x => !x.skipNullable).length > 0;
test.each(
createEnginesColumnsSource(engines.filter(x => !x.skipDropColumn)).filter(
([_label, col, engine]) => !engine.skipPkDrop || !col.endsWith('_pk')
)
)(
'Drop column - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column)));
})
);
if (hasEnginesWithNullable) {
const source = create_engines_columns_source(engines.filter(x => !x.skipNullable));
test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullable && !x.skipChangeNullability)))(
'Change nullability - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
engine,
conn,
driver,
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x)))
);
})
);
test.each(source)(
'Change nullability - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
engine,
conn,
driver,
tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, notNull: true } : x)))
);
})
);
}
test.each(columnsSource)(
test.each(createEnginesColumnsSource(engines.filter(x => !x.skipRenameColumn)))(
'Rename column - %s - %s',
testWrapper(async (conn, driver, column, engine) => {
await testTableDiff(
@@ -175,37 +187,32 @@ describe('Alter table', () => {
})
);
const enginesWithDefault = engines.filter(x => !x.skipDefaultValue);
const hasEnginesWithDefault = enginesWithDefault.length > 0;
test.each(engines.filter(x => !x.skipDefaultValue).map(engine => [engine.label, engine]))(
'Add default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123';
});
})
);
if (hasEnginesWithDefault) {
test.each(enginesWithDefault.map(engine => [engine.label, engine]))(
'Add default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123';
});
})
);
test.each(engines.filter(x => !x.skipDefaultValue).map(engine => [engine.label, engine]))(
'Unset default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined;
});
})
);
test.each(enginesWithDefault.map(engine => [engine.label, engine]))(
'Unset default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined;
});
})
);
test.each(enginesWithDefault.map(engine => [engine.label, engine]))(
'Change default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567';
});
})
);
}
test.each(engines.filter(x => !x.skipDefaultValue).map(engine => [engine.label, engine]))(
'Change default value - %s',
testWrapper(async (conn, driver, engine) => {
await testTableDiff(engine, conn, driver, tbl => {
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567';
});
})
);
// test.each(engines.map(engine => [engine.label, engine]))(
// 'Change autoincrement - %s',

View File

@@ -1,160 +0,0 @@
const engines = require('../engines');
const stream = require('stream');
const { testWrapper } = require('../tools');
const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator');
const { runCommandOnDriver, runQueryOnDriver } = require('dbgate-tools');
describe('Data duplicator', () => {
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
'Insert simple data - %s',
testWrapper(async (conn, driver, engine) => {
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't2',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
{ columnName: 'valfk', dataType: 'int', notNull: true },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
})
);
const gett1 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1' },
{ id: 2, val: 'v2' },
{ id: 3, val: 'v3' },
]);
const gett2 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1', valfk: 1 },
{ id: 2, val: 'v2', valfk: 2 },
{ id: 3, val: 'v3', valfk: 3 },
]);
await dataDuplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
operation: 'copy',
openStream: gett1,
},
{
name: 't2',
operation: 'copy',
openStream: gett2,
},
],
});
await dataDuplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
operation: 'copy',
openStream: gett1,
},
{
name: 't2',
operation: 'copy',
openStream: gett2,
},
],
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('6');
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
'Skip nullable weak refs - %s',
testWrapper(async (conn, driver, engine) => {
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't2',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
{ columnName: 'valfk', dataType: 'int', notNull: false },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
})
);
runCommandOnDriver(conn, driver, dmp => dmp.put("insert into ~t1 (~id, ~val) values (1, 'first')"));
const gett2 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1', valfk: 1 },
{ id: 2, val: 'v2', valfk: 2 },
]);
await dataDuplicator({
systemConnection: conn,
driver,
items: [
{
name: 't2',
operation: 'copy',
openStream: gett2,
},
],
options: {
setNullForUnresolvedNullableRefs: true,
},
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('1');
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('2');
const res3 = await runQueryOnDriver(conn, driver, dmp =>
dmp.put(`select count(*) as ~cnt from ~t2 where ~valfk is not null`)
);
expect(res3.rows[0].cnt.toString()).toEqual('1');
})
);
});

View File

@@ -0,0 +1,306 @@
const engines = require('../engines');
const stream = require('stream');
const { testWrapper } = require('../tools');
const dataReplicator = require('dbgate-api/src/shell/dataReplicator');
const deployDb = require('dbgate-api/src/shell/deployDb');
const storageModel = require('dbgate-api/src/storageModel');
const { runCommandOnDriver, runQueryOnDriver, adaptDatabaseInfo } = require('dbgate-tools');
describe('Data replicator', () => {
test.each(engines.filter(x => !x.skipDataReplicator).map(engine => [engine.label, engine]))(
'Insert simple data - %s',
testWrapper(async (conn, driver, engine) => {
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't2',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
{ columnName: 'valfk', dataType: 'int', notNull: true },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
})
);
const gett1 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1' },
{ id: 2, val: 'v2' },
{ id: 3, val: 'v3' },
]);
const gett2 = () =>
stream.Readable.from([
{ __isStreamHeader: true, __isDynamicStructure: true },
{ id: 1, val: 'v1', valfk: 1 },
{ id: 2, val: 'v2', valfk: 2 },
{ id: 3, val: 'v3', valfk: 3 },
]);
await dataReplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
createNew: true,
openStream: gett1,
},
{
name: 't2',
createNew: true,
openStream: gett2,
},
],
});
await dataReplicator({
systemConnection: conn,
driver,
items: [
{
name: 't1',
createNew: true,
openStream: gett1,
},
{
name: 't2',
createNew: true,
openStream: gett2,
},
],
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('6');
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
test.each(engines.filter(x => !x.skipDataReplicator).map(engine => [engine.label, engine]))(
'Skip nullable weak refs - %s',
testWrapper(async (conn, driver, engine) => {
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't2',
columns: [
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
{ columnName: 'val', dataType: 'varchar(50)' },
{ columnName: 'valfk', dataType: 'int', notNull: false },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
})
);
runCommandOnDriver(conn, driver, dmp => dmp.put("insert into ~t1 (~id, ~val) values (1, 'first')"));
await dataReplicator({
systemConnection: conn,
driver,
items: [
{
name: 't2',
createNew: true,
jsonArray: [
{ id: 1, val: 'v1', valfk: 1 },
{ id: 2, val: 'v2', valfk: 2 },
],
},
],
options: {
setNullForUnresolvedNullableRefs: true,
},
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('1');
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('2');
const res3 = await runQueryOnDriver(conn, driver, dmp =>
dmp.put(`select count(*) as ~cnt from ~t2 where ~valfk is not null`)
);
expect(res3.rows[0].cnt.toString()).toEqual('1');
})
);
test.each(engines.filter(x => !x.skipDataReplicator).map(engine => [engine.label, engine]))(
'Import storage DB - %s',
testWrapper(async (conn, driver, engine) => {
await deployDb({
systemConnection: conn,
driver,
loadedDbModel: adaptDatabaseInfo(storageModel, driver),
targetSchema: engine.defaultSchemaName,
});
async function queryValue(sql) {
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(sql));
return res1.rows[0].val?.toString();
}
expect(await queryValue(`select count(*) as ~val from ~auth_methods`)).toEqual('2');
expect(
await queryValue(
`select ~is_disabled as ~val from ~auth_methods where ~amoid='790ca4d2-7f01-4800-955b-d691b890cc50'`
)
).toBeTruthy();
const DB1 = {
auth_methods: [
{ id: -1, name: 'Anonymous', amoid: '790ca4d2-7f01-4800-955b-d691b890cc50', is_disabled: 0 },
{ id: 10, name: 'OAuth', amoid: '4269b660-54b6-11ef-a3aa-a9021250bf4b' },
],
auth_methods_config: [{ id: 20, auth_method_id: 10, key: 'oauthClient', value: 'dbgate' }],
config: [
{ group: 'admin', key: 'encyptKey', value: '1234' },
{ group: 'admin', key: 'adminPasswordState', value: 'set' },
{ group: 'license', key: 'licenseKey', value: '123467' },
],
roles: [
{ id: -3, name: 'superadmin' },
{ id: -2, name: 'logged-user' },
{ id: -1, name: 'anonymous-user' },
],
role_permissions: [
{ id: 14, role_id: -1, permission: 'perm1' },
{ id: 29, role_id: -1, permission: 'perm2' },
{ id: 1, role_id: -1, permission: 'perm3' },
],
};
const DB2 = {
auth_methods: [{ id: 10, name: 'My Auth', amoid: 'myauth1' }],
auth_methods_config: [{ id: 20, auth_method_id: 10, key: 'my authClient', value: 'mydbgate' }],
config: [],
roles: [{ id: 1, name: 'test' }],
role_permissions: [{ id: 14, role_id: 1, permission: 'permxx' }],
};
function createDuplConfig(db) {
return {
systemConnection: conn,
driver,
items: [
{
name: 'auth_methods',
findExisting: true,
updateExisting: true,
createNew: true,
matchColumns: ['amoid'],
jsonArray: db.auth_methods,
},
{
name: 'auth_methods_config',
findExisting: true,
updateExisting: true,
createNew: true,
matchColumns: ['auth_method_id', 'key'],
jsonArray: db.auth_methods_config,
},
{
name: 'config',
findExisting: true,
updateExisting: true,
createNew: true,
matchColumns: ['group', 'key'],
jsonArray: db.config,
},
{
name: 'roles',
findExisting: true,
updateExisting: true,
createNew: true,
matchColumns: ['name'],
jsonArray: db.roles,
},
{
name: 'role_permissions',
findExisting: true,
updateExisting: true,
createNew: true,
deleteMissing: true,
matchColumns: ['role_id', 'permission'],
deleteRestrictionColumns: ['role_id'],
jsonArray: db.role_permissions,
},
],
};
}
await dataReplicator(createDuplConfig(DB1));
expect(
await queryValue(
`select ~is_disabled as ~val from ~auth_methods where ~amoid='790ca4d2-7f01-4800-955b-d691b890cc50'`
)
).toEqual('0');
expect(await queryValue(`select count(*) as ~val from ~auth_methods`)).toEqual('3');
expect(await queryValue(`select count(*) as ~val from ~auth_methods_config`)).toEqual('1');
expect(await queryValue(`select count(*) as ~val from ~config`)).toEqual('3');
expect(await queryValue(`select ~value as ~val from ~auth_methods_config`)).toEqual('dbgate');
expect(
await queryValue(`select ~value as ~val from ~config where ~group='license' and ~key='licenseKey'`)
).toEqual('123467');
expect(await queryValue(`select count(*) as ~val from ~role_permissions`)).toEqual('3');
DB1.auth_methods_config[0].value = 'dbgate2';
DB1.config[2].value = '567';
DB1.role_permissions.splice(2, 1);
await dataReplicator(createDuplConfig(DB1));
expect(await queryValue(`select count(*) as ~val from ~auth_methods_config`)).toEqual('1');
expect(await queryValue(`select count(*) as ~val from ~config`)).toEqual('3');
expect(await queryValue(`select ~value as ~val from ~auth_methods_config`)).toEqual('dbgate2');
expect(
await queryValue(`select ~value as ~val from ~config where ~group='license' and ~key='licenseKey'`)
).toEqual('567');
expect(await queryValue(`select count(*) as ~val from ~role_permissions`)).toEqual('2');
// now add DB2
await dataReplicator(createDuplConfig(DB2));
expect(await queryValue(`select count(*) as ~val from ~auth_methods`)).toEqual('4');
expect(await queryValue(`select count(*) as ~val from ~auth_methods_config`)).toEqual('2');
expect(await queryValue(`select count(*) as ~val from ~role_permissions`)).toEqual('3');
DB1.role_permissions.splice(1, 1);
await dataReplicator(createDuplConfig(DB1));
expect(await queryValue(`select count(*) as ~val from ~role_permissions`)).toEqual('2');
}),
15 * 1000
);
});

View File

@@ -51,7 +51,8 @@ describe('DB Import/export', () => {
await copyStream(reader, writer);
const res = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res.rows[0].cnt.toString()).toEqual('6');
const cnt = parseInt(res.rows[0].cnt.toString());
expect(cnt).toEqual(6);
})
);
@@ -75,7 +76,8 @@ describe('DB Import/export', () => {
await copyStream(reader, writer);
const res = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res.rows[0].cnt.toString()).toEqual('6');
const cnt = parseInt(res.rows[0].cnt.toString());
expect(cnt).toEqual(6);
})
);
@@ -103,10 +105,12 @@ describe('DB Import/export', () => {
await copyStream(reader2, writer2);
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res1.rows[0].cnt.toString()).toEqual('6');
const cnt = parseInt(res1.rows[0].cnt.toString());
expect(cnt).toEqual(6);
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6');
const cnt2 = parseInt(res2.rows[0].cnt.toString());
expect(cnt2).toEqual(6);
})
);
const enginesWithDumpFile = engines.filter(x => x.dumpFile);
@@ -192,7 +196,8 @@ describe('DB Import/export', () => {
});
const res1 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~categories`));
expect(res1.rows[0].cnt.toString()).toEqual('4');
const cnt1 = parseInt(res1.rows[0].cnt.toString());
expect(cnt1).toEqual(4);
})
);
});

View File

@@ -106,7 +106,9 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
for (const loadedDbModel of dbModelsYaml) {
if (_.isString(loadedDbModel)) {
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel));
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel), {
useTransaction: engine.runDeployInTransaction,
});
} else {
const { sql, isEmpty } = await generateDeploySql({
systemConnection: conn.isPreparedOnly ? undefined : conn,
@@ -131,6 +133,7 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
driver,
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
dbdiffOptionsExtra,
useTransaction: engine.runDeployInTransaction,
});
}
@@ -606,7 +609,7 @@ describe('Deploy database', () => {
})
);
test.each(engines.filter(i => !i.skipDeploy).map(engine => [engine.label, engine]))(
test.each(engines.filter(i => !i.skipDeploy && !i.skipRenameTable).map(engine => [engine.label, engine]))(
'Mark table removed - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(engine, conn, driver, [[T1], [], []], {
@@ -822,7 +825,7 @@ describe('Deploy database', () => {
})
);
test.each(engines.filter(i => !i.skipDeploy).map(engine => [engine.label, engine]))(
test.each(engines.filter(i => !i.skipDeploy && !i.skipRenameTable).map(engine => [engine.label, engine]))(
'Mark table removed, one remains - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(engine, conn, driver, [[T1, T2], [T2], [T2]], {

View File

@@ -20,7 +20,11 @@ function flatSourceParameters() {
}
function flatSourceTriggers() {
return _.flatten(engines.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine])));
return _.flatten(
engines
.filter(engine => !engine.skipTriggers)
.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine]))
);
}
function flatSourceSchedulerEvents() {

View File

@@ -183,12 +183,12 @@ describe('Query', () => {
{ discardResult: true }
);
const res = await runQueryOnDriver(conn, driver, dmp => dmp.put('SELECT COUNT(*) AS ~cnt FROM ~t1'));
// console.log(res);
expect(res.rows[0].cnt == 3).toBeTruthy();
const cnt = parseInt(res.rows[0].cnt);
expect(cnt).toEqual(3);
})
);
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipDataReplicator).map(engine => [engine.label, engine]))(
'Select scope identity - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp =>

View File

@@ -94,7 +94,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
'Table add - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
@@ -112,7 +112,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
'Table remove - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));
@@ -130,7 +130,7 @@ describe('Table analyse', () => {
})
);
test.each(engines.map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
'Table change - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql(engine)));

View File

@@ -64,6 +64,40 @@ describe('Table create', () => {
})
);
test.each(
engines.filter(i => i.supportTableComments || i.supportColumnComments).map(engine => [engine.label, engine])
)(
'Simple table with comment - %s',
testWrapper(async (conn, driver, engine) => {
await testTableCreate(engine, conn, driver, {
...(engine.supportTableComments && {
schemaName: 'dbo',
objectComment: 'table comment',
}),
...(engine.defaultSchemaName && {
schemaName: engine.defaultSchemaName,
}),
columns: [
{
columnName: 'col1',
dataType: 'int',
pureName: 'tested',
...(engine.skipNullability ? {} : { notNull: true }),
...(engine.supportColumnComments && {
columnComment: 'column comment',
}),
...(engine.defaultSchemaName && {
schemaName: engine.defaultSchemaName,
}),
},
],
primaryKey: {
columns: [{ columnName: 'col1' }],
},
});
})
);
test.each(engines.filter(x => !x.skipIndexes).map(engine => [engine.label, engine]))(
'Table with index - %s',
testWrapper(async (conn, driver, engine) => {

View File

@@ -8,14 +8,25 @@ services:
# ports:
# - 15000:5432
#
# mariadb:
# image: mariadb
# command: --default-authentication-plugin=mysql_native_password
# restart: always
# ports:
# - 15004:3306
# environment:
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
mariadb:
image: mariadb
command: --default-authentication-plugin=mysql_native_password
restart: always
ports:
- 15004:3306
environment:
- MYSQL_ROOT_PASSWORD=Pwd2020Db
db2:
image: icr.io/db2_community/db2:11.5.8.0
privileged: true
ports:
- "15055:50000"
environment:
LICENSE: accept
DB2INST1_PASSWORD: Pwd2020Db
DBNAME: testdb
DB2INSTANCE: db2inst1
# mysql:
# image: mysql:8.0.18
@@ -25,7 +36,7 @@ services:
# - 15001:3306
# environment:
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
#
# cassandradb:
# image: cassandra:5.0.2
@@ -81,11 +92,36 @@ services:
# ports:
# - 15006:1521
libsql:
image: ghcr.io/tursodatabase/libsql-server:latest
platform: linux/amd64
# libsql:
# image: ghcr.io/tursodatabase/libsql-server:latest
# platform: linux/amd64
# ports:
# - '8080:8080'
# - '5002:5001'
# volumes:
# - ./data/libsql:/var/lib/sqld
firebird:
image: firebirdsql/firebird:latest
container_name: firebird-db
environment:
- FIREBIRD_DATABASE=mydatabase.fdb
- FIREBIRD_USER=dbuser
- FIREBIRD_PASSWORD=dbpassword
- ISC_PASSWORD=masterkey
- FIREBIRD_TRACE=false
- FIREBIRD_USE_LEGACY_AUTH=true
ports:
- '8080:8080'
- '5002:5001'
- '3050:3050'
volumes:
- ./data/libsql:/var/lib/sqld
- firebird-data:/firebird/data
- ./firebird.conf:/firebird/firebird.conf # Mount custom config file
healthcheck:
test: ['CMD', 'nc', '-z', 'localhost', '3050']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
firebird-data:

View File

@@ -443,6 +443,8 @@ const sqlServerEngine = {
supportSchemas: true,
supportRenameSqlObject: true,
defaultSchemaName: 'dbo',
supportTableComments: true,
supportColumnComments: true,
// skipSeparateSchemas: true,
triggers: [
{
@@ -551,7 +553,7 @@ const clickhouseEngine = {
skipUnique: true,
skipAutoIncrement: true,
skipPkColumnTesting: true,
skipDataDuplicator: true,
skipDataReplicator: true,
skipStringLength: true,
alterTableAddColumnSyntax: true,
dbSnapshotBySeconds: true,
@@ -643,7 +645,7 @@ const cassandraEngine = {
skipOrderBy: true,
skipAutoIncrement: true,
skipDataModifications: true,
skipDataDuplicator: true,
skipDataReplicator: true,
skipDeploy: true,
skipImportModel: true,
@@ -654,6 +656,82 @@ const cassandraEngine = {
objects: [],
};
/** @type {import('dbgate-types').TestEngineInfo} */
const duckdbEngine = {
label: 'DuckDB',
generateDbFile: true,
defaultSchemaName: 'main',
connection: {
engine: 'duckdb@dbgate-plugin-duckdb',
},
objects: [views],
skipOnCI: false,
skipChangeColumn: true,
// skipIndexes: true,
skipStringLength: true,
skipTriggers: true,
skipDataReplicator: true,
skipAutoIncrement: true,
skipDropColumn: true,
skipRenameColumn: true,
skipChangeNullability: true,
skipDeploy: true,
supportRenameSqlObject: true,
skipIncrementalAnalysis: true,
skipDefaultValue: true,
skipDropReferences: true,
};
/** @type {import('dbgate-types').TestEngineInfo} */
const firebirdEngine = {
label: 'Firebird',
generateDbFile: true,
databaseFileLocationOnServer: '/var/lib/firebird/data/',
defaultSchemaName: 'main',
connection: {
engine: 'firebird@dbgate-plugin-firebird',
server: 'localhost',
port: 3050,
// databaseUrl: '/var/lib/firebird/data/mydatabase.fdb',
// databaseFile: '/var/lib/firebird/data/mydatabase.fdb',
user: 'SYSDBA',
password: 'masterkey',
},
objects: [],
triggers: [
{
testName: 'triggers after each row',
create: `CREATE OR ALTER TRIGGER ~obj1 AFTER INSERT ON ~t1 AS BEGIN END;`,
drop: 'DROP TRIGGER ~obj1;',
objectTypeField: 'triggers',
expected: {
pureName: 'obj1',
tableName: 't1',
eventType: 'INSERT',
triggerTiming: 'AFTER',
},
},
],
skipOnCI: false,
runDeployInTransaction: true,
skipDataModifications: true,
skipChangeColumn: true,
// skipIndexes: true,
// skipStringLength: true,
// skipTriggers: true,
skipDataReplicator: true,
skipAutoIncrement: true,
// skipDropColumn: true,
skipRenameColumn: true,
// skipChangeNullability: true,
// skipDeploy: true,
// supportRenameSqlObject: true,
skipIncrementalAnalysis: true,
skipRenameTable: true,
// skipDefaultValue: true,
skipDropReferences: true,
};
const enginesOnCi = [
// all engines, which would be run on GitHub actions
mysqlEngine,
@@ -667,6 +745,8 @@ const enginesOnCi = [
clickhouseEngine,
oracleEngine,
cassandraEngine,
duckdbEngine,
firebirdEngine,
];
const enginesOnLocal = [
@@ -680,8 +760,10 @@ const enginesOnLocal = [
// cockroachDbEngine,
// clickhouseEngine,
// libsqlFileEngine,
libsqlWsEngine,
// libsqlWsEngine,
// oracleEngine,
// duckdbEngine,
firebirdEngine,
];
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */
@@ -696,3 +778,7 @@ module.exports.cockroachDbEngine = cockroachDbEngine;
module.exports.clickhouseEngine = clickhouseEngine;
module.exports.oracleEngine = oracleEngine;
module.exports.cassandraEngine = cassandraEngine;
module.exports.libsqlFileEngine = libsqlFileEngine;
module.exports.libsqlWsEngine = libsqlWsEngine;
module.exports.duckdbEngine = duckdbEngine;
module.exports.firebirdEngine = firebirdEngine;

View File

@@ -0,0 +1,45 @@
# Custom Firebird Configuration
# Wire encryption settings
# Options: Enabled, Required, Disabled
WireCrypt = Disabled
# Authentication settings
# Add Legacy_Auth to support older clients
AuthServer = Legacy_Auth
# User manager plugin
UserManager = Legacy_UserManager
# Default character set
DefaultCharSet = UTF8
# Buffer settings for better performance
DefaultDbCachePages = 2048
TempCacheLimit = 512M
# Connection settings
ConnectionTimeout = 180
DatabaseGrowthIncrement = 128M
# TCP Protocol settings
TcpRemoteBufferSize = 8192
TcpNoNagle = 1
# Security settings
RemoteServiceName = gds_db
RemoteServicePort = 3050
RemoteAuxPort = 0
RemotePipeName = firebird
# Lock settings
LockMemSize = 1M
LockHashSlots = 8191
LockAcquireSpins = 0
# Log settings
FileSystemCacheThreshold = 65536
FileSystemCacheSize = 0
# Compatibility settings for older clients
CompatiblityDialect = 3

View File

@@ -12,7 +12,7 @@
"wait:local": "cross-env DEVMODE=1 LOCALTEST=1 node wait.js",
"wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js",
"test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest --testTimeout=5000",
"test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js",
"test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/alter-database.spec.js",
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit --testTimeout=10000",
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
},

View File

@@ -5,7 +5,12 @@ const crypto = require('crypto');
function randomDbName(dialect) {
const generatedKey = crypto.randomBytes(6);
const newKey = generatedKey.toString('hex');
const res = `db${newKey}`;
let res = `db${newKey}`;
if (dialect.dbFileExtension) {
res += dialect.dbFileExtension;
}
if (dialect.upperCaseAllDbObjectNames) return res.toUpperCase();
return res;
}
@@ -17,7 +22,7 @@ async function connect(engine, database) {
if (engine.generateDbFile) {
const conn = await driver.connect({
...connection,
databaseFile: `dbtemp/${database}`,
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
});
return conn;
} else {
@@ -42,7 +47,7 @@ async function prepareConnection(engine, database) {
if (engine.generateDbFile) {
return {
...connection,
databaseFile: `dbtemp/${database}`,
databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database,
isPreparedOnly: true,
};
} else {

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.2.2-packer-beta.4",
"version": "6.6.1-beta.17",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -9,6 +9,7 @@
],
"scripts": {
"start:api": "yarn workspace dbgate-api start | pino-pretty",
"start:api:watch": "nodemon --watch 'src/**' --ext 'ts,json,js' --exec yarn start:api",
"start:api:json": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start | pino-pretty",
"start:app:singledb": "CONNECTIONS=con1 SERVER_con1=localhost ENGINE_con1=mysql@dbgate-plugin-mysql USER_con1=root PASSWORD_con1=Pwd2020Db SINGLE_CONNECTION=con1 SINGLE_DATABASE=Chinook yarn start:app",
@@ -42,7 +43,7 @@
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
"build:plugins:backend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:backend",
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
"storage-json": "dbmodel model-to-json storage-db packages/api/src/storageModel.js --commonjs",
"storage-json": "node packages/dbmodel/bin/dbmodel.js model-to-json storage-db packages/api/src/storageModel.js --commonjs",
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
"build:app:local": "yarn plugins:copydist && cd app && yarn build:local",
"start:app:local": "cd app && yarn start:local",

View File

@@ -1,5 +1,14 @@
DEVMODE=1
SHELL_SCRIPTING=1
ALLOW_DBGATE_PRIVATE_CLOUD=1
DEVWEB=1
# LOCAL_AI_GATEWAY=true
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1
# PROD_DBGATE_CLOUD=1
# PROD_DBGATE_IDENTITY=1
# LOCAL_DBGATE_CLOUD=1
# LOCAL_DBGATE_IDENTITY=1
# CLOUD_UPGRADE_FILE=c:\test\upg\upgrade.zip
@@ -7,7 +16,6 @@ SHELL_SCRIPTING=1
# DISABLE_SHELL=1
# HIDE_APP_EDITOR=1
# DEVWEB=1
# LOGINS=admin,test

View File

@@ -22,6 +22,7 @@
"dependencies": {
"@aws-sdk/rds-signer": "^3.665.0",
"activedirectory2": "^2.1.0",
"archiver": "^7.0.1",
"async-lock": "^1.2.6",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
@@ -30,7 +31,7 @@
"cors": "^2.8.5",
"cross-env": "^6.0.3",
"dbgate-datalib": "^6.0.0-alpha.1",
"dbgate-query-splitter": "^4.11.3",
"dbgate-query-splitter": "^4.11.5",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"debug": "^4.3.4",
@@ -62,10 +63,12 @@
"simple-encryptor": "^4.0.0",
"ssh2": "^1.16.0",
"stream-json": "^1.8.0",
"tar": "^6.0.5"
"tar": "^6.0.5",
"yauzl": "^3.2.0"
},
"scripts": {
"start": "env-cmd -f .env node src/index.js --listen-api",
"start:debug": "env-cmd -f .env node --inspect src/index.js --listen-api",
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",

View File

@@ -11,7 +11,7 @@ const logger = getLogger('authProvider');
class AuthProviderBase {
amoid = 'none';
async login(login, password, options = undefined) {
async login(login, password, options = undefined, req = undefined) {
return {
accessToken: jwt.sign(
{
@@ -23,7 +23,7 @@ class AuthProviderBase {
};
}
oauthToken(params) {
oauthToken(params, req) {
return {};
}

View File

@@ -2,14 +2,20 @@ const fs = require('fs-extra');
const readline = require('readline');
const crypto = require('crypto');
const path = require('path');
const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('../utility/directories');
const { archivedir, clearArchiveLinksCache, resolveArchiveFolder, uploadsdir } = require('../utility/directories');
const socket = require('../utility/socket');
const loadFilesRecursive = require('../utility/loadFilesRecursive');
const getJslFileName = require('../utility/getJslFileName');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const { getLogger, extractErrorLogData, jsonLinesParse } = require('dbgate-tools');
const dbgateApi = require('../shell');
const jsldata = require('./jsldata');
const platformInfo = require('../utility/platformInfo');
const { isProApp } = require('../utility/checkLicense');
const listZipEntries = require('../utility/listZipEntries');
const unzipJsonLinesFile = require('../shell/unzipJsonLinesFile');
const { zip } = require('lodash');
const zipDirectory = require('../shell/zipDirectory');
const unzipDirectory = require('../shell/unzipDirectory');
const logger = getLogger('archive');
@@ -47,9 +53,31 @@ module.exports = {
return folder;
},
async getZipFiles({ file }) {
const entries = await listZipEntries(path.join(archivedir(), file));
const files = entries.map(entry => {
let name = entry.fileName;
if (isProApp() && entry.fileName.endsWith('.jsonl')) {
name = entry.fileName.slice(0, -6);
}
return {
name: name,
label: name,
type: isProApp() && entry.fileName.endsWith('.jsonl') ? 'jsonl' : 'other',
};
});
return files;
},
files_meta: true,
async files({ folder }) {
try {
if (folder.endsWith('.zip')) {
if (await fs.exists(path.join(archivedir(), folder))) {
return this.getZipFiles({ file: folder });
}
return [];
}
const dir = resolveArchiveFolder(folder);
if (!(await fs.exists(dir))) return [];
const files = await loadFilesRecursive(dir); // fs.readdir(dir);
@@ -91,6 +119,16 @@ module.exports = {
return true;
},
createFile_meta: true,
async createFile({ folder, file, fileType, tableInfo }) {
await fs.writeFile(
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
tableInfo ? JSON.stringify({ __isStreamHeader: true, tableInfo }) : ''
);
socket.emitChanged(`archive-files-changed`, { folder });
return true;
},
deleteFile_meta: true,
async deleteFile({ folder, file, fileType }) {
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
@@ -158,7 +196,7 @@ module.exports = {
deleteFolder_meta: true,
async deleteFolder({ folder }) {
if (!folder) throw new Error('Missing folder parameter');
if (folder.endsWith('.link')) {
if (folder.endsWith('.link') || folder.endsWith('.zip')) {
await fs.unlink(path.join(archivedir(), folder));
} else {
await fs.rmdir(path.join(archivedir(), folder), { recursive: true });
@@ -204,9 +242,10 @@ module.exports = {
},
async getNewArchiveFolder({ database }) {
const isLink = database.endsWith(database);
const name = isLink ? database.slice(0, -5) : database;
const suffix = isLink ? '.link' : '';
const isLink = database.endsWith('.link');
const isZip = database.endsWith('.zip');
const name = isLink ? database.slice(0, -5) : isZip ? database.slice(0, -4) : database;
const suffix = isLink ? '.link' : isZip ? '.zip' : '';
if (!(await fs.exists(path.join(archivedir(), database)))) return database;
let index = 2;
while (await fs.exists(path.join(archivedir(), `${name}${index}${suffix}`))) {
@@ -214,4 +253,58 @@ module.exports = {
}
return `${name}${index}${suffix}`;
},
getArchiveData_meta: true,
async getArchiveData({ folder, file }) {
let rows;
if (folder.endsWith('.zip')) {
rows = await unzipJsonLinesFile(path.join(archivedir(), folder), `${file}.jsonl`);
} else {
rows = jsonLinesParse(await fs.readFile(path.join(archivedir(), folder, `${file}.jsonl`), { encoding: 'utf8' }));
}
return rows.filter(x => !x.__isStreamHeader);
},
saveUploadedZip_meta: true,
async saveUploadedZip({ filePath, fileName }) {
if (!fileName?.endsWith('.zip')) {
throw new Error(`${fileName} is not a ZIP file`);
}
const folder = await this.getNewArchiveFolder({ database: fileName });
await fs.copyFile(filePath, path.join(archivedir(), folder));
socket.emitChanged(`archive-folders-changed`);
return null;
},
zip_meta: true,
async zip({ folder }) {
const newFolder = await this.getNewArchiveFolder({ database: folder + '.zip' });
await zipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
socket.emitChanged(`archive-folders-changed`);
return null;
},
unzip_meta: true,
async unzip({ folder }) {
const newFolder = await this.getNewArchiveFolder({ database: folder.slice(0, -4) });
await unzipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
socket.emitChanged(`archive-folders-changed`);
return null;
},
getZippedPath_meta: true,
async getZippedPath({ folder }) {
if (folder.endsWith('.zip')) {
return { filePath: path.join(archivedir(), folder) };
}
const uploadName = crypto.randomUUID();
const filePath = path.join(uploadsdir(), uploadName);
await zipDirectory(path.join(archivedir(), folder), filePath);
return { filePath };
},
};

View File

@@ -12,6 +12,22 @@ const {
getAuthProviderById,
} = require('../auth/authProvider');
const storage = require('./storage');
const { decryptPasswordString } = require('../utility/crypting');
const {
createDbGateIdentitySession,
startCloudTokenChecking,
readCloudTokenHolder,
readCloudTestTokenHolder,
} = require('../utility/cloudIntf');
const socket = require('../utility/socket');
const { sendToAuditLog } = require('../utility/auditlog');
const {
isLoginLicensed,
LOGIN_LIMIT_ERROR,
markTokenAsLoggedIn,
markUserAsActive,
markLoginAsLoggedOut,
} = require('../utility/loginchecker');
const logger = getLogger('auth');
@@ -43,12 +59,19 @@ function authMiddleware(req, res, next) {
'/connections/dblogin-app',
'/connections/dblogin-auth',
'/connections/dblogin-auth-token',
'/health',
'/__health',
];
// console.log('********************* getAuthProvider()', getAuthProvider());
// const isAdminPage = req.headers['x-is-admin-page'] == 'true';
if (process.env.SKIP_ALL_AUTH) {
// API is not authorized for basic auth
return next();
}
if (process.env.BASIC_AUTH) {
// API is not authorized for basic auth
return next();
@@ -67,6 +90,8 @@ function authMiddleware(req, res, next) {
try {
const decoded = jwt.verify(token, getTokenSecret());
req.user = decoded;
markUserAsActive(decoded.licenseUid, token);
return next();
} catch (err) {
if (skipAuth) {
@@ -82,40 +107,67 @@ function authMiddleware(req, res, next) {
module.exports = {
oauthToken_meta: true,
async oauthToken(params) {
async oauthToken(params, req) {
const { amoid } = params;
return getAuthProviderById(amoid).oauthToken(params);
return getAuthProviderById(amoid).oauthToken(params, req);
},
login_meta: true,
async login(params) {
async login(params, req) {
const { amoid, login, password, isAdminPage } = params;
if (isAdminPage) {
let adminPassword = process.env.ADMIN_PASSWORD;
if (!adminPassword) {
const adminConfig = await storage.readConfig({ group: 'admin' });
adminPassword = adminConfig?.adminPassword;
adminPassword = decryptPasswordString(adminConfig?.adminPassword);
}
if (adminPassword && adminPassword == password) {
if (!(await isLoginLicensed(req, `superadmin`))) {
return { error: LOGIN_LIMIT_ERROR };
}
sendToAuditLog(req, {
category: 'auth',
component: 'AuthController',
action: 'login',
event: 'login.admin',
severity: 'info',
message: 'Administration login successful',
});
const licenseUid = `superadmin`;
const accessToken = jwt.sign(
{
login: 'superadmin',
permissions: await storage.loadSuperadminPermissions(),
roleId: -3,
licenseUid,
},
getTokenSecret(),
{
expiresIn: getTokenLifetime(),
}
);
markTokenAsLoggedIn(licenseUid, accessToken);
return {
accessToken: jwt.sign(
{
login: 'superadmin',
permissions: await storage.loadSuperadminPermissions(),
roleId: -3,
},
getTokenSecret(),
{
expiresIn: getTokenLifetime(),
}
),
accessToken,
};
}
sendToAuditLog(req, {
category: 'auth',
component: 'AuthController',
action: 'loginFail',
event: 'login.adminFailed',
severity: 'warn',
message: 'Administraton login failed',
});
return { error: 'Login failed' };
}
return getAuthProviderById(amoid).login(login, password);
return getAuthProviderById(amoid).login(login, password, undefined, req);
},
getProviders_meta: true,
@@ -132,5 +184,40 @@ module.exports = {
return getAuthProviderById(amoid).redirect(params);
},
createCloudLoginSession_meta: true,
async createCloudLoginSession({ client, redirectUri }) {
const res = await createDbGateIdentitySession(client, redirectUri);
startCloudTokenChecking(res.sid, tokenHolder => {
socket.emit('got-cloud-token', tokenHolder);
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
});
return res;
},
cloudLoginRedirected_meta: true,
async cloudLoginRedirected({ sid }) {
const tokenHolder = await readCloudTokenHolder(sid);
return tokenHolder;
},
cloudTestLogin_meta: true,
async cloudTestLogin({ email }) {
const tokenHolder = await readCloudTestTokenHolder(email);
return tokenHolder;
},
logoutAdmin_meta: true,
async logoutAdmin() {
await markLoginAsLoggedOut('superadmin');
return true;
},
logoutUser_meta: true,
async logoutUser({}, req) {
await markLoginAsLoggedOut(req?.user?.licenseUid);
return true;
},
authMiddleware,
};

View File

@@ -0,0 +1,293 @@
const {
getPublicCloudFiles,
getPublicFileData,
refreshPublicFiles,
callCloudApiGet,
callCloudApiPost,
getCloudFolderEncryptor,
getCloudContent,
putCloudContent,
removeCloudCachedConnection,
} = require('../utility/cloudIntf');
const connections = require('./connections');
const socket = require('../utility/socket');
const { recryptConnection, getInternalEncryptor, encryptConnection } = require('../utility/crypting');
const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-tools');
const logger = getLogger('cloud');
const _ = require('lodash');
const fs = require('fs-extra');
const { getAiGatewayServer } = require('../utility/authProxy');
module.exports = {
publicFiles_meta: true,
async publicFiles() {
const res = await getPublicCloudFiles();
return res;
},
publicFileData_meta: true,
async publicFileData({ path }) {
const res = getPublicFileData(path);
return res;
},
refreshPublicFiles_meta: true,
async refreshPublicFiles({ isRefresh }) {
await refreshPublicFiles(isRefresh);
return {
status: 'ok',
};
},
contentList_meta: true,
async contentList() {
try {
const resp = await callCloudApiGet('content-list');
return resp;
} catch (err) {
logger.error(extractErrorLogData(err), 'Error getting cloud content list');
return [];
}
},
getContent_meta: true,
async getContent({ folid, cntid }) {
const resp = await getCloudContent(folid, cntid);
return resp;
},
putContent_meta: true,
async putContent({ folid, cntid, content, name, type }) {
const resp = await putCloudContent(folid, cntid, content, name, type, {});
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
createFolder_meta: true,
async createFolder({ name }) {
const resp = await callCloudApiPost(`folders/create`, { name });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
grantFolder_meta: true,
async grantFolder({ inviteLink }) {
const m = inviteLink.match(/^dbgate\:\/\/folder\/v1\/([a-zA-Z0-9]+)\?mode=(read|write|admin)$/);
if (!m) {
throw new Error('Invalid invite link format');
}
const invite = m[1];
const mode = m[2];
const resp = await callCloudApiPost(`folders/grant/${mode}`, { invite });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
renameFolder_meta: true,
async renameFolder({ folid, name }) {
const resp = await callCloudApiPost(`folders/rename`, { folid, name });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
deleteFolder_meta: true,
async deleteFolder({ folid }) {
const resp = await callCloudApiPost(`folders/delete`, { folid });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
getInviteToken_meta: true,
async getInviteToken({ folid, role }) {
const resp = await callCloudApiGet(`invite-token/${folid}/${role}`);
return resp;
},
refreshContent_meta: true,
async refreshContent() {
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return {
status: 'ok',
};
},
copyConnectionCloud_meta: true,
async copyConnectionCloud({ conid, folid }) {
const conn = await connections.getCore({ conid });
const folderEncryptor = await getCloudFolderEncryptor(folid);
const recryptedConn = recryptConnection(conn, getInternalEncryptor(), folderEncryptor);
const connToSend = _.omit(recryptedConn, ['_id']);
const resp = await putCloudContent(
folid,
undefined,
JSON.stringify(connToSend),
getConnectionLabel(conn),
'connection',
{
connectionColor: conn.connectionColor,
connectionEngine: conn.engine,
}
);
return resp;
},
saveConnection_meta: true,
async saveConnection({ folid, connection }) {
let cntid = undefined;
if (connection._id) {
const m = connection._id.match(/^cloud\:\/\/(.+)\/(.+)$/);
if (!m) {
throw new Error('Invalid cloud connection ID format');
}
folid = m[1];
cntid = m[2];
}
if (!folid) {
throw new Error('Missing cloud folder ID');
}
const folderEncryptor = await getCloudFolderEncryptor(folid);
const recryptedConn = encryptConnection(connection, folderEncryptor);
const resp = await putCloudContent(
folid,
cntid,
JSON.stringify(recryptedConn),
getConnectionLabel(recryptedConn),
'connection',
{
connectionColor: connection.connectionColor,
connectionEngine: connection.engine,
}
);
if (resp.apiErrorMessage) {
return resp;
}
removeCloudCachedConnection(folid, resp.cntid);
cntid = resp.cntid;
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return {
...recryptedConn,
_id: `cloud://${folid}/${cntid}`,
};
},
duplicateConnection_meta: true,
async duplicateConnection({ conid }) {
const m = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
if (!m) {
throw new Error('Invalid cloud connection ID format');
}
const folid = m[1];
const cntid = m[2];
const respGet = await getCloudContent(folid, cntid);
const conn = JSON.parse(respGet.content);
const conn2 = {
...conn,
displayName: getConnectionLabel(conn) + ' - copy',
};
const respPut = await putCloudContent(folid, undefined, JSON.stringify(conn2), conn2.displayName, 'connection', {
connectionColor: conn.connectionColor,
connectionEngine: conn.engine,
});
return respPut;
},
deleteConnection_meta: true,
async deleteConnection({ conid }) {
const m = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
if (!m) {
throw new Error('Invalid cloud connection ID format');
}
const folid = m[1];
const cntid = m[2];
const resp = await callCloudApiPost(`content/delete/${folid}/${cntid}`);
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
deleteContent_meta: true,
async deleteContent({ folid, cntid }) {
const resp = await callCloudApiPost(`content/delete/${folid}/${cntid}`);
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
renameContent_meta: true,
async renameContent({ folid, cntid, name }) {
const resp = await callCloudApiPost(`content/rename/${folid}/${cntid}`, { name });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
saveFile_meta: true,
async saveFile({ folid, cntid, fileName, data, contentFolder, format }) {
const resp = await putCloudContent(folid, cntid, data, fileName, 'file', { contentFolder, contentType: format });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
copyFile_meta: true,
async copyFile({ folid, cntid, name }) {
const resp = await callCloudApiPost(`content/duplicate/${folid}/${cntid}`, { name });
socket.emitChanged('cloud-content-changed');
socket.emit('cloud-content-updated');
return resp;
},
exportFile_meta: true,
async exportFile({ folid, cntid, filePath }, req) {
const { content } = await getCloudContent(folid, cntid);
if (!content) {
throw new Error('File not found');
}
await fs.writeFile(filePath, content);
return true;
},
folderUsers_meta: true,
async folderUsers({ folid }) {
const resp = await callCloudApiGet(`content-folders/users/${folid}`);
return resp;
},
setFolderUserRole_meta: true,
async setFolderUserRole({ folid, email, role }) {
const resp = await callCloudApiPost(`content-folders/set-user-role/${folid}`, { email, role });
return resp;
},
removeFolderUser_meta: true,
async removeFolderUser({ folid, email }) {
const resp = await callCloudApiPost(`content-folders/remove-user/${folid}`, { email });
return resp;
},
getAiGateway_meta: true,
async getAiGateway() {
return getAiGatewayServer();
},
// chatStream_meta: {
// raw: true,
// method: 'post',
// },
// chatStream(req, res) {
// callChatStream(req.body, res);
// },
};

View File

@@ -16,11 +16,20 @@ const connections = require('../controllers/connections');
const { getAuthProviderFromReq } = require('../auth/authProvider');
const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
const storage = require('./storage');
const { getAuthProxyUrl } = require('../utility/authProxy');
const { getAuthProxyUrl, tryToGetRefreshedLicense } = require('../utility/authProxy');
const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
const { extractErrorMessage } = require('dbgate-tools');
const {
generateTransportEncryptionKey,
createTransportEncryptor,
recryptConnection,
getInternalEncryptor,
recryptUser,
recryptObjectPasswordFieldInPlace,
} = require('../utility/crypting');
const lock = new AsyncLock();
let cachedSettingsValue = null;
module.exports = {
// settingsValue: {},
@@ -100,6 +109,7 @@ module.exports = {
),
isAdminPasswordMissing,
isInvalidToken: req?.isInvalidToken,
skipAllAuth: !!process.env.SKIP_ALL_AUTH,
adminPasswordState: adminConfig?.adminPasswordState,
storageDatabase: process.env.STORAGE_DATABASE,
logsFilePath: getLogsFilePath(),
@@ -107,7 +117,10 @@ module.exports = {
datadir(),
processArgs.runE2eTests ? 'connections-e2etests.jsonl' : 'connections.jsonl'
),
supportCloudAutoUpgrade: !!process.env.CLOUD_UPGRADE_FILE,
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
...currentVersion,
redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
};
return configResult;
@@ -134,6 +147,13 @@ module.exports = {
return res;
},
async getCachedSettings() {
if (!cachedSettingsValue) {
cachedSettingsValue = await this.loadSettings();
}
return cachedSettingsValue;
},
deleteSettings_meta: true,
async deleteSettings() {
await fs.unlink(path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'));
@@ -144,7 +164,7 @@ module.exports = {
const res = {
...value,
};
if (value['app.useNativeMenu'] !== true && value['app.useNativeMenu'] !== false) {
if (platformInfo.isElectron && value['app.useNativeMenu'] !== true && value['app.useNativeMenu'] !== false) {
// res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
res['app.useNativeMenu'] = false;
}
@@ -161,14 +181,20 @@ module.exports = {
async loadSettings() {
try {
const settingsText = await fs.readFile(
path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'),
{ encoding: 'utf-8' }
);
return {
...this.fillMissingSettings(JSON.parse(settingsText)),
'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
};
if (process.env.STORAGE_DATABASE) {
const settings = await storage.readConfig({ group: 'settings' });
return this.fillMissingSettings(settings);
} else {
const settingsText = await fs.readFile(
path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'),
{ encoding: 'utf-8' }
);
return {
...this.fillMissingSettings(JSON.parse(settingsText)),
'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
// 'other.licenseKey': await this.loadLicenseKey(),
};
}
} catch (err) {
return this.fillMissingSettings({});
}
@@ -184,21 +210,34 @@ module.exports = {
},
saveLicenseKey_meta: true,
async saveLicenseKey({ licenseKey }) {
const decoded = jwt.decode(licenseKey);
if (!decoded) {
return {
status: 'error',
errorMessage: 'Invalid license key',
};
}
async saveLicenseKey({ licenseKey, forceSave = false, tryToRenew = false }) {
if (!forceSave) {
const decoded = jwt.decode(licenseKey?.trim());
if (!decoded) {
return {
status: 'error',
errorMessage: 'Invalid license key',
};
}
const { exp } = decoded;
if (exp * 1000 < Date.now()) {
return {
status: 'error',
errorMessage: 'License key is expired',
};
const { exp } = decoded;
if (exp * 1000 < Date.now()) {
let renewed = false;
if (tryToRenew) {
const newLicenseKey = await tryToGetRefreshedLicense(licenseKey);
if (newLicenseKey.status == 'ok') {
licenseKey = newLicenseKey.token;
renewed = true;
}
}
if (!renewed) {
return {
status: 'error',
errorMessage: 'License key is expired',
};
}
}
}
try {
@@ -242,23 +281,40 @@ module.exports = {
updateSettings_meta: true,
async updateSettings(values, req) {
if (!hasPermission(`settings/change`, req)) return false;
cachedSettingsValue = null;
const res = await lock.acquire('settings', async () => {
const currentValue = await this.loadSettings();
try {
const updated = {
...currentValue,
..._.omit(values, ['other.licenseKey']),
};
await fs.writeFile(
path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'),
JSON.stringify(updated, undefined, 2)
);
// this.settingsValue = updated;
let updated = currentValue;
if (process.env.STORAGE_DATABASE) {
updated = {
...currentValue,
..._.mapValues(values, v => {
if (v === true) return 'true';
if (v === false) return 'false';
return v;
}),
};
await storage.writeConfig({
group: 'settings',
config: updated,
});
} else {
updated = {
...currentValue,
..._.omit(values, ['other.licenseKey']),
};
await fs.writeFile(
path.join(datadir(), processArgs.runE2eTests ? 'settings-e2etests.json' : 'settings.json'),
JSON.stringify(updated, undefined, 2)
);
// this.settingsValue = updated;
if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
socket.emitChanged(`config-changed`);
if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'], forceSave: true });
socket.emitChanged(`config-changed`);
}
}
socket.emitChanged(`settings-changed`);
@@ -272,8 +328,12 @@ module.exports = {
changelog_meta: true,
async changelog() {
const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
return resp.data;
try {
const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
return resp.data;
} catch (err) {
return '';
}
},
checkLicense_meta: true,
@@ -281,4 +341,101 @@ module.exports = {
const resp = await checkLicenseKey(licenseKey);
return resp;
},
getNewLicense_meta: true,
async getNewLicense({ oldLicenseKey }) {
const newLicenseKey = await tryToGetRefreshedLicense(oldLicenseKey);
const res = await checkLicenseKey(newLicenseKey.token);
if (res.status == 'ok') {
res.licenseKey = newLicenseKey.token;
}
return res;
},
recryptDatabaseForExport(db) {
const encryptionKey = generateTransportEncryptionKey();
const transportEncryptor = createTransportEncryptor(encryptionKey);
const config = _.cloneDeep([
...(db.config?.filter(c => !(c.group == 'admin' && c.key == 'encryptionKey')) || []),
{ group: 'admin', key: 'encryptionKey', value: encryptionKey },
]);
const adminPassword = config.find(c => c.group == 'admin' && c.key == 'adminPassword');
recryptObjectPasswordFieldInPlace(adminPassword, 'value', getInternalEncryptor(), transportEncryptor);
return {
...db,
connections: db.connections?.map(conn => recryptConnection(conn, getInternalEncryptor(), transportEncryptor)),
users: db.users?.map(conn => recryptUser(conn, getInternalEncryptor(), transportEncryptor)),
config,
};
},
recryptDatabaseFromImport(db) {
const encryptionKey = db.config?.find(c => c.group == 'admin' && c.key == 'encryptionKey')?.value;
if (!encryptionKey) {
throw new Error('Missing encryption key in the database');
}
const config = _.cloneDeep(db.config || []).filter(c => !(c.group == 'admin' && c.key == 'encryptionKey'));
const transportEncryptor = createTransportEncryptor(encryptionKey);
const adminPassword = config.find(c => c.group == 'admin' && c.key == 'adminPassword');
recryptObjectPasswordFieldInPlace(adminPassword, 'value', transportEncryptor, getInternalEncryptor());
return {
...db,
connections: db.connections?.map(conn => recryptConnection(conn, transportEncryptor, getInternalEncryptor())),
users: db.users?.map(conn => recryptUser(conn, transportEncryptor, getInternalEncryptor())),
config,
};
},
exportConnectionsAndSettings_meta: true,
async exportConnectionsAndSettings(_params, req) {
if (!hasPermission(`admin/config`, req)) {
throw new Error('Permission denied: admin/config');
}
if (connections.portalConnections) {
throw new Error('Not allowed');
}
if (process.env.STORAGE_DATABASE) {
const db = await storage.getExportedDatabase();
return this.recryptDatabaseForExport(db);
}
return this.recryptDatabaseForExport({
connections: (await connections.list(null, req)).map((conn, index) => ({
..._.omit(conn, ['_id']),
id: index + 1,
conid: conn._id,
})),
});
},
importConnectionsAndSettings_meta: true,
async importConnectionsAndSettings({ db }, req) {
if (!hasPermission(`admin/config`, req)) {
throw new Error('Permission denied: admin/config');
}
if (connections.portalConnections) {
throw new Error('Not allowed');
}
const recryptedDb = this.recryptDatabaseFromImport(db);
if (process.env.STORAGE_DATABASE) {
await storage.replicateImportedDatabase(recryptedDb);
} else {
await connections.importFromArray(
recryptedDb.connections.map(conn => ({
..._.omit(conn, ['conid', 'id']),
_id: conn.conid,
}))
);
}
return true;
},
};

View File

@@ -38,6 +38,11 @@ function getNamedArgs() {
res.databaseFile = name;
res.engine = 'sqlite@dbgate-plugin-sqlite';
}
if (name.endsWith('.duckdb')) {
res.databaseFile = name;
res.engine = 'duckdb@dbgate-plugin-duckdb';
}
}
}
return res;
@@ -102,12 +107,21 @@ function getPortalCollections() {
trustServerCertificate: process.env[`SSL_TRUST_CERTIFICATE_${id}`],
}));
for (const conn of connections) {
for (const prop in process.env) {
if (prop.startsWith(`CONNECTION_${conn._id}_`)) {
const name = prop.substring(`CONNECTION_${conn._id}_`.length);
conn[name] = process.env[prop];
}
}
}
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'Using connections from ENV variables');
const noengine = connections.filter(x => !x.engine);
if (noengine.length > 0) {
logger.warn(
{ connections: noengine.map(x => x._id) },
'Invalid CONNECTIONS configutation, missing ENGINE for connection ID'
'Invalid CONNECTIONS configuration, missing ENGINE for connection ID'
);
}
return connections;
@@ -225,6 +239,19 @@ module.exports = {
return (await this.datastore.find()).filter(x => connectionHasPermission(x, req));
},
async getUsedEngines() {
const storage = require('./storage');
const storageEngines = await storage.getUsedEngines();
if (storageEngines) {
return storageEngines;
}
if (portalConnections) {
return _.uniq(_.compact(portalConnections.map(x => x.engine)));
}
return _.uniq((await this.datastore.find()).map(x => x.engine));
},
test_meta: true,
test({ connection, requestDbList = false }) {
const subprocess = fork(
@@ -307,6 +334,18 @@ module.exports = {
return res;
},
importFromArray(list) {
this.datastore.transformAll(connections => {
const mapped = connections.map(x => {
const found = list.find(y => y._id == x._id);
if (found) return found;
return x;
});
return [...mapped, ...list.filter(x => !connections.find(y => y._id == x._id))];
});
socket.emitChanged('connection-list-changed');
},
async checkUnsavedConnectionsLimit() {
if (!this.datastore) {
return;
@@ -384,6 +423,13 @@ module.exports = {
return volatile;
}
const cloudMatch = conid.match(/^cloud\:\/\/(.+)\/(.+)$/);
if (cloudMatch) {
const { loadCachedCloudConnection } = require('../utility/cloudIntf');
const conn = await loadCachedCloudConnection(cloudMatch[1], cloudMatch[2]);
return conn;
}
const storage = require('./storage');
const storageConnection = await storage.getConnection({ conid });
@@ -426,6 +472,22 @@ module.exports = {
return res;
},
newDuckdbDatabase_meta: true,
async newDuckdbDatabase({ file }) {
const duckdbDir = path.join(filesdir(), 'duckdb');
if (!(await fs.exists(duckdbDir))) {
await fs.mkdir(duckdbDir);
}
const databaseFile = path.join(duckdbDir, `${file}.duckdb`);
const res = await this.save({
engine: 'duckdb@dbgate-plugin-duckdb',
databaseFile,
singleDatabase: true,
defaultDatabase: `${file}.duckdb`,
});
return res;
},
dbloginWeb_meta: {
raw: true,
method: 'get',
@@ -474,14 +536,14 @@ module.exports = {
},
dbloginAuthToken_meta: true,
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }, req) {
try {
const connection = await this.getCore({ conid });
const driver = requireEngineDriver(connection);
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
const volatile = await this.saveVolatile({ conid, accessToken });
const authProvider = getAuthProviderById(amoid);
const resp = await authProvider.login(null, null, { conid: volatile._id });
const resp = await authProvider.login(null, null, { conid: volatile._id }, req);
return resp;
} catch (err) {
logger.error(extractErrorLogData(err), 'Error getting DB token');
@@ -490,18 +552,18 @@ module.exports = {
},
dbloginAuth_meta: true,
async dbloginAuth({ amoid, conid, user, password }) {
async dbloginAuth({ amoid, conid, user, password }, req) {
if (user || password) {
const saveResp = await this.saveVolatile({ conid, user, password, test: true });
if (saveResp.msgtype == 'connected') {
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id }, req);
return loginResp;
}
return saveResp;
}
// user and password is stored in connection, volatile connection is not needed
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid }, req);
return loginResp;
},

View File

@@ -1,4 +1,5 @@
const connections = require('./connections');
const runners = require('./runners');
const archive = require('./archive');
const socket = require('../utility/socket');
const { fork } = require('child_process');
@@ -36,6 +37,11 @@ const loadModelTransform = require('../utility/loadModelTransform');
const exportDbModelSql = require('../utility/exportDbModelSql');
const axios = require('axios');
const { callTextToSqlApi, callCompleteOnCursorApi, callRefactorSqlQueryApi } = require('../utility/authProxy');
const { decryptConnection } = require('../utility/crypting');
const { getSshTunnel } = require('../utility/sshTunnel');
const sessions = require('./sessions');
const jsldata = require('./jsldata');
const { sendToAuditLog } = require('../utility/auditlog');
const logger = getLogger('databaseConnections');
@@ -78,8 +84,11 @@ module.exports = {
}
},
handle_response(conid, database, { msgid, ...response }) {
const [resolve, reject] = this.requests[msgid];
const [resolve, reject, additionalData] = this.requests[msgid];
resolve(response);
if (additionalData?.auditLogger) {
additionalData?.auditLogger(response);
}
delete this.requests[msgid];
},
handle_status(conid, database, { status }) {
@@ -93,10 +102,59 @@ module.exports = {
handle_ping() {},
// session event handlers
handle_info(conid, database, props) {
const { sesid, info } = props;
sessions.dispatchMessage(sesid, info);
},
handle_done(conid, database, props) {
const { sesid } = props;
socket.emit(`session-done-${sesid}`);
sessions.dispatchMessage(sesid, 'Query execution finished');
},
handle_recordset(conid, database, props) {
const { jslid, resultIndex } = props;
socket.emit(`session-recordset-${props.sesid}`, { jslid, resultIndex });
},
handle_stats(conid, database, stats) {
jsldata.notifyChangedStats(stats);
},
handle_initializeFile(conid, database, props) {
const { jslid } = props;
socket.emit(`session-initialize-file-${jslid}`);
},
// eval event handler
handle_runnerDone(conid, database, props) {
const { runid } = props;
socket.emit(`runner-done-${runid}`);
},
handle_progress(conid, database, progressData) {
const { progressName } = progressData;
const { name, runid } = progressName;
socket.emit(`runner-progress-${runid}`, { ...progressData, progressName: name });
},
handle_copyStreamError(conid, database, { copyStreamError }) {
const { progressName } = copyStreamError;
const { runid } = progressName;
logger.error(`Error in database connection ${conid}, database ${database}: ${copyStreamError}`);
socket.emit(`runner-done-${runid}`);
},
async ensureOpened(conid, database) {
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) return existing;
const connection = await connections.getCore({ conid });
if (!connection) {
throw new Error(`databaseConnections: Connection with conid="${conid}" not found`);
}
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
}
@@ -133,12 +191,23 @@ module.exports = {
const { msgtype } = message;
if (handleProcessCommunication(message, subprocess)) return;
if (newOpened.disconnected) return;
this[`handle_${msgtype}`](conid, database, message);
const funcName = `handle_${msgtype}`;
if (!this[funcName]) {
logger.error(`Unknown message type ${msgtype} from subprocess databaseConnectionProcess`);
return;
}
this[funcName](conid, database, message);
});
subprocess.on('exit', () => {
if (newOpened.disconnected) return;
this.close(conid, database, false);
});
subprocess.on('error', err => {
logger.error(extractErrorLogData(err), 'Error in database connection subprocess');
if (newOpened.disconnected) return;
this.close(conid, database, false);
});
subprocess.send({
msgtype: 'connect',
@@ -150,10 +219,10 @@ module.exports = {
},
/** @param {import('dbgate-types').OpenedDatabaseConnection} conn */
sendRequest(conn, message) {
sendRequest(conn, message, additionalData = {}) {
const msgid = crypto.randomUUID();
const promise = new Promise((resolve, reject) => {
this.requests[msgid] = [resolve, reject];
this.requests[msgid] = [resolve, reject, additionalData];
try {
conn.subprocess.send({ msgid, ...message });
} catch (err) {
@@ -177,18 +246,57 @@ module.exports = {
},
sqlSelect_meta: true,
async sqlSelect({ conid, database, select }, req) {
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
const res = await this.sendRequest(
opened,
{ msgtype: 'sqlSelect', select },
{
auditLogger:
auditLogSessionGroup && select?.from?.name?.pureName
? response => {
sendToAuditLog(req, {
category: 'dbop',
component: 'DatabaseConnectionsController',
event: 'sql.select',
action: 'select',
severity: 'info',
conid,
database,
schemaName: select?.from?.name?.schemaName,
pureName: select?.from?.name?.pureName,
sumint1: response?.rows?.length,
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${
select?.from?.name?.pureName
}`,
sessionGroup: auditLogSessionGroup,
message: `Loaded table data from ${select?.from?.name?.pureName}`,
});
}
: null,
}
);
return res;
},
runScript_meta: true,
async runScript({ conid, database, sql, useTransaction }, req) {
async runScript({ conid, database, sql, useTransaction, logMessage }, req) {
testConnectionPermission(conid, req);
logger.info({ conid, database, sql }, 'Processing script');
const opened = await this.ensureOpened(conid, database);
sendToAuditLog(req, {
category: 'dbop',
component: 'DatabaseConnectionsController',
event: 'sql.runscript',
action: 'runscript',
severity: 'info',
conid,
database,
detail: sql,
message: logMessage || `Running SQL script`,
});
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql, useTransaction });
return res;
},
@@ -197,16 +305,53 @@ module.exports = {
async runOperation({ conid, database, operation, useTransaction }, req) {
testConnectionPermission(conid, req);
logger.info({ conid, database, operation }, 'Processing operation');
sendToAuditLog(req, {
category: 'dbop',
component: 'DatabaseConnectionsController',
event: 'sql.runoperation',
action: operation.type,
severity: 'info',
conid,
database,
detail: operation,
message: `Running DB operation: ${operation.type}`,
});
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'runOperation', operation, useTransaction });
return res;
},
collectionData_meta: true,
async collectionData({ conid, database, options }, req) {
async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
const res = await this.sendRequest(
opened,
{ msgtype: 'collectionData', options },
{
auditLogger:
auditLogSessionGroup && options?.pureName
? response => {
sendToAuditLog(req, {
category: 'dbop',
component: 'DatabaseConnectionsController',
event: 'nosql.collectionData',
action: 'select',
severity: 'info',
conid,
database,
pureName: options?.pureName,
sumint1: response?.result?.rows?.length,
sessionParam: `${conid}::${database}::${options?.pureName}`,
sessionGroup: auditLogSessionGroup,
message: `Loaded collection data ${options?.pureName}`,
});
}
: null,
}
);
return res.result || null;
},
@@ -242,6 +387,12 @@ module.exports = {
return this.loadDataCore('loadKeys', { conid, database, root, filter, limit });
},
scanKeys_meta: true,
async scanKeys({ conid, database, root, pattern, cursor, count }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('scanKeys', { conid, database, root, pattern, cursor, count });
},
exportKeys_meta: true,
async exportKeys({ conid, database, options }, req) {
testConnectionPermission(conid, req);
@@ -421,6 +572,20 @@ module.exports = {
}
const opened = await this.ensureOpened(conid, database);
sendToAuditLog(req, {
category: 'dbop',
component: 'DatabaseConnectionsController',
action: 'structure',
event: 'dbStructure.get',
severity: 'info',
conid,
database,
sessionParam: `${conid}::${database}`,
sessionGroup: 'getStructure',
message: `Loaded database structure for ${database}`,
});
return opened.structure;
// const existing = this.opened.find((x) => x.conid == conid && x.database == database);
// if (existing) return existing.status;
@@ -613,4 +778,167 @@ module.exports = {
return res;
},
async getNativeOpCommandArgs(
command,
{ conid, database, outputFile, inputFile, options, selectedTables, skippedTables, argsFormat }
) {
const sourceConnection = await connections.getCore({ conid });
const connection = {
...decryptConnection(sourceConnection),
};
const driver = requireEngineDriver(connection);
if (!connection.port && driver.defaultPort) {
connection.port = driver.defaultPort.toString();
}
if (connection.useSshTunnel) {
const tunnel = await getSshTunnel(connection);
if (tunnel.state == 'error') {
throw new Error(tunnel.message);
}
connection.server = tunnel.localHost;
connection.port = tunnel.localPort;
}
const settingsValue = await config.getSettings();
const externalTools = {};
for (const pair of Object.entries(settingsValue || {})) {
const [name, value] = pair;
if (name.startsWith('externalTools.')) {
externalTools[name.substring('externalTools.'.length)] = value;
}
}
return {
...(command == 'backup'
? driver.backupDatabaseCommand(
connection,
{ outputFile, database, options, selectedTables, skippedTables, argsFormat },
// @ts-ignore
externalTools
)
: driver.restoreDatabaseCommand(
connection,
{ inputFile, database, options, argsFormat },
// @ts-ignore
externalTools
)),
transformMessage: driver.transformNativeCommandMessage
? message => driver.transformNativeCommandMessage(message, command)
: null,
};
},
commandArgsToCommandLine(commandArgs) {
const { command, args, stdinFilePath } = commandArgs;
let res = `${command} ${args.join(' ')}`;
if (stdinFilePath) {
res += ` < ${stdinFilePath}`;
}
return res;
},
nativeBackup_meta: true,
async nativeBackup({ conid, database, outputFile, runid, options, selectedTables, skippedTables }) {
const commandArgs = await this.getNativeOpCommandArgs('backup', {
conid,
database,
inputFile: undefined,
outputFile,
options,
selectedTables,
skippedTables,
argsFormat: 'spawn',
});
return runners.nativeRunCore(runid, {
...commandArgs,
onFinished: () => {
socket.emitChanged(`files-changed`, { folder: 'sql' });
socket.emitChanged(`all-files-changed`);
},
});
},
nativeBackupCommand_meta: true,
async nativeBackupCommand({ conid, database, outputFile, options, selectedTables, skippedTables }) {
const commandArgs = await this.getNativeOpCommandArgs('backup', {
conid,
database,
outputFile,
inputFile: undefined,
options,
selectedTables,
skippedTables,
argsFormat: 'shell',
});
return {
...commandArgs,
transformMessage: null,
commandLine: this.commandArgsToCommandLine(commandArgs),
};
},
nativeRestore_meta: true,
async nativeRestore({ conid, database, inputFile, runid }) {
const commandArgs = await this.getNativeOpCommandArgs('restore', {
conid,
database,
inputFile,
outputFile: undefined,
options: undefined,
argsFormat: 'spawn',
});
return runners.nativeRunCore(runid, {
...commandArgs,
onFinished: () => {
this.syncModel({ conid, database, isFullRefresh: true });
},
});
},
nativeRestoreCommand_meta: true,
async nativeRestoreCommand({ conid, database, inputFile }) {
const commandArgs = await this.getNativeOpCommandArgs('restore', {
conid,
database,
inputFile,
outputFile: undefined,
options: undefined,
argsFormat: 'shell',
});
return {
...commandArgs,
transformMessage: null,
commandLine: this.commandArgsToCommandLine(commandArgs),
};
},
executeSessionQuery_meta: true,
async executeSessionQuery({ sesid, conid, database, sql }, req) {
testConnectionPermission(conid, req);
logger.info({ sesid, sql }, 'Processing query');
sessions.dispatchMessage(sesid, 'Query execution started');
const opened = await this.ensureOpened(conid, database);
opened.subprocess.send({ msgtype: 'executeSessionQuery', sql, sesid });
return { state: 'ok' };
},
evalJsonScript_meta: true,
async evalJsonScript({ conid, database, script, runid }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
opened.subprocess.send({ msgtype: 'evalJsonScript', script, runid });
return { state: 'ok' };
},
};

View File

@@ -9,6 +9,11 @@ const scheduler = require('./scheduler');
const getDiagramExport = require('../utility/getDiagramExport');
const apps = require('./apps');
const getMapExport = require('../utility/getMapExport');
const dbgateApi = require('../shell');
const { getLogger } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo');
const { checkSecureFilePathsWithoutDirectory, checkSecureDirectories } = require('../utility/security');
const logger = getLogger('files');
function serialize(format, data) {
if (format == 'text') return data;
@@ -48,6 +53,9 @@ module.exports = {
delete_meta: true,
async delete({ folder, file }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
return false;
}
await fs.unlink(path.join(filesdir(), folder, file));
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
@@ -57,6 +65,9 @@ module.exports = {
rename_meta: true,
async rename({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false;
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
return false;
}
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`);
@@ -74,6 +85,9 @@ module.exports = {
copy_meta: true,
async copy({ folder, file, newFile }, req) {
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
return false;
}
if (!hasPermission(`files/${folder}/write`, req)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed`, { folder });
@@ -83,6 +97,10 @@ module.exports = {
load_meta: true,
async load({ folder, file, format }, req) {
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
return false;
}
if (folder.startsWith('archive:')) {
const text = await fs.readFile(path.join(resolveArchiveFolder(folder.substring('archive:'.length)), file), {
encoding: 'utf-8',
@@ -102,12 +120,20 @@ module.exports = {
loadFrom_meta: true,
async loadFrom({ filePath, format }, req) {
if (!platformInfo.isElectron) {
// this is available only in electron app
return false;
}
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
return deserialize(format, text);
},
save_meta: true,
async save({ folder, file, data, format }, req) {
if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
return false;
}
if (folder.startsWith('archive:')) {
if (!hasPermission(`archive/write`, req)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
@@ -140,6 +166,11 @@ module.exports = {
saveAs_meta: true,
async saveAs({ filePath, data, format }) {
if (!platformInfo.isElectron) {
// this is available only in electron app
return false;
}
await fs.writeFile(filePath, serialize(format, data));
},
@@ -172,10 +203,10 @@ module.exports = {
},
exportChart_meta: true,
async exportChart({ filePath, title, config, image }) {
async exportChart({ filePath, title, config, image, plugins }) {
const fileName = path.parse(filePath).base;
const imageFile = fileName.replace('.html', '-preview.png');
const html = getChartExport(title, config, imageFile);
const html = getChartExport(title, config, imageFile, plugins);
await fs.writeFile(filePath, html);
if (image) {
const index = image.indexOf('base64,');
@@ -195,8 +226,8 @@ module.exports = {
},
exportDiagram_meta: true,
async exportDiagram({ filePath, html, css, themeType, themeClassName }) {
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName));
async exportDiagram({ filePath, html, css, themeType, themeClassName, watermark }) {
await fs.writeFile(filePath, getDiagramExport(html, css, themeType, themeClassName, watermark));
return true;
},
@@ -219,4 +250,65 @@ module.exports = {
return path.join(dir, file);
}
},
createZipFromJsons_meta: true,
async createZipFromJsons({ db, filePath }) {
logger.info(`Creating zip file from JSONS ${filePath}`);
await dbgateApi.zipJsonLinesData(db, filePath);
return true;
},
getJsonsFromZip_meta: true,
async getJsonsFromZip({ filePath }) {
const res = await dbgateApi.unzipJsonLinesData(filePath);
return res;
},
downloadText_meta: true,
async downloadText({ uri }, req) {
if (!uri) return null;
const filePath = await dbgateApi.download(uri);
const text = await fs.readFile(filePath, {
encoding: 'utf-8',
});
return text;
},
saveUploadedFile_meta: true,
async saveUploadedFile({ filePath, fileName }) {
const FOLDERS = ['sql', 'sqlite'];
for (const folder of FOLDERS) {
if (fileName.toLowerCase().endsWith('.' + folder)) {
logger.info(`Saving ${folder} file ${fileName}`);
await fs.copyFile(filePath, path.join(filesdir(), folder, fileName));
socket.emitChanged(`files-changed`, { folder: folder });
socket.emitChanged(`all-files-changed`);
return {
name: path.basename(filePath),
folder: folder,
};
}
}
throw new Error(`${fileName} doesn't have one of supported extensions: ${FOLDERS.join(', ')}`);
},
exportFile_meta: true,
async exportFile({ folder, file, filePath }, req) {
if (!hasPermission(`files/${folder}/read`, req)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), filePath);
return true;
},
simpleCopy_meta: true,
async simpleCopy({ sourceFilePath, targetFilePath }, req) {
if (!platformInfo.isElectron) {
if (!checkSecureDirectories(sourceFilePath, targetFilePath)) {
return false;
}
}
await fs.copyFile(sourceFilePath, targetFilePath);
return true;
},
};

View File

@@ -8,6 +8,9 @@ const getJslFileName = require('../utility/getJslFileName');
const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
const requirePluginFunction = require('../utility/requirePluginFunction');
const socket = require('../utility/socket');
const crypto = require('crypto');
const dbgateApi = require('../shell');
const { ChartProcessor } = require('dbgate-datalib');
function readFirstLine(file) {
return new Promise((resolve, reject) => {
@@ -293,4 +296,26 @@ module.exports = {
})),
};
},
downloadJslData_meta: true,
async downloadJslData({ uri }) {
const jslid = crypto.randomUUID();
await dbgateApi.download(uri, { targetFile: getJslFileName(jslid) });
return { jslid };
},
buildChart_meta: true,
async buildChart({ jslid, definition }) {
const datastore = new JsonLinesDatastore(getJslFileName(jslid));
const processor = new ChartProcessor(definition ? [definition] : undefined);
await datastore.enumRows(row => {
processor.addRow(row);
return true;
});
processor.finalize();
return {
charts: processor.charts,
columns: processor.availableColumns,
};
},
};

View File

@@ -4,19 +4,23 @@ const path = require('path');
const fs = require('fs-extra');
const byline = require('byline');
const socket = require('../utility/socket');
const { fork } = require('child_process');
const { fork, spawn } = require('child_process');
const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
const {
extractShellApiPlugins,
extractShellApiFunctionName,
compileShellApiFunctionName,
jsonScriptToJavascript,
getLogger,
safeJsonParse,
pinoLogRecordToMessageRecord,
extractErrorMessage,
extractErrorLogData,
} = require('dbgate-tools');
const { handleProcessCommunication } = require('../utility/processComm');
const processArgs = require('../utility/processArgs');
const platformInfo = require('../utility/platformInfo');
const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security');
const { sendToAuditLog, logJsonRunnerScript } = require('../utility/auditlog');
const logger = getLogger('runners');
function extractPlugins(script) {
@@ -56,7 +60,7 @@ dbgateApi.initializeApiEnvironment();
${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
require=null;
async function run() {
const reader=await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});
const reader=await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});
const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
await dbgateApi.copyStream(reader, writer);
}
@@ -80,6 +84,7 @@ module.exports = {
}
: {
message,
severity: 'info',
time: new Date(),
};
@@ -93,9 +98,9 @@ module.exports = {
handle_ping() {},
handle_freeData(runid, { freeData }) {
handle_dataResult(runid, { dataResult }) {
const { resolve } = this.requests[runid];
resolve(freeData);
resolve(dataResult);
delete this.requests[runid];
},
@@ -168,15 +173,17 @@ module.exports = {
this.rejectRequest(runid, { message: 'No data returned, maybe input data source is too big' });
logger.info({ code, pid: subprocess.pid }, 'Exited process');
socket.emit(`runner-done-${runid}`, code);
this.opened = this.opened.filter(x => x.runid != runid);
});
subprocess.on('error', error => {
// console.log('... ERROR subprocess', error);
this.rejectRequest(runid, { message: error && (error.message || error.toString()) });
console.error('... ERROR subprocess', error);
this.dispatchMessage({
this.dispatchMessage(runid, {
severity: 'error',
message: error.toString(),
});
this.opened = this.opened.filter(x => x.runid != runid);
});
const newOpened = {
runid,
@@ -192,19 +199,118 @@ module.exports = {
return _.pick(newOpened, ['runid']);
},
nativeRunCore(runid, commandArgs) {
const { command, args, env, transformMessage, stdinFilePath, onFinished } = commandArgs;
const pipeDispatcher = severity => data => {
let messageObject = {
message: data.toString().trim(),
severity,
};
if (transformMessage) {
messageObject = transformMessage(messageObject);
}
if (messageObject) {
return this.dispatchMessage(runid, messageObject);
}
};
const subprocess = spawn(command, args, { env: { ...process.env, ...env } });
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
subprocess.on('exit', code => {
console.log('... EXITED', code);
logger.info({ code, pid: subprocess.pid }, 'Exited process');
this.dispatchMessage(runid, `Finished external process with code ${code}`);
socket.emit(`runner-done-${runid}`, code);
if (onFinished) {
onFinished();
}
this.opened = this.opened.filter(x => x.runid != runid);
});
subprocess.on('spawn', () => {
this.dispatchMessage(runid, `Started external process ${command}`);
});
subprocess.on('error', error => {
console.log('... ERROR subprocess', error);
this.dispatchMessage(runid, {
severity: 'error',
message: error.toString(),
});
if (error['code'] == 'ENOENT') {
this.dispatchMessage(runid, {
severity: 'error',
message: `Command ${command} not found, please install it and configure its location in DbGate settings, Settings/External tools, if ${command} is not in system PATH`,
});
}
socket.emit(`runner-done-${runid}`);
this.opened = this.opened.filter(x => x.runid != runid);
});
if (stdinFilePath) {
const inputStream = fs.createReadStream(stdinFilePath);
inputStream.pipe(subprocess.stdin);
subprocess.stdin.on('error', err => {
this.dispatchMessage(runid, {
severity: 'error',
message: extractErrorMessage(err),
});
logger.error(extractErrorLogData(err), 'Caught error on stdin');
});
}
const newOpened = {
runid,
subprocess,
};
this.opened.push(newOpened);
return _.pick(newOpened, ['runid']);
},
start_meta: true,
async start({ script }) {
async start({ script }, req) {
const runid = crypto.randomUUID();
if (script.type == 'json') {
const js = jsonScriptToJavascript(script);
if (!platformInfo.isElectron) {
if (!checkSecureDirectoriesInScript(script)) {
return { errorMessage: 'Unallowed directories in script' };
}
}
logJsonRunnerScript(req, script);
const js = await jsonScriptToJavascript(script);
return this.startCore(runid, scriptTemplate(js, false));
}
if (!platformInfo.allowShellScripting) {
sendToAuditLog(req, {
category: 'shell',
component: 'RunnersController',
event: 'script.runFailed',
action: 'script',
severity: 'warn',
detail: script,
message: 'Scripts are not allowed',
});
return { errorMessage: 'Shell scripting is not allowed' };
}
sendToAuditLog(req, {
category: 'shell',
component: 'RunnersController',
event: 'script.run.shell',
action: 'script',
severity: 'info',
detail: script,
message: 'Running JS script',
});
return this.startCore(runid, scriptTemplate(script, false));
},
@@ -241,6 +347,11 @@ module.exports = {
loadReader_meta: true,
async loadReader({ functionName, props }) {
if (!platformInfo.isElectron) {
if (props?.fileName && !checkSecureDirectories(props.fileName)) {
return { errorMessage: 'Unallowed file' };
}
}
const prefix = extractShellApiPlugins(functionName)
.map(packageName => `// @require ${packageName}\n`)
.join('');
@@ -252,4 +363,24 @@ module.exports = {
});
return promise;
},
scriptResult_meta: true,
async scriptResult({ script }) {
if (script.type != 'json') {
return { errorMessage: 'Only JSON scripts are allowed' };
}
const promise = new Promise(async (resolve, reject) => {
const runid = crypto.randomUUID();
this.requests[runid] = { resolve, reject, exitOnStreamError: true };
const cloned = _.cloneDeepWith(script, node => {
if (node?.$replace == 'runid') {
return runid;
}
});
const js = await jsonScriptToJavascript(cloned);
this.startCore(runid, scriptTemplate(js, false));
});
return promise;
},
};

View File

@@ -12,6 +12,7 @@ const { testConnectionPermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const { sendToAuditLog } = require('../utility/auditlog');
const logger = getLogger('serverConnection');
@@ -52,7 +53,10 @@ module.exports = {
if (existing) return existing;
const connection = await connections.getCore({ conid });
if (!connection) {
throw new Error(`Connection with conid="${conid}" not found`);
throw new Error(`serverConnections: Connection with conid="${conid}" not found`);
}
if (connection.singleDatabase) {
return null;
}
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
@@ -98,6 +102,11 @@ module.exports = {
if (newOpened.disconnected) return;
this.close(conid, false);
});
subprocess.on('error', err => {
logger.error(extractErrorLogData(err), 'Error in server connection subprocess');
if (newOpened.disconnected) return;
this.close(conid, false);
});
subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() });
return newOpened;
});
@@ -137,14 +146,25 @@ module.exports = {
if (conid == '__model') return [];
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.databases;
sendToAuditLog(req, {
category: 'serverop',
component: 'ServerConnectionsController',
action: 'listDatabases',
event: 'databases.list',
severity: 'info',
conid,
sessionParam: `${conid}`,
sessionGroup: 'listDatabases',
message: `Loaded databases for connection`,
});
return opened?.databases ?? [];
},
version_meta: true,
async version({ conid }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.version;
return opened?.version ?? null;
},
serverStatus_meta: true,
@@ -165,6 +185,9 @@ module.exports = {
}
this.lastPinged[conid] = new Date().getTime();
const opened = await this.ensureOpened(conid);
if (!opened) {
return Promise.resolve();
}
try {
opened.subprocess.send({ msgtype: 'ping' });
} catch (err) {
@@ -189,6 +212,9 @@ module.exports = {
async sendDatabaseOp({ conid, msgtype, name }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (!opened) {
return null;
}
if (opened.connection.isReadOnly) return false;
const res = await this.sendRequest(opened, { msgtype, name });
if (res.errorMessage) {
@@ -228,6 +254,9 @@ module.exports = {
async loadDataCore(msgtype, { conid, ...args }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (!opened) {
return null;
}
const res = await this.sendRequest(opened, { msgtype, ...args });
if (res.errorMessage) {
console.error(res.errorMessage);
@@ -249,6 +278,9 @@ module.exports = {
async summaryCommand({ conid, command, row }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (!opened) {
return null;
}
if (opened.connection.isReadOnly) return false;
return this.loadDataCore('summaryCommand', { conid, command, row });
},

View File

@@ -11,6 +11,7 @@ const { appdir } = require('../utility/directories');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const pipeForkLogs = require('../utility/pipeForkLogs');
const config = require('./config');
const { sendToAuditLog } = require('../utility/auditlog');
const logger = getLogger('sessions');
@@ -83,6 +84,11 @@ module.exports = {
jsldata.notifyChangedStats(stats);
},
handle_charts(sesid, props) {
const { jslid, charts, resultIndex } = props;
socket.emit(`session-charts-${sesid}`, { jslid, resultIndex, charts });
},
handle_initializeFile(sesid, props) {
const { jslid } = props;
socket.emit(`session-initialize-file-${jslid}`);
@@ -127,6 +133,9 @@ module.exports = {
this.dispatchMessage(sesid, 'Query session closed');
socket.emit(`session-closed-${sesid}`);
});
subprocess.on('error', () => {
this.opened = this.opened.filter(x => x.sesid != sesid);
});
subprocess.send({
msgtype: 'connect',
@@ -138,15 +147,34 @@ module.exports = {
},
executeQuery_meta: true,
async executeQuery({ sesid, sql, autoCommit }) {
async executeQuery({ sesid, sql, autoCommit, autoDetectCharts, limitRows, frontMatter }, req) {
const session = this.opened.find(x => x.sesid == sesid);
if (!session) {
throw new Error('Invalid session');
}
sendToAuditLog(req, {
category: 'dbop',
component: 'SessionController',
action: 'executeQuery',
event: 'query.execute',
severity: 'info',
detail: sql,
conid: session.conid,
database: session.database,
message: 'Executing query',
});
logger.info({ sesid, sql }, 'Processing query');
this.dispatchMessage(sesid, 'Query execution started');
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit });
session.subprocess.send({
msgtype: 'executeQuery',
sql,
autoCommit,
autoDetectCharts: autoDetectCharts || !!frontMatter?.['selected-chart'],
limitRows,
frontMatter,
});
return { state: 'ok' };
},

View File

@@ -4,6 +4,10 @@ module.exports = {
return null;
},
async getExportedDatabase() {
return {};
},
getConnection_meta: true,
async getConnection({ conid }) {
return null;
@@ -27,5 +31,14 @@ module.exports = {
return {};
},
sendAuditLog_meta: true,
async sendAuditLog({}) {
return null;
},
startRefreshLicense() {},
async getUsedEngines() {
return null;
},
};

View File

@@ -1,6 +1,6 @@
const crypto = require('crypto');
const path = require('path');
const { uploadsdir, getLogsFilePath } = require('../utility/directories');
const { uploadsdir, getLogsFilePath, filesdir } = require('../utility/directories');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const logger = getLogger('uploads');
const axios = require('axios');
@@ -13,6 +13,7 @@ const serverConnections = require('./serverConnections');
const config = require('./config');
const gistSecret = require('../gistSecret');
const currentVersion = require('../currentVersion');
const socket = require('../utility/socket');
module.exports = {
upload_meta: {
@@ -43,6 +44,10 @@ module.exports = {
raw: true,
},
get(req, res) {
if (req.query.file.includes('..') || req.query.file.includes('/') || req.query.file.includes('\\')) {
res.status(400).send('Invalid file path');
return;
}
res.sendFile(path.join(uploadsdir(), req.query.file));
},

View File

@@ -27,10 +27,11 @@ const plugins = require('./controllers/plugins');
const files = require('./controllers/files');
const scheduler = require('./controllers/scheduler');
const queryHistory = require('./controllers/queryHistory');
const cloud = require('./controllers/cloud');
const onFinished = require('on-finished');
const processArgs = require('./utility/processArgs');
const { rundir } = require('./utility/directories');
const { rundir, filesdir } = require('./utility/directories');
const platformInfo = require('./utility/platformInfo');
const getExpressPath = require('./utility/getExpressPath');
const _ = require('lodash');
@@ -38,6 +39,8 @@ const { getLogger } = require('dbgate-tools');
const { getDefaultAuthProvider } = require('./auth/authProvider');
const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
const { isProApp } = require('./utility/checkLicense');
const { getHealthStatus, getHealthStatusSprinx } = require('./utility/healthStatus');
const { startCloudFiles } = require('./utility/cloudIntf');
const logger = getLogger('main');
@@ -117,6 +120,18 @@ function start() {
});
});
app.get(getExpressPath('/health'), async function (req, res) {
res.setHeader('Content-Type', 'application/json');
const health = await getHealthStatus();
res.end(JSON.stringify(health, null, 2));
});
app.get(getExpressPath('/__health'), async function (req, res) {
res.setHeader('Content-Type', 'application/json');
const health = await getHealthStatusSprinx();
res.end(JSON.stringify(health, null, 2));
});
app.use(bodyParser.json({ limit: '50mb' }));
app.use(
@@ -133,6 +148,7 @@ function start() {
// }
app.use(getExpressPath('/runners/data'), express.static(rundir()));
app.use(getExpressPath('/files/data'), express.static(filesdir()));
if (platformInfo.isDocker) {
const port = process.env.PORT || 3000;
@@ -186,6 +202,8 @@ function start() {
if (process.env.CLOUD_UPGRADE_FILE) {
startCloudUpgradeTimer();
}
startCloudFiles();
}
function useAllControllers(app, electron) {
@@ -206,6 +224,7 @@ function useAllControllers(app, electron) {
useController(app, electron, '/query-history', queryHistory);
useController(app, electron, '/apps', apps);
useController(app, electron, '/auth', auth);
useController(app, electron, '/cloud', cloud);
}
function setElectronSender(electronSender) {

View File

@@ -1,9 +0,0 @@
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
const content = {};
content['better-sqlite3'] = () => require('better-sqlite3');
content['oracledb'] = () => require('oracledb');
module.exports = content;

View File

@@ -4,6 +4,8 @@ const { connectUtility } = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
const { pickSafeConnectionInfo } = require('../utility/crypting');
const _ = require('lodash');
const { getLogger, extractErrorLogData } = require('dbgate-tools');
const logger = getLogger('connectProcess');
const formatErrorDetail = (e, connection) => `${e.stack}
@@ -23,12 +25,15 @@ function start() {
try {
const driver = requireEngineDriver(connection);
const dbhan = await connectUtility(driver, connection, 'app');
const res = await driver.getVersion(dbhan);
let version = {
version: 'Unknown',
};
version = await driver.getVersion(dbhan);
let databases = undefined;
if (requestDbList) {
databases = await driver.listDatabases(dbhan);
}
process.send({ msgtype: 'connected', ...res, databases });
process.send({ msgtype: 'connected', ...version, databases });
await driver.close(dbhan);
} catch (e) {
console.error(e);
@@ -38,6 +43,8 @@ function start() {
detail: formatErrorDetail(e, connection),
});
}
process.exit(0);
});
}

View File

@@ -9,13 +9,22 @@ const {
dbNameLogCategory,
extractErrorMessage,
extractErrorLogData,
ScriptWriterEval,
SqlGenerator,
playJsonScriptWriter,
serializeJsTypesForJsonStringify,
} = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
const { SqlGenerator } = require('dbgate-tools');
const generateDeploySql = require('../shell/generateDeploySql');
const { dumpSqlSelect } = require('dbgate-sqltree');
const { allowExecuteCustomScript, handleQueryStream } = require('../utility/handleQueryStream');
const dbgateApi = require('../shell');
const requirePlugin = require('../shell/requirePlugin');
const path = require('path');
const { rundir } = require('../utility/directories');
const fs = require('fs-extra');
const logger = getLogger('dbconnProcess');
@@ -120,10 +129,15 @@ function setStatusName(name) {
async function readVersion() {
const driver = requireEngineDriver(storedConnection);
const version = await driver.getVersion(dbhan);
logger.debug(`Got server version: ${version.version}`);
process.send({ msgtype: 'version', version });
serverVersion = version;
try {
const version = await driver.getVersion(dbhan);
logger.debug(`Got server version: ${version.version}`);
serverVersion = version;
} catch (err) {
logger.error(extractErrorLogData(err), 'Error getting DB server version');
serverVersion = { version: 'Unknown' };
}
process.send({ msgtype: 'version', version: serverVersion });
}
async function handleConnect({ connection, structure, globalSettings }) {
@@ -219,7 +233,7 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
try {
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
const res = await driver.query(dbhan, sql, { range });
process.send({ msgtype: 'response', msgid, ...res });
process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
} catch (err) {
process.send({
msgtype: 'response',
@@ -241,7 +255,7 @@ async function handleDriverDataCore(msgid, callMethod, { logName }) {
const driver = requireEngineDriver(storedConnection);
try {
const result = await callMethod(driver);
process.send({ msgtype: 'response', msgid, result });
process.send({ msgtype: 'response', msgid, result: serializeJsTypesForJsonStringify(result) });
} catch (err) {
logger.error(extractErrorLogData(err, { logName }), `Error when handling message ${logName}`);
process.send({ msgtype: 'response', msgid, errorMessage: extractErrorMessage(err, 'Error executing DB data') });
@@ -261,6 +275,10 @@ async function handleLoadKeys({ msgid, root, filter, limit }) {
return handleDriverDataCore(msgid, driver => driver.loadKeys(dbhan, root, filter, limit), { logName: 'loadKeys' });
}
async function handleScanKeys({ msgid, pattern, cursor, count }) {
return handleDriverDataCore(msgid, driver => driver.scanKeys(dbhan, pattern, cursor, count), { logName: 'scanKeys' });
}
async function handleExportKeys({ msgid, options }) {
return handleDriverDataCore(msgid, driver => driver.exportKeys(dbhan, options), { logName: 'exportKeys' });
}
@@ -370,6 +388,56 @@ async function handleGenerateDeploySql({ msgid, modelFolder }) {
}
}
async function handleExecuteSessionQuery({ sesid, sql }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (!allowExecuteCustomScript(storedConnection, driver)) {
process.send({
msgtype: 'info',
info: {
message: 'Connection without read-only sessions is read only',
severity: 'error',
},
sesid,
});
process.send({ msgtype: 'done', sesid, skipFinishedMessage: true });
return;
//process.send({ msgtype: 'error', error: e.message });
}
const queryStreamInfoHolder = {
resultIndex: 0,
canceled: false,
};
for (const sqlItem of splitQuery(sql, {
...driver.getQuerySplitterOptions('stream'),
returnRichInfo: true,
})) {
await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, sesid);
if (queryStreamInfoHolder.canceled) {
break;
}
}
process.send({ msgtype: 'done', sesid });
}
async function handleEvalJsonScript({ script, runid }) {
const directory = path.join(rundir(), runid);
fs.mkdirSync(directory);
const originalCwd = process.cwd();
try {
process.chdir(directory);
const evalWriter = new ScriptWriterEval(dbgateApi, requirePlugin, dbhan, runid);
await playJsonScriptWriter(script, evalWriter);
process.send({ msgtype: 'runnerDone', runid });
} finally {
process.chdir(originalCwd);
}
}
// async function handleRunCommand({ msgid, sql }) {
// await waitConnected();
// const driver = engines(storedConnection);
@@ -389,6 +457,7 @@ const messageHandlers = {
updateCollection: handleUpdateCollection,
collectionData: handleCollectionData,
loadKeys: handleLoadKeys,
scanKeys: handleScanKeys,
loadKeyInfo: handleLoadKeyInfo,
callMethod: handleCallMethod,
loadKeyTableRange: handleLoadKeyTableRange,
@@ -400,6 +469,8 @@ const messageHandlers = {
sqlSelect: handleSqlSelect,
exportKeys: handleExportKeys,
schemaList: handleSchemaList,
executeSessionQuery: handleExecuteSessionQuery,
evalJsonScript: handleEvalJsonScript,
// runCommand: handleRunCommand,
};

View File

@@ -46,7 +46,13 @@ async function handleRefresh() {
async function readVersion() {
const driver = requireEngineDriver(storedConnection);
const version = await driver.getVersion(dbhan);
let version;
try {
version = await driver.getVersion(dbhan);
} catch (err) {
logger.error(extractErrorLogData(err), 'Error getting DB server version');
version = { version: 'Unknown' };
}
process.send({ msgtype: 'version', version });
}

View File

@@ -11,6 +11,7 @@ const { decryptConnection } = require('../utility/crypting');
const { connectUtility } = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm');
const { getLogger, extractIntSettingsValue, extractBoolSettingsValue } = require('dbgate-tools');
const { handleQueryStream, QueryStreamTableWriter, allowExecuteCustomScript } = require('../utility/handleQueryStream');
const logger = getLogger('sessionProcess');
@@ -23,175 +24,6 @@ let lastActivity = null;
let currentProfiler = null;
let executingScripts = 0;
class TableWriter {
constructor() {
this.currentRowCount = 0;
this.currentChangeIndex = 1;
this.initializedFile = false;
}
initializeFromQuery(structure, resultIndex) {
this.jslid = crypto.randomUUID();
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
fs.writeFileSync(
this.currentFile,
JSON.stringify({
...structure,
__isStreamHeader: true,
}) + '\n'
);
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
this.writeCurrentStats(false, false);
this.resultIndex = resultIndex;
this.initializedFile = true;
process.send({ msgtype: 'recordset', jslid: this.jslid, resultIndex });
}
initializeFromReader(jslid) {
this.jslid = jslid;
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
this.writeCurrentStats(false, false);
}
row(row) {
// console.log('ACCEPT ROW', row);
this.currentStream.write(JSON.stringify(row) + '\n');
this.currentRowCount += 1;
if (!this.plannedStats) {
this.plannedStats = true;
process.nextTick(() => {
if (this.currentStream) this.currentStream.uncork();
process.nextTick(() => this.writeCurrentStats(false, true));
this.plannedStats = false;
});
}
}
rowFromReader(row) {
if (!this.initializedFile) {
process.send({ msgtype: 'initializeFile', jslid: this.jslid });
this.initializedFile = true;
fs.writeFileSync(this.currentFile, JSON.stringify(row) + '\n');
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
this.writeCurrentStats(false, false);
this.initializedFile = true;
return;
}
this.row(row);
}
writeCurrentStats(isFinished = false, emitEvent = false) {
const stats = {
rowCount: this.currentRowCount,
changeIndex: this.currentChangeIndex,
isFinished,
jslid: this.jslid,
};
fs.writeFileSync(`${this.currentFile}.stats`, JSON.stringify(stats));
this.currentChangeIndex += 1;
if (emitEvent) {
process.send({ msgtype: 'stats', ...stats });
}
}
close(afterClose) {
if (this.currentStream) {
this.currentStream.end(() => {
this.writeCurrentStats(true, true);
if (afterClose) afterClose();
});
}
}
}
class StreamHandler {
constructor(resultIndexHolder, resolve, startLine) {
this.recordset = this.recordset.bind(this);
this.startLine = startLine;
this.row = this.row.bind(this);
// this.error = this.error.bind(this);
this.done = this.done.bind(this);
this.info = this.info.bind(this);
// use this for cancelling - not implemented
// this.stream = null;
this.plannedStats = false;
this.resultIndexHolder = resultIndexHolder;
this.resolve = resolve;
// currentHandlers = [...currentHandlers, this];
}
closeCurrentWriter() {
if (this.currentWriter) {
this.currentWriter.close();
this.currentWriter = null;
}
}
recordset(columns) {
this.closeCurrentWriter();
this.currentWriter = new TableWriter();
this.currentWriter.initializeFromQuery(
Array.isArray(columns) ? { columns } : columns,
this.resultIndexHolder.value
);
this.resultIndexHolder.value += 1;
// this.writeCurrentStats();
// this.onRow = _.throttle((jslid) => {
// if (jslid == this.jslid) {
// this.writeCurrentStats(false, true);
// }
// }, 500);
}
row(row) {
if (this.currentWriter) this.currentWriter.row(row);
else if (row.message) process.send({ msgtype: 'info', info: { message: row.message } });
// this.onRow(this.jslid);
}
// error(error) {
// process.send({ msgtype: 'error', error });
// }
done(result) {
this.closeCurrentWriter();
// currentHandlers = currentHandlers.filter((x) => x != this);
this.resolve();
}
info(info) {
if (info && info.line != null) {
info = {
...info,
line: this.startLine + info.line,
};
}
process.send({ msgtype: 'info', info });
}
}
function handleStream(driver, resultIndexHolder, sqlItem) {
return new Promise((resolve, reject) => {
const start = sqlItem.trimStart || sqlItem.start;
const handler = new StreamHandler(resultIndexHolder, resolve, start && start.line);
driver.stream(dbhan, sqlItem.text, handler);
});
}
function allowExecuteCustomScript(driver) {
if (driver.readOnlySessions) {
return true;
}
if (storedConnection.isReadOnly) {
return false;
// throw new Error('Connection is read only');
}
return true;
}
async function handleConnect(connection) {
storedConnection = connection;
@@ -222,12 +54,12 @@ async function handleStartProfiler({ jslid }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (!allowExecuteCustomScript(driver)) {
if (!allowExecuteCustomScript(storedConnection, driver)) {
process.send({ msgtype: 'done' });
return;
}
const writer = new TableWriter();
const writer = new QueryStreamTableWriter();
writer.initializeFromReader(jslid);
currentProfiler = await driver.startProfiler(dbhan, {
@@ -251,7 +83,7 @@ async function handleExecuteControlCommand({ command }) {
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (command == 'commitTransaction' && !allowExecuteCustomScript(driver)) {
if (command == 'commitTransaction' && !allowExecuteCustomScript(storedConnection, driver)) {
process.send({
msgtype: 'info',
info: {
@@ -285,13 +117,13 @@ async function handleExecuteControlCommand({ command }) {
}
}
async function handleExecuteQuery({ sql, autoCommit }) {
async function handleExecuteQuery({ sql, autoCommit, autoDetectCharts, limitRows, frontMatter }) {
lastActivity = new Date().getTime();
await waitConnected();
const driver = requireEngineDriver(storedConnection);
if (!allowExecuteCustomScript(driver)) {
if (!allowExecuteCustomScript(storedConnection, driver)) {
process.send({
msgtype: 'info',
info: {
@@ -306,18 +138,32 @@ async function handleExecuteQuery({ sql, autoCommit }) {
executingScripts++;
try {
const resultIndexHolder = {
value: 0,
const queryStreamInfoHolder = {
resultIndex: 0,
canceled: false,
};
for (const sqlItem of splitQuery(sql, {
...driver.getQuerySplitterOptions('stream'),
returnRichInfo: true,
})) {
await handleStream(driver, resultIndexHolder, sqlItem);
await handleQueryStream(
dbhan,
driver,
queryStreamInfoHolder,
sqlItem,
undefined,
limitRows,
frontMatter,
autoDetectCharts
);
// const handler = new StreamHandler(resultIndex);
// const stream = await driver.stream(systemConnection, sqlItem, handler);
// handler.stream = stream;
// resultIndex = handler.resultIndex;
if (queryStreamInfoHolder.canceled) {
break;
}
}
process.send({ msgtype: 'done', autoCommit });
} finally {
@@ -335,13 +181,13 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
if (fileName) {
sql = fs.readFileSync(fileName, 'utf-8');
} else {
if (!allowExecuteCustomScript(driver)) {
if (!allowExecuteCustomScript(storedConnection, driver)) {
process.send({ msgtype: 'done' });
return;
}
}
const writer = new TableWriter();
const writer = new QueryStreamTableWriter();
writer.initializeFromReader(jslid);
const reader = await driver.readQuery(dbhan, sql);

View File

@@ -3,7 +3,9 @@ const { archivedir, resolveArchiveFolder } = require('../utility/directories');
const jsonLinesReader = require('./jsonLinesReader');
function archiveReader({ folderName, fileName, ...other }) {
const jsonlFile = path.join(resolveArchiveFolder(folderName), `${fileName}.jsonl`);
const jsonlFile = folderName.endsWith('.zip')
? `zip://archive:${folderName}//${fileName}.jsonl`
: path.join(resolveArchiveFolder(folderName), `${fileName}.jsonl`);
const res = jsonLinesReader({ fileName: jsonlFile, ...other });
return res;
}

View File

@@ -15,9 +15,9 @@ class CollectorWriterStream extends stream.Writable {
_final(callback) {
process.send({
msgtype: 'freeData',
msgtype: 'dataResult',
runid: this.runid,
freeData: { rows: this.rows, structure: this.structure },
dataResult: { rows: this.rows, structure: this.structure },
});
callback();
}

View File

@@ -69,6 +69,7 @@ async function copyStream(input, output, options) {
msgtype: 'copyStreamError',
copyStreamError: {
message: extractErrorMessage(err),
progressName,
...err,
},
});

View File

@@ -1,61 +0,0 @@
const stream = require('stream');
const path = require('path');
const { quoteFullName, fullNameToString, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility');
const logger = getLogger('dataDuplicator');
const { DataDuplicator } = require('dbgate-datalib');
const copyStream = require('./copyStream');
const jsonLinesReader = require('./jsonLinesReader');
const { resolveArchiveFolder } = require('../utility/directories');
async function dataDuplicator({
connection,
archive,
folder,
items,
options,
analysedStructure = null,
driver,
systemConnection,
}) {
if (!driver) driver = requireEngineDriver(connection);
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
try {
if (!analysedStructure) {
analysedStructure = await driver.analyseFull(dbhan);
}
const sourceDir = archive
? resolveArchiveFolder(archive)
: folder?.startsWith('archive:')
? resolveArchiveFolder(folder.substring('archive:'.length))
: folder;
const dupl = new DataDuplicator(
dbhan,
driver,
analysedStructure,
items.map(item => ({
name: item.name,
operation: item.operation,
matchColumns: item.matchColumns,
openStream:
item.openStream || (() => jsonLinesReader({ fileName: path.join(sourceDir, `${item.name}.jsonl`) })),
})),
stream,
copyStream,
options
);
await dupl.run();
} finally {
if (!systemConnection) {
await driver.close(dbhan);
}
}
}
module.exports = dataDuplicator;

View File

@@ -0,0 +1,96 @@
const stream = require('stream');
const path = require('path');
const { quoteFullName, fullNameToString, getLogger } = require('dbgate-tools');
const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility');
const logger = getLogger('datareplicator');
const { DataReplicator } = require('dbgate-datalib');
const { compileCompoudEvalCondition } = require('dbgate-filterparser');
const copyStream = require('./copyStream');
const jsonLinesReader = require('./jsonLinesReader');
const { resolveArchiveFolder } = require('../utility/directories');
const { evaluateCondition } = require('dbgate-sqltree');
function compileOperationFunction(enabled, condition) {
if (!enabled) return _row => false;
const conditionCompiled = compileCompoudEvalCondition(condition);
if (condition) {
return row => evaluateCondition(conditionCompiled, row);
}
return _row => true;
}
async function dataReplicator({
connection,
archive,
folder,
items,
options,
analysedStructure = null,
driver,
systemConnection,
}) {
if (!driver) driver = requireEngineDriver(connection);
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
try {
if (!analysedStructure) {
analysedStructure = await driver.analyseFull(dbhan);
}
let joinPath;
if (archive?.endsWith('.zip')) {
joinPath = file => `zip://archive:${archive}//${file}`;
} else {
const sourceDir = archive
? resolveArchiveFolder(archive)
: folder?.startsWith('archive:')
? resolveArchiveFolder(folder.substring('archive:'.length))
: folder;
joinPath = file => path.join(sourceDir, file);
}
const repl = new DataReplicator(
dbhan,
driver,
analysedStructure,
items.map(item => {
return {
name: item.name,
matchColumns: item.matchColumns,
findExisting: compileOperationFunction(item.findExisting, item.findCondition),
createNew: compileOperationFunction(item.createNew, item.createCondition),
updateExisting: compileOperationFunction(item.updateExisting, item.updateCondition),
deleteMissing: !!item.deleteMissing,
deleteRestrictionColumns: item.deleteRestrictionColumns ?? [],
openStream: item.openStream
? item.openStream
: item.jsonArray
? () => stream.Readable.from(item.jsonArray)
: () => jsonLinesReader({ fileName: joinPath(`${item.name}.jsonl`) }),
};
}),
stream,
copyStream,
options
);
await repl.run();
if (options?.runid) {
process.send({
msgtype: 'dataResult',
runid: options?.runid,
dataResult: repl.result,
});
}
return repl.result;
} finally {
if (!systemConnection) {
await driver.close(dbhan);
}
}
}
module.exports = dataReplicator;

View File

@@ -14,12 +14,13 @@ const crypto = require('crypto');
* @param {object} options.driver - driver object. If not provided, it will be loaded from connection
* @param {object} options.analysedStructure - analysed structure of the database. If not provided, it will be loaded
* @param {string} options.modelFolder - folder with model files (YAML files for tables, SQL files for views, procedures, ...)
* @param {import('dbgate-tools').DatabaseModelFile[]} options.loadedDbModel - loaded database model - collection of yaml and SQL files loaded into array
* @param {import('dbgate-tools').DatabaseModelFile[] | import('dbgate-types').DatabaseInfo} options.loadedDbModel - loaded database model - collection of yaml and SQL files loaded into array
* @param {function[]} options.modelTransforms - array of functions for transforming model
* @param {object} options.dbdiffOptionsExtra - extra options for dbdiff
* @param {string} options.ignoreNameRegex - regex for ignoring objects by name
* @param {string} options.targetSchema - target schema for deployment
* @param {number} options.maxMissingTablesRatio - maximum ratio of missing tables in database. Safety check, if missing ratio is highe, deploy is stopped (preventing accidental drop of all tables)
* @param {boolean} options.useTransaction - run deploy in transaction. If not provided, it will be set to true if driver supports transactions
*/
async function deployDb({
connection,
@@ -33,6 +34,7 @@ async function deployDb({
ignoreNameRegex = '',
targetSchema = null,
maxMissingTablesRatio = undefined,
useTransaction,
}) {
if (!driver) driver = requireEngineDriver(connection);
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
@@ -60,7 +62,14 @@ async function deployDb({
maxMissingTablesRatio,
});
// console.log('RUNNING DEPLOY SCRIPT:', sql);
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
await executeQuery({
connection,
systemConnection: dbhan,
driver,
sql,
logScriptItems: true,
useTransaction,
});
await scriptDeployer.runPost();
} finally {

View File

@@ -1,14 +1,30 @@
const crypto = require('crypto');
const path = require('path');
const { uploadsdir } = require('../utility/directories');
const { uploadsdir, archivedir } = require('../utility/directories');
const { downloadFile } = require('../utility/downloader');
const extractSingleFileFromZip = require('../utility/extractSingleFileFromZip');
async function download(url) {
if (url && url.match(/(^http:\/\/)|(^https:\/\/)/)) {
const tmpFile = path.join(uploadsdir(), crypto.randomUUID());
await downloadFile(url, tmpFile);
return tmpFile;
async function download(url, options = {}) {
const { targetFile } = options || {};
if (url) {
if (url.match(/(^http:\/\/)|(^https:\/\/)/)) {
const destFile = targetFile || path.join(uploadsdir(), crypto.randomUUID());
await downloadFile(url, destFile);
return destFile;
}
const zipMatch = url.match(/^zip\:\/\/(.*)\/\/(.*)$/);
if (zipMatch) {
const destFile = targetFile || path.join(uploadsdir(), crypto.randomUUID());
let zipFile = zipMatch[1];
if (zipFile.startsWith('archive:')) {
zipFile = path.join(archivedir(), zipFile.substring('archive:'.length));
}
await extractSingleFileFromZip(zipFile, zipMatch[2], destFile);
return destFile;
}
}
return url;
}

View File

@@ -1,47 +0,0 @@
const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility');
const { getLogger } = require('dbgate-tools');
const logger = getLogger('dumpDb');
function doDump(dumper) {
return new Promise((resolve, reject) => {
dumper.once('end', () => {
resolve(true);
});
dumper.once('error', err => {
reject(err);
});
dumper.run();
});
}
async function dumpDatabase({
connection = undefined,
systemConnection = undefined,
driver = undefined,
outputFile,
databaseName,
schemaName,
}) {
logger.info(`Dumping database`);
if (!driver) driver = requireEngineDriver(connection);
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
try {
const dumper = await driver.createBackupDumper(dbhan, {
outputFile,
databaseName,
schemaName,
});
await doDump(dumper);
} finally {
if (!systemConnection) {
await driver.close(dbhan);
}
}
}
module.exports = dumpDatabase;

View File

@@ -14,6 +14,8 @@ const logger = getLogger('execQuery');
* @param {string} [options.sql] - SQL query
* @param {string} [options.sqlFile] - SQL file
* @param {boolean} [options.logScriptItems] - whether to log script items instead of whole script
* @param {boolean} [options.useTransaction] - run query in transaction
* @param {boolean} [options.skipLogging] - whether to skip logging
*/
async function executeQuery({
connection = undefined,
@@ -22,8 +24,10 @@ async function executeQuery({
sql,
sqlFile = undefined,
logScriptItems = false,
skipLogging = false,
useTransaction,
}) {
if (!logScriptItems) {
if (!logScriptItems && !skipLogging) {
logger.info({ sql: getLimitedQuery(sql) }, `Execute query`);
}
@@ -36,9 +40,11 @@ async function executeQuery({
}
try {
logger.debug(`Running SQL query, length: ${sql.length}`);
if (!skipLogging) {
logger.debug(`Running SQL query, length: ${sql.length}`);
}
await driver.script(dbhan, sql, { logScriptItems });
await driver.script(dbhan, sql, { logScriptItems, useTransaction });
} finally {
if (!systemConnection) {
await driver.close(dbhan);

View File

@@ -23,7 +23,7 @@ const { connectUtility } = require('../utility/connectUtility');
* @param {object} options.driver - driver object. If not provided, it will be loaded from connection
* @param {object} options.analysedStructure - analysed structure of the database. If not provided, it will be loaded
* @param {string} options.modelFolder - folder with model files (YAML files for tables, SQL files for views, procedures, ...)
* @param {import('dbgate-tools').DatabaseModelFile[]} options.loadedDbModel - loaded database model - collection of yaml and SQL files loaded into array
* @param {import('dbgate-tools').DatabaseModelFile[] | import('dbgate-types').DatabaseInfo} options.loadedDbModel - loaded database model - collection of yaml and SQL files loaded into array
* @param {function[]} options.modelTransforms - array of functions for transforming model
* @param {object} options.dbdiffOptionsExtra - extra options for dbdiff
* @param {string} options.ignoreNameRegex - regex for ignoring objects by name
@@ -52,7 +52,10 @@ async function generateDeploySql({
dbdiffOptionsExtra?.['schemaMode'] !== 'ignore' &&
dbdiffOptionsExtra?.['schemaMode'] !== 'ignoreImplicit'
) {
throw new Error('targetSchema is required for databases with multiple schemas');
if (!driver?.dialect?.defaultSchemaName) {
throw new Error('targetSchema is required for databases with multiple schemas');
}
targetSchema = driver.dialect.defaultSchemaName;
}
try {

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const executeQuery = require('./executeQuery');
const { connectUtility } = require('../utility/connectUtility');
const requireEngineDriver = require('../utility/requireEngineDriver');
const { getAlterDatabaseScript, DatabaseAnalyser, runCommandOnDriver } = require('dbgate-tools');
const { getAlterDatabaseScript, DatabaseAnalyser, runCommandOnDriver, adaptDatabaseInfo } = require('dbgate-tools');
const importDbModel = require('../utility/importDbModel');
const jsonLinesReader = require('./jsonLinesReader');
const tableWriter = require('./tableWriter');
@@ -26,10 +26,7 @@ async function importDbFromFolder({ connection, systemConnection, driver, folder
if (driver?.databaseEngineTypes?.includes('sql')) {
const model = await importDbModel(folder);
let modelAdapted = {
...model,
tables: model.tables.map(table => driver.adaptTableInfo(table)),
};
let modelAdapted = adaptDatabaseInfo(model, driver);
for (const transform of modelTransforms || []) {
modelAdapted = transform(modelAdapted);
}

Some files were not shown because too many files have changed in this diff Show More