Compare commits

...

194 Commits

Author SHA1 Message Date
Nybkox 029f24b079 feat: update dbgate-query-splitter tp 4.11.3 2025-01-07 09:20:59 +01:00
Nybkox 9f4b55ce07 feat: sqlite triggers tests 2025-01-02 11:47:07 +01:00
Nybkox 01b7c42851 fix: prevent testing empty parameters 2025-01-02 11:41:00 +01:00
Nybkox f0221f6e43 feat: sqlite triggers 2025-01-02 11:26:40 +01:00
Nybkox 30bbdb0f1d refactor: move sqlite queries to sql directory 2025-01-02 11:26:40 +01:00
Nybkox a6f0bc78d4 fix: always show pin icon on the right 2025-01-02 10:59:22 +01:00
Jan Prochazka ceb6a88964 Merge branch 'feature/workflow-refactor' 2024-12-30 15:25:39 +01:00
Jan Prochazka a39fff8b3a ssh tunnel test disabled 2024-12-30 15:25:23 +01:00
Jan Prochazka 0cf3d5993c generated header 2024-12-30 08:28:25 +01:00
Jan Prochazka 2966484cda v6.1.3-beta.1 2024-12-30 08:23:36 +01:00
Jan Prochazka ac3bb58106 generated workflows 2024-12-30 08:23:18 +01:00
Jan Prochazka 36e2f1bdee workflow templates 2024-12-30 08:11:18 +01:00
Jan Prochazka 70083bd870 workflows - defs support 2024-12-30 07:05:25 +01:00
Jan Prochazka f7b39fca26 code cleanup 2024-12-29 09:57:33 +01:00
Jan Prochazka 70873e83bd docker pipeline refactor 2024-12-29 09:57:18 +01:00
Jan Prochazka 2858bba8b2 yaml replace changed 2024-12-28 21:16:52 +01:00
Jan Prochazka bd0404fbaf fixes 2024-12-28 15:36:05 +01:00
Jan Prochazka 317f6256f6 fix 2024-12-28 15:28:34 +01:00
Jan Prochazka 6c9b738717 fix 2024-12-28 15:10:45 +01:00
Jan Prochazka 2f8c584af5 workflows 2024-12-28 15:03:38 +01:00
Jan Prochazka d3b417679e workflow processor 2024-12-28 15:00:59 +01:00
Jan Prochazka 00dd8bfc72 fixed links to dbgate.org 2024-12-28 14:01:19 +01:00
Jan Prochazka e1eea4adf3 fixed links to dbgate.org 2024-12-28 11:30:18 +01:00
Jan Prochazka fd2d2e90d4 workflow refactor WIP 2024-12-27 08:34:17 +01:00
Jan Prochazka f97b70ce45 v6.1.2 2024-12-26 08:05:17 +01:00
Jan Prochazka 45d79478fb workflow fixes 2024-12-26 08:05:03 +01:00
Jan Prochazka a59f42bd62 v6.1.2-premium-beta.18 2024-12-26 07:47:14 +01:00
Jan Prochazka a189fa7b6d fix 2024-12-26 07:46:58 +01:00
Jan Prochazka 794bc97207 v6.1.2-beta.17 2024-12-26 07:45:27 +01:00
Jan Prochazka 24f7def7ab v6.1.2-premium-beta.16 2024-12-26 07:45:16 +01:00
Jan Prochazka 01071c236e notarization log 2024-12-26 07:44:53 +01:00
Jan Prochazka a6326ac9fa v6.1.2-premium-beta.15 2024-12-26 07:37:36 +01:00
Jan Prochazka 4c6d409be8 print notarization log 2024-12-26 07:37:19 +01:00
Jan Prochazka 44ad8e51a5 v6.1.2-premium-beta.14 2024-12-26 07:18:26 +01:00
Jan Prochazka e82578adfb v6.1.2-beta.14 2024-12-26 07:17:52 +01:00
Jan Prochazka 24f28b559e pro app build 2024-12-26 07:17:24 +01:00
Jan Prochazka b086a5d3d2 prevent double upload snap 2024-12-25 21:32:17 +01:00
Jan Prochazka bb0b87b770 v6.1.2-beta.13 2024-12-25 20:20:26 +01:00
Jan Prochazka aa4f82fa98 fix 2024-12-25 20:20:10 +01:00
Jan Prochazka 873ace4170 v6.1.2-beta.12 2024-12-25 19:51:24 +01:00
Jan Prochazka 5a9d8ba0a7 fix 2024-12-25 19:51:16 +01:00
Jan Prochazka 07f0fdcc1c v6.1.2-beta.11 2024-12-25 19:43:27 +01:00
Jan Prochazka ff7ee4fb98 fix 2024-12-25 19:43:16 +01:00
Jan Prochazka b1f5b62757 v6.1.2-beta.10 2024-12-25 19:39:37 +01:00
Jan Prochazka a67c857204 python 3.11 fix 2024-12-25 19:39:26 +01:00
Jan Prochazka 4032c53a30 v6.1.2-beta.9 2024-12-25 12:19:09 +01:00
Jan Prochazka 0a37ed74d9 electron reverted to version 30 2024-12-25 12:18:58 +01:00
Jan Prochazka f07efe440a v6.1.2-beta.8 2024-12-25 12:09:35 +01:00
Jan Prochazka 2d5e67802d removed mongodb-client-encryption from community build 2024-12-25 12:09:25 +01:00
Jan Prochazka 7b8e013084 v6.1.2-beta.7 2024-12-25 11:29:42 +01:00
Jan Prochazka b69fc04b5c try to upgrade electron 2024-12-25 11:29:23 +01:00
Jan Prochazka b6dfdd4741 v6.1.2-beta.6 2024-12-25 11:14:10 +01:00
Jan Prochazka ff3985a542 upgraded mongodb-client-encryption 2024-12-25 11:13:54 +01:00
Jan Prochazka 9c7e1dcaf1 v6.1.2-beta.5 2024-12-25 10:44:39 +01:00
Jan Prochazka 48eb09687c app build - node version 22 2024-12-25 10:44:02 +01:00
Jan Prochazka 86b339535d updated electron builder 2024-12-25 10:42:49 +01:00
Jan Prochazka 576b6dc774 v6.1.2-beta.4 2024-12-25 10:28:26 +01:00
Jan Prochazka 18ca971f67 try to use electron notarize 2024-12-25 10:28:10 +01:00
Jan Prochazka 3916a5e6e8 fixed ssh tunnel connection test #972 #973 2024-12-25 09:49:23 +01:00
Jan Prochazka 1446fdad5e v6.1.2-beta.3 2024-12-23 09:52:31 +01:00
Jan Prochazka 1478886e04 fix 2024-12-23 09:52:22 +01:00
Jan Prochazka 54d61790a9 v6.1.2-beta.2 2024-12-23 09:27:28 +01:00
Jan Prochazka bb638fd8db fix 2024-12-23 09:27:14 +01:00
Jan Prochazka e3a2589517 v6.1.2-beta.1 2024-12-23 09:21:42 +01:00
Jan Prochazka 6f557a6463 print notarization error 2024-12-23 09:21:31 +01:00
Jan Prochazka 323f27f6e4 changelog 2024-12-23 07:06:36 +01:00
Jan Prochazka 8d4a38dccb v6.1.1 2024-12-23 07:06:12 +01:00
SPRINX0\prochazka 54a3b30e83 Merge branch 'master' of https://github.com/dbgate/dbgate 2024-12-20 13:45:41 +01:00
SPRINX0\prochazka 6251bcd8fd changelog 2024-12-20 13:45:39 +01:00
SPRINX0\prochazka e42099c1a9 v6.1.1-beta.1 2024-12-20 12:57:25 +01:00
Jan Prochazka b07ee91cf9 fixed oracle export + clickhouse test 2024-12-20 12:23:29 +01:00
Jan Prochazka 62c028fc3c export test finished 2024-12-20 11:33:28 +01:00
SPRINX0\prochazka 07ea575b0f fixed postgres export + export tests #970 2024-12-20 11:17:43 +01:00
SPRINX0\prochazka ee78786974 #968 mysql table comment fix 2024-12-20 10:47:14 +01:00
SPRINX0\prochazka e1814663cd trigger fulltext search 2024-12-20 10:40:57 +01:00
SPRINX0\prochazka 6ae3e019ff ux 2024-12-20 10:38:29 +01:00
Jan Prochazka cadf539c3d fixed diff for triggers 2024-12-20 10:30:05 +01:00
Jan Prochazka 51929d7ef7 some tests fixed 2024-12-20 10:23:49 +01:00
Jan Prochazka 0edc7f077f test trigger createSql 2024-12-20 09:26:53 +01:00
SPRINX0\prochazka 2488cee7ea test definition refactor 2024-12-20 09:03:30 +01:00
SPRINX0\prochazka edf64db69a search settings improvement 2024-12-20 08:42:30 +01:00
SPRINX0\prochazka c65bf51dcd Merge branch 'feature/parameters-oracle' 2024-12-20 07:46:51 +01:00
Nybkox effe235a95 feat: oracle parameters 2024-12-19 16:06:56 +01:00
Nybkox 583d1494c2 feat: fallback to position if param does not have name 2024-12-19 12:50:50 +01:00
Nybkox 983daf0d1c feat: add options position to params typing 2024-12-19 12:09:25 +01:00
Jan Prochazka 756ddf1a8b Merge pull request #969 from dbgate/feature/triggers
Feature/triggers
2024-12-19 12:09:08 +01:00
Nybkox 9ccd647d97 fix: update triggers typing to cover all oracle timing options 2024-12-19 11:55:22 +01:00
Nybkox dc9bfdc553 fix: update pssql triggers query to match ts def 2024-12-19 10:50:39 +01:00
Nybkox af4e91faba fix: update oracle triggers test create queries 2024-12-19 10:46:40 +01:00
Nybkox 394020157b fix: update oracle triggers tests expected values 2024-12-19 10:26:48 +01:00
Nybkox f16aab12e1 fix: add trigger event type to oracle triggers query 2024-12-19 10:17:16 +01:00
Nybkox e08216d6dd feat: mssql triggers tests 2024-12-19 10:13:15 +01:00
Nybkox ebb4f8e73e fix: mssql triggers query naming 2024-12-19 10:13:04 +01:00
Nybkox b9939e5d5f fix: correctly map mysql triggers sql output 2024-12-19 09:58:54 +01:00
Nybkox 9eb3bca8d6 feat: add other create/drop for triggers tests 2024-12-19 09:54:17 +01:00
Nybkox 022f263bf9 fix: update pssql triggers test sql 2024-12-19 09:53:47 +01:00
Nybkox 56972652d5 fix: pssql triggers - map tgtype to timing / event type correctly 2024-12-19 09:53:11 +01:00
SPRINX0\prochazka d75b9e2688 ace-builds patch #954 2024-12-19 09:21:57 +01:00
Nybkox d2c5440e39 feat: pssql tirgger test 2024-12-19 08:33:26 +01:00
Jan Prochazka 8ff30e426e fix - expand limited when accessing bykeyboard nav 2024-12-18 14:13:01 +01:00
Jan Prochazka 7cdbef609e arm-64 builds 2024-12-18 10:59:21 +01:00
Jan Prochazka f6195a468d v6.1.0 2024-12-18 10:37:54 +01:00
Jan Prochazka c23bf72d55 changelog 2024-12-18 10:35:54 +01:00
Jan Prochazka c02441402b fixed search in list 2024-12-18 10:22:02 +01:00
Jan Prochazka b9a8764b55 fixed search 2024-12-18 10:01:52 +01:00
Jan Prochazka a2374c1981 v6.0.1-beta.6 2024-12-18 09:39:57 +01:00
Jan Prochazka 9cfd5af704 prevent jump to first item when focusing because of mouse 2024-12-18 09:37:40 +01:00
Jan Prochazka a6f473b8ed better connection UX 2024-12-18 09:08:42 +01:00
Jan Prochazka e0a74402cb dropdown for default database 2024-12-18 08:43:51 +01:00
Jan Prochazka c6e57b278e use default database 2024-12-18 08:18:09 +01:00
Jan Prochazka e63f1f8f09 clickAction refactor, settings - open detail after keyboard navigation 2024-12-18 08:08:45 +01:00
Nybkox e866c019f0 feat: mysql and oracle triggers tests 2024-12-18 06:09:30 +01:00
Nybkox 5f4bd6d3e3 fix: triggers typing and yaml conv 2024-12-18 06:05:49 +01:00
SPRINX0\prochazka 57da9c9885 changelog wip 2024-12-17 17:17:31 +01:00
SPRINX0\prochazka e6a3acf4c2 search GUI improved 2024-12-17 16:27:54 +01:00
SPRINX0\prochazka 537869e862 data grid - no rows info 2024-12-17 16:03:21 +01:00
SPRINX0\prochazka ae2ff7b3b1 fixed display CLOB an NCLOB columns in Oracle #944 2024-12-17 15:31:10 +01:00
SPRINX0\prochazka 1db01dbdb1 optimalization 2024-12-17 14:59:44 +01:00
SPRINX0\prochazka 7988438dc7 fix search connections 2024-12-17 14:43:05 +01:00
SPRINX0\prochazka 3b32823f94 v6.0.1-beta.5 2024-12-17 14:29:56 +01:00
SPRINX0\prochazka 3370c754f2 try to fix postgres plugin 2024-12-17 14:29:45 +01:00
SPRINX0\prochazka 2d84e5a611 dataGrid align numbers right #957 2024-12-17 13:43:21 +01:00
SPRINX0\prochazka 8d5f73849e upgraded ace-builds #954 2024-12-17 12:42:09 +01:00
SPRINX0\prochazka 7a5019164a configurable search in connections 2024-12-17 12:26:50 +01:00
SPRINX0\prochazka f5733ea2d7 handle camelCase in tokenizer 2024-12-17 10:16:19 +01:00
Nybkox 413287c691 fix: remove triggerName 2024-12-17 10:16:07 +01:00
Nybkox 9e941dfce2 feat: add triggers to ui 2024-12-17 10:16:07 +01:00
Nybkox d32af771dc fix: add purename to trigger analysers 2024-12-17 10:16:07 +01:00
Nybkox 1c84f40bcf feat: add triggers loading message 2024-12-17 10:16:06 +01:00
Nybkox 671eba22e0 feat: add oracle triggers to analyser 2024-12-17 10:16:06 +01:00
Nybkox c00cb3076c feat: add mysql triggers to analyser 2024-12-17 10:16:06 +01:00
Nybkox 62daa13e54 feat: add mssql triggers to analyser 2024-12-17 10:16:06 +01:00
Nybkox 4cc0a66a7d feat: add pssql triggers to analyser 2024-12-17 10:16:06 +01:00
SPRINX0\prochazka 92e13220d8 tokenized search in references 2024-12-17 10:12:45 +01:00
SPRINX0\prochazka 2a5fdd852a search tokenizer optimalization 2024-12-17 10:09:52 +01:00
SPRINX0\prochazka 7759fd862f ability to disable tab preview mode 2024-12-17 09:34:16 +01:00
SPRINX0\prochazka ca5dd0ac30 v6.0.1-beta.4 2024-12-16 17:01:39 +01:00
SPRINX0\prochazka 18be29fd88 Merge branch 'feature/search' 2024-12-16 17:01:26 +01:00
SPRINX0\prochazka 6ea54a5b0a tokenized column search 2024-12-16 17:01:05 +01:00
SPRINX0\prochazka 5d294f6236 camel search tokenizer 2024-12-16 16:58:10 +01:00
SPRINX0\prochazka 5544b6291b search tokenizer 2024-12-16 16:50:17 +01:00
SPRINX0\prochazka bf4841bca4 filter name optimalization 2024-12-16 16:23:55 +01:00
SPRINX0\prochazka 358a641449 refactor - not working 2024-12-16 15:53:52 +01:00
SPRINX0\prochazka 7e1ceb69ae UX fix 2024-12-16 15:36:26 +01:00
SPRINX0\prochazka 939f04ae62 optimalization 2024-12-16 15:19:54 +01:00
SPRINX0\prochazka 2fc2ac491c find in SQL text 2024-12-16 14:45:38 +01:00
SPRINX0\prochazka 20a5a50516 rename file 2024-12-16 14:10:18 +01:00
SPRINX0\prochazka 0932f4c537 search columns WIP 2024-12-16 14:08:19 +01:00
SPRINX0\prochazka c46c9a4e16 lisgt matcher refactor 2024-12-16 13:08:59 +01:00
SPRINX0\prochazka a20b4b3339 search settings 2024-12-16 11:47:53 +01:00
SPRINX0\prochazka dc302f89c7 Display comments into TABLES and COLUMNS lists #755 2024-12-16 09:46:07 +01:00
SPRINX0\prochazka f5a2d142e2 fixed [MSSQL] Foreign keys show up in a weird way #734 2024-12-16 08:26:25 +01:00
SPRINX0\prochazka 1020a5820b Merge branch 'master' of https://github.com/dbgate/dbgate 2024-12-13 16:27:17 +01:00
SPRINX0\prochazka deb13505b8 fixed loading constraints #734 2024-12-13 16:27:14 +01:00
Jan Prochazka 71c06e31f7 sql terminator in oracle routines 2024-12-13 15:06:12 +01:00
Jan Prochazka a203480a72 oracle function tests 2024-12-13 15:06:12 +01:00
Jan Prochazka c0fcd681be oracle procedure & function analyser 2024-12-13 15:06:12 +01:00
Jan Prochazka 7402bb6823 test fix 2024-12-13 15:06:11 +01:00
Jan Prochazka 35d791bee4 fixed some tests 2024-12-13 15:06:11 +01:00
Jan Prochazka ad4a599800 oracle view fix 2024-12-13 15:06:11 +01:00
Jan Prochazka 33db85f03b oracle tests 2024-12-13 15:06:11 +01:00
Jan Prochazka ec5d05fc26 oracle fixes 2024-12-13 15:06:11 +01:00
Jan Prochazka face7ecdb5 rename sql object oracle 2024-12-13 15:06:11 +01:00
Jan Prochazka 6035319035 uncommented oracle container for tests 2024-12-13 15:06:11 +01:00
Jan Prochazka e698da71fb oracle tests - run on CI 2024-12-13 15:06:11 +01:00
Jan Prochazka b466de781a oracle - rename table, deploy scripts passes 2024-12-13 15:06:11 +01:00
Jan Prochazka e5d583310d merged tests pipelines 2024-12-13 15:06:11 +01:00
Jan Prochazka f72dbf19c2 driver tests 2024-12-13 15:06:11 +01:00
Jan Prochazka 10538a04b4 oracle import pass 2024-12-13 15:06:11 +01:00
Jan Prochazka d71452a397 oracle - implemented scope identity 2024-12-13 15:06:11 +01:00
Jan Prochazka 3f45bfcdd0 select scope identity test 2024-12-13 15:06:11 +01:00
Jan Prochazka 545e9863b6 fix identity test 2024-12-13 15:06:11 +01:00
Jan Prochazka a37f2a5240 select scope identity test 2024-12-13 15:06:11 +01:00
Jan Prochazka d6b4c0a96b oracle alter table passes 2024-12-13 15:06:11 +01:00
Jan Prochazka d56e917b3f oracle query spec passed 2024-12-13 15:06:11 +01:00
Jan Prochazka 860c811504 oracle table-create test passes 2024-12-13 15:06:11 +01:00
Jan Prochazka c93e8c35ec table-analyse oracle tests pass 2024-12-13 15:06:11 +01:00
Jan Prochazka 5278e5da0c fixed oracle index analyser 2024-12-13 15:06:11 +01:00
Jan Prochazka e7e3c307fc table analyse refactor (works on oracle) 2024-12-13 15:06:11 +01:00
Jan Prochazka e9af85038e try to use smaller oracle image 2024-12-13 15:06:11 +01:00
Jan Prochazka bf04721e36 run oracle tests on CI 2024-12-13 15:06:11 +01:00
SPRINX0\prochazka a810dc4204 focused connection changes 2024-12-13 10:48:42 +01:00
Jan Prochazka faad82fc34 Merge pull request #961 from dbgate/feature/postgis-geo
feat: transform geography binary data to wkt
2024-12-12 16:47:29 +01:00
Nybkox b8ae53db7d feat: transform geography binary data to wkt 2024-12-12 16:37:54 +01:00
Jan Prochazka 1571295ab6 focus UX 2024-12-12 16:29:12 +01:00
Jan Prochazka d3cc3a92c1 v6.0.1-beta.3 2024-12-12 16:15:18 +01:00
Jan Prochazka 381dc6a535 Add apple silicon only build #949 2024-12-12 16:15:03 +01:00
Jan Prochazka 6b9df571af handler UX scroll problem 2024-12-12 16:06:36 +01:00
Jan Prochazka 897547371e configurable connection click, database click #959 2024-12-12 15:53:00 +01:00
Jan Prochazka bf85a922ca first oracle test works 2024-12-10 16:03:26 +01:00
Jan Prochazka 00525f6b81 oracle tests WIP 2024-12-10 15:40:15 +01:00
SPRINX0\prochazka 6dd27eb34f basic auth check config #934 2024-12-10 13:11:03 +01:00
SPRINX0\prochazka 0b30386fee e2e test config 2024-12-10 08:48:53 +01:00
SPRINX0\prochazka 1f7f0ea8a2 Merge branch 'feature/e2e-tests' 2024-12-10 08:33:52 +01:00
149 changed files with 5002 additions and 2561 deletions
+51 -43
View File
@@ -1,83 +1,86 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Electron app BETA
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
os: [macos-14, windows-2022, ubuntu-22.04]
# os: [macOS-10.15]
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) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- 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
- name: yarn adjustPackageJson
node-version: 22.x
- name: adjustPackageJson
run: |
yarn adjustPackageJson
node adjustPackageJson --community
- name: setUpdaterChannel beta
run: |
node setUpdaterChannel beta
- 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
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
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 }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_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 }}
- name: publishSnap
if: matrix.os == 'ubuntu-22.04'
run: |
snapcraft upload --release=beta app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
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
@@ -89,8 +92,10 @@ 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/*-mac_x64.dmg artifacts/dbgate-beta.dmg || 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
mv app/dist/*.snap artifacts/dbgate-beta.snap || true
mv app/dist/*.exe artifacts/ || true
mv app/dist/*.zip artifacts/ || true
@@ -103,18 +108,21 @@ jobs:
mv app/dist/*.yml artifacts/ || true
rm artifacts/builder-debug.yml
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: '${{ matrix.os }}'
path: artifacts
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: 'startsWith(github.ref, ''refs/tags/'')'
with:
files: 'artifacts/**'
files: artifacts/**
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- 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 {} ===" \;
+62 -50
View File
@@ -1,43 +1,44 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Electron app PREMIUM BETA
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+-premium-beta.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
# os: [windows-2022]
# os: [ubuntu-22.04]
# os: [windows-2022, ubuntu-22.04]
os: [macos-14, windows-2022, ubuntu-22.04]
# os: [macOS-10.15]
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) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- 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 }}
token: '${{ secrets.GH_TOKEN }}'
path: dbgate-pro
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -49,76 +50,82 @@ jobs:
yarn
node sync.js --nowatch
cd ..
- name: adjustPackageJson
run: |
cd ..
cd dbgate-merged
- name: yarn adjustPackageJson
run: |
cd ..
cd dbgate-merged
yarn adjustPackageJson
- name: adjustAppPackageJsonPremium
run: |
cd ..
cd dbgate-merged
node adjustAppPackageJsonPremium
node adjustPackageJson --premium
- name: setUpdaterChannel premium-beta
run: |
cd ..
cd dbgate-merged
node setUpdaterChannel premium-beta
- name: yarn set timeout
run: |
cd ..
cd dbgate-merged
yarn config set network-timeout 100000
- name: yarn install
run: |
cd ..
cd dbgate-merged
yarn install
- name: setCurrentVersion
run: |
cd ..
cd dbgate-merged
yarn setCurrentVersion
- name: printSecrets
run: |
cd ..
cd dbgate-merged
yarn printSecrets
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: fillPackagedPlugins
run: |
cd ..
cd dbgate-merged
yarn fillPackagedPlugins
- name: Publish
run: |
cd ..
cd dbgate-merged
yarn run build:app
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_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 }}
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 ../dbgate-merged/app/dist/*.deb artifacts/dbgate-premium-beta.deb || true
cp ../dbgate-merged/app/dist/*x86*.AppImage artifacts/dbgate-premium-beta.AppImage || true
cp ../dbgate-merged/app/dist/*arm64*.AppImage artifacts/dbgate-premium-beta-arm64.AppImage || true
cp ../dbgate-merged/app/dist/*armv7l*.AppImage artifacts/dbgate-premium-beta-armv7l.AppImage || true
cp ../dbgate-merged/app/dist/*win*.exe artifacts/dbgate-premium-beta.exe || true
cp ../dbgate-merged/app/dist/*-mac_x64.dmg artifacts/dbgate-premium-beta.dmg || 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/*-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
mv ../dbgate-merged/app/dist/*.snap artifacts/dbgate-premium-beta.snap || true
mv ../dbgate-merged/app/dist/*.exe artifacts/ || true
mv ../dbgate-merged/app/dist/*.zip artifacts/ || true
@@ -131,18 +138,23 @@ jobs:
mv ../dbgate-merged/app/dist/*.yml artifacts/ || true
rm artifacts/builder-debug.yml
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: '${{ matrix.os }}'
path: artifacts
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: 'startsWith(github.ref, ''refs/tags/'')'
with:
files: 'artifacts/**'
files: artifacts/**
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Print content of notarization-error.log
if: failure() && matrix.os == 'macos-14'
run: |
cd ..
cd dbgate-merged
find . -type f -name "notarization-error.log" -exec echo "=== Start of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \;
+62 -52
View File
@@ -1,44 +1,44 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Electron app PREMIUM
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# branches:
# - production
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
# os: [ubuntu-22.04, windows-2016]
os: [macos-14, windows-2022, ubuntu-22.04]
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) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- 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 }}
token: '${{ secrets.GH_TOKEN }}'
path: dbgate-pro
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -50,101 +50,111 @@ jobs:
yarn
node sync.js --nowatch
cd ..
- name: adjustPackageJson
run: |
cd ..
cd dbgate-merged
- name: yarn adjustPackageJson
run: |
cd ..
cd dbgate-merged
yarn adjustPackageJson
- name: adjustAppPackageJsonPremium
run: |
cd ..
cd dbgate-merged
node adjustAppPackageJsonPremium
node adjustPackageJson --premium
- name: setUpdaterChannel premium
run: |
cd ..
cd dbgate-merged
node setUpdaterChannel premium
- name: yarn set timeout
run: |
cd ..
cd dbgate-merged
yarn config set network-timeout 100000
- name: yarn install
run: |
cd ..
cd dbgate-merged
yarn install
- name: setCurrentVersion
run: |
cd ..
cd dbgate-merged
yarn setCurrentVersion
- name: printSecrets
run: |
cd ..
cd dbgate-merged
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: fillPackagedPlugins
run: |
cd ..
cd dbgate-merged
yarn fillPackagedPlugins
- name: Publish
run: |
cd ..
cd dbgate-merged
yarn run build:app
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_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 }}
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 ../dbgate-merged/app/dist/*.deb artifacts/dbgate-premium-latest.deb || true
cp ../dbgate-merged/app/dist/*x86*.AppImage artifacts/dbgate-premium-latest.AppImage || true
cp ../dbgate-merged/app/dist/*.exe artifacts/dbgate-premium-latest.exe || true
cp ../dbgate-merged/app/dist/*win_x64.zip artifacts/dbgate-premium-windows-latest.zip || true
cp ../dbgate-merged/app/dist/*win_arm64.zip artifacts/dbgate-premium-windows-latest-arm64.zip || true
cp ../dbgate-merged/app/dist/*arm64*.AppImage artifacts/dbgate-premium-latest-arm64.AppImage || true
cp ../dbgate-merged/app/dist/*armv7l*.AppImage artifacts/dbgate-premium-latest-armv7l.AppImage || true
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/*-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
mv ../dbgate-merged/app/dist/*.snap artifacts/dbgate-premium-latest.snap || true
mv ../dbgate-merged/app/dist/*.exe artifacts/ || true
mv ../dbgate-merged/app/dist/*.zip artifacts/ || true
mv ../dbgate-merged/app/dist/*.tar.gz artifacts/ || true
mv ../dbgate-merged/app/dist/*.AppImage artifacts/ || true
mv ../dbgate-merged/app/dist/*.deb artifacts/ || true
mv ../dbgate-merged/app/dist/*.snap artifacts/ || true
mv ../dbgate-merged/app/dist/*.dmg artifacts/ || true
mv ../dbgate-merged/app/dist/*.blockmap artifacts/ || true
mv ../dbgate-merged/app/dist/*.yml artifacts/ || true
rm artifacts/builder-debug.yml
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: '${{ matrix.os }}'
path: artifacts
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: 'startsWith(github.ref, ''refs/tags/'')'
with:
files: 'artifacts/**'
files: artifacts/**
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Print content of notarization-error.log
if: failure() && matrix.os == 'macos-14'
run: |
cd ..
cd dbgate-merged
find . -type f -name "notarization-error.log" -exec echo "=== Start of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \;
+51 -86
View File
@@ -1,90 +1,85 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Electron app
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# branches:
# - production
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
# os: [ubuntu-22.04, windows-2016]
os: [macos-14, windows-2022, ubuntu-22.04]
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) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- 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
- name: yarn adjustPackageJson
node-version: 22.x
- name: adjustPackageJson
run: |
yarn adjustPackageJson
node adjustPackageJson --community
- name: yarn set timeout
run: |
yarn config set network-timeout 100000
- name: yarn install
run: |
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
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 }} # token for electron publish
WIN_CSC_LINK: ${{ secrets.WINCERT_2025 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_2025_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_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 }}
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: generatePadFile
run: |
yarn generatePadFile
- name: publishSnap
if: matrix.os == 'ubuntu-22.04'
run: |
snapcraft upload --release=stable app/dist/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{secrets.SNAPCRAFT_LOGIN}}
- name: Copy artifacts
run: |
mkdir artifacts
@@ -93,74 +88,44 @@ jobs:
cp app/dist/*x86*.AppImage artifacts/dbgate-latest.AppImage || true
cp app/dist/*arm64*.AppImage artifacts/dbgate-latest-arm64.AppImage || true
cp app/dist/*armv7l*.AppImage artifacts/dbgate-latest-armv7l.AppImage || true
cp app/dist/*.exe artifacts/dbgate-latest.exe || true
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/*-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
mv app/dist/*.snap artifacts/dbgate-latest.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/*.snap artifacts/dbgate-latest.snap || true
mv app/dist/*.blockmap artifacts/ || true
mv app/dist/*.yml artifacts/ || true
rm artifacts/builder-debug.yml
# - name: Copy artifacts Linux, MacOs
# if: matrix.os != 'windows-2016'
# run: |
# mkdir artifacts
# cp app/dist/*.AppImage artifacts/ || true
# cp app/dist/*.dmg artifacts/ || true
# cp app/dist/*.deb artifacts/ || true
# mv app/dist/*.deb artifacts/dbgate-linux.deb || true
# mv app/dist/*.AppImage artifacts/dbgate-linux.AppImage || true
# mv app/dist/*.dmg artifacts/dbgate-mac.dmg || true
# - name: Copy artifacts Win
# if: matrix.os == 'windows-2016'
# run: |
# mkdir artifacts
# cp app/dist/*.exe artifacts/ || true
# mv app/dist/*.exe artifacts/dbgate-windows.exe
# mv app/dist/latest.yml artifacts/latest.yml || true
- name: Copy PAD file
if: matrix.os == 'windows-2022'
run: |
mv app/dist/dbgate-pad.xml artifacts/ || true
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: '${{ matrix.os }}'
path: artifacts
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: 'startsWith(github.ref, ''refs/tags/'')'
with:
files: 'artifacts/**'
files: artifacts/**
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Print content of notarization-error.log
if: failure() && matrix.os == 'macos-14'
run: |
# - name: Create Release
# id: create_release
# uses: actions/create-release@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# tag_name: ${{ github.ref }}
# release_name: Release ${{ github.ref }}
# draft: false
# prerelease: false
find . -type f -name "notarization-error.log" -exec echo "=== Start of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \;
+25 -50
View File
@@ -1,47 +1,41 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: AWS image PREMIUM
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# - 'v[0-9]+.[0-9]+.[0-9]+-premium-beta.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-packer-beta.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
matrix:
os: [ubuntu-22.04]
os:
- ubuntu-22.04
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Setup `packer`
uses: hashicorp/setup-packer@main
with:
version: latest
- name: Checkout dbgate/dbgate-pro
uses: actions/checkout@v2
with:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
token: '${{ secrets.GH_TOKEN }}'
path: dbgate-pro
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -53,33 +47,28 @@ jobs:
yarn
node sync.js --nowatch
cd ..
- name: yarn adjustPackageJson
- name: adjustPackageJson
run: |
cd ..
cd dbgate-merged
yarn adjustPackageJson
node adjustPackageJson --premium
- name: yarn install
run: |
cd ..
cd dbgate-merged
yarn install
- name: setCurrentVersion
run: |
cd ..
cd dbgate-merged
yarn setCurrentVersion
- name: printSecrets
run: |
cd ..
cd dbgate-merged
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: Prepare packer build
run: |
cd ..
@@ -87,62 +76,48 @@ jobs:
yarn run prepare:packer
cd packer
zip -r cloud-build.zip build
- name: Copy artifacts
run: |
mkdir artifacts
cp ../dbgate-merged/packer/cloud-build.zip artifacts/cloud-build.zip || true
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}
name: '${{ matrix.os }}'
path: artifacts
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: 'startsWith(github.ref, ''refs/tags/'')'
with:
files: 'artifacts/**'
files: artifacts/**
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Run `packer init`
run: |
cd ../dbgate-merged/packer
packer init ./aws-ubuntu.pkr.hcl
env:
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}}
AWS_ACCESS_KEY_ID: '${{secrets.AWS_ACCESS_KEY_ID}}'
AWS_SECRET_ACCESS_KEY: '${{secrets.AWS_SECRET_ACCESS_KEY}}'
AWS_DEFAULT_REGION: '${{secrets.AWS_DEFAULT_REGION}}'
- name: Run `packer build`
run: |
cd ../dbgate-merged/packer
packer build ./aws-ubuntu.pkr.hcl
env:
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}}
# - name: Install AWS CLI
# run: |
# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# unzip awscliv2.zip
# sudo ./aws/install
# sudo apt-get install jq -y
AWS_ACCESS_KEY_ID: '${{secrets.AWS_ACCESS_KEY_ID}}'
AWS_SECRET_ACCESS_KEY: '${{secrets.AWS_SECRET_ACCESS_KEY}}'
AWS_DEFAULT_REGION: '${{secrets.AWS_DEFAULT_REGION}}'
- name: Install jq
run: |
sudo apt-get install jq -y
- name: Delete old AMIs
run: |
cd ../dbgate-merged/packer
chmod +x delete-old-amis.sh
./delete-old-amis.sh
env:
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}}
AWS_ACCESS_KEY_ID: '${{secrets.AWS_ACCESS_KEY_ID}}'
AWS_SECRET_ACCESS_KEY: '${{secrets.AWS_SECRET_ACCESS_KEY}}'
AWS_DEFAULT_REGION: '${{secrets.AWS_DEFAULT_REGION}}'
+24 -29
View File
@@ -1,35 +1,32 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Docker image PREMIUM
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-premium-beta.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
matrix:
os: [ubuntu-22.04]
os:
- ubuntu-22.04
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
dbgate/dbgate-premium
images: dbgate/dbgate-premium
flavor: |
latest=false
tags: |
@@ -37,19 +34,16 @@ jobs:
type=match,pattern=\d+.\d+.\d+,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Checkout dbgate/dbgate-pro
uses: actions/checkout@v2
with:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
token: '${{ secrets.GH_TOKEN }}'
path: dbgate-pro
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -61,50 +55,51 @@ jobs:
yarn
node sync.js --nowatch
cd ..
- name: yarn adjustPackageJson
- name: adjustPackageJson
run: |
cd ..
cd dbgate-merged
yarn adjustPackageJson
node adjustPackageJson --premium
- name: yarn install
run: |
cd ..
cd dbgate-merged
yarn install
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
cd ..
cd dbgate-merged
yarn setCurrentVersion
- name: printSecrets
run: |
cd ..
cd dbgate-merged
yarn printSecrets
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: Prepare docker image
run: |
cd ..
cd dbgate-merged
yarn run prepare:docker
yarn run prepare:docker
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
username: '${{ secrets.DOCKER_USERNAME }}'
password: '${{ secrets.DOCKER_PASSWORD }}'
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
context: ../dbgate-merged/docker
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/amd64,linux/arm64
tags: '${{ steps.meta.outputs.tags }}'
platforms: 'linux/amd64,linux/arm64'
+24 -31
View File
@@ -1,35 +1,32 @@
name: Docker image
on:
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Docker image Community
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
matrix:
os: [ubuntu-22.04]
os:
- ubuntu-22.04
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
dbgate/dbgate
images: dbgate/dbgate
flavor: |
latest=false
tags: |
@@ -37,7 +34,6 @@ jobs:
type=match,pattern=\d+.\d+.\d+,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
- name: Docker alpine meta
id: alpmeta
uses: docker/metadata-action@v4
@@ -51,56 +47,53 @@ jobs:
type=match,pattern=\d+.\d+.\d+,suffix=-alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
type=raw,value=alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: yarn adjustPackageJson
- name: adjustPackageJson
run: |
yarn adjustPackageJson
node adjustPackageJson --community
- name: yarn install
run: |
# yarn --version
# yarn config set network-timeout 300000
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: Prepare docker image
run: |
yarn run prepare:docker
yarn run prepare:docker
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
username: '${{ secrets.DOCKER_USERNAME }}'
password: '${{ secrets.DOCKER_PASSWORD }}'
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
context: ./docker
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: '${{ steps.meta.outputs.tags }}'
platforms: 'linux/amd64,linux/arm64,linux/arm/v7'
- name: Build and push alpine
uses: docker/build-push-action@v3
with:
push: true
context: ./docker
file: ./docker/Dockerfile-alpine
tags: ${{ steps.alpmeta.outputs.tags }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: '${{ steps.alpmeta.outputs.tags }}'
platforms: 'linux/amd64,linux/arm64,linux/arm/v7'
+11 -31
View File
@@ -1,31 +1,23 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: NPM packages PREMIUM
# on: [push]
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+'
# on:
# push:
# branches:
# - production
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
matrix:
os: [ubuntu-22.04]
os:
- ubuntu-22.04
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
@@ -34,14 +26,12 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Checkout dbgate/dbgate-pro
uses: actions/checkout@v2
with:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
token: '${{ secrets.GH_TOKEN }}'
path: dbgate-pro
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
@@ -53,65 +43,55 @@ jobs:
yarn
node sync.js --nowatch
cd ..
- name: adjustNpmPackageJsonPremium
run: |
cd ..
cd dbgate-merged
node adjustNpmPackageJsonPremium
- name: Configure NPM token
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: '${{ secrets.NPM_TOKEN }}'
run: |
cd ..
cd dbgate-merged
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
- name: Remove dbmodel - should be not published
run: |
cd ..
cd dbgate-merged
rm -rf packages/dbmodel
- name: yarn install
run: |
cd ..
cd dbgate-merged
yarn install
- name: setCurrentVersion
run: |
cd ..
cd dbgate-merged
yarn setCurrentVersion
- name: printSecrets
run: |
cd ..
cd dbgate-merged
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: Publish dbgate-api-premium
run: |
cd ..
cd dbgate-merged/packages/api
npm publish
- name: Publish dbgate-web-premium
run: |
cd ..
cd dbgate-merged/packages/web
npm publish
- name: Publish dbgate-serve-premium
run: |
cd ..
cd dbgate-merged/packages/serve
npm publish
- name: Publish dbgate-plugin-cosmosdb
run: |
cd ..
+11 -42
View File
@@ -1,31 +1,23 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: NPM packages
# on: [push]
on:
'on':
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+'
# on:
# push:
# branches:
# - production
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: '${{ matrix.os }}'
strategy:
matrix:
os: [ubuntu-22.04]
os:
- ubuntu-22.04
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_CONTEXT: '${{ toJson(github) }}'
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
with:
@@ -34,37 +26,30 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Configure NPM token
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: '${{ secrets.NPM_TOKEN }}'
run: |
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
- name: yarn install
run: |
yarn install
- name: setCurrentVersion
run: |
yarn setCurrentVersion
- name: printSecrets
run: |
yarn printSecrets
yarn printSecrets
env:
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
GIST_UPLOAD_SECRET: '${{secrets.GIST_UPLOAD_SECRET}}'
- name: Publish types
working-directory: packages/types
run: |
npm publish
- name: Publish tools
working-directory: packages/tools
run: |
npm publish
- name: Publish sqltree
working-directory: packages/sqltree
run: |
@@ -74,82 +59,66 @@ jobs:
working-directory: packages/api
run: |
npm publish
- name: Publish datalib
working-directory: packages/datalib
run: |
npm publish
- name: Publish filterparser
working-directory: packages/filterparser
run: |
npm publish
- name: Publish web
working-directory: packages/web
run: |
npm publish
- name: Publish dbgate-serve
working-directory: packages/serve
run: |
npm publish
- name: Publish dbmodel
working-directory: packages/dbmodel
run: |
npm publish
- name: Publish dbgate-plugin-csv
working-directory: plugins/dbgate-plugin-csv
run: |
npm publish
- name: Publish dbgate-plugin-xml
working-directory: plugins/dbgate-plugin-xml
run: |
npm publish
- name: Publish dbgate-plugin-excel
working-directory: plugins/dbgate-plugin-excel
run: |
npm publish
- name: Publish dbgate-plugin-mssql
working-directory: plugins/dbgate-plugin-mssql
run: |
npm publish
- name: Publish dbgate-plugin-mysql
working-directory: plugins/dbgate-plugin-mysql
run: |
npm publish
- name: Publish dbgate-plugin-mongo
working-directory: plugins/dbgate-plugin-mongo
run: |
npm publish
- name: Publish dbgate-plugin-postgres
working-directory: plugins/dbgate-plugin-postgres
run: |
npm publish
- name: Publish dbgate-plugin-sqlite
working-directory: plugins/dbgate-plugin-sqlite
run: |
npm publish
- name: Publish dbgate-plugin-redis
working-directory: plugins/dbgate-plugin-redis
run: |
npm publish
- name: Publish dbgate-plugin-oracle
working-directory: plugins/dbgate-plugin-oracle
run: |
npm publish
- name: Publish dbgate-plugin-clickhouse
working-directory: plugins/dbgate-plugin-clickhouse
run: |
-41
View File
@@ -1,41 +0,0 @@
name: E2E tests
on:
push:
branches:
- master
- develop
- 'feature/**'
jobs:
e2e-tests:
runs-on: ubuntu-latest
container: node:18
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: yarn install
run: |
yarn install
- name: Build
run: |
yarn prepare:packer
- name: yarn install cypress
run: |
cd e2e-tests
yarn install
- name: Run Cypress tests
run: |
cd e2e-tests
yarn test:ci
services:
mysql:
image: mysql:8.0.18
env:
MYSQL_ROOT_PASSWORD: Pwd2020Db
+32 -28
View File
@@ -1,32 +1,43 @@
# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
name: Run tests
on:
'on':
push:
branches:
- master
- develop
- 'feature/**'
- feature/**
jobs:
test-runner:
runs-on: ubuntu-latest
container: node:18
container: 'node:18'
steps:
- name: Context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Install dependencies for cypress
run: |
apt-get update
apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: yarn install
run: |
yarn install
- name: Build packer dist for cypress
run: |
yarn prepare:packer
- name: yarn install cypress
run: |
cd e2e-tests
yarn install
- name: Run Cypress tests
run: |
cd e2e-tests
yarn test:ci
- name: Integration tests
run: |
cd integration-tests
yarn test:ci
# yarn wait:ci
- name: Filter parser tests
if: always()
run: |
@@ -40,49 +51,42 @@ jobs:
- uses: tanmen/jest-reporter@v1
if: always()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: '${{ secrets.GITHUB_TOKEN }}'
result-file: integration-tests/result.json
action-name: Integration tests
- uses: tanmen/jest-reporter@v1
if: always()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: '${{ secrets.GITHUB_TOKEN }}'
result-file: packages/filterparser/result.json
action-name: Filter parser test results
- uses: tanmen/jest-reporter@v1
if: always()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: '${{ secrets.GITHUB_TOKEN }}'
result-file: packages/datalib/result.json
action-name: Datalib (perspectives) test results
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: Pwd2020Db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
options: '--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5'
mysql:
image: mysql:8.0.18
image: 'mysql:8.0.18'
env:
MYSQL_ROOT_PASSWORD: Pwd2020Db
mssql:
image: mcr.microsoft.com/mssql/server
env:
ACCEPT_EULA: Y
ACCEPT_EULA: 'Y'
SA_PASSWORD: Pwd2020Db
MSSQL_PID: Express
clickhouse:
image: bitnami/clickhouse:24.8.4
image: 'bitnami/clickhouse:24.8.4'
env:
CLICKHOUSE_ADMIN_PASSWORD: Pwd2020Db
# cockroachdb:
# image: cockroachdb/cockroach
oracle:
image: 'gvenzl/oracle-xe:21-slim'
env:
ORACLE_PASSWORD: Pwd2020Db
+23
View File
@@ -8,6 +8,29 @@ Builds:
- linux - application for linux
- win - application for Windows
### 6.1.1
- ADDED: Trigger support (SQL Server, PostgreSQL, MySQL, Oracle)
- FIXED: PostgreSQL and Oracle export #970
- FIXED: Cursor Becomes Stuck When Escaping "Case" #954
- CHANGED: Defualt search criteria for tables are names only
- FIXED: Search in packed list
### 6.1.0
- ADDED: Fulltext search in DB model and connections, highlight searched names
- ADDED: Tab preview mode configuration #963
- CHANGED: Single-click to open server connection/database + ability to configure this #959
- ADDED: Option to align numbers to right in data grid #957
- FIXED: Cursor Becomes Stuck When Escaping "Case" #954
- ADDED: Postgres GEOGRAPHY types are shown on map, event when executing query #948
- FIXED: Error displaying CLOB and NCLOB in Oracle
- FIXED: Analysing of foreign keys in Postgres and MS SQL, when the same FKS are used across different schemas
- ADDED: Support of views, procedures, functions to Oracle. Added integration tests for Oracle
- ADDED: Display "No rows" message, quick add new row
- ADDED: Choose default database from list
- ADDED: Default database is automatically selected on connect
- ADDED: Apple-Silicon-only build for Mac #949
- ADDED: Display comment into tables and column list #755
### 6.0.0
- ADDED: Order or filter the indexes for huge tables #922
- ADDED: Empty string filters
+2 -2
View File
@@ -17,7 +17,7 @@ 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/)
* 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.html) ([API documentation](https://dbgate.org/docs/apidoc.html))
* Use nodeJs [scripting interface](https://dbgate.org/docs/scripting) ([API documentation](https://dbgate.org/docs/apidoc))
* [Recommend DbGate](https://testimonial.to/dbgate) | [Rate on G2](https://www.g2.com/products/dbgate/reviews)
## Supported databases
@@ -87,7 +87,7 @@ Any contributions are welcome. If you want to contribute without coding, conside
* 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.html). Plugins for new themes can be created actually without JS coding
* 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
Thank you!
+23
View File
@@ -41,6 +41,29 @@ function adjustFile(file, isApp = false) {
delete json.optionalDependencies.msnodesqlv8;
}
if (process.argv.includes('--community')) {
delete json.optionalDependencies['mongodb-client-encryption'];
}
if (isApp && process.argv.includes('--premium')) {
json.build.win.target = [
{
target: 'nsis',
arch: ['x64'],
},
];
json.build.linux.target = [
{
target: 'AppImage',
arch: ['x64'],
},
];
json.name = 'dbgate-premium';
json.build.artifactName = 'dbgate-premium-${version}-${os}_${arch}.${ext}';
json.build.appId = 'org.dbgate.premium';
json.build.productName = 'DbGate Premium';
}
fs.writeFileSync(file, JSON.stringify(json, null, 2), 'utf-8');
}
+5 -5
View File
@@ -19,7 +19,6 @@
"artifactName": "dbgate-${version}-${os}_${arch}.${ext}",
"appId": "org.dbgate",
"productName": "DbGate",
"afterSign": "electron-builder-notarize",
"asarUnpack": "**/*.node",
"mac": {
"category": "database",
@@ -38,9 +37,11 @@
"target": "default",
"arch": [
"universal",
"x64"
"x64",
"arm64"
]
}
},
"notarize": true
},
"linux": {
"target": [
@@ -129,7 +130,6 @@
"copyfiles": "^2.2.0",
"cross-env": "^6.0.3",
"electron": "30.0.2",
"electron-builder": "23.1.0",
"electron-builder-notarize": "^1.5.2"
"electron-builder": "25.1.8"
}
}
+828 -1022
View File
File diff suppressed because it is too large Load Diff
+174
View File
@@ -0,0 +1,174 @@
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const _ = require('lodash');
const indir = path.resolve(path.join(__dirname, '..', 'workflow-templates'));
const outdir = path.resolve(path.join(__dirname, '..', '.github', 'workflows'));
const includes = {};
const HEADER = `# --------------------------------------------------------------------------------------------
# This file is generated. Do not edit manually
# --------------------------------------------------------------------------------------------
`;
function readIncludes() {
for (const file of fs.readdirSync(indir)) {
const text = fs.readFileSync(path.join(indir, file), { encoding: 'utf-8' });
const json = yaml.load(text);
if (json._module) {
for (const key in json) {
if (key === '_module') {
continue;
}
includes[key] = json[key];
}
}
}
}
let modified = false;
function conditionMatch(condition, args) {
if (_.isString(condition)) {
return args.defs.includes(condition);
}
return false;
}
function processJsonStep(json, args) {
return _.cloneDeepWith(json, value => {
if (_.isArray(value)) {
const res = [];
let arrayModified = false;
for (const item of value) {
if (item._if) {
modified = true;
arrayModified = true;
if (conditionMatch(item._if, args)) {
res.push(_.omit(item, ['_if']));
}
} else if (item._replace || item._include) {
const replaceWith = item._replace ? args.replace?.[item._replace] : includes[item._include];
if (replaceWith) {
modified = true;
arrayModified = true;
if (_.isArray(replaceWith)) {
res.push(...replaceWith);
} else {
res.push(replaceWith);
}
} else {
res.push(item);
}
} else {
res.push(item);
}
}
if (arrayModified) {
return res;
}
return undefined;
}
if (_.isPlainObject(value)) {
if (_.intersection(args.allDefs ?? [], Object.keys(value))?.length > 0) {
modified = true;
for (const key in value) {
if (args.defs.includes(key)) {
return value[key];
}
}
return undefined;
}
}
if (_.isString(value)) {
let stringModified = false;
for (const key of Object.keys(args.stringReplace ?? {})) {
if (value.includes(key)) {
modified = true;
stringModified = true;
value = value.replaceAll(key, args.stringReplace[key]);
}
}
if (stringModified) {
return value;
}
return undefined;
}
if (value?._include) {
modified = true;
return includes[value?._include];
}
if (value?._replace) {
modified = true;
return args?.replace[value?._replace];
}
});
}
function processJson(json, args = {}) {
const MAX_STEPS = 64;
for (let i = 0; i < MAX_STEPS; i++) {
modified = false;
json = processJsonStep(json, args);
if (!modified) {
break;
}
}
return json;
}
function processFiles() {
const dumpOptions = {
lineWidth: -1,
};
for (const file of fs.readdirSync(indir)) {
const text = fs.readFileSync(path.join(indir, file), { encoding: 'utf-8' });
const json = yaml.load(text);
if (json._module) {
continue;
}
if (json._templates) {
const allDefs = Object.keys(json._templates);
for (const key in json._templates) {
allDefs.push(...(json._templates[key].defs ?? []));
}
for (const key in json._templates) {
const args = {
defs: [key, ...(json._templates[key]?.defs ?? [])],
replace: json._templates[key]?.replace,
stringReplace: json._templates[key]?.['string-replace'],
allDefs,
};
const converted = processJson(_.omit(json, ['_templates']), args);
const out = path.join(outdir, json._templates[key].file);
fs.writeFileSync(out, HEADER + yaml.dump(converted, dumpOptions));
}
} else {
fs.writeFileSync(path.join(outdir, file), HEADER + yaml.dump(processJson(json), dumpOptions));
}
}
}
function deleteOldFiles() {
const files = fs.readdirSync(outdir);
for (const file of files) {
fs.unlinkSync(path.join(outdir, file));
}
}
function run() {
deleteOldFiles();
readIncludes();
processFiles();
}
run();
+1
View File
@@ -19,6 +19,7 @@ const volatilePackages = [
'activedirectory2',
'axios',
'ssh2',
'wkx',
];
module.exports = volatilePackages;
+2 -2
View File
@@ -1,6 +1,6 @@
const { defineConfig } = require('cypress');
const killPort = require('kill-port');
const { clearDataWithBackup } = require('./e2eTestTools');
const { clearTestingData } = require('./e2eTestTools');
const waitOn = require('wait-on');
const { exec } = require('child_process');
@@ -10,7 +10,7 @@ module.exports = defineConfig({
// implement node event listeners here
on('before:spec', async details => {
await clearDataWithBackup();
await clearTestingData();
if (config.isInteractive) {
await killPort(3000);
+40 -1
View File
@@ -23,9 +23,48 @@ describe('Initialization', () => {
cy.contains('performance_schema');
});
// it('SSH connection', () => {
// const runOnCI = Cypress.env('runOnCI');
// cy.get('body')
// .trigger('keydown', {
// key: 'F1',
// code: 'F1',
// which: 112,
// keyCode: 112,
// bubbles: true,
// })
// .trigger('keyup', {
// key: 'F1',
// code: 'F1',
// which: 112,
// keyCode: 112,
// bubbles: true,
// });
// cy.get('body').type('Close all');
// cy.get('body').type('{enter}');
// cy.visit('http://localhost:3000');
// cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
// cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');
// cy.get('[data-testid=ConnectionDriverFields_user]').clear().type('root');
// cy.get('[data-testid=ConnectionDriverFields_password]').clear().type('root');
// cy.get('[data-testid=ConnectionSshTunnelFields_sshLogin]').clear().type('root');
// cy.get('[data-testid=ConnectionSshTunnelFields_sshPassword]').clear().type('root');
// if (runOnCI) {
// cy.get('[data-testid=ConnectionSshTunnelFields_sshHost]').clear().type('mysql-ssh');
// } else {
// cy.get('[data-testid=ConnectionSshTunnelFields_sshPort]').clear().type('16006');
// }
// cy.get('[data-testid=ConnectionDriverFields_displayName]').clear().type('test-mysql-ssh-1');
// cy.get('[data-testid=ConnectionTab_buttonSave]').click();
// cy.get('[data-testid=ConnectionTab_buttonConnect]').click();
// cy.contains('performance_schema');
// });
// it('import chinook DB', () => {
// cy.visit('http://localhost:3000');
// cy.get('[data-testid=ConnectionTab_buttonConnect]').click();
// });
});
+7
View File
@@ -16,3 +16,10 @@ services:
- 16004:3306
environment:
- MYSQL_ROOT_PASSWORD=Pwd2020Db
mysql-ssh:
build: mysql-ssh
restart: always
ports:
- 16005:3306
- 16006:22
+16 -19
View File
@@ -4,29 +4,26 @@ const fs = require('fs');
const baseDir = path.join(os.homedir(), '.dbgate');
function createTimeStamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // měsíc je 0-indexovaný
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
// function createTimeStamp() {
// const now = new Date();
// const year = now.getFullYear();
// const month = String(now.getMonth() + 1).padStart(2, '0'); // měsíc je 0-indexovaný
// const day = String(now.getDate()).padStart(2, '0');
// const hours = String(now.getHours()).padStart(2, '0');
// const minutes = String(now.getMinutes()).padStart(2, '0');
// const seconds = String(now.getSeconds()).padStart(2, '0');
// Poskládáme datum a čas do názvu souboru
const ts = `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
return ts;
}
// // Poskládáme datum a čas do názvu souboru
// const ts = `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
// return ts;
// }
function clearDataWithBackup() {
if (fs.existsSync(path.join(baseDir, 'connections.jsonl'))) {
fs.renameSync(
path.join(baseDir, 'connections.jsonl'),
path.join(baseDir, `connections-${createTimeStamp()}.jsonl.bak`)
);
function clearTestingData() {
if (fs.existsSync(path.join(baseDir, 'connections-e2etests.jsonl'))) {
fs.unlinkSync(path.join(baseDir, 'connections-e2etests.jsonl'));
}
}
module.exports = {
clearDataWithBackup,
clearTestingData,
};
+22
View File
@@ -0,0 +1,22 @@
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y openssh-server mysql-server && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN mkdir /var/run/sshd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN echo 'root:root' | chpasswd
RUN service mysql start && \
mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;" && \
service mysql stop
EXPOSE 22 3306
COPY start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
service ssh start
service mysql start
tail -f /dev/null
@@ -3,9 +3,15 @@ const _ = require('lodash');
const fp = require('lodash/fp');
const { testWrapper } = require('../tools');
const engines = require('../engines');
const { getAlterDatabaseScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
const {
getAlterDatabaseScript,
extendDatabaseInfo,
generateDbPairingId,
formatQueryWithoutParams,
runCommandOnDriver,
} = require('dbgate-tools');
const initSql = ['CREATE TABLE t1 (id int primary key)', 'CREATE TABLE t2 (id int primary key)'];
const initSql = ['CREATE TABLE ~t1 (~id int primary key)', 'CREATE TABLE ~t2 (~id int primary key)'];
function flatSource(engineCond = x => !x.skipReferences) {
return _.flatten(
@@ -16,13 +22,14 @@ function flatSource(engineCond = x => !x.skipReferences) {
}
async function testDatabaseDiff(conn, driver, mangle, createObject = null) {
await driver.query(conn, `create table t1 (id int not null primary key)`);
await runCommandOnDriver(conn, driver, `create table ~t1 (~id int not null primary key)`);
await driver.query(
await runCommandOnDriver(
conn,
`create table t2 (
id int not null primary key,
t1_id int null references t1(id)
driver,
`create table ~t2 (
~id int not null primary key,
~t1_id int null references ~t1(~id)
)`
);
@@ -63,7 +70,7 @@ describe('Alter database', () => {
db => {
_.remove(db[type], x => x.pureName == 'obj1');
},
object.create1
formatQueryWithoutParams(driver, object.create1)
);
expect(db[type].length).toEqual(0);
})
@@ -72,9 +79,9 @@ describe('Alter database', () => {
test.each(flatSource(x => x.supportRenameSqlObject))(
'Rename object - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create1);
const structure = extendDatabaseInfo(await driver.analyseFull(conn));
+30 -14
View File
@@ -4,7 +4,12 @@ const fp = require('lodash/fp');
const { testWrapper } = require('../tools');
const engines = require('../engines');
const crypto = require('crypto');
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
const {
getAlterTableScript,
extendDatabaseInfo,
generateDbPairingId,
formatQueryWithoutParams,
} = require('dbgate-tools');
function pickImportantTableInfo(engine, table) {
const props = ['columnName', 'defaultValue'];
@@ -15,7 +20,10 @@ function pickImportantTableInfo(engine, table) {
columns: table.columns
.filter(x => x.columnName != 'rowid')
.map(fp.pick(props))
.map(props => _.omitBy(props, x => x == null)),
.map(props => _.omitBy(props, x => x == null))
.map(props =>
_.omitBy(props, (v, k) => k == 'defaultValue' && v == 'NULL' && engine.setNullDefaultInsteadOfDrop)
),
};
}
@@ -25,27 +33,36 @@ function checkTableStructure(engine, t1, t2) {
}
async function testTableDiff(engine, conn, driver, mangle) {
await driver.query(conn, `create table t0 (id int not null primary key)`);
await driver.query(conn, formatQueryWithoutParams(driver, `create table ~t0 (~id int not null primary key)`));
await driver.query(
conn,
`create table t1 (
col_pk int not null primary key,
col_std int,
col_def int default 12,
${engine.skipReferences ? '' : 'col_fk int references t0(id),'}
col_idx int,
col_uq int ${engine.skipUnique ? '' : 'unique'} ,
col_ref int ${engine.skipUnique ? '' : 'unique'}
formatQueryWithoutParams(
driver,
`create table ~t1 (
~col_pk int not null primary key,
~col_std int,
~col_def int default 12,
${engine.skipReferences ? '' : '~col_fk int references ~t0(~id),'}
~col_idx int,
~col_uq int ${engine.skipUnique ? '' : 'unique'} ,
~col_ref int ${engine.skipUnique ? '' : 'unique'}
)`
)
);
if (!engine.skipIndexes) {
await driver.query(conn, `create index idx1 on t1(col_idx)`);
await driver.query(conn, formatQueryWithoutParams(driver, `create index ~idx1 on ~t1(~col_idx)`));
}
if (!engine.skipReferences) {
await driver.query(conn, `create table t2 (id int not null primary key, fkval int null references t1(col_ref))`);
await driver.query(
conn,
formatQueryWithoutParams(
driver,
`create table ~t2 (~id int not null primary key, ~fkval int null references ~t1(~col_ref))`
)
);
}
const tget = x => x.tables.find(y => y.pureName == 't1');
@@ -175,5 +192,4 @@ describe('Alter table', () => {
// });
// })
// );
});
@@ -2,7 +2,7 @@ const engines = require('../engines');
const stream = require('stream');
const { testWrapper } = require('../tools');
const dataDuplicator = require('dbgate-api/src/shell/dataDuplicator');
const { runCommandOnDriver } = require('dbgate-tools');
const { runCommandOnDriver, runQueryOnDriver } = require('dbgate-tools');
describe('Data duplicator', () => {
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
@@ -84,10 +84,10 @@ describe('Data duplicator', () => {
],
});
const res1 = await driver.query(conn, `select count(*) as cnt from t1`);
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 driver.query(conn, `select count(*) as cnt from t2`);
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
@@ -145,13 +145,15 @@ describe('Data duplicator', () => {
},
});
const res1 = await driver.query(conn, `select count(*) as cnt from t1`);
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 driver.query(conn, `select count(*) as cnt from t2`);
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 driver.query(conn, `select count(*) as cnt from t2 where valfk is not null`);
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');
})
);
@@ -2,9 +2,11 @@ const engines = require('../engines');
const stream = require('stream');
const { testWrapper } = require('../tools');
const tableWriter = require('dbgate-api/src/shell/tableWriter');
const tableReader = require('dbgate-api/src/shell/tableReader');
const copyStream = require('dbgate-api/src/shell/copyStream');
const importDatabase = require('dbgate-api/src/shell/importDatabase');
const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader');
const { runQueryOnDriver, runCommandOnDriver } = require('dbgate-tools');
function createImportStream() {
const pass = new stream.PassThrough({
@@ -22,7 +24,17 @@ function createImportStream() {
return pass;
}
describe('DB Import', () => {
function createExportStream() {
const writable = new stream.Writable({ objectMode: true });
writable.resultArray = [];
writable._write = (chunk, encoding, callback) => {
writable.resultArray.push(chunk);
callback();
};
return writable;
}
describe('DB Import/export', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Import one table - %s',
testWrapper(async (conn, driver, engine) => {
@@ -37,7 +49,7 @@ describe('DB Import', () => {
});
await copyStream(reader, writer);
const res = await driver.query(conn, `select count(*) as cnt from t1`);
const res = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t1`));
expect(res.rows[0].cnt.toString()).toEqual('6');
})
);
@@ -65,10 +77,10 @@ describe('DB Import', () => {
});
await copyStream(reader2, writer2);
const res1 = await driver.query(conn, `select count(*) as cnt from t1`);
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 driver.query(conn, `select count(*) as cnt from t2`);
const res2 = await runQueryOnDriver(conn, driver, dmp => dmp.put(`select count(*) as ~cnt from ~t2`));
expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
@@ -98,4 +110,35 @@ describe('DB Import', () => {
// expect(res2.rows[0].cnt.toString()).toEqual('6');
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Export one table - %s',
testWrapper(async (conn, driver, engine) => {
// const reader = await fakeObjectReader({ delay: 10 });
// const reader = await fakeObjectReader();
await runCommandOnDriver(conn, driver, 'create table ~t1 (~id int primary key, ~country varchar(100))');
const data = [
[1, 'Czechia'],
[2, 'Austria'],
[3, 'Germany'],
[4, 'Romania'],
[5, 'Great Britain'],
[6, 'Bosna, Hecegovina'],
];
for (const row of data) {
await runCommandOnDriver(conn, driver, dmp =>
dmp.put('insert into ~t1(~id, ~country) values (%v, %v)', ...row)
);
}
const reader = await tableReader({
systemConnection: conn,
driver,
pureName: 't1',
});
const writer = createExportStream();
await copyStream(reader, writer);
expect(writer.resultArray.filter(x => !x.__isStreamHeader).map(row => [row.id, row.country])).toEqual(data);
})
);
});
@@ -4,7 +4,7 @@ const { testWrapper, testWrapperPrepareOnly } = require('../tools');
const _ = require('lodash');
const engines = require('../engines');
const deployDb = require('dbgate-api/src/shell/deployDb');
const { databaseInfoFromYamlModel } = require('dbgate-tools');
const { databaseInfoFromYamlModel, runQueryOnDriver, formatQueryWithoutParams } = require('dbgate-tools');
const generateDeploySql = require('dbgate-api/src/shell/generateDeploySql');
const connectUtility = require('dbgate-api/src/utility/connectUtility');
@@ -69,6 +69,29 @@ function checkStructure(
}
}
// function convertObjectText(text, driver) {
// if (!text) return undefined;
// text = formatQueryWithoutParams(driver, text);
// if (driver.dialect.requireFromDual && text.startsWith('create view ') && !text.includes('from')) {
// text = text + ' from dual';
// }
// return text;
// }
// function convertModelToEngine(model, driver) {
// return model.map(x => ({
// ...x,
// text: convertObjectText(x.text, driver),
// }));
// }
function convertModelToEngine(model, driver) {
return model.map(x => ({
...x,
text: x.text ? formatQueryWithoutParams(driver, x.text) : undefined,
}));
}
async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
const { testEmptyLastScript, finalCheckAgainstModel, markDeleted, allowDropStatements } = options || {};
let index = 0;
@@ -83,13 +106,13 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
for (const loadedDbModel of dbModelsYaml) {
if (_.isString(loadedDbModel)) {
await driver.script(conn, loadedDbModel);
await driver.script(conn, formatQueryWithoutParams(driver, loadedDbModel));
} else {
const { sql, isEmpty } = await generateDeploySql({
systemConnection: conn.isPreparedOnly ? undefined : conn,
connection: conn.isPreparedOnly ? conn : undefined,
driver,
loadedDbModel,
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
dbdiffOptionsExtra,
});
console.debug('Generated deploy script:', sql);
@@ -106,7 +129,7 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
systemConnection: conn.isPreparedOnly ? undefined : conn,
connection: conn.isPreparedOnly ? conn : undefined,
driver,
loadedDbModel,
loadedDbModel: convertModelToEngine(loadedDbModel, driver),
dbdiffOptionsExtra,
});
}
@@ -117,7 +140,12 @@ async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
const dbhan = conn.isPreparedOnly ? await connectUtility(driver, conn, 'read') : conn;
const structure = await driver.analyseFull(dbhan);
if (conn.isPreparedOnly) await driver.close(dbhan);
checkStructure(engine, structure, finalCheckAgainstModel ?? _.findLast(dbModelsYaml, x => _.isArray(x)), options);
checkStructure(
engine,
structure,
convertModelToEngine(finalCheckAgainstModel ?? _.findLast(dbModelsYaml, x => _.isArray(x)), driver),
options
);
}
describe('Deploy database', () => {
@@ -339,7 +367,7 @@ describe('Deploy database', () => {
],
]);
const res = await driver.query(conn, `select count(*) as cnt from t1`);
const res = await runQueryOnDriver(conn, driver, `select count(*) as ~cnt from ~t1`);
expect(res.rows[0].cnt.toString()).toEqual('3');
})
);
@@ -386,12 +414,12 @@ describe('Deploy database', () => {
],
]);
const res = await driver.query(conn, `select val from t1 where id = 2`);
const res = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 2`);
expect(res.rows[0].val.toString()).toEqual('5');
})
);
test.each(engines.enginesPostgre.map(engine => [engine.label, engine]))(
test.each([engines.postgreSqlEngine].map(engine => [engine.label, engine]))(
'Current timestamp default value - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(engine, conn, driver, [
@@ -414,8 +442,8 @@ describe('Deploy database', () => {
],
]);
await driver.query(conn, `insert into t1 (id) values (1)`);
const res = await driver.query(conn, ` select val from t1 where id = 1`);
await runQueryOnDriver(conn, driver, `insert into ~t1 (~id) values (1)`);
const res = await runQueryOnDriver(conn, driver, ` select ~val from ~t1 where ~id = 1`);
expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20');
})
);
@@ -438,7 +466,7 @@ describe('Deploy database', () => {
},
},
],
'insert into t1 (id, val) values (1, 1); insert into t1 (id) values (2)',
'insert into ~t1 (~id, ~val) values (1, 1); insert into ~t1 (~id) values (2)',
[
{
name: 't1.table.yaml',
@@ -452,16 +480,16 @@ describe('Deploy database', () => {
},
},
],
'insert into t1 (id) values (3);',
'insert into ~t1 (~id) values (3);',
]);
const res1 = await driver.query(conn, `select val from t1 where id = 1`);
const res1 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 1`);
expect(res1.rows[0].val).toEqual(1);
const res2 = await driver.query(conn, `select val from t1 where id = 2`);
const res2 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 2`);
expect(res2.rows[0].val).toEqual(20);
const res3 = await driver.query(conn, `select val from t1 where id = 3`);
const res3 = await runQueryOnDriver(conn, driver, `select ~val from ~t1 where ~id = 3`);
expect(res2.rows[0].val).toEqual(20);
})
);
@@ -525,17 +553,17 @@ describe('Deploy database', () => {
const V1 = {
name: 'v1.view.sql',
text: 'create view v1 as select * from t1',
text: 'create view ~v1 as select * from ~t1',
};
const V1_VARIANT2 = {
name: 'v1.view.sql',
text: 'create view v1 as select 1 as c1',
text: 'create view ~v1 as select ~id + ~id ~idsum from ~t1',
};
const V1_DELETED = {
name: '_deleted_v1.view.sql',
text: 'create view _deleted_v1 as select * from t1',
text: 'create view ~_deleted_v1 as select * from ~t1',
};
test.each(engines.map(engine => [engine.label, engine]))(
@@ -682,15 +710,15 @@ describe('Deploy database', () => {
[
{
name: '1.predeploy.sql',
text: 'create table t1 (id int primary key); insert into t1 (id) values (1);',
text: 'create table ~t1 (~id int primary key); insert into ~t1 (~id) values (1);',
},
],
]);
const res1 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t1');
const res1 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~t1');
expect(res1.rows[0].cnt == 1).toBeTruthy();
const res2 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM dbgate_deploy_journal');
const res2 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~dbgate_deploy_journal');
expect(res2.rows[0].cnt == 1).toBeTruthy();
})
);
@@ -702,48 +730,53 @@ describe('Deploy database', () => {
[
{
name: 't1.uninstall.sql',
text: 'drop table t1',
text: 'drop table ~t1',
},
{
name: 't1.install.sql',
text: 'create table t1 (id int primary key); insert into t1 (id) values (1)',
text: 'create table ~t1 (~id int primary key); insert into ~t1 (~id) values (1)',
},
{
name: 't2.once.sql',
text: 'create table t2 (id int primary key); insert into t2 (id) values (1)',
text: 'create table ~t2 (~id int primary key); insert into ~t2 (~id) values (1)',
},
],
[
{
name: 't1.uninstall.sql',
text: 'drop table t1',
text: 'drop table ~t1',
},
{
name: 't1.install.sql',
text: 'create table t1 (id int primary key, val int); insert into t1 (id, val) values (1, 11)',
text: 'create table ~t1 (~id int primary key, ~val int); insert into ~t1 (~id, ~val) values (1, 11)',
},
{
name: 't2.once.sql',
text: 'insert into t2 (id) values (2)',
text: 'insert into ~t2 (~id) values (2)',
},
],
]);
const res1 = await driver.query(conn, 'SELECT val from t1 where id = 1');
const res1 = await runQueryOnDriver(conn, driver, 'SELECT ~val from ~t1 where ~id = 1');
expect(res1.rows[0].val == 11).toBeTruthy();
const res2 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t2');
const res2 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~t2');
expect(res2.rows[0].cnt == 1).toBeTruthy();
const res3 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM dbgate_deploy_journal');
const res3 = await runQueryOnDriver(conn, driver, 'SELECT COUNT(*) AS ~cnt FROM ~dbgate_deploy_journal');
expect(res3.rows[0].cnt == 3).toBeTruthy();
const res4 = await driver.query(conn, "SELECT run_count from dbgate_deploy_journal where name = 't2.once.sql'");
const res4 = await runQueryOnDriver(
conn,
driver,
"SELECT ~run_count from ~dbgate_deploy_journal where ~name = 't2.once.sql'"
);
expect(res4.rows[0].run_count == 1).toBeTruthy();
const res5 = await driver.query(
const res5 = await runQueryOnDriver(
conn,
"SELECT run_count from dbgate_deploy_journal where name = 't1.install.sql'"
driver,
"SELECT ~run_count from ~dbgate_deploy_journal where ~name = 't1.install.sql'"
);
expect(res5.rows[0].run_count == 2).toBeTruthy();
})
@@ -1,8 +1,9 @@
const { testWrapper } = require('../tools');
const engines = require('../engines');
const _ = require('lodash');
const { formatQueryWithoutParams, runCommandOnDriver } = require('dbgate-tools');
const initSql = ['CREATE TABLE t1 (id int primary key)', 'CREATE TABLE t2 (id int primary key)'];
const initSql = ['CREATE TABLE ~t1 (~id int primary key)', 'CREATE TABLE ~t2 (~id int primary key)'];
function flatSource() {
return _.flatten(
@@ -18,6 +19,10 @@ function flatSourceParameters() {
);
}
function flatSourceTriggers() {
return _.flatten(engines.map(engine => (engine.triggers || []).map(trigger => [engine.label, trigger, engine])));
}
const obj1Match = expect.objectContaining({
pureName: 'obj1',
});
@@ -34,9 +39,9 @@ describe('Object analyse', () => {
test.each(flatSource())(
'Full analysis - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create1);
const structure = await driver.analyseFull(conn);
expect(structure[type].length).toEqual(1);
@@ -47,11 +52,11 @@ describe('Object analyse', () => {
test.each(flatSource())(
'Incremental analysis - add - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create2, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create2);
const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.create1, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create1);
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(2);
@@ -62,12 +67,12 @@ describe('Object analyse', () => {
test.each(flatSource())(
'Incremental analysis - drop - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true });
await driver.query(conn, object.create2, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create1);
await runCommandOnDriver(conn, driver, object.create2);
const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.drop2, { discardResult: true });
await runCommandOnDriver(conn, driver, object.drop2);
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(1);
@@ -78,11 +83,11 @@ describe('Object analyse', () => {
test.each(flatSource())(
'Create SQL - add - %s - %s',
testWrapper(async (conn, driver, type, object, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
await driver.query(conn, object.create1, { discardResult: true });
await runCommandOnDriver(conn, driver, object.create1);
const structure1 = await driver.analyseFull(conn);
await driver.query(conn, object.drop1, { discardResult: true });
await runCommandOnDriver(conn, driver, object.drop1);
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2[type].length).toEqual(0);
@@ -95,44 +100,78 @@ describe('Object analyse', () => {
})
);
test.each(flatSourceParameters())(
'Test parameters simple analyse - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
const flatParameters = flatSourceParameters();
await driver.query(conn, parameter.create, { discardResult: true });
if (flatParameters.length > 0) {
test.each(flatParameters)(
'Test parameters simple analyse - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
for (const sql of engine.parametersOtherSql) await runCommandOnDriver(conn, driver, sql);
await runCommandOnDriver(conn, driver, parameter.create);
const structure = await driver.analyseFull(conn);
const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
expect(parameters.length).toEqual(parameter.list.length);
for (let i = 0; i < parameters.length; i += 1) {
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
}
})
);
test.each(flatParameters)(
'Test parameters create SQL - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
for (const sql of engine.parametersOtherSql) await runCommandOnDriver(conn, driver, sql);
await runCommandOnDriver(conn, driver, parameter.create);
const structure1 = await driver.analyseFull(conn);
await runCommandOnDriver(conn, driver, parameter.drop);
const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1');
await driver.script(conn, obj.createSql, { discardResult: true });
const structure2 = await driver.analyseFull(conn);
const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
expect(parameters.length).toEqual(parameter.list.length);
for (let i = 0; i < parameters.length; i += 1) {
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
}
})
);
}
test.each(flatSourceTriggers())(
'Test triggers - %s - %s',
testWrapper(async (conn, driver, trigger) => {
for (const sql of initSql) await runCommandOnDriver(conn, driver, sql);
const { triggerOtherDropSql, triggerOtherCreateSql, create, drop, expected, objectTypeField } = trigger;
if (triggerOtherCreateSql) await runCommandOnDriver(conn, driver, triggerOtherCreateSql);
await runCommandOnDriver(conn, driver, create);
const structure = await driver.analyseFull(conn);
await runCommandOnDriver(conn, driver, drop);
const parameters = structure[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
if (triggerOtherDropSql) await runCommandOnDriver(conn, driver, triggerOtherDropSql);
expect(parameters.length).toEqual(parameter.list.length);
for (let i = 0; i < parameters.length; i += 1) {
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
}
})
);
const createdTrigger = structure[objectTypeField].find(x => x.pureName == expected.pureName);
expect(createdTrigger).toEqual(expect.objectContaining(expected));
test.each(flatSourceParameters())(
'Test parameters create SQL - %s - %s',
testWrapper(async (conn, driver, testName, parameter, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of engine.parametersOtherSql) await driver.query(conn, sql, { discardResult: true });
await driver.query(conn, parameter.create, { discardResult: true });
const structure1 = await driver.analyseFull(conn);
await driver.query(conn, parameter.drop, { discardResult: true });
const obj = structure1[parameter.objectTypeField].find(x => x.pureName == 'obj1');
await driver.script(conn, obj.createSql);
// test trigger createSql
if (triggerOtherCreateSql) await runCommandOnDriver(conn, driver, triggerOtherCreateSql);
await driver.script(conn, createdTrigger.createSql);
const structure2 = await driver.analyseFull(conn);
const parameters = structure2[parameter.objectTypeField].find(x => x.pureName == 'obj1').parameters;
expect(parameters.length).toEqual(parameter.list.length);
for (let i = 0; i < parameters.length; i += 1) {
expect(parameters[i]).toEqual(expect.objectContaining(parameter.list[i]));
}
const createdTrigger2 = structure2[objectTypeField].find(x => x.pureName == expected.pureName);
expect(createdTrigger2).toEqual(expect.objectContaining(expected));
})
);
});
console.log(flatSourceTriggers());
+65 -15
View File
@@ -1,11 +1,12 @@
const engines = require('../engines');
const { splitQuery } = require('dbgate-query-splitter');
const { testWrapper } = require('../tools');
const { runQueryOnDriver, runCommandOnDriver, formatQueryWithoutParams } = require('dbgate-tools');
const initSql = [
'CREATE TABLE t1 (id int primary key)',
'INSERT INTO t1 (id) VALUES (1)',
'INSERT INTO t1 (id) VALUES (2)',
'CREATE TABLE ~t1 (~id int primary key)',
'INSERT INTO ~t1 (~id) VALUES (1)',
'INSERT INTO ~t1 (~id) VALUES (2)',
];
expect.extend({
@@ -51,7 +52,7 @@ class StreamHandler {
function executeStreamItem(driver, conn, sql) {
return new Promise(resolve => {
const handler = new StreamHandler(resolve);
driver.stream(conn, sql, handler);
driver.stream(conn, formatQueryWithoutParams(driver, sql), handler);
});
}
@@ -68,9 +69,11 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Simple query - %s',
testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const res = await driver.query(conn, 'SELECT id FROM t1 ORDER BY id');
const res = await runQueryOnDriver(conn, driver, dmp => dmp.put('SELECT ~id FROM ~t1 ORDER BY ~id'));
expect(res.columns).toEqual([
expect.objectContaining({
columnName: 'id',
@@ -91,8 +94,11 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Simple stream query - %s',
testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
const results = await executeStream(driver, conn, 'SELECT id FROM t1 ORDER BY id');
for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const results = await executeStream(driver, conn, 'SELECT ~id FROM ~t1 ORDER BY ~id');
expect(results.length).toEqual(1);
const res = results[0];
@@ -104,11 +110,14 @@ describe('Query', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'More queries - %s',
testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
const results = await executeStream(
driver,
conn,
'SELECT id FROM t1 ORDER BY id; SELECT id FROM t1 ORDER BY id DESC'
'SELECT ~id FROM ~t1 ORDER BY ~id; SELECT ~id FROM ~t1 ORDER BY ~id DESC'
);
expect(results.length).toEqual(2);
@@ -128,7 +137,7 @@ describe('Query', () => {
const results = await executeStream(
driver,
conn,
'CREATE TABLE t1 (id int primary key); INSERT INTO t1 (id) VALUES (1); INSERT INTO t1 (id) VALUES (2); SELECT id FROM t1 ORDER BY id; '
'CREATE TABLE ~t1 (~id int primary key); INSERT INTO ~t1 (~id) VALUES (1); INSERT INTO ~t1 (~id) VALUES (2); SELECT ~id FROM ~t1 ORDER BY ~id; '
);
expect(results.length).toEqual(1);
@@ -144,7 +153,7 @@ describe('Query', () => {
const results = await executeStream(
driver,
conn,
'CREATE TABLE t1 (id int); INSERT INTO t1 (id) VALUES (1); INSERT INTO t1 (id) VALUES (2) '
'CREATE TABLE ~t1 (~id int); INSERT INTO ~t1 (~id) VALUES (1); INSERT INTO ~t1 (~id) VALUES (2) '
);
expect(results.length).toEqual(0);
})
@@ -153,16 +162,57 @@ describe('Query', () => {
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
'Save data query - %s',
testWrapper(async (conn, driver, engine) => {
for (const sql of initSql) await driver.query(conn, sql, { discardResult: true });
for (const sql of initSql) {
await runCommandOnDriver(conn, driver, dmp => dmp.put(sql));
}
await driver.script(
conn,
'INSERT INTO t1 (id) VALUES (3);INSERT INTO t1 (id) VALUES (4);UPDATE t1 SET id=10 WHERE id=1;DELETE FROM t1 WHERE id=2;',
formatQueryWithoutParams(
driver,
'INSERT INTO ~t1 (~id) VALUES (3);INSERT INTO ~t1 (~id) VALUES (4);UPDATE ~t1 SET ~id=10 WHERE ~id=1;DELETE FROM ~t1 WHERE ~id=2;'
),
{ discardResult: true }
);
const res = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t1');
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();
})
);
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
'Select scope identity - %s',
testWrapper(async (conn, driver, engine) => {
await runCommandOnDriver(conn, driver, dmp =>
dmp.createTable({
pureName: 't1',
columns: [
{ columnName: 'id', dataType: 'int', notNull: true, autoIncrement: true },
{ columnName: 'val', dataType: 'varchar(50)' },
],
primaryKey: {
columns: [{ columnName: 'id' }],
},
})
);
const structure = await driver.analyseFull(conn);
const table = structure.tables.find(x => x.pureName == 't1');
let res;
if (driver.dialect.requireStandaloneSelectForScopeIdentity) {
await runCommandOnDriver(conn, driver, dmp => dmp.put("INSERT INTO ~t1 (~val) VALUES ('aaa')"));
res = await runQueryOnDriver(conn, driver, dmp => dmp.selectScopeIdentity(table));
} else {
res = await runQueryOnDriver(conn, driver, dmp => {
dmp.putCmd("INSERT INTO ~t1 (~val) VALUES ('aaa')");
dmp.selectScopeIdentity(table);
});
}
const row = res.rows[0];
const keys = Object.keys(row);
expect(keys.length).toEqual(1);
expect(row[keys[0]] == 1).toBeTruthy();
})
);
});
@@ -76,7 +76,7 @@ describe('Schema tests', () => {
});
describe('Base analyser test', () => {
test.each(engines.map(engine => [engine.label, engine]))(
test.each(engines.filter(x => !x.skipIncrementalAnalysis).map(engine => [engine.label, engine]))(
'Structure without change - %s',
testWrapper(async (conn, driver, engine) => {
await baseStructure(conn, driver);
@@ -1,12 +1,13 @@
const { runCommandOnDriver } = require('dbgate-tools');
const engines = require('../engines');
const { testWrapper } = require('../tools');
const t1Sql = 'CREATE TABLE t1 (id int not null primary key, val1 varchar(50))';
const ix1Sql = 'CREATE index ix1 ON t1(val1, id)';
const t1Sql = 'CREATE TABLE ~t1 (~id int not null primary key, ~val1 varchar(50))';
const ix1Sql = 'CREATE index ~ix1 ON ~t1(~val1, ~id)';
const t2Sql = engine =>
`CREATE TABLE t2 (id int not null primary key, val2 varchar(50) ${engine.skipUnique ? '' : 'unique'})`;
const t3Sql = 'CREATE TABLE t3 (id int not null primary key, valfk int, foreign key (valfk) references t2(id))';
const t4Sql = 'CREATE TABLE t4 (id int not null primary key, valdef int not null default 12)';
`CREATE TABLE ~t2 (~id int not null primary key, ~val2 varchar(50) ${engine.skipUnique ? '' : 'unique'})`;
const t3Sql = 'CREATE TABLE ~t3 (~id int not null primary key, ~valfk int, foreign key (~valfk) references ~t2(~id))';
const t4Sql = 'CREATE TABLE ~t4 (~id int not null primary key, ~valdef int default 12 not null)';
// const fkSql = 'ALTER TABLE t3 ADD FOREIGN KEY (valfk) REFERENCES t2(id)'
const txMatch = (engine, tname, vcolname, nextcol, defaultValue) =>
@@ -15,7 +16,7 @@ const txMatch = (engine, tname, vcolname, nextcol, defaultValue) =>
columns: [
expect.objectContaining({
columnName: 'id',
dataType: expect.stringMatching(/int.*/i),
dataType: expect.stringMatching(/int.*|number/i),
...(engine.skipNullability ? {} : { notNull: true }),
}),
expect.objectContaining({
@@ -59,9 +60,10 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Table structure - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t1Sql);
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
const structure = await driver.analyseFull(conn);
console.log(JSON.stringify(structure, null, 2));
expect(structure.tables.length).toEqual(1);
expect(structure.tables[0]).toEqual(t1Match(engine));
@@ -71,13 +73,13 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Table add - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn);
expect(structure1.tables.length).toEqual(1);
expect(structure1.tables[0]).toEqual(t2Match(engine));
await driver.query(conn, t1Sql);
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2.tables.length).toEqual(2);
@@ -89,14 +91,14 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Table remove - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t1Sql);
await driver.query(conn, t2Sql(engine));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn);
expect(structure1.tables.length).toEqual(2);
expect(structure1.tables.find(x => x.pureName == 't1')).toEqual(t1Match(engine));
expect(structure1.tables.find(x => x.pureName == 't2')).toEqual(t2Match(engine));
await driver.query(conn, 'DROP TABLE t2');
await runCommandOnDriver(conn, driver, dmp => dmp.put('DROP TABLE ~t2'));
const structure2 = await driver.analyseIncremental(conn, structure1);
expect(structure2.tables.length).toEqual(1);
@@ -107,15 +109,14 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Table change - incremental analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t1Sql);
await driver.query(conn, t2Sql(engine));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure1 = await driver.analyseFull(conn);
if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100));
await driver.query(
conn,
`ALTER TABLE t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} nextcol varchar(50)`
await runCommandOnDriver(conn, driver, dmp =>
dmp.put(`ALTER TABLE ~t2 ADD ${engine.alterTableAddColumnSyntax ? 'COLUMN' : ''} ~nextcol varchar(50)`)
);
const structure2 = await driver.analyseIncremental(conn, structure1);
@@ -130,8 +131,8 @@ describe('Table analyse', () => {
test.each(engines.filter(x => !x.skipIndexes).map(engine => [engine.label, engine]))(
'Index - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t1Sql);
await driver.query(conn, ix1Sql);
await runCommandOnDriver(conn, driver, dmp => dmp.put(t1Sql));
await runCommandOnDriver(conn, driver, dmp => dmp.put(ix1Sql));
const structure = await driver.analyseFull(conn);
const t1 = structure.tables.find(x => x.pureName == 't1');
@@ -145,7 +146,7 @@ describe('Table analyse', () => {
test.each(engines.filter(x => !x.skipUnique).map(engine => [engine.label, engine]))(
'Unique - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
const structure = await driver.analyseFull(conn);
const t2 = structure.tables.find(x => x.pureName == 't2');
@@ -159,8 +160,8 @@ describe('Table analyse', () => {
test.each(engines.filter(x => !x.skipReferences).map(engine => [engine.label, engine]))(
'Foreign key - full analysis - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t2Sql(engine));
await driver.query(conn, t3Sql);
await runCommandOnDriver(conn, driver, dmp => dmp.put(t2Sql(engine)));
await runCommandOnDriver(conn, driver, dmp => dmp.put(t3Sql));
// await driver.query(conn, fkSql);
const structure = await driver.analyseFull(conn);
@@ -179,7 +180,7 @@ describe('Table analyse', () => {
test.each(engines.map(engine => [engine.label, engine]))(
'Table structure - default value - %s',
testWrapper(async (conn, driver, engine) => {
await driver.query(conn, t4Sql);
await runCommandOnDriver(conn, driver, dmp => dmp.put(t4Sql));
const structure = await driver.analyseFull(conn);
@@ -2,7 +2,7 @@ const _ = require('lodash');
const fp = require('lodash/fp');
const engines = require('../engines');
const { testWrapper } = require('../tools');
const { extendDatabaseInfo } = require('dbgate-tools');
const { extendDatabaseInfo, runCommandOnDriver } = require('dbgate-tools');
function createExpector(value) {
return _.cloneDeepWith(value, x => {
@@ -25,7 +25,7 @@ function checkTableStructure2(t1, t2) {
}
async function testTableCreate(conn, driver, table) {
await driver.query(conn, `create table t0 (id int not null primary key)`);
await runCommandOnDriver(conn, driver, dmp => dmp.put('create table ~t0 (~id int not null primary key)'));
const dmp = driver.createDumper();
const table1 = {
+6
View File
@@ -70,3 +70,9 @@ services:
# - cockroachdb
# restart: on-failure
oracle:
image: gvenzl/oracle-xe:21-slim
environment:
ORACLE_PASSWORD: Pwd2020Db
ports:
- 15006:1521
File diff suppressed because it is too large Load Diff
+14 -8
View File
@@ -1,10 +1,12 @@
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
const crypto = require('crypto');
function randomDbName() {
function randomDbName(dialect) {
const generatedKey = crypto.randomBytes(6);
const newKey = generatedKey.toString('hex');
return `db${newKey}`;
const res = `db${newKey}`;
if (dialect.upperCaseAllDbObjectNames) return res.toUpperCase();
return res;
}
function extractConnection(engine) {
@@ -32,12 +34,14 @@ async function connect(engine, database) {
return conn;
} else {
const conn = await driver.connect(connection);
await driver.query(conn, `CREATE DATABASE ${database}`);
const dmp = driver.createDumper();
dmp.createDatabase(database);
await driver.query(conn, dmp.s);
await driver.close(conn);
const res = await driver.connect({
...connection,
database,
database: (driver.dialect.userDatabaseNamePrefix ?? '') + database,
});
return res;
}
@@ -55,12 +59,14 @@ async function prepareConnection(engine, database) {
};
} else {
const conn = await driver.connect(connection);
await driver.query(conn, `CREATE DATABASE ${database}`);
const dmp = driver.createDumper();
dmp.createDatabase(database);
await driver.query(conn, dmp.s);
await driver.close(conn);
return {
...connection,
database,
database: (driver.dialect.userDatabaseNamePrefix ?? '') + database,
isPreparedOnly: true,
};
}
@@ -71,7 +77,7 @@ const testWrapper =
async (label, ...other) => {
const engine = other[other.length - 1];
const driver = requireEngineDriver(engine.connection);
const conn = await connect(engine, randomDbName());
const conn = await connect(engine, randomDbName(driver.dialect));
try {
await body(conn, driver, ...other);
} finally {
@@ -84,7 +90,7 @@ const testWrapperPrepareOnly =
async (label, ...other) => {
const engine = other[other.length - 1];
const driver = requireEngineDriver(engine.connection);
const conn = await prepareConnection(engine, randomDbName());
const conn = await prepareConnection(engine, randomDbName(driver.dialect));
await body(conn, driver, ...other);
};
+3 -3
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.0.1-packer-beta.2",
"version": "6.1.3-beta.1",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -47,7 +47,6 @@
"setCurrentVersion": "node setCurrentVersion",
"printSecrets": "node printSecrets",
"generatePadFile": "node generatePadFile",
"adjustPackageJson": "node adjustPackageJson",
"fillPackagedPlugins": "node fillPackagedPlugins",
"resetPackagedPlugins": "node resetPackagedPlugins",
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
@@ -63,7 +62,8 @@
"ts:web": "yarn workspace dbgate-web ts",
"ts": "yarn ts:api && yarn ts:web",
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn build:plugins:frontend",
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js"
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js",
"workflows": "node common/processWorkflows.js"
},
"dependencies": {
"concurrently": "^5.1.0",
+1 -1
View File
@@ -30,7 +30,7 @@
"cors": "^2.8.5",
"cross-env": "^6.0.3",
"dbgate-datalib": "^6.0.0-alpha.1",
"dbgate-query-splitter": "^4.11.2",
"dbgate-query-splitter": "^4.11.3",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"debug": "^4.3.4",
+10 -3
View File
@@ -230,7 +230,7 @@ class LoginsProvider extends AuthProviderBase {
),
};
}
return { error: 'Invalid credentials' };
}
@@ -271,11 +271,10 @@ function hasEnvLogins() {
return false;
}
function detectEnvAuthProvider() {
function detectEnvAuthProviderCore() {
if (process.env.AUTH_PROVIDER) {
return process.env.AUTH_PROVIDER;
}
if (process.env.STORAGE_DATABASE) {
return 'denyall';
}
@@ -291,6 +290,14 @@ function detectEnvAuthProvider() {
return 'none';
}
function detectEnvAuthProvider() {
const authProvider = detectEnvAuthProviderCore();
if (process.env.BASIC_AUTH && authProvider != 'logins' && authProvider != 'ad') {
throw new Error(`BASIC_AUTH is not supported with ${authProvider} auth provider`);
}
return authProvider;
}
function createEnvAuthProvider() {
const authProvider = detectEnvAuthProvider();
switch (authProvider) {
+7 -2
View File
@@ -9,6 +9,7 @@ const _ = require('lodash');
const AsyncLock = require('async-lock');
const jwt = require('jsonwebtoken');
const processArgs = require('../utility/processArgs');
const currentVersion = require('../currentVersion');
const platformInfo = require('../utility/platformInfo');
const connections = require('../controllers/connections');
@@ -83,7 +84,8 @@ module.exports = {
isElectron: platformInfo.isElectron,
isLicenseValid,
isLicenseExpired: checkedLicense?.isExpired,
trialDaysLeft: checkedLicense?.licenseTypeObj?.isTrial && !checkedLicense?.isExpired ? checkedLicense?.daysLeft : null,
trialDaysLeft:
checkedLicense?.licenseTypeObj?.isTrial && !checkedLicense?.isExpired ? checkedLicense?.daysLeft : null,
checkedLicense,
configurationError,
logoutUrl,
@@ -101,7 +103,10 @@ module.exports = {
adminPasswordState: adminConfig?.adminPasswordState,
storageDatabase: process.env.STORAGE_DATABASE,
logsFilePath: getLogsFilePath(),
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
connectionsFilePath: path.join(
datadir(),
processArgs.runE2eTests ? 'connections-e2etests.jsonl' : 'connections.jsonl'
),
...currentVersion,
};
+5 -3
View File
@@ -199,7 +199,9 @@ module.exports = {
const dir = datadir();
if (!portalConnections) {
// @ts-ignore
this.datastore = new JsonLinesDatabase(path.join(dir, 'connections.jsonl'));
this.datastore = new JsonLinesDatabase(
path.join(dir, processArgs.runE2eTests ? 'connections-e2etests.jsonl' : 'connections.jsonl')
);
}
await this.checkUnsavedConnectionsLimit();
},
@@ -220,7 +222,7 @@ module.exports = {
},
test_meta: true,
test(connection) {
test({ connection, requestDbList }) {
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
@@ -235,7 +237,7 @@ module.exports = {
}
);
pipeForkLogs(subprocess);
subprocess.send(connection);
subprocess.send({ ...connection, requestDbList });
return new Promise(resolve => {
subprocess.on('message', resp => {
if (handleProcessCommunication(resp, subprocess)) return;
+7 -1
View File
@@ -17,12 +17,18 @@ Platform: ${process.platform}
function start() {
childProcessChecker();
process.on('message', async connection => {
// @ts-ignore
const { requestDbList } = connection;
if (handleProcessCommunication(connection)) return;
try {
const driver = requireEngineDriver(connection);
const dbhan = await connectUtility(driver, connection, 'app');
const res = await driver.getVersion(dbhan);
process.send({ msgtype: 'connected', ...res });
let databases = undefined;
if (requestDbList) {
databases = await driver.listDatabases(dbhan);
}
process.send({ msgtype: 'connected', ...res, databases });
await driver.close(dbhan);
} catch (e) {
console.error(e);
+5 -2
View File
@@ -8,12 +8,15 @@ const logger = getLogger('tableReader');
* @param {object} options
* @param {connectionType} options.connection - connection object
* @param {object} options.systemConnection - system connection (result of driver.connect). If not provided, new connection will be created
* @param {object} options.driver - driver object. If not provided, it will be loaded from connection
* @param {string} options.pureName - table name
* @param {string} options.schemaName - schema name
* @returns {Promise<readerType>} - reader object
*/
async function tableReader({ connection, systemConnection, pureName, schemaName }) {
const driver = requireEngineDriver(connection);
async function tableReader({ connection, systemConnection, pureName, schemaName, driver }) {
if (!driver) {
driver = requireEngineDriver(connection);
}
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
logger.info(`Connected.`);
+5 -1
View File
@@ -66,7 +66,7 @@ class DuplicatorItemHolder {
this.autoColumn = this.table.columns.find(x => x.autoIncrement)?.columnName;
if (
this.table.primaryKey?.columns?.length != 1 ||
this.table.primaryKey?.columns?.[0].columnName != this.autoColumn
this.table.primaryKey?.columns?.[0]?.columnName != this.autoColumn
) {
this.autoColumn = null;
}
@@ -140,6 +140,9 @@ class DuplicatorItemHolder {
weakref.foreignKey.columns[0].columnName
);
});
if (this.duplicator.driver.dialect.requireFromDual) {
dmp.put(' ^from ^dual');
}
});
const qrow = qres.rows[0];
return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName);
@@ -194,6 +197,7 @@ class DuplicatorItemHolder {
res = await runQueryOnDriver(pool, driver, dmp => dmp.selectScopeIdentity(this.table));
}
// console.log('IDRES', JSON.stringify(res));
// console.log('*********** ENTRIES OF', res?.rows?.[0]);
const resId = Object.entries(res?.rows?.[0])?.[0]?.[1];
if (resId != null) {
this.idMap[chunk[this.autoColumn]] = resId;
+1 -1
View File
@@ -41,7 +41,7 @@ STORAGE_DATABASE=dbname
STORAGE_ENGINE=mysql@dbgate-plugin-mysql
```
You could find more about environment variable configuration on [DbGate docs](https://dbgate.org/docs/env-variables.html) page.
You could find more about environment variable configuration on [DbGate docs](https://dbgate.org/docs/env-variables/) page.
After installing, you can run dbgate with command:
```sh
+1 -1
View File
@@ -31,7 +31,7 @@
"typescript": "^4.4.3"
},
"dependencies": {
"dbgate-query-splitter": "^4.11.2",
"dbgate-query-splitter": "^4.11.3",
"dbgate-sqltree": "^6.0.0-alpha.1",
"debug": "^4.3.4",
"json-stable-stringify": "^1.0.1",
+1 -1
View File
@@ -359,7 +359,7 @@ export class DatabaseAnalyser {
}
static byTableFilter(table) {
return x => x.pureName == table.pureName && x.schemaName == x.schemaName;
return x => x.pureName == table.pureName && x.schemaName == table.schemaName;
}
static extractPrimaryKeys(table, pkColumns) {
+1 -1
View File
@@ -715,7 +715,7 @@ export function createAlterDatabasePlan(
): AlterPlan {
const plan = new AlterPlan(wholeOldDb, wholeNewDb, driver.dialect, opts);
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions']) {
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions', 'triggers']) {
for (const oldobj of oldDb[objectTypeField] || []) {
const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId);
if (objectTypeField == 'tables') {
+22 -3
View File
@@ -1,4 +1,5 @@
import _compact from 'lodash/compact';
import _isString from 'lodash/isString';
import { SqlDumper } from './SqlDumper';
import { splitQuery } from 'dbgate-query-splitter';
import { dumpSqlSelect } from 'dbgate-sqltree';
@@ -26,9 +27,17 @@ const dialect = {
defaultSchemaName: null,
};
export async function runCommandOnDriver(pool, driver: EngineDriver, cmd: (dmp: SqlDumper) => void): Promise<void> {
export async function runCommandOnDriver(
pool,
driver: EngineDriver,
cmd: (dmp: SqlDumper) => void | string
): Promise<void> {
const dmp = driver.createDumper();
cmd(dmp as any);
if (_isString(cmd)) {
dmp.put(cmd);
} else {
cmd(dmp as any);
}
// console.log('CMD:', dmp.s);
await driver.query(pool, dmp.s, { discardResult: true });
}
@@ -39,11 +48,21 @@ export async function runQueryOnDriver(
cmd: (dmp: SqlDumper) => void
): Promise<QueryResult> {
const dmp = driver.createDumper();
cmd(dmp as any);
if (_isString(cmd)) {
dmp.put(cmd);
} else {
cmd(dmp as any);
}
// console.log('QUERY:', dmp.s);
return await driver.query(pool, dmp.s);
}
export function formatQueryWithoutParams(driver: EngineDriver, sql: string) {
const dmp = driver.createDumper();
dmp.put(sql);
return dmp.s;
}
export const driverBase = {
analyserClass: null,
dumperClass: SqlDumper,
+131 -18
View File
@@ -2,9 +2,9 @@ import _compact from 'lodash/compact';
import _isString from 'lodash/isString';
import _startCase from 'lodash/startCase';
export interface FilterNameDefinition {
childName: string;
}
// export interface FilterNameDefinition {
// childName: string;
// }
function camelMatch(filter: string, text: string): boolean {
if (!text) return false;
@@ -20,7 +20,7 @@ function camelMatch(filter: string, text: string): boolean {
}
}
export function filterName(filter: string, ...names: (string | FilterNameDefinition)[]) {
export function filterName(filter: string, ...names: string[]) {
if (!filter) return true;
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
@@ -28,22 +28,135 @@ export function filterName(filter: string, ...names: (string | FilterNameDefinit
const namesCompacted = _compact(names);
// @ts-ignore
const namesOwn: string[] = namesCompacted.filter(x => _isString(x));
// @ts-ignore
const namesChild: string[] = namesCompacted.filter(x => x.childName).map(x => x.childName);
for (const token of tokens) {
// const tokenUpper = token.toUpperCase();
if (token.startsWith('#')) {
// const tokenUpperSub = tokenUpper.substring(1);
const found = namesChild.find(name => camelMatch(token.substring(1), name));
if (!found) return false;
} else {
const found = namesOwn.find(name => camelMatch(token, name));
if (!found) return false;
}
const found = namesCompacted.find(name => camelMatch(token, name));
if (!found) return false;
}
return true;
}
export function filterNameCompoud(
filter: string,
namesMain: string[],
namesChild: string[]
): 'main' | 'child' | 'both' | 'none' {
if (!filter) return 'both';
// const camelVariants = [name.replace(/[^A-Z]/g, '')]
const tokens = filter.split(' ').map(x => x.trim());
const namesCompactedMain = _compact(namesMain);
const namesCompactedChild = _compact(namesChild);
let isMainOnly = true;
let isChildOnly = true;
for (const token of tokens) {
const foundMain = namesCompactedMain.find(name => camelMatch(token, name));
const foundChild = namesCompactedChild.find(name => camelMatch(token, name));
if (!foundMain && !foundChild) return 'none';
if (!foundMain) isMainOnly = false;
if (!foundChild) isChildOnly = false;
}
if (isMainOnly && isChildOnly) return 'both';
if (isMainOnly) return 'main';
if (isChildOnly) return 'child';
return 'none';
}
export function tokenizeBySearchFilter(text: string, filter: string): { text: string; isMatch: boolean }[] {
const camelTokens = [];
const stdTokens = [];
for (const token of filter.split(' ').map(x => x.trim())) {
if (token.replace(/[A-Z]/g, '').length == 0) {
camelTokens.push(token);
} else {
stdTokens.push(token.toUpperCase());
}
}
let res = [
{
text,
isMatch: false,
},
];
for (const token of camelTokens) {
const nextres = [];
for (const item of res) {
const indexes = [];
for (const char of token) {
if (indexes.length == 0 && char == item.text[0]?.toUpperCase()) {
// handle first letter of camelcase
indexes.push(0);
} else {
const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
if (index < 0) {
indexes.push(-1);
} else {
indexes.push(index);
}
}
}
if (indexes.some(x => x < 0)) {
nextres.push(item);
} else {
let lastIndex = 0;
for (let i = 0; i < indexes.length; i++) {
if (indexes[i] > lastIndex) {
nextres.push({ text: item.text.substring(lastIndex, indexes[i]), isMatch: false });
}
nextres.push({ text: item.text.substring(indexes[i], indexes[i] + 1), isMatch: true });
lastIndex = indexes[i] + 1;
}
nextres.push({ text: item.text.substring(lastIndex), isMatch: false });
}
}
res = nextres;
}
for (const token of stdTokens) {
const nextres = [];
for (const item of res) {
const index = item.text?.toUpperCase().indexOf(token);
if (index < 0) {
nextres.push(item);
} else {
nextres.push({ text: item.text.substring(0, index), isMatch: false });
nextres.push({ text: item.text.substring(index, index + token.length), isMatch: true });
nextres.push({ text: item.text.substring(index + token.length), isMatch: false });
}
}
res = nextres;
}
res = res.filter(x => x.text.length > 0);
if (res.length == 1 && !res[0].isMatch) {
return null;
}
return res;
// const result = [];
// let lastMatch = 0;
// for (const token of tokens) {
// const index = text.indexOf(token, lastMatch);
// if (index < 0) {
// result.push({ token, isMatch: false });
// continue;
// }
// result.push({ token: text.substring(lastMatch, index), isMatch: false });
// result.push({ token: text.substring(index, index + token.length), isMatch: true });
// lastMatch = index + token.length;
// }
// result.push({ token: text.substring(lastMatch), isMatch: false });
// return result;
}
+1
View File
@@ -234,6 +234,7 @@ export function databaseInfoFromYamlModel(filesOrDbInfo: DatabaseModelFile[] | D
if (file.name.endsWith('.trigger.sql')) {
model.triggers.push({
objectId: `triggers:${file.name.slice(0, -'.trigger.sql'.length)}`,
pureName: file.name.slice(0, -'.trigger.sql'.length),
createSql: file.text,
});
+21 -2
View File
@@ -75,7 +75,7 @@ export interface DatabaseObjectInfo extends NamedObjectInfo {
modifyDate?: string;
hashCode?: string;
objectTypeField?: string;
obejctComment?: string;
objectComment?: string;
}
export interface SqlObjectInfo extends DatabaseObjectInfo {
@@ -124,6 +124,7 @@ export interface ParameterInfo extends NamedObjectInfo {
parameterName?: string;
dataType: string;
parameterMode?: ParameterMode;
position?: number;
}
export interface CallableObjectInfo extends SqlObjectInfo {
@@ -136,7 +137,25 @@ export interface FunctionInfo extends CallableObjectInfo {
returnType?: string;
}
export interface TriggerInfo extends SqlObjectInfo {}
export interface TriggerInfo extends SqlObjectInfo {
objectId: string;
functionName?: string;
tableName?: string;
triggerTiming?:
| 'BEFORE'
| 'AFTER'
| 'INSTEAD OF'
| 'BEFORE EACH ROW'
| 'INSTEAD OF'
| 'AFTER EACH ROW'
| 'AFTER STATEMENT'
| 'BEFORE STATEMENT'
| 'AFTER EVENT'
| 'BEFORE EVENT'
| null;
triggerLevel?: 'ROW' | 'STATEMENT';
eventType?: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE';
}
export interface SchemaInfo {
objectId?: string;
+2
View File
@@ -51,6 +51,8 @@ export interface SqlDialect {
dropReferencesWhenDropTable?: boolean;
requireFromDual?: boolean;
userDatabaseNamePrefix?: string; // c## in Oracle
upperCaseAllDbObjectNames?: boolean;
predefinedDataTypes: string[];
+2 -2
View File
@@ -20,12 +20,12 @@
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@tsconfig/svelte": "^1.0.0",
"ace-builds": "^1.4.8",
"ace-builds": "^1.36.5",
"chart.js": "^4.4.2",
"chartjs-adapter-moment": "^1.0.0",
"cross-env": "^7.0.3",
"dbgate-datalib": "^6.0.0-alpha.1",
"dbgate-query-splitter": "^4.11.2",
"dbgate-query-splitter": "^4.11.3",
"dbgate-sqltree": "^6.0.0-alpha.1",
"dbgate-tools": "^6.0.0-alpha.1",
"dbgate-types": "^6.0.0-alpha.1",
@@ -29,8 +29,8 @@
export const extractKey = data => data.fileName;
export const createMatcher =
({ fileName }) =>
filter =>
({ fileName }) =>
filterName(filter, fileName);
const APP_ICONS = {
'config.json': 'img json',
@@ -1,6 +1,6 @@
<script lang="ts" context="module">
export const extractKey = data => data.name;
export const createMatcher = data => filter => filterName(filter, data.name);
export const createMatcher = filter => data => filterName(filter, data.name);
</script>
<script lang="ts">
+24 -4
View File
@@ -5,6 +5,7 @@
import CheckboxField from '../forms/CheckboxField.svelte';
import { copyTextToClipboard } from '../utility/clipboard';
import { showSnackbarSuccess } from '../utility/snackbar';
import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
const dispatch = createEventDispatcher();
@@ -30,6 +31,9 @@
export let onUnpin = null;
export let showPinnedInsteadOfUnpin = false;
export let indentLevel = 0;
export let disableBoldScroll = false;
export let filter = null;
export let disableHover = false;
$: isChecked =
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));
@@ -57,6 +61,13 @@
}
}
function handleMouseDown(e) {
if (e.button == 1) {
e.preventDefault();
e.stopPropagation();
}
}
function setChecked(value) {
if (!value && isChecked) {
checkedObjectsStore.update(x => x.filter(y => module?.extractKey(data) != module?.extractKey(y)));
@@ -69,7 +80,7 @@
// $: console.log(title, indentLevel);
let domDiv;
$: if (isBold && domDiv) {
$: if (isBold && domDiv && !disableBoldScroll) {
domDiv.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
@@ -82,9 +93,12 @@
class="main"
class:isBold
class:isChoosed
class:disableHover
draggable={true}
on:click={handleClick}
on:mouseup={handleMouseUp}
on:mousedown={handleMouseDown}
on:mousedown
on:dblclick
use:contextMenu={disableContextMenu ? null : menu}
on:dragstart={e => {
@@ -121,7 +135,7 @@
{#if colorMark}
<FontIcon style={`color:${colorMark}`} icon="icon square" />
{/if}
{title}
<TokenizedFilteredText text={title} {filter} />
{#if statusIconBefore}
<span class="status">
<FontIcon icon={statusIconBefore} />
@@ -143,7 +157,7 @@
{/if}
{#if extInfo}
<span class="ext-info">
{extInfo}
<TokenizedFilteredText text={extInfo} {filter} />
</span>
{/if}
{#if onPin}
@@ -180,13 +194,19 @@
<slot />
<style>
.pin,
.pin-active {
position: absolute;
right: 0;
z-index: 150;
}
.main {
padding: 5px;
cursor: pointer;
white-space: nowrap;
font-weight: normal;
}
.main:hover {
.main:hover:not(.disableHover) {
background-color: var(--theme-bg-hover);
}
.isBold {
@@ -19,6 +19,7 @@
export let onDropOnGroup = undefined;
export let groupContextMenu = null;
export let collapsedGroupNames;
export let filter = undefined;
$: isExpanded = !$collapsedGroupNames.includes(group);
@@ -86,6 +87,9 @@
on:objectClick
{disableContextMenu}
{passProps}
isExpandedBySearch={filter && item.isChildMatched}
{filter}
isMainMatched={item.isMainMatched}
/>
{/each}
</div>
+72 -32
View File
@@ -1,11 +1,13 @@
<script>
import _, { sortBy } from 'lodash';
import { asyncFilter } from '../utility/common';
import _ from 'lodash';
import AppObjectGroup from './AppObjectGroup.svelte';
import { plusExpandIcon } from '../icons/expandIcons';
import AppObjectListItem from './AppObjectListItem.svelte';
import { writable } from 'svelte/store';
import Link from '../elements/Link.svelte';
import { focusedConnectionOrDatabase } from '../stores';
import { tick } from 'svelte';
export let list;
export let module;
@@ -16,7 +18,7 @@
export let expandIconFunc = undefined;
export let checkedObjectsStore = null;
export let disableContextMenu = false;
export let passProps;
export let passProps = {};
export let getIsExpanded = null;
export let setIsExpanded = null;
export let sortGroups = false;
@@ -26,25 +28,47 @@
export let groupFunc = undefined;
export let onDropOnGroup = undefined;
export let emptyGroupNames = [];
export let isExpandedBySearch = false;
export let collapsedGroupNames = writable([]);
export let onChangeFilteredList;
export let onChangeFilteredList = undefined;
$: filtered = !groupFunc
? list.filter(data => {
const matcher = module.createMatcher && module.createMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
})
: null;
let expandLimited = false;
$: childrenMatched = !groupFunc
? list.filter(data => {
const matcher = module.createChildMatcher && module.createChildMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
})
: null;
$: matcher = module.createMatcher && module.createMatcher(filter, passProps?.searchSettings);
$: dataLabeled = _.compact(
(list || []).map(data => {
const matchResult = matcher ? matcher(data) : true;
let isMatched = true;
let isMainMatched = true;
let isChildMatched = true;
if (matchResult == false) {
isMatched = false;
isChildMatched = false;
isMainMatched = false;
} else if (matchResult == 'child') {
isMainMatched = false;
} else if (matchResult == 'main') {
isChildMatched = false;
} else if (matchResult == 'none') {
isMatched = false;
isChildMatched = false;
isMainMatched = false;
} else if (matchResult == 'both') {
isChildMatched = !module.disableShowChildrenWithParentMatch;
}
const group = groupFunc ? groupFunc(data) : undefined;
return { group, data, isMatched, isChildMatched, isMainMatched };
})
);
$: filtered = dataLabeled.filter(x => x.isMatched).map(x => x.data);
$: childrenMatched = dataLabeled.filter(x => x.isChildMatched).map(x => x.data);
$: mainMatched = dataLabeled.filter(x => x.isMainMatched).map(x => x.data);
// let filtered = [];
@@ -60,17 +84,6 @@
// }
// }
$: listGrouped = groupFunc
? _.compact(
(list || []).map(data => {
const matcher = module.createMatcher && module.createMatcher(data);
const isMatched = matcher && !matcher(filter) ? false : true;
const group = groupFunc(data);
return { group, data, isMatched };
})
)
: null;
function extendGroups(base, emptyList) {
const res = {
...base,
@@ -82,7 +95,24 @@
return res;
}
$: groups = groupFunc ? extendGroups(_.groupBy(listGrouped, 'group'), emptyGroupNames) : null;
function setExpandLimited() {
expandLimited = true;
}
$: groups = groupFunc ? extendGroups(_.groupBy(dataLabeled, 'group'), emptyGroupNames) : null;
$: listLimited = isExpandedBySearch && !expandLimited ? filtered.slice(0, filter.trim().length < 3 ? 1 : 3) : list;
$: isListLimited = isExpandedBySearch && listLimited.length < filtered.length;
$: listMissingItems = isListLimited ? filtered.slice(listLimited.length) : [];
$: if (
$focusedConnectionOrDatabase &&
listMissingItems.some(
x => $focusedConnectionOrDatabase.conid == x?.connection?._id && $focusedConnectionOrDatabase.database == x?.name
)
) {
tick().then(setExpandLimited);
}
</script>
{#if groupFunc}
@@ -108,7 +138,7 @@
/>
{/each}
{:else}
{#each list as data}
{#each listLimited as data}
<AppObjectListItem
isHidden={!filtered.includes(data)}
{module}
@@ -121,10 +151,20 @@
{checkedObjectsStore}
{disableContextMenu}
{filter}
isExpandedBySearch={childrenMatched.includes(data)}
isExpandedBySearch={filter && childrenMatched.includes(data)}
isMainMatched={filter && mainMatched.includes(data)}
{passProps}
{getIsExpanded}
{setIsExpanded}
/>
{/each}
{#if isListLimited}
<div class="ml-2">
<Link
onClick={() => {
expandLimited = true;
}}>Show next {filtered.length - listLimited.length}</Link
>
</div>
{/if}
{/if}
@@ -24,6 +24,7 @@
export let passProps;
export let getIsExpanded = null;
export let setIsExpanded = null;
export let isMainMatched = false;
let isExpandedCore = false;
@@ -57,16 +58,21 @@
{module}
{disableContextMenu}
{passProps}
{filter}
/>
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
<div class="subitems">
<svelte:component
this={subItemsComponent(data)}
this={subItemsComponent(data, {
isExpandedBySearch,
})}
{data}
{filter}
{passProps}
isExpandedOnlyBySearch={isExpandedBySearch && !isExpanded}
{isExpandedBySearch}
{isExpanded}
{isMainMatched}
/>
</div>
{/if}
@@ -42,8 +42,8 @@
export const extractKey = data => data.fileName;
export const createMatcher =
({ fileName }) =>
filter =>
({ fileName }) =>
filterName(filter, fileName);
const ARCHIVE_ICONS = {
'table.yaml': 'img table',
@@ -70,7 +70,7 @@
import { getExtensions } from '../stores';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportQuickExportFile, } from '../utility/exportFileTools';
import { exportQuickExportFile } from '../utility/exportFileTools';
import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte';
import InputTextModal from '../modals/InputTextModal.svelte';
@@ -1,6 +1,6 @@
<script lang="ts" context="module">
export const extractKey = data => data.name;
export const createMatcher = data => filter => filterName(filter, data.name);
export const createMatcher = filter => data => filterName(filter, data.name);
</script>
<script lang="ts">
+30 -2
View File
@@ -1,5 +1,17 @@
<script lang="ts" context="module">
export const extractKey = ({ columnName }) => columnName;
export const createMatcher =
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
data => {
const filterArgs = [];
if (cfg.columnName) filterArgs.push(data.columnName);
if (cfg.columnComment) filterArgs.push(data.columnComment);
if (cfg.columnDataType) filterArgs.push(data.dataType);
const res = filterName(filter, ...filterArgs);
return res;
};
</script>
<script lang="ts">
@@ -9,6 +21,8 @@
import { renameDatabaseObjectDialog, alterDatabaseDialog } from '../utility/alterDatabaseTools';
import AppObjectCore from './AppObjectCore.svelte';
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
import { filterName } from 'dbgate-tools';
export let data;
@@ -35,7 +49,21 @@
];
}
$: extInfo = data.foreignKey ? `${data.dataType} -> ${data.foreignKey.refTableName}` : data.dataType;
function getExtInfo(data) {
const res = [];
if (data.foreignKey) {
res.push(`${data.dataType} -> ${data.foreignKey.refTableName}`);
} else {
res.push(data.dataType);
}
if (data.columnComment) {
res.push(data.columnComment);
}
if (res.length > 0) return res.join(', ');
return null;
}
$: extInfo = getExtInfo(data);
</script>
<AppObjectCore
@@ -45,5 +73,5 @@
{extInfo}
icon={getColumnIcon(data, true)}
menu={createMenu}
disableHover
\
/>
@@ -1,16 +1,24 @@
<script context="module">
export const extractKey = data => data._id;
export const createMatcher = props => filter => {
const { _id, displayName, server } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
return filterName(filter, displayName, server, ...databases.map(x => x.name));
};
export const createChildMatcher = props => filter => {
if (!filter) return false;
const { _id } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
return filterName(filter, ...databases.map(x => x.name));
};
export const createMatcher =
(filter, cfg = DEFAULT_CONNECTION_SEARCH_SETTINGS) =>
props => {
const { _id, displayName, server, user, engine } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
const match = (engine || '').match(/^([^@]*)@/);
const engineDisplay = match ? match[1] : engine;
return filterNameCompoud(
filter,
[
cfg.displayName ? displayName : null,
cfg.server ? server : null,
cfg.user ? user : null,
cfg.engine ? engineDisplay : null,
],
cfg.database ? databases.map(x => x.name) : []
);
};
export function openConnection(connection, disableExpand = false) {
if (connection.singleDatabase) {
if (getOpenedSingleDatabaseConnections().includes(connection._id)) {
@@ -42,6 +50,10 @@
expandedConnections.update(x => _.uniq([...x, connection._id]));
}
if (connection.defaultDatabase) {
switchCurrentDatabase({ connection, name: connection.defaultDatabase });
}
// if (!config.runAsPortal && getCurrentSettings()['defaultAction.connectionClick'] != 'connect') {
// expandedConnections.update(x => _.uniq([...x, connection._id]));
// }
@@ -94,6 +106,7 @@
import AppObjectCore from './AppObjectCore.svelte';
import {
currentDatabase,
DEFAULT_CONNECTION_SEARCH_SETTINGS,
expandedConnections,
extensions,
focusedConnectionOrDatabase,
@@ -106,7 +119,7 @@
openedConnections,
openedSingleDatabaseConnections,
} from '../stores';
import { filterName } from 'dbgate-tools';
import { filterName, filterNameCompoud } from 'dbgate-tools';
import { showModal } from '../modals/modalTools';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import InputTextModal from '../modals/InputTextModal.svelte';
@@ -123,6 +136,7 @@
import { getConnectionLabel } from 'dbgate-tools';
import hasPermission from '../utility/hasPermission';
import { switchCurrentDatabase } from '../utility/common';
import { getConnectionClickActionSetting } from '../settings/settingsTools';
export let data;
export let passProps;
@@ -177,24 +191,40 @@
};
const handleClick = async e => {
// focusedConnectionOrDatabase.set({
// conid: data?._id,
// connection: data,
// database: data.singleDatabase ? data.defaultDatabase : null,
// });
const config = getCurrentConfig();
const connectionClickAction = getConnectionClickActionSetting();
if (connectionClickAction == 'openDetails') {
if (config.runAsPortal == false && !config.storageDatabase) {
openNewTab({
title: getConnectionLabel(data),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: true,
props: {
conid: data._id,
},
});
}
}
if (connectionClickAction == 'connect') {
await tick();
handleConnect();
}
};
const handleMouseDown = () => {
focusedConnectionOrDatabase.set({
conid: data?._id,
connection: data,
database: data.singleDatabase ? data.defaultDatabase : null,
});
const config = getCurrentConfig();
if (config.runAsPortal == false && !config.storageDatabase) {
openNewTab({
title: getConnectionLabel(data),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: true,
props: {
conid: data._id,
},
});
}
};
const handleSqlRestore = () => {
@@ -373,6 +403,7 @@
colorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data._id })}
menu={getContextMenu}
on:click={handleClick}
on:mousedown={handleMouseDown}
on:dblclick
on:expand
on:dblclick={handleDoubleClick}
@@ -385,4 +416,5 @@
(data.singleDatabase
? $focusedConnectionOrDatabase?.database == data.defaultDatabase
: !$focusedConnectionOrDatabase?.database)}
disableBoldScroll={!!$focusedConnectionOrDatabase}
/>
@@ -3,6 +3,11 @@
export const extractKey = props => props.name;
export const createMatcher = filter => props => {
const { name, displayName, server } = props;
return filterName(filter, name, displayName, server);
};
export function disconnectDatabaseConnection(conid, database, showConfirmation = true) {
const closeCondition = x =>
x.props?.conid == conid &&
@@ -351,11 +356,13 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
text: `New ${driver?.collectionSingularLabel ?? 'collection/container'}`,
},
hasPermission(`dbops/query`) &&
driver?.databaseEngineTypes?.includes('sql') && isProApp() && { onClick: handleQueryDesigner, text: 'Design query' },
driver?.databaseEngineTypes?.includes('sql') && isProApp() && {
onClick: handleNewPerspective,
text: 'Design perspective query',
},
driver?.databaseEngineTypes?.includes('sql') &&
isProApp() && { onClick: handleQueryDesigner, text: 'Design query' },
driver?.databaseEngineTypes?.includes('sql') &&
isProApp() && {
onClick: handleNewPerspective,
text: 'Design perspective query',
},
connection.useSeparateSchemas && { onClick: handleRefreshSchemas, text: 'Refresh schemas' },
{ divider: true },
@@ -463,7 +470,13 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte';
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
import { extractDbNameFromComposite, extractPackageName, findEngineDriver, getConnectionLabel } from 'dbgate-tools';
import {
extractDbNameFromComposite,
extractPackageName,
filterName,
findEngineDriver,
getConnectionLabel,
} from 'dbgate-tools';
import InputTextModal from '../modals/InputTextModal.svelte';
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
import { openJsonDocument } from '../tabs/JsonTab.svelte';
@@ -486,6 +499,7 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
import ChooseArchiveFolderModal from '../modals/ChooseArchiveFolderModal.svelte';
import { extractShellConnection } from '../impexp/createImpExpScript';
import { getNumberIcon } from '../icons/FontIcon.svelte';
import { getDatabaseClickActionSetting } from '../settings/settingsTools';
export let data;
export let passProps;
@@ -522,6 +536,11 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
}}
on:click={() => {
// switchCurrentDatabase(data);
if (getDatabaseClickActionSetting() == 'switch') {
switchCurrentDatabase(data);
}
}}
on:mousedown={() => {
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
}}
on:dragstart
@@ -553,4 +572,5 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
: null}
isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid &&
data.name == $focusedConnectionOrDatabase?.database}
disableBoldScroll={!!$focusedConnectionOrDatabase}
/>
@@ -3,14 +3,34 @@
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const createMatcher =
({ schemaName, pureName, columns }) =>
filter =>
filterName(
filter,
pureName,
schemaName,
...(columns?.map(({ columnName }) => ({ childName: columnName })) || [])
);
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
({ schemaName, pureName, objectComment, tableEngine, columns, objectTypeField, tableName, createSql }) => {
const mainArgs = [];
const childArgs = [];
if (cfg.schemaName) mainArgs.push(schemaName);
if (cfg.pureName) mainArgs.push(pureName);
if (objectTypeField == 'tables') {
if (cfg.tableComment) mainArgs.push(objectComment);
if (cfg.tableEngine) mainArgs.push(tableEngine);
for (const column of columns || []) {
if (cfg.columnName) childArgs.push(column.columnName);
if (cfg.columnComment) childArgs.push(column.columnComment);
if (cfg.columnDataType) childArgs.push(column.dataType);
}
} else {
if (cfg.sqlObjectText) childArgs.push(createSql);
}
if (objectTypeField == 'triggers' && cfg.pureName) {
mainArgs.push(tableName);
}
const res = filterNameCompoud(filter, mainArgs, childArgs);
return res;
};
export const disableShowChildrenWithParentMatch = true;
export const createTitle = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const databaseObjectIcons = {
@@ -21,6 +41,7 @@
procedures: 'img procedure',
functions: 'img function',
queries: 'img query-data',
triggers: 'icon trigger',
};
const defaultTabs = {
@@ -31,6 +52,7 @@
queries: 'QueryDataTab',
procedures: 'SqlObjectTab',
functions: 'SqlObjectTab',
triggers: 'SqlObjectTab',
};
function createScriptTemplatesSubmenu(objectTypeField) {
@@ -322,40 +344,8 @@
},
];
case 'functions':
return [
...defaultDatabaseObjectAppObjectActions['functions'],
{
divider: true,
},
hasPermission('dbops/model/edit') && {
label: 'Drop function',
isDrop: true,
requiresWriteAccess: true,
},
hasPermission('dbops/model/edit') && {
label: 'Rename function',
isRename: true,
requiresWriteAccess: true,
},
createScriptTemplatesSubmenu('functions'),
{
label: 'SQL generator',
submenu: [
{
label: 'CREATE FUNCTION',
sqlGeneratorProps: {
createFunctions: true,
},
},
{
label: 'DROP FUNCTION',
sqlGeneratorProps: {
dropFunctions: true,
},
},
],
},
];
case 'triggers':
return [...defaultDatabaseObjectAppObjectActions['triggers']];
case 'collections':
return [
...defaultDatabaseObjectAppObjectActions['collections'],
@@ -733,7 +723,7 @@
return;
}
const availableDefaultActions = defaultDatabaseObjectAppObjectActions[objectTypeField];
const availableDefaultActions = defaultDatabaseObjectAppObjectActions[objectTypeField] ?? [];
const configuredActionId = getLastUsedDefaultActions()[objectTypeField];
const prefferedAction =
@@ -867,7 +857,35 @@
return createDatabaseObjectMenu(data);
}
export function handleObjectClick(data, { forceNewTab = false, tabPreviewMode = false, focusTab = false }) {
export function handleObjectClick(data, clickAction) {
// on:click={() => handleObjectClick(data, { tabPreviewMode: true })}
// on:middleclick={() => handleObjectClick(data, { forceNewTab: true })}
// on:dblclick={() => handleObjectClick(data, { tabPreviewMode: false, focusTab: true })}
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
let forceNewTab = false;
let tabPreviewMode = false;
let focusTab = false;
switch (clickAction) {
case 'leftClick':
tabPreviewMode = true;
break;
case 'middleClick':
forceNewTab = true;
break;
case 'dblClick':
focusTab = true;
break;
case 'keyEnter':
focusTab = true;
break;
case 'keyArrow':
if (!openDetailOnArrows) return;
tabPreviewMode = true;
break;
}
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
</script>
@@ -876,27 +894,18 @@
import _ from 'lodash';
import AppObjectCore from './AppObjectCore.svelte';
import {
currentDatabase,
extensions,
DEFAULT_OBJECT_SEARCH_SETTINGS,
getActiveTab,
getCurrentSettings,
getExtensions,
getLastUsedDefaultActions,
lastUsedDefaultActions,
openedConnections,
openedTabs,
pinnedTables,
selectedDatabaseObjectAppObject,
} from '../stores';
import openNewTab from '../utility/openNewTab';
import {
extractDbNameFromComposite,
filterName,
generateDbPairingId,
getAlterDatabaseScript,
getConnectionLabel,
} from 'dbgate-tools';
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
import { extractDbNameFromComposite, filterNameCompoud, getConnectionLabel } from 'dbgate-tools';
import { getConnectionInfo } from '../utility/metadataLoaders';
import fullDisplayName from '../utility/fullDisplayName';
import { showModal } from '../modals/modalTools';
import { findEngineDriver } from 'dbgate-tools';
@@ -907,7 +916,6 @@
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
import { alterDatabaseDialog, renameDatabaseObjectDialog } from '../utility/alterDatabaseTools';
import ConfirmModal from '../modals/ConfirmModal.svelte';
import { apiCall } from '../utility/api';
import InputTextModal from '../modals/InputTextModal.svelte';
import { extractShellConnection } from '../impexp/createImpExpScript';
import { format as dateFormat } from 'date-fns';
@@ -916,23 +924,24 @@
import { openImportExportTab } from '../utility/importExportTools';
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
import { getBoolSettingsValue } from '../settings/settingsTools';
import { getBoolSettingsValue, getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
import { isProApp } from '../utility/proTools';
export let data;
export let passProps;
function handleClick({ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}) {
$selectedDatabaseObjectAppObject = _.pick(data, ['conid', 'database', 'objectTypeField', 'pureName', 'schemaName']);
handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
function createMenu() {
return createDatabaseObjectMenu(data, passProps?.connection);
}
function getExtInfo(data) {
const res = [];
if (data.objectTypeField === 'triggers') {
res.push(`${data.tableName}, ${data.triggerTiming?.toLowerCase() ?? ''} ${data.eventType?.toLowerCase() ?? ''}`);
}
if (data.objectComment) {
res.push(data.objectComment);
}
if (data.tableRowCount != null) {
res.push(`${formatRowCount(data.tableRowCount)} rows`);
}
@@ -958,12 +967,15 @@
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
extInfo={getExtInfo(data)}
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
on:click={() => handleClick({ tabPreviewMode: true })}
on:middleclick={() => handleClick({ forceNewTab: true })}
on:dblclick={() => handleClick({ tabPreviewMode: false, focusTab: true })}
on:click={() => handleObjectClick(data, 'leftClick')}
on:middleclick={() => handleObjectClick(data, 'middleClick')}
on:dblclick={() => handleObjectClick(data, 'dblClick')}
on:expand
on:dragstart
on:dragenter
on:dragend
on:drop
on:mousedown={() => {
$selectedDatabaseObjectAppObject = _.pick(data, ['conid', 'database', 'objectTypeField', 'pureName', 'schemaName']);
}}
/>
@@ -1,6 +1,9 @@
<script lang="ts" context="module">
export const extractKey = data => data.name;
export const createMatcher = ({ name, title }) => filter => filterName(filter, name, title);
export const createMatcher =
filter =>
({ name, title }) =>
filterName(filter, name, title);
</script>
<script lang="ts">
@@ -6,13 +6,26 @@
import AppObjectCore from './AppObjectCore.svelte';
export let data;
function createParameterTitle(parameter) {
if (!parameter.parameterName) return parameter.position ?? null;
if (parameter.parameterName.startsWith('@')) return parameter.parameterName.substring(1);
return data.parameterName;
}
function createParameterExtInfo(parameter) {
if (parameter.parameterMode && parameter.parameterMode !== 'IN') {
return `${parameter.dataType} ${parameter.parameterMode}`;
}
return parameter.dataType;
}
</script>
<AppObjectCore
{...$$restProps}
{data}
title={data.parameterName?.startsWith('@') ? data.parameterName.substring(1) : data.parameterName}
extInfo={data.parameterMode && data.parameterMode !== 'IN' ? `${data.dataType} ${data.parameterMode}` : data.dataType}
title={createParameterTitle(data)}
extInfo={createParameterExtInfo(data)}
icon={'icon parameter'}
disableHover
/>
@@ -0,0 +1,23 @@
<script lang="ts" context="module">
export const extractKey = ({ columnName }) => columnName;
export const createMatcher =
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
data => {
const filterArgs = [];
if (cfg.sqlObjectText) filterArgs.push(data.lineData);
const res = filterName(filter, ...filterArgs);
return res;
};
</script>
<script lang="ts">
import AppObjectCore from './AppObjectCore.svelte';
import { filterName } from 'dbgate-tools';
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
export let data;
</script>
<AppObjectCore {...$$restProps} {data} icon="icon text" title={data.lineData?.substring(0, 100)} disableHover />
@@ -104,8 +104,8 @@
export const extractKey = data => data.file;
export const createMatcher =
({ file }) =>
filter =>
({ file }) =>
filterName(filter, file);
</script>
+10 -5
View File
@@ -11,17 +11,22 @@
export let data;
export let passProps;
export let isExpandedOnlyBySearch;
export let isExpandedBySearch;
export let isExpanded;
export let isMainMatched;
$: isExpandedOnlyBySearch = isExpandedBySearch && !isExpanded;
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
// .filter(x => filterName(filter, x.name, data.displayName, data.server))
</script>
<AppObjectList
list={_.sortBy(
dbList.filter(x => filterName(filter, x.name, data.displayName, data.server)),
x => x.sortOrder ?? x.name
).map(db => ({ ...db, connection: data }))}
list={_.sortBy(dbList, x => x.sortOrder ?? x.name).map(db => ({ ...db, connection: data }))}
module={databaseAppObject}
{passProps}
filter={isMainMatched ? '' : filter}
{isExpandedBySearch}
/>
@@ -0,0 +1,21 @@
<script lang="ts" context="module">
export const extractKey = ({ lineData }) => lineData;
</script>
<script lang="ts">
import AppObjectList from './AppObjectList.svelte';
import * as procedureLineAppObject from './ProcedureLineAppObject.svelte';
export let data;
export let filter;
export let isExpandedBySearch;
</script>
<AppObjectList
list={(data.createSql?.split('\n') || []).map(lineData => ({
lineData,
}))}
module={procedureLineAppObject}
{filter}
{isExpandedBySearch}
/>
@@ -5,6 +5,8 @@
import * as columnAppObject from './ColumnAppObject.svelte';
export let data;
export let filter;
export let isExpandedBySearch;
</script>
<AppObjectList
@@ -14,4 +16,6 @@
foreignKey: findForeignKeyForColumn(data, col),
}))}
module={columnAppObject}
{filter}
{isExpandedBySearch}
/>
@@ -60,6 +60,14 @@ export const defaultDatabaseObjectAppObjectActions = {
icon: 'img sql-file',
},
],
triggers: [
{
label: 'Show SQL',
tab: 'SqlObjectTab',
defaultActionId: 'showSql',
icon: 'img sql-file',
},
],
collections: [
{
label: 'Open data',
+18 -6
View File
@@ -7,20 +7,32 @@
export let icon = 'icon chevron-down';
export let menu;
export let asyncMenu = undefined;
export let narrow = false;
export let square = true;
export let disabled = false;
let domButton;
function handleClick() {
let domButton;
let isLoading = false;
async function handleClick() {
if (disabled) return;
let items = menu;
if (asyncMenu) {
isLoading = true;
items = await asyncMenu();
isLoading = false;
}
const rect = domButton.getBoundingClientRect();
const left = rect.left;
const top = rect.bottom;
currentDropDownMenu.set({ left, top, items: menu });
currentDropDownMenu.set({ left, top, items });
}
</script>
<InlineButton square {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
<FontIcon {icon} />
<InlineButton {square} {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
<FontIcon icon={isLoading ? 'icon loading' : icon} />
</InlineButton>
@@ -235,6 +235,7 @@
{columnIndex}
{allowChangeChangeSetStructure}
isSelected={selectedColumns.includes(column.uniqueName) || currentColumnUniqueName == column.uniqueName}
{filter}
on:click={() => {
if (domFocusField) domFocusField.focus();
selectedColumns = [column.uniqueName];
@@ -15,6 +15,7 @@
export let conid;
export let database;
export let isDynamicStructure;
export let filter = undefined;
export let tableInfo;
export let setTableInfo;
@@ -83,7 +84,7 @@
}}
/>
{/if}
<ColumnLabel {...column} showDataType {conid} {database} />
<ColumnLabel {...column} showDataType {conid} {database} {filter} />
</div>
{#if allowChangeChangeSetStructure && !isDynamicStructure}
@@ -54,6 +54,8 @@
// don't parse JSON for explicit data types
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;
$: showHint = allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName]);
</script>
<td
@@ -68,11 +70,12 @@
class:isDeleted
class:isAutofillSelected
class:isFocusedColumn
class:alignRight={_.isNumber(value) && !showHint}
{style}
>
<CellValue {rowData} {value} {jsonParsedValue} {editorTypes} />
{#if allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName])}
{#if showHint}
<span class="hint"
>{col.hintColumnNames.map(hintColumnName => rowData[hintColumnName]).join(col.hintColumnDelimiter || ' ')}</span
>
@@ -199,4 +202,9 @@
overflow: visible;
cursor: crosshair;
}
.alignRight {
color: var(--theme-icon-green);
text-align: var(--data-grid-numbers-align);
}
</style>
+33 -3
View File
@@ -483,6 +483,7 @@
let domFocusField;
let domHorizontalScroll;
let domVerticalScroll;
let domContainer;
let currentCell = topLeftCell;
let selectedCells = [topLeftCell];
@@ -492,7 +493,7 @@
let autofillSelectedCells = emptyCellArray;
const domFilterControlsRef = createRef({});
let isGridFocused=false;
let isGridFocused = false;
const tabid = getContext('tabid');
@@ -596,6 +597,7 @@
selectedCells = [cell];
await tick();
scrollIntoView(cell);
domFocusField?.focus();
}
export async function cloneRows() {
@@ -1838,11 +1840,11 @@
{/if}
</div>
{:else if isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
<div>
<div class="ml-2">
<ErrorInfo
alignTop
message={grider.editable
? 'No rows loaded, check filter or add new documents. You could copy documents from ohter collections/tables with Copy advanved/Copy as JSON command.'
? 'No rows loaded, check filter or add new documents. You could copy documents from other collections/tables with Copy advanved/Copy as JSON command.'
: 'No rows loaded'}
/>
{#if display.filterCount > 0}
@@ -1867,9 +1869,15 @@
class:data-grid-focused={isGridFocused}
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
bind:this={domContainer}
use:contextMenu={buildMenu}
use:contextMenuActivator={activator}
on:wheel={handleGridWheel}
on:click={e => {
if (e.target == domContainer) {
domFocusField?.focus();
}
}}
>
<input
type="text"
@@ -2014,6 +2022,24 @@
{/each}
</tbody>
</table>
{#if !isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
<div class="no-rows-info ml-2">
<div class="mb-3">
<ErrorInfo alignTop message="No rows loaded" icon="img info" />
</div>
{#if display.filterCount > 0}
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
{/if}
{#if grider.editable}
<FormStyledButton value="Add row" on:click={insertNewRow} />
{/if}
{#if onOpenQuery}
<FormStyledButton value="Open Query" on:click={onOpenQuery} />
{/if}
</div>
{/if}
<HorizontalScrollBar
minimum={0}
maximum={maxScrollColumn}
@@ -2092,4 +2118,8 @@
right: 40px;
bottom: 20px;
}
.no-rows-info {
margin-top: 60px;
}
</style>
@@ -9,7 +9,7 @@
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import ColumnManagerRow from './ColumnManagerRow.svelte';
import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
export let managerSize;
export let display: GridDisplay;
@@ -21,7 +21,6 @@
$: foreignKeys = display?.baseTable?.foreignKeys || [];
$: dependencies = display?.baseTable?.dependencies || [];
</script>
<SearchBoxWrapper>
@@ -46,7 +45,7 @@
>
<FontIcon icon="img link" />
<div class="ml-1 nowrap">
{fk.refTableName}
<TokenizedFilteredText text={fk.refTableName} {filter} />
({fk.columns.map(x => x.columnName).join(', ')})
</div>
</div>
@@ -70,7 +69,7 @@
>
<FontIcon icon="img reference" />
<div class="ml-1 nowrap">
{fk.pureName}
<TokenizedFilteredText text={fk.pureName} {filter} />
({fk.columns.map(x => x.columnName).join(', ')})
</div>
</div>
@@ -89,5 +88,4 @@
.link:hover {
text-decoration: underline;
}
</style>
+3 -1
View File
@@ -11,6 +11,7 @@
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
import Link from './Link.svelte';
export let notNull = false;
@@ -25,6 +26,7 @@
export let conid = undefined;
export let database = undefined;
export let iconOverride = undefined;
export let filter = undefined;
$: icon = iconOverride || getColumnIcon($$props, forceIcon);
</script>
@@ -33,7 +35,7 @@
{#if icon}
<FontIcon {icon} />
{/if}
{headerText || columnName}
<TokenizedFilteredText text={headerText || columnName} {filter} />
{#if extInfo}
<span class="extinfo">{extInfo}</span>
{/if}
@@ -9,6 +9,7 @@
export let disabled = false;
export let defaultValue;
export let menu;
export let asyncMenu;
const { values, setFieldValue } = getFormContext();
@@ -26,5 +27,5 @@
value={$values[name] ?? defaultValue}
on:input={e => setFieldValue(name, e.target['value'])}
/>
<DropDownButton {menu} {disabled} />
<DropDownButton {menu} {asyncMenu} {disabled} />
</div>
+5
View File
@@ -64,6 +64,7 @@
'icon add-folder': 'mdi mdi-folder-plus-outline',
'icon add-column': 'mdi mdi-table-column-plus-after',
'icon parameter': 'mdi mdi-at',
'icon trigger': 'mdi mdi-lightning-bolt',
'icon window-restore': 'mdi mdi-window-restore',
'icon window-maximize': 'mdi mdi-window-maximize',
@@ -143,6 +144,7 @@
'icon parent-filter': 'mdi mdi-home-alert',
'icon parent-filter-outline': 'mdi mdi-home-alert-outline',
'icon download': 'mdi mdi-download',
'icon text': 'mdi mdi-text',
'icon run': 'mdi mdi-play',
'icon chevron-down': 'mdi mdi-chevron-down',
@@ -286,6 +288,9 @@
'img import': 'mdi mdi-database-import color-icon-green',
'img export': 'mdi mdi-database-export color-icon-green',
'img transform': 'mdi mdi-rotate-orbit color-icon-blue',
'img tip': 'mdi mdi-lightbulb-on color-icon-yellow',
'img filter-active': 'mdi mdi-filter-cog color-icon-blue',
};
</script>
+23 -1
View File
@@ -55,6 +55,8 @@
let submenuItem;
let submenuOffset;
let switchIndex = 0;
const dispatch = createEventDispatcher();
let closeHandlers = [];
@@ -80,6 +82,14 @@
submenuOffset = hoverOffset;
return;
}
if (item.switchStore && item.switchValue) {
item.switchStore.update(x => ({
...x,
[item.switchValue]: !x[item.switchValue],
}));
switchIndex++;
return;
}
dispatchClose();
if (onCloseParent) onCloseParent();
if (item.onClick) item.onClick();
@@ -131,7 +141,18 @@
}}
>
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
{item.text || item.label}
<span>
{#if item.switchValue && item.switchStoreGetter}
{#key switchIndex}
{#if item.switchStoreGetter()[item.switchValue]}
<FontIcon icon="icon check" padRight />
{:else}
<FontIcon icon="icon invisible-box" padRight />
{/if}
{/key}
{/if}
{item.text || item.label}
</span>
{#if item.keyText}
<span class="keyText">{formatKeyText(item.keyText)}</span>
{/if}
@@ -179,6 +200,7 @@
white-space: nowrap;
overflow-y: auto;
max-height: calc(100% - 20px);
user-select: none;
}
.keyText {
@@ -16,8 +16,12 @@
import FormColorField from '../forms/FormColorField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
import { getConnectionLabel } from 'dbgate-tools';
const { values } = getFormContext();
export let getDatabaseList;
export let currentConnection;
const { values, setFieldValue } = getFormContext();
const electron = getElectron();
$: authType = $values.authType;
@@ -69,6 +73,14 @@
'me-central-1',
'sa-east-1',
];
async function createDatabasesMenu() {
const databases = await getDatabaseList();
return databases.map(db => ({
text: db.name,
onClick: () => setFieldValue('defaultDatabase', db.name),
}));
}
</script>
<FormSelectField
@@ -377,11 +389,13 @@
{/if}
{#if driver?.showConnectionField('defaultDatabase', $values, showConnectionFieldArgs)}
<FormTextField
<FormDropDownTextField
label="Default database"
name="defaultDatabase"
disabled={isConnected}
data-testid="ConnectionDriverFields_defaultDatabase"
asyncMenu={createDatabasesMenu}
placeholder="(not selected - optional)"
/>
{/if}
@@ -412,7 +426,8 @@
templateProps={{ noMargin: true }}
disabled={isConnected}
data-testid="ConnectionDriverFields_displayName"
/>
placeholder={getConnectionLabel(currentConnection)}
/>
</div>
<div class="col-6 mr-1">
<FormColorField
@@ -28,7 +28,12 @@
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
</script>
<FormCheckboxField label="Use SSH tunnel" name="useSshTunnel" disabled={isConnected} />
<FormCheckboxField
label="Use SSH tunnel"
name="useSshTunnel"
disabled={isConnected}
data-testid="ConnectionSshTunnelFields_useSshTunnel"
/>
<div class="row">
<div class="col-9 mr-1">
@@ -37,6 +42,7 @@
name="sshHost"
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshHost"
/>
</div>
<div class="col-3">
@@ -46,6 +52,7 @@
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
placeholder="22"
data-testid="ConnectionSshTunnelFields_sshPort"
/>
</div>
</div>
@@ -62,10 +69,16 @@
{ value: 'agent', label: 'SSH agent' },
{ value: 'keyFile', label: 'Key file' },
]}
data-testid="ConnectionSshTunnelFields_sshMode"
/>
{#if ($values.sshMode || 'userPassword') != 'userPassword'}
<FormTextField label="Login" name="sshLogin" disabled={isConnected || !useSshTunnel} />
<FormTextField
label="Login"
name="sshLogin"
disabled={isConnected || !useSshTunnel}
data-testid="ConnectionSshTunnelFields_sshLogin"
/>
{/if}
{#if ($values.sshMode || 'userPassword') == 'userPassword'}
@@ -76,6 +89,7 @@
name="sshLogin"
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshLogin"
/>
</div>
<div class="col-6">
@@ -84,6 +98,7 @@
name="sshPassword"
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshPassword"
/>
</div>
</div>
@@ -99,6 +114,7 @@
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
defaultFileName={$platformInfo?.defaultKeyfile}
data-testid="ConnectionSshTunnelFields_sshKeyfile"
/>
{:else}
<FormTextField
@@ -107,6 +123,7 @@
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
placeholder={$platformInfo?.defaultKeyfile}
data-testid="ConnectionSshTunnelFields_sshKeyfile"
/>
{/if}
</div>
@@ -116,6 +133,7 @@
name="sshKeyfilePassword"
disabled={isConnected || !useSshTunnel}
templateProps={{ noMargin: true }}
data-testid="ConnectionSshTunnelFields_sshKeyfilePassword"
/>
</div>
</div>
+50 -1
View File
@@ -100,7 +100,7 @@ ORDER BY
{ label: 'Connection', slot: 2 },
{ label: 'Themes', slot: 3 },
{ label: 'Default Actions', slot: 4 },
{ label: 'Confirmations', slot: 5 },
{ label: 'Behaviour', slot: 5 },
{ label: 'Other', slot: 6 },
]}
>
@@ -138,6 +138,8 @@ ORDER BY
defaultValue="10"
/>
<FormCheckboxField name="dataGrid.alignNumbersRight" label="Align numbers to right" defaultValue={false} />
<div class="heading">SQL editor</div>
<div class="flex">
@@ -278,6 +280,30 @@ ORDER BY
</svelte:fragment>
<svelte:fragment slot="4">
<div class="heading">Default actions</div>
<FormSelectField
label="Connection click"
name="defaultAction.connectionClick"
isNative
defaultValue="connect"
options={[
{ value: 'openDetails', label: 'Edit / open details' },
{ value: 'connect', label: 'Connect' },
{ value: 'none', label: 'Do nothing' },
]}
/>
<FormSelectField
label="Database click"
name="defaultAction.databaseClick"
isNative
defaultValue="switch"
options={[
{ value: 'switch', label: 'Switch database' },
{ value: 'none', label: 'Do nothing' },
]}
/>
<FormCheckboxField name="defaultAction.useLastUsedAction" label="Use last used action" defaultValue={true} />
<FormDefaultActionField
@@ -307,6 +333,24 @@ ORDER BY
/>
</svelte:fragment>
<svelte:fragment slot="5">
<div class="heading">Behaviour</div>
<FormCheckboxField name="behaviour.useTabPreviewMode" label="Use tab preview mode" defaultValue={true} />
<div class="tip">
<FontIcon icon="img tip" /> When you single-click or select a file in the "Tables, Views, Functions" view, it
is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing
tables and don't want every visited table to have its own tab. When you start editing the table or use double-click
to open the table from the "Tables" view, a new tab is dedicated to that table.
</div>
<FormCheckboxField
name="behaviour.openDetailOnArrows"
label="Open detail on keyboard navigation"
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
/>
<div class="heading">Confirmations</div>
<FormCheckboxField name="skipConfirm.tableDataSave" label="Skip confirmation when saving table data (SQL)" />
@@ -381,6 +425,11 @@ ORDER BY
margin-top: var(--dim-large-form-margin);
}
.tip {
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
.themes {
overflow-x: scroll;
display: flex;
@@ -28,3 +28,18 @@ export function getStringSettingsValue(name, defaultValue) {
if (res == null) return defaultValue;
return res;
}
export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | 'none' {
return getStringSettingsValue('defaultAction.connectionClick', 'connect');
}
export function getDatabaseClickActionSetting(): 'switch' | 'none' {
return getStringSettingsValue('defaultAction.databaseClick', 'switch');
}
export function getOpenDetailOnArrowsSettings(): boolean {
return (
getBoolSettingsValue('behaviour.useTabPreviewMode', true) &&
getBoolSettingsValue('behaviour.openDetailOnArrows', true)
);
}
+50 -1
View File
@@ -161,6 +161,35 @@ export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultAc
export const selectedDatabaseObjectAppObject = writable(null);
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(null);
export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
pureName: true,
schemaName: false,
columnName: false,
columnDataType: false,
tableComment: true,
columnComment: false,
sqlObjectText: false,
tableEngine: false,
};
export const DEFAULT_CONNECTION_SEARCH_SETTINGS = {
displayName: true,
server: true,
user: false,
engine: false,
database: true,
};
export const databaseObjectAppObjectSearchSettings = writableWithStorage(
DEFAULT_OBJECT_SEARCH_SETTINGS,
'databaseObjectAppObjectSearchSettings2'
);
export const connectionAppObjectSearchSettings = writableWithStorage(
DEFAULT_CONNECTION_SEARCH_SETTINGS,
'connectionAppObjectSearchSettings2'
);
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme)
);
@@ -186,6 +215,9 @@ export const visibleTitleBar = derived(useSettings(), $settings => {
// console.log('nativeMenuOnStartup', nativeMenuOnStartup);
return !$settings['app.fullscreen'] && !nativeMenuOnStartup;
});
export const alignDataGridNumbersToRight = derived(useSettings(), $settings => {
return !!$settings?.['dataGrid.alignNumbersRight'];
});
export const visibleHamburgerMenuWidget = derived(useSettings(), $settings => {
const electron = getElectron();
@@ -199,6 +231,7 @@ subscribeCssVariable(visibleSelectedWidget, x => (x ? 1 : 0), '--dim-visible-lef
subscribeCssVariable(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
subscribeCssVariable(visibleTitleBar, x => (x ? 1 : 0), '--dim-visible-titlebar');
subscribeCssVariable(lockedDatabaseMode, x => (x ? 0 : 1), '--dim-visible-tabs-databases');
subscribeCssVariable(alignDataGridNumbersToRight, x => (x ? 'right' : 'left'), '--data-grid-numbers-align');
let activeTabIdValue = null;
activeTabId.subscribe(value => {
@@ -358,4 +391,20 @@ let lastUsedDefaultActionsValue = {};
lastUsedDefaultActions.subscribe(value => {
lastUsedDefaultActionsValue = value;
});
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_OBJECT_SEARCH_SETTINGS = {
...DEFAULT_OBJECT_SEARCH_SETTINGS,
};
databaseObjectAppObjectSearchSettings.subscribe(value => {
databaseObjectAppObjectSearchSettingsValue = value;
});
export const getDatabaseObjectAppObjectSearchSettings = () => databaseObjectAppObjectSearchSettingsValue;
let connectionAppObjectSearchSettingsValue: typeof DEFAULT_CONNECTION_SEARCH_SETTINGS = {
...DEFAULT_CONNECTION_SEARCH_SETTINGS,
};
connectionAppObjectSearchSettings.subscribe(value => {
connectionAppObjectSearchSettingsValue = value;
});
export const getConnectionAppObjectSearchSettings = () => connectionAppObjectSearchSettingsValue;
+34 -14
View File
@@ -63,15 +63,16 @@
const testIdRef = createRef(0);
async function handleTest(e) {
async function handleTest(e, requestDbList = false) {
isTesting = true;
testIdRef.update(x => x + 1);
const testid = testIdRef.get();
const resp = await apiCall('connections/test', e.detail);
const resp = await apiCall('connections/test', { connection: e.detail, requestDbList });
if (testIdRef.get() != testid) return;
isTesting = false;
sqlConnectResult = resp;
return resp;
}
function handleCancelTest() {
@@ -80,6 +81,10 @@
}
function getCurrentConnection() {
return getCurrentConnectionCore($values, driver);
}
function getCurrentConnectionCore(values, driver) {
const allProps = [
'databaseFile',
'useDatabaseUrl',
@@ -94,15 +99,15 @@
'socketPath',
'serviceName',
];
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values, { config: $config }));
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, values, { config: $config }));
const omitProps = _.difference(allProps, visibleProps);
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
if (!values.defaultDatabase) omitProps.push('singleDatabase');
let connection: Dictionary<string | boolean> = _.omit($values, omitProps);
let connection: Dictionary<string | boolean> = _.omit(values, omitProps);
if (driver?.beforeConnectionSave) connection = driver?.beforeConnectionSave(connection);
if (driver?.showConnectionTab('sshTunnel', $values)) {
if (!$values.useSshTunnel) {
if (driver?.showConnectionTab('sshTunnel', values)) {
if (!values.useSshTunnel) {
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
}
} else {
@@ -110,8 +115,8 @@
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
}
if (driver?.showConnectionTab('ssl', $values)) {
if (!$values.useSsl) {
if (driver?.showConnectionTab('ssl', values)) {
if (!values.useSsl) {
connection = _.omitBy(connection, (v, k) => k.startsWith('ssl'));
}
} else {
@@ -122,6 +127,8 @@
return connection;
}
$: currentConnection = getCurrentConnectionCore($values, driver);
async function handleSave() {
let connection = getCurrentConnection();
connection = {
@@ -190,6 +197,14 @@
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
// $: console.log('CONN VALUES', $values);
async function getDatabaseList() {
const resp = await handleTest({ detail: getCurrentConnection() }, true);
if (resp && resp.msgtype == 'connected') {
return resp.databases;
}
return [];
}
</script>
<FormProviderCore template={FormFieldTemplateLarge} {values}>
@@ -202,6 +217,7 @@
{
label: 'General',
component: ConnectionDriverFields,
props: { getDatabaseList, currentConnection },
},
driver?.showConnectionTab('sshTunnel', $values) && {
label: 'SSH Tunnel',
@@ -223,20 +239,24 @@
<div class="buttons">
{#if onlyTestButton}
{#if isTesting}
<FormButton value="Cancel test" on:click={handleCancelTest} data-testid="ConnectionTab_buttonCancelTest" />
<FormButton
value="Cancel test"
on:click={handleCancelTest}
data-testid="ConnectionTab_buttonCancelTest"
/>
{:else}
<FormButton value="Test connection" on:click={handleTest} data-testid="ConnectionTab_buttonDisconnect" />
{/if}
{:else if isConnected}
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid='ConnectionTab_buttonDisconnect' />
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid="ConnectionTab_buttonDisconnect" />
{:else}
<FormButton value="Connect" on:click={handleConnect} data-testid='ConnectionTab_buttonConnect' />
<FormButton value="Connect" on:click={handleConnect} data-testid="ConnectionTab_buttonConnect" />
{#if isTesting}
<FormButton value="Cancel test" on:click={handleCancelTest} />
{:else}
<FormButton value="Test" on:click={handleTest} data-testid='ConnectionTab_buttonTest' />
<FormButton value="Test" on:click={handleTest} data-testid="ConnectionTab_buttonTest" />
{/if}
<FormButton value="Save" on:click={handleSave} data-testid='ConnectionTab_buttonSave' />
<FormButton value="Save" on:click={handleSave} data-testid="ConnectionTab_buttonSave" />
{/if}
</div>
<div class="test-result">
@@ -166,6 +166,13 @@ export function getSupportedScriptTemplates(objectTypeField: string): { label: s
scriptTemplate: 'CALL OBJECT',
},
];
case 'triggers':
return [
{
label: 'CREATE TRIGGER',
scriptTemplate: 'CREATE OBJECT',
},
];
}
return [];
@@ -12,6 +12,7 @@ import { shouldShowTab } from '../tabpanel/TabsPanel.svelte';
import { callWhenAppLoaded, getAppLoaded } from './appLoadManager';
import { getConnectionInfo } from './metadataLoaders';
import { switchCurrentDatabase } from './common';
import { extractDbNameFromComposite, isCompositeDbName } from 'dbgate-tools';
// let lastCurrentTab = null;
@@ -82,7 +83,10 @@ currentDatabase.subscribe(currentDb => {
if (currentDb) {
focusedConnectionOrDatabase.set({
conid: currentDb.connection?._id,
database: currentDb.name,
database:
currentDb.connection?.useSeparateSchemas && isCompositeDbName(currentDb.name)
? extractDbNameFromComposite(currentDb.name)
: currentDb.name,
connection: currentDb.connection,
});
} else {
+20 -2
View File
@@ -2,10 +2,12 @@ import _ from 'lodash';
import uuidv1 from 'uuid/v1';
import { getActiveTab, getOpenedTabs, openedTabs } from '../stores';
import tabs from '../tabs';
import { setSelectedTabFunc } from './common';
import { setSelectedTabFunc, switchCurrentDatabase } from './common';
import localforage from 'localforage';
import stableStringify from 'json-stable-stringify';
import { saveAllPendingEditorData } from '../query/useEditorData';
import { getConnectionInfo } from './metadataLoaders';
import { getBoolSettingsValue } from '../settings/settingsTools';
function findFreeNumber(numbers: number[]) {
if (numbers.length == 0) return 1;
@@ -21,7 +23,23 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
let existing = null;
const { savedFile, savedFolder, savedFilePath } = newTab.props || {};
if (!getBoolSettingsValue('behaviour.useTabPreviewMode', true) && newTab.tabPreviewMode) {
newTab = {
...newTab,
tabPreviewMode: false,
};
}
const { savedFile, savedFolder, savedFilePath, conid, database } = newTab.props || {};
if (conid && database) {
const connection = await getConnectionInfo({ conid });
await switchCurrentDatabase({
connection,
name: database,
});
}
const { tabPreviewMode } = newTab;
if (savedFile || savedFilePath) {
existing = oldTabs.find(
@@ -16,6 +16,7 @@
let isListFocused = false;
let domDiv = null;
let lastInputMethod = null;
export let hideContent = false;
function handleKeyDown(ev) {
@@ -25,6 +26,11 @@
const selected = getSelectedObject();
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
if (index < 0) {
focusFirst();
return;
}
if (index == 0 && diff < 0) {
onFocusFilterBox?.();
return;
@@ -41,7 +47,7 @@
if (listInstance[newIndex]) {
selectedObjectStore.set(listInstance[newIndex]);
handleObjectClick?.(listInstance[newIndex], { tabPreviewMode: true });
handleObjectClick?.(listInstance[newIndex], 'keyArrow');
}
if (newIndex == 0) {
@@ -58,7 +64,7 @@
ev.preventDefault();
}
if (ev.keyCode == keycodes.enter) {
handleObjectClick?.(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
handleObjectClick?.(getSelectedObject(), 'keyEnter');
ev.preventDefault();
}
if (ev.keyCode == keycodes.pageDown) {
@@ -72,14 +78,14 @@
if (ev.keyCode == keycodes.home) {
if (listInstance[0]) {
selectedObjectStore.set(listInstance[0]);
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
handleObjectClick?.(listInstance[0], 'keyArrow');
onScrollTop?.();
}
}
if (ev.keyCode == keycodes.end) {
if (listInstance[listInstance.length - 1]) {
selectedObjectStore.set(listInstance[listInstance.length - 1]);
handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
handleObjectClick?.(listInstance[listInstance.length - 1], 'keyArrow');
}
}
if (ev.keyCode == keycodes.numPadAdd) {
@@ -110,16 +116,18 @@
domDiv?.focus();
if (listInstance[0]) {
selectedObjectStore.set(listInstance[0]);
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
handleObjectClick?.(listInstance[0], 'keyArrow');
onScrollTop?.();
}
}
async function handleFocus() {
async function handleFocus(e) {
isListFocused = true;
// await tick();
await sleep(100);
// console.log('ON FOCUS AFTER SLEEP');
if (lastInputMethod == 'mouse') {
return;
}
const listInstance = _.isFunction(list) ? list() : list;
const selected = getSelectedObject();
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
@@ -129,7 +137,7 @@
const index2 = _.findIndex(listInstance, x => selectedObjectMatcher(x, focused));
if (index2 >= 0) {
selectedObjectStore.set(focused);
handleObjectClick?.(focused, { tabPreviewMode: true });
handleObjectClick?.(focused, 'keyArrow');
return;
}
}
@@ -138,6 +146,15 @@
}
</script>
<svelte:window
on:keydown={() => {
lastInputMethod = 'keyboard';
}}
on:mousedown={() => {
lastInputMethod = 'mouse';
}}
/>
<div
tabindex="0"
on:keydown={handleKeyDown}
+65 -15
View File
@@ -20,6 +20,8 @@
getFocusedConnectionOrDatabase,
currentDatabase,
getCurrentConfig,
connectionAppObjectSearchSettings,
getConnectionAppObjectSearchSettings,
} from '../stores';
import runCommand from '../commands/runCommand';
import { filterName, getConnectionLabel } from 'dbgate-tools';
@@ -38,6 +40,13 @@
import { switchCurrentDatabase } from '../utility/common';
import openNewTab from '../utility/openNewTab';
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
import {
getBoolSettingsValue,
getConnectionClickActionSetting,
getDatabaseClickActionSetting,
getOpenDetailOnArrowsSettings,
} from '../settings/settingsTools';
import DropDownButton from '../buttons/DropDownButton.svelte';
const connections = useConnectionList();
const serverStatus = useServerStatus();
@@ -181,6 +190,21 @@
{ text: 'Delete', onClick: handleDelete },
];
}
function createSearchMenu() {
const res = [];
res.push({ label: 'Search by:', isBold: true, disabled: true });
res.push({ label: 'Display name', switchValue: 'displayName' });
res.push({ label: 'Server', switchValue: 'server' });
res.push({ label: 'User', switchValue: 'user' });
res.push({ label: 'Database engine', switchValue: 'engine' });
res.push({ label: 'Database name', switchValue: 'database' });
return res.map(item => ({
...item,
switchStore: connectionAppObjectSearchSettings,
switchStoreGetter: getConnectionAppObjectSearchSettings,
}));
}
</script>
<SearchBoxWrapper>
@@ -193,8 +217,18 @@
}}
/>
<CloseSearchButton bind:filter />
<DropDownButton
icon={filter ? 'img filter-active' : 'icon filter'}
menu={createSearchMenu}
square={!!filter}
narrow={false}
/>
{#if $commandsCustomized['new.connection']?.enabled}
<InlineButton on:click={() => runCommand('new.connection')} title="Add new connection" data-testid="ConnectionList_buttonNewConnection">
<InlineButton
on:click={() => runCommand('new.connection')}
title="Add new connection"
data-testid="ConnectionList_buttonNewConnection"
>
<FontIcon icon="icon plus-thick" />
</InlineButton>
<InlineButton on:click={() => runCommand('new.connection.folder')} title="Add new connection folder">
@@ -234,28 +268,35 @@
onFocusFilterBox={text => {
domFilter?.focus(text);
}}
handleObjectClick={(data, options) => {
handleObjectClick={(data, clickAction) => {
const connectionClickAction = getConnectionClickActionSetting();
const databaseClickAction = getDatabaseClickActionSetting();
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
if (data.database) {
if (options.focusTab) {
if ($openedSingleDatabaseConnections.includes(data.conid)) {
switchCurrentDatabase({ connection: data.connection, name: data.database });
} else {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
// console.log('FOCUSING DB', passProps);
// passProps?.onFocusSqlObjectList?.();
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
} else {
if (options.focusTab) {
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
openConnection(data.connection);
} else {
const config = getCurrentConfig();
if (config.runAsPortal == false && !config.storageDatabase) {
if (
config.runAsPortal == false &&
!config.storageDatabase &&
connectionClickAction == 'openDetails' &&
(clickAction == 'leftClick' || (clickAction == 'keyArrow' && openDetailOnArrows))
) {
openNewTab({
title: getConnectionLabel(data.connection),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: options.tabPreviewMode,
tabPreviewMode: true,
props: {
conid: data.conid,
},
@@ -278,7 +319,12 @@
expandOnClick
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
{filter}
passProps={{ ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
passProps={{
...passProps,
connectionColorFactory: $connectionColorFactory,
showPinnedInsteadOfUnpin: true,
searchSettings: $connectionAppObjectSearchSettings,
}}
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
setIsExpanded={(data, value) => {
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
@@ -302,7 +348,11 @@
expandOnClick
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
{filter}
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
passProps={{
connectionColorFactory: $connectionColorFactory,
showPinnedInsteadOfUnpin: true,
searchSettings: $connectionAppObjectSearchSettings,
}}
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
setIsExpanded={(data, value) => {
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
@@ -13,6 +13,7 @@
import { filterName } from 'dbgate-tools';
import { currentDatabase, focusedConnectionOrDatabase, getFocusedConnectionOrDatabase } from '../stores';
import { switchCurrentDatabase } from '../utility/common';
import { getConnectionClickActionSetting, getDatabaseClickActionSetting } from '../settings/settingsTools';
export let connection;
@@ -81,9 +82,16 @@
onFocusFilterBox={text => {
domFilter?.focus(text);
}}
handleObjectClick={(data, options) => {
handleObjectClick={(data, clickAction) => {
const connectionClickAction = getConnectionClickActionSetting();
const databaseClickAction = getDatabaseClickActionSetting();
if (data.database) {
if (options.focusTab) {
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
}
+52 -15
View File
@@ -27,7 +27,7 @@
import AppObjectList from '../appobj/AppObjectList.svelte';
import _ from 'lodash';
import * as databaseObjectAppObject from '../appobj/DatabaseObjectAppObject.svelte';
import SubColumnParamList from '../appobj/SubColumnParamList.svelte';
import SubTableColumnList from '../appobj/SubTableColumnList.svelte';
import { chevronExpandIcon } from '../icons/expandIcons';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import LoadingInfo from '../elements/LoadingInfo.svelte';
@@ -35,11 +35,13 @@
import DropDownButton from '../buttons/DropDownButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import { findEngineDriver } from 'dbgate-tools';
import { extractDbNameFromComposite, findEngineDriver } from 'dbgate-tools';
import {
currentDatabase,
databaseObjectAppObjectSearchSettings,
extensions,
focusedConnectionOrDatabase,
getDatabaseObjectAppObjectSearchSettings,
getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject,
} from '../stores';
@@ -53,6 +55,7 @@
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
import SubProcedureParamList from '../appobj/SubProcedureParamList.svelte';
import SubProcedureLineList from '../appobj/SubProcedureLineList.svelte';
export let conid;
export let database;
@@ -77,7 +80,7 @@
// $: console.log('OBJECTS', $objects);
$: objectList = _.flatten([
...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers'].map(objectTypeField =>
_.sortBy(
(($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
['schemaName', 'pureName']
@@ -124,11 +127,31 @@
return res;
}
$: flatFilteredList = objectList.filter(data => {
const matcher = databaseObjectAppObject.createMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
});
function createSearchMenu() {
const res = [];
res.push({ label: 'Search by:', isBold: true, disabled: true });
if (driver?.databaseEngineTypes?.includes('document')) {
res.push({ label: 'Collection name', switchValue: 'pureName' });
}
if (driver?.databaseEngineTypes?.includes('sql')) {
res.push({ label: 'Table/view/procedure name', switchValue: 'pureName' });
res.push({ label: 'Schema', switchValue: 'schemaName' });
res.push({ label: 'Column name', switchValue: 'columnName' });
res.push({ label: 'Column data type', switchValue: 'columnType' });
res.push({ label: 'Table comment', switchValue: 'tableComment' });
res.push({ label: 'Column comment', switchValue: 'columnComment' });
res.push({ label: 'View/procedure/trigger text', switchValue: 'sqlObjectText' });
res.push({ label: 'Table engine', switchValue: 'tableEngine' });
}
return res.map(item => ({
...item,
switchStore: databaseObjectAppObjectSearchSettings,
switchStoreGetter: getDatabaseObjectAppObjectSearchSettings,
}));
}
$: matcher = databaseObjectAppObject.createMatcher(filter, $databaseObjectAppObjectSearchSettings);
$: flatFilteredList = objectList.filter(data => !matcher || matcher(data));
export function focus() {
domListHandler?.focusFirst();
@@ -137,7 +160,8 @@
$: differentFocusedDb =
$focusedConnectionOrDatabase &&
($focusedConnectionOrDatabase.conid != conid ||
($focusedConnectionOrDatabase?.database && $focusedConnectionOrDatabase?.database != database));
($focusedConnectionOrDatabase?.database &&
$focusedConnectionOrDatabase?.database != extractDbNameFromComposite(database)));
</script>
{#if $status && $status.name == 'error'}
@@ -183,7 +207,7 @@
{:else}
<SearchBoxWrapper>
<SearchInput
placeholder="Search in tables, objects, # prefix in columns"
placeholder="Search in tables, views, procedures"
bind:value={filter}
bind:this={domFilter}
onFocusFilteredList={() => {
@@ -191,7 +215,15 @@
}}
/>
<CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
<DropDownButton
icon={filter ? 'img filter-active' : 'icon filter'}
menu={createSearchMenu}
square={!!filter}
narrow={false}
/>
{#if !filter}
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
{/if}
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
<FontIcon icon="icon refresh" />
</InlineButton>
@@ -220,7 +252,7 @@
selectedObjectStore={selectedDatabaseObjectAppObject}
getSelectedObject={getSelectedDatabaseObjectAppObject}
selectedObjectMatcher={matchDatabaseObjectAppObject}
handleObjectClick={(data, options) => databaseObjectAppObject.handleObjectClick(data, options)}
handleObjectClick={(data, clickAction) => databaseObjectAppObject.handleObjectClick(data, clickAction)}
onScrollTop={() => {
domContainer?.scrollTop();
}}
@@ -239,10 +271,14 @@
.map(x => ({ ...x, conid, database }))}
module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
subItemsComponent={data =>
subItemsComponent={(data, { isExpandedBySearch }) =>
data.objectTypeField == 'procedures' || data.objectTypeField == 'functions'
? SubProcedureParamList
: SubColumnParamList}
? isExpandedBySearch
? SubProcedureLineList
: SubProcedureParamList
: isExpandedBySearch && (data.objectTypeField == 'views' || data.objectTypeField == 'matviews')
? SubProcedureLineList
: SubTableColumnList}
isExpandable={data =>
data.objectTypeField == 'tables' ||
data.objectTypeField == 'views' ||
@@ -255,6 +291,7 @@
showPinnedInsteadOfUnpin: true,
connection: $connection,
hideSchemaName: !!$appliedCurrentSchema,
searchSettings: $databaseObjectAppObjectSearchSettings,
}}
getIsExpanded={data =>
expandedObjects.includes(`${data.objectTypeField}||${data.schemaName}||${data.pureName}`)}
@@ -0,0 +1,26 @@
<script lang="ts">
import { tokenizeBySearchFilter } from 'dbgate-tools';
export let text = '';
export let filter = '';
$: tokenized = filter ? tokenizeBySearchFilter(text, filter) : null;
</script>
{#if tokenized}
{#each tokenized as token}
{#if token.isMatch}
<span class="highlight">{token.text}</span>
{:else}
{token.text}
{/if}
{/each}
{:else}
{text}
{/if}
<style>
.highlight {
background-color: var(--theme-bg-orange);
}
</style>
+22
View File
@@ -0,0 +1,22 @@
diff --git a/node_modules/ace-builds/src-noconflict/mode-sqlserver.js b/node_modules/ace-builds/src-noconflict/mode-sqlserver.js
index 7e61d3d..3e0fb97 100644
--- a/node_modules/ace-builds/src-noconflict/mode-sqlserver.js
+++ b/node_modules/ace-builds/src-noconflict/mode-sqlserver.js
@@ -304,7 +304,7 @@ var BaseFoldMode = require("./cstyle").FoldMode;
var FoldMode = exports.FoldMode = function () { };
oop.inherits(FoldMode, BaseFoldMode);
(function () {
- this.foldingStartMarker = /(\bCASE\b|\bBEGIN\b)|^\s*(\/\*)/i;
+ this.foldingStartMarker = /(?<!\[)(\bCASE\b|\bBEGIN\b)(?!\])|^\s*(\/\*)/i;
this.startRegionRe = /^\s*(\/\*|--)#?region\b/;
this.getFoldWidgetRange = function (session, foldStyle, row, forceMultiline) {
var line = session.getLine(row);
@@ -337,7 +337,7 @@ oop.inherits(FoldMode, BaseFoldMode);
var maxRow = session.getLength();
var line;
var depth = 1;
- var re = /(\bCASE\b|\bBEGIN\b)|(\bEND\b)/i;
+ var re = /(?<!\[)(\bCASE\b|\bBEGIN\b)|(\bEND\b)(?!\])/i;
while (++row < maxRow) {
line = session.getLine(row);
var m = re.exec(line);

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