Compare commits
391 Commits
develop
...
v5.5.7-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dd73e7319 | ||
|
|
c4fe4b40dd | ||
|
|
251137ac60 | ||
|
|
0ad7c99274 | ||
|
|
f53142d98a | ||
|
|
1f868523b0 | ||
|
|
94db02db2e | ||
|
|
9f0e06e663 | ||
|
|
c6dab85fc2 | ||
|
|
59aa2e3f33 | ||
|
|
21b26773e6 | ||
|
|
f308c5f6b0 | ||
|
|
2763b6028a | ||
|
|
a3df6d6e7d | ||
|
|
473bfcbec5 | ||
|
|
8976c9e653 | ||
|
|
86795dcc63 | ||
|
|
9b90f15621 | ||
|
|
7d0d9d3e22 | ||
|
|
17f0248a3e | ||
|
|
25d3dcad59 | ||
|
|
cbd857422f | ||
|
|
e65b4d0c2a | ||
|
|
6bcebb63e4 | ||
|
|
ac7708138c | ||
|
|
9d8ec9cc6b | ||
|
|
1b8a2cb923 | ||
|
|
a97ab9c09e | ||
|
|
9a73eb3620 | ||
|
|
f50e460335 | ||
|
|
fa72d9a39f | ||
|
|
75b4f49e31 | ||
|
|
a069093f6b | ||
|
|
62c741198a | ||
|
|
0266d912e0 | ||
|
|
55bc0fc93f | ||
|
|
47c00d7eb0 | ||
|
|
ad9fac861e | ||
|
|
14afd08fcb | ||
|
|
319580554f | ||
|
|
c750bd04ad | ||
|
|
bdd55d8432 | ||
|
|
98464e414b | ||
|
|
2f2d9c45a3 | ||
|
|
3665a0d064 | ||
|
|
c19c69266a | ||
|
|
bafa2c2fff | ||
|
|
2d823140b9 | ||
|
|
1fb4a06092 | ||
|
|
cb450a0313 | ||
|
|
7aaf6bb024 | ||
|
|
6a02ba3220 | ||
|
|
83610783e0 | ||
|
|
cec26b0614 | ||
|
|
fbf288198b | ||
|
|
193940fd63 | ||
|
|
bd169c316a | ||
|
|
5315f65cfb | ||
|
|
5a859d81d3 | ||
|
|
904fc4d500 | ||
|
|
634fe18127 | ||
|
|
89a9cc4380 | ||
|
|
57c62fbe27 | ||
|
|
590eff1e3b | ||
|
|
a71309a604 | ||
|
|
343e983b64 | ||
|
|
e31a52b659 | ||
|
|
41162ee2c3 | ||
|
|
55745c18e9 | ||
|
|
7d6b77ad2a | ||
|
|
90813b23d8 | ||
|
|
a999d29b1d | ||
|
|
f6f9b0a61a | ||
|
|
724edf44cb | ||
|
|
07248ca49f | ||
|
|
3a068c37b5 | ||
|
|
df44e5f6e9 | ||
|
|
9328d966ba | ||
|
|
1a293deec7 | ||
|
|
665a70ba3d | ||
|
|
967587c8e4 | ||
|
|
4927c13e55 | ||
|
|
1f9f997748 | ||
|
|
521e4ea3a2 | ||
|
|
941843e4c0 | ||
|
|
1434a42421 | ||
|
|
8f57d3a316 | ||
|
|
a74b789a8c | ||
|
|
ac4dd37249 | ||
|
|
75b3b4e012 | ||
|
|
188ab4c483 | ||
|
|
f9a562808d | ||
|
|
4ea763124b | ||
|
|
836d15c68f | ||
|
|
8ce4c0a7ce | ||
|
|
9613c2c410 | ||
|
|
e5d4bbadc1 | ||
|
|
5d4d2a447a | ||
|
|
d905962298 | ||
|
|
4ab9ad6881 | ||
|
|
2ce20b5fac | ||
|
|
81297383cb | ||
|
|
2aed60390c | ||
|
|
67386da136 | ||
|
|
bdc40c2c02 | ||
|
|
1d916e43d5 | ||
|
|
98bff4925a | ||
|
|
9af2c80b05 | ||
|
|
3a03c82f8d | ||
|
|
de66e75eb2 | ||
|
|
10d79dca4d | ||
|
|
460f511bf6 | ||
|
|
81207f95d8 | ||
|
|
3d3aca3290 | ||
|
|
d661b9f6a4 | ||
|
|
7deeb78d69 | ||
|
|
4dcf47b81f | ||
|
|
a674b9b3a1 | ||
|
|
b2aa4d9377 | ||
|
|
bc4a595815 | ||
|
|
2704825d03 | ||
|
|
456d3ba42e | ||
|
|
803a272331 | ||
|
|
89e4bfe3e1 | ||
|
|
697440693e | ||
|
|
c0cd04a96f | ||
|
|
c92f02bdda | ||
|
|
c4766d163c | ||
|
|
18889092aa | ||
|
|
adafa3c5c4 | ||
|
|
91994016a7 | ||
|
|
dfcf253217 | ||
|
|
bd3503912f | ||
|
|
b062c5fd66 | ||
|
|
b1cd60d0dd | ||
|
|
67ac1a1c8d | ||
|
|
c166eab2e8 | ||
|
|
c6b3ced493 | ||
|
|
4e922e806d | ||
|
|
0e087565b3 | ||
|
|
104b25d898 | ||
|
|
9bdff41ec1 | ||
|
|
d27b0a7be3 | ||
|
|
076b20ef6d | ||
|
|
d93d107039 | ||
|
|
708dcfd088 | ||
|
|
14501a70b9 | ||
|
|
3b82679c2d | ||
|
|
48d4fb4fec | ||
|
|
a03ca73d93 | ||
|
|
a46e592cfb | ||
|
|
634bea1bda | ||
|
|
3dfa23a30c | ||
|
|
24408dd7c2 | ||
|
|
32c7919885 | ||
|
|
967615b6e5 | ||
|
|
aee3a28465 | ||
|
|
3fdf27f820 | ||
|
|
e9302c7d6f | ||
|
|
c38fe83e48 | ||
|
|
adfc427d25 | ||
|
|
3912a58127 | ||
|
|
720d25e838 | ||
|
|
bc1d77a6f8 | ||
|
|
00a8d472ff | ||
|
|
5076ee0463 | ||
|
|
4a5ddd65f4 | ||
|
|
ec3c224f44 | ||
|
|
39b99ecf8f | ||
|
|
50232f9b90 | ||
|
|
66e8cc6e1d | ||
|
|
f6b4c94d00 | ||
|
|
f3ce240bcd | ||
|
|
7be9bf1bab | ||
|
|
d39871be70 | ||
|
|
3324eec011 | ||
|
|
dd9790fca5 | ||
|
|
41a3769c5f | ||
|
|
42601ff960 | ||
|
|
e54cffbaf3 | ||
|
|
f8dbad362c | ||
|
|
925db50418 | ||
|
|
f40db68579 | ||
|
|
3afde5f1fa | ||
|
|
b631519009 | ||
|
|
f05c60c628 | ||
|
|
e235de6d56 | ||
|
|
da47c437e0 | ||
|
|
f0048bc6cf | ||
|
|
f80ae284fa | ||
|
|
06753ff312 | ||
|
|
1e07614306 | ||
|
|
2d716ba43a | ||
|
|
c39dc6295d | ||
|
|
7557666135 | ||
|
|
5ee65124cb | ||
|
|
ab79617377 | ||
|
|
3b2a47a4ef | ||
|
|
7b4d9d8717 | ||
|
|
4c9734ac7f | ||
|
|
152e8a80ab | ||
|
|
98a348b091 | ||
|
|
e448f63ec3 | ||
|
|
0b13850eca | ||
|
|
de97404602 | ||
|
|
cbd6ce7872 | ||
|
|
3c479eb33c | ||
|
|
beaff158cc | ||
|
|
6806620d90 | ||
|
|
4459347169 | ||
|
|
98e01497e9 | ||
|
|
4ed4c8c32c | ||
|
|
620acecdff | ||
|
|
2d214cfdb3 | ||
|
|
cd36259739 | ||
|
|
d049d8c571 | ||
|
|
7c51fcad96 | ||
|
|
1948c8ef89 | ||
|
|
f2b2ac6fd0 | ||
|
|
4098f63ce2 | ||
|
|
4d903abd85 | ||
|
|
f00eb2d3ef | ||
|
|
6752dcfd39 | ||
|
|
c3022eb80a | ||
|
|
2f6cbf25df | ||
|
|
8dfc2e7bcd | ||
|
|
fa0ad477cc | ||
|
|
ac6a68c38d | ||
|
|
25223471e7 | ||
|
|
56535b1e6f | ||
|
|
131c51f7c4 | ||
|
|
a27a2077ed | ||
|
|
7758fabc89 | ||
|
|
c9da9bdd23 | ||
|
|
9520b053af | ||
|
|
8a1e717c1b | ||
|
|
21127f661a | ||
|
|
7d614a2395 | ||
|
|
669ae024f9 | ||
|
|
9d8dd558e2 | ||
|
|
67f58a8dfe | ||
|
|
52d230b9e2 | ||
|
|
fd2a35fb4a | ||
|
|
d61b5e135f | ||
|
|
ef23b786ac | ||
|
|
29a66bfcb0 | ||
|
|
ef5e30df3d | ||
|
|
ab28a06bef | ||
|
|
9910c54aa6 | ||
|
|
6ec431f471 | ||
|
|
3ec7f651c1 | ||
|
|
87aa60bc3e | ||
|
|
73874aa5a1 | ||
|
|
976438f860 | ||
|
|
eb095b7c44 | ||
|
|
3ca745e74b | ||
|
|
040de84d93 | ||
|
|
4f1d63440e | ||
|
|
7c3cf1bb67 | ||
|
|
cbf1b0a3cc | ||
|
|
5287a86397 | ||
|
|
ae599ac6f6 | ||
|
|
a08a8ef208 | ||
|
|
19a4d97765 | ||
|
|
6f1f5f84c6 | ||
|
|
e2f352149d | ||
|
|
1fa39b20d2 | ||
|
|
53dc2e6f03 | ||
|
|
555f30c0b3 | ||
|
|
7549d37a04 | ||
|
|
29072eb71b | ||
|
|
48e9e77be5 | ||
|
|
4dd3f15ba3 | ||
|
|
3603501ae2 | ||
|
|
338180a21a | ||
|
|
28193ed6f3 | ||
|
|
0509710602 | ||
|
|
a4872b4159 | ||
|
|
888f5c6260 | ||
|
|
7a5abb5f47 | ||
|
|
61a9f02899 | ||
|
|
354c4201f7 | ||
|
|
d8340087c5 | ||
|
|
e3249c6d79 | ||
|
|
58e65608e4 | ||
|
|
2b6fdf5a6a | ||
|
|
967d7849ee | ||
|
|
7c476ab2f0 | ||
|
|
caaf35e45a | ||
|
|
6ddc9ee6c5 | ||
|
|
39aa250223 | ||
|
|
031a92db8e | ||
|
|
0c2579897f | ||
|
|
b63479bf45 | ||
|
|
4c89552265 | ||
|
|
517002e079 | ||
|
|
85bfb1986d | ||
|
|
632421eb73 | ||
|
|
21365be411 | ||
|
|
eaa54022fc | ||
|
|
55f7f39efd | ||
|
|
71e709b346 | ||
|
|
1d2d295a45 | ||
|
|
5ed23beff0 | ||
|
|
43a8db55a2 | ||
|
|
0a9cba7bf7 | ||
|
|
02af761bf7 | ||
|
|
6882a146e7 | ||
|
|
c9834f9792 | ||
|
|
21b4baf700 | ||
|
|
d3a24627dd | ||
|
|
8aac9cf59d | ||
|
|
ce70b2e71a | ||
|
|
62a5ef60f6 | ||
|
|
95430e9c11 | ||
|
|
1cee36cc9b | ||
|
|
aa475f81a0 | ||
|
|
1173d5db1d | ||
|
|
f34d0cbb90 | ||
|
|
780d187911 | ||
|
|
48d4374346 | ||
|
|
6b8b511d0d | ||
|
|
c6be115634 | ||
|
|
dadde225f1 | ||
|
|
31dfc1dc28 | ||
|
|
1de163af44 | ||
|
|
2181eada53 | ||
|
|
75e63d2710 | ||
|
|
fbfcdcbc40 | ||
|
|
0238e6a7f1 | ||
|
|
be17301c91 | ||
|
|
b1118c7f43 | ||
|
|
24bf5e5b0c | ||
|
|
122471f81f | ||
|
|
a6136cee25 | ||
|
|
83357ba2cc | ||
|
|
4fe10b26b0 | ||
|
|
485f6c9759 | ||
|
|
732c5b763b | ||
|
|
4431d08a88 | ||
|
|
cb7224ac94 | ||
|
|
66b39c1f80 | ||
|
|
b30f139b5d | ||
|
|
f39ec26c29 | ||
|
|
8c3c32aeba | ||
|
|
9eb27f5e92 | ||
|
|
3e5b45de8f | ||
|
|
e7b4a6ffcc | ||
|
|
e7ec75138d | ||
|
|
6c4679d83b | ||
|
|
5f23b29c4e | ||
|
|
55db98fe1b | ||
|
|
f7c5ffa0ce | ||
|
|
d1e98e5640 | ||
|
|
e785fdf9b7 | ||
|
|
fc0db925c5 | ||
|
|
5ab686b721 | ||
|
|
327d43096f | ||
|
|
1f7b632553 | ||
|
|
592d7987ab | ||
|
|
c429424fda | ||
|
|
0b4709d383 | ||
|
|
336929ff3f | ||
|
|
677f83cc4b | ||
|
|
5c58c35a64 | ||
|
|
b346a458a6 | ||
|
|
226512a4ca | ||
|
|
a0527d78e9 | ||
|
|
3357295d98 | ||
|
|
fc6a43b4fe | ||
|
|
260b2e4b12 | ||
|
|
f080b18d3f | ||
|
|
56f015ffd5 | ||
|
|
fd8a28831e | ||
|
|
503e09ddd1 | ||
|
|
880912806a | ||
|
|
665ce22741 | ||
|
|
e5c9ec7681 | ||
|
|
74fceeec78 | ||
|
|
77d60ccfa5 | ||
|
|
0c2b25f79a | ||
|
|
4065e05013 | ||
|
|
319a7fd003 | ||
|
|
26c01f43f9 | ||
|
|
88d7e07bea | ||
|
|
a9a5a3491e | ||
|
|
d255273368 | ||
|
|
a7846b4adf | ||
|
|
ce431e6e21 | ||
|
|
f8e39a2a5d | ||
|
|
e5135b1a9d |
3
.github/workflows/build-app-beta.yaml
vendored
3
.github/workflows/build-app-beta.yaml
vendored
@@ -47,9 +47,6 @@ jobs:
|
||||
yarn printSecrets
|
||||
env:
|
||||
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
|
||||
5
.github/workflows/build-app-pro-beta.yaml
vendored
5
.github/workflows/build-app-pro-beta.yaml
vendored
@@ -87,11 +87,6 @@ jobs:
|
||||
yarn printSecrets
|
||||
env:
|
||||
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
cd ..
|
||||
|
||||
5
.github/workflows/build-app-pro.yaml
vendored
5
.github/workflows/build-app-pro.yaml
vendored
@@ -88,11 +88,6 @@ jobs:
|
||||
yarn printSecrets
|
||||
env:
|
||||
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
cd ..
|
||||
|
||||
3
.github/workflows/build-app.yaml
vendored
3
.github/workflows/build-app.yaml
vendored
@@ -50,9 +50,6 @@ jobs:
|
||||
yarn printSecrets
|
||||
env:
|
||||
GIST_UPLOAD_SECRET : ${{secrets.GIST_UPLOAD_SECRET}}
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
|
||||
142
.github/workflows/build-aws-pro-beta.yaml
vendored
Normal file
142
.github/workflows/build-aws-pro-beta.yaml
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
name: AWS image PREMIUM
|
||||
|
||||
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 }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
env:
|
||||
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 }}
|
||||
path: dbgate-pro
|
||||
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
mv dbgate-pro/* ../dbgate-pro/
|
||||
cd ..
|
||||
mkdir dbgate-merged
|
||||
cd dbgate-pro
|
||||
cd sync
|
||||
yarn
|
||||
node sync.js --nowatch
|
||||
cd ..
|
||||
|
||||
- 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}}
|
||||
|
||||
- name: Prepare packer build
|
||||
run: |
|
||||
cd ..
|
||||
cd dbgate-merged
|
||||
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 }}
|
||||
path: artifacts
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: 'artifacts/**'
|
||||
prerelease: true
|
||||
env:
|
||||
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}}
|
||||
|
||||
- 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
|
||||
|
||||
- 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}}
|
||||
1
.github/workflows/run-tests.yaml
vendored
1
.github/workflows/run-tests.yaml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- 'feature/**'
|
||||
|
||||
jobs:
|
||||
test-runner:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,8 +28,6 @@ docker/plugins
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
app/src/nativeModulesContent.js
|
||||
packages/api/src/nativeModulesContent.js
|
||||
packages/api/src/packagedPluginsContent.js
|
||||
.VSCodeCounter
|
||||
|
||||
|
||||
75
CHANGELOG.md
75
CHANGELOG.md
@@ -8,10 +8,83 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### Not published
|
||||
- ADDED: Order or filter the indexes for huge tables #922
|
||||
- ADDED: Empty string filters
|
||||
- CHANGED: (Premium) Workflow for new installation (used in Docker and AWS distribution)
|
||||
|
||||
|
||||
### 5.5.6
|
||||
- FIXED: DbGate process consumes 100% after UI closed - Mac, Linux (#917, #915)
|
||||
- FIXED: Correctly closing connection behind SSH tunnel (#920)
|
||||
- FIXED: Updating MongoDB documents on MongoDB 4 (#916)
|
||||
- FIXED: (Premium) DbGate container correctly waits for underlying storage database, if database container is started after dbgate container is started
|
||||
- FIXED: (Premium) Better handling of connection storage errors
|
||||
|
||||
### 5.5.5
|
||||
- ADDED: AWS IAM authentication for MySQL, MariaDB, PostgreSQL (Premium)
|
||||
- FIXED: Datitme filtering #912
|
||||
- FIXED: Load redis keys
|
||||
- ADDED: Query parameters #913
|
||||
- FIXED: Data grid with hidden columns #911
|
||||
- ADDED: Added buttons for one-click authentification methods (Anonymous, OAuth) (Team Premium)
|
||||
- ADDED: Link for switching Admin/user login (Team Premium)
|
||||
- FIXED: Save connection params in administration for MS SQL and Postgres storages (Team Premium)
|
||||
|
||||
### 5.5.4
|
||||
- FIXED: correct handling when use LOGIN and PASSWORD env variables #903
|
||||
- FIXED: fixed problems in dbmodel commandline tool
|
||||
- ADDED: dbmodel - allow connection defined in environment variables
|
||||
- FIXED: Load postgres schema on Azure #906
|
||||
- FIXED: Oauth2 in combination with Google doesn't log payload #727
|
||||
- CHANGED: Improved error reporting for unhandler errors
|
||||
- CHANGED: Don't restart docker container in case of unhandler error
|
||||
- FIXED: Crash when displaying specific data values from MongoDB #908
|
||||
- ADDED: (Premium) Show purchase button after trial license is expired
|
||||
|
||||
### 5.5.3
|
||||
- FIXED: Separate schema mode #894 - for databases with many schemas
|
||||
- FIXED: Sort by UUID column in POstgreSQL #895
|
||||
- ADDED: Load pg_dump outputs #893
|
||||
- ADDED: Improved column mapping in import/export #330
|
||||
- FIXED: Fixed some errors in create-table workflow
|
||||
- CHANGED: Show single schema by default only if all objects are from default schema
|
||||
- FIXED: MS Entra authentication for Azure SQL
|
||||
|
||||
### 5.5.2
|
||||
- FIXED: MySQL, PostgreSQL readonly conections #900
|
||||
|
||||
### 5.5.1
|
||||
- ADDED: Clickhouse support (#532)
|
||||
- ADDED: MySQL - specify table engine, show table engine in table list
|
||||
- FIXED: Hidden primary key name in PK editor for DB engines with anonymous PK (MySQL)
|
||||
- CHANGED: Import/export dialog is now tacub instead of modal
|
||||
- ADDED: Saving import/export job
|
||||
- REMOVED: Ability to reopen export/import wizard from generated script. This was a bit hack, now you could save import/export job instead
|
||||
- ADDED: Autodetect CSV delimited
|
||||
- FIXED: Import CSV files with spaces around quotes
|
||||
- ADDED: JSON file import
|
||||
- ADDED: JSON export can export objects with ID field used as object key
|
||||
- ADDED: JSON and JSON lines imports supports importing from web URL
|
||||
- FIXED: Editing imported URL in job editor
|
||||
- ADDED: Quick export from table data grid (#892)
|
||||
- CHANGED: Create table workflow is reworked, you can specify schema and table name in table editor
|
||||
- FIXED: After saving new table, table editor is reset to empty state
|
||||
- ADDED: (PostgreSQL, SQL Server) - ability to filter objects by schema
|
||||
- ADDED: (PostgreSQL, SQL Server) - Use separate schemas option - for databases with lot of schemas, only selected schema is loaded
|
||||
- FIXED: Internal refactor of drivers, client objects are not more messed up with auxiliary fields
|
||||
- ADDED: Copy connection error to clipboard after clicking on error icon
|
||||
- FIXED: (MySQL) Fixed importing SQL dump exported from mysqldump (#702)
|
||||
- FIXED: (PostgreSQL) Fixed filtering JSONB fields (#889)
|
||||
- FIXED: OIDC authentication not working anymore (#891)
|
||||
- ADDED: Added tests for import from CSV and JSON
|
||||
- FIXED: multiple shortcuts handling #898
|
||||
- ADDED: (Premium) MS Entra authentization for Azure SQL databases
|
||||
|
||||
### 5.4.4
|
||||
- CHANGED: Improved autoupdate, notification is now in app
|
||||
- CHANGED: Default behaviour of autoupdate, new version is downloaded after click of "Download" button
|
||||
- ADDED: Ability to configure autoupdate (check only, check+download, don't check)\
|
||||
- ADDED: Ability to configure autoupdate (check only, check+download, don't check)
|
||||
- ADDED: Option to run check for new version manually
|
||||
- FIXED: Fixed autoupgrade channel for premium edition
|
||||
- FIXED: Fixes following issues: #886, #865, #782, #375
|
||||
|
||||
@@ -26,10 +26,11 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
* MongoDB
|
||||
* Redis
|
||||
* SQLite
|
||||
* Amazon Redshift
|
||||
* Amazon Redshift (Premium)
|
||||
* CockroachDB
|
||||
* MariaDB
|
||||
* CosmosDB (Premium)
|
||||
* ClickHouse
|
||||
|
||||
<!-- Learn more about DbGate features at the [DbGate website](https://dbgate.org/), or try our online [demo application](https://demo.dbgate.org) -->
|
||||
|
||||
@@ -69,8 +70,8 @@ DbGate is licensed under GPL-3.0 license and is free to use for any purpose.
|
||||
* Redis tree view, generate script from keys, run Redis script
|
||||
* Runs as application for Windows, Linux and Mac. Or in Docker container on server and in web Browser on client.
|
||||
* Import, export from/to CSV, Excel, JSON, NDJSON, XML
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
* Archives - backup your data in NDJSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* NDJSON data viewer and editor - browse NDJSON data, edit data and structure directly on NDJSON files. Works also for big NDSON files
|
||||
* Charts, export chart to HTML page
|
||||
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Extensible plugin architecture
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function adjustFile(file) {
|
||||
const json = JSON.parse(fs.readFileSync(file, { encoding: 'utf-8' }));
|
||||
|
||||
for (const packageName of fs.readdirSync('plugins')) {
|
||||
if (!packageName.startsWith('dbgate-plugin-')) continue;
|
||||
const pluginJson = JSON.parse(
|
||||
fs.readFileSync(path.join('plugins', packageName, 'package.json'), { encoding: 'utf-8' })
|
||||
);
|
||||
for (const depkey of ['dependencies', 'optionalDependencies']) {
|
||||
for (const dependency of Object.keys(pluginJson[depkey] || {})) {
|
||||
if (!json[depkey]) {
|
||||
json[depkey] = {};
|
||||
}
|
||||
if (json[depkey][dependency]) {
|
||||
if (json[depkey][dependency] != pluginJson[depkey][dependency]) {
|
||||
console.log(`Dependency ${dependency} in ${packageName} is different from ${file}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
json[depkey][dependency] = pluginJson[depkey][dependency];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform != 'win32') {
|
||||
delete json.optionalDependencies.msnodesqlv8;
|
||||
}
|
||||
|
||||
fs.writeFileSync(file, JSON.stringify(json, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
|
||||
@@ -130,10 +130,5 @@
|
||||
"electron": "30.0.2",
|
||||
"electron-builder": "23.1.0",
|
||||
"electron-builder-notarize": "^1.5.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^6.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +430,6 @@ function createWindow() {
|
||||
);
|
||||
|
||||
global.API_PACKAGE = apiPackage;
|
||||
global.NATIVE_MODULES = path.join(__dirname, 'nativeModules');
|
||||
|
||||
// console.log('global.API_PACKAGE', global.API_PACKAGE);
|
||||
const api = require(apiPackage);
|
||||
|
||||
@@ -9,9 +9,9 @@ module.exports = ({ editMenu }) => [
|
||||
{ command: 'new.queryDesign', hideDisabled: true },
|
||||
{ command: 'new.diagram', hideDisabled: true },
|
||||
{ command: 'new.perspective', hideDisabled: true },
|
||||
{ command: 'new.freetable', hideDisabled: true },
|
||||
{ command: 'new.shell', hideDisabled: true },
|
||||
{ command: 'new.jsonl', hideDisabled: true },
|
||||
{ command: 'new.modelTransform', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'file.open', hideDisabled: true },
|
||||
{ command: 'file.openArchive', hideDisabled: true },
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
const content = require('./nativeModulesContent');
|
||||
|
||||
module.exports = content;
|
||||
@@ -1,24 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
let fillContent = '';
|
||||
|
||||
if (process.platform == 'win32') {
|
||||
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');\n`;
|
||||
}
|
||||
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');\n`;
|
||||
fillContent += `content['oracledb'] = () => require('oracledb');\n`;
|
||||
|
||||
const getContent = empty => `
|
||||
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
||||
const content = {};
|
||||
|
||||
${empty ? '' : fillContent}
|
||||
|
||||
module.exports = content;
|
||||
`;
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/api/src/nativeModulesContent.js',
|
||||
getContent(process.argv.includes('--electron') ? true : false)
|
||||
);
|
||||
fs.writeFileSync('app/src/nativeModulesContent.js', getContent(false));
|
||||
@@ -5,10 +5,12 @@ const { testWrapper } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
const { getAlterDatabaseScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
|
||||
|
||||
function flatSource() {
|
||||
const initSql = ['CREATE TABLE t1 (id int primary key)', 'CREATE TABLE t2 (id int primary key)'];
|
||||
|
||||
function flatSource(engineCond = x => !x.skipReferences) {
|
||||
return _.flatten(
|
||||
engines
|
||||
.filter(x => !x.skipReferences)
|
||||
.filter(engineCond)
|
||||
.map(engine => (engine.objects || []).map(object => [engine.label, object.type, object, engine]))
|
||||
);
|
||||
}
|
||||
@@ -66,4 +68,24 @@ describe('Alter database', () => {
|
||||
expect(db[type].length).toEqual(0);
|
||||
})
|
||||
);
|
||||
|
||||
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 });
|
||||
|
||||
await driver.query(conn, object.create1, { discardResult: true });
|
||||
|
||||
const structure = extendDatabaseInfo(await driver.analyseFull(conn));
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dmp.renameSqlObject(structure[type][0], 'renamed1');
|
||||
|
||||
await driver.query(conn, dmp.s);
|
||||
|
||||
const structure2 = await driver.analyseFull(conn);
|
||||
expect(structure2[type].length).toEqual(1);
|
||||
expect(structure2[type][0].pureName).toEqual('renamed1');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,12 +7,15 @@ const crypto = require('crypto');
|
||||
const { getAlterTableScript, extendDatabaseInfo, generateDbPairingId } = require('dbgate-tools');
|
||||
|
||||
function pickImportantTableInfo(engine, table) {
|
||||
const props = ['columnName'];
|
||||
const props = ['columnName', 'defaultValue'];
|
||||
if (!engine.skipNullability) props.push('notNull');
|
||||
if (!engine.skipAutoIncrement) props.push('autoIncrement');
|
||||
return {
|
||||
pureName: table.pureName,
|
||||
columns: table.columns.filter(x => x.columnName != 'rowid').map(fp.pick(props)),
|
||||
columns: table.columns
|
||||
.filter(x => x.columnName != 'rowid')
|
||||
.map(fp.pick(props))
|
||||
.map(props => _.omitBy(props, x => x == null)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,4 +139,31 @@ describe('Alter table', () => {
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Add default value - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testTableDiff(engine, conn, driver, tbl => {
|
||||
tbl.columns.find(x => x.columnName == 'col_std').defaultValue = '123';
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Unset default value - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testTableDiff(engine, conn, driver, tbl => {
|
||||
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = undefined;
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Change default value - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testTableDiff(engine, conn, driver, tbl => {
|
||||
tbl.columns.find(x => x.columnName == 'col_def').defaultValue = '567';
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -91,4 +91,68 @@ describe('Data duplicator', () => {
|
||||
expect(res2.rows[0].cnt.toString()).toEqual('6');
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipDataDuplicator).map(engine => [engine.label, engine]))(
|
||||
'Skip nullable weak refs - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
runCommandOnDriver(conn, driver, dmp =>
|
||||
dmp.createTable({
|
||||
pureName: 't1',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', notNull: true },
|
||||
{ columnName: 'val', dataType: 'varchar(50)' },
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
runCommandOnDriver(conn, driver, dmp =>
|
||||
dmp.createTable({
|
||||
pureName: 't2',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true },
|
||||
{ columnName: 'val', dataType: 'varchar(50)' },
|
||||
{ columnName: 'valfk', dataType: 'int', notNull: false },
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
},
|
||||
foreignKeys: [{ refTableName: 't1', columns: [{ columnName: 'valfk', refColumnName: 'id' }] }],
|
||||
})
|
||||
);
|
||||
runCommandOnDriver(conn, driver, dmp => dmp.put("insert into ~t1 (~id, ~val) values (1, 'first')"));
|
||||
|
||||
const gett2 = () =>
|
||||
stream.Readable.from([
|
||||
{ __isStreamHeader: true, __isDynamicStructure: true },
|
||||
{ id: 1, val: 'v1', valfk: 1 },
|
||||
{ id: 2, val: 'v2', valfk: 2 },
|
||||
]);
|
||||
|
||||
await dataDuplicator({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
items: [
|
||||
{
|
||||
name: 't2',
|
||||
operation: 'copy',
|
||||
openStream: gett2,
|
||||
},
|
||||
],
|
||||
options: {
|
||||
setNullForUnresolvedNullableRefs: true,
|
||||
},
|
||||
});
|
||||
|
||||
const res1 = await driver.query(conn, `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`);
|
||||
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`);
|
||||
expect(res3.rows[0].cnt.toString()).toEqual('1');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const stream = require('stream');
|
||||
const { testWrapper } = require('../tools');
|
||||
const tableWriter = require('dbgate-api/src/shell/tableWriter');
|
||||
const copyStream = require('dbgate-api/src/shell/copyStream');
|
||||
const importDatabase = require('dbgate-api/src/shell/importDatabase');
|
||||
const fakeObjectReader = require('dbgate-api/src/shell/fakeObjectReader');
|
||||
|
||||
function createImportStream() {
|
||||
@@ -72,4 +73,29 @@ describe('DB Import', () => {
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.dumpFile).map(engine => [engine.label, engine]))(
|
||||
'Import SQL dump - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
// const reader = await fakeObjectReader({ delay: 10 });
|
||||
// const reader = await fakeObjectReader();
|
||||
await importDatabase({
|
||||
systemConnection: conn,
|
||||
driver,
|
||||
inputFile: engine.dumpFile,
|
||||
});
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
for (const check of engine.dumpChecks || []) {
|
||||
const res = await driver.query(conn, check.sql);
|
||||
expect(res.rows[0].res.toString()).toEqual(check.res);
|
||||
}
|
||||
|
||||
// const res1 = await driver.query(conn, `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`);
|
||||
// expect(res2.rows[0].cnt.toString()).toEqual('6');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,34 +1,98 @@
|
||||
/// TODO
|
||||
|
||||
const { testWrapper } = require('../tools');
|
||||
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 generateDeploySql = require('dbgate-api/src/shell/generateDeploySql');
|
||||
const connectUtility = require('dbgate-api/src/utility/connectUtility');
|
||||
|
||||
function checkStructure(structure, model) {
|
||||
function checkStructure(
|
||||
engine,
|
||||
structure,
|
||||
model,
|
||||
{ checkRenameDeletedObjects = false, disallowExtraObjects = false } = {}
|
||||
) {
|
||||
const expected = databaseInfoFromYamlModel(model);
|
||||
expect(structure.tables.length).toEqual(expected.tables.length);
|
||||
|
||||
for (const [realTable, expectedTable] of _.zip(
|
||||
_.sortBy(structure.tables, 'pureName'),
|
||||
_.sortBy(expected.tables, 'pureName')
|
||||
)) {
|
||||
expect(realTable.columns.length).toBeGreaterThanOrEqual(expectedTable.columns.length);
|
||||
for (const expectedTable of expected.tables) {
|
||||
const realTable = structure.tables.find(x => x.pureName == expectedTable.pureName);
|
||||
|
||||
for (const column of expectedTable.columns) {
|
||||
const realColumn = realTable.columns.find(x => x.columnName == column.columnName);
|
||||
expect(realColumn).toBeTruthy();
|
||||
if (!engine.skipNullability) {
|
||||
expect(realColumn.notNull).toEqual(column.notNull);
|
||||
}
|
||||
}
|
||||
|
||||
for (const realColumn of realTable.columns) {
|
||||
const column = expectedTable.columns.find(x => x.columnName == realColumn.columnName);
|
||||
if (!column) {
|
||||
if (checkRenameDeletedObjects) {
|
||||
expect(realColumn.columnName).toMatch(/^_deleted_/);
|
||||
}
|
||||
|
||||
if (disallowExtraObjects) {
|
||||
expect(realColumn).toBeFalsy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const realTable of structure.tables) {
|
||||
const expectedTable = expected.tables.find(x => x.pureName == realTable.pureName);
|
||||
if (!expectedTable) {
|
||||
if (checkRenameDeletedObjects) {
|
||||
expect(realTable.pureName).toMatch(/^_deleted_/);
|
||||
}
|
||||
|
||||
if (disallowExtraObjects) {
|
||||
expect(realTable).toBeFalsy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const expectedView of expected.views) {
|
||||
const realView = structure.views.find(x => x.pureName == expectedView.pureName);
|
||||
expect(realView).toBeTruthy();
|
||||
}
|
||||
|
||||
for (const realView of structure.views) {
|
||||
const expectedView = expected.views.find(x => x.pureName == realView.pureName);
|
||||
if (!expectedView) {
|
||||
if (disallowExtraObjects) {
|
||||
expect(realView).toBeFalsy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testDatabaseDeploy(conn, driver, dbModelsYaml, testEmptyLastScript) {
|
||||
async function testDatabaseDeploy(engine, conn, driver, dbModelsYaml, options) {
|
||||
const { testEmptyLastScript, finalCheckAgainstModel, markDeleted, allowDropStatements } = options || {};
|
||||
let index = 0;
|
||||
const dbdiffOptionsExtra = markDeleted
|
||||
? {
|
||||
deletedTablePrefix: '_deleted_',
|
||||
deletedColumnPrefix: '_deleted_',
|
||||
deletedSqlObjectPrefix: '_deleted_',
|
||||
}
|
||||
: {};
|
||||
dbdiffOptionsExtra.schemaMode = 'ignore';
|
||||
|
||||
for (const loadedDbModel of dbModelsYaml) {
|
||||
const { sql, isEmpty } = await generateDeploySql({
|
||||
systemConnection: conn,
|
||||
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
||||
connection: conn.isPreparedOnly ? conn : undefined,
|
||||
driver,
|
||||
loadedDbModel,
|
||||
dbdiffOptionsExtra,
|
||||
});
|
||||
console.debug('Generated deploy script:', sql);
|
||||
expect(sql.toUpperCase().includes('DROP ')).toBeFalsy();
|
||||
if (!allowDropStatements) {
|
||||
expect(sql.toUpperCase().includes('DROP ')).toBeFalsy();
|
||||
}
|
||||
|
||||
console.log('dbModelsYaml.length', dbModelsYaml.length, index);
|
||||
if (testEmptyLastScript && index == dbModelsYaml.length - 1) {
|
||||
@@ -36,23 +100,45 @@ async function testDatabaseDeploy(conn, driver, dbModelsYaml, testEmptyLastScrip
|
||||
}
|
||||
|
||||
await deployDb({
|
||||
systemConnection: conn,
|
||||
systemConnection: conn.isPreparedOnly ? undefined : conn,
|
||||
connection: conn.isPreparedOnly ? conn : undefined,
|
||||
driver,
|
||||
loadedDbModel,
|
||||
dbdiffOptionsExtra,
|
||||
});
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
checkStructure(structure, dbModelsYaml[dbModelsYaml.length - 1]);
|
||||
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 ?? dbModelsYaml[dbModelsYaml.length - 1], options);
|
||||
}
|
||||
|
||||
describe('Deploy database', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Deploy database simple - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(conn, driver, [
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
json: {
|
||||
name: 't1',
|
||||
columns: [{ name: 'id', type: 'int' }],
|
||||
primaryKey: ['id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Deploy database simple - %s - not connected',
|
||||
testWrapperPrepareOnly(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
@@ -71,6 +157,7 @@ describe('Deploy database', () => {
|
||||
'Deploy database simple twice - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
@@ -95,7 +182,7 @@ describe('Deploy database', () => {
|
||||
},
|
||||
],
|
||||
],
|
||||
true
|
||||
{ testEmptyLastScript: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
@@ -103,7 +190,7 @@ describe('Deploy database', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Add column - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(conn, driver, [
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
@@ -135,6 +222,7 @@ describe('Deploy database', () => {
|
||||
'Dont drop column - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
@@ -162,7 +250,7 @@ describe('Deploy database', () => {
|
||||
},
|
||||
],
|
||||
],
|
||||
true
|
||||
{ testEmptyLastScript: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
@@ -171,6 +259,7 @@ describe('Deploy database', () => {
|
||||
'Foreign keys - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
@@ -217,7 +306,7 @@ describe('Deploy database', () => {
|
||||
},
|
||||
],
|
||||
],
|
||||
true
|
||||
{ testEmptyLastScript: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
@@ -225,7 +314,7 @@ describe('Deploy database', () => {
|
||||
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
|
||||
'Deploy preloaded data - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(conn, driver, [
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
@@ -254,7 +343,7 @@ describe('Deploy database', () => {
|
||||
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
|
||||
'Deploy preloaded data - update - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(conn, driver, [
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
@@ -301,7 +390,7 @@ describe('Deploy database', () => {
|
||||
test.each(engines.enginesPostgre.map(engine => [engine.label, engine]))(
|
||||
'Current timestamp default value - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(conn, driver, [
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.table.yaml',
|
||||
@@ -326,4 +415,275 @@ describe('Deploy database', () => {
|
||||
expect(res.rows[0].val.toString().substring(0, 2)).toEqual('20');
|
||||
})
|
||||
);
|
||||
|
||||
const T1 = {
|
||||
name: 't1.table.yaml',
|
||||
json: {
|
||||
name: 't1',
|
||||
columns: [
|
||||
{ name: 'id', type: 'int' },
|
||||
{ name: 'val', type: 'int' },
|
||||
],
|
||||
primaryKey: ['id'],
|
||||
},
|
||||
};
|
||||
|
||||
const T1_DELETED = {
|
||||
name: '_deleted_t1.table.yaml',
|
||||
json: {
|
||||
name: '_deleted_t1',
|
||||
columns: [
|
||||
{ name: 'id', type: 'int' },
|
||||
{ name: 'val', type: 'int' },
|
||||
],
|
||||
primaryKey: ['id'],
|
||||
},
|
||||
};
|
||||
|
||||
const T1_NO_VAL = {
|
||||
name: 't1.table.yaml',
|
||||
json: {
|
||||
name: 't1',
|
||||
columns: [{ name: 'id', type: 'int' }],
|
||||
primaryKey: ['id'],
|
||||
},
|
||||
};
|
||||
|
||||
const T1_DELETED_VAL = {
|
||||
name: 't1.table.yaml',
|
||||
json: {
|
||||
name: 't1',
|
||||
columns: [
|
||||
{ name: 'id', type: 'int' },
|
||||
{ name: '_deleted_val', type: 'int' },
|
||||
],
|
||||
primaryKey: ['id'],
|
||||
},
|
||||
};
|
||||
|
||||
const V1 = {
|
||||
name: 'v1.view.sql',
|
||||
text: 'create view v1 as select * from t1',
|
||||
};
|
||||
|
||||
const V1_VARIANT2 = {
|
||||
name: 'v1.view.sql',
|
||||
text: 'create view v1 as select 1 as c1',
|
||||
};
|
||||
|
||||
const V1_DELETED = {
|
||||
name: '_deleted_v1.view.sql',
|
||||
text: 'create view _deleted_v1 as select * from t1',
|
||||
};
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Dont remove column - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1], [T1_NO_VAL]], {
|
||||
finalCheckAgainstModel: [T1],
|
||||
disallowExtraObjects: true,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Dont remove table - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1], []], {
|
||||
finalCheckAgainstModel: [T1],
|
||||
disallowExtraObjects: true,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Mark table removed - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1], [], []], {
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
finalCheckAgainstModel: [T1_DELETED],
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(engine => engine.supportRenameSqlObject).map(engine => [engine.label, engine]))(
|
||||
'Mark view removed - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1, V1], [T1], [T1]], {
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
finalCheckAgainstModel: [T1, V1_DELETED],
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Mark column removed - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1], [T1_NO_VAL]], {
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
finalCheckAgainstModel: [T1_DELETED_VAL],
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Undelete table - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
[T1],
|
||||
// delete table
|
||||
[],
|
||||
// undelete table
|
||||
[T1],
|
||||
],
|
||||
{
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(engine => engine.supportRenameSqlObject).map(engine => [engine.label, engine]))(
|
||||
'Undelete view - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1, V1], [T1], [T1, V1]], {
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
allowDropStatements: true,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Undelete column - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [[T1], [T1_NO_VAL], [T1]], {
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'View redeploy - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
[T1, V1],
|
||||
[T1, V1],
|
||||
[T1, V1],
|
||||
],
|
||||
{
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
allowDropStatements: true,
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Change view - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(
|
||||
engine,
|
||||
conn,
|
||||
driver,
|
||||
[
|
||||
[T1, V1],
|
||||
[T1, V1_VARIANT2],
|
||||
],
|
||||
{
|
||||
markDeleted: true,
|
||||
disallowExtraObjects: true,
|
||||
allowDropStatements: true,
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
|
||||
'Script drived deploy - basic predeploy - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: '1.predeploy.sql',
|
||||
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');
|
||||
expect(res1.rows[0].cnt == 1).toBeTruthy();
|
||||
|
||||
const res2 = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM dbgate_deploy_journal');
|
||||
expect(res2.rows[0].cnt == 1).toBeTruthy();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => !x.skipDataModifications).map(engine => [engine.label, engine]))(
|
||||
'Script drived deploy - install+uninstall - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await testDatabaseDeploy(engine, conn, driver, [
|
||||
[
|
||||
{
|
||||
name: 't1.uninstall.sql',
|
||||
text: 'drop table t1',
|
||||
},
|
||||
{
|
||||
name: 't1.install.sql',
|
||||
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)',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 't1.uninstall.sql',
|
||||
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)',
|
||||
},
|
||||
{
|
||||
name: 't2.once.sql',
|
||||
text: 'insert into t2 (id) values (2)',
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
const res1 = await driver.query(conn, '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');
|
||||
expect(res2.rows[0].cnt == 1).toBeTruthy();
|
||||
|
||||
const res3 = await driver.query(conn, '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'");
|
||||
expect(res4.rows[0].run_count == 1).toBeTruthy();
|
||||
|
||||
const res5 = await driver.query(
|
||||
conn,
|
||||
"SELECT run_count from dbgate_deploy_journal where name = 't1.install.sql'"
|
||||
);
|
||||
expect(res5.rows[0].run_count == 2).toBeTruthy();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
151
integration-tests/__tests__/import-formats.spec.js
Normal file
151
integration-tests/__tests__/import-formats.spec.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const dbgateApi = require('dbgate-api/src/shell');
|
||||
// const jsonLinesWriter = require('dbgate-api/src/shell/jsonLinesWriter');
|
||||
const tmp = require('tmp');
|
||||
// const dbgatePluginCsv = require('dbgate-plugin-csv/src/backend');
|
||||
const fs = require('fs');
|
||||
const requirePlugin = require('dbgate-api/src/shell/requirePlugin');
|
||||
|
||||
const CSV_DATA = `Issue Number; Title; Github URL; Labels; State; Created At; Updated At; Reporter; Assignee
|
||||
801; "Does it 'burst' the database on startup or first lUI load ? "; https://github.com/dbgate/dbgate/issues/801; ""; open; 05/23/2024; 05/23/2024; rgarrigue;
|
||||
799; "BUG: latest AppImage crashes on opening in Fedora 39"; https://github.com/dbgate/dbgate/issues/799; ""; open; 05/21/2024; 05/24/2024; BenGraham-Git;
|
||||
798; "MongoDB write operations fail"; https://github.com/dbgate/dbgate/issues/798; "bug,solved"; open; 05/21/2024; 05/24/2024; mahmed0715;
|
||||
797; "BUG: Unable to open SQL files"; https://github.com/dbgate/dbgate/issues/797; "bug"; open; 05/20/2024; 05/21/2024; cesarValdivia;
|
||||
795; "BUG: MS SQL Server connection error (KEY_USAGE_BIT_INCORRECT)"; https://github.com/dbgate/dbgate/issues/795; ""; open; 05/20/2024; 05/20/2024; keskinonur;
|
||||
794; "GLIBC_2.29' not found and i have 2.31"; https://github.com/dbgate/dbgate/issues/794; ""; closed; 05/20/2024; 05/21/2024; MFdanGM;
|
||||
793; "BUG: PostgresSQL doesn't show tables when connected"; https://github.com/dbgate/dbgate/issues/793; ""; open; 05/20/2024; 05/22/2024; stomper013;
|
||||
792; "FEAT: Wayland support"; https://github.com/dbgate/dbgate/issues/792; ""; closed; 05/19/2024; 05/21/2024; VosaXalo;
|
||||
`;
|
||||
|
||||
async function getReaderRows(reader) {
|
||||
const jsonLinesFileName = tmp.tmpNameSync();
|
||||
|
||||
const writer = await dbgateApi.jsonLinesWriter({
|
||||
fileName: jsonLinesFileName,
|
||||
});
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
|
||||
const jsonData = fs.readFileSync(jsonLinesFileName, 'utf-8');
|
||||
const rows = jsonData
|
||||
.split('\n')
|
||||
.filter(x => x.trim() !== '')
|
||||
.map(x => JSON.parse(x));
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
test('csv import test', async () => {
|
||||
const dbgatePluginCsv = requirePlugin('dbgate-plugin-csv');
|
||||
|
||||
const csvFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(csvFileName, CSV_DATA);
|
||||
|
||||
const reader = await dbgatePluginCsv.shellApi.reader({
|
||||
fileName: csvFileName,
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows[0].columns).toEqual([
|
||||
{ columnName: 'Issue Number' },
|
||||
{ columnName: 'Title' },
|
||||
{ columnName: 'Github URL' },
|
||||
{ columnName: 'Labels' },
|
||||
{ columnName: 'State' },
|
||||
{ columnName: 'Created At' },
|
||||
{ columnName: 'Updated At' },
|
||||
{ columnName: 'Reporter' },
|
||||
{ columnName: 'Assignee' },
|
||||
]);
|
||||
expect(rows.length).toEqual(9);
|
||||
expect(rows[1]).toEqual({
|
||||
'Issue Number': '801',
|
||||
Title: "Does it 'burst' the database on startup or first lUI load ? ",
|
||||
'Github URL': 'https://github.com/dbgate/dbgate/issues/801',
|
||||
Labels: '',
|
||||
State: 'open',
|
||||
'Created At': '05/23/2024',
|
||||
'Updated At': '05/23/2024',
|
||||
Reporter: 'rgarrigue',
|
||||
Assignee: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('JSON array import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify([
|
||||
{ id: 1, val: 'v1' },
|
||||
{ id: 2, val: 'v2' },
|
||||
])
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ id: 1, val: 'v1' },
|
||||
{ id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('JSON object import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify({
|
||||
k1: { id: 1, val: 'v1' },
|
||||
k2: { id: 2, val: 'v2' },
|
||||
})
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
jsonStyle: 'object',
|
||||
keyField: 'mykey',
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ mykey: 'k1', id: 1, val: 'v1' },
|
||||
{ mykey: 'k2', id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('JSON filtered object import test', async () => {
|
||||
const jsonFileName = tmp.tmpNameSync();
|
||||
|
||||
fs.writeFileSync(
|
||||
jsonFileName,
|
||||
JSON.stringify({
|
||||
filtered: {
|
||||
k1: { id: 1, val: 'v1' },
|
||||
k2: { id: 2, val: 'v2' },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const reader = await dbgateApi.jsonReader({
|
||||
fileName: jsonFileName,
|
||||
jsonStyle: 'object',
|
||||
keyField: 'mykey',
|
||||
rootField: 'filtered',
|
||||
});
|
||||
|
||||
const rows = await getReaderRows(reader);
|
||||
|
||||
expect(rows.length).toEqual(2);
|
||||
expect(rows).toEqual([
|
||||
{ mykey: 'k1', id: 1, val: 'v1' },
|
||||
{ mykey: 'k2', id: 2, val: 'v2' },
|
||||
]);
|
||||
});
|
||||
90
integration-tests/__tests__/schema-tests.spec.js
Normal file
90
integration-tests/__tests__/schema-tests.spec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const _ = require('lodash');
|
||||
const fp = require('lodash/fp');
|
||||
const { testWrapper, extractConnection } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
const { runCommandOnDriver } = require('dbgate-tools');
|
||||
|
||||
async function baseStructure(conn, driver) {
|
||||
await driver.query(conn, `create table t1 (id int not null primary key)`);
|
||||
|
||||
await driver.query(
|
||||
conn,
|
||||
`create table t2 (
|
||||
id int not null primary key,
|
||||
t1_id int
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
describe('Schema tests', () => {
|
||||
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||
'Create schema - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
const schemas1 = await driver.listSchemas(conn);
|
||||
expect(schemas1.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||
const count = schemas1.length;
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
const schemas2 = await driver.listSchemas(conn);
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||
expect(schemas2.length).toEqual(count + 1);
|
||||
expect(schemas2.find(x => x.isDefault).schemaName).toEqual(engine.defaultSchemaName);
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.supportSchemas).map(engine => [engine.label, engine]))(
|
||||
'Drop schema - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.createSchema('myschema'));
|
||||
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
const schemas1 = await driver.listSchemas(conn);
|
||||
expect(schemas1.find(x => x.schemaName == 'myschema')).toBeTruthy();
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
await runCommandOnDriver(conn, driver, dmp => dmp.dropSchema('myschema'));
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
const schemas2 = await driver.listSchemas(conn);
|
||||
expect(schemas2.find(x => x.schemaName == 'myschema')).toBeFalsy();
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.supportSchemas && !x.skipSeparateSchemas).map(engine => [engine.label, engine]))(
|
||||
'Table inside schema - %s',
|
||||
testWrapper(async (handle, driver, engine) => {
|
||||
await baseStructure(handle, driver);
|
||||
await runCommandOnDriver(handle, driver, dmp => dmp.createSchema('myschema'));
|
||||
|
||||
const schemaConnDef = {
|
||||
...extractConnection(engine),
|
||||
database: `${handle.database}::myschema`,
|
||||
};
|
||||
|
||||
const schemaConn = await driver.connect(schemaConnDef);
|
||||
await driver.query(schemaConn, `create table myschema.myt1 (id int not null primary key)`);
|
||||
const structure1 = await driver.analyseFull(schemaConn);
|
||||
expect(structure1.tables.length).toEqual(1);
|
||||
expect(structure1.tables[0].pureName).toEqual('myt1');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('Base analyser test', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Structure without change - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await baseStructure(conn, driver);
|
||||
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
expect(structure2).toBeNull();
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -6,9 +6,10 @@ 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)';
|
||||
// const fkSql = 'ALTER TABLE t3 ADD FOREIGN KEY (valfk) REFERENCES t2(id)'
|
||||
|
||||
const txMatch = (engine, tname, vcolname, nextcol) =>
|
||||
const txMatch = (engine, tname, vcolname, nextcol, defaultValue) =>
|
||||
expect.objectContaining({
|
||||
pureName: tname,
|
||||
columns: [
|
||||
@@ -19,10 +20,14 @@ const txMatch = (engine, tname, vcolname, nextcol) =>
|
||||
}),
|
||||
expect.objectContaining({
|
||||
columnName: vcolname,
|
||||
...(engine.skipNullability ? {} : { notNull: false }),
|
||||
dataType: engine.skipStringLength
|
||||
? expect.stringMatching(/.*string|char.*/i)
|
||||
: expect.stringMatching(/.*char.*\(50\)/i),
|
||||
...(engine.skipNullability ? {} : { notNull: !!defaultValue }),
|
||||
...(defaultValue
|
||||
? { defaultValue }
|
||||
: {
|
||||
dataType: engine.skipStringLength
|
||||
? expect.stringMatching(/.*string|char.*/i)
|
||||
: expect.stringMatching(/.*char.*\(50\)/i),
|
||||
}),
|
||||
}),
|
||||
...(nextcol
|
||||
? [
|
||||
@@ -48,6 +53,7 @@ const txMatch = (engine, tname, vcolname, nextcol) =>
|
||||
const t1Match = engine => txMatch(engine, 't1', 'val1');
|
||||
const t2Match = engine => txMatch(engine, 't2', 'val2');
|
||||
const t2NextColMatch = engine => txMatch(engine, 't2', 'val2', true);
|
||||
const t4Match = engine => txMatch(engine, 't4', 'valdef', null, '12');
|
||||
|
||||
describe('Table analyse', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
@@ -169,4 +175,16 @@ 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);
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
expect(structure.tables.length).toEqual(1);
|
||||
expect(structure.tables[0]).toEqual(t4Match(engine));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
15963
integration-tests/data/chinook-mysql.sql
Normal file
15963
integration-tests/data/chinook-mysql.sql
Normal file
File diff suppressed because it is too large
Load Diff
16173
integration-tests/data/chinook-postgre.sql
Normal file
16173
integration-tests/data/chinook-postgre.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,21 @@
|
||||
version: '3'
|
||||
services:
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# restart: always
|
||||
# environment:
|
||||
# POSTGRES_PASSWORD: Pwd2020Db
|
||||
# ports:
|
||||
# - 15000:5432
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Pwd2020Db
|
||||
ports:
|
||||
- 15000:5432
|
||||
|
||||
# mariadb:
|
||||
# image: mariadb
|
||||
# command: --default-authentication-plugin=mysql_native_password
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15004:3306
|
||||
# environment:
|
||||
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
mariadb:
|
||||
image: mariadb
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
ports:
|
||||
- 15004:3306
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
|
||||
# mysql:
|
||||
# image: mysql:8.0.18
|
||||
@@ -26,13 +26,13 @@ services:
|
||||
# environment:
|
||||
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
|
||||
clickhouse:
|
||||
image: bitnami/clickhouse:24.8.4
|
||||
restart: always
|
||||
ports:
|
||||
- 15005:8123
|
||||
environment:
|
||||
- CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
||||
# clickhouse:
|
||||
# image: bitnami/clickhouse:24.8.4
|
||||
# restart: always
|
||||
# ports:
|
||||
# - 15005:8123
|
||||
# environment:
|
||||
# - CLICKHOUSE_ADMIN_PASSWORD=Pwd2020Db
|
||||
|
||||
# mssql:
|
||||
# image: mcr.microsoft.com/mssql/server
|
||||
|
||||
@@ -30,6 +30,13 @@ const engines = [
|
||||
// skipOnCI: true,
|
||||
objects: [views],
|
||||
dbSnapshotBySeconds: true,
|
||||
dumpFile: 'data/chinook-mysql.sql',
|
||||
dumpChecks: [
|
||||
{
|
||||
sql: 'select count(*) as res from genre',
|
||||
res: '25',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'MariaDB',
|
||||
@@ -47,6 +54,13 @@ const engines = [
|
||||
skipOnCI: true,
|
||||
objects: [views],
|
||||
dbSnapshotBySeconds: true,
|
||||
dumpFile: 'data/chinook-mysql.sql',
|
||||
dumpChecks: [
|
||||
{
|
||||
sql: 'select count(*) as res from genre',
|
||||
res: '25',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'PostgreSQL',
|
||||
@@ -81,6 +95,16 @@ const engines = [
|
||||
drop2: 'DROP FUNCTION obj2',
|
||||
},
|
||||
],
|
||||
supportSchemas: true,
|
||||
supportRenameSqlObject: true,
|
||||
defaultSchemaName: 'public',
|
||||
dumpFile: 'data/chinook-postgre.sql',
|
||||
dumpChecks: [
|
||||
{
|
||||
sql: 'select count(*) as res from "public"."Genre"',
|
||||
res: '25',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SQL Server',
|
||||
@@ -105,6 +129,10 @@ const engines = [
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
supportSchemas: true,
|
||||
supportRenameSqlObject: true,
|
||||
defaultSchemaName: 'dbo',
|
||||
// skipSeparateSchemas: true,
|
||||
},
|
||||
{
|
||||
label: 'SQLite',
|
||||
@@ -113,6 +141,7 @@ const engines = [
|
||||
engine: 'sqlite@dbgate-plugin-sqlite',
|
||||
},
|
||||
objects: [views],
|
||||
skipOnCI: false,
|
||||
},
|
||||
{
|
||||
label: 'CockroachDB',
|
||||
@@ -159,11 +188,11 @@ const filterLocal = [
|
||||
// filter local testing
|
||||
'-MySQL',
|
||||
'-MariaDB',
|
||||
'-PostgreSQL',
|
||||
'PostgreSQL',
|
||||
'-SQL Server',
|
||||
'-SQLite',
|
||||
'-CockroachDB',
|
||||
'ClickHouse',
|
||||
'-ClickHouse',
|
||||
];
|
||||
|
||||
const enginesPostgre = engines.filter(x => x.label == 'PostgreSQL');
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js",
|
||||
"test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest",
|
||||
"test:local:path": "cross-env DEVMODE=1 LOCALTEST=1 jest --runTestsByPath __tests__/data-duplicator.spec.js",
|
||||
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults",
|
||||
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults --detectOpenHandles --forceExit",
|
||||
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
|
||||
},
|
||||
"jest": {
|
||||
@@ -22,6 +22,7 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^27.0.1",
|
||||
"pino-pretty": "^11.2.2"
|
||||
"pino-pretty": "^11.2.2",
|
||||
"tmp": "^0.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
};
|
||||
|
||||
const { prettyFactory } = require('pino-pretty');
|
||||
const tmp = require('tmp');
|
||||
|
||||
const pretty = prettyFactory({
|
||||
colorize: true,
|
||||
@@ -20,3 +26,5 @@ global.console = {
|
||||
process.stdout.write(messages.join(' ') + '\n');
|
||||
},
|
||||
};
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
'dbgate-sqltree': require('dbgate-sqltree'),
|
||||
};
|
||||
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const crypto = require('crypto');
|
||||
|
||||
@@ -48,6 +43,29 @@ async function connect(engine, database) {
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareConnection(engine, database) {
|
||||
const connection = extractConnection(engine);
|
||||
const driver = requireEngineDriver(connection);
|
||||
|
||||
if (engine.generateDbFile) {
|
||||
return {
|
||||
...connection,
|
||||
databaseFile: `dbtemp/${database}`,
|
||||
isPreparedOnly: true,
|
||||
};
|
||||
} else {
|
||||
const conn = await driver.connect(connection);
|
||||
await driver.query(conn, `CREATE DATABASE ${database}`);
|
||||
await driver.close(conn);
|
||||
|
||||
return {
|
||||
...connection,
|
||||
database,
|
||||
isPreparedOnly: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const testWrapper =
|
||||
body =>
|
||||
async (label, ...other) => {
|
||||
@@ -61,9 +79,19 @@ const testWrapper =
|
||||
}
|
||||
};
|
||||
|
||||
const testWrapperPrepareOnly =
|
||||
body =>
|
||||
async (label, ...other) => {
|
||||
const engine = other[other.length - 1];
|
||||
const driver = requireEngineDriver(engine.connection);
|
||||
const conn = await prepareConnection(engine, randomDbName());
|
||||
await body(conn, driver, ...other);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
randomDbName,
|
||||
connect,
|
||||
extractConnection,
|
||||
testWrapper,
|
||||
testWrapperPrepareOnly,
|
||||
};
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.4.5-beta.6",
|
||||
"version": "5.5.7-alpha.32",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -21,6 +21,7 @@
|
||||
"start:api:auth": "yarn workspace dbgate-api start:auth | pino-pretty",
|
||||
"start:api:dblogin": "yarn workspace dbgate-api start:dblogin | pino-pretty",
|
||||
"start:api:storage": "yarn workspace dbgate-api start:storage | pino-pretty",
|
||||
"start:api:storage:built": "yarn workspace dbgate-api start:storage:built | pino-pretty",
|
||||
"sync:pro": "cd sync && yarn start",
|
||||
"start:web": "yarn workspace dbgate-web dev",
|
||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||
@@ -34,8 +35,9 @@
|
||||
"build:lib": "yarn build:sqltree && yarn build:tools && yarn build:filterparser && yarn build:datalib",
|
||||
"build:app": "yarn plugins:copydist && cd app && yarn install && yarn build",
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:web:docker": "yarn workspace dbgate-web build",
|
||||
"build:web": "yarn workspace dbgate-web build",
|
||||
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||
"build:plugins:backend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:backend",
|
||||
"build:plugins:frontend:watch": "workspaces-run --parallel --only=\"dbgate-plugin-*\" -- yarn build:frontend:watch",
|
||||
"storage-json": "dbmodel model-to-json storage-db packages/api/src/storageModel.js --commonjs",
|
||||
"plugins:copydist": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn copydist",
|
||||
@@ -45,20 +47,20 @@
|
||||
"printSecrets": "node printSecrets",
|
||||
"generatePadFile": "node generatePadFile",
|
||||
"adjustPackageJson": "node adjustPackageJson",
|
||||
"fillNativeModules": "node fillNativeModules",
|
||||
"fillNativeModulesElectron": "node fillNativeModules --electron",
|
||||
"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",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||
"copy:packer:build": "copyfiles packages/api/dist/* packer/build -f && copyfiles packages/web/public/* packer/build -u 2 && copyfiles \"packages/web/public/**/*\" packer/build -u 2 && copyfiles \"plugins/dist/**/*\" packer/build/plugins -u 2 && copyfiles packer/install-packages.sh packer/build -f",
|
||||
"install:drivers:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && yarn add oracledb && cd ..",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
|
||||
"prepare:packer": "yarn plugins:copydist && yarn build:web && yarn build:api && yarn copy:packer:build",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
"ts:web": "yarn workspace dbgate-web ts",
|
||||
"ts": "yarn ts:api && yarn ts:web",
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend",
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn build:plugins:frontend",
|
||||
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
DEVMODE=1
|
||||
SHELL_SCRIPTING=1
|
||||
|
||||
CLOUD_UPGRADE_FILE=c:\test\upg\upgrade.zip
|
||||
|
||||
# PERMISSIONS=~widgets/app,~widgets/plugins
|
||||
# DISABLE_SHELL=1
|
||||
# HIDE_APP_EDITOR=1
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
"dbgate"
|
||||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/rds-signer": "^3.665.0",
|
||||
"activedirectory2": "^2.1.0",
|
||||
"async-lock": "^1.2.4",
|
||||
"async-lock": "^1.2.6",
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"bufferutil": "^4.0.1",
|
||||
@@ -27,7 +28,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-datalib": "^5.0.0-alpha.1",
|
||||
"dbgate-query-splitter": "^4.10.3",
|
||||
"dbgate-query-splitter": "^4.11.2",
|
||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||
"dbgate-tools": "^5.0.0-alpha.1",
|
||||
"debug": "^4.3.4",
|
||||
@@ -55,8 +56,10 @@
|
||||
"pinomin": "^1.0.4",
|
||||
"portfinder": "^1.0.28",
|
||||
"rimraf": "^3.0.0",
|
||||
"semver": "^7.6.3",
|
||||
"simple-encryptor": "^4.0.0",
|
||||
"ssh2": "^1.11.0",
|
||||
"stream-json": "^1.8.0",
|
||||
"tar": "^6.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -67,6 +70,7 @@
|
||||
"start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api",
|
||||
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
||||
"start:storage": "env-cmd -f env/storage/.env node src/index.js --listen-api",
|
||||
"start:storage:built": "env-cmd -f env/storage/.env cross-env DEVMODE= BUILTWEBMODE=1 node dist/bundle.js --listen-api",
|
||||
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
||||
"ts": "tsc",
|
||||
"build": "webpack"
|
||||
@@ -81,10 +85,5 @@
|
||||
"typescript": "^4.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^6.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,16 @@ class OAuthProvider extends AuthProviderBase {
|
||||
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
|
||||
);
|
||||
|
||||
const { access_token, refresh_token } = resp.data;
|
||||
const { access_token, refresh_token, id_token } = resp.data;
|
||||
|
||||
const payload = jwt.decode(access_token);
|
||||
let payload = jwt.decode(access_token);
|
||||
|
||||
// Fallback to id_token in case the access_token is not a JWT
|
||||
// https://www.oauth.com/oauth2-servers/access-tokens/
|
||||
// https://github.com/dbgate/dbgate/issues/727
|
||||
if (!payload && id_token) {
|
||||
payload = jwt.decode(id_token);
|
||||
}
|
||||
|
||||
logger.info({ payload }, 'User payload returned from OAUTH');
|
||||
|
||||
@@ -198,6 +205,19 @@ class LoginsProvider extends AuthProviderBase {
|
||||
amoid = 'logins';
|
||||
|
||||
async login(login, password, options = undefined) {
|
||||
if (login && password && process.env['LOGIN'] == login && process.env['PASSWORD'] == password) {
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
amoid: this.amoid,
|
||||
login,
|
||||
},
|
||||
getTokenSecret(),
|
||||
{ expiresIn: getTokenLifetime() }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (password == process.env[`LOGIN_PASSWORD_${login}`]) {
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
@@ -210,6 +230,7 @@ class LoginsProvider extends AuthProviderBase {
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return { error: 'Invalid credentials' };
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const { archivedir, clearArchiveLinksCache, resolveArchiveFolder } = require('..
|
||||
const socket = require('../utility/socket');
|
||||
const loadFilesRecursive = require('../utility/loadFilesRecursive');
|
||||
const getJslFileName = require('../utility/getJslFileName');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const dbgateApi = require('../shell');
|
||||
const jsldata = require('./jsldata');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
@@ -74,7 +74,7 @@ module.exports = {
|
||||
...fileType('.matview.sql', 'matview.sql'),
|
||||
];
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error reading archive files');
|
||||
logger.error(extractErrorLogData(err), 'Error reading archive files');
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const axios = require('axios');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const getExpressPath = require('../utility/getExpressPath');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const AD = require('activedirectory2').promiseWrapper;
|
||||
const crypto = require('crypto');
|
||||
const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon');
|
||||
@@ -22,7 +22,8 @@ function unauthorizedResponse(req, res, text) {
|
||||
// if (req.path == getExpressPath('/connections/list')) {
|
||||
// return res.json([]);
|
||||
// }
|
||||
return res.sendStatus(401).send(text);
|
||||
|
||||
return res.status(401).send(text);
|
||||
}
|
||||
|
||||
function authMiddleware(req, res, next) {
|
||||
@@ -35,8 +36,9 @@ function authMiddleware(req, res, next) {
|
||||
'/auth/login',
|
||||
'/auth/redirect',
|
||||
'/stream',
|
||||
'storage/get-connections-for-login-page',
|
||||
'auth/get-providers',
|
||||
'/storage/get-connections-for-login-page',
|
||||
'/storage/set-admin-password',
|
||||
'/auth/get-providers',
|
||||
'/connections/dblogin-web',
|
||||
'/connections/dblogin-app',
|
||||
'/connections/dblogin-auth',
|
||||
@@ -68,10 +70,11 @@ function authMiddleware(req, res, next) {
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (skipAuth) {
|
||||
req.isInvalidToken = true;
|
||||
return next();
|
||||
}
|
||||
|
||||
logger.error({ err }, 'Sending invalid token error');
|
||||
logger.error(extractErrorLogData(err), 'Sending invalid token error');
|
||||
|
||||
return unauthorizedResponse(req, res, 'invalid token');
|
||||
}
|
||||
@@ -88,7 +91,12 @@ module.exports = {
|
||||
const { amoid, login, password, isAdminPage } = params;
|
||||
|
||||
if (isAdminPage) {
|
||||
if (process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD == password) {
|
||||
let adminPassword = process.env.ADMIN_PASSWORD;
|
||||
if (!adminPassword) {
|
||||
const adminConfig = await storage.readConfig({ group: 'admin' });
|
||||
adminPassword = adminConfig?.adminPassword;
|
||||
}
|
||||
if (adminPassword && adminPassword == password) {
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
|
||||
const storage = require('./storage');
|
||||
const { getAuthProxyUrl } = require('../utility/authProxy');
|
||||
const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
|
||||
const { extractErrorMessage } = require('dbgate-tools');
|
||||
|
||||
const lock = new AsyncLock();
|
||||
|
||||
@@ -39,10 +40,12 @@ module.exports = {
|
||||
const isUserLoggedIn = authProvider.isUserLoggedIn(req);
|
||||
|
||||
const singleConid = authProvider.getSingleConnectionId(req);
|
||||
const storageConnectionError = storage.getStorageConnectionError();
|
||||
|
||||
const singleConnection = singleConid
|
||||
? await connections.getCore({ conid: singleConid })
|
||||
: connections.singleConnection;
|
||||
const singleConnection =
|
||||
singleConid && !storageConnectionError
|
||||
? await connections.getCore({ conid: singleConid })
|
||||
: connections.singleConnection;
|
||||
|
||||
let configurationError = null;
|
||||
if (process.env.STORAGE_DATABASE && process.env.BASIC_AUTH) {
|
||||
@@ -50,10 +53,23 @@ module.exports = {
|
||||
'Basic authentization is not allowed, when using storage. Cannot use both STORAGE_DATABASE and BASIC_AUTH';
|
||||
}
|
||||
|
||||
const checkedLicense = await checkLicense();
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
if (storageConnectionError && !configurationError) {
|
||||
configurationError = extractErrorMessage(storageConnectionError);
|
||||
}
|
||||
|
||||
return {
|
||||
const checkedLicense = storageConnectionError ? null : await checkLicense();
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
|
||||
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
|
||||
|
||||
const isAdminPasswordMissing = !!(
|
||||
process.env.STORAGE_DATABASE &&
|
||||
!process.env.ADMIN_PASSWORD &&
|
||||
!process.env.BASIC_AUTH &&
|
||||
!adminConfig?.adminPasswordState
|
||||
);
|
||||
|
||||
const configResult = {
|
||||
runAsPortal: !!connections.portalConnections,
|
||||
singleDbConnection: connections.singleDbConnection,
|
||||
singleConnection: singleConnection,
|
||||
@@ -64,24 +80,30 @@ module.exports = {
|
||||
isDocker: platformInfo.isDocker,
|
||||
isElectron: platformInfo.isElectron,
|
||||
isLicenseValid,
|
||||
isLicenseExpired: checkedLicense?.isExpired,
|
||||
trialDaysLeft: checkedLicense?.isGeneratedTrial && !checkedLicense?.isExpired ? checkedLicense?.daysLeft : null,
|
||||
checkedLicense,
|
||||
configurationError,
|
||||
logoutUrl: await authProvider.getLogoutUrl(),
|
||||
logoutUrl,
|
||||
permissions,
|
||||
login,
|
||||
// ...additionalConfigProps,
|
||||
isBasicAuth: !!process.env.BASIC_AUTH,
|
||||
isAdminLoginForm: !!(
|
||||
process.env.STORAGE_DATABASE &&
|
||||
process.env.ADMIN_PASSWORD &&
|
||||
!process.env.BASIC_AUTH &&
|
||||
checkedLicense?.type == 'premium'
|
||||
(process.env.ADMIN_PASSWORD || adminConfig?.adminPasswordState == 'set') &&
|
||||
!process.env.BASIC_AUTH
|
||||
),
|
||||
isAdminPasswordMissing,
|
||||
isInvalidToken: req?.isInvalidToken,
|
||||
adminPasswordState: adminConfig?.adminPasswordState,
|
||||
storageDatabase: process.env.STORAGE_DATABASE,
|
||||
logsFilePath: getLogsFilePath(),
|
||||
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
|
||||
...currentVersion,
|
||||
};
|
||||
|
||||
return configResult;
|
||||
},
|
||||
|
||||
logout_meta: {
|
||||
|
||||
@@ -12,7 +12,7 @@ const { pickSafeConnectionInfo } = require('../utility/crypting');
|
||||
const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
|
||||
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { safeJsonParse, getLogger } = require('dbgate-tools');
|
||||
const { safeJsonParse, getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
@@ -76,6 +76,7 @@ function getPortalCollections() {
|
||||
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
|
||||
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
||||
parent: process.env[`PARENT_${id}`] || undefined,
|
||||
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
|
||||
|
||||
// SSH tunnel
|
||||
useSshTunnel: process.env[`USE_SSH_${id}`],
|
||||
@@ -367,6 +368,11 @@ module.exports = {
|
||||
|
||||
get_meta: true,
|
||||
async get({ conid }, req) {
|
||||
if (conid == '__model') {
|
||||
return {
|
||||
_id: '__model',
|
||||
};
|
||||
}
|
||||
testConnectionPermission(conid, req);
|
||||
return this.getCore({ conid, mask: true });
|
||||
},
|
||||
@@ -429,7 +435,7 @@ module.exports = {
|
||||
socket.emit('got-volatile-token', { strmid, savedConId: conid, volatileConId: volatile._id });
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error getting DB token');
|
||||
logger.error(extractErrorLogData(err), 'Error getting DB token');
|
||||
return { error: err.message };
|
||||
}
|
||||
},
|
||||
@@ -445,7 +451,7 @@ module.exports = {
|
||||
const resp = await authProvider.login(null, null, { conid: volatile._id });
|
||||
return resp;
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error getting DB token');
|
||||
logger.error(extractErrorLogData(err), 'Error getting DB token');
|
||||
return { error: err.message };
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,6 +12,8 @@ const {
|
||||
extendDatabaseInfo,
|
||||
modelCompareDbDiffOptions,
|
||||
getLogger,
|
||||
extractErrorLogData,
|
||||
filterStructureBySchema,
|
||||
} = require('dbgate-tools');
|
||||
const { html, parse } = require('diff2html');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
@@ -30,6 +32,8 @@ const { testConnectionPermission } = require('../utility/hasPermission');
|
||||
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const crypto = require('crypto');
|
||||
const loadModelTransform = require('../utility/loadModelTransform');
|
||||
const exportDbModelSql = require('../utility/exportDbModelSql');
|
||||
|
||||
const logger = getLogger('databaseConnections');
|
||||
|
||||
@@ -146,7 +150,7 @@ module.exports = {
|
||||
try {
|
||||
conn.subprocess.send({ msgid, ...message });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error sending request do process');
|
||||
logger.error(extractErrorLogData(err), 'Error sending request do process');
|
||||
this.close(conn.conid, conn.database);
|
||||
}
|
||||
});
|
||||
@@ -213,6 +217,17 @@ module.exports = {
|
||||
return res.result || null;
|
||||
},
|
||||
|
||||
schemaList_meta: true,
|
||||
async schemaList({ conid, database }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
return this.loadDataCore('schemaList', { conid, database });
|
||||
},
|
||||
|
||||
dispatchDatabaseChangedEvent_meta: true,
|
||||
dispatchDatabaseChangedEvent({ event, conid, database }) {
|
||||
socket.emitChanged(event, { conid, database });
|
||||
},
|
||||
|
||||
loadKeys_meta: true,
|
||||
async loadKeys({ conid, database, root, filter }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
@@ -307,7 +322,7 @@ module.exports = {
|
||||
try {
|
||||
existing.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging DB connection');
|
||||
logger.error(extractErrorLogData(err), 'Error pinging DB connection');
|
||||
this.close(conid, database);
|
||||
|
||||
return {
|
||||
@@ -337,6 +352,11 @@ module.exports = {
|
||||
|
||||
syncModel_meta: true,
|
||||
async syncModel({ conid, database, isFullRefresh }, req) {
|
||||
if (conid == '__model') {
|
||||
socket.emitChanged('database-structure-changed', { conid, database });
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
testConnectionPermission(conid, req);
|
||||
const conn = await this.ensureOpened(conid, database);
|
||||
conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh });
|
||||
@@ -351,7 +371,7 @@ module.exports = {
|
||||
try {
|
||||
existing.subprocess.kill();
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error killing subprocess');
|
||||
logger.error(extractErrorLogData(err), 'Error killing subprocess');
|
||||
}
|
||||
}
|
||||
this.opened = this.opened.filter(x => x.conid != conid || x.database != database);
|
||||
@@ -380,11 +400,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
structure_meta: true,
|
||||
async structure({ conid, database }, req) {
|
||||
async structure({ conid, database, modelTransFile = null }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
if (conid == '__model') {
|
||||
const model = await importDbModel(database);
|
||||
return model;
|
||||
const trans = await loadModelTransform(modelTransFile);
|
||||
return trans ? trans(model) : model;
|
||||
}
|
||||
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
@@ -420,14 +441,35 @@ module.exports = {
|
||||
},
|
||||
|
||||
exportModel_meta: true,
|
||||
async exportModel({ conid, database }, req) {
|
||||
async exportModel({ conid, database, outputFolder, schema }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
const archiveFolder = await archive.getNewArchiveFolder({ database });
|
||||
await fs.mkdir(path.join(archivedir(), archiveFolder));
|
||||
|
||||
const realFolder = outputFolder.startsWith('archive:')
|
||||
? resolveArchiveFolder(outputFolder.substring('archive:'.length))
|
||||
: outputFolder;
|
||||
|
||||
const model = await this.structure({ conid, database });
|
||||
await exportDbModel(model, path.join(archivedir(), archiveFolder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
return { archiveFolder };
|
||||
const filteredModel = schema ? filterStructureBySchema(model, schema) : model;
|
||||
await exportDbModel(extendDatabaseInfo(filteredModel), realFolder);
|
||||
|
||||
if (outputFolder.startsWith('archive:')) {
|
||||
socket.emitChanged(`archive-files-changed`, { folder: outputFolder.substring('archive:'.length) });
|
||||
}
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
exportModelSql_meta: true,
|
||||
async exportModelSql({ conid, database, outputFolder, outputFile, schema }, req) {
|
||||
testConnectionPermission(conid, req);
|
||||
|
||||
const connection = await connections.getCore({ conid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
|
||||
const model = await this.structure({ conid, database });
|
||||
const filteredModel = schema ? filterStructureBySchema(model, schema) : model;
|
||||
await exportDbModelSql(extendDatabaseInfo(filteredModel), driver, outputFolder, outputFile);
|
||||
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
generateDeploySql_meta: true,
|
||||
|
||||
@@ -18,11 +18,14 @@ function readFirstLine(file) {
|
||||
}
|
||||
if (reader.hasNextLine()) {
|
||||
reader.nextLine((err, line) => {
|
||||
if (err) reject(err);
|
||||
resolve(line);
|
||||
if (err) {
|
||||
reader.close(() => reject(err)); // Ensure reader is closed on error
|
||||
return;
|
||||
}
|
||||
reader.close(() => resolve(line)); // Ensure reader is closed after reading
|
||||
});
|
||||
} else {
|
||||
resolve(null);
|
||||
reader.close(() => resolve(null)); // Properly close if no lines are present
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
jsonScriptToJavascript,
|
||||
getLogger,
|
||||
safeJsonParse,
|
||||
pinoLogRecordToMessageRecord,
|
||||
} = require('dbgate-tools');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
@@ -68,18 +69,20 @@ module.exports = {
|
||||
|
||||
dispatchMessage(runid, message) {
|
||||
if (message) {
|
||||
const json = safeJsonParse(message.message);
|
||||
if (_.isPlainObject(message)) logger.log(message);
|
||||
else logger.info(message);
|
||||
|
||||
if (json) logger.log(json);
|
||||
else logger.info(message.message);
|
||||
const toEmit = _.isPlainObject(message)
|
||||
? {
|
||||
time: new Date(),
|
||||
...message,
|
||||
}
|
||||
: {
|
||||
message,
|
||||
time: new Date(),
|
||||
};
|
||||
|
||||
const toEmit = {
|
||||
time: new Date(),
|
||||
...message,
|
||||
message: json ? json.msg : message.message,
|
||||
};
|
||||
|
||||
if (json && json.level >= 50) {
|
||||
if (toEmit.level >= 50) {
|
||||
toEmit.severity = 'error';
|
||||
}
|
||||
|
||||
@@ -131,7 +134,16 @@ module.exports = {
|
||||
}
|
||||
);
|
||||
const pipeDispatcher = severity => data => {
|
||||
return this.dispatchMessage(runid, { severity, message: data.toString().trim() });
|
||||
const json = safeJsonParse(data, null);
|
||||
|
||||
if (json) {
|
||||
return this.dispatchMessage(runid, pinoLogRecordToMessageRecord(json));
|
||||
} else {
|
||||
return this.dispatchMessage(runid, {
|
||||
message: json == null ? data.toString().trim() : null,
|
||||
severity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
|
||||
@@ -165,7 +177,7 @@ module.exports = {
|
||||
|
||||
start_meta: true,
|
||||
async start({ script }) {
|
||||
const runid = crypto.randomUUID()
|
||||
const runid = crypto.randomUUID();
|
||||
|
||||
if (script.type == 'json') {
|
||||
const js = jsonScriptToJavascript(script);
|
||||
|
||||
@@ -11,7 +11,7 @@ const processArgs = require('../utility/processArgs');
|
||||
const { testConnectionPermission } = require('../utility/hasPermission');
|
||||
const { MissingCredentialsError } = require('../utility/exceptions');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('serverConnection');
|
||||
|
||||
@@ -112,7 +112,7 @@ module.exports = {
|
||||
try {
|
||||
existing.subprocess.kill();
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error killing subprocess');
|
||||
logger.error(extractErrorLogData(err), 'Error killing subprocess');
|
||||
}
|
||||
}
|
||||
this.opened = this.opened.filter(x => x.conid != conid);
|
||||
@@ -134,6 +134,7 @@ module.exports = {
|
||||
listDatabases_meta: true,
|
||||
async listDatabases({ conid }, req) {
|
||||
if (!conid) return [];
|
||||
if (conid == '__model') return [];
|
||||
testConnectionPermission(conid, req);
|
||||
const opened = await this.ensureOpened(conid);
|
||||
return opened.databases;
|
||||
@@ -167,12 +168,12 @@ module.exports = {
|
||||
try {
|
||||
opened.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging server connection');
|
||||
logger.error(extractErrorLogData(err), 'Error pinging server connection');
|
||||
this.close(conid);
|
||||
}
|
||||
})
|
||||
);
|
||||
socket.setStreamIdFilter(strmid, { conid: conidArray });
|
||||
socket.setStreamIdFilter(strmid, { conid: [...(conidArray ?? []), '__model'] });
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
@@ -217,7 +218,7 @@ module.exports = {
|
||||
try {
|
||||
conn.subprocess.send({ msgid, ...message });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error sending request');
|
||||
logger.error(extractErrorLogData(err), 'Error sending request');
|
||||
this.close(conn.conid);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ const path = require('path');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { appdir } = require('../utility/directories');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const config = require('./config');
|
||||
|
||||
@@ -222,7 +222,7 @@ module.exports = {
|
||||
try {
|
||||
session.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error pinging session');
|
||||
logger.error(extractErrorLogData(err), 'Error pinging session');
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
|
||||
@@ -17,4 +17,13 @@ module.exports = {
|
||||
async getConnectionsForLoginPage() {
|
||||
return null;
|
||||
},
|
||||
|
||||
getStorageConnectionError() {
|
||||
return null;
|
||||
},
|
||||
|
||||
readConfig_meta: true,
|
||||
async readConfig({ group }) {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const { uploadsdir, getLogsFilePath } = require('../utility/directories');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const logger = getLogger('uploads');
|
||||
const axios = require('axios');
|
||||
const os = require('os');
|
||||
@@ -110,7 +110,7 @@ module.exports = {
|
||||
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error uploading gist');
|
||||
logger.error(extractErrorLogData(err), 'Error uploading gist');
|
||||
|
||||
return {
|
||||
apiErrorMessage: err.message,
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
const { setLogConfig, getLogger, setLoggerName } = require('dbgate-tools');
|
||||
const { setLogConfig, getLogger, setLoggerName, extractErrorLogData } = require('dbgate-tools');
|
||||
const processArgs = require('./utility/processArgs');
|
||||
const fs = require('fs');
|
||||
const moment = require('moment');
|
||||
const path = require('path');
|
||||
const { logsdir, setLogsFilePath, getLogsFilePath } = require('./utility/directories');
|
||||
const { createLogger } = require('pinomin');
|
||||
const currentVersion = require('./currentVersion');
|
||||
|
||||
const logger = getLogger('apiIndex');
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
logger.fatal(extractErrorLogData(err), 'Uncaught exception, exiting process');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (processArgs.startProcess) {
|
||||
setLoggerName(processArgs.startProcess.replace(/Process$/, ''));
|
||||
@@ -94,10 +101,17 @@ function configureLogger() {
|
||||
|
||||
if (processArgs.listenApi) {
|
||||
configureLogger();
|
||||
logger.info(`Starting API process version ${currentVersion.version}`);
|
||||
|
||||
if (process.env.DEBUG_PRINT_ENV_VARIABLES) {
|
||||
logger.info('Debug print environment variables:');
|
||||
for (const key of Object.keys(process.env)) {
|
||||
logger.info(` ${key}: ${JSON.stringify(process.env[key])}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shell = require('./shell/index');
|
||||
const currentVersion = require('./currentVersion');
|
||||
|
||||
global.DBGATE_PACKAGES = {
|
||||
'dbgate-tools': require('dbgate-tools'),
|
||||
|
||||
@@ -35,6 +35,7 @@ const getExpressPath = require('./utility/getExpressPath');
|
||||
const _ = require('lodash');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getDefaultAuthProvider } = require('./auth/authProvider');
|
||||
const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
|
||||
|
||||
const logger = getLogger('main');
|
||||
|
||||
@@ -73,6 +74,8 @@ function start() {
|
||||
if (platformInfo.isDocker) {
|
||||
// server static files inside docker container
|
||||
app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
|
||||
} else if (platformInfo.isAwsUbuntuLayout) {
|
||||
app.use(getExpressPath('/'), express.static('/home/ubuntu/build/public'));
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
|
||||
} else if (process.env.DEVWEB) {
|
||||
@@ -126,6 +129,10 @@ function start() {
|
||||
const port = process.env.PORT || 3000;
|
||||
logger.info(`DbGate API listening on port ${port} (docker build)`);
|
||||
server.listen(port);
|
||||
} else if (platformInfo.isAwsUbuntuLayout) {
|
||||
const port = process.env.PORT || 3000;
|
||||
logger.info(`DbGate API listening on port ${port} (AWS AMI build)`);
|
||||
server.listen(port);
|
||||
} else if (platformInfo.isNpmDist) {
|
||||
getPort({
|
||||
port: parseInt(
|
||||
@@ -162,6 +169,10 @@ function start() {
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGBREAK', shutdown);
|
||||
|
||||
if (process.env.CLOUD_UPGRADE_FILE) {
|
||||
startCloudUpgradeTimer();
|
||||
}
|
||||
}
|
||||
|
||||
function useAllControllers(app, electron) {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
const argIndex = process.argv.indexOf('--native-modules');
|
||||
const redirectFile = global['NATIVE_MODULES'] || (argIndex > 0 ? process.argv[argIndex + 1] : null);
|
||||
|
||||
function requireDynamic(file) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return __non_webpack_require__(redirectFile);
|
||||
} catch (err) {
|
||||
return require(redirectFile);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = redirectFile ? requireDynamic(redirectFile) : require('./nativeModulesContent');
|
||||
@@ -20,9 +20,10 @@ function start() {
|
||||
if (handleProcessCommunication(connection)) return;
|
||||
try {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const conn = await connectUtility(driver, connection, 'app');
|
||||
const res = await driver.getVersion(conn);
|
||||
const dbhan = await connectUtility(driver, connection, 'app');
|
||||
const res = await driver.getVersion(dbhan);
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
await driver.close(dbhan);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.send({
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const { splitQuery } = require('dbgate-query-splitter');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger } = require('dbgate-tools');
|
||||
const {
|
||||
extractBoolSettingsValue,
|
||||
extractIntSettingsValue,
|
||||
getLogger,
|
||||
isCompositeDbName,
|
||||
dbNameLogCategory,
|
||||
extractErrorMessage,
|
||||
extractErrorLogData,
|
||||
} = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
@@ -11,7 +19,7 @@ const { dumpSqlSelect } = require('dbgate-sqltree');
|
||||
|
||||
const logger = getLogger('dbconnProcess');
|
||||
|
||||
let systemConnection;
|
||||
let dbhan;
|
||||
let storedConnection;
|
||||
let afterConnectCallbacks = [];
|
||||
let afterAnalyseCallbacks = [];
|
||||
@@ -35,7 +43,7 @@ async function checkedAsyncCall(promise) {
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
message: extractErrorMessage(err, 'Checked call error'),
|
||||
});
|
||||
// console.error(err);
|
||||
setTimeout(() => process.exit(1), 1000);
|
||||
@@ -46,10 +54,16 @@ async function checkedAsyncCall(promise) {
|
||||
let loadingModel = false;
|
||||
|
||||
async function handleFullRefresh() {
|
||||
if (storedConnection.useSeparateSchemas && !isCompositeDbName(dbhan?.database)) {
|
||||
resolveAnalysedPromises();
|
||||
// skip loading DB structure
|
||||
return;
|
||||
}
|
||||
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('loadStructure');
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection, serverVersion));
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(dbhan, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||
process.send({ msgtype: 'structureTime', analysedTime });
|
||||
@@ -60,12 +74,15 @@ async function handleFullRefresh() {
|
||||
}
|
||||
|
||||
async function handleIncrementalRefresh(forceSend) {
|
||||
if (storedConnection.useSeparateSchemas && !isCompositeDbName(dbhan?.database)) {
|
||||
resolveAnalysedPromises();
|
||||
// skip loading DB structure
|
||||
return;
|
||||
}
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('checkStructure');
|
||||
const newStructure = await checkedAsyncCall(
|
||||
driver.analyseIncremental(systemConnection, analysedStructure, serverVersion)
|
||||
);
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(dbhan, analysedStructure, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
if (newStructure != null) {
|
||||
analysedStructure = newStructure;
|
||||
@@ -103,7 +120,8 @@ function setStatusName(name) {
|
||||
|
||||
async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
const version = await driver.getVersion(dbhan);
|
||||
logger.debug(`Got server version: ${version.version}`);
|
||||
process.send({ msgtype: 'version', version });
|
||||
serverVersion = version;
|
||||
}
|
||||
@@ -114,8 +132,13 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
systemConnection.feedback = feedback => setStatus({ feedback });
|
||||
dbhan = await checkedAsyncCall(connectUtility(driver, storedConnection, 'app'));
|
||||
logger.debug(
|
||||
`Connected to database, driver: ${storedConnection.engine}, separate schemas: ${
|
||||
storedConnection.useSeparateSchemas ? 'YES' : 'NO'
|
||||
}, 'DB: ${dbNameLogCategory(dbhan.database)} }`
|
||||
);
|
||||
dbhan.feedback = feedback => setStatus({ feedback });
|
||||
await checkedAsyncCall(readVersion());
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
@@ -138,7 +161,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
}
|
||||
|
||||
function waitConnected() {
|
||||
if (systemConnection) return Promise.resolve();
|
||||
if (dbhan) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
afterConnectCallbacks.push([resolve, reject]);
|
||||
});
|
||||
@@ -163,10 +186,14 @@ async function handleRunScript({ msgid, sql, useTransaction }, skipReadonlyCheck
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
await driver.script(systemConnection, sql, { useTransaction });
|
||||
await driver.script(dbhan, sql, { useTransaction });
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing SQL script'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,10 +202,14 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
await driver.operation(systemConnection, operation, { useTransaction });
|
||||
await driver.operation(dbhan, operation, { useTransaction });
|
||||
process.send({ msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing DB operation'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +219,14 @@ async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
// console.log(sql);
|
||||
const res = await driver.query(systemConnection, sql);
|
||||
const res = await driver.query(dbhan, sql);
|
||||
process.send({ msgtype: 'response', msgid, ...res });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
errorMessage: extractErrorMessage(err, 'Error executing SQL script'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,52 +237,64 @@ async function handleSqlSelect({ msgid, select }) {
|
||||
return handleQueryData({ msgid, sql: dmp.s }, true);
|
||||
}
|
||||
|
||||
async function handleDriverDataCore(msgid, callMethod) {
|
||||
async function handleDriverDataCore(msgid, callMethod, { logName }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const result = await callMethod(driver);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
logger.error(extractErrorLogData(err, { logName }), `Error when handling message ${logName}`);
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: extractErrorMessage(err, 'Error executing DB data') });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSchemaList({ msgid }) {
|
||||
logger.debug('Loading schema list');
|
||||
return handleDriverDataCore(msgid, driver => driver.listSchemas(dbhan), { logName: 'listSchemas' });
|
||||
}
|
||||
|
||||
async function handleCollectionData({ msgid, options }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.readCollection(systemConnection, options));
|
||||
return handleDriverDataCore(msgid, driver => driver.readCollection(dbhan, options), { logName: 'readCollection' });
|
||||
}
|
||||
|
||||
async function handleLoadKeys({ msgid, root, filter }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeys(systemConnection, root, filter));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeys(dbhan, root, filter), { logName: 'loadKeys' });
|
||||
}
|
||||
|
||||
async function handleExportKeys({ msgid, options }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.exportKeys(systemConnection, options));
|
||||
return handleDriverDataCore(msgid, driver => driver.exportKeys(dbhan, options), { logName: 'exportKeys' });
|
||||
}
|
||||
|
||||
async function handleLoadKeyInfo({ msgid, key }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(systemConnection, key));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(dbhan, key), { logName: 'loadKeyInfo' });
|
||||
}
|
||||
|
||||
async function handleCallMethod({ msgid, method, args }) {
|
||||
return handleDriverDataCore(msgid, driver => {
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only, cannot call custom methods');
|
||||
}
|
||||
return handleDriverDataCore(
|
||||
msgid,
|
||||
driver => {
|
||||
if (storedConnection.isReadOnly) {
|
||||
throw new Error('Connection is read only, cannot call custom methods');
|
||||
}
|
||||
|
||||
ensureExecuteCustomScript(driver);
|
||||
return driver.callMethod(systemConnection, method, args);
|
||||
});
|
||||
ensureExecuteCustomScript(driver);
|
||||
return driver.callMethod(dbhan, method, args);
|
||||
},
|
||||
{ logName: `callMethod:${method}` }
|
||||
);
|
||||
}
|
||||
|
||||
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(systemConnection, key, cursor, count));
|
||||
return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(dbhan, key, cursor, count), {
|
||||
logName: 'loadKeyTableRange',
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLoadFieldValues({ msgid, schemaName, pureName, field, search }) {
|
||||
return handleDriverDataCore(msgid, driver =>
|
||||
driver.loadFieldValues(systemConnection, { schemaName, pureName }, field, search)
|
||||
);
|
||||
return handleDriverDataCore(msgid, driver => driver.loadFieldValues(dbhan, { schemaName, pureName }, field, search), {
|
||||
logName: 'loadFieldValues',
|
||||
});
|
||||
}
|
||||
|
||||
function ensureExecuteCustomScript(driver) {
|
||||
@@ -264,10 +311,10 @@ async function handleUpdateCollection({ msgid, changeSet }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
ensureExecuteCustomScript(driver);
|
||||
const result = await driver.updateCollection(systemConnection, changeSet);
|
||||
const result = await driver.updateCollection(dbhan, changeSet);
|
||||
process.send({ msgtype: 'response', msgid, result });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: extractErrorMessage(err, 'Error updating collection') });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,18 +324,24 @@ async function handleSqlPreview({ msgid, objects, options }) {
|
||||
|
||||
try {
|
||||
const dmp = driver.createDumper();
|
||||
const generator = new SqlGenerator(analysedStructure, options, objects, dmp, driver, systemConnection);
|
||||
const generator = new SqlGenerator(analysedStructure, options, objects, dmp, driver, dbhan);
|
||||
|
||||
await generator.dump();
|
||||
process.send({ msgtype: 'response', msgid, sql: dmp.s, isTruncated: generator.isTruncated });
|
||||
if (generator.isUnhandledException) {
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
logger.error('Exiting because of unhandled exception');
|
||||
await driver.close(dbhan);
|
||||
process.exit(0);
|
||||
}, 500);
|
||||
}
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, isError: true, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
isError: true,
|
||||
errorMessage: extractErrorMessage(err, 'Error generating SQL preview'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,14 +350,19 @@ async function handleGenerateDeploySql({ msgid, modelFolder }) {
|
||||
|
||||
try {
|
||||
const res = await generateDeploySql({
|
||||
systemConnection,
|
||||
systemConnection: dbhan,
|
||||
connection: storedConnection,
|
||||
analysedStructure,
|
||||
modelFolder,
|
||||
});
|
||||
process.send({ ...res, msgtype: 'response', msgid });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, isError: true, errorMessage: err.message });
|
||||
process.send({
|
||||
msgtype: 'response',
|
||||
msgid,
|
||||
isError: true,
|
||||
errorMessage: extractErrorMessage(err, 'Error generating deploy SQL'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +395,7 @@ const messageHandlers = {
|
||||
loadFieldValues: handleLoadFieldValues,
|
||||
sqlSelect: handleSqlSelect,
|
||||
exportKeys: handleExportKeys,
|
||||
schemaList: handleSchemaList,
|
||||
// runCommand: handleRunCommand,
|
||||
};
|
||||
|
||||
@@ -348,10 +407,12 @@ async function handleMessage({ msgtype, ...other }) {
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
|
||||
setInterval(() => {
|
||||
setInterval(async () => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 40 * 1000) {
|
||||
logger.info('Database connection not alive, exiting');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
await driver.close(dbhan);
|
||||
process.exit(0);
|
||||
}
|
||||
}, 10 * 1000);
|
||||
@@ -361,8 +422,8 @@ function start() {
|
||||
try {
|
||||
await handleMessage(message);
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error in DB connection');
|
||||
process.send({ msgtype: 'error', error: err.message });
|
||||
logger.error(extractErrorLogData(err), 'Error in DB connection');
|
||||
process.send({ msgtype: 'error', error: extractErrorMessage(err, 'Error processing message') });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger } = require('dbgate-tools');
|
||||
const { extractBoolSettingsValue, extractIntSettingsValue, getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const logger = getLogger('srvconnProcess');
|
||||
|
||||
let systemConnection;
|
||||
let dbhan;
|
||||
let storedConnection;
|
||||
let lastDatabases = null;
|
||||
let lastStatus = null;
|
||||
@@ -16,7 +16,7 @@ let afterConnectCallbacks = [];
|
||||
async function handleRefresh() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
let databases = await driver.listDatabases(systemConnection);
|
||||
let databases = await driver.listDatabases(dbhan);
|
||||
if (storedConnection?.allowedDatabases?.trim()) {
|
||||
const allowedDatabaseList = storedConnection.allowedDatabases
|
||||
.split('\n')
|
||||
@@ -39,14 +39,14 @@ async function handleRefresh() {
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
});
|
||||
// console.error(err);
|
||||
logger.error(extractErrorLogData(err), 'Error refreshing server databases');
|
||||
setTimeout(() => process.exit(1), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
const version = await driver.getVersion(dbhan);
|
||||
process.send({ msgtype: 'version', version });
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ async function handleConnect(connection) {
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
dbhan = await connectUtility(driver, storedConnection, 'app');
|
||||
readVersion();
|
||||
handleRefresh();
|
||||
if (extractBoolSettingsValue(globalSettings, 'connection.autoRefresh', false)) {
|
||||
@@ -84,7 +84,7 @@ async function handleConnect(connection) {
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
});
|
||||
// console.error(err);
|
||||
logger.error(extractErrorLogData(err), 'Error connecting to server');
|
||||
setTimeout(() => process.exit(1), 1000);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ async function handleConnect(connection) {
|
||||
}
|
||||
|
||||
function waitConnected() {
|
||||
if (systemConnection) return Promise.resolve();
|
||||
if (dbhan) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
afterConnectCallbacks.push([resolve, reject]);
|
||||
});
|
||||
@@ -108,14 +108,14 @@ function handlePing() {
|
||||
async function handleDatabaseOp(op, { msgid, name }) {
|
||||
try {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
dbhan = await connectUtility(driver, storedConnection, 'app');
|
||||
if (driver[op]) {
|
||||
await driver[op](systemConnection, name);
|
||||
await driver[op](dbhan, name);
|
||||
} else {
|
||||
const dmp = driver.createDumper();
|
||||
dmp[op](name);
|
||||
logger.info({ sql: dmp.s }, 'Running script');
|
||||
await driver.query(systemConnection, dmp.s, { discardResult: true });
|
||||
await driver.query(dbhan, dmp.s, { discardResult: true });
|
||||
}
|
||||
await handleRefresh();
|
||||
|
||||
@@ -137,11 +137,11 @@ async function handleDriverDataCore(msgid, callMethod) {
|
||||
}
|
||||
|
||||
async function handleServerSummary({ msgid }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.serverSummary(systemConnection));
|
||||
return handleDriverDataCore(msgid, driver => driver.serverSummary(dbhan));
|
||||
}
|
||||
|
||||
async function handleSummaryCommand({ msgid, command, row }) {
|
||||
return handleDriverDataCore(msgid, driver => driver.summaryCommand(systemConnection, command, row));
|
||||
return handleDriverDataCore(msgid, driver => driver.summaryCommand(dbhan, command, row));
|
||||
}
|
||||
|
||||
const messageHandlers = {
|
||||
@@ -161,10 +161,12 @@ async function handleMessage({ msgtype, ...other }) {
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
|
||||
setInterval(() => {
|
||||
setInterval(async () => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 40 * 1000) {
|
||||
logger.info('Server connection not alive, exiting');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
await driver.close(dbhan);
|
||||
process.exit(0);
|
||||
}
|
||||
}, 10 * 1000);
|
||||
@@ -178,6 +180,7 @@ function start() {
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
});
|
||||
logger.error(extractErrorLogData(err), `Error processing message ${message?.['msgtype']}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const { getLogger, extractIntSettingsValue, extractBoolSettingsValue } = require
|
||||
|
||||
const logger = getLogger('sessionProcess');
|
||||
|
||||
let systemConnection;
|
||||
let dbhan;
|
||||
let storedConnection;
|
||||
let afterConnectCallbacks = [];
|
||||
// let currentHandlers = [];
|
||||
@@ -177,7 +177,7 @@ function handleStream(driver, resultIndexHolder, sqlItem) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = sqlItem.trimStart || sqlItem.start;
|
||||
const handler = new StreamHandler(resultIndexHolder, resolve, start && start.line);
|
||||
driver.stream(systemConnection, sqlItem.text, handler);
|
||||
driver.stream(dbhan, sqlItem.text, handler);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||
dbhan = await connectUtility(driver, storedConnection, 'app');
|
||||
for (const [resolve] of afterConnectCallbacks) {
|
||||
resolve();
|
||||
}
|
||||
@@ -210,7 +210,7 @@ async function handleConnect(connection) {
|
||||
// }
|
||||
|
||||
function waitConnected() {
|
||||
if (systemConnection) return Promise.resolve();
|
||||
if (dbhan) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
afterConnectCallbacks.push([resolve, reject]);
|
||||
});
|
||||
@@ -230,7 +230,7 @@ async function handleStartProfiler({ jslid }) {
|
||||
const writer = new TableWriter();
|
||||
writer.initializeFromReader(jslid);
|
||||
|
||||
currentProfiler = await driver.startProfiler(systemConnection, {
|
||||
currentProfiler = await driver.startProfiler(dbhan, {
|
||||
row: data => writer.rowFromReader(data),
|
||||
});
|
||||
currentProfiler.writer = writer;
|
||||
@@ -241,7 +241,7 @@ async function handleStopProfiler({ jslid }) {
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
currentProfiler.writer.close();
|
||||
driver.stopProfiler(systemConnection, currentProfiler);
|
||||
driver.stopProfiler(dbhan, currentProfiler);
|
||||
currentProfiler = null;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
|
||||
const writer = new TableWriter();
|
||||
writer.initializeFromReader(jslid);
|
||||
|
||||
const reader = await driver.readQuery(systemConnection, sql);
|
||||
const reader = await driver.readQuery(dbhan, sql);
|
||||
|
||||
reader.on('data', data => {
|
||||
writer.rowFromReader(data);
|
||||
@@ -340,10 +340,12 @@ function start() {
|
||||
|
||||
lastPing = new Date().getTime();
|
||||
|
||||
setInterval(() => {
|
||||
setInterval(async () => {
|
||||
const time = new Date().getTime();
|
||||
if (time - lastPing > 25 * 1000) {
|
||||
logger.info('Session not alive, exiting');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
await driver.close(dbhan);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -362,6 +364,8 @@ function start() {
|
||||
executingScripts == 0
|
||||
) {
|
||||
logger.info('Session not active, exiting');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
await driver.close(dbhan);
|
||||
process.exit(0);
|
||||
}
|
||||
}, 10 * 1000);
|
||||
|
||||
@@ -3,7 +3,7 @@ const platformInfo = require('../utility/platformInfo');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const { handleProcessCommunication } = require('../utility/processComm');
|
||||
const { SSHConnection } = require('../utility/SSHConnection');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData, extractErrorMessage } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('sshProcess');
|
||||
|
||||
@@ -40,13 +40,13 @@ async function handleStart({ connection, tunnelConfig }) {
|
||||
tunnelConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error creating SSH tunnel connection:');
|
||||
logger.error(extractErrorLogData(err), 'Error creating SSH tunnel connection:');
|
||||
|
||||
process.send({
|
||||
msgtype: 'error',
|
||||
connection,
|
||||
tunnelConfig,
|
||||
errorMessage: err.message,
|
||||
errorMessage: extractErrorMessage(err.message),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/api/src/shell/autoIndexForeignKeysTransform.js
Normal file
19
packages/api/src/shell/autoIndexForeignKeysTransform.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const autoIndexForeignKeysTransform = () => database => {
|
||||
return {
|
||||
...database,
|
||||
tables: database.tables.map(table => {
|
||||
return {
|
||||
...table,
|
||||
indexes: [
|
||||
...(table.indexes || []),
|
||||
...table.foreignKeys.map(fk => ({
|
||||
constraintName: `IX_${fk.constraintName}`,
|
||||
columns: fk.columns.map(x => ({ columnName: x.columnName })),
|
||||
})),
|
||||
],
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = autoIndexForeignKeysTransform;
|
||||
@@ -12,6 +12,7 @@ const { resolveArchiveFolder } = require('../utility/directories');
|
||||
async function dataDuplicator({
|
||||
connection,
|
||||
archive,
|
||||
folder,
|
||||
items,
|
||||
options,
|
||||
analysedStructure = null,
|
||||
@@ -19,32 +20,44 @@ async function dataDuplicator({
|
||||
systemConnection,
|
||||
}) {
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
|
||||
logger.info(`Connected.`);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(pool);
|
||||
try {
|
||||
logger.info(`Connected.`);
|
||||
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(dbhan);
|
||||
}
|
||||
|
||||
const sourceDir = archive
|
||||
? resolveArchiveFolder(archive)
|
||||
: folder?.startsWith('archive:')
|
||||
? resolveArchiveFolder(folder.substring('archive:'.length))
|
||||
: folder;
|
||||
|
||||
const dupl = new DataDuplicator(
|
||||
dbhan,
|
||||
driver,
|
||||
analysedStructure,
|
||||
items.map(item => ({
|
||||
name: item.name,
|
||||
operation: item.operation,
|
||||
matchColumns: item.matchColumns,
|
||||
openStream:
|
||||
item.openStream || (() => jsonLinesReader({ fileName: path.join(sourceDir, `${item.name}.jsonl`) })),
|
||||
})),
|
||||
stream,
|
||||
copyStream,
|
||||
options
|
||||
);
|
||||
|
||||
await dupl.run();
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
|
||||
const dupl = new DataDuplicator(
|
||||
pool,
|
||||
driver,
|
||||
analysedStructure,
|
||||
items.map(item => ({
|
||||
name: item.name,
|
||||
operation: item.operation,
|
||||
matchColumns: item.matchColumns,
|
||||
openStream:
|
||||
item.openStream ||
|
||||
(() => jsonLinesReader({ fileName: path.join(resolveArchiveFolder(archive), `${item.name}.jsonl`) })),
|
||||
})),
|
||||
stream,
|
||||
copyStream,
|
||||
options
|
||||
);
|
||||
|
||||
await dupl.run();
|
||||
}
|
||||
|
||||
module.exports = dataDuplicator;
|
||||
|
||||
21
packages/api/src/shell/dataTypeMapperTransform.js
Normal file
21
packages/api/src/shell/dataTypeMapperTransform.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const dataTypeMapperTransform = (oldType, newType) => database => {
|
||||
return {
|
||||
...database,
|
||||
tables: database.tables.map(table => {
|
||||
return {
|
||||
...table,
|
||||
columns: table.columns.map(column => {
|
||||
if (column.dataType?.toLowerCase() === oldType?.toLowerCase()) {
|
||||
return {
|
||||
...column,
|
||||
dataType: newType,
|
||||
};
|
||||
}
|
||||
return column;
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = dataTypeMapperTransform;
|
||||
@@ -1,17 +1,56 @@
|
||||
const generateDeploySql = require('./generateDeploySql');
|
||||
const executeQuery = require('./executeQuery');
|
||||
const { ScriptDrivedDeployer } = require('dbgate-datalib');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const loadModelFolder = require('../utility/loadModelFolder');
|
||||
const crypto = require('crypto');
|
||||
|
||||
async function deployDb({ connection, systemConnection, driver, analysedStructure, modelFolder, loadedDbModel }) {
|
||||
const { sql } = await generateDeploySql({
|
||||
connection,
|
||||
systemConnection,
|
||||
driver,
|
||||
analysedStructure,
|
||||
modelFolder,
|
||||
loadedDbModel,
|
||||
});
|
||||
// console.log('RUNNING DEPLOY SCRIPT:', sql);
|
||||
await executeQuery({ connection, systemConnection, driver, sql });
|
||||
async function deployDb({
|
||||
connection,
|
||||
systemConnection,
|
||||
driver,
|
||||
analysedStructure,
|
||||
modelFolder,
|
||||
loadedDbModel,
|
||||
modelTransforms,
|
||||
dbdiffOptionsExtra,
|
||||
ignoreNameRegex = '',
|
||||
targetSchema = null,
|
||||
}) {
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||
|
||||
try {
|
||||
const scriptDeployer = new ScriptDrivedDeployer(
|
||||
dbhan,
|
||||
driver,
|
||||
Array.isArray(loadedDbModel) ? loadedDbModel : modelFolder ? await loadModelFolder(modelFolder) : [],
|
||||
crypto
|
||||
);
|
||||
await scriptDeployer.runPre();
|
||||
|
||||
const { sql } = await generateDeploySql({
|
||||
connection,
|
||||
systemConnection: dbhan,
|
||||
driver,
|
||||
analysedStructure,
|
||||
modelFolder,
|
||||
loadedDbModel,
|
||||
modelTransforms,
|
||||
dbdiffOptionsExtra,
|
||||
ignoreNameRegex,
|
||||
targetSchema,
|
||||
});
|
||||
// console.log('RUNNING DEPLOY SCRIPT:', sql);
|
||||
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
|
||||
|
||||
await scriptDeployer.runPost();
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = deployDb;
|
||||
|
||||
42
packages/api/src/shell/dropAllDbObjects.js
Normal file
42
packages/api/src/shell/dropAllDbObjects.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const executeQuery = require('./executeQuery');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { getLogger, extendDatabaseInfo } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('dropAllDbObjects');
|
||||
|
||||
async function dropAllDbObjects({ connection, systemConnection, driver, analysedStructure }) {
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
|
||||
logger.info(`Connected.`);
|
||||
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(dbhan);
|
||||
}
|
||||
|
||||
analysedStructure = extendDatabaseInfo(analysedStructure);
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
|
||||
for (const table of analysedStructure.tables) {
|
||||
for (const fk of table.foreignKeys) {
|
||||
dmp.dropForeignKey(fk);
|
||||
}
|
||||
}
|
||||
for (const table of analysedStructure.tables) {
|
||||
dmp.dropTable(table);
|
||||
}
|
||||
for (const field of Object.keys(analysedStructure)) {
|
||||
if (dmp.getSqlObjectSqlName(field)) {
|
||||
for (const obj of analysedStructure[field]) {
|
||||
dmp.dropSqlObject(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await executeQuery({ connection, systemConnection, driver, sql: dmp.s, logScriptItems: true });
|
||||
}
|
||||
|
||||
module.exports = dropAllDbObjects;
|
||||
@@ -27,15 +27,23 @@ async function dumpDatabase({
|
||||
logger.info(`Dumping database`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
||||
logger.info(`Connected.`);
|
||||
|
||||
const dumper = await driver.createBackupDumper(pool, {
|
||||
outputFile,
|
||||
databaseName,
|
||||
schemaName,
|
||||
});
|
||||
await doDump(dumper);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
||||
|
||||
try {
|
||||
logger.info(`Connected.`);
|
||||
|
||||
const dumper = await driver.createBackupDumper(dbhan, {
|
||||
outputFile,
|
||||
databaseName,
|
||||
schemaName,
|
||||
});
|
||||
await doDump(dumper);
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = dumpDatabase;
|
||||
|
||||
@@ -1,17 +1,39 @@
|
||||
const fs = require('fs-extra');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, getLimitedQuery } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('execQuery');
|
||||
|
||||
async function executeQuery({ connection = undefined, systemConnection = undefined, driver = undefined, sql }) {
|
||||
logger.info({ sql }, `Execute query`);
|
||||
async function executeQuery({
|
||||
connection = undefined,
|
||||
systemConnection = undefined,
|
||||
driver = undefined,
|
||||
sql,
|
||||
sqlFile = undefined,
|
||||
logScriptItems = false,
|
||||
}) {
|
||||
if (!logScriptItems) {
|
||||
logger.info({ sql: getLimitedQuery(sql) }, `Execute query`);
|
||||
}
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'script'));
|
||||
logger.info(`Connected.`);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'script'));
|
||||
|
||||
await driver.script(pool, sql);
|
||||
if (sqlFile) {
|
||||
logger.debug(`Loading SQL file ${sqlFile}`);
|
||||
sql = await fs.readFile(sqlFile, { encoding: 'utf-8' });
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Connected.`);
|
||||
|
||||
await driver.script(dbhan, sql, { logScriptItems });
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = executeQuery;
|
||||
|
||||
@@ -6,6 +6,10 @@ const {
|
||||
extendDatabaseInfo,
|
||||
modelCompareDbDiffOptions,
|
||||
enrichWithPreloadedRows,
|
||||
skipNamesInStructureByRegex,
|
||||
replaceSchemaInStructure,
|
||||
filterStructureBySchema,
|
||||
skipDbGateInternalObjects,
|
||||
} = require('dbgate-tools');
|
||||
const importDbModel = require('../utility/importDbModel');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
@@ -18,44 +22,74 @@ async function generateDeploySql({
|
||||
analysedStructure = undefined,
|
||||
modelFolder = undefined,
|
||||
loadedDbModel = undefined,
|
||||
modelTransforms = undefined,
|
||||
dbdiffOptionsExtra = {},
|
||||
ignoreNameRegex = '',
|
||||
targetSchema = null,
|
||||
}) {
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(pool);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||
|
||||
try {
|
||||
if (!analysedStructure) {
|
||||
analysedStructure = await driver.analyseFull(dbhan);
|
||||
}
|
||||
|
||||
if (ignoreNameRegex) {
|
||||
analysedStructure = skipNamesInStructureByRegex(analysedStructure, new RegExp(ignoreNameRegex, 'i'));
|
||||
}
|
||||
analysedStructure = skipDbGateInternalObjects(analysedStructure);
|
||||
|
||||
let deployedModelSource = loadedDbModel
|
||||
? databaseInfoFromYamlModel(loadedDbModel)
|
||||
: await importDbModel(modelFolder);
|
||||
|
||||
for (const transform of modelTransforms || []) {
|
||||
deployedModelSource = transform(deployedModelSource);
|
||||
}
|
||||
|
||||
if (targetSchema) {
|
||||
deployedModelSource = replaceSchemaInStructure(deployedModelSource, targetSchema);
|
||||
analysedStructure = filterStructureBySchema(analysedStructure, targetSchema);
|
||||
}
|
||||
|
||||
const deployedModel = generateDbPairingId(extendDatabaseInfo(deployedModelSource));
|
||||
const currentModel = generateDbPairingId(extendDatabaseInfo(analysedStructure));
|
||||
const opts = {
|
||||
...modelCompareDbDiffOptions,
|
||||
|
||||
noDropTable: true,
|
||||
noDropColumn: true,
|
||||
noDropConstraint: true,
|
||||
noDropSqlObject: true,
|
||||
noRenameTable: true,
|
||||
noRenameColumn: true,
|
||||
|
||||
...dbdiffOptionsExtra,
|
||||
};
|
||||
const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
|
||||
const currentModelPairedPreloaded = await enrichWithPreloadedRows(deployedModel, currentModelPaired, dbhan, driver);
|
||||
|
||||
// console.log('currentModelPairedPreloaded', currentModelPairedPreloaded.tables[0]);
|
||||
// console.log('deployedModel', deployedModel.tables[0]);
|
||||
// console.log('currentModel', currentModel.tables[0]);
|
||||
// console.log('currentModelPaired', currentModelPaired.tables[0]);
|
||||
const res = getAlterDatabaseScript(
|
||||
currentModelPairedPreloaded,
|
||||
deployedModel,
|
||||
opts,
|
||||
currentModelPairedPreloaded,
|
||||
deployedModel,
|
||||
driver
|
||||
);
|
||||
|
||||
return res;
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
|
||||
const deployedModel = generateDbPairingId(
|
||||
extendDatabaseInfo(loadedDbModel ? databaseInfoFromYamlModel(loadedDbModel) : await importDbModel(modelFolder))
|
||||
);
|
||||
const currentModel = generateDbPairingId(extendDatabaseInfo(analysedStructure));
|
||||
const opts = {
|
||||
...modelCompareDbDiffOptions,
|
||||
|
||||
noDropTable: true,
|
||||
noDropColumn: true,
|
||||
noDropConstraint: true,
|
||||
noDropSqlObject: true,
|
||||
noRenameTable: true,
|
||||
noRenameColumn: true,
|
||||
};
|
||||
const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
|
||||
const currentModelPairedPreloaded = await enrichWithPreloadedRows(deployedModel, currentModelPaired, pool, driver);
|
||||
|
||||
// console.log('currentModelPairedPreloaded', currentModelPairedPreloaded.tables[0]);
|
||||
// console.log('deployedModel', deployedModel.tables[0]);
|
||||
// console.log('currentModel', currentModel.tables[0]);
|
||||
// console.log('currentModelPaired', currentModelPaired.tables[0]);
|
||||
const res = getAlterDatabaseScript(
|
||||
currentModelPairedPreloaded,
|
||||
deployedModel,
|
||||
opts,
|
||||
currentModelPairedPreloaded,
|
||||
deployedModel,
|
||||
driver
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = generateDeploySql;
|
||||
|
||||
@@ -9,14 +9,28 @@ const { getLogger } = require('dbgate-tools');
|
||||
const logger = getLogger('importDb');
|
||||
|
||||
class ImportStream extends stream.Transform {
|
||||
constructor(pool, driver) {
|
||||
constructor(dbhan, driver) {
|
||||
super({ objectMode: true });
|
||||
this.pool = pool;
|
||||
this.dbhan = dbhan;
|
||||
this.driver = driver;
|
||||
this.writeQueryStream = null;
|
||||
}
|
||||
async _transform(chunk, encoding, cb) {
|
||||
try {
|
||||
await this.driver.script(this.pool, chunk);
|
||||
if (chunk.specialMarker == 'copy_stdin_start') {
|
||||
this.writeQueryStream = await this.driver.writeQueryFromStream(this.dbhan, chunk.text);
|
||||
} else if (chunk.specialMarker == 'copy_stdin_line') {
|
||||
this.writeQueryStream.write(chunk.text);
|
||||
} else if (chunk.specialMarker == 'copy_stdin_end') {
|
||||
this.writeQueryStream.end();
|
||||
await new Promise((resolve, reject) => {
|
||||
this.writeQueryStream.on('finish', resolve);
|
||||
this.writeQueryStream.on('error', reject);
|
||||
});
|
||||
this.writeQueryStream = null;
|
||||
} else {
|
||||
await this.driver.script(this.dbhan, chunk.text, { queryOptions: { importSqlDump: true } });
|
||||
}
|
||||
} catch (err) {
|
||||
this.emit('error', err.message);
|
||||
}
|
||||
@@ -44,17 +58,28 @@ async function importDatabase({ connection = undefined, systemConnection = undef
|
||||
logger.info(`Importing database`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
logger.info(`Connected.`);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
try {
|
||||
logger.info(`Connected.`);
|
||||
|
||||
const downloadedFile = await download(inputFile);
|
||||
logger.info(`Input file: ${inputFile}`);
|
||||
const downloadedFile = await download(inputFile);
|
||||
logger.info(`Downloaded file: ${downloadedFile}`);
|
||||
|
||||
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
||||
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions('script'));
|
||||
const importStream = new ImportStream(pool, driver);
|
||||
// @ts-ignore
|
||||
splittedStream.pipe(importStream);
|
||||
await awaitStreamEnd(importStream);
|
||||
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
||||
const splittedStream = splitQueryStream(fileStream, {
|
||||
...driver.getQuerySplitterOptions('import'),
|
||||
returnRichInfo: true,
|
||||
});
|
||||
const importStream = new ImportStream(dbhan, driver);
|
||||
// @ts-ignore
|
||||
splittedStream.pipe(importStream);
|
||||
await awaitStreamEnd(importStream);
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = importDatabase;
|
||||
|
||||
@@ -6,7 +6,7 @@ const copyStream = require('./copyStream');
|
||||
const fakeObjectReader = require('./fakeObjectReader');
|
||||
const consoleObjectWriter = require('./consoleObjectWriter');
|
||||
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||
const jsonArrayWriter = require('./jsonArrayWriter');
|
||||
const jsonWriter = require('./jsonWriter');
|
||||
const jsonLinesReader = require('./jsonLinesReader');
|
||||
const sqlDataWriter = require('./sqlDataWriter');
|
||||
const jslDataReader = require('./jslDataReader');
|
||||
@@ -29,6 +29,12 @@ const modifyJsonLinesReader = require('./modifyJsonLinesReader');
|
||||
const dataDuplicator = require('./dataDuplicator');
|
||||
const dbModelToJson = require('./dbModelToJson');
|
||||
const jsonToDbModel = require('./jsonToDbModel');
|
||||
const jsonReader = require('./jsonReader');
|
||||
const dataTypeMapperTransform = require('./dataTypeMapperTransform');
|
||||
const sqlTextReplacementTransform = require('./sqlTextReplacementTransform');
|
||||
const autoIndexForeignKeysTransform = require('./autoIndexForeignKeysTransform');
|
||||
const generateDeploySql = require('./generateDeploySql');
|
||||
const dropAllDbObjects = require('./dropAllDbObjects');
|
||||
|
||||
const dbgateApi = {
|
||||
queryReader,
|
||||
@@ -37,8 +43,9 @@ const dbgateApi = {
|
||||
tableReader,
|
||||
copyStream,
|
||||
jsonLinesWriter,
|
||||
jsonArrayWriter,
|
||||
jsonLinesReader,
|
||||
jsonReader,
|
||||
jsonWriter,
|
||||
sqlDataWriter,
|
||||
fakeObjectReader,
|
||||
consoleObjectWriter,
|
||||
@@ -61,6 +68,11 @@ const dbgateApi = {
|
||||
dataDuplicator,
|
||||
dbModelToJson,
|
||||
jsonToDbModel,
|
||||
dataTypeMapperTransform,
|
||||
sqlTextReplacementTransform,
|
||||
autoIndexForeignKeysTransform,
|
||||
generateDeploySql,
|
||||
dropAllDbObjects,
|
||||
};
|
||||
|
||||
requirePlugin.initializeDbgateApi(dbgateApi);
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
|
||||
const logger = getLogger('jsonArrayWriter');
|
||||
|
||||
class StringifyStream extends stream.Transform {
|
||||
constructor() {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.wasRecord = false;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
let skip = false;
|
||||
|
||||
if (!this.wasHeader) {
|
||||
skip = chunk.__isStreamHeader;
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!skip) {
|
||||
if (!this.wasRecord) {
|
||||
this.push('[\n');
|
||||
} else {
|
||||
this.push(',\n');
|
||||
}
|
||||
this.wasRecord = true;
|
||||
|
||||
this.push(JSON.stringify(chunk));
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
_flush(done) {
|
||||
if (!this.wasRecord) {
|
||||
this.push('[]\n');
|
||||
} else {
|
||||
this.push('\n]\n');
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonArrayWriter({ fileName, encoding = 'utf-8' }) {
|
||||
logger.info(`Writing file ${fileName}`);
|
||||
const stringify = new StringifyStream();
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
return stringify;
|
||||
}
|
||||
|
||||
module.exports = jsonArrayWriter;
|
||||
@@ -2,6 +2,7 @@ const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const byline = require('byline');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const download = require('./download');
|
||||
const logger = getLogger('jsonLinesReader');
|
||||
|
||||
class ParseStream extends stream.Transform {
|
||||
@@ -35,8 +36,10 @@ class ParseStream extends stream.Transform {
|
||||
async function jsonLinesReader({ fileName, encoding = 'utf-8', limitRows = undefined }) {
|
||||
logger.info(`Reading file ${fileName}`);
|
||||
|
||||
const downloadedFile = await download(fileName);
|
||||
|
||||
const fileStream = fs.createReadStream(
|
||||
fileName,
|
||||
downloadedFile,
|
||||
// @ts-ignore
|
||||
encoding
|
||||
);
|
||||
|
||||
84
packages/api/src/shell/jsonReader.js
Normal file
84
packages/api/src/shell/jsonReader.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const byline = require('byline');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { parser } = require('stream-json');
|
||||
const { pick } = require('stream-json/filters/Pick');
|
||||
const { streamArray } = require('stream-json/streamers/StreamArray');
|
||||
const { streamObject } = require('stream-json/streamers/StreamObject');
|
||||
const download = require('./download');
|
||||
|
||||
const logger = getLogger('jsonReader');
|
||||
|
||||
|
||||
class ParseStream extends stream.Transform {
|
||||
constructor({ limitRows, jsonStyle, keyField }) {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.limitRows = limitRows;
|
||||
this.jsonStyle = jsonStyle;
|
||||
this.keyField = keyField || '_key';
|
||||
this.rowsWritten = 0;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (!this.wasHeader) {
|
||||
this.push({
|
||||
__isStreamHeader: true,
|
||||
__isDynamicStructure: true,
|
||||
});
|
||||
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!this.limitRows || this.rowsWritten < this.limitRows) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push({
|
||||
...chunk.value,
|
||||
[this.keyField]: chunk.key,
|
||||
});
|
||||
} else {
|
||||
this.push(chunk.value);
|
||||
}
|
||||
|
||||
this.rowsWritten += 1;
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonReader({
|
||||
fileName,
|
||||
jsonStyle,
|
||||
keyField = '_key',
|
||||
rootField = null,
|
||||
encoding = 'utf-8',
|
||||
limitRows = undefined,
|
||||
}) {
|
||||
logger.info(`Reading file ${fileName}`);
|
||||
|
||||
const downloadedFile = await download(fileName);
|
||||
const fileStream = fs.createReadStream(
|
||||
downloadedFile,
|
||||
// @ts-ignore
|
||||
encoding
|
||||
);
|
||||
const parseJsonStream = parser();
|
||||
fileStream.pipe(parseJsonStream);
|
||||
|
||||
const parseStream = new ParseStream({ limitRows, jsonStyle, keyField });
|
||||
|
||||
const tramsformer = jsonStyle === 'object' ? streamObject() : streamArray();
|
||||
|
||||
if (rootField) {
|
||||
const filterStream = pick({ filter: rootField });
|
||||
parseJsonStream.pipe(filterStream);
|
||||
filterStream.pipe(tramsformer);
|
||||
} else {
|
||||
parseJsonStream.pipe(tramsformer);
|
||||
}
|
||||
|
||||
tramsformer.pipe(parseStream);
|
||||
|
||||
return parseStream;
|
||||
}
|
||||
|
||||
module.exports = jsonReader;
|
||||
97
packages/api/src/shell/jsonWriter.js
Normal file
97
packages/api/src/shell/jsonWriter.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const fs = require('fs');
|
||||
const stream = require('stream');
|
||||
const _ = require('lodash');
|
||||
|
||||
const logger = getLogger('jsonArrayWriter');
|
||||
|
||||
class StringifyStream extends stream.Transform {
|
||||
constructor({ jsonStyle, keyField, rootField }) {
|
||||
super({ objectMode: true });
|
||||
this.wasHeader = false;
|
||||
this.wasRecord = false;
|
||||
this.jsonStyle = jsonStyle;
|
||||
this.keyField = keyField || '_key';
|
||||
this.rootField = rootField;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
let skip = false;
|
||||
|
||||
if (!this.wasHeader) {
|
||||
skip = chunk.__isStreamHeader;
|
||||
this.wasHeader = true;
|
||||
}
|
||||
if (!skip) {
|
||||
if (!this.wasRecord) {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push(`{"${this.rootField}": {\n`);
|
||||
} else {
|
||||
this.push(`{"${this.rootField}": [\n`);
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('{\n');
|
||||
} else {
|
||||
this.push('[\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.push(',\n');
|
||||
}
|
||||
this.wasRecord = true;
|
||||
|
||||
if (this.jsonStyle === 'object') {
|
||||
const key = chunk[this.keyField] ?? chunk[Object.keys(chunk)[0]];
|
||||
this.push(`"${key}": ${JSON.stringify(_.omit(chunk, [this.keyField]))}`);
|
||||
} else {
|
||||
this.push(JSON.stringify(chunk));
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
_flush(done) {
|
||||
if (!this.wasRecord) {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push(`{"${this.rootField}": {}}\n`);
|
||||
} else {
|
||||
this.push(`{"${this.rootField}": []}\n`);
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('{}\n');
|
||||
} else {
|
||||
this.push('[]\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.rootField) {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('\n}}\n');
|
||||
} else {
|
||||
this.push('\n]}\n');
|
||||
}
|
||||
} else {
|
||||
if (this.jsonStyle === 'object') {
|
||||
this.push('\n}\n');
|
||||
} else {
|
||||
this.push('\n]\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
async function jsonWriter({ fileName, jsonStyle, keyField = '_key', rootField, encoding = 'utf-8' }) {
|
||||
logger.info(`Writing file ${fileName}`);
|
||||
const stringify = new StringifyStream({ jsonStyle, keyField, rootField });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
stringify.pipe(fileStream);
|
||||
stringify['finisher'] = fileStream;
|
||||
return stringify;
|
||||
}
|
||||
|
||||
module.exports = jsonWriter;
|
||||
@@ -9,13 +9,19 @@ async function loadDatabase({ connection = undefined, systemConnection = undefin
|
||||
logger.info(`Analysing database`);
|
||||
|
||||
if (!driver) driver = requireEngineDriver(connection);
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
||||
logger.info(`Connected.`);
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
||||
try {
|
||||
logger.info(`Connected.`);
|
||||
|
||||
const dbInfo = await driver.analyseFull(pool);
|
||||
logger.info(`Analyse finished`);
|
||||
const dbInfo = await driver.analyseFull(dbhan);
|
||||
logger.info(`Analyse finished`);
|
||||
|
||||
await exportDbModel(dbInfo, outputDir);
|
||||
await exportDbModel(dbInfo, outputDir);
|
||||
} finally {
|
||||
if (!systemConnection) {
|
||||
await driver.close(dbhan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = loadDatabase;
|
||||
|
||||
@@ -66,7 +66,7 @@ class ParseStream extends stream.Transform {
|
||||
...obj,
|
||||
...update.fields,
|
||||
},
|
||||
(v, k) => v.$$undefined$$
|
||||
(v, k) => v?.$$undefined$$
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { pluginsdir, packagedPluginsDir, getPluginBackendPath } = require('../utility/directories');
|
||||
const nativeModules = require('../nativeModules');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const authProxy = require('../utility/authProxy');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
@@ -11,7 +10,6 @@ const loadedPlugins = {};
|
||||
|
||||
const dbgateEnv = {
|
||||
dbgateApi: null,
|
||||
nativeModules,
|
||||
platformInfo,
|
||||
authProxy,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const logger = getLogger();
|
||||
@@ -11,7 +11,7 @@ async function runScript(func) {
|
||||
await func();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Error running script: ${err.message}`);
|
||||
logger.error(extractErrorLogData(err), `Error running script`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/api/src/shell/sqlTextReplacementTransform.js
Normal file
32
packages/api/src/shell/sqlTextReplacementTransform.js
Normal file
@@ -0,0 +1,32 @@
|
||||
function replaceInText(text, replacements) {
|
||||
let result = text;
|
||||
for (const key of Object.keys(replacements)) {
|
||||
result = result.split(key).join(replacements[key]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function replaceInCollection(collection, replacements) {
|
||||
if (!collection) return collection;
|
||||
return collection.map(item => {
|
||||
if (item.createSql) {
|
||||
return {
|
||||
...item,
|
||||
createSql: replaceInText(item.createSql, replacements),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
const sqlTextReplacementTransform = replacements => database => {
|
||||
return {
|
||||
...database,
|
||||
views: replaceInCollection(database.views, replacements),
|
||||
matviews: replaceInCollection(database.matviews, replacements),
|
||||
procedures: replaceInCollection(database.procedures, replacements),
|
||||
functions: replaceInCollection(database.functions, replacements),
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = sqlTextReplacementTransform;
|
||||
@@ -3,9 +3,9 @@ const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
const logger = getLogger('tableReader');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
async function tableReader({ connection, systemConnection, pureName, schemaName }) {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await connectUtility(driver, connection, 'read');
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
||||
logger.info(`Connected.`);
|
||||
|
||||
const fullName = { pureName, schemaName };
|
||||
@@ -14,26 +14,26 @@ async function tableReader({ connection, pureName, schemaName }) {
|
||||
// @ts-ignore
|
||||
logger.info(`Reading collection ${fullNameToString(fullName)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, JSON.stringify(fullName));
|
||||
return await driver.readQuery(dbhan, JSON.stringify(fullName));
|
||||
}
|
||||
|
||||
const table = await driver.analyseSingleObject(pool, fullName, 'tables');
|
||||
const table = await driver.analyseSingleObject(dbhan, fullName, 'tables');
|
||||
const query = `select * from ${quoteFullName(driver.dialect, fullName)}`;
|
||||
if (table) {
|
||||
// @ts-ignore
|
||||
logger.info(`Reading table ${fullNameToString(table)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, table);
|
||||
return await driver.readQuery(dbhan, query, table);
|
||||
}
|
||||
const view = await driver.analyseSingleObject(pool, fullName, 'views');
|
||||
const view = await driver.analyseSingleObject(dbhan, fullName, 'views');
|
||||
if (view) {
|
||||
// @ts-ignore
|
||||
logger.info(`Reading view ${fullNameToString(view)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, view);
|
||||
return await driver.readQuery(dbhan, query, view);
|
||||
}
|
||||
|
||||
return await driver.readQuery(pool, query);
|
||||
return await driver.readQuery(dbhan, query);
|
||||
}
|
||||
|
||||
module.exports = tableReader;
|
||||
|
||||
@@ -9,10 +9,10 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
|
||||
if (!driver) {
|
||||
driver = requireEngineDriver(connection);
|
||||
}
|
||||
const pool = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
||||
|
||||
logger.info(`Connected.`);
|
||||
return await driver.writeTable(pool, { schemaName, pureName }, options);
|
||||
return await driver.writeTable(dbhan, { schemaName, pureName }, options);
|
||||
}
|
||||
|
||||
module.exports = tableWriter;
|
||||
|
||||
@@ -3,7 +3,7 @@ const { fork } = require('child_process');
|
||||
const { handleProcessCommunication } = require('./processComm');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const pipeForkLogs = require('./pipeForkLogs');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const logger = getLogger('DatastoreProxy');
|
||||
|
||||
class DatastoreProxy {
|
||||
@@ -73,7 +73,7 @@ class DatastoreProxy {
|
||||
try {
|
||||
this.subprocess.send({ msgtype: 'read', msgid, offset, limit });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error getting rows');
|
||||
logger.error(extractErrorLogData(err), 'Error getting rows');
|
||||
this.subprocess = null;
|
||||
}
|
||||
});
|
||||
@@ -87,7 +87,7 @@ class DatastoreProxy {
|
||||
try {
|
||||
this.subprocess.send({ msgtype: 'notify', msgid });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error notifying subprocess');
|
||||
logger.error(extractErrorLogData(err), 'Error notifying subprocess');
|
||||
this.subprocess = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,10 +16,20 @@ function getAuthProxyUrl() {
|
||||
return 'https://auth.dbgate.eu';
|
||||
}
|
||||
|
||||
function supportsAwsIam() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getAwsIamToken(params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAuthProxySupported,
|
||||
authProxyGetRedirectUrl,
|
||||
authProxyGetTokenFromCode,
|
||||
startTokenChecking,
|
||||
getAuthProxyUrl,
|
||||
supportsAwsIam,
|
||||
getAwsIamToken,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('childProcessChecked');
|
||||
|
||||
@@ -12,7 +12,7 @@ function childProcessChecker() {
|
||||
// This will come once parent dies.
|
||||
// One way can be to check for error code ERR_IPC_CHANNEL_CLOSED
|
||||
// and call process.exit()
|
||||
logger.error({ err }, 'parent died');
|
||||
logger.error(extractErrorLogData(err), 'parent died');
|
||||
process.exit(1);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
61
packages/api/src/utility/cloudUpgrade.js
Normal file
61
packages/api/src/utility/cloudUpgrade.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const fsp = require('fs/promises');
|
||||
const semver = require('semver');
|
||||
const currentVersion = require('../currentVersion');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('cloudUpgrade');
|
||||
|
||||
async function checkCloudUpgrade() {
|
||||
try {
|
||||
const resp = await axios.default.get('https://api.github.com/repos/dbgate/dbgate/releases/latest');
|
||||
const json = resp.data;
|
||||
const version = json.name.substring(1);
|
||||
let cloudDownloadedVersion = null;
|
||||
try {
|
||||
cloudDownloadedVersion = await fsp.readFile(process.env.CLOUD_UPGRADE_FILE + '.version', 'utf-8');
|
||||
} catch (err) {
|
||||
cloudDownloadedVersion = null;
|
||||
}
|
||||
if (
|
||||
semver.gt(version, currentVersion.version) &&
|
||||
(!cloudDownloadedVersion || semver.gt(version, cloudDownloadedVersion))
|
||||
) {
|
||||
logger.info(`New version available: ${version}`);
|
||||
const zipUrl = json.assets.find(x => x.name == 'cloud-build.zip').browser_download_url;
|
||||
|
||||
const writer = fs.createWriteStream(process.env.CLOUD_UPGRADE_FILE);
|
||||
|
||||
const response = await axios.default({
|
||||
url: zipUrl,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
response.data.pipe(writer);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
writer.on('finish', resolve);
|
||||
writer.on('error', reject);
|
||||
});
|
||||
await fsp.writeFile(process.env.CLOUD_UPGRADE_FILE + '.version', version);
|
||||
|
||||
logger.info(`Downloaded new version from ${zipUrl}`);
|
||||
} else {
|
||||
logger.info(`Checked version ${version} is not newer than ${cloudDownloadedVersion ?? currentVersion.version}, upgrade skippped`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'Error checking cloud upgrade');
|
||||
}
|
||||
}
|
||||
|
||||
function startCloudUpgradeTimer() {
|
||||
// at first in 5 seconds
|
||||
setTimeout(checkCloudUpgrade, 5000);
|
||||
|
||||
// hourly
|
||||
setInterval(checkCloudUpgrade, 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
module.exports = startCloudUpgradeTimer;
|
||||
@@ -3,9 +3,10 @@ const { decryptConnection } = require('./crypting');
|
||||
const { getSshTunnelProxy } = require('./sshTunnelProxy');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const connections = require('../controllers/connections');
|
||||
const _ = require('lodash');
|
||||
|
||||
async function loadConnection(driver, storedConnection, connectionMode) {
|
||||
const { allowShellConnection } = platformInfo;
|
||||
const { allowShellConnection, allowConnectionFromEnvVariables } = platformInfo;
|
||||
|
||||
if (connectionMode == 'app') {
|
||||
return storedConnection;
|
||||
@@ -33,6 +34,16 @@ async function loadConnection(driver, storedConnection, connectionMode) {
|
||||
}
|
||||
return loadedWithDb;
|
||||
}
|
||||
|
||||
if (allowConnectionFromEnvVariables) {
|
||||
return _.mapValues(storedConnection, (value, key) => {
|
||||
if (_.isString(value) && value.startsWith('${') && value.endsWith('}')) {
|
||||
return process.env[value.slice(2, -1)];
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
return storedConnection;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,15 @@ function packagedPluginsDir() {
|
||||
if (platformInfo.isDevMode) {
|
||||
return path.resolve(__dirname, '../../../../plugins');
|
||||
}
|
||||
if (platformInfo.isBuiltWebMode) {
|
||||
return path.resolve(__dirname, '../../plugins');
|
||||
}
|
||||
if (platformInfo.isDocker) {
|
||||
return '/home/dbgate-docker/plugins';
|
||||
}
|
||||
if (platformInfo.isAwsUbuntuLayout) {
|
||||
return '/home/ubuntu/build/plugins';
|
||||
}
|
||||
if (platformInfo.isNpmDist) {
|
||||
// node_modules
|
||||
return global['PLUGINS_DIR'];
|
||||
|
||||
80
packages/api/src/utility/exportDbModelSql.js
Normal file
80
packages/api/src/utility/exportDbModelSql.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { getSchemasUsedByStructure } = require('dbgate-tools');
|
||||
|
||||
async function exportDbModelSql(dbModel, driver, outputDir, outputFile) {
|
||||
const { tables, views, procedures, functions, triggers, matviews } = dbModel;
|
||||
|
||||
const usedSchemas = getSchemasUsedByStructure(dbModel);
|
||||
const useSchemaDir = usedSchemas.length > 1;
|
||||
|
||||
const createdDirs = new Set();
|
||||
async function ensureDir(dir) {
|
||||
if (!createdDirs.has(dir)) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
createdDirs.add(dir);
|
||||
}
|
||||
}
|
||||
|
||||
async function writeLists(writeList) {
|
||||
await writeList(views, 'views');
|
||||
await writeList(procedures, 'procedures');
|
||||
await writeList(functions, 'functions');
|
||||
await writeList(triggers, 'triggers');
|
||||
await writeList(matviews, 'matviews');
|
||||
}
|
||||
|
||||
if (outputFile) {
|
||||
const dmp = driver.createDumper();
|
||||
for (const table of tables || []) {
|
||||
dmp.createTable({
|
||||
...table,
|
||||
foreignKeys: [],
|
||||
dependencies: [],
|
||||
});
|
||||
}
|
||||
for (const table of tables || []) {
|
||||
for (const fk of table.foreignKeys || []) {
|
||||
dmp.createForeignKey(fk);
|
||||
}
|
||||
}
|
||||
writeLists((list, folder) => {
|
||||
for (const obj of list || []) {
|
||||
dmp.createSqlObject(obj);
|
||||
}
|
||||
});
|
||||
|
||||
const script = dmp.s;
|
||||
await fs.writeFile(outputFile, script);
|
||||
}
|
||||
|
||||
if (outputDir) {
|
||||
for (const table of tables || []) {
|
||||
const tablesDir = useSchemaDir
|
||||
? path.join(outputDir, table.schemaName ?? 'default', 'tables')
|
||||
: path.join(outputDir, 'tables');
|
||||
await ensureDir(tablesDir);
|
||||
const dmp = driver.createDumper();
|
||||
dmp.createTable({
|
||||
...table,
|
||||
foreignKeys: [],
|
||||
dependencies: [],
|
||||
});
|
||||
await fs.writeFile(path.join(tablesDir, `${table.pureName}.sql`), dmp.s);
|
||||
}
|
||||
|
||||
await writeLists(async (list, folder) => {
|
||||
for (const obj of list || []) {
|
||||
const objdir = useSchemaDir
|
||||
? path.join(outputDir, obj.schemaName ?? 'default', folder)
|
||||
: path.join(outputDir, folder);
|
||||
await ensureDir(objdir);
|
||||
const dmp = driver.createDumper();
|
||||
dmp.createSqlObject(obj);
|
||||
await fs.writeFile(path.join(objdir, `${obj.pureName}.sql`), dmp.s);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exportDbModelSql;
|
||||
@@ -72,6 +72,7 @@ async function getPublicHardwareFingerprint() {
|
||||
country: fingerprint.country,
|
||||
region: fingerprint.region,
|
||||
isDocker: platformInfo.isDocker,
|
||||
isAwsUbuntuLayout: platformInfo.isAwsUbuntuLayout,
|
||||
isElectron: platformInfo.isElectron,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,28 +1,8 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const { databaseInfoFromYamlModel, DatabaseAnalyser } = require('dbgate-tools');
|
||||
const { startsWith } = require('lodash');
|
||||
const { archivedir, resolveArchiveFolder } = require('./directories');
|
||||
const loadFilesRecursive = require('./loadFilesRecursive');
|
||||
const loadModelFolder = require('./loadModelFolder');
|
||||
|
||||
async function importDbModel(inputDir) {
|
||||
const files = [];
|
||||
|
||||
const dir = inputDir.startsWith('archive:') ? resolveArchiveFolder(inputDir.substring('archive:'.length)) : inputDir;
|
||||
|
||||
for (const name of await loadFilesRecursive(dir)) {
|
||||
if (name.endsWith('.table.yaml') || name.endsWith('.sql')) {
|
||||
const text = await fs.readFile(path.join(dir, name), { encoding: 'utf-8' });
|
||||
|
||||
files.push({
|
||||
name: path.parse(name).base,
|
||||
text,
|
||||
json: name.endsWith('.yaml') ? yaml.load(text) : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const files = await loadModelFolder(inputDir);
|
||||
return databaseInfoFromYamlModel(files);
|
||||
}
|
||||
|
||||
|
||||
29
packages/api/src/utility/loadModelFolder.js
Normal file
29
packages/api/src/utility/loadModelFolder.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const { resolveArchiveFolder } = require('./directories');
|
||||
const loadFilesRecursive = require('./loadFilesRecursive');
|
||||
|
||||
async function loadModelFolder(inputDir) {
|
||||
const files = [];
|
||||
|
||||
const dir = inputDir.startsWith('archive:')
|
||||
? resolveArchiveFolder(inputDir.substring('archive:'.length))
|
||||
: path.resolve(inputDir);
|
||||
|
||||
for (const name of await loadFilesRecursive(dir)) {
|
||||
if (name.endsWith('.table.yaml') || name.endsWith('.sql')) {
|
||||
const text = await fs.readFile(path.join(dir, name), { encoding: 'utf-8' });
|
||||
|
||||
files.push({
|
||||
name: path.parse(name).base,
|
||||
text,
|
||||
json: name.endsWith('.yaml') ? yaml.load(text) : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
module.exports = loadModelFolder;
|
||||
36
packages/api/src/utility/loadModelTransform.js
Normal file
36
packages/api/src/utility/loadModelTransform.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { filesdir } = require('./directories');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const _ = require('lodash');
|
||||
const dbgateApi = require('../shell');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const logger = getLogger('loadModelTransform');
|
||||
|
||||
function modelTransformFromJson(json) {
|
||||
if (!dbgateApi[json.transform]) return null;
|
||||
const creator = dbgateApi[json.transform];
|
||||
return creator(...json.arguments);
|
||||
}
|
||||
|
||||
async function loadModelTransform(file) {
|
||||
if (!file) return null;
|
||||
try {
|
||||
const dir = filesdir();
|
||||
const fullPath = path.join(dir, 'modtrans', file);
|
||||
const text = await fs.readFile(fullPath, { encoding: 'utf-8' });
|
||||
const json = JSON.parse(text);
|
||||
if (_.isArray(json)) {
|
||||
const array = _.compact(json.map(x => modelTransformFromJson(x)));
|
||||
return array.length ? structure => array.reduce((acc, val) => val(acc), structure) : null;
|
||||
}
|
||||
if (_.isPlainObject(json)) {
|
||||
return modelTransformFromJson(json);
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), `Error loading model transform ${file}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = loadModelTransform;
|
||||
@@ -10,8 +10,11 @@ const isMac = platform === 'darwin';
|
||||
const isLinux = platform === 'linux';
|
||||
const isDocker = fs.existsSync('/home/dbgate-docker/public');
|
||||
const isDevMode = process.env.DEVMODE == '1';
|
||||
const isBuiltWebMode = process.env.BUILTWEBMODE == '1';
|
||||
const isNpmDist = !!global['IS_NPM_DIST'];
|
||||
const isDbModel = !!global['IS_DB_MODEL'];
|
||||
const isForkedApi = processArgs.isForkedApi;
|
||||
const isAwsUbuntuLayout = fs.existsSync('/home/ubuntu/build/public');
|
||||
|
||||
// function moduleAvailable(name) {
|
||||
// try {
|
||||
@@ -39,9 +42,21 @@ const platformInfo = {
|
||||
environment: process.env.NODE_ENV,
|
||||
platform,
|
||||
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
|
||||
allowShellConnection: (!processArgs.listenApiChild && !isNpmDist) || !!process.env.SHELL_CONNECTION || !!isElectron(),
|
||||
allowShellScripting: (!processArgs.listenApiChild && !isNpmDist) || !!process.env.SHELL_SCRIPTING || !!isElectron(),
|
||||
allowShellConnection:
|
||||
(!processArgs.listenApiChild && !isNpmDist) ||
|
||||
!!process.env.SHELL_CONNECTION ||
|
||||
!!isElectron() ||
|
||||
!!isDbModel ||
|
||||
isDevMode,
|
||||
allowShellScripting:
|
||||
(!processArgs.listenApiChild && !isNpmDist) ||
|
||||
!!process.env.SHELL_SCRIPTING ||
|
||||
!!isElectron() ||
|
||||
!!isDbModel ||
|
||||
isDevMode,
|
||||
allowConnectionFromEnvVariables: !!isDbModel,
|
||||
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
|
||||
isAwsUbuntuLayout,
|
||||
};
|
||||
|
||||
module.exports = platformInfo;
|
||||
|
||||
@@ -17,9 +17,6 @@ const listenApiChild = process.argv.includes('--listen-api-child') || listenApi;
|
||||
|
||||
function getPassArgs() {
|
||||
const res = [];
|
||||
if (global['NATIVE_MODULES']) {
|
||||
res.push('--native-modules', global['NATIVE_MODULES']);
|
||||
}
|
||||
if (global['PLUGINS_DIR']) {
|
||||
res.push('--plugins-dir', global['PLUGINS_DIR']);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const AsyncLock = require('async-lock');
|
||||
const lock = new AsyncLock();
|
||||
const { fork } = require('child_process');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const pipeForkLogs = require('./pipeForkLogs');
|
||||
const logger = getLogger('sshTunnel');
|
||||
|
||||
@@ -40,7 +40,7 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
|
||||
tunnelConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error connecting SSH');
|
||||
logger.error(extractErrorLogData(err), 'Error connecting SSH');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
subprocess.on('message', resp => {
|
||||
@@ -50,7 +50,7 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
|
||||
resolve(subprocess);
|
||||
}
|
||||
if (msgtype == 'error') {
|
||||
reject(errorMessage);
|
||||
reject(new Error(errorMessage));
|
||||
}
|
||||
});
|
||||
subprocess.on('exit', code => {
|
||||
@@ -91,6 +91,7 @@ async function getSshTunnel(connection) {
|
||||
};
|
||||
return sshTunnelCache[tunnelCacheKey];
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'Error creating SSH tunnel:');
|
||||
// error is not cached
|
||||
return {
|
||||
state: 'error',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const { getSshTunnel } = require('./sshTunnel');
|
||||
const logger = getLogger('sshTunnelProxy');
|
||||
|
||||
@@ -10,7 +10,7 @@ async function handleGetSshTunnelRequest({ msgid, connection }, subprocess) {
|
||||
try {
|
||||
subprocess.send({ msgtype: 'getsshtunnel-response', msgid, response });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error sending to SSH tunnel');
|
||||
logger.error(extractErrorLogData(err), 'Error sending to SSH tunnel');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const _ = require('lodash');
|
||||
const express = require('express');
|
||||
const getExpressPath = require('./getExpressPath');
|
||||
const { MissingCredentialsError } = require('./exceptions');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
|
||||
const logger = getLogger('useController');
|
||||
/**
|
||||
@@ -16,7 +16,7 @@ module.exports = function useController(app, electron, route, controller) {
|
||||
try {
|
||||
controller._init();
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Error initializing controller, exiting application`);
|
||||
logger.error(extractErrorLogData(err), `Error initializing controller, exiting application`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ module.exports = function useController(app, electron, route, controller) {
|
||||
const data = await controller[key]({ ...req.body, ...req.query }, req);
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Error when processing route ${route}/${key}`);
|
||||
logger.error(extractErrorLogData(err), `Error when processing route ${route}/${key}`);
|
||||
if (err instanceof MissingCredentialsError) {
|
||||
res.json({
|
||||
missingCredentials: true,
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"dependencies": {
|
||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||
"dbgate-tools": "^5.0.0-alpha.1",
|
||||
"dbgate-filterparser": "^5.0.0-alpha.1"
|
||||
"dbgate-filterparser": "^5.0.0-alpha.1",
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^5.0.0-alpha.1",
|
||||
|
||||
@@ -536,3 +536,12 @@ export function changeSetContainsChanges(changeSet: ChangeSet) {
|
||||
export function changeSetChangedCount(changeSet: ChangeSet) {
|
||||
return changeSet.deletes.length + changeSet.updates.length + changeSet.inserts.length;
|
||||
}
|
||||
|
||||
export function removeSchemaFromChangeSet(changeSet: ChangeSet) {
|
||||
return {
|
||||
...changeSet,
|
||||
inserts: changeSet.inserts.map(x => ({ ...x, schemaName: undefined })),
|
||||
updates: changeSet.updates.map(x => ({ ...x, schemaName: undefined })),
|
||||
deletes: changeSet.deletes.map(x => ({ ...x, schemaName: undefined })),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { createAsyncWriteStream, getLogger, runCommandOnDriver, runQueryOnDriver } from 'dbgate-tools';
|
||||
import {
|
||||
createAsyncWriteStream,
|
||||
extractErrorLogData,
|
||||
getLogger,
|
||||
runCommandOnDriver,
|
||||
runQueryOnDriver,
|
||||
} from 'dbgate-tools';
|
||||
import { DatabaseInfo, EngineDriver, ForeignKeyInfo, TableInfo } from 'dbgate-types';
|
||||
import _pick from 'lodash/pick';
|
||||
import _omit from 'lodash/omit';
|
||||
@@ -15,6 +21,7 @@ export interface DataDuplicatorItem {
|
||||
export interface DataDuplicatorOptions {
|
||||
rollbackAfterFinish?: boolean;
|
||||
skipRowsWithUnresolvedRefs?: boolean;
|
||||
setNullForUnresolvedNullableRefs?: boolean;
|
||||
}
|
||||
|
||||
class DuplicatorReference {
|
||||
@@ -30,9 +37,19 @@ class DuplicatorReference {
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicatorWeakReference {
|
||||
constructor(public base: DuplicatorItemHolder, public ref: TableInfo, public foreignKey: ForeignKeyInfo) {}
|
||||
|
||||
get columnName() {
|
||||
return this.foreignKey.columns[0].columnName;
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicatorItemHolder {
|
||||
references: DuplicatorReference[] = [];
|
||||
backReferences: DuplicatorReference[] = [];
|
||||
// not mandatory references to entities out of the model
|
||||
weakReferences: DuplicatorWeakReference[] = [];
|
||||
table: TableInfo;
|
||||
isPlanned = false;
|
||||
idMap = {};
|
||||
@@ -59,23 +76,33 @@ class DuplicatorItemHolder {
|
||||
for (const fk of this.table.foreignKeys) {
|
||||
if (fk.columns?.length != 1) continue;
|
||||
const refHolder = this.duplicator.itemHolders.find(y => y.name.toUpperCase() == fk.refTableName.toUpperCase());
|
||||
if (refHolder == null) continue;
|
||||
const isMandatory = this.table.columns.find(x => x.columnName == fk.columns[0]?.columnName)?.notNull;
|
||||
const newref = new DuplicatorReference(this, refHolder, isMandatory, fk);
|
||||
this.references.push(newref);
|
||||
this.refByColumn[newref.columnName] = newref;
|
||||
if (refHolder == null) {
|
||||
if (!isMandatory) {
|
||||
const weakref = new DuplicatorWeakReference(
|
||||
this,
|
||||
this.duplicator.db.tables.find(x => x.pureName == fk.refTableName),
|
||||
fk
|
||||
);
|
||||
this.weakReferences.push(weakref);
|
||||
}
|
||||
} else {
|
||||
const newref = new DuplicatorReference(this, refHolder, isMandatory, fk);
|
||||
this.references.push(newref);
|
||||
this.refByColumn[newref.columnName] = newref;
|
||||
|
||||
refHolder.isReferenced = true;
|
||||
refHolder.isReferenced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createInsertObject(chunk) {
|
||||
createInsertObject(chunk, weakrefcols: string[]) {
|
||||
const res = _omit(
|
||||
_pick(
|
||||
chunk,
|
||||
this.table.columns.map(x => x.columnName)
|
||||
),
|
||||
[this.autoColumn, ...this.backReferences.map(x => x.columnName)]
|
||||
[this.autoColumn, ...this.backReferences.map(x => x.columnName), ...weakrefcols]
|
||||
);
|
||||
|
||||
for (const key in res) {
|
||||
@@ -96,6 +123,28 @@ class DuplicatorItemHolder {
|
||||
return res;
|
||||
}
|
||||
|
||||
// returns list of columns that are weak references and are not resolved
|
||||
async getMissingWeakRefsForRow(row): Promise<string[]> {
|
||||
if (!this.duplicator.options.setNullForUnresolvedNullableRefs || !this.weakReferences?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const qres = await runQueryOnDriver(this.duplicator.pool, this.duplicator.driver, dmp => {
|
||||
dmp.put('^select ');
|
||||
dmp.putCollection(',', this.weakReferences, weakref => {
|
||||
dmp.put(
|
||||
'(^case ^when ^exists (^select * ^from %f where %i = %v) ^then 1 ^else 0 ^end) as %i',
|
||||
weakref.ref,
|
||||
weakref.foreignKey.columns[0].refColumnName,
|
||||
row[weakref.foreignKey.columns[0].columnName],
|
||||
weakref.foreignKey.columns[0].columnName
|
||||
);
|
||||
});
|
||||
});
|
||||
const qrow = qres.rows[0];
|
||||
return this.weakReferences.filter(x => qrow[x.columnName] == 0).map(x => x.columnName);
|
||||
}
|
||||
|
||||
async runImport() {
|
||||
const readStream = await this.item.openStream();
|
||||
const driver = this.duplicator.driver;
|
||||
@@ -106,6 +155,8 @@ class DuplicatorItemHolder {
|
||||
let skipped = 0;
|
||||
let lastLogged = new Date();
|
||||
|
||||
const existingWeakRefs = {};
|
||||
|
||||
const writeStream = createAsyncWriteStream(this.duplicator.stream, {
|
||||
processItem: async chunk => {
|
||||
if (chunk.__isStreamHeader) {
|
||||
@@ -114,7 +165,8 @@ class DuplicatorItemHolder {
|
||||
|
||||
const doCopy = async () => {
|
||||
// console.log('chunk', this.name, JSON.stringify(chunk));
|
||||
const insertedObj = this.createInsertObject(chunk);
|
||||
const weakrefcols = await this.getMissingWeakRefsForRow(chunk);
|
||||
const insertedObj = this.createInsertObject(chunk, weakrefcols);
|
||||
// console.log('insertedObj', this.name, JSON.stringify(insertedObj));
|
||||
if (insertedObj == null) {
|
||||
skipped += 1;
|
||||
@@ -255,7 +307,7 @@ export class DataDuplicator {
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, `Failed duplicator job, rollbacking. ${err.message}`);
|
||||
logger.error(extractErrorLogData(err), `Failed duplicator job, rollbacking. ${err.message}`);
|
||||
await runCommandOnDriver(this.pool, this.driver, dmp => dmp.rollbackTransaction());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,12 +169,12 @@ export abstract class GridDisplay {
|
||||
if (isIncluded) {
|
||||
res = {
|
||||
...res,
|
||||
[field]: [...(cfg[field] || []), uniqueName],
|
||||
[field]: _.uniq([...(cfg[field] || []), uniqueName]),
|
||||
};
|
||||
} else {
|
||||
res = {
|
||||
...res,
|
||||
[field]: (cfg[field] || []).filter(x => x != uniqueName),
|
||||
[field]: _.uniq((cfg[field] || []).filter(x => x != uniqueName)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export abstract class GridDisplay {
|
||||
conditions.push(
|
||||
_.cloneDeepWith(condition, (expr: Expression) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return this.createColumnExpression(column, { alias: column.sourceAlias });
|
||||
return this.createColumnExpression(column, { alias: column.sourceAlias }, undefined, 'filter');
|
||||
}
|
||||
// return {
|
||||
// exprType: 'column',
|
||||
@@ -253,7 +253,7 @@ export abstract class GridDisplay {
|
||||
orCondition.conditions.push(
|
||||
_.cloneDeepWith(condition, (expr: Expression) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return this.createColumnExpression(column, { alias: 'basetbl' });
|
||||
return this.createColumnExpression(column, { alias: 'basetbl' }, undefined, 'filter');
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -278,7 +278,7 @@ export abstract class GridDisplay {
|
||||
|
||||
applySortOnSelect(select: Select, displayedColumnInfo: DisplayedColumnInfo) {
|
||||
if (this.config.sort?.length > 0) {
|
||||
select.orderBy = this.config.sort
|
||||
const orderByColumns = this.config.sort
|
||||
.map(col => ({ ...col, dispInfo: displayedColumnInfo[col.uniqueName] }))
|
||||
.map(col => ({ ...col, expr: select.columns.find(x => x.alias == col.uniqueName) }))
|
||||
.filter(col => col.dispInfo && col.expr)
|
||||
@@ -286,6 +286,10 @@ export abstract class GridDisplay {
|
||||
...col.expr,
|
||||
direction: col.order,
|
||||
}));
|
||||
|
||||
if (orderByColumns.length > 0) {
|
||||
select.orderBy = orderByColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,10 +574,10 @@ export abstract class GridDisplay {
|
||||
|
||||
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {}
|
||||
|
||||
createColumnExpression(col, source, alias?) {
|
||||
createColumnExpression(col, source, alias?, purpose: 'view' | 'filter' = 'view') {
|
||||
let expr = null;
|
||||
if (this.dialect.createColumnViewExpression) {
|
||||
expr = this.dialect.createColumnViewExpression(col.columnName, col.dataType, source, alias);
|
||||
expr = this.dialect.createColumnViewExpression(col.columnName, col.dataType, source, alias, purpose);
|
||||
if (expr) {
|
||||
return expr;
|
||||
}
|
||||
@@ -595,7 +599,7 @@ export abstract class GridDisplay {
|
||||
name: _.pick(name, ['schemaName', 'pureName']),
|
||||
alias: 'basetbl',
|
||||
},
|
||||
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' })),
|
||||
columns: columns.map(col => this.createColumnExpression(col, { alias: 'basetbl' }, undefined, 'view')),
|
||||
orderBy: this.driver?.requiresDefaultSortCriteria
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ import { GridDisplay, ChangeCacheFunc, ChangeConfigFunc } from './GridDisplay';
|
||||
import { GridConfig, GridCache } from './GridConfig';
|
||||
import { analyseCollectionDisplayColumns } from './CollectionGridDisplay';
|
||||
import { evalFilterBehaviour } from 'dbgate-tools';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
|
||||
export class JslGridDisplay extends GridDisplay {
|
||||
constructor(
|
||||
@@ -15,9 +16,10 @@ export class JslGridDisplay extends GridDisplay {
|
||||
rows: any,
|
||||
isDynamicStructure: boolean,
|
||||
supportsReload: boolean,
|
||||
editable: boolean = false
|
||||
editable: boolean = false,
|
||||
driver: EngineDriver = null
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, null);
|
||||
super(config, setConfig, cache, setCache, driver);
|
||||
|
||||
this.filterable = true;
|
||||
this.sortable = true;
|
||||
|
||||
175
packages/datalib/src/ScriptDrivedDeployer.ts
Normal file
175
packages/datalib/src/ScriptDrivedDeployer.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { DatabaseModelFile, extractErrorLogData, getLogger, runCommandOnDriver, runQueryOnDriver } from 'dbgate-tools';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
|
||||
const logger = getLogger('ScriptDrivedDeployer');
|
||||
|
||||
interface DeployScriptJournalItem {
|
||||
id: number;
|
||||
name: string;
|
||||
category: string;
|
||||
first_run_date: string;
|
||||
last_run_date: string;
|
||||
script_hash: string;
|
||||
}
|
||||
|
||||
export class ScriptDrivedDeployer {
|
||||
predeploy: DatabaseModelFile[] = [];
|
||||
uninstall: DatabaseModelFile[] = [];
|
||||
install: DatabaseModelFile[] = [];
|
||||
once: DatabaseModelFile[] = [];
|
||||
postdeploy: DatabaseModelFile[] = [];
|
||||
isEmpty = false;
|
||||
|
||||
journalItems: DeployScriptJournalItem[] = [];
|
||||
|
||||
constructor(public dbhan: any, public driver: EngineDriver, public files: DatabaseModelFile[], public crypto: any) {
|
||||
this.predeploy = files.filter(x => x.name.endsWith('.predeploy.sql'));
|
||||
this.uninstall = files.filter(x => x.name.endsWith('.uninstall.sql'));
|
||||
this.install = files.filter(x => x.name.endsWith('.install.sql'));
|
||||
this.once = files.filter(x => x.name.endsWith('.once.sql'));
|
||||
this.postdeploy = files.filter(x => x.name.endsWith('.postdeploy.sql'));
|
||||
this.isEmpty =
|
||||
this.predeploy.length === 0 &&
|
||||
this.uninstall.length === 0 &&
|
||||
this.install.length === 0 &&
|
||||
this.once.length === 0 &&
|
||||
this.postdeploy.length === 0;
|
||||
}
|
||||
|
||||
async loadJournalItems() {
|
||||
try {
|
||||
const { rows } = await runQueryOnDriver(this.dbhan, this.driver, dmp =>
|
||||
dmp.put('select * from ~dbgate_deploy_journal')
|
||||
);
|
||||
this.journalItems = rows;
|
||||
logger.debug(`Loaded ${rows.length} items from DbGate deploy journal`);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
extractErrorLogData(err),
|
||||
'Error loading DbGate deploy journal, creating table dbgate_deploy_journal'
|
||||
);
|
||||
const dmp = this.driver.createDumper();
|
||||
dmp.createTable({
|
||||
pureName: 'dbgate_deploy_journal',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', autoIncrement: true, notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'name', dataType: 'varchar(100)', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'category', dataType: 'varchar(100)', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'script_hash', dataType: 'varchar(100)', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'first_run_date', dataType: 'varchar(100)', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'last_run_date', dataType: 'varchar(100)', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
{ columnName: 'run_count', dataType: 'int', notNull: true, pureName: 'dbgate_deploy_journal' },
|
||||
],
|
||||
foreignKeys: [],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
constraintType: 'primaryKey',
|
||||
pureName: 'dbgate_deploy_journal',
|
||||
},
|
||||
});
|
||||
await this.driver.query(this.dbhan, dmp.s, { discardResult: true });
|
||||
}
|
||||
}
|
||||
|
||||
async runPre() {
|
||||
// don't create journal table if no scripts are present
|
||||
if (this.isEmpty) return;
|
||||
await this.loadJournalItems();
|
||||
await this.runFiles(this.predeploy, 'predeploy');
|
||||
}
|
||||
|
||||
async runPost() {
|
||||
await this.runFiles(this.install, 'install');
|
||||
await this.runFiles(this.once, 'once');
|
||||
await this.runFiles(this.postdeploy, 'postdeploy');
|
||||
}
|
||||
|
||||
async run() {
|
||||
await this.runPre();
|
||||
await this.runPost();
|
||||
}
|
||||
|
||||
async runFiles(files: DatabaseModelFile[], category: string) {
|
||||
for (const file of _sortBy(files, x => x.name)) {
|
||||
await this.runFile(file, category);
|
||||
}
|
||||
}
|
||||
|
||||
async saveToJournal(file: DatabaseModelFile, category: string, hash: string) {
|
||||
const existing = this.journalItems.find(x => x.name == file.name);
|
||||
if (existing) {
|
||||
await runCommandOnDriver(this.dbhan, this.driver, dmp => {
|
||||
dmp.put(
|
||||
'update ~dbgate_deploy_journal set ~last_run_date = %v, ~script_hash = %v, ~run_count = ~run_count + 1 where ~id = %v',
|
||||
new Date().toISOString(),
|
||||
hash,
|
||||
existing.id
|
||||
);
|
||||
});
|
||||
} else {
|
||||
await runCommandOnDriver(this.dbhan, this.driver, dmp => {
|
||||
dmp.put(
|
||||
'insert into ~dbgate_deploy_journal (~name, ~category, ~first_run_date, ~last_run_date, ~script_hash, ~run_count) values (%v, %v, %v, %v, %v, 1)',
|
||||
file.name,
|
||||
category,
|
||||
new Date().toISOString(),
|
||||
new Date().toISOString(),
|
||||
hash
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async runFileCore(file: DatabaseModelFile, category: string, hash: string) {
|
||||
if (this.driver.supportsTransactions) {
|
||||
runCommandOnDriver(this.dbhan, this.driver, dmp => dmp.beginTransaction());
|
||||
}
|
||||
|
||||
logger.debug(`Running ${category} script ${file.name}`);
|
||||
try {
|
||||
await this.driver.script(this.dbhan, file.text);
|
||||
await this.saveToJournal(file, category, hash);
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), `Error running ${category} script ${file.name}`);
|
||||
if (this.driver.supportsTransactions) {
|
||||
runCommandOnDriver(this.dbhan, this.driver, dmp => dmp.rollbackTransaction());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.driver.supportsTransactions) {
|
||||
runCommandOnDriver(this.dbhan, this.driver, dmp => dmp.commitTransaction());
|
||||
}
|
||||
}
|
||||
|
||||
async runFile(file: DatabaseModelFile, category: string) {
|
||||
const hash = this.crypto.createHash('md5').update(file.text.trim()).digest('hex');
|
||||
const journalItem = this.journalItems.find(x => x.name == file.name);
|
||||
const isEqual = journalItem && journalItem.script_hash == hash;
|
||||
|
||||
switch (category) {
|
||||
case 'predeploy':
|
||||
case 'postdeploy':
|
||||
await this.runFileCore(file, category, hash);
|
||||
break;
|
||||
case 'once':
|
||||
if (journalItem) return;
|
||||
await this.runFileCore(file, category, hash);
|
||||
break;
|
||||
case 'install':
|
||||
if (isEqual) return;
|
||||
const uninstallFile = this.uninstall.find(x => x.name == file.name.replace('.install.sql', '.uninstall.sql'));
|
||||
if (uninstallFile && journalItem) {
|
||||
// file was previously installed, uninstall first
|
||||
await this.runFileCore(
|
||||
uninstallFile,
|
||||
'uninstall',
|
||||
this.crypto.createHash('md5').update(uninstallFile.text.trim()).digest('hex')
|
||||
);
|
||||
}
|
||||
await this.runFileCore(file, category, hash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export class TableGridDisplay extends GridDisplay {
|
||||
? this.table.primaryKey.columns.map(x => x.columnName)
|
||||
: this.table.columns.map(x => x.columnName);
|
||||
}
|
||||
|
||||
|
||||
if (this.config.isFormView) {
|
||||
this.addAllExpandedColumnsToSelected = true;
|
||||
this.hintBaseColumns = this.formColumns;
|
||||
@@ -287,7 +287,7 @@ export class TableGridDisplay extends GridDisplay {
|
||||
for (const column of columns) {
|
||||
if (this.addAllExpandedColumnsToSelected || this.config.addedColumns.includes(column.uniqueName)) {
|
||||
select.columns.push(
|
||||
this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName)
|
||||
this.createColumnExpression(column, { name: column, alias: parentAlias }, column.uniqueName, 'view')
|
||||
);
|
||||
displayedColumnInfo[column.uniqueName] = {
|
||||
...column,
|
||||
|
||||
@@ -22,3 +22,4 @@ export * from './DataDuplicator';
|
||||
export * from './FreeTableGridDisplay';
|
||||
export * from './FreeTableModel';
|
||||
export * from './CustomGridDisplay';
|
||||
export * from './ScriptDrivedDeployer';
|
||||
|
||||
@@ -8,10 +8,12 @@ global.PLUGINS_DIR = process.env.DEVMODE
|
||||
? path.join(path.dirname(path.dirname(global.API_PACKAGE)), 'plugins')
|
||||
: path.dirname(global.API_PACKAGE);
|
||||
global.IS_NPM_DIST = true;
|
||||
global.IS_DB_MODEL = true;
|
||||
|
||||
const program = require('commander');
|
||||
const dbgateApi = require('dbgate-api');
|
||||
const { createLogger } = require('pinomin');
|
||||
const { extractErrorLogData } = require('dbgate-tools');
|
||||
|
||||
const logger = createLogger('dbmodel');
|
||||
|
||||
@@ -21,7 +23,7 @@ async function runAndExit(promise) {
|
||||
logger.info('Success');
|
||||
process.exit();
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Processing failed');
|
||||
logger.error(extractErrorLogData(err), 'Processing failed');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@types/parsimmon": "^1.10.1",
|
||||
"dbgate-tools": "^5.0.0-alpha.1",
|
||||
"lodash": "^4.17.21",
|
||||
"date-fns": "^4.1.0",
|
||||
"moment": "^2.24.0",
|
||||
"parsimmon": "^1.13.0"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user