Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 875011a6ea | |||
| e544c12945 | |||
| 433d3be8d5 | |||
| 35fc2e0f5b | |||
| 93edcc4d0a | |||
| 0a06ebf9c3 | |||
| 94804957e5 | |||
| fcaac322f2 | |||
| 5fa9a303cb | |||
| 5fa3f39f69 | |||
| b13f01d3b3 | |||
| b85334cb2d | |||
| 188c2b7a9c | |||
| b63987dbfc | |||
| 09bf18eeca | |||
| e5ec444e52 | |||
| e2c313af66 | |||
| 73d8ad36ad | |||
| 8896260df7 | |||
| e8433423b4 | |||
| 9af28e7d20 | |||
| d67637c7e8 | |||
| d058003cc9 | |||
| 1ccdf7f07e | |||
| 0d7d0bdd60 | |||
| d7e7b97fb8 | |||
| f360ba7187 | |||
| 84a67be272 | |||
| 54ddfe2ef9 | |||
| 3953f764a5 | |||
| 7ef7d9d2af | |||
| f45969f5d3 | |||
| 9dbe73d9c3 | |||
| 35a48fb3d5 | |||
| dd1cfa677f | |||
| cb2f6e6a78 | |||
| 55ad3a05e4 | |||
| e6a6534887 | |||
| 5fca73d531 | |||
| 081dff38e3 | |||
| 30f291c525 | |||
| dab9d33394 | |||
| e74521fdab | |||
| a5ad8dc5f6 | |||
| 28c554a75b | |||
| 41e55f329c | |||
| 73d3d00e9d | |||
| 54fec7fd6d | |||
| 0413075af6 | |||
| c733ac9c02 | |||
| 1970ec29c5 | |||
| 08fc3ffce4 | |||
| b057fcfb3e | |||
| 67504a9481 | |||
| 95cb8c7cb6 | |||
| 183365c461 | |||
| 4cce1f6670 | |||
| 777f72af88 | |||
| dacea78123 | |||
| 44b8a14868 | |||
| aa709dbee3 | |||
| b0c7adf0d1 | |||
| f345677d4f | |||
| cc82df92ae | |||
| b151a13f65 | |||
| 3f7caa6078 | |||
| db9133af51 | |||
| bfc3717dea | |||
| 13f30891b6 | |||
| aaf6715dd1 | |||
| 98b3b242eb | |||
| 843a080fb8 | |||
| f6e2ddbf11 | |||
| 06593a5835 | |||
| b2b6d4ad24 | |||
| c4789bbb9e | |||
| ae8b498300 | |||
| f0475be69a | |||
| db7d02de87 | |||
| 61a2398fb8 | |||
| 60464052e2 | |||
| 16dffba9a9 | |||
| f30b062431 | |||
| fea9c5dd66 | |||
| 0c843ea806 | |||
| decfe3197d | |||
| 6b1243eef5 | |||
| fcb87bbfc3 | |||
| 3f2635a421 | |||
| f70c554966 | |||
| 9abc835d53 | |||
| d5648cc944 | |||
| 44e0902ded | |||
| cc6bcfb4b3 | |||
| 688086e00f | |||
| 09498f2ac3 | |||
| 7dd9e9a9b1 | |||
| 19d135e435 | |||
| d453e52ff3 | |||
| 29a77cc053 | |||
| 11fd2d1d8a | |||
| b5fe8508b1 | |||
| 25881e80db | |||
| e43fa96e34 | |||
| 0200c7c78b | |||
| 19f769480b | |||
| 65eb89de95 | |||
| a6fec38d83 | |||
| cfa4e50084 | |||
| 8e18b4f692 | |||
| 82a9368d01 | |||
| 8db86c7e02 | |||
| b3c0aa1701 | |||
| 627ec520a5 | |||
| 91ae9a92af | |||
| 4c1c251aef | |||
| 9c1179c451 | |||
| ef25ea1885 | |||
| 6b6c5b945f | |||
| ca900b0cf0 | |||
| 7c5f274f83 | |||
| 3a396c6555 | |||
| 070ffe0c56 | |||
| f770b011ce | |||
| 0d806be3dc | |||
| 2716db4bf3 | |||
| 2d22fef06b | |||
| c78aefe2ab |
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: dbgate
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: dbgate
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Electron app
|
||||
name: Electron app BETA
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
os: [macOS-10.15, windows-2016, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -35,10 +35,14 @@ jobs:
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
- name: Publish
|
||||
if: matrix.os != 'macOS-10.15'
|
||||
run: |
|
||||
yarn run build:app
|
||||
env:
|
||||
@@ -46,6 +50,13 @@ jobs:
|
||||
WIN_CSC_LINK: ${{ secrets.WINCERT_CERTIFICATE }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINCERT_PASSWORD }}
|
||||
|
||||
- name: Publish Mac
|
||||
if: matrix.os == 'macOS-10.15'
|
||||
run: |
|
||||
yarn run build:app:mac
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
|
||||
|
||||
- name: Save snap login
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
run: 'echo "$SNAPCRAFT_LOGIN" > snapcraft.login'
|
||||
@@ -64,10 +75,13 @@ jobs:
|
||||
mkdir artifacts
|
||||
|
||||
cp app/dist/*.deb artifacts/dbgate-beta.deb || true
|
||||
cp app/dist/*.AppImage artifacts/dbgate-beta.AppImage || true
|
||||
cp app/dist/*x86*.AppImage artifacts/dbgate-beta.AppImage || true
|
||||
cp app/dist/*arm64*.AppImage artifacts/dbgate-beta-arm64.AppImage || true
|
||||
cp app/dist/*armv7l*.AppImage artifacts/dbgate-beta-armv7l.AppImage || true
|
||||
cp app/dist/*.exe artifacts/dbgate-beta.exe || true
|
||||
cp app/dist/*windows*.zip artifacts/dbgate-windows-beta.zip || true
|
||||
cp app/dist/*.dmg artifacts/dbgate-beta.dmg || true
|
||||
cp app/dist/*win*.zip artifacts/dbgate-windows-beta.zip || true
|
||||
cp app/dist/*-mac.dmg artifacts/dbgate-beta.dmg || true
|
||||
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-beta-arm64.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# os: [ubuntu-18.04, windows-2016]
|
||||
os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
os: [macOS-10.15, windows-2016, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
@@ -39,6 +39,9 @@ jobs:
|
||||
- name: fillNativeModulesElectron
|
||||
run: |
|
||||
yarn fillNativeModulesElectron
|
||||
- name: fillPackagedPlugins
|
||||
run: |
|
||||
yarn fillPackagedPlugins
|
||||
- name: Install Snapcraft
|
||||
if: matrix.os == 'ubuntu-18.04'
|
||||
uses: samuelmeuli/action-snapcraft@v1
|
||||
@@ -72,10 +75,13 @@ jobs:
|
||||
mkdir artifacts
|
||||
|
||||
cp app/dist/*.deb artifacts/dbgate-latest.deb || true
|
||||
cp app/dist/*.AppImage artifacts/dbgate-latest.AppImage || true
|
||||
cp app/dist/*x86*.AppImage artifacts/dbgate-latest.AppImage || true
|
||||
cp app/dist/*arm64*.AppImage artifacts/dbgate-latest-arm64.AppImage || true
|
||||
cp app/dist/*armv7l*.AppImage artifacts/dbgate-latest-armv7l.AppImage || true
|
||||
cp app/dist/*.exe artifacts/dbgate-latest.exe || true
|
||||
cp app/dist/*windows*.zip artifacts/dbgate-windows-latest.zip || true
|
||||
cp app/dist/*.dmg artifacts/dbgate-latest.dmg || true
|
||||
cp app/dist/*win*.zip artifacts/dbgate-windows-latest.zip || true
|
||||
cp app/dist/*-mac.dmg artifacts/dbgate-latest.dmg || true
|
||||
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-latest-arm64.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
@@ -120,7 +126,7 @@ jobs:
|
||||
mv app/dist/latest-linux.yml artifacts/latest-linux.yml || true
|
||||
|
||||
- name: Copy latest-mac.yml
|
||||
if: matrix.os == 'macOS-10.14'
|
||||
if: matrix.os == 'macOS-10.15'
|
||||
run: |
|
||||
mv app/dist/latest-mac.yml artifacts/latest-mac.yml || true
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Docker image
|
||||
name: Docker image BETA
|
||||
|
||||
# on: [push]
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04]
|
||||
# os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
|
||||
@@ -21,7 +21,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04]
|
||||
# os: [macOS-10.14, windows-2016, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Integration tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:10.18-jessie
|
||||
|
||||
steps:
|
||||
- name: Context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd integration-tests
|
||||
yarn test:ci
|
||||
# yarn wait:ci
|
||||
- uses: tanmen/jest-reporter@v1
|
||||
if: always()
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
result-file: integration-tests/result.json
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: Pwd2020Db
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.18
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: Pwd2020Db
|
||||
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
env:
|
||||
ACCEPT_EULA: Y
|
||||
SA_PASSWORD: Pwd2020Db
|
||||
MSSQL_PID: Express
|
||||
|
||||
# cockroachdb:
|
||||
# image: cockroachdb/cockroach
|
||||
@@ -30,4 +30,5 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
app/src/nativeModulesContent.js
|
||||
packages/api/src/nativeModulesContent.js
|
||||
packages/api/src/packagedPluginsContent.js
|
||||
.VSCodeCounter
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"jestrunner.jestCommand": "node_modules/.bin/cross-env DEVMODE=1 LOCALTEST=1 node_modules/.bin/jest"
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
# ChangeLog
|
||||
|
||||
### 4.2.2
|
||||
- CHANGED: Further startup optimalization (approx. 2 times quicker start of electron app)
|
||||
|
||||
### 4.2.1
|
||||
- FIXED: Fixed+optimalized app startup (esp. on Windows)
|
||||
|
||||
|
||||
@@ -26,22 +26,31 @@ Supported databases:
|
||||
|
||||
## Features
|
||||
* Table data editing, with SQL change script preview
|
||||
* Light and dark theme
|
||||
* Master/detail views
|
||||
* Query designer
|
||||
* Form view for comfortable work with tables with many columns
|
||||
* JSON view on MongoDB collections
|
||||
* Explore tables, views, procedures, functions, MongoDB collections
|
||||
* SQL editor, execute SQL script, SQL code formatter, SQL code completion, SQL join wizard
|
||||
* SQL editor
|
||||
* execute SQL script
|
||||
* SQL code formatter
|
||||
* SQL code completion
|
||||
* Add SQL LEFT/INNER/RIGHT join utility
|
||||
* Mongo JavaScript editor, execute Mongo script (with NodeJs syntax)
|
||||
* 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
|
||||
* Free table editor - quick table data editing (cleanup data after import/before export, prototype tables etc.)
|
||||
* Archives - backup your data in JSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* Light and dark theme
|
||||
* Charts
|
||||
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Extensible plugin architecture
|
||||
|
||||
## How to contribute
|
||||
Any contributions are welcome. If you want to contribute without coding, consider following:
|
||||
* Create issue, if you find problem in app, or you have idea to new feature. If issue already exists, you could leave comment on it, to prioritise most wanted issues.
|
||||
* Tell your friends about DbGate or share on social networks - when more people will use DbGate, it will grow to be better
|
||||
|
||||
## Why is DbGate different
|
||||
There are many database managers now, so why DbGate?
|
||||
* Works everywhere - Windows, Linux, Mac, Web browser (+mobile web is planned), without compromises in features
|
||||
@@ -55,7 +64,6 @@ There are many database managers now, so why DbGate?
|
||||
* Backend - NodeJs, ExpressJs, socket.io, database connection drivers
|
||||
* JavaScript + TypeScript
|
||||
* App - electron
|
||||
* There is plan to incorporate SQLite to support work with local datasets
|
||||
* Platform independed - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
|
||||
|
||||
## Plugins
|
||||
@@ -104,15 +112,3 @@ yarn build:app:local
|
||||
yarn start:app:local
|
||||
```
|
||||
|
||||
## Packages
|
||||
Some dbgate packages can be used also without DbGate. You can find them on [NPM repository](https://www.npmjs.com/search?q=keywords:dbgate)
|
||||
|
||||
* [api](https://github.com/dbgate/dbgate/tree/master/packages/api) - backend, Javascript, ExpressJS [](https://www.npmjs.com/package/dbgate-api)
|
||||
* [datalib](https://github.com/dbgate/dbgate/tree/master/packages/datalib) - TypeScript library for utility classes [](https://www.npmjs.com/package/dbgate-datalib)
|
||||
* [app](https://github.com/dbgate/dbgate/tree/master/app) - application (JavaScript) structure, creating specific queries (JavaScript)
|
||||
* [filterparser](https://github.com/dbgate/dbgate/tree/master/packages/filterparser) - TypeScript library for parsing data filter expressions using parsimmon [](https://www.npmjs.com/package/dbgate-filterparser)
|
||||
* [sqltree](https://github.com/dbgate/dbgate/tree/master/packages/sqltree) - JSON representation of SQL query, functions converting to SQL (TypeScript) [](https://www.npmjs.com/package/dbgate-sqltree)
|
||||
* [types](https://github.com/dbgate/dbgate/tree/master/packages/types) - common TypeScript definitions [](https://www.npmjs.com/package/dbgate-types)
|
||||
* [web](https://github.com/dbgate/dbgate/tree/master/packages/web) - frontend in Svelte (JavaScript) [](https://www.npmjs.com/package/dbgate-web)
|
||||
* [tools](https://github.com/dbgate/dbgate/tree/master/packages/tools) - various tools [](https://www.npmjs.com/package/dbgate-tools)
|
||||
|
||||
|
||||
+22
-10
@@ -8,42 +8,54 @@
|
||||
"better-sqlite3-with-prebuilds": "^7.1.8",
|
||||
"electron-log": "^4.3.1",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.5"
|
||||
"electron-updater": "^4.3.5",
|
||||
"patch-package": "^6.4.7"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"build": {
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"appId": "org.dbgate",
|
||||
"mac": {
|
||||
"category": "database",
|
||||
"icon": "icon512.png",
|
||||
"artifactName": "dbgate-mac-${version}.${ext}",
|
||||
"publish": [
|
||||
"github"
|
||||
],
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"snap"
|
||||
"snap",
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"armv7l",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "icon.png",
|
||||
"artifactName": "dbgate-linux-${version}.${ext}",
|
||||
"category": "Development",
|
||||
"synopsis": "Database administration tool for MS SQL, MySQL and PostgreSQL",
|
||||
"synopsis": "Database manager for SQL Server, MySQL, PostgreSQL, MongoDB and SQLite",
|
||||
"publish": [
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"appImage": {
|
||||
"license": "./LICENSE",
|
||||
"category": "Development"
|
||||
},
|
||||
"snap": {
|
||||
"publish": [
|
||||
"github",
|
||||
@@ -58,7 +70,6 @@
|
||||
"nsis",
|
||||
"zip"
|
||||
],
|
||||
"artifactName": "dbgate-windows-${version}.${ext}",
|
||||
"icon": "icon.ico",
|
||||
"publish": [
|
||||
"github"
|
||||
@@ -77,8 +88,9 @@
|
||||
"start:local": "cross-env electron .",
|
||||
"dist": "electron-builder",
|
||||
"build": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn dist",
|
||||
"build:mac": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && node setMacPlatform x64 && yarn dist && node setMacPlatform arm64 && yarn dist",
|
||||
"build:local": "cd ../packages/api && yarn build && cd ../web && yarn build && cd ../../app && yarn predist",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postinstall": "electron-builder install-app-deps && patch-package",
|
||||
"predist": "copyfiles ../packages/api/dist/* packages && copyfiles \"../packages/web/public/*\" packages && copyfiles \"../packages/web/public/**/*\" packages && copyfiles --up 3 \"../plugins/dist/**/*\" packages/plugins"
|
||||
},
|
||||
"main": "src/electron.js",
|
||||
@@ -86,9 +98,9 @@
|
||||
"copyfiles": "^2.2.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"electron": "11.2.3",
|
||||
"electron-builder": "22.9.1"
|
||||
"electron-builder": "22.10.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"msnodesqlv8": "^2.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const text = fs.readFileSync('package.json', { encoding: 'utf-8' });
|
||||
const json = JSON.parse(text);
|
||||
|
||||
json.build.mac.target.arch = process.argv[2];
|
||||
|
||||
fs.writeFileSync('package.json', JSON.stringify(json, null, 2), { encoding: 'utf-8' });
|
||||
+683
-180
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function load() {
|
||||
const plugins = {};
|
||||
|
||||
for (const packageName of fs.readdirSync('plugins')) {
|
||||
if (!packageName.startsWith('dbgate-plugin-')) continue;
|
||||
const dir = path.join('plugins', packageName);
|
||||
const frontend = fs.readFileSync(path.join(dir, 'dist', 'frontend.js'), 'utf-8');
|
||||
const readme = fs.readFileSync(path.join(dir, 'README.md'), 'utf-8');
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
||||
plugins[packageName] = {
|
||||
manifest,
|
||||
frontend,
|
||||
readme,
|
||||
};
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
fs.writeFileSync('packages/api/src/packagedPluginsContent.js', `module.exports = () => (${JSON.stringify(load())});`);
|
||||
@@ -0,0 +1 @@
|
||||
dbtemp
|
||||
@@ -0,0 +1,89 @@
|
||||
const { testWrapper } = require('../tools');
|
||||
const engines = require('../engines');
|
||||
const _ = require('lodash');
|
||||
|
||||
const initSql = ['CREATE TABLE t1 (id int)', 'CREATE TABLE t2 (id int)'];
|
||||
|
||||
function flatSource() {
|
||||
return _.flatten(
|
||||
engines.map(engine => (engine.objects || []).map(object => [engine.label, object.type, object, engine]))
|
||||
);
|
||||
}
|
||||
|
||||
const obj1Match = expect.objectContaining({
|
||||
pureName: 'obj1',
|
||||
});
|
||||
const view1Match = expect.objectContaining({
|
||||
pureName: 'obj1',
|
||||
columns: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
columnName: 'id',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
describe('Object analyse', () => {
|
||||
test.each(flatSource())(
|
||||
'Full analysis - %s - %s',
|
||||
testWrapper(async (conn, driver, type, object, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
await driver.query(conn, object.create1);
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
expect(structure[type].length).toEqual(1);
|
||||
expect(structure[type][0]).toEqual(type.includes('views') ? view1Match : obj1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(flatSource())(
|
||||
'Incremental analysis - add - %s - %s',
|
||||
testWrapper(async (conn, driver, type, object, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
await driver.query(conn, object.create2);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
await driver.query(conn, object.create1);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2[type].length).toEqual(2);
|
||||
expect(structure2[type].find(x => x.pureName == 'obj1')).toEqual(type.includes('views') ? view1Match : obj1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(flatSource())(
|
||||
'Incremental analysis - drop - %s - %s',
|
||||
testWrapper(async (conn, driver, type, object, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
await driver.query(conn, object.create1);
|
||||
await driver.query(conn, object.create2);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
await driver.query(conn, object.drop2);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2[type].length).toEqual(1);
|
||||
expect(structure2[type][0]).toEqual(type.includes('views') ? view1Match : obj1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(flatSource())(
|
||||
'Create SQL - add - %s - %s',
|
||||
testWrapper(async (conn, driver, type, object, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
await driver.query(conn, object.create1);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
await driver.query(conn, object.drop1);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
expect(structure2[type].length).toEqual(0);
|
||||
|
||||
await driver.query(conn, structure1[type][0].createSql);
|
||||
|
||||
const structure3 = await driver.analyseIncremental(conn, structure2);
|
||||
|
||||
expect(structure3[type].length).toEqual(1);
|
||||
expect(structure3[type][0]).toEqual(type.includes('views') ? view1Match : obj1Match);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
const engines = require('../engines');
|
||||
const { testWrapper } = require('../tools');
|
||||
|
||||
const initSql = ['CREATE TABLE t1 (id int)', 'INSERT INTO t1 (id) VALUES (1)', 'INSERT INTO t1 (id) VALUES (2)'];
|
||||
|
||||
expect.extend({
|
||||
dataRow(row, expected) {
|
||||
for (const key in expected) {
|
||||
if (row[key] != expected[key]) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Different key: ${key}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
pass: true,
|
||||
message: () => '',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
class StreamHandler {
|
||||
constructor(resolve) {
|
||||
this.results = [];
|
||||
this.resolve = resolve;
|
||||
this.infoRows = [];
|
||||
}
|
||||
row(row) {
|
||||
this.results[this.results.length - 1].rows.push(row);
|
||||
}
|
||||
recordset(columns) {
|
||||
this.results.push({
|
||||
columns,
|
||||
rows: [],
|
||||
});
|
||||
}
|
||||
done(result) {
|
||||
this.resolve(this.results);
|
||||
}
|
||||
info(msg) {
|
||||
this.infoRows.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function executeStream(driver, conn, sql) {
|
||||
return new Promise(resolve => {
|
||||
const handler = new StreamHandler(resolve);
|
||||
driver.stream(conn, sql, handler);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Query', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Simple query - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
const res = await driver.query(conn, 'SELECT id FROM t1 ORDER BY id');
|
||||
expect(res.columns).toEqual([
|
||||
expect.objectContaining({
|
||||
columnName: 'id',
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(res.rows).toEqual([
|
||||
expect.dataRow({
|
||||
id: 1,
|
||||
}),
|
||||
expect.dataRow({
|
||||
id: 2,
|
||||
}),
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Simple stream query - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
const results = await executeStream(driver, conn, 'SELECT id FROM t1 ORDER BY id');
|
||||
expect(results.length).toEqual(1);
|
||||
const res = results[0];
|
||||
|
||||
expect(res.columns).toEqual([expect.objectContaining({ columnName: 'id' })]);
|
||||
expect(res.rows).toEqual([expect.dataRow({ id: 1 }), expect.dataRow({ id: 2 })]);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'More queries - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
const results = await executeStream(
|
||||
driver,
|
||||
conn,
|
||||
'SELECT id FROM t1 ORDER BY id; SELECT id FROM t1 ORDER BY id DESC'
|
||||
);
|
||||
expect(results.length).toEqual(2);
|
||||
|
||||
const res1 = results[0];
|
||||
expect(res1.columns).toEqual([expect.objectContaining({ columnName: 'id' })]);
|
||||
expect(res1.rows).toEqual([expect.dataRow({ id: 1 }), expect.dataRow({ id: 2 })]);
|
||||
|
||||
const res2 = results[1];
|
||||
expect(res2.columns).toEqual([expect.objectContaining({ columnName: 'id' })]);
|
||||
expect(res2.rows).toEqual([expect.dataRow({ id: 2 }), expect.dataRow({ id: 1 })]);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Script - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
const results = await executeStream(
|
||||
driver,
|
||||
conn,
|
||||
'CREATE TABLE t1 (id int); INSERT INTO t1 (id) VALUES (1); INSERT INTO t1 (id) VALUES (2); SELECT id FROM t1 ORDER BY id; '
|
||||
);
|
||||
expect(results.length).toEqual(1);
|
||||
|
||||
const res1 = results[0];
|
||||
expect(res1.columns).toEqual([expect.objectContaining({ columnName: 'id' })]);
|
||||
expect(res1.rows).toEqual([expect.dataRow({ id: 1 }), expect.dataRow({ id: 2 })]);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Save data query - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
for (const sql of initSql) await driver.query(conn, sql);
|
||||
|
||||
await driver.query(
|
||||
conn,
|
||||
'INSERT INTO t1 (id) VALUES (3);INSERT INTO t1 (id) VALUES (4);UPDATE t1 SET id=10 WHERE id=1;DELETE FROM t1 WHERE id=2;'
|
||||
);
|
||||
const res = await driver.query(conn, 'SELECT COUNT(*) AS cnt FROM t1');
|
||||
console.log(res);
|
||||
expect(res.rows[0].cnt == 3).toBeTruthy();
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
const engines = require('../engines');
|
||||
const { testWrapper } = require('../tools');
|
||||
|
||||
const t1Sql = 'CREATE TABLE t1 (id int not null primary key, val1 varchar(50) null)';
|
||||
const t2Sql = 'CREATE TABLE t2 (id int not null primary key, val2 varchar(50) null)';
|
||||
|
||||
const txMatch = (tname, vcolname, nextcol) =>
|
||||
expect.objectContaining({
|
||||
pureName: tname,
|
||||
columns: [
|
||||
expect.objectContaining({
|
||||
columnName: 'id',
|
||||
notNull: true,
|
||||
dataType: expect.stringContaining('int'),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
columnName: vcolname,
|
||||
notNull: false,
|
||||
dataType: expect.stringMatching(/.*char.*\(50\)/),
|
||||
}),
|
||||
...(nextcol
|
||||
? [
|
||||
expect.objectContaining({
|
||||
columnName: 'nextcol',
|
||||
notNull: false,
|
||||
dataType: expect.stringMatching(/.*char.*\(50\)/),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
primaryKey: expect.objectContaining({
|
||||
columns: [
|
||||
expect.objectContaining({
|
||||
columnName: 'id',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const t1Match = txMatch('t1', 'val1');
|
||||
const t2Match = txMatch('t2', 'val2');
|
||||
const t2NextColMatch = txMatch('t2', 'val2', true);
|
||||
|
||||
describe('Table analyse', () => {
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table structure - full analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t1Sql);
|
||||
|
||||
const structure = await driver.analyseFull(conn);
|
||||
|
||||
expect(structure.tables.length).toEqual(1);
|
||||
expect(structure.tables[0]).toEqual(t1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table add - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t2Sql);
|
||||
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
expect(structure1.tables.length).toEqual(1);
|
||||
expect(structure1.tables[0]).toEqual(t2Match);
|
||||
|
||||
await driver.query(conn, t1Sql);
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2.tables.length).toEqual(2);
|
||||
expect(structure2.tables.find(x => x.pureName == 't1')).toEqual(t1Match);
|
||||
expect(structure2.tables.find(x => x.pureName == 't2')).toEqual(t2Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table remove - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t1Sql);
|
||||
await driver.query(conn, t2Sql);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
expect(structure1.tables.length).toEqual(2);
|
||||
expect(structure1.tables.find(x => x.pureName == 't1')).toEqual(t1Match);
|
||||
expect(structure1.tables.find(x => x.pureName == 't2')).toEqual(t2Match);
|
||||
|
||||
await driver.query(conn, 'DROP TABLE t2');
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2.tables.length).toEqual(1);
|
||||
expect(structure2.tables[0]).toEqual(t1Match);
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.map(engine => [engine.label, engine]))(
|
||||
'Table change - incremental analysis - %s',
|
||||
testWrapper(async (conn, driver, engine) => {
|
||||
await driver.query(conn, t1Sql);
|
||||
await driver.query(conn, t2Sql);
|
||||
const structure1 = await driver.analyseFull(conn);
|
||||
|
||||
if (engine.dbSnapshotBySeconds) await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
|
||||
await driver.query(conn, 'ALTER TABLE t2 ADD nextcol varchar(50)');
|
||||
const structure2 = await driver.analyseIncremental(conn, structure1);
|
||||
|
||||
expect(structure2).toBeTruthy(); // if falsy, no modification is detected
|
||||
|
||||
expect(structure2.tables.length).toEqual(2);
|
||||
expect(structure2.tables.find(x => x.pureName == 't1')).toEqual(t1Match);
|
||||
expect(structure2.tables.find(x => x.pureName == 't2')).toEqual(t2NextColMatch);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Pwd2020Db
|
||||
ports:
|
||||
- 15000:5432
|
||||
|
||||
mysql:
|
||||
image: mysql
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
restart: always
|
||||
ports:
|
||||
- 15001:3306
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=Pwd2020Db
|
||||
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server
|
||||
restart: always
|
||||
ports:
|
||||
- 15002:1433
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- SA_PASSWORD=Pwd2020Db
|
||||
- MSSQL_PID=Express
|
||||
|
||||
cockroachdb:
|
||||
image: cockroachdb/cockroach
|
||||
ports:
|
||||
- 15003:26257
|
||||
command: start-single-node --insecure
|
||||
|
||||
# mongodb:
|
||||
# image: mongo:4.0.12
|
||||
# restart: always
|
||||
# volumes:
|
||||
# - mongo-data:/data/db
|
||||
# - mongo-config:/data/configdb
|
||||
# ports:
|
||||
# - 27017:27017
|
||||
|
||||
|
||||
# cockroachdb-init:
|
||||
# image: cockroachdb/cockroach
|
||||
# # build: cockroach
|
||||
# # entrypoint: /cockroach/init.sh
|
||||
# entrypoint: ./cockroach sql --insecure --host="cockroachdb" --execute="CREATE DATABASE IF NOT EXISTS test;"
|
||||
|
||||
# depends_on:
|
||||
# - cockroachdb
|
||||
# restart: on-failure
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
const views = {
|
||||
type: 'views',
|
||||
create1: 'CREATE VIEW obj1 AS SELECT id FROM t1',
|
||||
create2: 'CREATE VIEW obj2 AS SELECT id FROM t2',
|
||||
drop1: 'DROP VIEW obj1',
|
||||
drop2: 'DROP VIEW obj2',
|
||||
};
|
||||
const matviews = {
|
||||
type: 'matviews',
|
||||
create1: 'CREATE MATERIALIZED VIEW obj1 AS SELECT id FROM t1',
|
||||
create2: 'CREATE MATERIALIZED VIEW obj2 AS SELECT id FROM t2',
|
||||
drop1: 'DROP MATERIALIZED VIEW obj1',
|
||||
drop2: 'DROP MATERIALIZED VIEW obj2',
|
||||
};
|
||||
|
||||
const engines = [
|
||||
{
|
||||
label: 'MySQL',
|
||||
connection: {
|
||||
engine: 'mysql@dbgate-plugin-mysql',
|
||||
password: 'Pwd2020Db',
|
||||
user: 'root',
|
||||
server: 'mysql',
|
||||
port: 3306,
|
||||
},
|
||||
local: {
|
||||
server: 'localhost',
|
||||
port: 15001,
|
||||
},
|
||||
// skipOnCI: true,
|
||||
objects: [views],
|
||||
dbSnapshotBySeconds: true,
|
||||
},
|
||||
{
|
||||
label: 'PostgreSQL',
|
||||
connection: {
|
||||
engine: 'postgres@dbgate-plugin-postgres',
|
||||
password: 'Pwd2020Db',
|
||||
user: 'postgres',
|
||||
server: 'postgres',
|
||||
port: 5432,
|
||||
},
|
||||
local: {
|
||||
server: 'localhost',
|
||||
port: 15000,
|
||||
},
|
||||
objects: [
|
||||
views,
|
||||
matviews,
|
||||
{
|
||||
type: 'procedures',
|
||||
create1: 'CREATE PROCEDURE obj1() LANGUAGE SQL AS $$ select * from t1 $$',
|
||||
create2: 'CREATE PROCEDURE obj2() LANGUAGE SQL AS $$ select * from t2 $$',
|
||||
drop1: 'DROP PROCEDURE obj1',
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SQL Server',
|
||||
connection: {
|
||||
engine: 'mssql@dbgate-plugin-mssql',
|
||||
password: 'Pwd2020Db',
|
||||
user: 'sa',
|
||||
server: 'mssql',
|
||||
port: 1433,
|
||||
},
|
||||
local: {
|
||||
server: 'localhost',
|
||||
port: 15002,
|
||||
},
|
||||
objects: [
|
||||
views,
|
||||
{
|
||||
type: 'procedures',
|
||||
create1: 'CREATE PROCEDURE obj1 AS SELECT id FROM t1',
|
||||
create2: 'CREATE PROCEDURE obj2 AS SELECT id FROM t2',
|
||||
drop1: 'DROP PROCEDURE obj1',
|
||||
drop2: 'DROP PROCEDURE obj2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SQLite',
|
||||
generateDbFile: true,
|
||||
connection: {
|
||||
engine: 'sqlite@dbgate-plugin-sqlite',
|
||||
},
|
||||
objects: [views],
|
||||
},
|
||||
{
|
||||
label: 'CockroachDB',
|
||||
connection: {
|
||||
engine: 'cockroach@dbgate-plugin-postgres',
|
||||
user: 'root',
|
||||
server: 'cockroachdb',
|
||||
port: 26257,
|
||||
},
|
||||
local: {
|
||||
server: 'localhost',
|
||||
port: 15003,
|
||||
},
|
||||
skipOnCI: true,
|
||||
objects: [views, matviews],
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = process.env.CITEST ? engines.filter(x => !x.skipOnCI) : engines;
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "dbgate-integration-tests",
|
||||
"version": "4.1.1",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate.git"
|
||||
},
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"wait:local": "cross-env DEVMODE=1 LOCALTEST=1 node wait.js",
|
||||
"wait:ci": "cross-env DEVMODE=1 CITEST=1 node wait.js",
|
||||
|
||||
"test:local": "cross-env DEVMODE=1 LOCALTEST=1 jest",
|
||||
"test:ci": "cross-env DEVMODE=1 CITEST=1 jest --runInBand --json --outputFile=result.json --testLocationInResults",
|
||||
|
||||
"run:local": "docker-compose down && docker-compose up -d && yarn wait:local && yarn test:local"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^27.0.1"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
global.DBGATE_TOOLS = require('dbgate-tools');
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const crypto = require('crypto');
|
||||
|
||||
function randomDbName() {
|
||||
const generatedKey = crypto.randomBytes(6);
|
||||
const newKey = generatedKey.toString('hex');
|
||||
return `db${newKey}`;
|
||||
}
|
||||
|
||||
function extractConnection(engine) {
|
||||
const { connection } = engine;
|
||||
|
||||
if (process.env.LOCALTEST && engine.local) {
|
||||
return {
|
||||
...connection,
|
||||
...engine.local,
|
||||
};
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
async function connect(engine, database) {
|
||||
const connection = extractConnection(engine);
|
||||
const driver = requireEngineDriver(connection);
|
||||
|
||||
if (engine.generateDbFile) {
|
||||
const conn = await driver.connect({
|
||||
...connection,
|
||||
databaseFile: `dbtemp/${database}`,
|
||||
});
|
||||
return conn;
|
||||
} else {
|
||||
const conn = await driver.connect(connection);
|
||||
await driver.query(conn, `CREATE DATABASE ${database}`);
|
||||
await driver.close(conn);
|
||||
|
||||
const res = await driver.connect({
|
||||
...connection,
|
||||
database,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
const testWrapper = body => async (label, ...other) => {
|
||||
const engine = other[other.length - 1];
|
||||
const driver = requireEngineDriver(engine.connection);
|
||||
const conn = await connect(engine, randomDbName());
|
||||
try {
|
||||
await body(conn, driver, ...other);
|
||||
} finally {
|
||||
await driver.close(conn);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
randomDbName,
|
||||
connect,
|
||||
extractConnection,
|
||||
testWrapper,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
const requireEngineDriver = require('dbgate-api/src/utility/requireEngineDriver');
|
||||
const engines = require('./engines');
|
||||
const { extractConnection } = require('./tools');
|
||||
global.DBGATE_TOOLS = require('dbgate-tools');
|
||||
|
||||
async function connectEngine(engine) {
|
||||
const connection = extractConnection(engine);
|
||||
const driver = requireEngineDriver(connection);
|
||||
for (;;) {
|
||||
try {
|
||||
const conn = await driver.connect(connection);
|
||||
await driver.getVersion(conn);
|
||||
console.log(`Connect to ${engine.label} - OK`);
|
||||
await driver.close(conn);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.log(`Waiting for ${engine.label}, error: ${err.message}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2500));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
await Promise.all(engines.map(engine => connectEngine(engine)));
|
||||
}
|
||||
|
||||
run();
|
||||
+7
-3
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.4-beta.2",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
"plugins/*",
|
||||
"integration-tests"
|
||||
],
|
||||
"scripts": {
|
||||
"start:api": "yarn workspace dbgate-api start",
|
||||
@@ -21,6 +22,7 @@
|
||||
"build:tools": "yarn workspace dbgate-tools build",
|
||||
"build:lib": "yarn build:tools && yarn build:sqltree && yarn build:filterparser && yarn build:datalib",
|
||||
"build:app": "yarn plugins:copydist && cd app && yarn install && yarn build",
|
||||
"build:app:mac": "yarn plugins:copydist && cd app && yarn install && yarn build:mac",
|
||||
"build:api": "yarn workspace dbgate-api build",
|
||||
"build:web:docker": "yarn workspace dbgate-web build",
|
||||
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||
@@ -32,6 +34,8 @@
|
||||
"generatePadFile": "node generatePadFile",
|
||||
"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",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build",
|
||||
@@ -40,7 +44,7 @@
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
"ts:web": "yarn workspace dbgate-web ts",
|
||||
"ts": "yarn ts:api && yarn ts:web",
|
||||
"postinstall": "yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
|
||||
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^5.1.0",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"async-lock": "^1.2.4",
|
||||
"axios": "^0.19.0",
|
||||
"axios": "^0.21.1",
|
||||
"better-sqlite3-with-prebuilds": "^7.1.8",
|
||||
"body-parser": "^1.19.0",
|
||||
"bufferutil": "^4.0.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"http": "^0.0.0",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"line-reader": "^0.4.0",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"ncp": "^2.0.0",
|
||||
"nedb-promises": "^4.0.1",
|
||||
"node-cron": "^2.0.3",
|
||||
|
||||
@@ -9,10 +9,17 @@ const requirePlugin = require('../shell/requirePlugin');
|
||||
const downloadPackage = require('../utility/downloadPackage');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const _ = require('lodash');
|
||||
const packagedPluginsContent = require('../packagedPluginsContent');
|
||||
|
||||
module.exports = {
|
||||
script_meta: 'get',
|
||||
async script({ packageName }) {
|
||||
const packagedContent = packagedPluginsContent();
|
||||
|
||||
if (packagedContent && packagedContent[packageName]) {
|
||||
return packagedContent[packageName].frontend;
|
||||
}
|
||||
|
||||
const file1 = path.join(packagedPluginsDir(), packageName, 'dist', 'frontend.js');
|
||||
const file2 = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
||||
// @ts-ignore
|
||||
@@ -58,26 +65,37 @@ module.exports = {
|
||||
|
||||
installed_meta: 'get',
|
||||
async installed() {
|
||||
const files1 = await fs.readdir(packagedPluginsDir());
|
||||
const packagedContent = packagedPluginsContent();
|
||||
|
||||
const files1 = packagedContent ? _.keys(packagedContent) : await fs.readdir(packagedPluginsDir());
|
||||
const files2 = await fs.readdir(pluginsdir());
|
||||
|
||||
const res = [];
|
||||
for (const packageName of _.union(files1, files2)) {
|
||||
if (!/^dbgate-plugin-.*$/.test(packageName)) continue;
|
||||
try {
|
||||
const isPackaged = files1.includes(packageName);
|
||||
const manifest = await fs
|
||||
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
.then(x => JSON.parse(x));
|
||||
const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md');
|
||||
// @ts-ignore
|
||||
if (await fs.exists(readmeFile)) {
|
||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
if (packagedContent && packagedContent[packageName]) {
|
||||
const manifest = {
|
||||
...packagedContent[packageName].manifest,
|
||||
};
|
||||
manifest.isPackaged = true;
|
||||
manifest.readme = packagedContent[packageName].readme;
|
||||
res.push(manifest);
|
||||
} else {
|
||||
const isPackaged = files1.includes(packageName);
|
||||
const manifest = await fs
|
||||
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
.then(x => JSON.parse(x));
|
||||
const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md');
|
||||
// @ts-ignore
|
||||
if (await fs.exists(readmeFile)) {
|
||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||
}
|
||||
manifest.isPackaged = isPackaged;
|
||||
res.push(manifest);
|
||||
}
|
||||
manifest.isPackaged = isPackaged;
|
||||
res.push(manifest);
|
||||
} catch (err) {
|
||||
console.log(`Skipped plugin ${packageName}, error:`, err.message);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const shell = require('./shell');
|
||||
const processArgs = require('./utility/processArgs');
|
||||
const dbgateTools = require('dbgate-tools');
|
||||
|
||||
global['DBGATE_TOOLS'] = dbgateTools;
|
||||
|
||||
if (processArgs.startProcess) {
|
||||
const proc = require('./proc');
|
||||
|
||||
@@ -22,7 +22,7 @@ function requirePlugin(packageName, requiredPlugin = null) {
|
||||
// @ts-ignore
|
||||
module = __non_webpack_require__(modulePath);
|
||||
} catch (err) {
|
||||
console.log('Failed load webpacked module', err.message);
|
||||
// console.log('Failed load webpacked module', err.message);
|
||||
module = require(modulePath);
|
||||
}
|
||||
requiredPlugin = module.__esModule ? module.default : module;
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface GridConfig extends GridConfigColumns {
|
||||
formViewKey?: { [uniqueName: string]: string };
|
||||
formViewKeyRequested?: { [uniqueName: string]: string };
|
||||
formFilterColumns: string[];
|
||||
formColumnFilterText?: string;
|
||||
}
|
||||
|
||||
export interface GridCache {
|
||||
|
||||
@@ -288,8 +288,8 @@ export abstract class GridDisplay {
|
||||
return this.config.expandedColumns.includes(uniqueName);
|
||||
}
|
||||
|
||||
toggleExpandedColumn(uniqueName: string) {
|
||||
this.includeInColumnSet('expandedColumns', uniqueName, !this.isExpandedColumn(uniqueName));
|
||||
toggleExpandedColumn(uniqueName: string, value?: boolean) {
|
||||
this.includeInColumnSet('expandedColumns', uniqueName, value == null ? !this.isExpandedColumn(uniqueName) : value);
|
||||
}
|
||||
|
||||
getFilter(uniqueName: string) {
|
||||
|
||||
@@ -265,8 +265,8 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
||||
};
|
||||
}
|
||||
|
||||
toggleExpandedColumn(uniqueName: string) {
|
||||
this.gridDisplay.toggleExpandedColumn(uniqueName);
|
||||
toggleExpandedColumn(uniqueName: string, value?: boolean) {
|
||||
this.gridDisplay.toggleExpandedColumn(uniqueName, value);
|
||||
this.gridDisplay.reload();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@types/parsimmon": "^1.10.1",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.24.0",
|
||||
"parsimmon": "^1.13.0"
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,6 @@
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export class DatabaseAnalyser {
|
||||
}
|
||||
|
||||
const res = {};
|
||||
for (const field of ['tables', 'collections', 'views', 'functions', 'procedures', 'triggers']) {
|
||||
for (const field of ['tables', 'collections', 'views', 'matviews', 'functions', 'procedures', 'triggers']) {
|
||||
const removedIds = this.modifications
|
||||
.filter(x => x.action == 'remove' && x.objectTypeField == field)
|
||||
.map(x => x.objectId);
|
||||
@@ -77,6 +77,16 @@ export class DatabaseAnalyser {
|
||||
[...(this.structure[field] || []).filter(x => !removeAllIds.includes(x.objectId)), ...newArray],
|
||||
x => x.pureName
|
||||
);
|
||||
|
||||
// merge missing data from old structure
|
||||
for (const item of res[field]) {
|
||||
const original = (this.structure[field] || []).find(x => x.objectId == item.objectId);
|
||||
if (original) {
|
||||
for (const key in original) {
|
||||
if (!item[key]) item[key] = original[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -94,7 +104,10 @@ export class DatabaseAnalyser {
|
||||
if (typeField == objectTypeField) return [pureName];
|
||||
}
|
||||
if (this.modifications) {
|
||||
return this.modifications.filter(x => x.objectTypeField == objectTypeField).map(x => x.newName.pureName);
|
||||
return this.modifications
|
||||
.filter(x => x.objectTypeField == objectTypeField)
|
||||
.filter(x => x.newName)
|
||||
.map(x => x.newName.pureName);
|
||||
}
|
||||
return allPureNames;
|
||||
}
|
||||
@@ -146,6 +159,7 @@ export class DatabaseAnalyser {
|
||||
...this.getDeletedObjectsForField(snapshot, 'tables'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'collections'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'views'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'matviews'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'procedures'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'functions'),
|
||||
...this.getDeletedObjectsForField(snapshot, 'triggers'),
|
||||
@@ -166,6 +180,10 @@ export class DatabaseAnalyser {
|
||||
res.push({ objectTypeField: field, action: 'all' });
|
||||
continue;
|
||||
}
|
||||
if (items === undefined) {
|
||||
// skip - undefined meens, that field is not supported
|
||||
continue;
|
||||
}
|
||||
for (const item of items) {
|
||||
const { objectId, schemaName, pureName, contentHash } = item;
|
||||
const obj = this.structure[field].find(x => x.objectId == objectId);
|
||||
@@ -188,9 +206,9 @@ export class DatabaseAnalyser {
|
||||
};
|
||||
res.push(action);
|
||||
}
|
||||
|
||||
return [..._compact(res), ...this.getDeletedObjects(snapshot)];
|
||||
}
|
||||
|
||||
return [..._compact(res), ...this.getDeletedObjects(snapshot)];
|
||||
}
|
||||
|
||||
static createEmptyStructure(): DatabaseInfo {
|
||||
@@ -198,6 +216,7 @@ export class DatabaseAnalyser {
|
||||
tables: [],
|
||||
collections: [],
|
||||
views: [],
|
||||
matviews: [],
|
||||
functions: [],
|
||||
procedures: [],
|
||||
triggers: [],
|
||||
|
||||
@@ -293,6 +293,20 @@ export class SqlDumper {
|
||||
changeViewSchema(obj: ViewInfo, newSchema: string) {}
|
||||
renameView(obj: ViewInfo, newSchema: string) {}
|
||||
|
||||
createMatview(obj: ViewInfo) {
|
||||
this.putRaw(obj.createSql);
|
||||
this.endCommand();
|
||||
}
|
||||
dropMatview(obj: ViewInfo, { testIfExists = false }) {
|
||||
this.putCmd('^drop ^materialized ^view %f', obj);
|
||||
}
|
||||
alterMatview(obj: ViewInfo) {
|
||||
this.putRaw(obj.createSql.replace(/create\s+view/i, 'ALTER VIEW'));
|
||||
this.endCommand();
|
||||
}
|
||||
changeMatviewSchema(obj: ViewInfo, newSchema: string) {}
|
||||
renameMatview(obj: ViewInfo, newSchema: string) {}
|
||||
|
||||
createProcedure(obj: ProcedureInfo) {
|
||||
this.putRaw(obj.createSql);
|
||||
this.endCommand();
|
||||
|
||||
@@ -30,6 +30,10 @@ interface SqlGeneratorOptions {
|
||||
checkIfViewExists: boolean;
|
||||
createViews: boolean;
|
||||
|
||||
dropMatviews: boolean;
|
||||
checkIfMatviewExists: boolean;
|
||||
createMatviews: boolean;
|
||||
|
||||
dropProcedures: boolean;
|
||||
checkIfProcedureExists: boolean;
|
||||
createProcedures: boolean;
|
||||
@@ -52,6 +56,7 @@ interface SqlGeneratorObject {
|
||||
export class SqlGenerator {
|
||||
private tables: TableInfo[];
|
||||
private views: ViewInfo[];
|
||||
private matviews: ViewInfo[];
|
||||
private procedures: ProcedureInfo[];
|
||||
private functions: FunctionInfo[];
|
||||
private triggers: TriggerInfo[];
|
||||
@@ -70,6 +75,7 @@ export class SqlGenerator {
|
||||
this.dbinfo = extendDatabaseInfo(dbinfo);
|
||||
this.tables = this.extract('tables');
|
||||
this.views = this.extract('views');
|
||||
this.matviews = this.extract('matviews');
|
||||
this.procedures = this.extract('procedures');
|
||||
this.functions = this.extract('functions');
|
||||
this.triggers = this.extract('triggers');
|
||||
@@ -90,6 +96,8 @@ export class SqlGenerator {
|
||||
if (this.checkDumper()) return;
|
||||
this.dropObjects(this.views, 'View');
|
||||
if (this.checkDumper()) return;
|
||||
this.dropObjects(this.matviews, 'Matview');
|
||||
if (this.checkDumper()) return;
|
||||
this.dropObjects(this.triggers, 'Trigger');
|
||||
if (this.checkDumper()) return;
|
||||
|
||||
@@ -114,6 +122,8 @@ export class SqlGenerator {
|
||||
if (this.checkDumper()) return;
|
||||
this.createObjects(this.views, 'View');
|
||||
if (this.checkDumper()) return;
|
||||
this.createObjects(this.matviews, 'Matview');
|
||||
if (this.checkDumper()) return;
|
||||
this.createObjects(this.triggers, 'Trigger');
|
||||
if (this.checkDumper()) return;
|
||||
} finally {
|
||||
|
||||
@@ -64,6 +64,10 @@ function fillTableExtendedInfo(db: DatabaseInfo): DatabaseInfo {
|
||||
...obj,
|
||||
objectTypeField: 'views',
|
||||
})),
|
||||
matviews: (db.matviews || []).map(obj => ({
|
||||
...obj,
|
||||
objectTypeField: 'matviews',
|
||||
})),
|
||||
procedures: (db.procedures || []).map(obj => ({
|
||||
...obj,
|
||||
objectTypeField: 'procedures',
|
||||
|
||||
Vendored
+1
@@ -95,6 +95,7 @@ export interface DatabaseInfoObjects {
|
||||
tables: TableInfo[];
|
||||
collections: CollectionInfo[];
|
||||
views: ViewInfo[];
|
||||
matviews: ViewInfo[];
|
||||
procedures: ProcedureInfo[];
|
||||
functions: FunctionInfo[];
|
||||
triggers: TriggerInfo[];
|
||||
|
||||
Vendored
+2
-1
@@ -43,7 +43,8 @@ export interface EngineDriver {
|
||||
showConnectionTab?: (tab: 'ssl' | 'sshTunnel', values: any) => boolean;
|
||||
beforeConnectionSave?: (values: any) => any;
|
||||
databaseUrlPlaceholder?: string;
|
||||
connect({ server, port, user, password, database }): any;
|
||||
connect({ server, port, user, password, database }): Promise<any>;
|
||||
close(pool): Promise<any>;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
stream(pool: any, sql: string, options: StreamOptions);
|
||||
readQuery(pool: any, sql: string, structure?: TableInfo): Promise<stream.Readable>;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"file-selector": "^0.2.4",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"localforage": "^1.9.0",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"randomcolor": "^0.6.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rollup": "^2.3.4",
|
||||
|
||||
@@ -21,9 +21,25 @@
|
||||
<link rel='stylesheet' href='build/fonts/materialdesignicons.css'>
|
||||
|
||||
<script defer src='build/bundle.js'></script>
|
||||
|
||||
<style>
|
||||
#starting_dbgate_zero {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id='starting_dbgate_zero'>
|
||||
Loading DbGate App ...
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -33,6 +33,11 @@
|
||||
|
||||
onMount(loadApi);
|
||||
|
||||
onMount(() => {
|
||||
const removed = document.getElementById('starting_dbgate_zero');
|
||||
if (removed) removed.remove();
|
||||
});
|
||||
|
||||
$: {
|
||||
if (loadedApi && $loadingPluginStore?.loaded) {
|
||||
setAppLoaded();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
tables: 'img table',
|
||||
collections: 'img collection',
|
||||
views: 'img view',
|
||||
matviews: 'img view',
|
||||
procedures: 'img procedure',
|
||||
functions: 'img function',
|
||||
};
|
||||
@@ -14,6 +15,7 @@
|
||||
tables: 'TableDataTab',
|
||||
collections: 'CollectionDataTab',
|
||||
views: 'ViewDataTab',
|
||||
matviews: 'ViewDataTab',
|
||||
};
|
||||
|
||||
const menus = {
|
||||
@@ -146,6 +148,63 @@
|
||||
},
|
||||
},
|
||||
],
|
||||
matviews: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'ViewDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
},
|
||||
{
|
||||
label: 'Query designer',
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
label: 'Open in free table editor',
|
||||
isOpenFreeTable: true,
|
||||
},
|
||||
{
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE MATERIALIZED VIEW',
|
||||
scriptTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL: CREATE TABLE',
|
||||
scriptTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'SQL: SELECT',
|
||||
scriptTemplate: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: CREATE MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
createMatviews: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'SQL Generator: DROP MATERIALIZED VIEW',
|
||||
sqlGeneratorProps: {
|
||||
dropMatviews: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
procedures: [
|
||||
{
|
||||
label: 'SQL: CREATE PROCEDURE',
|
||||
|
||||
@@ -152,9 +152,12 @@
|
||||
function isDataCell(cell) {
|
||||
return cell[1] % 2 == 1;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getContext } from 'svelte';
|
||||
@@ -210,7 +213,10 @@
|
||||
|
||||
$: rowCount = Math.floor((wrapperHeight - 22) / (rowHeight + 2));
|
||||
|
||||
$: columnChunks = _.chunk(formDisplay.columns, rowCount) as any[][];
|
||||
$: columnChunks = _.chunk(
|
||||
formDisplay.columns.filter(x => filterName(formDisplay.config.formColumnFilterText, x.columnName)),
|
||||
rowCount
|
||||
) as any[][];
|
||||
|
||||
$: rowCountInfo = getRowCountInfo(rowCountBefore, allRowCount);
|
||||
|
||||
@@ -380,10 +386,38 @@
|
||||
(event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) ||
|
||||
event.keyCode == keycodes.dash)
|
||||
) {
|
||||
// @ts-ignore
|
||||
event.preventDefault();
|
||||
dispatchInsplaceEditor({ type: 'show', text: event.key, cell: currentCell });
|
||||
// console.log('event', event.nativeEvent);
|
||||
if (currentCell[1] % 2 == 0) {
|
||||
setConfig(x => ({
|
||||
...x,
|
||||
// @ts-ignore
|
||||
formColumnFilterText: (x.formColumnFilterText || '') + event.key,
|
||||
}));
|
||||
} else {
|
||||
// @ts-ignore
|
||||
event.preventDefault();
|
||||
dispatchInsplaceEditor({ type: 'show', text: event.key, cell: currentCell });
|
||||
}
|
||||
}
|
||||
|
||||
if (event.keyCode == keycodes.escape) {
|
||||
setConfig(x => ({
|
||||
...x,
|
||||
formColumnFilterText: '',
|
||||
}));
|
||||
}
|
||||
|
||||
if (event.keyCode == keycodes.numPadAdd) {
|
||||
const col = getCellColumn(currentCell);
|
||||
if (col.foreignKey) {
|
||||
formDisplay.toggleExpandedColumn(col.uniqueName, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.keyCode == keycodes.numPadSub) {
|
||||
const col = getCellColumn(currentCell);
|
||||
if (col.foreignKey) {
|
||||
formDisplay.toggleExpandedColumn(col.uniqueName, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.keyCode == keycodes.f2) {
|
||||
@@ -441,6 +475,7 @@
|
||||
function handleSetFormView(rowData, column) {
|
||||
openReferenceForm(rowData, column, conid, database);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="outer">
|
||||
@@ -594,4 +629,5 @@
|
||||
right: 40px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,19 +2,46 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import ManagerInnerContainer from '../elements/ManagerInnerContainer.svelte';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import FormViewFilterColumn from './FormViewFilterColumn.svelte';
|
||||
import PrimaryKeyFilterEditor from './PrimaryKeyFilterEditor.svelte';
|
||||
|
||||
export let managerSize;
|
||||
export let formDisplay;
|
||||
export let setConfig;
|
||||
|
||||
$: baseTable = formDisplay?.baseTable;
|
||||
$: formFilterColumns = formDisplay?.config?.formFilterColumns;
|
||||
$: filters = formDisplay?.config?.filters;
|
||||
|
||||
$: allFilterNames = _.union(_.keys(filters || {}), formFilterColumns || []);
|
||||
|
||||
</script>
|
||||
|
||||
<div class="m-1">
|
||||
<div>Column filter</div>
|
||||
<div class="flex">
|
||||
<input
|
||||
type="text"
|
||||
value={formDisplay?.config?.formColumnFilterText || ''}
|
||||
on:keydown={e => {
|
||||
if (e.keyCode == keycodes.escape) {
|
||||
setConfig(x => ({
|
||||
...x,
|
||||
formColumnFilterText: '',
|
||||
}));
|
||||
}
|
||||
}}
|
||||
on:input={e =>
|
||||
setConfig(x => ({
|
||||
...x,
|
||||
// @ts-ignore
|
||||
formColumnFilterText: e.target.value,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if baseTable?.primaryKey}
|
||||
<ManagerInnerContainer width={managerSize}>
|
||||
{#each baseTable.primaryKey.columns as col}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import { getObjectTypeFieldLabel } from '../utility/common';
|
||||
import { useDatabaseInfo, useDatabaseList } from '../utility/metadataLoaders';
|
||||
|
||||
export let conidName;
|
||||
@@ -15,19 +16,25 @@
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
$: dbinfo = useDatabaseInfo({ conid: $values[conidName], database: $values[databaseName] });
|
||||
|
||||
$: tablesOptions = _.compact([...($dbinfo?.tables || []), ...($dbinfo?.views || []), ...($dbinfo?.collections || [])])
|
||||
$: tablesOptions = _.compact([
|
||||
...($dbinfo?.tables || []),
|
||||
...($dbinfo?.views || []),
|
||||
...($dbinfo?.matviews || []),
|
||||
...($dbinfo?.collections || []),
|
||||
])
|
||||
.filter(x => !$values[schemaName] || x.schemaName == $values[schemaName])
|
||||
.map(x => ({
|
||||
value: x.pureName,
|
||||
label: x.pureName,
|
||||
}));
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<FormSelectField {...$$restProps} {name} options={tablesOptions} isMulti templateProps={{ noMargin: true }} />
|
||||
|
||||
<div>
|
||||
{#each ['tables', 'views', 'collections'] as field}
|
||||
{#each ['tables', 'views', 'matviews', 'collections'] as field}
|
||||
{#if $dbinfo && $dbinfo[field]?.length > 0}
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
@@ -49,4 +56,5 @@
|
||||
.wrapper {
|
||||
margin: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import { getObjectTypeFieldLabel } from '../utility/common';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -41,6 +42,7 @@
|
||||
createTables: true,
|
||||
createForeignKeys: true,
|
||||
createViews: true,
|
||||
createMatviews: true,
|
||||
createProcedures: true,
|
||||
createFunctions: true,
|
||||
createTriggers: true,
|
||||
@@ -72,7 +74,7 @@
|
||||
$: generatePreview($valuesStore, $checkedObjectsStore);
|
||||
|
||||
$: objectList = _.flatten(
|
||||
['tables', 'views', 'procedures', 'functions'].map(objectTypeField =>
|
||||
['tables', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
|
||||
_.sortBy(
|
||||
(($dbinfo || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
||||
['schemaName', 'pureName']
|
||||
@@ -125,6 +127,7 @@
|
||||
);
|
||||
closeCurrentModal();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<FormProviderCore values={valuesStore} template={FormFieldTemplateTiny}>
|
||||
@@ -152,7 +155,7 @@
|
||||
<AppObjectList
|
||||
list={objectList.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => _.startCase(data.objectTypeField)}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField)}
|
||||
isExpandable={data => data.objectTypeField == 'tables' || data.objectTypeField == 'views'}
|
||||
filter={objectsFilter}
|
||||
disableContextMenu
|
||||
@@ -211,8 +214,8 @@
|
||||
|
||||
<FormCheckboxField label="Truncate tables (delete all rows)" name="truncate" />
|
||||
|
||||
{#each ['View', 'Procedure', 'Function', 'Trigger'] as objtype}
|
||||
<div class="obj-heading">{objtype}s</div>
|
||||
{#each ['View', 'Matview', 'Procedure', 'Function', 'Trigger'] as objtype}
|
||||
<div class="obj-heading">{getObjectTypeFieldLabel(objtype.toLowerCase() + 's')}s</div>
|
||||
<FormCheckboxField label="Create" name={`create${objtype}s`} />
|
||||
<FormCheckboxField label="Drop" name={`drop${objtype}s`} />
|
||||
{#if values[`drop${objtype}s`]}
|
||||
@@ -254,4 +257,5 @@
|
||||
.dbname {
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
};
|
||||
|
||||
async function loadPlugins(pluginsDict, installedPlugins) {
|
||||
window['DBGATE_TOOLS'] = dbgateTools;
|
||||
|
||||
const newPlugins = {};
|
||||
for (const installed of installedPlugins || []) {
|
||||
if (!_.keys(pluginsDict).includes(installed.name)) {
|
||||
@@ -63,6 +65,7 @@
|
||||
import { useInstalledPlugins } from '../utility/metadataLoaders';
|
||||
import { buildFileFormats } from './fileformats';
|
||||
import { buildThemes } from './themes';
|
||||
import dbgateTools from 'dbgate-tools';
|
||||
|
||||
let pluginsDict = {};
|
||||
const installedPlugins = useInstalledPlugins();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { openedTabs } from '../stores';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class LoadingToken {
|
||||
isCanceled = false;
|
||||
@@ -26,3 +27,8 @@ export function setSelectedTabFunc(files, tabid) {
|
||||
export function setSelectedTab(tabid) {
|
||||
openedTabs.update(tabs => setSelectedTabFunc(tabs, tabid));
|
||||
}
|
||||
|
||||
export function getObjectTypeFieldLabel(objectTypeField) {
|
||||
if (objectTypeField == 'matviews') return 'Materialized Views';
|
||||
return _.startCase(objectTypeField);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ export default {
|
||||
numPad7: 103,
|
||||
numPad8: 104,
|
||||
numPad9: 105,
|
||||
numPadAdd: 107,
|
||||
numPadSub: 109,
|
||||
multiply: 106,
|
||||
add: 107,
|
||||
subtract: 109,
|
||||
|
||||
@@ -260,9 +260,18 @@ export function useDbCore(args, objectTypeField = undefined) {
|
||||
if (!dbStore) return null;
|
||||
return derived(dbStore, db => {
|
||||
if (!db) return null;
|
||||
return db[objectTypeField || args.objectTypeField].find(
|
||||
x => x.pureName == args.pureName && x.schemaName == args.schemaName
|
||||
);
|
||||
if (_.isArray(objectTypeField)) {
|
||||
for (const field of objectTypeField) {
|
||||
const res = db[field || args.objectTypeField].find(
|
||||
x => x.pureName == args.pureName && x.schemaName == args.schemaName
|
||||
);
|
||||
if (res) return res;
|
||||
}
|
||||
} else {
|
||||
return db[objectTypeField || args.objectTypeField].find(
|
||||
x => x.pureName == args.pureName && x.schemaName == args.schemaName
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -283,7 +292,7 @@ export function getViewInfo(args) {
|
||||
|
||||
/** @returns {import('dbgate-types').ViewInfo} */
|
||||
export function useViewInfo(args) {
|
||||
return useDbCore(args, 'views');
|
||||
return useDbCore(args, ['views', 'matviews']);
|
||||
}
|
||||
|
||||
/** @returns {import('dbgate-types').CollectionInfo} */
|
||||
@@ -344,7 +353,6 @@ export function useDatabaseServerVersion(args) {
|
||||
return useCore(databaseServerVersionLoader, args);
|
||||
}
|
||||
|
||||
|
||||
export function getServerStatus() {
|
||||
return getCore(serverStatusLoader, {});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import { getObjectTypeFieldLabel } from '../utility/common';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -21,10 +22,10 @@
|
||||
$: objects = useDatabaseInfo({ conid, database });
|
||||
$: status = useDatabaseStatus({ conid, database });
|
||||
|
||||
// $: console.log('objects', $objects);
|
||||
// $: console.log('OBJECTS', $objects);
|
||||
|
||||
$: objectList = _.flatten(
|
||||
['tables', 'collections', 'views', 'procedures', 'functions'].map(objectTypeField =>
|
||||
['tables', 'collections', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
|
||||
_.sortBy(
|
||||
(($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
||||
['schemaName', 'pureName']
|
||||
@@ -35,6 +36,7 @@
|
||||
const handleRefreshDatabase = () => {
|
||||
axiosInstance.post('database-connections/refresh', { conid, database });
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
{#if $status && $status.name == 'error'}
|
||||
@@ -62,9 +64,10 @@
|
||||
<AppObjectList
|
||||
list={objectList.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => _.startCase(data.objectTypeField)}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField)}
|
||||
subItemsComponent={SubColumnParamList}
|
||||
isExpandable={data => data.objectTypeField == 'tables' || data.objectTypeField == 'views'}
|
||||
isExpandable={data =>
|
||||
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
|
||||
expandIconFunc={chevronExpandIcon}
|
||||
{filter}
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"devDependencies": {
|
||||
"csv": "^5.3.2",
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"lodash": "^4.17.20",
|
||||
"lodash": "^4.17.21",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ var config = {
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lodash": "^4.17.20",
|
||||
"lodash": "^4.17.21",
|
||||
"xlsx": "^0.16.8",
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"webpack": "^4.42.0",
|
||||
|
||||
@@ -15,6 +15,12 @@ var config = {
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
const { SqlDumper } = global.DBGATE_TOOLS;
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
}
|
||||
class Dumper extends SqlDumper {}
|
||||
|
||||
module.exports = Dumper;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const { driverBase } = global.DBGATE_TOOLS;
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
const mongoIdRegex = /^[0-9a-f]{24}$/;
|
||||
|
||||
@@ -15,6 +15,12 @@ var config = {
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -63,6 +63,9 @@ const driver = {
|
||||
|
||||
return tediousConnect(conn);
|
||||
},
|
||||
async close(pool) {
|
||||
return pool.close();
|
||||
},
|
||||
async queryCore(pool, sql, options) {
|
||||
if (pool._connectionType == 'msnodesqlv8') {
|
||||
return nativeQueryCore(pool, sql, options);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
const { SqlDumper } = global.DBGATE_TOOLS;
|
||||
|
||||
class MsSqlDumper extends SqlDumper {
|
||||
autoIncrement() {
|
||||
@@ -67,12 +67,12 @@ class MsSqlDumper extends SqlDumper {
|
||||
|
||||
dropDefault(col) {
|
||||
if (col.defaultConstraint) {
|
||||
this.putCmd("^alter ^table %f ^drop ^constraint %i", col, col.defaultConstraint);
|
||||
this.putCmd('^alter ^table %f ^drop ^constraint %i', col, col.defaultConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
guessDefaultName(col) {
|
||||
return col.defaultConstraint || `DF${col.schemaName || 'dbo'}_${col.pureName}_col.columnName`
|
||||
return col.defaultConstraint || `DF${col.schemaName || 'dbo'}_${col.pureName}_col.columnName`;
|
||||
}
|
||||
|
||||
createDefault(col) {
|
||||
@@ -80,7 +80,7 @@ class MsSqlDumper extends SqlDumper {
|
||||
const defsql = col.defaultValue;
|
||||
if (!defsql) {
|
||||
const defname = this.guessDefaultName(col);
|
||||
this.putCmd("^alter ^table %f ^add ^constraint %i ^default %s for %i", col, defname, defsql, col.columnName);
|
||||
this.putCmd('^alter ^table %f ^add ^constraint %i ^default %s for %i', col, defname, defsql, col.columnName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,14 @@ class MsSqlDumper extends SqlDumper {
|
||||
}
|
||||
|
||||
renameConstraint(cnt, newname) {
|
||||
if (cnt.constraintType == 'index') this.putCmd("^execute sp_rename '%f.%i', '%s', 'INDEX'", cnt, cnt.constraintName, newname);
|
||||
else this.putCmd("^execute sp_rename '%f', '%s', 'OBJECT'", { schemaName: cnt.schemaName, pureName: cnt.constraintName }, newname);
|
||||
if (cnt.constraintType == 'index')
|
||||
this.putCmd("^execute sp_rename '%f.%i', '%s', 'INDEX'", cnt, cnt.constraintName, newname);
|
||||
else
|
||||
this.putCmd(
|
||||
"^execute sp_rename '%f', '%s', 'OBJECT'",
|
||||
{ schemaName: cnt.schemaName, pureName: cnt.constraintName },
|
||||
newname
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,5 +115,4 @@ MsSqlDumper.prototype.changeTriggerSchema = MsSqlDumper.prototype.changeObjectSc
|
||||
MsSqlDumper.prototype.renameTable = MsSqlDumper.prototype.renameObject;
|
||||
MsSqlDumper.prototype.changeTableSchema = MsSqlDumper.prototype.changeObjectSchema;
|
||||
|
||||
|
||||
module.exports = MsSqlDumper;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const { driverBase } = global.DBGATE_TOOLS;
|
||||
const MsSqlDumper = require('./MsSqlDumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
|
||||
@@ -15,6 +15,12 @@ var config = {
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
// },
|
||||
|
||||
@@ -112,6 +112,9 @@ const drivers = driverBases.map(driverBase => ({
|
||||
connection._database_name = database;
|
||||
return connection;
|
||||
},
|
||||
async close(pool) {
|
||||
return pool.close();
|
||||
},
|
||||
async query(connection, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
const { SqlDumper } = global.DBGATE_TOOLS;
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const { driverBase } = global.DBGATE_TOOLS;
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
|
||||
@@ -15,6 +15,12 @@ var config = {
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"devDependencies": {
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"dbgate-tools": "^4.1.1",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"pg": "^7.17.0",
|
||||
"sql-query-identifier": "^2.1.0",
|
||||
"webpack": "^4.42.0",
|
||||
|
||||
@@ -52,10 +52,16 @@ class Analyser extends DatabaseAnalyser {
|
||||
this.pool,
|
||||
this.createQuery(this.driver.dialect.stringAgg ? 'tableModifications' : 'tableList', ['tables'])
|
||||
);
|
||||
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables']));
|
||||
const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables', 'views']));
|
||||
const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables']));
|
||||
const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables']));
|
||||
const views = await this.driver.query(this.pool, this.createQuery('views', ['views']));
|
||||
const matviews = this.driver.dialect.materializedViews
|
||||
? await this.driver.query(this.pool, this.createQuery('matviews', ['matviews']))
|
||||
: null;
|
||||
const matviewColumns = this.driver.dialect.materializedViews
|
||||
? await this.driver.query(this.pool, this.createQuery('matviewColumns', ['matviews']))
|
||||
: null;
|
||||
const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions']));
|
||||
|
||||
return {
|
||||
@@ -103,20 +109,34 @@ class Analyser extends DatabaseAnalyser {
|
||||
pureName: view.pure_name,
|
||||
schemaName: view.schema_name,
|
||||
contentHash: view.hash_code,
|
||||
createSql: `CREATE VIEW "${view.schema_name}"."${view.pure_name}"\nAS\n${view.create_sql}`,
|
||||
columns: columns.rows
|
||||
.filter(col => col.pure_name == view.pure_name && col.schema_name == view.schema_name)
|
||||
.map(getColumnInfo),
|
||||
})),
|
||||
matviews: matviews
|
||||
? matviews.rows.map(matview => ({
|
||||
objectId: `matviews:${matview.schema_name}.${matview.pure_name}`,
|
||||
pureName: matview.pure_name,
|
||||
schemaName: matview.schema_name,
|
||||
contentHash: matview.hash_code,
|
||||
createSql: `CREATE MATERIALIZED VIEW "${matview.schema_name}"."${matview.pure_name}"\nAS\n${matview.definition}`,
|
||||
columns: matviewColumns.rows
|
||||
.filter(col => col.pure_name == matview.pure_name && col.schema_name == matview.schema_name)
|
||||
.map(getColumnInfo),
|
||||
}))
|
||||
: undefined,
|
||||
procedures: routines.rows
|
||||
.filter(x => x.objectType == 'PROCEDURE')
|
||||
.filter(x => x.object_type == 'PROCEDURE')
|
||||
.map(proc => ({
|
||||
objectId: `procedures:${proc.schema_name}.${proc.pure_name}`,
|
||||
pureName: proc.pure_name,
|
||||
schemaName: proc.schema_name,
|
||||
createSql: `CREATE PROCEDURE "${proc.schema_name}"."${proc.pure_name}"() LANGUAGE ${proc.language}\nAS\n$$\n${proc.definition}\n$$`,
|
||||
contentHash: proc.hash_code,
|
||||
})),
|
||||
functions: routines.rows
|
||||
.filter(x => x.objectType == 'FUNCTION')
|
||||
.filter(x => x.object_type == 'FUNCTION')
|
||||
.map(func => ({
|
||||
objectId: `functions:${func.schema_name}.${func.pure_name}`,
|
||||
pureName: func.pure_name,
|
||||
@@ -131,6 +151,9 @@ class Analyser extends DatabaseAnalyser {
|
||||
? await this.driver.query(this.pool, this.createQuery('tableModifications'))
|
||||
: null;
|
||||
const viewModificationsQueryData = await this.driver.query(this.pool, this.createQuery('viewModifications'));
|
||||
const matviewModificationsQueryData = this.driver.dialect.materializedViews
|
||||
? await this.driver.query(this.pool, this.createQuery('matviewModifications'))
|
||||
: null;
|
||||
const routineModificationsQueryData = await this.driver.query(this.pool, this.createQuery('routineModifications'));
|
||||
|
||||
return {
|
||||
@@ -148,8 +171,16 @@ class Analyser extends DatabaseAnalyser {
|
||||
schemaName: x.schema_name,
|
||||
contentHash: x.hash_code,
|
||||
})),
|
||||
matviews: matviewModificationsQueryData
|
||||
? matviewModificationsQueryData.rows.map(x => ({
|
||||
objectId: `matviews:${x.schema_name}.${x.pure_name}`,
|
||||
pureName: x.pure_name,
|
||||
schemaName: x.schema_name,
|
||||
contentHash: x.hash_code,
|
||||
}))
|
||||
: undefined,
|
||||
procedures: routineModificationsQueryData.rows
|
||||
.filter(x => x.objectType == 'PROCEDURE')
|
||||
.filter(x => x.object_type == 'PROCEDURE')
|
||||
.map(x => ({
|
||||
objectId: `procedures:${x.schema_name}.${x.pure_name}`,
|
||||
pureName: x.pure_name,
|
||||
@@ -157,7 +188,7 @@ class Analyser extends DatabaseAnalyser {
|
||||
contentHash: x.hash_code,
|
||||
})),
|
||||
functions: routineModificationsQueryData.rows
|
||||
.filter(x => x.objectType == 'FUNCTION')
|
||||
.filter(x => x.object_type == 'FUNCTION')
|
||||
.map(x => ({
|
||||
objectId: `functions:${x.schema_name}.${x.pure_name}`,
|
||||
pureName: x.pure_name,
|
||||
|
||||
@@ -113,6 +113,9 @@ const drivers = driverBases.map(driverBase => ({
|
||||
await client.connect();
|
||||
return client;
|
||||
},
|
||||
async close(pool) {
|
||||
return pool.end();
|
||||
},
|
||||
async query(client, sql) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
|
||||
@@ -14,6 +14,10 @@ where
|
||||
table_schema <> 'information_schema'
|
||||
and table_schema <> 'pg_catalog'
|
||||
and table_schema !~ '^pg_toast'
|
||||
and ('tables:' || table_schema || '.' || table_name) =OBJECT_ID_CONDITION
|
||||
and (
|
||||
('tables:' || table_schema || '.' || table_name) =OBJECT_ID_CONDITION
|
||||
or
|
||||
('views:' || table_schema || '.' || table_name) =OBJECT_ID_CONDITION
|
||||
)
|
||||
order by ordinal_position
|
||||
`;
|
||||
@@ -2,11 +2,14 @@ const columns = require('./columns');
|
||||
const tableModifications = require('./tableModifications');
|
||||
const tableList = require('./tableList');
|
||||
const viewModifications = require('./viewModifications');
|
||||
const matviewModifications = require('./matviewModifications');
|
||||
const primaryKeys = require('./primaryKeys');
|
||||
const foreignKeys = require('./foreignKeys');
|
||||
const views = require('./views');
|
||||
const matviews = require('./matviews');
|
||||
const routines = require('./routines');
|
||||
const routineModifications = require('./routineModifications');
|
||||
const matviewColumns = require('./matviewColumns');
|
||||
|
||||
module.exports = {
|
||||
columns,
|
||||
@@ -18,4 +21,7 @@ module.exports = {
|
||||
views,
|
||||
routines,
|
||||
routineModifications,
|
||||
matviews,
|
||||
matviewModifications,
|
||||
matviewColumns,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module.exports = `
|
||||
SELECT pg_namespace.nspname AS "schema_name"
|
||||
, pg_class.relname AS "pure_name"
|
||||
, pg_attribute.attname AS "column_name"
|
||||
, pg_catalog.format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS "data_type"
|
||||
FROM pg_catalog.pg_class
|
||||
INNER JOIN pg_catalog.pg_namespace
|
||||
ON pg_class.relnamespace = pg_namespace.oid
|
||||
INNER JOIN pg_catalog.pg_attribute
|
||||
ON pg_class.oid = pg_attribute.attrelid
|
||||
-- Keeps only materialized views, and non-db/catalog/index columns
|
||||
WHERE pg_class.relkind = 'm'
|
||||
AND pg_attribute.attnum >= 1
|
||||
AND ('matviews:' || pg_namespace.nspname || '.' || pg_class.relname) =OBJECT_ID_CONDITION
|
||||
|
||||
ORDER BY pg_attribute.attnum
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = `
|
||||
select
|
||||
matviewname as "pure_name",
|
||||
schemaname as "schema_name",
|
||||
md5(definition) as "hash_code"
|
||||
from
|
||||
pg_catalog.pg_matviews WHERE schemaname NOT LIKE 'pg_%'
|
||||
`;
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = `
|
||||
select
|
||||
matviewname as "pure_name",
|
||||
schemaname as "schema_name",
|
||||
definition as "definition",
|
||||
md5(definition) as "hash_code"
|
||||
from
|
||||
pg_catalog.pg_matviews WHERE schemaname NOT LIKE 'pg_%'
|
||||
and ('matviews:' || schemaname || '.' || matviewname) =OBJECT_ID_CONDITION
|
||||
`;
|
||||
@@ -1,10 +1,11 @@
|
||||
module.exports = `
|
||||
select
|
||||
routine_name as "pureName",
|
||||
routine_schema as "schemaName",
|
||||
routine_definition as "createSql",
|
||||
md5(routine_definition) as "hashCode",
|
||||
routine_type as "objectType"
|
||||
routine_name as "pure_name",
|
||||
routine_schema as "schema_name",
|
||||
routine_definition as "definition",
|
||||
md5(routine_definition) as "hash_code",
|
||||
routine_type as "object_type",
|
||||
external_language as "language"
|
||||
from
|
||||
information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog'
|
||||
and (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
const { SqlDumper } = global.DBGATE_TOOLS;
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
/** @param type {import('dbgate-types').TransformType} */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const { driverBase } = global.DBGATE_TOOLS;
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
@@ -29,6 +29,10 @@ const postgresDriver = {
|
||||
engine: 'postgres@dbgate-plugin-postgres',
|
||||
title: 'Postgre SQL',
|
||||
defaultPort: 5432,
|
||||
dialect: {
|
||||
...dialect,
|
||||
materializedViews: true,
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
@@ -37,6 +41,10 @@ const cockroachDriver = {
|
||||
engine: 'cockroach@dbgate-plugin-postgres',
|
||||
title: 'CockroachDB',
|
||||
defaultPort: 26257,
|
||||
dialect: {
|
||||
...dialect,
|
||||
materializedViews: true,
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
context: __dirname + '/src/frontend',
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
app: './index.js',
|
||||
},
|
||||
target: "web",
|
||||
target: 'web',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'frontend.js',
|
||||
libraryTarget: 'var',
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"name": "dbgate-plugin-sqlite",
|
||||
"main": "dist/backend.js",
|
||||
"version": "1.0.0",
|
||||
"version": "4.1.1",
|
||||
"homepage": "https://dbgate.org",
|
||||
"description": "SQLite connect plugin for DbGate",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbgate/dbgate"
|
||||
},
|
||||
"author": "Jan Prochazka",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"dbgate",
|
||||
@@ -9,7 +16,8 @@
|
||||
"sqlite"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"icon.svg"
|
||||
],
|
||||
"scripts": {
|
||||
"build:frontend": "webpack --config webpack-frontend.config",
|
||||
|
||||
@@ -6,27 +6,58 @@ class Analyser extends DatabaseAnalyser {
|
||||
super(pool, driver);
|
||||
}
|
||||
|
||||
async _getFastSnapshot() {
|
||||
const objects = await this.driver.query(this.pool, "select * from sqlite_master where type='table' or type='view'");
|
||||
|
||||
return {
|
||||
tables: objects.rows
|
||||
.filter((x) => x.type == 'table')
|
||||
.map((x) => ({
|
||||
pureName: x.name,
|
||||
objectId: x.name,
|
||||
contentHash: x.sql,
|
||||
})),
|
||||
views: objects.rows
|
||||
.filter((x) => x.type == 'view')
|
||||
.map((x) => ({
|
||||
pureName: x.name,
|
||||
objectId: x.name,
|
||||
contentHash: x.sql,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
const tables = await this.driver.query(this.pool, "select * from sqlite_master where type='table'");
|
||||
const objects = await this.driver.query(this.pool, "select * from sqlite_master where type='table' or type='view'");
|
||||
const tables = objects.rows.filter((x) => x.type == 'table');
|
||||
const views = objects.rows.filter((x) => x.type == 'view');
|
||||
// console.log('TABLES', tables);
|
||||
|
||||
const tableSqls = _.zipObject(
|
||||
tables.rows.map((x) => x.name),
|
||||
tables.rows.map((x) => x.sql)
|
||||
tables.map((x) => x.name),
|
||||
tables.map((x) => x.sql)
|
||||
);
|
||||
|
||||
const tableList = tables.rows.map((x) => ({
|
||||
const tableList = tables.map((x) => ({
|
||||
pureName: x.name,
|
||||
objectId: x.name,
|
||||
contentHash: x.sql,
|
||||
}));
|
||||
|
||||
const viewList = views.map((x) => ({
|
||||
pureName: x.name,
|
||||
objectId: x.name,
|
||||
contentHash: x.sql,
|
||||
createSql: x.sql,
|
||||
}));
|
||||
|
||||
for (const tableName of this.getRequestedObjectPureNames(
|
||||
'tables',
|
||||
tables.rows.map((x) => x.name)
|
||||
tables.map((x) => x.name)
|
||||
)) {
|
||||
const tableObj = tableList.find((x) => x.pureName == tableName);
|
||||
if (!tableObj) continue;
|
||||
|
||||
|
||||
const info = await this.driver.query(this.pool, `pragma table_info('${tableName}')`);
|
||||
tableObj.columns = info.rows.map((col) => ({
|
||||
columnName: col.name,
|
||||
@@ -68,14 +99,25 @@ class Analyser extends DatabaseAnalyser {
|
||||
// console.log(info);
|
||||
}
|
||||
|
||||
const res = this.mergeAnalyseResult(
|
||||
{
|
||||
tables: tableList,
|
||||
},
|
||||
(x) => x.pureName
|
||||
);
|
||||
// console.log('MERGED', res);
|
||||
return res;
|
||||
for (const viewName of this.getRequestedObjectPureNames(
|
||||
'views',
|
||||
views.map((x) => x.name)
|
||||
)) {
|
||||
const viewObj = viewList.find((x) => x.pureName == viewName);
|
||||
if (!viewObj) continue;
|
||||
|
||||
const info = await this.driver.query(this.pool, `pragma table_info('${viewName}')`);
|
||||
viewObj.columns = info.rows.map((col) => ({
|
||||
columnName: col.name,
|
||||
dataType: col.type,
|
||||
notNull: !!col.notnull,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
tables: tableList,
|
||||
views: viewList,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ const driver = {
|
||||
const pool = new Database(databaseFile);
|
||||
return pool;
|
||||
},
|
||||
async close(pool) {
|
||||
return pool.close();
|
||||
},
|
||||
// @ts-ignore
|
||||
async query(pool, sql) {
|
||||
const stmt = pool.prepare(sql);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const { SqlDumper } = require('dbgate-tools');
|
||||
const { SqlDumper } = global.DBGATE_TOOLS;
|
||||
|
||||
class Dumper extends SqlDumper {
|
||||
}
|
||||
class Dumper extends SqlDumper {}
|
||||
|
||||
module.exports = Dumper;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { driverBase } = require('dbgate-tools');
|
||||
const { driverBase } = global.DBGATE_TOOLS;
|
||||
const Dumper = require('./Dumper');
|
||||
|
||||
function getDatabaseFileLabel(databaseFile) {
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
var webpack = require("webpack");
|
||||
var path = require("path");
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
var config = {
|
||||
context: __dirname + "/src/frontend",
|
||||
context: __dirname + '/src/frontend',
|
||||
|
||||
entry: {
|
||||
app: "./index.js",
|
||||
app: './index.js',
|
||||
},
|
||||
target: "web",
|
||||
target: 'web',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "frontend.js",
|
||||
libraryTarget: "var",
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'frontend.js',
|
||||
libraryTarget: 'var',
|
||||
library: 'plugin',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'global.DBGATE_TOOLS': 'window.DBGATE_TOOLS',
|
||||
}),
|
||||
],
|
||||
|
||||
// uncomment for disable minimalization
|
||||
// optimization: {
|
||||
// minimize: false,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
const fs = require('fs');
|
||||
|
||||
fs.writeFileSync('packages/api/src/packagedPluginsContent.js', `module.exports = () => null;`);
|
||||
@@ -46,3 +46,4 @@ changePackageFile('plugins/dbgate-plugin-mssql', json.version);
|
||||
changePackageFile('plugins/dbgate-plugin-mysql', json.version);
|
||||
changePackageFile('plugins/dbgate-plugin-mongo', json.version);
|
||||
changePackageFile('plugins/dbgate-plugin-postgres', json.version);
|
||||
changePackageFile('plugins/dbgate-plugin-sqlite', json.version);
|
||||
|
||||
Reference in New Issue
Block a user