mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 18:55:59 +00:00
Merge branch 'dbgate:master' into oracle
This commit is contained in:
58
.github/workflows/build-docker-beta.yaml
vendored
58
.github/workflows/build-docker-beta.yaml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: Docker image BETA
|
|
||||||
|
|
||||||
# on: [push]
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
|
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+-docker.[0-9]+'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-18.04]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Context
|
|
||||||
env:
|
|
||||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
|
||||||
run: echo "$GITHUB_CONTEXT"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
- name: Use Node.js 14.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 14.x
|
|
||||||
- name: yarn install
|
|
||||||
run: |
|
|
||||||
# yarn --version
|
|
||||||
# yarn config set network-timeout 300000
|
|
||||||
yarn install
|
|
||||||
- name: setCurrentVersion
|
|
||||||
run: |
|
|
||||||
yarn setCurrentVersion
|
|
||||||
- name: Prepare docker image
|
|
||||||
run: |
|
|
||||||
yarn run prepare:docker
|
|
||||||
- name: Build docker image
|
|
||||||
run: |
|
|
||||||
docker build ./docker -t dbgate
|
|
||||||
- name: Push docker image
|
|
||||||
run: |
|
|
||||||
docker tag dbgate dbgate/dbgate:beta
|
|
||||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
docker push dbgate/dbgate:beta
|
|
||||||
- name: Build alpine docker image
|
|
||||||
run: |
|
|
||||||
docker build ./docker -t dbgate -f docker/Dockerfile-alpine
|
|
||||||
- name: Push alpine docker image
|
|
||||||
run: |
|
|
||||||
docker tag dbgate dbgate/dbgate:beta-alpine
|
|
||||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
docker push dbgate/dbgate:beta-alpine
|
|
||||||
82
.github/workflows/build-docker.yaml
vendored
82
.github/workflows/build-docker.yaml
vendored
@@ -1,17 +1,11 @@
|
|||||||
name: Docker image
|
name: Docker image
|
||||||
|
|
||||||
# on: [push]
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
# - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
- 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+'
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-docker.[0-9]+'
|
||||||
# on:
|
|
||||||
# push:
|
|
||||||
# branches:
|
|
||||||
# - production
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -30,12 +24,43 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
dbgate/dbgate
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=beta,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
|
||||||
|
|
||||||
|
type=match,pattern=\d+.\d+.\d+,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||||
|
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||||
|
|
||||||
|
- name: Docker alpine meta
|
||||||
|
id: alpmeta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
dbgate/dbgate
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=beta-alpine,enable=${{ contains(github.ref_name, '-docker.') || contains(github.ref_name, '-beta.') }}
|
||||||
|
|
||||||
|
type=match,pattern=\d+.\d+.\d+,suffix=-alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||||
|
type=raw,value=alpine,enable=${{ !contains(github.ref_name, '-docker.') && !contains(github.ref_name, '-beta.') }}
|
||||||
|
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
- name: yarn install
|
- name: yarn install
|
||||||
run: |
|
run: |
|
||||||
|
# yarn --version
|
||||||
|
# yarn config set network-timeout 300000
|
||||||
yarn install
|
yarn install
|
||||||
- name: setCurrentVersion
|
- name: setCurrentVersion
|
||||||
run: |
|
run: |
|
||||||
@@ -43,19 +68,28 @@ jobs:
|
|||||||
- name: Prepare docker image
|
- name: Prepare docker image
|
||||||
run: |
|
run: |
|
||||||
yarn run prepare:docker
|
yarn run prepare:docker
|
||||||
- name: Build docker image
|
|
||||||
run: |
|
- name: Set up Docker Buildx
|
||||||
docker build ./docker -t dbgate
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Push docker image
|
- name: Login to DockerHub
|
||||||
run: |
|
uses: docker/login-action@v2
|
||||||
docker tag dbgate dbgate/dbgate
|
with:
|
||||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
docker push dbgate/dbgate
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build alpine docker image
|
|
||||||
run: |
|
- name: Build and push
|
||||||
docker build ./docker -t dbgate -f docker/Dockerfile-alpine
|
uses: docker/build-push-action@v3
|
||||||
- name: Push alpine docker image
|
with:
|
||||||
run: |
|
push: true
|
||||||
docker tag dbgate dbgate/dbgate:alpine
|
context: ./docker
|
||||||
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
docker push dbgate/dbgate:alpine
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
|
||||||
|
- name: Build and push alpine
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
context: ./docker
|
||||||
|
file: ./docker/Dockerfile-alpine
|
||||||
|
tags: ${{ steps.alpmeta.outputs.tags }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
|||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -8,6 +8,32 @@ Builds:
|
|||||||
- linux - application for linux
|
- linux - application for linux
|
||||||
- win - application for Windows
|
- win - application for Windows
|
||||||
|
|
||||||
|
### 5.1.5
|
||||||
|
- ADDED: Support perspectives for MongoDB - MongoDB query designer
|
||||||
|
- ADDED: Show JSON content directly in the overview #395
|
||||||
|
- CHANGED: OSX Command H shortcut for hiding window #390
|
||||||
|
- ADDED: Uppercase Autocomplete Suggestions #389
|
||||||
|
- FIXED: Record view left/right arrows cause start record number to be treated as string #388
|
||||||
|
- FIXED: MongoDb ObjectId behaviour not consistent in nested objects #387
|
||||||
|
- FIXED: demo.dbgate.org - beta version crash 5.1.5-beta.3 #386
|
||||||
|
- ADDED: connect via socket - configurable via environment variables #358
|
||||||
|
|
||||||
|
### 5.1.4
|
||||||
|
- ADDED: Drop database commands #384
|
||||||
|
- ADDED: Customizable Redis key separator #379
|
||||||
|
- ADDED: ARM support for docker images
|
||||||
|
- ADDED: Version tags for docker images
|
||||||
|
- ADDED: Better SQL command splitting and highlighting
|
||||||
|
- ADDED: Unsaved marker for SQL files
|
||||||
|
|
||||||
|
### 5.1.3
|
||||||
|
- ADDED: Editing multiline cell values #378 #371 #359
|
||||||
|
- ADDED: Truncate table #333
|
||||||
|
- ADDED: Perspectives - show row count
|
||||||
|
- ADDED: Query - error markers in gutter area
|
||||||
|
- ADDED: Query - ability to execute query elements from gutter
|
||||||
|
- FIXED: Correct error line numbers returned from queries
|
||||||
|
|
||||||
### 5.1.2
|
### 5.1.2
|
||||||
- FIXED: MongoDb any export function does not work. #373
|
- FIXED: MongoDb any export function does not work. #373
|
||||||
- ADDED: Query Designer short order more flexibility #372
|
- ADDED: Query Designer short order more flexibility #372
|
||||||
|
|||||||
14
misc/play-dark-mode.svg
Normal file
14
misc/play-dark-mode.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 17.804 17.804" style="enable-background:new 0 0 17.804 17.804;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g id="c98_play">
|
||||||
|
<path fill='#ccc' d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
|
||||||
|
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
|
||||||
|
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Capa_1_78_">
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 733 B |
14
misc/play-light-mode.svg
Normal file
14
misc/play-light-mode.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 17.804 17.804" style="enable-background:new 0 0 17.804 17.804;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g id="c98_play">
|
||||||
|
<path fill='#444' d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
|
||||||
|
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
|
||||||
|
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Capa_1_78_">
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 733 B |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "5.1.3-beta.1",
|
"version": "5.1.5",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"compare-versions": "^3.6.0",
|
"compare-versions": "^3.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ function getPortalCollections() {
|
|||||||
databaseUrl: process.env[`URL_${id}`],
|
databaseUrl: process.env[`URL_${id}`],
|
||||||
useDatabaseUrl: !!process.env[`URL_${id}`],
|
useDatabaseUrl: !!process.env[`URL_${id}`],
|
||||||
databaseFile: process.env[`FILE_${id}`],
|
databaseFile: process.env[`FILE_${id}`],
|
||||||
|
socketPath: process.env[`SOCKET_PATH_${id}`],
|
||||||
|
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
|
||||||
defaultDatabase:
|
defaultDatabase:
|
||||||
process.env[`DATABASE_${id}`] ||
|
process.env[`DATABASE_${id}`] ||
|
||||||
(process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
|
(process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
|
||||||
|
|||||||
@@ -152,4 +152,13 @@ module.exports = {
|
|||||||
opened.subprocess.send({ msgtype: 'createDatabase', name });
|
opened.subprocess.send({ msgtype: 'createDatabase', name });
|
||||||
return { status: 'ok' };
|
return { status: 'ok' };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dropDatabase_meta: true,
|
||||||
|
async dropDatabase({ conid, name }, req) {
|
||||||
|
testConnectionPermission(conid, req);
|
||||||
|
const opened = await this.ensureOpened(conid);
|
||||||
|
if (opened.connection.isReadOnly) return false;
|
||||||
|
opened.subprocess.send({ msgtype: 'dropDatabase', name });
|
||||||
|
return { status: 'ok' };
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
|||||||
const res = await driver.query(systemConnection, sql);
|
const res = await driver.query(systemConnection, sql);
|
||||||
process.send({ msgtype: 'response', msgid, ...res });
|
process.send({ msgtype: 'response', msgid, ...res });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ const stableStringify = require('json-stable-stringify');
|
|||||||
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
const { extractBoolSettingsValue, extractIntSettingsValue } = require('dbgate-tools');
|
||||||
const childProcessChecker = require('../utility/childProcessChecker');
|
const childProcessChecker = require('../utility/childProcessChecker');
|
||||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||||
const { decryptConnection } = require('../utility/crypting');
|
|
||||||
const connectUtility = require('../utility/connectUtility');
|
const connectUtility = require('../utility/connectUtility');
|
||||||
const { handleProcessCommunication } = require('../utility/processComm');
|
const { handleProcessCommunication } = require('../utility/processComm');
|
||||||
|
|
||||||
@@ -81,14 +80,16 @@ function handlePing() {
|
|||||||
lastPing = new Date().getTime();
|
lastPing = new Date().getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreateDatabase({ name }) {
|
async function handleDatabaseOp(op, { name }) {
|
||||||
const driver = requireEngineDriver(storedConnection);
|
const driver = requireEngineDriver(storedConnection);
|
||||||
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
systemConnection = await connectUtility(driver, storedConnection, 'app');
|
||||||
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
if (driver[op]) {
|
||||||
if (driver.createDatabase) {
|
await driver[op](systemConnection, name);
|
||||||
await driver.createDatabase(systemConnection, name);
|
|
||||||
} else {
|
} else {
|
||||||
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
const dmp = driver.createDumper();
|
||||||
|
dmp[op](name);
|
||||||
|
console.log(`RUNNING SCRIPT: ${dmp.s}`);
|
||||||
|
await driver.query(systemConnection, dmp.s);
|
||||||
}
|
}
|
||||||
await handleRefresh();
|
await handleRefresh();
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,8 @@ async function handleCreateDatabase({ name }) {
|
|||||||
const messageHandlers = {
|
const messageHandlers = {
|
||||||
connect: handleConnect,
|
connect: handleConnect,
|
||||||
ping: handlePing,
|
ping: handlePing,
|
||||||
createDatabase: handleCreateDatabase,
|
createDatabase: props => handleDatabaseOp('createDatabase', props),
|
||||||
|
dropDatabase: props => handleDatabaseOp('dropDatabase', props),
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleMessage({ msgtype, ...other }) {
|
async function handleMessage({ msgtype, ...other }) {
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ class TableWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StreamHandler {
|
class StreamHandler {
|
||||||
constructor(resultIndexHolder, resolve) {
|
constructor(resultIndexHolder, resolve, startLine) {
|
||||||
this.recordset = this.recordset.bind(this);
|
this.recordset = this.recordset.bind(this);
|
||||||
|
this.startLine = startLine;
|
||||||
this.row = this.row.bind(this);
|
this.row = this.row.bind(this);
|
||||||
// this.error = this.error.bind(this);
|
// this.error = this.error.bind(this);
|
||||||
this.done = this.done.bind(this);
|
this.done = this.done.bind(this);
|
||||||
@@ -155,14 +156,21 @@ class StreamHandler {
|
|||||||
this.resolve();
|
this.resolve();
|
||||||
}
|
}
|
||||||
info(info) {
|
info(info) {
|
||||||
|
if (info && info.line != null) {
|
||||||
|
info = {
|
||||||
|
...info,
|
||||||
|
line: this.startLine + info.line,
|
||||||
|
};
|
||||||
|
}
|
||||||
process.send({ msgtype: 'info', info });
|
process.send({ msgtype: 'info', info });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStream(driver, resultIndexHolder, sql) {
|
function handleStream(driver, resultIndexHolder, sqlItem) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const handler = new StreamHandler(resultIndexHolder, resolve);
|
const start = sqlItem.trimStart || sqlItem.start;
|
||||||
driver.stream(systemConnection, sql, handler);
|
const handler = new StreamHandler(resultIndexHolder, resolve, start && start.line);
|
||||||
|
driver.stream(systemConnection, sqlItem.text, handler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +229,10 @@ async function handleExecuteQuery({ sql }) {
|
|||||||
const resultIndexHolder = {
|
const resultIndexHolder = {
|
||||||
value: 0,
|
value: 0,
|
||||||
};
|
};
|
||||||
for (const sqlItem of splitQuery(sql, driver.getQuerySplitterOptions('stream'))) {
|
for (const sqlItem of splitQuery(sql, {
|
||||||
|
...driver.getQuerySplitterOptions('stream'),
|
||||||
|
returnRichInfo: true,
|
||||||
|
})) {
|
||||||
await handleStream(driver, resultIndexHolder, sqlItem);
|
await handleStream(driver, resultIndexHolder, sqlItem);
|
||||||
// const handler = new StreamHandler(resultIndex);
|
// const handler = new StreamHandler(resultIndex);
|
||||||
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async function importDatabase({ connection = undefined, systemConnection = undef
|
|||||||
const downloadedFile = await download(inputFile);
|
const downloadedFile = await download(inputFile);
|
||||||
|
|
||||||
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
const fileStream = fs.createReadStream(downloadedFile, 'utf-8');
|
||||||
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions());
|
const splittedStream = splitQueryStream(fileStream, driver.getQuerySplitterOptions('script'));
|
||||||
const importStream = new ImportStream(pool, driver);
|
const importStream = new ImportStream(pool, driver);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
splittedStream.pipe(importStream);
|
splittedStream.pipe(importStream);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import _zip from 'lodash/zip';
|
|||||||
import _difference from 'lodash/difference';
|
import _difference from 'lodash/difference';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import stableStringify from 'json-stable-stringify';
|
import stableStringify from 'json-stable-stringify';
|
||||||
|
import { PerspectiveDataPattern } from './PerspectiveDataPattern';
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveCache');
|
const dbg = debug('dbgate:PerspectiveCache');
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ export class PerspectiveCacheTable {
|
|||||||
pureName: string;
|
pureName: string;
|
||||||
bindingColumns?: string[];
|
bindingColumns?: string[];
|
||||||
dataColumns: string[];
|
dataColumns: string[];
|
||||||
|
allColumns?: boolean;
|
||||||
loadedAll: boolean;
|
loadedAll: boolean;
|
||||||
loadedRows: any[] = [];
|
loadedRows: any[] = [];
|
||||||
bindingGroups: { [bindingKey: string]: PerspectiveBindingGroup } = {};
|
bindingGroups: { [bindingKey: string]: PerspectiveBindingGroup } = {};
|
||||||
@@ -86,14 +88,23 @@ export class PerspectiveCache {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
tables: { [tableKey: string]: PerspectiveCacheTable } = {};
|
tables: { [tableKey: string]: PerspectiveCacheTable } = {};
|
||||||
|
dataPatterns: PerspectiveDataPattern[] = [];
|
||||||
|
|
||||||
getTableCache(props: PerspectiveDataLoadProps) {
|
getTableCache(props: PerspectiveDataLoadProps) {
|
||||||
const tableKey = stableStringify(
|
const tableKey = stableStringify(
|
||||||
_pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy', 'condition'])
|
_pick(props, [
|
||||||
|
'schemaName',
|
||||||
|
'pureName',
|
||||||
|
'bindingColumns',
|
||||||
|
'databaseConfig',
|
||||||
|
'orderBy',
|
||||||
|
'sqlCondition',
|
||||||
|
'mongoCondition',
|
||||||
|
])
|
||||||
);
|
);
|
||||||
let res = this.tables[tableKey];
|
let res = this.tables[tableKey];
|
||||||
|
|
||||||
if (res && _difference(props.dataColumns, res.dataColumns).length > 0) {
|
if (res && _difference(props.dataColumns, res.dataColumns).length > 0 && !res.allColumns) {
|
||||||
dbg('Delete cache because incomplete columns', props.pureName, res.dataColumns);
|
dbg('Delete cache because incomplete columns', props.pureName, res.dataColumns);
|
||||||
|
|
||||||
// we have incomplete cache
|
// we have incomplete cache
|
||||||
@@ -113,5 +124,6 @@ export class PerspectiveCache {
|
|||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.tables = {};
|
this.tables = {};
|
||||||
|
this.dataPatterns = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ import uuidv1 from 'uuid/v1';
|
|||||||
// uncheckedColumns: string[];
|
// uncheckedColumns: string[];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
export type PerspectiveDatabaseEngineType = 'sqldb' | 'docdb';
|
||||||
|
|
||||||
|
export interface PerspectiveDatabaseConfig {
|
||||||
|
conid: string;
|
||||||
|
database: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PerspectiveCustomJoinConfig {
|
export interface PerspectiveCustomJoinConfig {
|
||||||
refNodeDesignerId: string;
|
refNodeDesignerId: string;
|
||||||
referenceDesignerId: string;
|
referenceDesignerId: string;
|
||||||
|
|||||||
@@ -1,19 +1,40 @@
|
|||||||
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
||||||
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
|
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import _zipObject from 'lodash/zipObject';
|
||||||
|
import _mapValues from 'lodash/mapValues';
|
||||||
|
import _isArray from 'lodash/isArray';
|
||||||
|
import { safeJsonParse } from 'dbgate-tools';
|
||||||
|
|
||||||
|
function normalizeLoadedRow(row) {
|
||||||
|
return _mapValues(row, v => safeJsonParse(v) || v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeResult(result) {
|
||||||
|
if (_isArray(result)) {
|
||||||
|
return result.map(normalizeLoadedRow);
|
||||||
|
}
|
||||||
|
if (result.errorMessage) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
errorMessage: 'Unspecified error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveDataLoader');
|
const dbg = debug('dbgate:PerspectiveDataLoader');
|
||||||
|
|
||||||
export class PerspectiveDataLoader {
|
export class PerspectiveDataLoader {
|
||||||
constructor(public apiCall) {}
|
constructor(public apiCall) {}
|
||||||
|
|
||||||
buildCondition(props: PerspectiveDataLoadProps): Condition {
|
buildSqlCondition(props: PerspectiveDataLoadProps): Condition {
|
||||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, sqlCondition } = props;
|
||||||
|
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
|
|
||||||
if (condition) {
|
if (sqlCondition) {
|
||||||
conditions.push(condition);
|
conditions.push(sqlCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingColumns?.length == 1) {
|
if (bindingColumns?.length == 1) {
|
||||||
@@ -38,8 +59,26 @@ export class PerspectiveDataLoader {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadGrouping(props: PerspectiveDataLoadProps) {
|
buildMongoCondition(props: PerspectiveDataLoadProps): {} {
|
||||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props;
|
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, mongoCondition } = props;
|
||||||
|
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (mongoCondition) {
|
||||||
|
conditions.push(mongoCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindingColumns?.length == 1) {
|
||||||
|
conditions.push({
|
||||||
|
[bindingColumns[0]]: { $in: bindingValues.map(x => x[0]) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions.length == 1 ? conditions[0] : conditions.length > 0 ? { $and: conditions } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadGroupingSqlDb(props: PerspectiveDataLoadProps) {
|
||||||
|
const { schemaName, pureName, bindingColumns } = props;
|
||||||
|
|
||||||
const bindingColumnExpressions = bindingColumns.map(
|
const bindingColumnExpressions = bindingColumns.map(
|
||||||
columnName =>
|
columnName =>
|
||||||
@@ -71,13 +110,13 @@ export class PerspectiveDataLoader {
|
|||||||
},
|
},
|
||||||
...bindingColumnExpressions,
|
...bindingColumnExpressions,
|
||||||
],
|
],
|
||||||
where: this.buildCondition(props),
|
where: this.buildSqlCondition(props),
|
||||||
};
|
};
|
||||||
|
|
||||||
select.groupBy = bindingColumnExpressions;
|
select.groupBy = bindingColumnExpressions;
|
||||||
|
|
||||||
if (dbg?.enabled) {
|
if (dbg?.enabled) {
|
||||||
dbg(`LOAD COUNTS, table=${props.pureName}, columns=${props.dataColumns?.join(',')}`);
|
dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.apiCall('database-connections/sql-select', {
|
const response = await this.apiCall('database-connections/sql-select', {
|
||||||
@@ -93,8 +132,63 @@ export class PerspectiveDataLoader {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(props: PerspectiveDataLoadProps) {
|
async loadGroupingDocDb(props: PerspectiveDataLoadProps) {
|
||||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
const { schemaName, pureName, bindingColumns } = props;
|
||||||
|
|
||||||
|
const aggregate = [
|
||||||
|
{ $match: this.buildMongoCondition(props) },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: _zipObject(
|
||||||
|
bindingColumns,
|
||||||
|
bindingColumns.map(col => '$' + col)
|
||||||
|
),
|
||||||
|
count: { $sum: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (dbg?.enabled) {
|
||||||
|
dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.apiCall('database-connections/collection-data', {
|
||||||
|
conid: props.databaseConfig.conid,
|
||||||
|
database: props.databaseConfig.database,
|
||||||
|
options: {
|
||||||
|
pureName,
|
||||||
|
aggregate,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.errorMessage) return response;
|
||||||
|
return response.rows.map(row => ({
|
||||||
|
...row._id,
|
||||||
|
_perspective_group_size_: parseInt(row.count),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadGrouping(props: PerspectiveDataLoadProps) {
|
||||||
|
const { engineType } = props;
|
||||||
|
switch (engineType) {
|
||||||
|
case 'sqldb':
|
||||||
|
return this.loadGroupingSqlDb(props);
|
||||||
|
case 'docdb':
|
||||||
|
return this.loadGroupingDocDb(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDataSqlDb(props: PerspectiveDataLoadProps) {
|
||||||
|
const {
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
bindingColumns,
|
||||||
|
bindingValues,
|
||||||
|
dataColumns,
|
||||||
|
orderBy,
|
||||||
|
sqlCondition: condition,
|
||||||
|
engineType,
|
||||||
|
} = props;
|
||||||
|
|
||||||
if (dataColumns?.length == 0) {
|
if (dataColumns?.length == 0) {
|
||||||
return [];
|
return [];
|
||||||
@@ -113,16 +207,19 @@ export class PerspectiveDataLoader {
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
selectAll: !dataColumns,
|
selectAll: !dataColumns,
|
||||||
orderBy: orderBy?.map(({ columnName, order }) => ({
|
orderBy:
|
||||||
exprType: 'column',
|
orderBy?.length > 0
|
||||||
columnName,
|
? orderBy?.map(({ columnName, order }) => ({
|
||||||
direction: order,
|
exprType: 'column',
|
||||||
source: {
|
columnName,
|
||||||
name: { schemaName, pureName },
|
direction: order,
|
||||||
},
|
source: {
|
||||||
})),
|
name: { schemaName, pureName },
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
range: props.range,
|
range: props.range,
|
||||||
where: this.buildCondition(props),
|
where: this.buildSqlCondition(props),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dbg?.enabled) {
|
if (dbg?.enabled) {
|
||||||
@@ -143,8 +240,76 @@ export class PerspectiveDataLoader {
|
|||||||
return response.rows;
|
return response.rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadRowCount(props: PerspectiveDataLoadProps) {
|
getDocDbLoadOptions(props: PerspectiveDataLoadProps, useSort: boolean) {
|
||||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
const { pureName } = props;
|
||||||
|
const res: any = {
|
||||||
|
pureName,
|
||||||
|
condition: this.buildMongoCondition(props),
|
||||||
|
skip: props.range?.offset,
|
||||||
|
limit: props.range?.limit,
|
||||||
|
};
|
||||||
|
if (useSort && props.orderBy?.length > 0) {
|
||||||
|
res.sort = _zipObject(
|
||||||
|
props.orderBy.map(col => col.columnName),
|
||||||
|
props.orderBy.map(col => (col.order == 'DESC' ? -1 : 1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDataDocDb(props: PerspectiveDataLoadProps) {
|
||||||
|
const {
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
bindingColumns,
|
||||||
|
bindingValues,
|
||||||
|
dataColumns,
|
||||||
|
orderBy,
|
||||||
|
sqlCondition: condition,
|
||||||
|
engineType,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (dbg?.enabled) {
|
||||||
|
dbg(
|
||||||
|
`LOAD DATA, collection=${props.pureName}, columns=${props.dataColumns?.join(',')}, range=${
|
||||||
|
props.range?.offset
|
||||||
|
},${props.range?.limit}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = this.getDocDbLoadOptions(props, true);
|
||||||
|
|
||||||
|
const response = await this.apiCall('database-connections/collection-data', {
|
||||||
|
conid: props.databaseConfig.conid,
|
||||||
|
database: props.databaseConfig.database,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.errorMessage) return response;
|
||||||
|
return response.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadData(props: PerspectiveDataLoadProps) {
|
||||||
|
const { engineType } = props;
|
||||||
|
switch (engineType) {
|
||||||
|
case 'sqldb':
|
||||||
|
return normalizeResult(await this.loadDataSqlDb(props));
|
||||||
|
case 'docdb':
|
||||||
|
return normalizeResult(await this.loadDataDocDb(props));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadRowCountSqlDb(props: PerspectiveDataLoadProps) {
|
||||||
|
const {
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
bindingColumns,
|
||||||
|
bindingValues,
|
||||||
|
dataColumns,
|
||||||
|
orderBy,
|
||||||
|
sqlCondition: condition,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const select: Select = {
|
const select: Select = {
|
||||||
commandType: 'select',
|
commandType: 'select',
|
||||||
@@ -158,7 +323,7 @@ export class PerspectiveDataLoader {
|
|||||||
alias: 'count',
|
alias: 'count',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
where: this.buildCondition(props),
|
where: this.buildSqlCondition(props),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this.apiCall('database-connections/sql-select', {
|
const response = await this.apiCall('database-connections/sql-select', {
|
||||||
@@ -170,4 +335,39 @@ export class PerspectiveDataLoader {
|
|||||||
if (response.errorMessage) return response;
|
if (response.errorMessage) return response;
|
||||||
return response.rows[0];
|
return response.rows[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadRowCountDocDb(props: PerspectiveDataLoadProps) {
|
||||||
|
const {
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
bindingColumns,
|
||||||
|
bindingValues,
|
||||||
|
dataColumns,
|
||||||
|
orderBy,
|
||||||
|
sqlCondition: condition,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...this.getDocDbLoadOptions(props, false),
|
||||||
|
countDocuments: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this.apiCall('database-connections/collection-data', {
|
||||||
|
conid: props.databaseConfig.conid,
|
||||||
|
database: props.databaseConfig.database,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadRowCount(props: PerspectiveDataLoadProps) {
|
||||||
|
const { engineType } = props;
|
||||||
|
switch (engineType) {
|
||||||
|
case 'sqldb':
|
||||||
|
return this.loadRowCountSqlDb(props);
|
||||||
|
case 'docdb':
|
||||||
|
return this.loadRowCountDocDb(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
packages/datalib/src/PerspectiveDataPattern.ts
Normal file
95
packages/datalib/src/PerspectiveDataPattern.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { PerspectiveDataLoader } from './PerspectiveDataLoader';
|
||||||
|
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
|
||||||
|
import _isString from 'lodash/isString';
|
||||||
|
import _isPlainObject from 'lodash/isPlainObject';
|
||||||
|
import _isNumber from 'lodash/isNumber';
|
||||||
|
import _isBoolean from 'lodash/isBoolean';
|
||||||
|
import _isArray from 'lodash/isArray';
|
||||||
|
import { safeJsonParse } from 'dbgate-tools';
|
||||||
|
|
||||||
|
export type PerspectiveDataPatternColumnType = 'null' | 'oid' | 'string' | 'number' | 'boolean' | 'json';
|
||||||
|
|
||||||
|
export interface PerspectiveDataPatternColumn {
|
||||||
|
name: string;
|
||||||
|
types: PerspectiveDataPatternColumnType[];
|
||||||
|
columns: PerspectiveDataPatternColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerspectiveDataPattern {
|
||||||
|
conid: string;
|
||||||
|
database: string;
|
||||||
|
schemaName?: string;
|
||||||
|
pureName: string;
|
||||||
|
columns: PerspectiveDataPatternColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PerspectiveDataPatternDict = { [designerId: string]: PerspectiveDataPattern };
|
||||||
|
|
||||||
|
function detectValueType(value): PerspectiveDataPatternColumnType {
|
||||||
|
if (_isString(value)) return 'string';
|
||||||
|
if (_isNumber(value)) return 'number';
|
||||||
|
if (_isBoolean(value)) return 'boolean';
|
||||||
|
if (value?.$oid) return 'oid';
|
||||||
|
if (_isPlainObject(value) || _isArray(value)) return 'json';
|
||||||
|
if (value == null) return 'null';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addObjectToColumns(columns: PerspectiveDataPatternColumn[], row) {
|
||||||
|
if (_isPlainObject(row)) {
|
||||||
|
for (const key of Object.keys(row)) {
|
||||||
|
let column: PerspectiveDataPatternColumn = columns.find(x => x.name == key);
|
||||||
|
if (!column) {
|
||||||
|
column = {
|
||||||
|
name: key,
|
||||||
|
types: [],
|
||||||
|
columns: [],
|
||||||
|
};
|
||||||
|
columns.push(column);
|
||||||
|
}
|
||||||
|
const value = row[key];
|
||||||
|
const type = detectValueType(value);
|
||||||
|
if (!column.types.includes(type)) {
|
||||||
|
column.types.push(type);
|
||||||
|
}
|
||||||
|
if (_isPlainObject(value)) {
|
||||||
|
addObjectToColumns(column.columns, value);
|
||||||
|
}
|
||||||
|
if (_isArray(value)) {
|
||||||
|
for (const item of value) {
|
||||||
|
addObjectToColumns(column.columns, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_isString(value)) {
|
||||||
|
const json = safeJsonParse(value);
|
||||||
|
if (json && (_isPlainObject(json) || _isArray(json))) {
|
||||||
|
if (!column.types.includes('json')) {
|
||||||
|
column.types.push('json');
|
||||||
|
}
|
||||||
|
if (_isPlainObject(json)) {
|
||||||
|
addObjectToColumns(column.columns, json);
|
||||||
|
}
|
||||||
|
if (_isArray(json)) {
|
||||||
|
for (const item of json) {
|
||||||
|
addObjectToColumns(column.columns, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyseDataPattern(
|
||||||
|
patternBase: Omit<PerspectiveDataPattern, 'columns'>,
|
||||||
|
rows: any[]
|
||||||
|
): PerspectiveDataPattern {
|
||||||
|
const res: PerspectiveDataPattern = {
|
||||||
|
...patternBase,
|
||||||
|
columns: [],
|
||||||
|
};
|
||||||
|
// console.log('ROWS', rows);
|
||||||
|
for (const row of rows) {
|
||||||
|
addObjectToColumns(res.columns, row);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { Condition } from 'dbgate-sqltree';
|
import { Condition } from 'dbgate-sqltree';
|
||||||
import { RangeDefinition } from 'dbgate-types';
|
import { RangeDefinition } from 'dbgate-types';
|
||||||
import { format } from 'path';
|
|
||||||
import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache';
|
import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache';
|
||||||
import { PerspectiveDataLoader } from './PerspectiveDataLoader';
|
import { PerspectiveDataLoader } from './PerspectiveDataLoader';
|
||||||
|
import { PerspectiveDataPatternDict } from './PerspectiveDataPattern';
|
||||||
|
import { PerspectiveDatabaseConfig, PerspectiveDatabaseEngineType } from './PerspectiveConfig';
|
||||||
|
|
||||||
export const PERSPECTIVE_PAGE_SIZE = 100;
|
export const PERSPECTIVE_PAGE_SIZE = 100;
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveDataProvider');
|
const dbg = debug('dbgate:PerspectiveDataProvider');
|
||||||
|
|
||||||
export interface PerspectiveDatabaseConfig {
|
|
||||||
conid: string;
|
|
||||||
database: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerspectiveDataLoadProps {
|
export interface PerspectiveDataLoadProps {
|
||||||
databaseConfig: PerspectiveDatabaseConfig;
|
databaseConfig: PerspectiveDatabaseConfig;
|
||||||
schemaName: string;
|
schemaName?: string;
|
||||||
pureName: string;
|
pureName: string;
|
||||||
dataColumns: string[];
|
dataColumns?: string[];
|
||||||
|
allColumns?: boolean;
|
||||||
orderBy: {
|
orderBy: {
|
||||||
columnName: string;
|
columnName: string;
|
||||||
order: 'ASC' | 'DESC';
|
order: 'ASC' | 'DESC';
|
||||||
@@ -27,11 +24,17 @@ export interface PerspectiveDataLoadProps {
|
|||||||
bindingValues?: any[][];
|
bindingValues?: any[][];
|
||||||
range?: RangeDefinition;
|
range?: RangeDefinition;
|
||||||
topCount?: number;
|
topCount?: number;
|
||||||
condition?: Condition;
|
sqlCondition?: Condition;
|
||||||
|
mongoCondition?: any;
|
||||||
|
engineType: PerspectiveDatabaseEngineType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PerspectiveDataProvider {
|
export class PerspectiveDataProvider {
|
||||||
constructor(public cache: PerspectiveCache, public loader: PerspectiveDataLoader) {}
|
constructor(
|
||||||
|
public cache: PerspectiveCache,
|
||||||
|
public loader: PerspectiveDataLoader,
|
||||||
|
public dataPatterns: PerspectiveDataPatternDict
|
||||||
|
) {}
|
||||||
async loadData(props: PerspectiveDataLoadProps): Promise<{ rows: any[]; incomplete: boolean }> {
|
async loadData(props: PerspectiveDataLoadProps): Promise<{ rows: any[]; incomplete: boolean }> {
|
||||||
dbg('load data', props);
|
dbg('load data', props);
|
||||||
// console.log('LOAD DATA', props);
|
// console.log('LOAD DATA', props);
|
||||||
@@ -182,6 +185,7 @@ export class PerspectiveDataProvider {
|
|||||||
|
|
||||||
// load missing rows
|
// load missing rows
|
||||||
tableCache.dataColumns = props.dataColumns;
|
tableCache.dataColumns = props.dataColumns;
|
||||||
|
tableCache.allColumns = props.allColumns;
|
||||||
|
|
||||||
const nextRows = await this.loader.loadData({
|
const nextRows = await this.loader.loadData({
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import _max from 'lodash/max';
|
|||||||
import _range from 'lodash/max';
|
import _range from 'lodash/max';
|
||||||
import _fill from 'lodash/fill';
|
import _fill from 'lodash/fill';
|
||||||
import _findIndex from 'lodash/findIndex';
|
import _findIndex from 'lodash/findIndex';
|
||||||
|
import _isPlainObject from 'lodash/isPlainObject';
|
||||||
|
import _isArray from 'lodash/isArray';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveDisplay');
|
const dbg = debug('dbgate:PerspectiveDisplay');
|
||||||
@@ -126,14 +128,14 @@ export class PerspectiveDisplay {
|
|||||||
|
|
||||||
fillColumns(children: PerspectiveTreeNode[], parentNodes: PerspectiveTreeNode[]) {
|
fillColumns(children: PerspectiveTreeNode[], parentNodes: PerspectiveTreeNode[]) {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child.isCheckedColumn || child.isCheckedNode) {
|
if (child.generatesHiearchicGridColumn || child.generatesDataGridColumn) {
|
||||||
this.processColumn(child, parentNodes);
|
this.processColumn(child, parentNodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processColumn(node: PerspectiveTreeNode, parentNodes: PerspectiveTreeNode[]) {
|
processColumn(node: PerspectiveTreeNode, parentNodes: PerspectiveTreeNode[]) {
|
||||||
if (node.isCheckedColumn) {
|
if (node.generatesDataGridColumn) {
|
||||||
const column = new PerspectiveDisplayColumn(this);
|
const column = new PerspectiveDisplayColumn(this);
|
||||||
column.title = node.columnTitle;
|
column.title = node.columnTitle;
|
||||||
column.dataField = node.dataField;
|
column.dataField = node.dataField;
|
||||||
@@ -145,7 +147,7 @@ export class PerspectiveDisplay {
|
|||||||
this.columns.push(column);
|
this.columns.push(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.isExpandable && node.isCheckedNode) {
|
if (node.generatesHiearchicGridColumn) {
|
||||||
const countBefore = this.columns.length;
|
const countBefore = this.columns.length;
|
||||||
this.fillColumns(node.childNodes, [...parentNodes, node]);
|
this.fillColumns(node.childNodes, [...parentNodes, node]);
|
||||||
|
|
||||||
@@ -167,13 +169,30 @@ export class PerspectiveDisplay {
|
|||||||
// return _findIndex(this.columns, x => x.dataNode.designerId == node.designerId);
|
// return _findIndex(this.columns, x => x.dataNode.designerId == node.designerId);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
extractArray(value) {
|
||||||
|
if (_isArray(value)) return value;
|
||||||
|
if (_isPlainObject(value)) return [value];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
collectRows(sourceRows: any[], nodes: PerspectiveTreeNode[]): CollectedPerspectiveDisplayRow[] {
|
collectRows(sourceRows: any[], nodes: PerspectiveTreeNode[]): CollectedPerspectiveDisplayRow[] {
|
||||||
// console.log('********** COLLECT ROWS', sourceRows);
|
// console.log('********** COLLECT ROWS', sourceRows);
|
||||||
const columnNodes = nodes.filter(x => x.isCheckedColumn);
|
const columnNodes = nodes.filter(x => x.generatesDataGridColumn);
|
||||||
const treeNodes = nodes.filter(x => x.isCheckedNode);
|
const treeNodes = nodes.filter(x => x.generatesHiearchicGridColumn);
|
||||||
|
|
||||||
// console.log('columnNodes', columnNodes);
|
// console.log(
|
||||||
// console.log('treeNodes', treeNodes);
|
// 'columnNodes',
|
||||||
|
// columnNodes.map(x => x.title)
|
||||||
|
// );
|
||||||
|
// console.log(
|
||||||
|
// 'treeNodes',
|
||||||
|
// treeNodes.map(x => x.title)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// 'nodes',
|
||||||
|
// nodes.map(x => x.title)
|
||||||
|
// );
|
||||||
|
|
||||||
const columnIndexes = columnNodes.map(node => this.findColumnIndexFromNode(node));
|
const columnIndexes = columnNodes.map(node => this.findColumnIndexFromNode(node));
|
||||||
|
|
||||||
@@ -181,13 +200,14 @@ export class PerspectiveDisplay {
|
|||||||
for (const sourceRow of sourceRows) {
|
for (const sourceRow of sourceRows) {
|
||||||
// console.log('PROCESS SOURCE', sourceRow);
|
// console.log('PROCESS SOURCE', sourceRow);
|
||||||
// row.startIndex = startIndex;
|
// row.startIndex = startIndex;
|
||||||
const rowData = columnNodes.map(node => sourceRow[node.codeName]);
|
const rowData = columnNodes.map(node => sourceRow[node.columnName]);
|
||||||
const subRowCollections = [];
|
const subRowCollections = [];
|
||||||
|
|
||||||
for (const node of treeNodes) {
|
for (const node of treeNodes) {
|
||||||
|
// console.log('sourceRow[node.fieldName]', node.fieldName, sourceRow[node.fieldName]);
|
||||||
if (sourceRow[node.fieldName]) {
|
if (sourceRow[node.fieldName]) {
|
||||||
const subrows = {
|
const subrows = {
|
||||||
rows: this.collectRows(sourceRow[node.fieldName], node.childNodes),
|
rows: this.collectRows(this.extractArray(sourceRow[node.fieldName]), node.childNodes),
|
||||||
};
|
};
|
||||||
subRowCollections.push(subrows);
|
subRowCollections.push(subrows);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
CollectionInfo,
|
||||||
ColumnInfo,
|
ColumnInfo,
|
||||||
DatabaseInfo,
|
DatabaseInfo,
|
||||||
ForeignKeyInfo,
|
ForeignKeyInfo,
|
||||||
@@ -7,13 +8,15 @@ import {
|
|||||||
TableInfo,
|
TableInfo,
|
||||||
ViewInfo,
|
ViewInfo,
|
||||||
} from 'dbgate-types';
|
} from 'dbgate-types';
|
||||||
import { equalFullName } from 'dbgate-tools';
|
import { equalFullName, isCollectionInfo, isTableInfo, isViewInfo } from 'dbgate-tools';
|
||||||
import {
|
import {
|
||||||
ChangePerspectiveConfigFunc,
|
ChangePerspectiveConfigFunc,
|
||||||
createPerspectiveNodeConfig,
|
createPerspectiveNodeConfig,
|
||||||
MultipleDatabaseInfo,
|
MultipleDatabaseInfo,
|
||||||
PerspectiveConfig,
|
PerspectiveConfig,
|
||||||
PerspectiveCustomJoinConfig,
|
PerspectiveCustomJoinConfig,
|
||||||
|
PerspectiveDatabaseConfig,
|
||||||
|
PerspectiveDatabaseEngineType,
|
||||||
PerspectiveFilterColumnInfo,
|
PerspectiveFilterColumnInfo,
|
||||||
PerspectiveNodeConfig,
|
PerspectiveNodeConfig,
|
||||||
PerspectiveReferenceConfig,
|
PerspectiveReferenceConfig,
|
||||||
@@ -27,17 +30,14 @@ import _uniqBy from 'lodash/uniqBy';
|
|||||||
import _sortBy from 'lodash/sortBy';
|
import _sortBy from 'lodash/sortBy';
|
||||||
import _cloneDeepWith from 'lodash/cloneDeepWith';
|
import _cloneDeepWith from 'lodash/cloneDeepWith';
|
||||||
import _findIndex from 'lodash/findIndex';
|
import _findIndex from 'lodash/findIndex';
|
||||||
import {
|
import { PerspectiveDataLoadProps, PerspectiveDataProvider } from './PerspectiveDataProvider';
|
||||||
PerspectiveDatabaseConfig,
|
|
||||||
PerspectiveDataLoadProps,
|
|
||||||
PerspectiveDataProvider,
|
|
||||||
} from './PerspectiveDataProvider';
|
|
||||||
import stableStringify from 'json-stable-stringify';
|
import stableStringify from 'json-stable-stringify';
|
||||||
import { getFilterType, parseFilter } from 'dbgate-filterparser';
|
import { getFilterType, parseFilter } from 'dbgate-filterparser';
|
||||||
import { FilterType } from 'dbgate-filterparser/lib/types';
|
import { FilterType } from 'dbgate-filterparser/lib/types';
|
||||||
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
||||||
// import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns';
|
// import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
|
import { PerspectiveDataPatternColumn } from './PerspectiveDataPattern';
|
||||||
|
|
||||||
export interface PerspectiveDataLoadPropsWithNode {
|
export interface PerspectiveDataLoadPropsWithNode {
|
||||||
props: PerspectiveDataLoadProps;
|
props: PerspectiveDataLoadProps;
|
||||||
@@ -79,7 +79,7 @@ export abstract class PerspectiveTreeNode {
|
|||||||
this.parentNodeConfig = parentNode?.nodeConfig;
|
this.parentNodeConfig = parentNode?.nodeConfig;
|
||||||
}
|
}
|
||||||
readonly nodeConfig: PerspectiveNodeConfig;
|
readonly nodeConfig: PerspectiveNodeConfig;
|
||||||
readonly parentNodeConfig: PerspectiveNodeConfig;
|
parentNodeConfig: PerspectiveNodeConfig;
|
||||||
// defaultChecked: boolean;
|
// defaultChecked: boolean;
|
||||||
abstract get title();
|
abstract get title();
|
||||||
abstract get codeName();
|
abstract get codeName();
|
||||||
@@ -108,6 +108,18 @@ export abstract class PerspectiveTreeNode {
|
|||||||
get namedObject(): NamedObjectInfo {
|
get namedObject(): NamedObjectInfo {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
get tableNodeOrParent(): PerspectiveTableNode {
|
||||||
|
if (this instanceof PerspectiveTableNode) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (this.parentNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.parentNode.tableNodeOrParent;
|
||||||
|
}
|
||||||
|
get engineType(): PerspectiveDatabaseEngineType {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
abstract getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps;
|
abstract getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps;
|
||||||
get isRoot() {
|
get isRoot() {
|
||||||
return this.parentNode == null;
|
return this.parentNode == null;
|
||||||
@@ -119,6 +131,12 @@ export abstract class PerspectiveTreeNode {
|
|||||||
get isSortable() {
|
get isSortable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
get generatesHiearchicGridColumn() {
|
||||||
|
return this.isExpandable && this.isCheckedNode;
|
||||||
|
}
|
||||||
|
get generatesDataGridColumn() {
|
||||||
|
return this.isCheckedColumn;
|
||||||
|
}
|
||||||
matchChildRow(parentRow: any, childRow: any): boolean {
|
matchChildRow(parentRow: any, childRow: any): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -271,14 +289,15 @@ export abstract class PerspectiveTreeNode {
|
|||||||
[field]: isIncluded ? [...(n[field] || []), this.codeName] : (n[field] || []).filter(x => x != this.codeName),
|
[field]: isIncluded ? [...(n[field] || []), this.codeName] : (n[field] || []).filter(x => x != this.codeName),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [cfgChanged, nodeCfg] = this.parentNode?.ensureNodeConfig(cfg);
|
const [cfgChanged, nodeCfg] = this.parentNode?.tableNodeOrParent?.ensureNodeConfig(cfg);
|
||||||
|
|
||||||
return {
|
const res = {
|
||||||
...cfgChanged,
|
...cfgChanged,
|
||||||
nodes: cfgChanged.nodes.map(n =>
|
nodes: cfgChanged.nodes.map(n =>
|
||||||
n.designerId == (this.parentNode?.designerId || nodeCfg?.designerId) ? changedFields(n) : n
|
n.designerId == (this.parentNode?.tableNodeOrParent?.designerId || nodeCfg?.designerId) ? changedFields(n) : n
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,11 +311,15 @@ export abstract class PerspectiveTreeNode {
|
|||||||
...this.childNodes.map(x => x.childDataColumn),
|
...this.childNodes.map(x => x.childDataColumn),
|
||||||
..._flatten(this.childNodes.filter(x => x.isExpandable && x.isChecked).map(x => x.getChildMatchColumns())),
|
..._flatten(this.childNodes.filter(x => x.isExpandable && x.isChecked).map(x => x.getChildMatchColumns())),
|
||||||
...this.getParentMatchColumns(),
|
...this.getParentMatchColumns(),
|
||||||
|
...this.childNodes
|
||||||
|
.filter(x => x instanceof PerspectivePatternColumnNode)
|
||||||
|
.filter(x => this.nodeConfig?.checkedColumns?.find(y => y.startsWith(x.codeName + '::')))
|
||||||
|
.map(x => x.columnName),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildrenCondition(source = null): Condition {
|
getChildrenSqlCondition(source = null): Condition {
|
||||||
const conditions = _compact([
|
const conditions = _compact([
|
||||||
...this.childNodes.map(x => x.parseFilterCondition(source)),
|
...this.childNodes.map(x => x.parseFilterCondition(source)),
|
||||||
...this.buildParentFilterConditions(),
|
...this.buildParentFilterConditions(),
|
||||||
@@ -313,7 +336,18 @@ export abstract class PerspectiveTreeNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderBy(table: TableInfo | ViewInfo): PerspectiveDataLoadProps['orderBy'] {
|
getChildrenMongoCondition(source = null): {} {
|
||||||
|
const conditions = _compact([...this.childNodes.map(x => x.parseFilterCondition(source))]);
|
||||||
|
if (conditions.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (conditions.length == 1) {
|
||||||
|
return conditions[0];
|
||||||
|
}
|
||||||
|
return { $and: conditions };
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderBy(table: TableInfo | ViewInfo | CollectionInfo): PerspectiveDataLoadProps['orderBy'] {
|
||||||
const res = _compact(
|
const res = _compact(
|
||||||
this.childNodes.map(node => {
|
this.childNodes.map(node => {
|
||||||
const sort = this.nodeConfig?.sort?.find(x => x.columnName == node.columnName);
|
const sort = this.nodeConfig?.sort?.find(x => x.columnName == node.columnName);
|
||||||
@@ -325,11 +359,15 @@ export abstract class PerspectiveTreeNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return res.length > 0
|
if (res.length > 0) return res;
|
||||||
? res
|
const pkColumns = (table as TableInfo)?.primaryKey?.columns.map(x => ({
|
||||||
: (table as TableInfo)?.primaryKey?.columns.map(x => ({ columnName: x.columnName, order: 'ASC' })) || [
|
columnName: x.columnName,
|
||||||
{ columnName: table?.columns[0].columnName, order: 'ASC' },
|
order: 'ASC' as 'ASC',
|
||||||
];
|
}));
|
||||||
|
if (pkColumns) return pkColumns;
|
||||||
|
const columns = (table as TableInfo | ViewInfo)?.columns;
|
||||||
|
if (columns) return [{ columnName: columns[0].columnName, order: 'ASC' }];
|
||||||
|
return [{ columnName: '_id', order: 'ASC' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
getBaseTables() {
|
getBaseTables() {
|
||||||
@@ -390,7 +428,9 @@ export abstract class PerspectiveTreeNode {
|
|||||||
return (
|
return (
|
||||||
(this.parentNode?.isRoot || this.parentNode?.supportsParentFilter) &&
|
(this.parentNode?.isRoot || this.parentNode?.supportsParentFilter) &&
|
||||||
this.parentNode?.databaseConfig?.conid == this.databaseConfig?.conid &&
|
this.parentNode?.databaseConfig?.conid == this.databaseConfig?.conid &&
|
||||||
this.parentNode?.databaseConfig?.database == this.databaseConfig?.database
|
this.parentNode?.databaseConfig?.database == this.databaseConfig?.database &&
|
||||||
|
this.engineType == 'sqldb' &&
|
||||||
|
this.parentNode?.engineType == 'sqldb'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +478,7 @@ export abstract class PerspectiveTreeNode {
|
|||||||
conditionType: 'and',
|
conditionType: 'and',
|
||||||
conditions: _compact([
|
conditions: _compact([
|
||||||
...lastNode.getParentJoinCondition(lastAlias, this.namedObject.pureName),
|
...lastNode.getParentJoinCondition(lastAlias, this.namedObject.pureName),
|
||||||
leafNode.getChildrenCondition({ alias: 'pert_0' }),
|
leafNode.getChildrenSqlCondition({ alias: 'pert_0' }),
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -496,6 +536,10 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get engineType() {
|
||||||
|
return this.parentNode.engineType;
|
||||||
|
}
|
||||||
|
|
||||||
matchChildRow(parentRow: any, childRow: any): boolean {
|
matchChildRow(parentRow: any, childRow: any): boolean {
|
||||||
if (!this.foreignKey) return false;
|
if (!this.foreignKey) return false;
|
||||||
return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName];
|
return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName];
|
||||||
@@ -552,7 +596,8 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
|||||||
dataColumns: this.getDataLoadColumns(),
|
dataColumns: this.getDataLoadColumns(),
|
||||||
databaseConfig: this.databaseConfig,
|
databaseConfig: this.databaseConfig,
|
||||||
orderBy: this.getOrderBy(this.refTable),
|
orderBy: this.getOrderBy(this.refTable),
|
||||||
condition: this.getChildrenCondition(),
|
sqlCondition: this.getChildrenSqlCondition(),
|
||||||
|
engineType: 'sqldb',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,6 +618,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
|||||||
|
|
||||||
get fieldName() {
|
get fieldName() {
|
||||||
return this.codeName + 'Ref';
|
return this.codeName + 'Ref';
|
||||||
|
// return this.codeName ;
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
@@ -670,6 +716,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
|||||||
pureName: this.foreignKey.refTableName,
|
pureName: this.foreignKey.refTableName,
|
||||||
conid: this.databaseConfig.conid,
|
conid: this.databaseConfig.conid,
|
||||||
database: this.databaseConfig.database,
|
database: this.databaseConfig.database,
|
||||||
|
objectTypeField: this.table.objectTypeField,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -693,9 +740,216 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PerspectivePatternColumnNode extends PerspectiveTreeNode {
|
||||||
|
foreignKey: ForeignKeyInfo;
|
||||||
|
refTable: TableInfo;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public table: TableInfo | ViewInfo | CollectionInfo,
|
||||||
|
public column: PerspectiveDataPatternColumn,
|
||||||
|
public tableColumn: ColumnInfo,
|
||||||
|
dbs: MultipleDatabaseInfo,
|
||||||
|
config: PerspectiveConfig,
|
||||||
|
setConfig: ChangePerspectiveConfigFunc,
|
||||||
|
dataProvider: PerspectiveDataProvider,
|
||||||
|
databaseConfig: PerspectiveDatabaseConfig,
|
||||||
|
parentNode: PerspectiveTreeNode,
|
||||||
|
designerId: string
|
||||||
|
) {
|
||||||
|
super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId);
|
||||||
|
this.parentNodeConfig = this.tableNodeOrParent?.nodeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isChildColumn() {
|
||||||
|
return this.parentNode instanceof PerspectivePatternColumnNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchChildRow(parentRow: any, childRow: any): boolean {
|
||||||
|
// if (!this.foreignKey) return false;
|
||||||
|
// return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getChildMatchColumns() {
|
||||||
|
// if (!this.foreignKey) return [];
|
||||||
|
// return [this.foreignKey.columns[0].columnName];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getParentMatchColumns() {
|
||||||
|
// if (!this.foreignKey) return [];
|
||||||
|
// return [this.foreignKey.columns[0].refColumnName];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getParentJoinCondition(alias: string, parentAlias: string): Condition[] {
|
||||||
|
// if (!this.foreignKey) return [];
|
||||||
|
// return this.foreignKey.columns.map(column => {
|
||||||
|
// const res: Condition = {
|
||||||
|
// conditionType: 'binary',
|
||||||
|
// operator: '=',
|
||||||
|
// left: {
|
||||||
|
// exprType: 'column',
|
||||||
|
// columnName: column.columnName,
|
||||||
|
// source: { alias: parentAlias },
|
||||||
|
// },
|
||||||
|
// right: {
|
||||||
|
// exprType: 'column',
|
||||||
|
// columnName: column.refColumnName,
|
||||||
|
// source: { alias },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// return res;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] {
|
||||||
|
// return this.foreignKey?.columns?.map(col => ({
|
||||||
|
// source: col.columnName,
|
||||||
|
// target: col.refColumnName,
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
|
||||||
|
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get generatesHiearchicGridColumn() {
|
||||||
|
// console.log('generatesHiearchicGridColumn', this.parentTableNode?.nodeConfig?.checkedColumns, this.codeName + '::');
|
||||||
|
return !!this.tableNodeOrParent?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get generatesHiearchicGridColumn() {
|
||||||
|
// // return this.config &&;
|
||||||
|
// }
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
if (this.column.types.includes('json')) {
|
||||||
|
return 'img json';
|
||||||
|
}
|
||||||
|
return 'img column';
|
||||||
|
}
|
||||||
|
|
||||||
|
get codeName() {
|
||||||
|
if (this.parentNode instanceof PerspectivePatternColumnNode) {
|
||||||
|
return `${this.parentNode.codeName}::${this.column.name}`;
|
||||||
|
}
|
||||||
|
return this.column.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get columnName() {
|
||||||
|
return this.column.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fieldName() {
|
||||||
|
return this.column.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.column.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isExpandable() {
|
||||||
|
return this.column.columns.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSortable() {
|
||||||
|
return !this.isChildColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
get filterType(): FilterType {
|
||||||
|
if (this.tableColumn) return getFilterType(this.tableColumn.dataType);
|
||||||
|
return 'mongo';
|
||||||
|
}
|
||||||
|
|
||||||
|
generateChildNodes(): PerspectiveTreeNode[] {
|
||||||
|
return this.column.columns.map(
|
||||||
|
column =>
|
||||||
|
new PerspectivePatternColumnNode(
|
||||||
|
this.table,
|
||||||
|
column,
|
||||||
|
this.tableColumn,
|
||||||
|
this.dbs,
|
||||||
|
this.config,
|
||||||
|
this.setConfig,
|
||||||
|
this.dataProvider,
|
||||||
|
this.databaseConfig,
|
||||||
|
this,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
// if (!this.foreignKey) return [];
|
||||||
|
// const tbl = this?.db?.tables?.find(
|
||||||
|
// x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return getTableChildPerspectiveNodes(
|
||||||
|
// tbl,
|
||||||
|
// this.dbs,
|
||||||
|
// this.config,
|
||||||
|
// this.setConfig,
|
||||||
|
// this.dataProvider,
|
||||||
|
// this.databaseConfig,
|
||||||
|
// this
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
get filterInfo(): PerspectiveFilterColumnInfo {
|
||||||
|
if (this.isChildColumn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
columnName: this.columnName,
|
||||||
|
filterType: this.filterType,
|
||||||
|
pureName: this.table.pureName,
|
||||||
|
schemaName: this.table.schemaName,
|
||||||
|
foreignKey: this.foreignKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parseFilterCondition(source = null): {} {
|
||||||
|
const filter = this.getFilter();
|
||||||
|
if (!filter) return null;
|
||||||
|
const condition = parseFilter(filter, 'mongo');
|
||||||
|
if (!condition) return null;
|
||||||
|
return _cloneDeepWith(condition, expr => {
|
||||||
|
if (expr.__placeholder__) {
|
||||||
|
return {
|
||||||
|
[this.columnName]: expr.__placeholder__,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get headerTableAttributes() {
|
||||||
|
// if (this.foreignKey) {
|
||||||
|
// return {
|
||||||
|
// schemaName: this.foreignKey.refSchemaName,
|
||||||
|
// pureName: this.foreignKey.refTableName,
|
||||||
|
// conid: this.databaseConfig.conid,
|
||||||
|
// database: this.databaseConfig.database,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get tableCode() {
|
||||||
|
// return `${this.collection.schemaName}|${this.table.pureName}`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get namedObject(): NamedObjectInfo {
|
||||||
|
// if (this.foreignKey) {
|
||||||
|
// return {
|
||||||
|
// schemaName: this.foreignKey.refSchemaName,
|
||||||
|
// pureName: this.foreignKey.refTableName,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
export class PerspectiveTableNode extends PerspectiveTreeNode {
|
export class PerspectiveTableNode extends PerspectiveTreeNode {
|
||||||
constructor(
|
constructor(
|
||||||
public table: TableInfo | ViewInfo,
|
public table: TableInfo | ViewInfo | CollectionInfo,
|
||||||
dbs: MultipleDatabaseInfo,
|
dbs: MultipleDatabaseInfo,
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
setConfig: ChangePerspectiveConfigFunc,
|
setConfig: ChangePerspectiveConfigFunc,
|
||||||
@@ -707,14 +961,22 @@ export class PerspectiveTableNode extends PerspectiveTreeNode {
|
|||||||
super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId);
|
super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get engineType(): PerspectiveDatabaseEngineType {
|
||||||
|
return isCollectionInfo(this.table) ? 'docdb' : 'sqldb';
|
||||||
|
}
|
||||||
|
|
||||||
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
||||||
|
const isMongo = isCollectionInfo(this.table);
|
||||||
return {
|
return {
|
||||||
schemaName: this.table.schemaName,
|
schemaName: this.table.schemaName,
|
||||||
pureName: this.table.pureName,
|
pureName: this.table.pureName,
|
||||||
dataColumns: this.getDataLoadColumns(),
|
dataColumns: this.getDataLoadColumns(),
|
||||||
|
allColumns: isMongo,
|
||||||
databaseConfig: this.databaseConfig,
|
databaseConfig: this.databaseConfig,
|
||||||
orderBy: this.getOrderBy(this.table),
|
orderBy: this.getOrderBy(this.table),
|
||||||
condition: this.getChildrenCondition(),
|
sqlCondition: isMongo ? null : this.getChildrenSqlCondition(),
|
||||||
|
mongoCondition: isMongo ? this.getChildrenMongoCondition() : null,
|
||||||
|
engineType: isMongo ? 'docdb' : 'sqldb',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,6 +1018,7 @@ export class PerspectiveTableNode extends PerspectiveTreeNode {
|
|||||||
pureName: this.table.pureName,
|
pureName: this.table.pureName,
|
||||||
conid: this.databaseConfig.conid,
|
conid: this.databaseConfig.conid,
|
||||||
database: this.databaseConfig.database,
|
database: this.databaseConfig.database,
|
||||||
|
objectTypeField: this.table.objectTypeField,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,64 +1033,6 @@ export class PerspectiveTableNode extends PerspectiveTreeNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// export class PerspectiveViewNode extends PerspectiveTreeNode {
|
|
||||||
// constructor(
|
|
||||||
// public view: ViewInfo,
|
|
||||||
// dbs: MultipleDatabaseInfo,
|
|
||||||
// config: PerspectiveConfig,
|
|
||||||
// setConfig: ChangePerspectiveConfigFunc,
|
|
||||||
// public dataProvider: PerspectiveDataProvider,
|
|
||||||
// databaseConfig: PerspectiveDatabaseConfig,
|
|
||||||
// parentNode: PerspectiveTreeNode
|
|
||||||
// ) {
|
|
||||||
// super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
|
||||||
// return {
|
|
||||||
// schemaName: this.view.schemaName,
|
|
||||||
// pureName: this.view.pureName,
|
|
||||||
// dataColumns: this.getDataLoadColumns(),
|
|
||||||
// databaseConfig: this.databaseConfig,
|
|
||||||
// orderBy: this.getOrderBy(this.view),
|
|
||||||
// condition: this.getChildrenCondition(),
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get codeName() {
|
|
||||||
// return this.view.schemaName ? `${this.view.schemaName}:${this.view.pureName}` : this.view.pureName;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get title() {
|
|
||||||
// return this.view.pureName;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get isExpandable() {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get childNodes(): PerspectiveTreeNode[] {
|
|
||||||
// return getTableChildPerspectiveNodes(
|
|
||||||
// this.view,
|
|
||||||
// this.dbs,
|
|
||||||
// this.config,
|
|
||||||
// this.setConfig,
|
|
||||||
// this.dataProvider,
|
|
||||||
// this.databaseConfig,
|
|
||||||
// this
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get icon() {
|
|
||||||
// return 'img table';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// getBaseTableFromThis() {
|
|
||||||
// return this.view;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
|
export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
|
||||||
constructor(
|
constructor(
|
||||||
public foreignKey: ForeignKeyInfo,
|
public foreignKey: ForeignKeyInfo,
|
||||||
@@ -872,7 +1077,8 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
|
|||||||
dataColumns: this.getDataLoadColumns(),
|
dataColumns: this.getDataLoadColumns(),
|
||||||
databaseConfig: this.databaseConfig,
|
databaseConfig: this.databaseConfig,
|
||||||
orderBy: this.getOrderBy(this.table),
|
orderBy: this.getOrderBy(this.table),
|
||||||
condition: this.getChildrenCondition(),
|
sqlCondition: this.getChildrenSqlCondition(),
|
||||||
|
engineType: 'sqldb',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,7 +1140,7 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
|
|||||||
export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
||||||
constructor(
|
constructor(
|
||||||
public customJoin: PerspectiveCustomJoinConfig,
|
public customJoin: PerspectiveCustomJoinConfig,
|
||||||
table: TableInfo | ViewInfo,
|
table: TableInfo | ViewInfo | CollectionInfo,
|
||||||
dbs: MultipleDatabaseInfo,
|
dbs: MultipleDatabaseInfo,
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
setConfig: ChangePerspectiveConfigFunc,
|
setConfig: ChangePerspectiveConfigFunc,
|
||||||
@@ -966,6 +1172,8 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
|||||||
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
||||||
// console.log('CUSTOM JOIN', this.customJoin);
|
// console.log('CUSTOM JOIN', this.customJoin);
|
||||||
// console.log('this.getDataLoadColumns()', this.getDataLoadColumns());
|
// console.log('this.getDataLoadColumns()', this.getDataLoadColumns());
|
||||||
|
const isMongo = isCollectionInfo(this.table);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schemaName: this.table.schemaName,
|
schemaName: this.table.schemaName,
|
||||||
pureName: this.table.pureName,
|
pureName: this.table.pureName,
|
||||||
@@ -975,9 +1183,12 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
|||||||
stableStringify
|
stableStringify
|
||||||
),
|
),
|
||||||
dataColumns: this.getDataLoadColumns(),
|
dataColumns: this.getDataLoadColumns(),
|
||||||
|
allColumns: isMongo,
|
||||||
databaseConfig: this.databaseConfig,
|
databaseConfig: this.databaseConfig,
|
||||||
orderBy: this.getOrderBy(this.table),
|
orderBy: this.getOrderBy(this.table),
|
||||||
condition: this.getChildrenCondition(),
|
sqlCondition: isMongo ? null : this.getChildrenSqlCondition(),
|
||||||
|
mongoCondition: isMongo ? this.getChildrenMongoCondition() : null,
|
||||||
|
engineType: isMongo ? 'docdb' : 'sqldb',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,7 +1293,7 @@ function findDesignerIdForNode<T extends PerspectiveTreeNode>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTableChildPerspectiveNodes(
|
export function getTableChildPerspectiveNodes(
|
||||||
table: TableInfo | ViewInfo,
|
table: TableInfo | ViewInfo | CollectionInfo,
|
||||||
dbs: MultipleDatabaseInfo,
|
dbs: MultipleDatabaseInfo,
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
setConfig: ChangePerspectiveConfigFunc,
|
setConfig: ChangePerspectiveConfigFunc,
|
||||||
@@ -1093,25 +1304,59 @@ export function getTableChildPerspectiveNodes(
|
|||||||
if (!table) return [];
|
if (!table) return [];
|
||||||
const db = parentNode.db;
|
const db = parentNode.db;
|
||||||
|
|
||||||
const columnNodes = table.columns.map(col =>
|
const pattern = dataProvider?.dataPatterns?.[parentNode.designerId];
|
||||||
findDesignerIdForNode(
|
|
||||||
config,
|
|
||||||
parentNode,
|
|
||||||
designerId =>
|
|
||||||
new PerspectiveTableColumnNode(
|
|
||||||
col,
|
|
||||||
table,
|
|
||||||
dbs,
|
|
||||||
config,
|
|
||||||
setConfig,
|
|
||||||
dataProvider,
|
|
||||||
databaseConfig,
|
|
||||||
parentNode,
|
|
||||||
designerId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const tableOrView = isTableInfo(table) || isViewInfo(table) ? table : null;
|
||||||
|
|
||||||
|
const columnNodes =
|
||||||
|
tableOrView?.columns?.map(col =>
|
||||||
|
findDesignerIdForNode(config, parentNode, designerId =>
|
||||||
|
pattern?.columns?.find(x => x.name == col.columnName)?.types.includes('json')
|
||||||
|
? new PerspectivePatternColumnNode(
|
||||||
|
table,
|
||||||
|
pattern?.columns?.find(x => x.name == col.columnName),
|
||||||
|
col,
|
||||||
|
dbs,
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
dataProvider,
|
||||||
|
databaseConfig,
|
||||||
|
parentNode,
|
||||||
|
designerId
|
||||||
|
)
|
||||||
|
: new PerspectiveTableColumnNode(
|
||||||
|
col,
|
||||||
|
tableOrView,
|
||||||
|
dbs,
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
dataProvider,
|
||||||
|
databaseConfig,
|
||||||
|
parentNode,
|
||||||
|
designerId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
pattern?.columns?.map(col =>
|
||||||
|
findDesignerIdForNode(
|
||||||
|
config,
|
||||||
|
parentNode,
|
||||||
|
designerId =>
|
||||||
|
new PerspectivePatternColumnNode(
|
||||||
|
table,
|
||||||
|
col,
|
||||||
|
null,
|
||||||
|
dbs,
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
dataProvider,
|
||||||
|
databaseConfig,
|
||||||
|
parentNode,
|
||||||
|
designerId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) ||
|
||||||
|
[];
|
||||||
// if (!columnNodes.find(x => x.isChecked)) {
|
// if (!columnNodes.find(x => x.isChecked)) {
|
||||||
// const circularColumns = columnNodes.filter(x => x.isCircular).map(x => x.columnName);
|
// const circularColumns = columnNodes.filter(x => x.isCircular).map(x => x.columnName);
|
||||||
// const defaultColumns = getPerspectiveDefaultColumns(table, db, circularColumns);
|
// const defaultColumns = getPerspectiveDefaultColumns(table, db, circularColumns);
|
||||||
@@ -1173,6 +1418,7 @@ export function getTableChildPerspectiveNodes(
|
|||||||
const db = dbs?.[newConfig.conid]?.[newConfig.database];
|
const db = dbs?.[newConfig.conid]?.[newConfig.database];
|
||||||
const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
const collection = db?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
|
||||||
const join: PerspectiveCustomJoinConfig = {
|
const join: PerspectiveCustomJoinConfig = {
|
||||||
refNodeDesignerId: node.designerId,
|
refNodeDesignerId: node.designerId,
|
||||||
@@ -1189,11 +1435,11 @@ export function getTableChildPerspectiveNodes(
|
|||||||
: ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })),
|
: ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (table || view) {
|
if (table || view || collection) {
|
||||||
customs.push(
|
customs.push(
|
||||||
new PerspectiveCustomJoinTreeNode(
|
new PerspectiveCustomJoinTreeNode(
|
||||||
join,
|
join,
|
||||||
table || view,
|
table || view || collection,
|
||||||
dbs,
|
dbs,
|
||||||
config,
|
config,
|
||||||
setConfig,
|
setConfig,
|
||||||
@@ -1210,34 +1456,5 @@ export function getTableChildPerspectiveNodes(
|
|||||||
|
|
||||||
res.push(..._sortBy(customs, 'title'));
|
res.push(..._sortBy(customs, 'title'));
|
||||||
|
|
||||||
// const customs = [];
|
|
||||||
// for (const join of config.customJoins || []) {
|
|
||||||
// if (join.baseUniqueName == parentColumn.uniqueName) {
|
|
||||||
// const newConfig = { ...databaseConfig };
|
|
||||||
// if (join.conid) newConfig.conid = join.conid;
|
|
||||||
// if (join.database) newConfig.database = join.database;
|
|
||||||
// const db = dbs?.[newConfig.conid]?.[newConfig.database];
|
|
||||||
// const table = db?.tables?.find(x => x.pureName == join.refTableName && x.schemaName == join.refSchemaName);
|
|
||||||
// const view = db?.views?.find(x => x.pureName == join.refTableName && x.schemaName == join.refSchemaName);
|
|
||||||
|
|
||||||
// if (table || view) {
|
|
||||||
// customs.push(
|
|
||||||
// new PerspectiveCustomJoinTreeNode(
|
|
||||||
// join,
|
|
||||||
// table || view,
|
|
||||||
// dbs,
|
|
||||||
// config,
|
|
||||||
// setConfig,
|
|
||||||
// dataProvider,
|
|
||||||
// newConfig,
|
|
||||||
// parentColumn,
|
|
||||||
// null
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// res.push(..._sortBy(customs, 'title'));
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ export * from './PerspectiveDataProvider';
|
|||||||
export * from './PerspectiveCache';
|
export * from './PerspectiveCache';
|
||||||
export * from './PerspectiveConfig';
|
export * from './PerspectiveConfig';
|
||||||
export * from './processPerspectiveDefaultColunns';
|
export * from './processPerspectiveDefaultColunns';
|
||||||
|
export * from './PerspectiveDataPattern';
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
import { findForeignKeyForColumn } from 'dbgate-tools';
|
import { findForeignKeyForColumn } from 'dbgate-tools';
|
||||||
import { DatabaseInfo, TableInfo, ViewInfo } from 'dbgate-types';
|
import { DatabaseInfo, TableInfo, ViewInfo } from 'dbgate-types';
|
||||||
import { createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig } from './PerspectiveConfig';
|
import { createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig } from './PerspectiveConfig';
|
||||||
|
import { PerspectiveDataPattern, PerspectiveDataPatternDict } from './PerspectiveDataPattern';
|
||||||
import { PerspectiveTableNode } from './PerspectiveTreeNode';
|
import { PerspectiveTableNode } from './PerspectiveTreeNode';
|
||||||
|
|
||||||
|
const namePredicates = [
|
||||||
|
x => x.toLowerCase() == 'name',
|
||||||
|
x => x.toLowerCase() == 'title',
|
||||||
|
x => x.toLowerCase().includes('name'),
|
||||||
|
x => x.toLowerCase().includes('title'),
|
||||||
|
x => x.toLowerCase().includes('subject'),
|
||||||
|
];
|
||||||
|
|
||||||
function getPerspectiveDefaultColumns(
|
function getPerspectiveDefaultColumns(
|
||||||
table: TableInfo | ViewInfo,
|
table: TableInfo | ViewInfo,
|
||||||
db: DatabaseInfo,
|
db: DatabaseInfo,
|
||||||
@@ -10,13 +19,7 @@ function getPerspectiveDefaultColumns(
|
|||||||
): [string[], string[]] {
|
): [string[], string[]] {
|
||||||
const columns = table.columns.map(x => x.columnName);
|
const columns = table.columns.map(x => x.columnName);
|
||||||
const predicates = [
|
const predicates = [
|
||||||
x => x.toLowerCase() == 'name',
|
...namePredicates,
|
||||||
x => x.toLowerCase() == 'title',
|
|
||||||
x => x.toLowerCase().includes('name'),
|
|
||||||
x => x.toLowerCase().includes('title'),
|
|
||||||
x => x.toLowerCase().includes('subject'),
|
|
||||||
// x => x.toLowerCase().includes('text'),
|
|
||||||
// x => x.toLowerCase().includes('desc'),
|
|
||||||
x =>
|
x =>
|
||||||
table.columns
|
table.columns
|
||||||
.find(y => y.columnName == x)
|
.find(y => y.columnName == x)
|
||||||
@@ -44,9 +47,20 @@ function getPerspectiveDefaultColumns(
|
|||||||
return [[columns[0]], null];
|
return [[columns[0]], null];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPerspectiveDefaultCollectionColumns(pattern: PerspectiveDataPattern): string[] {
|
||||||
|
const columns = pattern.columns.map(x => x.name);
|
||||||
|
const predicates = [...namePredicates, x => pattern.columns.find(y => y.name == x)?.types?.includes('string')];
|
||||||
|
|
||||||
|
for (const predicate of predicates) {
|
||||||
|
const col = columns.find(predicate);
|
||||||
|
if (col) return [col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function perspectiveNodesHaveStructure(
|
export function perspectiveNodesHaveStructure(
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
dbInfos: MultipleDatabaseInfo,
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataPatterns: PerspectiveDataPatternDict,
|
||||||
conid: string,
|
conid: string,
|
||||||
database: string
|
database: string
|
||||||
) {
|
) {
|
||||||
@@ -56,8 +70,10 @@ export function perspectiveNodesHaveStructure(
|
|||||||
|
|
||||||
const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
const collection = db.collections.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
|
||||||
if (!table && !view) return false;
|
if (!table && !view && !collection) return false;
|
||||||
|
if (collection && !dataPatterns?.[node.designerId]) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -66,18 +82,20 @@ export function perspectiveNodesHaveStructure(
|
|||||||
export function shouldProcessPerspectiveDefaultColunns(
|
export function shouldProcessPerspectiveDefaultColunns(
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
dbInfos: MultipleDatabaseInfo,
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataPatterns: PerspectiveDataPatternDict,
|
||||||
conid: string,
|
conid: string,
|
||||||
database: string
|
database: string
|
||||||
) {
|
) {
|
||||||
const nodesNotProcessed = config.nodes.filter(x => !x.defaultColumnsProcessed);
|
const nodesNotProcessed = config.nodes.filter(x => !x.defaultColumnsProcessed);
|
||||||
if (nodesNotProcessed.length == 0) return false;
|
if (nodesNotProcessed.length == 0) return false;
|
||||||
|
|
||||||
return perspectiveNodesHaveStructure(config, dbInfos, conid, database);
|
return perspectiveNodesHaveStructure(config, dbInfos, dataPatterns, conid, database);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processPerspectiveDefaultColunnsStep(
|
function processPerspectiveDefaultColunnsStep(
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
dbInfos: MultipleDatabaseInfo,
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataPatterns: PerspectiveDataPatternDict,
|
||||||
conid: string,
|
conid: string,
|
||||||
database: string
|
database: string
|
||||||
) {
|
) {
|
||||||
@@ -107,6 +125,7 @@ function processPerspectiveDefaultColunnsStep(
|
|||||||
|
|
||||||
const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
const collection = db.collections.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
|
||||||
if (table || view) {
|
if (table || view) {
|
||||||
const treeNode = root.findNodeByDesignerId(node.designerId);
|
const treeNode = root.findNodeByDesignerId(node.designerId);
|
||||||
@@ -181,6 +200,22 @@ function processPerspectiveDefaultColunnsStep(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const defaultColumns = getPerspectiveDefaultCollectionColumns(dataPatterns?.[node.designerId]);
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
nodes: config.nodes.map(n =>
|
||||||
|
n.designerId == node.designerId
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
defaultColumnsProcessed: true,
|
||||||
|
checkedColumns: defaultColumns,
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -199,11 +234,12 @@ function markAllProcessed(config: PerspectiveConfig): PerspectiveConfig {
|
|||||||
export function processPerspectiveDefaultColunns(
|
export function processPerspectiveDefaultColunns(
|
||||||
config: PerspectiveConfig,
|
config: PerspectiveConfig,
|
||||||
dbInfos: MultipleDatabaseInfo,
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataPatterns: PerspectiveDataPatternDict,
|
||||||
conid: string,
|
conid: string,
|
||||||
database: string
|
database: string
|
||||||
): PerspectiveConfig {
|
): PerspectiveConfig {
|
||||||
while (config.nodes.filter(x => !x.defaultColumnsProcessed).length > 0) {
|
while (config.nodes.filter(x => !x.defaultColumnsProcessed).length > 0) {
|
||||||
const newConfig = processPerspectiveDefaultColunnsStep(config, dbInfos, conid, database);
|
const newConfig = processPerspectiveDefaultColunnsStep(config, dbInfos, dataPatterns, conid, database);
|
||||||
if (!newConfig) {
|
if (!newConfig) {
|
||||||
return markAllProcessed(config);
|
return markAllProcessed(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TableInfo } from 'dbgate-types';
|
|
||||||
import { PerspectiveDisplay } from '../PerspectiveDisplay';
|
import { PerspectiveDisplay } from '../PerspectiveDisplay';
|
||||||
import { PerspectiveTableNode } from '../PerspectiveTreeNode';
|
import { PerspectiveTableNode } from '../PerspectiveTreeNode';
|
||||||
import { chinookDbInfo } from './chinookDbInfo';
|
import { chinookDbInfo } from './chinookDbInfo';
|
||||||
@@ -13,6 +12,7 @@ test('test flat view', () => {
|
|||||||
const configColumns = processPerspectiveDefaultColunns(
|
const configColumns = processPerspectiveDefaultColunns(
|
||||||
createPerspectiveConfig({ pureName: 'Artist' }),
|
createPerspectiveConfig({ pureName: 'Artist' }),
|
||||||
{ conid: { db: chinookDbInfo } },
|
{ conid: { db: chinookDbInfo } },
|
||||||
|
null,
|
||||||
'conid',
|
'conid',
|
||||||
'db'
|
'db'
|
||||||
);
|
);
|
||||||
@@ -47,7 +47,7 @@ test('test one level nesting', () => {
|
|||||||
columns: [{ source: 'ArtistId', target: 'ArtistId' }],
|
columns: [{ source: 'ArtistId', target: 'ArtistId' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, 'conid', 'db');
|
const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, null, 'conid', 'db');
|
||||||
|
|
||||||
// const config = createPerspectiveConfig({ pureName: 'Artist' });
|
// const config = createPerspectiveConfig({ pureName: 'Artist' });
|
||||||
// config.nodes[0].checkedColumns = ['Album'];
|
// config.nodes[0].checkedColumns = ['Album'];
|
||||||
@@ -107,7 +107,7 @@ test('test two level nesting', () => {
|
|||||||
designerId: '2',
|
designerId: '2',
|
||||||
columns: [{ source: 'AlbumId', target: 'AlbumId' }],
|
columns: [{ source: 'AlbumId', target: 'AlbumId' }],
|
||||||
});
|
});
|
||||||
const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, 'conid', 'db');
|
const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, null, 'conid', 'db');
|
||||||
|
|
||||||
const root = new PerspectiveTableNode(
|
const root = new PerspectiveTableNode(
|
||||||
artistTable,
|
artistTable,
|
||||||
|
|||||||
98
packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts
Normal file
98
packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { PerspectiveDisplay } from '../PerspectiveDisplay';
|
||||||
|
import { PerspectiveTableNode } from '../PerspectiveTreeNode';
|
||||||
|
import { createPerspectiveConfig, PerspectiveNodeConfig } from '../PerspectiveConfig';
|
||||||
|
import { processPerspectiveDefaultColunns } from '../processPerspectiveDefaultColunns';
|
||||||
|
import { DatabaseAnalyser } from 'dbgate-tools';
|
||||||
|
import { analyseDataPattern } from '../PerspectiveDataPattern';
|
||||||
|
import { PerspectiveDataProvider } from '../PerspectiveDataProvider';
|
||||||
|
|
||||||
|
const accountData = [
|
||||||
|
{
|
||||||
|
name: 'jan',
|
||||||
|
email: 'jan@foo.co',
|
||||||
|
follows: [{ name: 'lucie' }, { name: 'petr' }],
|
||||||
|
nested: { email: 'jan@nest.cz' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'romeo',
|
||||||
|
email: 'romeo@foo.co',
|
||||||
|
follows: [{ name: 'julie' }, { name: 'wiliam' }],
|
||||||
|
nested: { email: 'romeo@nest.cz' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function createDisplay(cfgFunc?: (cfg: PerspectiveNodeConfig) => void) {
|
||||||
|
const collectionInfo = {
|
||||||
|
objectTypeField: 'collections',
|
||||||
|
pureName: 'Account',
|
||||||
|
};
|
||||||
|
const dbInfo = {
|
||||||
|
...DatabaseAnalyser.createEmptyStructure(),
|
||||||
|
collections: [collectionInfo],
|
||||||
|
};
|
||||||
|
const config = createPerspectiveConfig({ pureName: 'Account' });
|
||||||
|
const dataPatterns = {
|
||||||
|
[config.rootDesignerId]: analyseDataPattern(
|
||||||
|
{
|
||||||
|
conid: 'conid',
|
||||||
|
database: 'db',
|
||||||
|
pureName: 'Account',
|
||||||
|
},
|
||||||
|
accountData
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const configColumns = processPerspectiveDefaultColunns(
|
||||||
|
config,
|
||||||
|
{ conid: { db: dbInfo } },
|
||||||
|
dataPatterns,
|
||||||
|
'conid',
|
||||||
|
'db'
|
||||||
|
);
|
||||||
|
if (cfgFunc) {
|
||||||
|
cfgFunc(configColumns.nodes[0]);
|
||||||
|
}
|
||||||
|
const root = new PerspectiveTableNode(
|
||||||
|
collectionInfo,
|
||||||
|
{ conid: { db: dbInfo } },
|
||||||
|
configColumns,
|
||||||
|
null,
|
||||||
|
new PerspectiveDataProvider(null, null, dataPatterns),
|
||||||
|
{ conid: 'conid', database: 'db' },
|
||||||
|
null,
|
||||||
|
configColumns.rootDesignerId
|
||||||
|
);
|
||||||
|
|
||||||
|
const display = new PerspectiveDisplay(root, accountData);
|
||||||
|
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('test nosql display', () => {
|
||||||
|
const display = createDisplay();
|
||||||
|
|
||||||
|
expect(display.rows.length).toEqual(2);
|
||||||
|
expect(display.rows[0].rowData).toEqual(['jan']);
|
||||||
|
expect(display.rows[1].rowData).toEqual(['romeo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test nosql nested array display', () => {
|
||||||
|
const display = createDisplay(cfg => {
|
||||||
|
cfg.checkedColumns = ['name', 'follows::name'];
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(display.rows.length).toEqual(4);
|
||||||
|
expect(display.rows[0].rowData).toEqual(['jan', 'lucie']);
|
||||||
|
expect(display.rows[1].rowData).toEqual([undefined, 'petr']);
|
||||||
|
expect(display.rows[2].rowData).toEqual(['romeo', 'julie']);
|
||||||
|
expect(display.rows[3].rowData).toEqual([undefined, 'wiliam']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test nosql nested object', () => {
|
||||||
|
const display = createDisplay(cfg => {
|
||||||
|
cfg.checkedColumns = ['name', 'nested::email'];
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(display.rows.length).toEqual(2);
|
||||||
|
expect(display.rows[0].rowData).toEqual(['jan', 'jan@nest.cz']);
|
||||||
|
expect(display.rows[1].rowData).toEqual(['romeo', 'romeo@nest.cz']);
|
||||||
|
});
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"typescript": "^4.4.3"
|
"typescript": "^4.4.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"json-stable-stringify": "^1.0.1",
|
"json-stable-stringify": "^1.0.1",
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
dialect: SqlDialect;
|
dialect: SqlDialect;
|
||||||
indentLevel = 0;
|
indentLevel = 0;
|
||||||
|
|
||||||
|
static keywordsCase = 'upperCase';
|
||||||
|
static convertKeywordCase(keyword: any): string {
|
||||||
|
if (this.keywordsCase == 'lowerCase') return keyword?.toString()?.toLowerCase();
|
||||||
|
return keyword?.toString()?.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(driver: EngineDriver) {
|
constructor(driver: EngineDriver) {
|
||||||
this.driver = driver;
|
this.driver = driver;
|
||||||
this.dialect = driver.dialect;
|
this.dialect = driver.dialect;
|
||||||
@@ -60,10 +66,10 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
this.putRaw("'");
|
this.putRaw("'");
|
||||||
}
|
}
|
||||||
putByteArrayValue(value) {
|
putByteArrayValue(value) {
|
||||||
this.putRaw('NULL');
|
this.put('^null');
|
||||||
}
|
}
|
||||||
putValue(value) {
|
putValue(value) {
|
||||||
if (value === null) this.putRaw('NULL');
|
if (value === null) this.put('^null');
|
||||||
else if (value === true) this.putRaw('1');
|
else if (value === true) this.putRaw('1');
|
||||||
else if (value === false) this.putRaw('0');
|
else if (value === false) this.putRaw('0');
|
||||||
else if (_isString(value)) this.putStringValue(value);
|
else if (_isString(value)) this.putStringValue(value);
|
||||||
@@ -71,7 +77,7 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
else if (_isDate(value)) this.putStringValue(new Date(value).toISOString());
|
else if (_isDate(value)) this.putStringValue(new Date(value).toISOString());
|
||||||
else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data);
|
else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data);
|
||||||
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));
|
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));
|
||||||
else this.putRaw('NULL');
|
else this.put('^null');
|
||||||
}
|
}
|
||||||
putCmd(format, ...args) {
|
putCmd(format, ...args) {
|
||||||
this.put(format, ...args);
|
this.put(format, ...args);
|
||||||
@@ -92,7 +98,7 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
case 'k':
|
case 'k':
|
||||||
{
|
{
|
||||||
if (value) {
|
if (value) {
|
||||||
this.putRaw(value.toUpperCase());
|
this.putRaw(SqlDumper.convertKeywordCase(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -128,7 +134,7 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
switch (c) {
|
switch (c) {
|
||||||
case '^':
|
case '^':
|
||||||
while (i < length && format[i].match(/[a-z0-9_]/i)) {
|
while (i < length && format[i].match(/[a-z0-9_]/i)) {
|
||||||
this.putRaw(format[i].toUpperCase());
|
this.putRaw(SqlDumper.convertKeywordCase(format[i]));
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -181,6 +187,14 @@ export class SqlDumper implements AlterProcessor {
|
|||||||
this.put(' ^auto_increment');
|
this.put(' ^auto_increment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDatabase(name: string) {
|
||||||
|
this.putCmd('^create ^database %i', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropDatabase(name: string) {
|
||||||
|
this.putCmd('^drop ^database %i', name);
|
||||||
|
}
|
||||||
|
|
||||||
specialColumnOptions(column) {}
|
specialColumnOptions(column) {}
|
||||||
|
|
||||||
columnDefinition(column: ColumnInfo, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) {
|
columnDefinition(column: ColumnInfo, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const dialect = {
|
|||||||
isSparse: false,
|
isSparse: false,
|
||||||
isPersisted: false,
|
isPersisted: false,
|
||||||
},
|
},
|
||||||
|
defaultSchemaName: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const driverBase = {
|
export const driverBase = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DatabaseInfo, TableInfo, ApplicationDefinition } from 'dbgate-types';
|
import { DatabaseInfo, TableInfo, ApplicationDefinition, ViewInfo, CollectionInfo } from 'dbgate-types';
|
||||||
import _flatten from 'lodash/flatten';
|
import _flatten from 'lodash/flatten';
|
||||||
|
|
||||||
export function addTableDependencies(db: DatabaseInfo): DatabaseInfo {
|
export function addTableDependencies(db: DatabaseInfo): DatabaseInfo {
|
||||||
@@ -118,3 +118,15 @@ export function isTableColumnUnique(table: TableInfo, column: string) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTableInfo(obj: { objectTypeField?: string }): obj is TableInfo {
|
||||||
|
return obj.objectTypeField == 'tables';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isViewInfo(obj: { objectTypeField?: string }): obj is ViewInfo {
|
||||||
|
return obj.objectTypeField == 'views';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCollectionInfo(obj: { objectTypeField?: string }): obj is CollectionInfo {
|
||||||
|
return obj.objectTypeField == 'collections';
|
||||||
|
}
|
||||||
|
|||||||
1
packages/types/dialect.d.ts
vendored
1
packages/types/dialect.d.ts
vendored
@@ -9,6 +9,7 @@ export interface SqlDialect {
|
|||||||
fallbackDataType?: string;
|
fallbackDataType?: string;
|
||||||
explicitDropConstraint?: boolean;
|
explicitDropConstraint?: boolean;
|
||||||
anonymousPrimaryKey?: boolean;
|
anonymousPrimaryKey?: boolean;
|
||||||
|
defaultSchemaName?: string;
|
||||||
enableConstraintsPerTable?: boolean;
|
enableConstraintsPerTable?: boolean;
|
||||||
|
|
||||||
dropColumnDependencies?: string[];
|
dropColumnDependencies?: string[];
|
||||||
|
|||||||
2
packages/types/dumper.d.ts
vendored
2
packages/types/dumper.d.ts
vendored
@@ -14,6 +14,8 @@ export interface SqlDumper extends AlterProcessor {
|
|||||||
putValue(value: string | number | Date);
|
putValue(value: string | number | Date);
|
||||||
putCollection<T>(delimiter: string, collection: T[], lambda: (item: T) => void);
|
putCollection<T>(delimiter: string, collection: T[], lambda: (item: T) => void);
|
||||||
transform(type: TransformType, dumpExpr: () => void);
|
transform(type: TransformType, dumpExpr: () => void);
|
||||||
|
createDatabase(name: string);
|
||||||
|
dropDatabase(name: string);
|
||||||
|
|
||||||
endCommand();
|
endCommand();
|
||||||
allowIdentityInsert(table: NamedObjectInfo, allow: boolean);
|
allowIdentityInsert(table: NamedObjectInfo, allow: boolean);
|
||||||
|
|||||||
7
packages/types/engines.d.ts
vendored
7
packages/types/engines.d.ts
vendored
@@ -89,9 +89,7 @@ export interface EngineDriver {
|
|||||||
): Promise<TableInfo | ViewInfo | ProcedureInfo | FunctionInfo | TriggerInfo>;
|
): Promise<TableInfo | ViewInfo | ProcedureInfo | FunctionInfo | TriggerInfo>;
|
||||||
analyseSingleTable(pool: any, name: NamedObjectInfo): Promise<TableInfo>;
|
analyseSingleTable(pool: any, name: NamedObjectInfo): Promise<TableInfo>;
|
||||||
getVersion(pool: any): Promise<{ version: string }>;
|
getVersion(pool: any): Promise<{ version: string }>;
|
||||||
listDatabases(
|
listDatabases(pool: any): Promise<
|
||||||
pool: any
|
|
||||||
): Promise<
|
|
||||||
{
|
{
|
||||||
name: string;
|
name: string;
|
||||||
}[]
|
}[]
|
||||||
@@ -112,7 +110,8 @@ export interface EngineDriver {
|
|||||||
updateCollection(pool: any, changeSet: any): Promise<any>;
|
updateCollection(pool: any, changeSet: any): Promise<any>;
|
||||||
getCollectionUpdateScript(changeSet: any): string;
|
getCollectionUpdateScript(changeSet: any): string;
|
||||||
createDatabase(pool: any, name: string): Promise;
|
createDatabase(pool: any, name: string): Promise;
|
||||||
getQuerySplitterOptions(usage: 'stream' | 'script'): any;
|
dropDatabase(pool: any, name: string): Promise;
|
||||||
|
getQuerySplitterOptions(usage: 'stream' | 'script' | 'editor'): any;
|
||||||
script(pool: any, sql: string): Promise;
|
script(pool: any, sql: string): Promise;
|
||||||
getNewObjectTemplates(): NewObjectTemplate[];
|
getNewObjectTemplates(): NewObjectTemplate[];
|
||||||
// direct call of pool method, only some methods could be supported, on only some drivers
|
// direct call of pool method, only some methods could be supported, on only some drivers
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"chartjs-adapter-moment": "^1.0.0",
|
"chartjs-adapter-moment": "^1.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dbgate-datalib": "^5.0.0-alpha.1",
|
"dbgate-datalib": "^5.0.0-alpha.1",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-sqltree": "^5.0.0-alpha.1",
|
"dbgate-sqltree": "^5.0.0-alpha.1",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"dbgate-types": "^5.0.0-alpha.1",
|
"dbgate-types": "^5.0.0-alpha.1",
|
||||||
|
|||||||
@@ -167,3 +167,33 @@ textarea {
|
|||||||
color: var(--theme-font-1);
|
color: var(--theme-font-1);
|
||||||
border: 1px solid var(--theme-border);
|
border: 1px solid var(--theme-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ace_gutter-cell.ace-gutter-sql-run {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 2px center;
|
||||||
|
|
||||||
|
/* content: '▶';
|
||||||
|
margin-right: 3px; */
|
||||||
|
|
||||||
|
/* border-radius: 20px 0px 0px 20px; */
|
||||||
|
/* Change the color of the breakpoint if you want
|
||||||
|
box-shadow: 0px 0px 1px 1px #248c46 inset; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-type-light .ace_gutter-cell.ace-gutter-sql-run {
|
||||||
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMTcuODA0IDE3LjgwNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTcuODA0IDE3LjgwNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnIGlkPSJjOThfcGxheSI+CgkJPHBhdGggZmlsbD0nIzQ0NCcgZD0iTTIuMDY3LDAuMDQzQzIuMjEtMC4wMjgsMi4zNzItMC4wMDgsMi40OTMsMC4wODVsMTMuMzEyLDguNTAzYzAuMDk0LDAuMDc4LDAuMTU0LDAuMTkxLDAuMTU0LDAuMzEzCgkJCWMwLDAuMTItMC4wNjEsMC4yMzctMC4xNTQsMC4zMTRMMi40OTIsMTcuNzE3Yy0wLjA3LDAuMDU3LTAuMTYyLDAuMDg3LTAuMjUsMC4wODdsLTAuMTc2LTAuMDQKCQkJYy0wLjEzNi0wLjA2NS0wLjIyMi0wLjIwNy0wLjIyMi0wLjM2MVYwLjQwMkMxLjg0NCwwLjI1LDEuOTMsMC4xMDcsMi4wNjcsMC4wNDN6Ii8+Cgk8L2c+Cgk8ZyBpZD0iQ2FwYV8xXzc4XyI+Cgk8L2c+CjwvZz4KPC9zdmc+Cg==);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-type-dark .ace_gutter-cell.ace-gutter-sql-run {
|
||||||
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMTcuODA0IDE3LjgwNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTcuODA0IDE3LjgwNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxnIGlkPSJjOThfcGxheSI+CgkJPHBhdGggZmlsbD0nI2RkZCcgZD0iTTIuMDY3LDAuMDQzQzIuMjEtMC4wMjgsMi4zNzItMC4wMDgsMi40OTMsMC4wODVsMTMuMzEyLDguNTAzYzAuMDk0LDAuMDc4LDAuMTU0LDAuMTkxLDAuMTU0LDAuMzEzCgkJCWMwLDAuMTItMC4wNjEsMC4yMzctMC4xNTQsMC4zMTRMMi40OTIsMTcuNzE3Yy0wLjA3LDAuMDU3LTAuMTYyLDAuMDg3LTAuMjUsMC4wODdsLTAuMTc2LTAuMDQKCQkJYy0wLjEzNi0wLjA2NS0wLjIyMi0wLjIwNy0wLjIyMi0wLjM2MVYwLjQwMkMxLjg0NCwwLjI1LDEuOTMsMC4xMDcsMi4wNjcsMC4wNDN6Ii8+Cgk8L2c+Cgk8ZyBpZD0iQ2FwYV8xXzc4XyI+Cgk8L2c+CjwvZz4KPC9zdmc+Cg==);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_gutter-cell.ace-gutter-sql-run:hover {
|
||||||
|
background-color: var(--theme-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_gutter-cell.ace-gutter-current-part {
|
||||||
|
/* background-color: var(--theme-bg-2); */
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--theme-font-hover);
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
import AppTitleProvider from './utility/AppTitleProvider.svelte';
|
import AppTitleProvider from './utility/AppTitleProvider.svelte';
|
||||||
import getElectron from './utility/getElectron';
|
import getElectron from './utility/getElectron';
|
||||||
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
||||||
|
import SettingsListener from './utility/SettingsListener.svelte';
|
||||||
|
|
||||||
let loadedApi = false;
|
let loadedApi = false;
|
||||||
let loadedPlugins = false;
|
let loadedPlugins = false;
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
<AppTitleProvider />
|
<AppTitleProvider />
|
||||||
{#if loadedPlugins}
|
{#if loadedPlugins}
|
||||||
<OpenTabsOnStartup />
|
<OpenTabsOnStartup />
|
||||||
|
<SettingsListener />
|
||||||
<Screen />
|
<Screen />
|
||||||
{:else}
|
{:else}
|
||||||
<AppStartInfo
|
<AppStartInfo
|
||||||
|
|||||||
@@ -77,6 +77,17 @@
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDropDatabase = () => {
|
||||||
|
showModal(ConfirmModal, {
|
||||||
|
message: `Really drop database ${name}? All opened sessions with this database will be forcefully closed.`,
|
||||||
|
onConfirm: () =>
|
||||||
|
apiCall('server-connections/drop-database', {
|
||||||
|
conid: connection._id,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleNewCollection = () => {
|
const handleNewCollection = () => {
|
||||||
showModal(InputTextModal, {
|
showModal(InputTextModal, {
|
||||||
value: '',
|
value: '',
|
||||||
@@ -218,6 +229,30 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleQueryDesigner = () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Query #',
|
||||||
|
icon: 'img query-design',
|
||||||
|
tabComponent: 'QueryDesignTab',
|
||||||
|
props: {
|
||||||
|
conid: connection._id,
|
||||||
|
database: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewPerspective = () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Perspective #',
|
||||||
|
icon: 'img perspective',
|
||||||
|
tabComponent: 'PerspectiveTab',
|
||||||
|
props: {
|
||||||
|
conid: connection._id,
|
||||||
|
database: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async function handleConfirmSql(sql) {
|
async function handleConfirmSql(sql) {
|
||||||
saveScriptToDatabase({ conid: connection._id, database: name }, sql, false);
|
saveScriptToDatabase({ conid: connection._id, database: name }, sql, false);
|
||||||
}
|
}
|
||||||
@@ -233,13 +268,21 @@
|
|||||||
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
||||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: 'New table' },
|
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: 'New table' },
|
||||||
driver?.databaseEngineTypes?.includes('document') && { onClick: handleNewCollection, text: 'New collection' },
|
driver?.databaseEngineTypes?.includes('document') && { onClick: handleNewCollection, text: 'New collection' },
|
||||||
|
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleQueryDesigner, text: 'Design query' },
|
||||||
|
driver?.databaseEngineTypes?.includes('sql') && {
|
||||||
|
onClick: handleNewPerspective,
|
||||||
|
text: 'Design perspective query',
|
||||||
|
},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
isSqlOrDoc && !connection.isReadOnly && { onClick: handleImport, text: 'Import wizard' },
|
isSqlOrDoc && !connection.isReadOnly && { onClick: handleImport, text: 'Import wizard' },
|
||||||
isSqlOrDoc && { onClick: handleExport, text: 'Export wizard' },
|
isSqlOrDoc && { onClick: handleExport, text: 'Export wizard' },
|
||||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' },
|
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' },
|
||||||
driver?.supportsDatabaseDump && { onClick: handleSqlDump, text: 'Backup/export SQL dump' },
|
driver?.supportsDatabaseDump && { onClick: handleSqlDump, text: 'Backup/export SQL dump' },
|
||||||
|
isSqlOrDoc &&
|
||||||
|
!connection.isReadOnly &&
|
||||||
|
!connection.singleDatabase && { onClick: handleDropDatabase, text: 'Drop database' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
isSqlOrDoc && { onClick: handleShowDiagram, text: 'Show diagram' },
|
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleShowDiagram, text: 'Show diagram' },
|
||||||
isSqlOrDoc && { onClick: handleSqlGenerator, text: 'SQL Generator' },
|
isSqlOrDoc && { onClick: handleSqlGenerator, text: 'SQL Generator' },
|
||||||
isSqlOrDoc && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
|
isSqlOrDoc && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
|
||||||
isSqlOrDoc && { onClick: handleExportModel, text: 'Export DB model - experimental' },
|
isSqlOrDoc && { onClick: handleExportModel, text: 'Export DB model - experimental' },
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
schemaName,
|
schemaName,
|
||||||
...(columns?.map(({ columnName }) => ({ childName: columnName })) || [])
|
...(columns?.map(({ columnName }) => ({ childName: columnName })) || [])
|
||||||
);
|
);
|
||||||
export const createTitle = ({ pureName }) => pureName;
|
export const createTitle = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||||
|
|
||||||
export const databaseObjectIcons = {
|
export const databaseObjectIcons = {
|
||||||
tables: 'img table',
|
tables: 'img table',
|
||||||
@@ -52,7 +52,12 @@
|
|||||||
icon: 'img table-structure',
|
icon: 'img table-structure',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open perspective',
|
label: 'Design query',
|
||||||
|
isQueryDesigner: true,
|
||||||
|
requiresWriteAccess: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Design perspective query',
|
||||||
tab: 'PerspectiveTab',
|
tab: 'PerspectiveTab',
|
||||||
forceNewTab: true,
|
forceNewTab: true,
|
||||||
icon: 'img perspective',
|
icon: 'img perspective',
|
||||||
@@ -80,11 +85,6 @@
|
|||||||
isDuplicateTable: true,
|
isDuplicateTable: true,
|
||||||
requiresWriteAccess: true,
|
requiresWriteAccess: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Query designer',
|
|
||||||
isQueryDesigner: true,
|
|
||||||
requiresWriteAccess: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Show diagram',
|
label: 'Show diagram',
|
||||||
isDiagram: true,
|
isDiagram: true,
|
||||||
@@ -155,7 +155,11 @@
|
|||||||
icon: 'img view-structure',
|
icon: 'img view-structure',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open perspective',
|
label: 'Design query',
|
||||||
|
isQueryDesigner: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Design perspective query',
|
||||||
tab: 'PerspectiveTab',
|
tab: 'PerspectiveTab',
|
||||||
forceNewTab: true,
|
forceNewTab: true,
|
||||||
icon: 'img perspective',
|
icon: 'img perspective',
|
||||||
@@ -164,10 +168,6 @@
|
|||||||
label: 'Drop view',
|
label: 'Drop view',
|
||||||
isDrop: true,
|
isDrop: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Query designer',
|
|
||||||
isQueryDesigner: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
@@ -345,6 +345,12 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Design perspective query',
|
||||||
|
tab: 'PerspectiveTab',
|
||||||
|
forceNewTab: true,
|
||||||
|
icon: 'img perspective',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
isExport: true,
|
isExport: true,
|
||||||
@@ -586,6 +592,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getObjectTitle(connection, schemaName, pureName) {
|
||||||
|
const driver = findEngineDriver(connection, getExtensions());
|
||||||
|
|
||||||
|
const defaultSchema = driver?.dialect?.defaultSchemaName;
|
||||||
|
if (schemaName && defaultSchema && schemaName != defaultSchema) {
|
||||||
|
return `${schemaName}.${pureName}`;
|
||||||
|
}
|
||||||
|
return pureName;
|
||||||
|
}
|
||||||
|
|
||||||
export async function openDatabaseObjectDetail(
|
export async function openDatabaseObjectDetail(
|
||||||
tabComponent,
|
tabComponent,
|
||||||
scriptTemplate,
|
scriptTemplate,
|
||||||
@@ -603,7 +619,7 @@
|
|||||||
|
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: scriptTemplate ? 'Query #' : pureName,
|
title: scriptTemplate ? 'Query #' : getObjectTitle(connection, schemaName, pureName),
|
||||||
tooltip,
|
tooltip,
|
||||||
icon: icon || (scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
|
icon: icon || (scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
|
||||||
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
|
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ registerCommand({
|
|||||||
name: 'Query design',
|
name: 'Query design',
|
||||||
menuName: 'New query design',
|
menuName: 'New query design',
|
||||||
onClick: () => newQueryDesign(),
|
onClick: () => newQueryDesign(),
|
||||||
|
testEnabled: () =>
|
||||||
|
getCurrentDatabase() &&
|
||||||
|
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'),
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
@@ -144,6 +147,9 @@ registerCommand({
|
|||||||
icon: 'img diagram',
|
icon: 'img diagram',
|
||||||
name: 'ER Diagram',
|
name: 'ER Diagram',
|
||||||
menuName: 'New ER diagram',
|
menuName: 'New ER diagram',
|
||||||
|
testEnabled: () =>
|
||||||
|
getCurrentDatabase() &&
|
||||||
|
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'),
|
||||||
onClick: () => newDiagram(),
|
onClick: () => newDiagram(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -599,7 +605,7 @@ export function registerFileCommands({
|
|||||||
registerCommand({
|
registerCommand({
|
||||||
id: idPrefix + '.replace',
|
id: idPrefix + '.replace',
|
||||||
category,
|
category,
|
||||||
keyText: 'CtrlOrCommand+H',
|
keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H',
|
||||||
name: 'Replace',
|
name: 'Replace',
|
||||||
testEnabled: () => getCurrentEditor() != null,
|
testEnabled: () => getCurrentEditor() != null,
|
||||||
onClick: () => getCurrentEditor().replace(),
|
onClick: () => getCurrentEditor().replace(),
|
||||||
|
|||||||
@@ -200,7 +200,7 @@
|
|||||||
id: 'dataGrid.hideColumn',
|
id: 'dataGrid.hideColumn',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
name: 'Hide column',
|
name: 'Hide column',
|
||||||
keyText: 'CtrlOrCommand+H',
|
keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H',
|
||||||
testEnabled: () => getCurrentDataGrid() != null,
|
testEnabled: () => getCurrentDataGrid() != null,
|
||||||
onClick: () => getCurrentDataGrid().hideColumn(),
|
onClick: () => getCurrentDataGrid().hideColumn(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
let changeIndex = 0;
|
let changeIndex = 0;
|
||||||
let rowCountLoaded = null;
|
let rowCountLoaded = null;
|
||||||
|
|
||||||
const throttleLoadNext = _.throttle(() => domGrid.resetLoadedAll(), 500);
|
const throttleLoadNext = _.throttle(() => domGrid?.resetLoadedAll(), 500);
|
||||||
|
|
||||||
const handleJslDataStats = stats => {
|
const handleJslDataStats = stats => {
|
||||||
if (stats.changeIndex < changeIndex) return;
|
if (stats.changeIndex < changeIndex) return;
|
||||||
|
|||||||
@@ -61,6 +61,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: sortOrderProps = settings?.getSortOrderProps ? settings?.getSortOrderProps(designerId, column.columnName) : null;
|
$: sortOrderProps = settings?.getSortOrderProps ? settings?.getSortOrderProps(designerId, column.columnName) : null;
|
||||||
|
$: iconOverride = settings?.getColumnIconOverride
|
||||||
|
? settings?.getColumnIconOverride(designerId, column.columnName)
|
||||||
|
: null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -144,7 +147,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ColumnLabel {...column} {foreignKey} forceIcon />
|
<ColumnLabel {...column} {foreignKey} forceIcon {iconOverride} />
|
||||||
{#if designerColumn?.filter}
|
{#if designerColumn?.filter}
|
||||||
<FontIcon icon="img filter" />
|
<FontIcon icon="img filter" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -479,7 +479,7 @@
|
|||||||
const rect = e.target.getBoundingClientRect();
|
const rect = e.target.getBoundingClientRect();
|
||||||
var json = JSON.parse(data);
|
var json = JSON.parse(data);
|
||||||
const { objectTypeField } = json;
|
const { objectTypeField } = json;
|
||||||
if (objectTypeField != 'tables' && objectTypeField != 'views') return;
|
if (objectTypeField != 'tables' && objectTypeField != 'views' && objectTypeField != 'collections') return;
|
||||||
json.designerId = `${json.pureName}-${uuidv1()}`;
|
json.designerId = `${json.pureName}-${uuidv1()}`;
|
||||||
json.left = e.clientX - rect.left;
|
json.left = e.clientX - rect.left;
|
||||||
json.top = e.clientY - rect.top;
|
json.top = e.clientY - rect.top;
|
||||||
@@ -941,6 +941,7 @@
|
|||||||
.empty {
|
.empty {
|
||||||
margin: 50px;
|
margin: 50px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
.canvas {
|
.canvas {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -213,6 +213,8 @@
|
|||||||
!isMultipleTableSelection && [{ divider: true }, createDatabaseObjectMenu({ ...table, conid, database })],
|
!isMultipleTableSelection && [{ divider: true }, createDatabaseObjectMenu({ ...table, conid, database })],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $: console.log('COLUMNS', columns);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -238,6 +240,7 @@
|
|||||||
class:isGrayed
|
class:isGrayed
|
||||||
class:isTable={objectTypeField == 'tables'}
|
class:isTable={objectTypeField == 'tables'}
|
||||||
class:isView={objectTypeField == 'views'}
|
class:isView={objectTypeField == 'views'}
|
||||||
|
class:isCollection={objectTypeField == 'collections'}
|
||||||
use:moveDrag={settings?.canSelectColumns ? [handleMoveStart, handleMove, handleMoveEnd] : null}
|
use:moveDrag={settings?.canSelectColumns ? [handleMoveStart, handleMove, handleMoveEnd] : null}
|
||||||
use:contextMenu={settings?.canSelectColumns ? createMenu : '__no_menu'}
|
use:contextMenu={settings?.canSelectColumns ? createMenu : '__no_menu'}
|
||||||
style={getTableColorStyle($currentThemeDefinition, table)}
|
style={getTableColorStyle($currentThemeDefinition, table)}
|
||||||
@@ -358,6 +361,10 @@
|
|||||||
.header.isView {
|
.header.isView {
|
||||||
background: var(--theme-bg-magenta);
|
background: var(--theme-bg-magenta);
|
||||||
}
|
}
|
||||||
|
.header.isCollection {
|
||||||
|
background: var(--theme-bg-red);
|
||||||
|
}
|
||||||
|
|
||||||
.header.isGrayed {
|
.header.isGrayed {
|
||||||
background: var(--theme-bg-2);
|
background: var(--theme-bg-2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,9 @@
|
|||||||
export let foreignKey;
|
export let foreignKey;
|
||||||
export let conid = undefined;
|
export let conid = undefined;
|
||||||
export let database = undefined;
|
export let database = undefined;
|
||||||
|
export let iconOverride = undefined;
|
||||||
|
|
||||||
$: icon = getColumnIcon($$props, forceIcon);
|
$: icon = iconOverride || getColumnIcon($$props, forceIcon);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="label" class:notNull>
|
<span class="label" class:notNull>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<InlineButton
|
<InlineButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
skip -= limit;
|
skip = parseInt(skip) - parseInt(limit);
|
||||||
if (skip < 0) skip = 0;
|
if (skip < 0) skip = 0;
|
||||||
dispatch('load');
|
dispatch('load');
|
||||||
}}
|
}}
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<TextField type="number" bind:value={limit} on:blur={() => dispatch('load')} on:keydown={handleKeyDown} />
|
<TextField type="number" bind:value={limit} on:blur={() => dispatch('load')} on:keydown={handleKeyDown} />
|
||||||
<InlineButton
|
<InlineButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
skip += limit;
|
skip = parseInt(skip) + parseInt(limit);
|
||||||
dispatch('load');
|
dispatch('load');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
31
packages/web/src/elements/TabCloseButton.svelte
Normal file
31
packages/web/src/elements/TabCloseButton.svelte
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
|
export let unsaved = false;
|
||||||
|
|
||||||
|
let mousein = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="close-button tabCloseButton"
|
||||||
|
on:click
|
||||||
|
on:mouseenter={() => {
|
||||||
|
mousein = true;
|
||||||
|
}}
|
||||||
|
on:mouseleave={() => {
|
||||||
|
mousein = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontIcon icon={unsaved && !mousein ? 'icon unsaved' : 'icon close'} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.close-button {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: var(--theme-font-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
'icon file': 'mdi mdi-file',
|
'icon file': 'mdi mdi-file',
|
||||||
'icon loading': 'mdi mdi-loading mdi-spin',
|
'icon loading': 'mdi mdi-loading mdi-spin',
|
||||||
'icon close': 'mdi mdi-close',
|
'icon close': 'mdi mdi-close',
|
||||||
|
'icon unsaved': 'mdi mdi-record',
|
||||||
'icon stop': 'mdi mdi-close-octagon',
|
'icon stop': 'mdi mdi-close-octagon',
|
||||||
'icon filter': 'mdi mdi-filter',
|
'icon filter': 'mdi mdi-filter',
|
||||||
'icon filter-off': 'mdi mdi-filter-off',
|
'icon filter-off': 'mdi mdi-filter-off',
|
||||||
|
|||||||
47
packages/web/src/modals/CloseTabModal.svelte
Normal file
47
packages/web/src/modals/CloseTabModal.svelte
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
|
|
||||||
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
|
||||||
|
export let tabs;
|
||||||
|
export let onConfirm;
|
||||||
|
export let onCancel;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProvider>
|
||||||
|
<ModalBase {...$$restProps}>
|
||||||
|
<svelte:fragment slot="header">Confirm close tabs</svelte:fragment>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Following files are modified, really close tabs? After closing, you could reopen them in history
|
||||||
|
<FontIcon icon="icon history" />
|
||||||
|
widget
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each tabs as tab}
|
||||||
|
<div class="ml-2"><FontIcon icon={tab.icon} /> {tab.title}</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<FormSubmit
|
||||||
|
value="Close tabs"
|
||||||
|
on:click={() => {
|
||||||
|
closeCurrentModal();
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormStyledButton
|
||||||
|
type="button"
|
||||||
|
value="Cancel"
|
||||||
|
on:click={() => {
|
||||||
|
closeCurrentModal();
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProvider>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { SqlDumper } from 'dbgate-tools';
|
||||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
import TableControl from '../elements/TableControl.svelte';
|
import TableControl from '../elements/TableControl.svelte';
|
||||||
import TextField from '../forms/TextField.svelte';
|
import TextField from '../forms/TextField.svelte';
|
||||||
@@ -63,9 +64,11 @@
|
|||||||
const source = sources[sourceIndex];
|
const source = sources[sourceIndex];
|
||||||
const target = targets[targetIndex];
|
const target = targets[targetIndex];
|
||||||
if (source && target) {
|
if (source && target) {
|
||||||
return `${JOIN_TYPES[joinIndex]} ${target.refTable}${alias ? ` ${alias}` : ''} ON ${target.columnMap
|
return `${SqlDumper.convertKeywordCase(JOIN_TYPES[joinIndex])} ${target.refTable}${
|
||||||
|
alias ? ` ${alias}` : ''
|
||||||
|
} ${SqlDumper.convertKeywordCase('ON')} ${target.columnMap
|
||||||
.map(col => `${source.name}.${col.columnName} = ${alias || target.refTable}.${col.refColumnName}`)
|
.map(col => `${source.name}.${col.columnName} = ${alias || target.refTable}.${col.refColumnName}`)
|
||||||
.join(' AND ')}`;
|
.join(SqlDumper.convertKeywordCase(' AND '))}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getAsImageSrc, safeJsonParse } from 'dbgate-tools';
|
import { getAsImageSrc, safeJsonParse } from 'dbgate-tools';
|
||||||
import { isArray } from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import CellValue from '../datagrid/CellValue.svelte';
|
import CellValue from '../datagrid/CellValue.svelte';
|
||||||
import JSONTree from '../jsontree/JSONTree.svelte';
|
import JSONTree from '../jsontree/JSONTree.svelte';
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
export let displayType;
|
export let displayType;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<td rowspan={rowSpan} data-column={columnIndex} class:isEmpty={value===undefined}>
|
<td rowspan={rowSpan} data-column={columnIndex} class:isEmpty={value === undefined}>
|
||||||
{#if value !== undefined}
|
{#if value !== undefined}
|
||||||
{#if displayType == 'json'}
|
{#if displayType == 'json'}
|
||||||
<JSONTree value={safeJsonParse(value, value?.toString())} slicedKeyCount={1} disableContextMenu />
|
<JSONTree value={safeJsonParse(value, value?.toString())} slicedKeyCount={1} disableContextMenu />
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<span class="null"> (no image)</span>
|
<span class="null"> (no image)</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if !value.$oid && (_.isArray(value) || _.isPlainObject(value))}
|
||||||
|
<JSONTree {value} slicedKeyCount={1} disableContextMenu />
|
||||||
{:else}
|
{:else}
|
||||||
<CellValue {rowData} {value} />
|
<CellValue {rowData} {value} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
createPerspectiveNodeConfig,
|
createPerspectiveNodeConfig,
|
||||||
MultipleDatabaseInfo,
|
MultipleDatabaseInfo,
|
||||||
PerspectiveConfig,
|
PerspectiveConfig,
|
||||||
|
PerspectiveDataPatternDict,
|
||||||
perspectiveNodesHaveStructure,
|
perspectiveNodesHaveStructure,
|
||||||
PerspectiveTreeNode,
|
PerspectiveTreeNode,
|
||||||
switchPerspectiveReferenceDirection,
|
switchPerspectiveReferenceDirection,
|
||||||
} from 'dbgate-datalib';
|
} from 'dbgate-datalib';
|
||||||
|
import { CollectionInfo } from 'dbgate-types';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import runCommand from '../commands/runCommand';
|
import runCommand from '../commands/runCommand';
|
||||||
@@ -18,6 +20,7 @@
|
|||||||
|
|
||||||
export let config: PerspectiveConfig;
|
export let config: PerspectiveConfig;
|
||||||
export let dbInfos: MultipleDatabaseInfo;
|
export let dbInfos: MultipleDatabaseInfo;
|
||||||
|
export let dataPatterns: PerspectiveDataPatternDict;
|
||||||
export let root: PerspectiveTreeNode;
|
export let root: PerspectiveTreeNode;
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -27,22 +30,39 @@
|
|||||||
|
|
||||||
export let onClickTableHeader = null;
|
export let onClickTableHeader = null;
|
||||||
|
|
||||||
function createDesignerModel(config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo) {
|
function createDesignerModel(
|
||||||
|
config: PerspectiveConfig,
|
||||||
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataPatterns: PerspectiveDataPatternDict
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
tables: _.compact(
|
tables: _.compact(
|
||||||
config.nodes.map(node => {
|
config.nodes.map(node => {
|
||||||
const table = dbInfos?.[node.conid || conid]?.[node.database || database]?.tables?.find(
|
const db = dbInfos?.[node.conid || conid]?.[node.database || database];
|
||||||
|
const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||||
|
let collection: CollectionInfo & { columns?: any[] } = db?.collections?.find(
|
||||||
x => x.pureName == node.pureName && x.schemaName == node.schemaName
|
x => x.pureName == node.pureName && x.schemaName == node.schemaName
|
||||||
);
|
);
|
||||||
const view = dbInfos?.[node.conid || conid]?.[node.database || database]?.views?.find(
|
|
||||||
x => x.pureName == node.pureName && x.schemaName == node.schemaName
|
if (collection) {
|
||||||
);
|
const pattern = dataPatterns?.[node.designerId];
|
||||||
if (!table && !view) return null;
|
if (!pattern) return null;
|
||||||
|
collection = {
|
||||||
|
...collection,
|
||||||
|
columns:
|
||||||
|
pattern?.columns.map(x => ({
|
||||||
|
columnName: x.name,
|
||||||
|
})) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table && !view && !collection) return null;
|
||||||
|
|
||||||
const { designerId } = node;
|
const { designerId } = node;
|
||||||
return {
|
return {
|
||||||
...(table || view),
|
...(table || view || collection),
|
||||||
left: node?.position?.x || 0,
|
left: node?.position?.x || 0,
|
||||||
top: node?.position?.y || 0,
|
top: node?.position?.y || 0,
|
||||||
alias: node.alias,
|
alias: node.alias,
|
||||||
@@ -55,7 +75,7 @@
|
|||||||
|
|
||||||
function handleChange(value, skipUndoChain, settings) {
|
function handleChange(value, skipUndoChain, settings) {
|
||||||
setConfig(oldValue => {
|
setConfig(oldValue => {
|
||||||
const newValue = _.isFunction(value) ? value(createDesignerModel(oldValue, dbInfos)) : value;
|
const newValue = _.isFunction(value) ? value(createDesignerModel(oldValue, dbInfos, dataPatterns)) : value;
|
||||||
let isArranged = oldValue.isArranged;
|
let isArranged = oldValue.isArranged;
|
||||||
if (settings?.isCalledFromArrange) {
|
if (settings?.isCalledFromArrange) {
|
||||||
isArranged = true;
|
isArranged = true;
|
||||||
@@ -122,11 +142,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectAutoArrange(config: PerspectiveConfig, dbInfos, root) {
|
async function detectAutoArrange(config: PerspectiveConfig, dbInfos, dataPatterns, root) {
|
||||||
if (
|
if (
|
||||||
root &&
|
root &&
|
||||||
config.nodes.find(x => !x.position) &&
|
config.nodes.find(x => !x.position) &&
|
||||||
perspectiveNodesHaveStructure(config, dbInfos, conid, database) &&
|
perspectiveNodesHaveStructure(config, dbInfos, dataPatterns, conid, database) &&
|
||||||
config.nodes.every(x => root?.findNodeByDesignerId(x.designerId))
|
config.nodes.every(x => root?.findNodeByDesignerId(x.designerId))
|
||||||
) {
|
) {
|
||||||
await tick();
|
await tick();
|
||||||
@@ -134,7 +154,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: detectAutoArrange(config, dbInfos, root);
|
$: detectAutoArrange(config, dbInfos, dataPatterns, root);
|
||||||
|
|
||||||
// $: console.log('DESIGNER ROOT', root);
|
// $: console.log('DESIGNER ROOT', root);
|
||||||
</script>
|
</script>
|
||||||
@@ -221,6 +241,14 @@
|
|||||||
const orderIndex = sort.length > 1 ? _.findIndex(sort, x => x.columnName == columnName) : -1;
|
const orderIndex = sort.length > 1 ? _.findIndex(sort, x => x.columnName == columnName) : -1;
|
||||||
return { order, orderIndex };
|
return { order, orderIndex };
|
||||||
},
|
},
|
||||||
|
getColumnIconOverride: (designerId, columnName) => {
|
||||||
|
const pattern = dataPatterns?.[designerId];
|
||||||
|
const column = pattern?.columns.find(x => x.name == columnName);
|
||||||
|
if (column?.types?.includes('json')) {
|
||||||
|
return 'img json';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
isColumnFiltered: (designerId, columnName) => {
|
isColumnFiltered: (designerId, columnName) => {
|
||||||
return !!config.nodes.find(x => x.designerId == designerId)?.filters?.[columnName];
|
return !!config.nodes.find(x => x.designerId == designerId)?.filters?.[columnName];
|
||||||
},
|
},
|
||||||
@@ -277,6 +305,6 @@
|
|||||||
onClickTableHeader,
|
onClickTableHeader,
|
||||||
}}
|
}}
|
||||||
referenceComponent={QueryDesignerReference}
|
referenceComponent={QueryDesignerReference}
|
||||||
value={createDesignerModel(config, dbInfos)}
|
value={createDesignerModel(config, dbInfos, dataPatterns)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
ChangePerspectiveConfigFunc,
|
ChangePerspectiveConfigFunc,
|
||||||
PerspectiveConfig,
|
PerspectiveConfig,
|
||||||
PerspectiveDisplay,
|
PerspectiveDisplay,
|
||||||
|
PerspectivePatternColumnNode,
|
||||||
PerspectiveTableColumnNode,
|
PerspectiveTableColumnNode,
|
||||||
PerspectiveTreeNode,
|
PerspectiveTreeNode,
|
||||||
PERSPECTIVE_PAGE_SIZE,
|
PERSPECTIVE_PAGE_SIZE,
|
||||||
@@ -41,6 +42,24 @@
|
|||||||
import { getFilterValueExpression } from 'dbgate-filterparser';
|
import { getFilterValueExpression } from 'dbgate-filterparser';
|
||||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||||
|
|
||||||
|
const TABS_BY_FIELD = {
|
||||||
|
tables: {
|
||||||
|
text: 'table',
|
||||||
|
tabComponent: 'TableDataTab',
|
||||||
|
icon: 'img table',
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
text: 'view',
|
||||||
|
tabComponent: 'ViewDataTab',
|
||||||
|
icon: 'img view',
|
||||||
|
},
|
||||||
|
collections: {
|
||||||
|
text: 'collection',
|
||||||
|
tabComponent: 'CollectionDataTab',
|
||||||
|
icon: 'img collection',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveTable');
|
const dbg = debug('dbgate:PerspectiveTable');
|
||||||
export const activator = createActivator('PerspectiveTable', true, ['Designer']);
|
export const activator = createActivator('PerspectiveTable', true, ['Designer']);
|
||||||
|
|
||||||
@@ -57,6 +76,7 @@
|
|||||||
let errorMessage;
|
let errorMessage;
|
||||||
let rowCount;
|
let rowCount;
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
let isLoadQueued = false;
|
||||||
const lastVisibleRowIndexRef = createRef(0);
|
const lastVisibleRowIndexRef = createRef(0);
|
||||||
const disableLoadNextRef = createRef(false);
|
const disableLoadNextRef = createRef(false);
|
||||||
|
|
||||||
@@ -121,6 +141,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadData(node: PerspectiveTreeNode, counts) {
|
async function loadData(node: PerspectiveTreeNode, counts) {
|
||||||
|
if (isLoading) {
|
||||||
|
isLoadQueued = true;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
isLoadQueued = false;
|
||||||
|
}
|
||||||
// console.log('LOADING', node);
|
// console.log('LOADING', node);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -147,6 +173,10 @@
|
|||||||
// loadProps.push(child.getNodeLoadProps());
|
// loadProps.push(child.getNodeLoadProps());
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if (isLoadQueued) {
|
||||||
|
loadData(root, $loadedCounts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openJson() {
|
export function openJson() {
|
||||||
@@ -199,24 +229,28 @@
|
|||||||
const tableNode = root?.findNodeByDesignerId(tableNodeDesignerId);
|
const tableNode = root?.findNodeByDesignerId(tableNodeDesignerId);
|
||||||
|
|
||||||
if (tableNode?.headerTableAttributes) {
|
if (tableNode?.headerTableAttributes) {
|
||||||
const { pureName, schemaName, conid, database } = tableNode?.headerTableAttributes;
|
const { pureName, schemaName, conid, database, objectTypeField } = tableNode?.headerTableAttributes;
|
||||||
res.push({
|
console.log('objectTypeField', objectTypeField);
|
||||||
text: `Open table ${pureName}`,
|
const tab = TABS_BY_FIELD[objectTypeField];
|
||||||
onClick: () => {
|
if (tab) {
|
||||||
openNewTab({
|
res.push({
|
||||||
title: pureName,
|
text: `Open ${tab.text} ${pureName}`,
|
||||||
icon: 'img table',
|
onClick: () => {
|
||||||
tabComponent: 'TableDataTab',
|
openNewTab({
|
||||||
props: {
|
title: pureName,
|
||||||
schemaName,
|
icon: tab.icon,
|
||||||
pureName,
|
tabComponent: tab.tabComponent,
|
||||||
conid: conid,
|
props: {
|
||||||
database: database,
|
schemaName,
|
||||||
objectTypeField: 'tables',
|
pureName,
|
||||||
},
|
conid: conid,
|
||||||
});
|
database: database,
|
||||||
},
|
objectTypeField,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setColumnDisplay = type => {
|
const setColumnDisplay = type => {
|
||||||
@@ -280,42 +314,39 @@
|
|||||||
const value = display.rows[rowIndex].rowData[columnIndex];
|
const value = display.rows[rowIndex].rowData[columnIndex];
|
||||||
const { dataNode } = column;
|
const { dataNode } = column;
|
||||||
|
|
||||||
if (dataNode instanceof PerspectiveTableColumnNode) {
|
if (
|
||||||
|
dataNode.filterInfo &&
|
||||||
|
(dataNode instanceof PerspectiveTableColumnNode || dataNode instanceof PerspectivePatternColumnNode)
|
||||||
|
) {
|
||||||
const { table } = dataNode;
|
const { table } = dataNode;
|
||||||
let tabComponent = null;
|
|
||||||
let icon = null;
|
const tab = TABS_BY_FIELD[table.objectTypeField];
|
||||||
let objectTypeField = null;
|
const filterExpression = getFilterValueExpression(
|
||||||
if (dataNode.isTable) {
|
value,
|
||||||
tabComponent = 'TableDataTab';
|
dataNode instanceof PerspectiveTableColumnNode ? dataNode.column.dataType : null
|
||||||
icon = 'img table';
|
);
|
||||||
objectTypeField = 'tables';
|
|
||||||
}
|
if (tab) {
|
||||||
if (dataNode.isView) {
|
|
||||||
tabComponent = 'ViewDataTab';
|
|
||||||
icon = 'img view';
|
|
||||||
objectTypeField = 'views';
|
|
||||||
}
|
|
||||||
if (tabComponent) {
|
|
||||||
res.push({
|
res.push({
|
||||||
text: 'Open filtered table',
|
text: 'Open filtered grid',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: table.pureName,
|
title: table.pureName,
|
||||||
icon,
|
icon: tab.icon,
|
||||||
tabComponent,
|
tabComponent: tab.tabComponent,
|
||||||
props: {
|
props: {
|
||||||
schemaName: table.schemaName,
|
schemaName: table.schemaName,
|
||||||
pureName: table.pureName,
|
pureName: table.pureName,
|
||||||
conid,
|
conid,
|
||||||
database,
|
database,
|
||||||
objectTypeField,
|
objectTypeField: table.objectTypeField,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
grid: {
|
grid: {
|
||||||
filters: {
|
filters: {
|
||||||
[dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType),
|
[dataNode.columnName]: filterExpression,
|
||||||
},
|
},
|
||||||
// isFormView: true,
|
// isFormView: true,
|
||||||
},
|
},
|
||||||
@@ -339,7 +370,7 @@
|
|||||||
...n,
|
...n,
|
||||||
filters: {
|
filters: {
|
||||||
...n.filters,
|
...n.filters,
|
||||||
[dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType),
|
[dataNode.columnName]: filterExpression,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: n
|
: n
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
import { sleep } from '../utility/common';
|
import { sleep } from '../utility/common';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
import InlineButton from '../buttons/InlineButton.svelte';
|
import InlineButton from '../buttons/InlineButton.svelte';
|
||||||
|
import { usePerspectiveDataPatterns } from '../utility/usePerspectiveDataPatterns';
|
||||||
|
|
||||||
const dbg = debug('dbgate:PerspectiveView');
|
const dbg = debug('dbgate:PerspectiveView');
|
||||||
|
|
||||||
@@ -128,17 +129,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: dbInfos = useMultipleDatabaseInfo(perspectiveDatabases);
|
$: dbInfos = useMultipleDatabaseInfo(perspectiveDatabases);
|
||||||
|
$: loader = new PerspectiveDataLoader(apiCall);
|
||||||
|
$: dataPatterns = usePerspectiveDataPatterns({ conid, database }, config, cache, $dbInfos, loader);
|
||||||
$: rootObject = config?.nodes?.find(x => x.designerId == config?.rootDesignerId);
|
$: rootObject = config?.nodes?.find(x => x.designerId == config?.rootDesignerId);
|
||||||
$: rootDb = rootObject ? $dbInfos?.[rootObject.conid || conid]?.[rootObject.database || database] : null;
|
$: rootDb = rootObject ? $dbInfos?.[rootObject.conid || conid]?.[rootObject.database || database] : null;
|
||||||
$: tableInfo = rootDb?.tables.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName);
|
$: tableInfo = rootDb?.tables.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName);
|
||||||
$: viewInfo = rootDb?.views.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName);
|
$: viewInfo = rootDb?.views.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName);
|
||||||
|
$: collectionInfo = rootDb?.collections.find(
|
||||||
|
x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName
|
||||||
|
);
|
||||||
|
|
||||||
$: loader = new PerspectiveDataLoader(apiCall);
|
$: dataProvider = new PerspectiveDataProvider(cache, loader, $dataPatterns);
|
||||||
$: dataProvider = new PerspectiveDataProvider(cache, loader);
|
|
||||||
$: root =
|
$: root =
|
||||||
tableInfo || viewInfo
|
tableInfo || viewInfo || collectionInfo
|
||||||
? new PerspectiveTableNode(
|
? new PerspectiveTableNode(
|
||||||
tableInfo || viewInfo,
|
tableInfo || viewInfo || collectionInfo,
|
||||||
$dbInfos,
|
$dbInfos,
|
||||||
config,
|
config,
|
||||||
setConfig,
|
setConfig,
|
||||||
@@ -151,13 +156,14 @@
|
|||||||
$: tempRoot = root?.findNodeByDesignerId(tempRootDesignerId);
|
$: tempRoot = root?.findNodeByDesignerId(tempRootDesignerId);
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, conid, database)) {
|
if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, $dataPatterns, conid, database)) {
|
||||||
setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, conid, database));
|
setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, $dataPatterns, conid, database));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// $: console.log('PERSPECTIVE', config);
|
// $: console.log('PERSPECTIVE', config);
|
||||||
// $: console.log('VIEW ROOT', root);
|
// $: console.log('VIEW ROOT', root);
|
||||||
|
// $: console.log('dataPatterns', $dataPatterns);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<HorizontalSplitter initialValue={getInitialManagerSize()} bind:size={managerSize} allowCollapseChild1>
|
<HorizontalSplitter initialValue={getInitialManagerSize()} bind:size={managerSize} allowCollapseChild1>
|
||||||
@@ -205,6 +211,7 @@
|
|||||||
{database}
|
{database}
|
||||||
{setConfig}
|
{setConfig}
|
||||||
dbInfos={$dbInfos}
|
dbInfos={$dbInfos}
|
||||||
|
dataPatterns={$dataPatterns}
|
||||||
{root}
|
{root}
|
||||||
onClickTableHeader={designerId => {
|
onClickTableHeader={designerId => {
|
||||||
sleep(100).then(() => {
|
sleep(100).then(() => {
|
||||||
|
|||||||
@@ -155,6 +155,8 @@
|
|||||||
export let readOnly = false;
|
export let readOnly = false;
|
||||||
export let splitterOptions = null;
|
export let splitterOptions = null;
|
||||||
export let onKeyDown = null;
|
export let onKeyDown = null;
|
||||||
|
export let onExecuteFragment = null;
|
||||||
|
export let errorMessages = null;
|
||||||
|
|
||||||
const tabVisible: any = getContext('tabVisible');
|
const tabVisible: any = getContext('tabVisible');
|
||||||
|
|
||||||
@@ -166,7 +168,8 @@
|
|||||||
|
|
||||||
let queryParts = [];
|
let queryParts = [];
|
||||||
let currentPart = null;
|
let currentPart = null;
|
||||||
let currentPartMarker = null;
|
let currentPartLines = [];
|
||||||
|
// let currentPartMarker = null;
|
||||||
|
|
||||||
let queryParserWorker;
|
let queryParserWorker;
|
||||||
|
|
||||||
@@ -183,16 +186,26 @@
|
|||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentCommandText(): string {
|
export function getCurrentCommandText(): { text: string; line?: number } {
|
||||||
if (currentPart != null) return currentPart.text;
|
if (currentPart != null) {
|
||||||
if (!editor) return '';
|
return {
|
||||||
const selectedText = editor.getSelectedText();
|
text: currentPart.text,
|
||||||
if (selectedText) return selectedText;
|
line: currentPart.trimStart.line,
|
||||||
if (editor.getHighlightActiveLine()) {
|
};
|
||||||
const line = editor.getSelectionRange().start.row;
|
|
||||||
return editor.session.getLine(line);
|
|
||||||
}
|
}
|
||||||
return '';
|
if (!editor) return { text: '' };
|
||||||
|
const selectedText = editor.getSelectedText();
|
||||||
|
if (selectedText) {
|
||||||
|
return {
|
||||||
|
text: selectedText,
|
||||||
|
line: editor.getSelectionRange().start.row,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const line = editor.getSelectionRange().start.row;
|
||||||
|
return {
|
||||||
|
text: editor.session.getLine(line),
|
||||||
|
line,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCodeCompletionCommandText() {
|
export function getCodeCompletionCommandText() {
|
||||||
@@ -285,8 +298,37 @@
|
|||||||
|
|
||||||
function processParserResult(data) {
|
function processParserResult(data) {
|
||||||
queryParts = data;
|
queryParts = data;
|
||||||
editor.setHighlightActiveLine(queryParts.length <= 1);
|
// editor.setHighlightActiveLine(queryParts.length <= 1);
|
||||||
changedCurrentQueryPart();
|
changedCurrentQueryPart();
|
||||||
|
updateAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnnotations() {
|
||||||
|
if (!mode?.includes('sql')) return;
|
||||||
|
|
||||||
|
// console.log('UPDATING ANNOTATIONS');
|
||||||
|
|
||||||
|
editor?.session?.setAnnotations([
|
||||||
|
...(queryParts || [])
|
||||||
|
.filter(part => !(errorMessages || []).find(err => err.line == part.trimStart.line))
|
||||||
|
.map(part => ({
|
||||||
|
row: part.trimStart.line,
|
||||||
|
text: part.text,
|
||||||
|
className: currentPartLines.includes(part.trimStart.line)
|
||||||
|
? 'ace-gutter-sql-run ace-gutter-current-part'
|
||||||
|
: 'ace-gutter-sql-run', // className: 'ace-gutter-sql-run',
|
||||||
|
})),
|
||||||
|
...(errorMessages || []).map(error => ({
|
||||||
|
row: error.line,
|
||||||
|
text: error.message,
|
||||||
|
type: 'error',
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
errorMessages;
|
||||||
|
updateAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContextMenu = e => {
|
const handleContextMenu = e => {
|
||||||
@@ -304,8 +346,6 @@
|
|||||||
function changedQueryParts() {
|
function changedQueryParts() {
|
||||||
const editor = getEditor();
|
const editor = getEditor();
|
||||||
if (splitterOptions && editor && queryParserWorker) {
|
if (splitterOptions && editor && queryParserWorker) {
|
||||||
const editor = getEditor();
|
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
text: editor.getValue(),
|
text: editor.getValue(),
|
||||||
options: {
|
options: {
|
||||||
@@ -329,19 +369,20 @@
|
|||||||
function changedCurrentQueryPart() {
|
function changedCurrentQueryPart() {
|
||||||
if (queryParts.length <= 1) {
|
if (queryParts.length <= 1) {
|
||||||
removeCurrentPartMarker();
|
removeCurrentPartMarker();
|
||||||
|
updateAnnotations();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectionRange = editor.getSelectionRange();
|
const selectionRange = editor.getSelectionRange();
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
selectionRange.start.row != selectionRange.end.row ||
|
// selectionRange.start.row != selectionRange.end.row ||
|
||||||
selectionRange.start.column != selectionRange.end.column
|
// selectionRange.start.column != selectionRange.end.column
|
||||||
) {
|
// ) {
|
||||||
removeCurrentPartMarker();
|
// removeCurrentPartMarker();
|
||||||
currentPart = null;
|
// currentPart = null;
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const cursor = selectionRange.start;
|
const cursor = selectionRange.start;
|
||||||
const part = queryParts.find(
|
const part = queryParts.find(
|
||||||
@@ -350,25 +391,39 @@
|
|||||||
((cursor.row == x.end.line && cursor.column <= x.end.column) || cursor.row < x.end.line)
|
((cursor.row == x.end.line && cursor.column <= x.end.column) || cursor.row < x.end.line)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (part?.text != currentPart?.text || part?.start?.position != currentPart?.start?.position) {
|
if (
|
||||||
|
part?.text != currentPart?.text ||
|
||||||
|
part?.start?.position != currentPart?.start?.position ||
|
||||||
|
part?.end?.position != currentPart?.end?.position
|
||||||
|
) {
|
||||||
removeCurrentPartMarker();
|
removeCurrentPartMarker();
|
||||||
|
|
||||||
currentPart = part;
|
currentPart = part;
|
||||||
if (currentPart) {
|
if (currentPart) {
|
||||||
const start = currentPart.trimStart || currentPart.start;
|
const start = currentPart.trimStart || currentPart.start;
|
||||||
const end = currentPart.trimEnd || currentPart.end;
|
const end = currentPart.trimEnd || currentPart.end;
|
||||||
currentPartMarker = editor
|
if (start && end) {
|
||||||
.getSession()
|
currentPartLines = _.range(start.line, end.line + 1);
|
||||||
.addMarker(new ace.Range(start.line, start.column, end.line, end.column), 'ace_active-line', 'text');
|
for (const row of currentPartLines) {
|
||||||
|
if ((queryParts || []).find(part => part.trimStart.line == row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editor.getSession().addGutterDecoration(row, 'ace-gutter-current-part');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// currentPartMarker = editor
|
||||||
|
// .getSession()
|
||||||
|
// .addMarker(new ace.Range(start.line, start.column, end.line, end.column), 'ace_active-line', 'text');
|
||||||
}
|
}
|
||||||
|
updateAnnotations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCurrentPartMarker() {
|
function removeCurrentPartMarker() {
|
||||||
if (currentPartMarker != null) {
|
for (const row of currentPartLines) {
|
||||||
editor.getSession().removeMarker(currentPartMarker);
|
editor.getSession().removeGutterDecoration(row, 'ace-gutter-current-part');
|
||||||
currentPartMarker = null;
|
|
||||||
}
|
}
|
||||||
|
currentPartLines = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -380,6 +435,7 @@
|
|||||||
editor.getSession().setMode('ace/mode/' + mode);
|
editor.getSession().setMode('ace/mode/' + mode);
|
||||||
editor.setTheme('ace/theme/' + theme);
|
editor.setTheme('ace/theme/' + theme);
|
||||||
editor.setValue(value, 1);
|
editor.setValue(value, 1);
|
||||||
|
editor.setHighlightActiveLine(false);
|
||||||
contentBackup = value;
|
contentBackup = value;
|
||||||
setEventCallBacks();
|
setEventCallBacks();
|
||||||
if (options) {
|
if (options) {
|
||||||
@@ -392,6 +448,77 @@
|
|||||||
editor.container.addEventListener('contextmenu', handleContextMenu);
|
editor.container.addEventListener('contextmenu', handleContextMenu);
|
||||||
editor.keyBinding.addKeyboardHandler(handleKeyDown);
|
editor.keyBinding.addKeyboardHandler(handleKeyDown);
|
||||||
changedQueryParts();
|
changedQueryParts();
|
||||||
|
|
||||||
|
// editor.session.addGutterDecoration(0, 'ace-gutter-sql-run');
|
||||||
|
|
||||||
|
// editor.session.setAnnotations([
|
||||||
|
// {
|
||||||
|
// row: 1,
|
||||||
|
// text: 'SQL SCRIPT 0',
|
||||||
|
// type: 'gutter',
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// editor.getSession().setAnnotations([
|
||||||
|
// {
|
||||||
|
// row: 0,
|
||||||
|
// // column: 0,
|
||||||
|
// text: 'Error Message', // Or the Json reply from the parser
|
||||||
|
// type: 'error', // also "warning" and "information"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// row: 1,
|
||||||
|
// // column: 0,
|
||||||
|
// text: 'SELECT * FROM \n22222', // Or the Json reply from the parser
|
||||||
|
// // type: 'info', // also "warning" and "information"
|
||||||
|
// className: 'ace-gutter-sql-run',
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// editor.on('guttermousemove', e => console.log('MOVE', e.target), true);
|
||||||
|
// editor.on('guttermouseout', e => console.log('OUT', e.target), true);
|
||||||
|
// editor.on('guttermouseleave', e => console.log('LEAVE', e.target), true);
|
||||||
|
// editor.session.setBreakpoint(0);
|
||||||
|
|
||||||
|
// editor.on(
|
||||||
|
// 'gutterclick',
|
||||||
|
// e => {
|
||||||
|
// const row = e.getDocumentPosition().row;
|
||||||
|
|
||||||
|
// const part = (queryParts || []).find(part => part.trimStart.line == row);
|
||||||
|
// if (part && onExecuteFragment) {
|
||||||
|
// onExecuteFragment(part.text);
|
||||||
|
// e.stop();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// true
|
||||||
|
// );
|
||||||
|
|
||||||
|
editor.on(
|
||||||
|
'guttermousedown',
|
||||||
|
e => {
|
||||||
|
const row = e.getDocumentPosition().row;
|
||||||
|
|
||||||
|
const part = (queryParts || []).find(part => part.trimStart.line == row);
|
||||||
|
if (part && onExecuteFragment) {
|
||||||
|
onExecuteFragment(part.text, part.trimStart.line);
|
||||||
|
e.stop();
|
||||||
|
editor.moveCursorTo(part.trimStart.line, 0);
|
||||||
|
editor.selection.clearSelection();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// editor.session.gutterRenderer = {
|
||||||
|
// getWidth: function (session, lastLineNumber, config) {
|
||||||
|
// return lastLineNumber.toString().length * config.characterWidth;
|
||||||
|
// },
|
||||||
|
// getText: function (session, row) {
|
||||||
|
// return (row + 1).toString();
|
||||||
|
// // return String.fromCharCode(row + 65);
|
||||||
|
// },
|
||||||
|
// };
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -427,6 +554,7 @@
|
|||||||
editor.on('focus', () => dispatch('focus'));
|
editor.on('focus', () => dispatch('focus'));
|
||||||
|
|
||||||
editor.setReadOnly(readOnly);
|
editor.setReadOnly(readOnly);
|
||||||
|
|
||||||
editor.on('change', () => {
|
editor.on('change', () => {
|
||||||
const content = editor.getValue();
|
const content = editor.getValue();
|
||||||
value = content;
|
value = content;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
export let items: any[];
|
export let items: any[];
|
||||||
export let showProcedure = false;
|
export let showProcedure = false;
|
||||||
export let showLine = false;
|
export let showLine = false;
|
||||||
|
export let startLine = 0;
|
||||||
|
|
||||||
$: time0 = items[0] && new Date(items[0].time).getTime();
|
$: time0 = items[0] && new Date(items[0].time).getTime();
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
<td>{row.procedure || ''}</td>
|
<td>{row.procedure || ''}</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if showLine}
|
{#if showLine}
|
||||||
<td>{row.line || ''}</td>
|
<td>{row.line == null ? '' : row.line + 1 + startLine}</td>
|
||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -13,8 +13,11 @@
|
|||||||
export let eventName;
|
export let eventName;
|
||||||
export let executeNumber;
|
export let executeNumber;
|
||||||
export let showNoMessagesAlert = false;
|
export let showNoMessagesAlert = false;
|
||||||
|
export let startLine = 0;
|
||||||
|
export let onChangeErrors = null;
|
||||||
|
|
||||||
const cachedMessagesRef = createRef([]);
|
const cachedMessagesRef = createRef([]);
|
||||||
|
const lastErrorMessageCountRef = createRef(0);
|
||||||
|
|
||||||
let displayedMessages = [];
|
let displayedMessages = [];
|
||||||
|
|
||||||
@@ -44,11 +47,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (onChangeErrors) {
|
||||||
|
const errors = displayedMessages.filter(x => x.severity == 'error' && x.line != null);
|
||||||
|
if (lastErrorMessageCountRef.get() != errors.length) {
|
||||||
|
onChangeErrors(
|
||||||
|
errors.map(err => ({
|
||||||
|
...err,
|
||||||
|
line: err.line == null ? null : err.line + startLine,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
lastErrorMessageCountRef.set(errors.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: $effect;
|
$: $effect;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showNoMessagesAlert && (!displayedMessages || displayedMessages.length == 0)}
|
{#if showNoMessagesAlert && (!displayedMessages || displayedMessages.length == 0)}
|
||||||
<ErrorInfo message="No messages" icon="img alert" />
|
<ErrorInfo message="No messages" icon="img alert" />
|
||||||
{:else}
|
{:else}
|
||||||
<MessageView items={displayedMessages} on:messageclick {showProcedure} {showLine} />
|
<MessageView items={displayedMessages} on:messageclick {showProcedure} {showLine} {startLine} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
return domEditor.getEditor();
|
return domEditor.getEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentCommandText(): string {
|
export function getCurrentCommandText(): { text: string; line?: number } {
|
||||||
return domEditor.getCurrentCommandText();
|
return domEditor.getCurrentCommandText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||||||
import { addCompleter, setCompleters } from 'ace-builds/src-noconflict/ext-language_tools';
|
import { addCompleter, setCompleters } from 'ace-builds/src-noconflict/ext-language_tools';
|
||||||
import { getDatabaseInfo } from '../utility/metadataLoaders';
|
import { getDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
import analyseQuerySources from './analyseQuerySources';
|
import analyseQuerySources from './analyseQuerySources';
|
||||||
|
import { getStringSettingsValue } from '../settings/settingsTools';
|
||||||
|
|
||||||
const COMMON_KEYWORDS = [
|
const COMMON_KEYWORDS = [
|
||||||
'select',
|
'select',
|
||||||
@@ -78,13 +79,21 @@ export function mountCodeCompletion({ conid, database, editor, getText }) {
|
|||||||
const line = session.getLine(cursor.row).slice(0, cursor.column);
|
const line = session.getLine(cursor.row).slice(0, cursor.column);
|
||||||
const dbinfo = await getDatabaseInfo({ conid, database });
|
const dbinfo = await getDatabaseInfo({ conid, database });
|
||||||
|
|
||||||
let list = COMMON_KEYWORDS.map(word => ({
|
const convertUpper = getStringSettingsValue('sqlEditor.sqlCommandsCase', 'upperCase') == 'upperCase';
|
||||||
name: word,
|
|
||||||
value: word,
|
let list = COMMON_KEYWORDS.map(word => {
|
||||||
caption: word,
|
if (convertUpper) {
|
||||||
meta: 'keyword',
|
word = word.toUpperCase();
|
||||||
score: 800,
|
}
|
||||||
}));
|
|
||||||
|
return {
|
||||||
|
name: word,
|
||||||
|
value: word,
|
||||||
|
caption: word,
|
||||||
|
meta: 'keyword',
|
||||||
|
score: 800,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (dbinfo) {
|
if (dbinfo) {
|
||||||
const colMatch = line.match(/([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)?$/);
|
const colMatch = line.match(/([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)?$/);
|
||||||
|
|||||||
@@ -173,6 +173,10 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if driver?.showConnectionField('treeKeySeparator', $values)}
|
||||||
|
<FormTextField label="Key separator" name="treeKeySeparator" disabled={isConnected} placeholder=":" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if driver?.showConnectionField('windowsDomain', $values)}
|
{#if driver?.showConnectionField('windowsDomain', $values)}
|
||||||
<FormTextField label="Domain (specify to use NTLM authentication)" name="windowsDomain" disabled={isConnected} />
|
<FormTextField label="Domain (specify to use NTLM authentication)" name="windowsDomain" disabled={isConnected} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -111,6 +111,19 @@ ORDER BY
|
|||||||
defaultValue="30"
|
defaultValue="30"
|
||||||
disabled={values['connection.autoRefresh'] === false}
|
disabled={values['connection.autoRefresh'] === false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div class="heading">SQL editor</div>
|
||||||
|
<FormSelectField
|
||||||
|
label="SQL commands case"
|
||||||
|
name="sqlEditor.sqlCommandsCase"
|
||||||
|
isNative
|
||||||
|
defaultValue="upperCase"
|
||||||
|
options={[
|
||||||
|
{ value: 'upperCase', label: 'UPPER CASE' },
|
||||||
|
{ value: 'lowerCase', label: 'lower case' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="2">
|
<svelte:fragment slot="2">
|
||||||
<div class="heading">Application theme</div>
|
<div class="heading">Application theme</div>
|
||||||
|
|||||||
@@ -21,3 +21,10 @@ export function getBoolSettingsValue(name, defaultValue) {
|
|||||||
if (res == null) return defaultValue;
|
if (res == null) return defaultValue;
|
||||||
return !!res;
|
return !!res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStringSettingsValue(name, defaultValue) {
|
||||||
|
const settings = getCurrentSettings();
|
||||||
|
const res = settings[name];
|
||||||
|
if (res == null) return defaultValue;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
import useEditorData from '../query/useEditorData';
|
import useEditorData from '../query/useEditorData';
|
||||||
import { extensions } from '../stores';
|
import { extensions } from '../stores';
|
||||||
import applyScriptTemplate from '../utility/applyScriptTemplate';
|
import applyScriptTemplate from '../utility/applyScriptTemplate';
|
||||||
import { changeTab } from '../utility/common';
|
import { changeTab, markTabUnsaved } from '../utility/common';
|
||||||
import { getDatabaseInfo, useConnectionInfo } from '../utility/metadataLoaders';
|
import { getDatabaseInfo, useConnectionInfo } from '../utility/metadataLoaders';
|
||||||
import SocketMessageView from '../query/SocketMessageView.svelte';
|
import SocketMessageView from '../query/SocketMessageView.svelte';
|
||||||
import useEffect from '../utility/useEffect';
|
import useEffect from '../utility/useEffect';
|
||||||
@@ -86,10 +86,11 @@
|
|||||||
|
|
||||||
let busy = false;
|
let busy = false;
|
||||||
let executeNumber = 0;
|
let executeNumber = 0;
|
||||||
|
let executeStartLine = 0;
|
||||||
let visibleResultTabs = false;
|
let visibleResultTabs = false;
|
||||||
let sessionId = null;
|
let sessionId = null;
|
||||||
let resultCount;
|
let resultCount;
|
||||||
|
let errorMessages;
|
||||||
let domEditor;
|
let domEditor;
|
||||||
|
|
||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
@@ -143,13 +144,14 @@
|
|||||||
return !!conid && (!$connection?.isReadOnly || driver?.readOnlySessions);
|
return !!conid && (!$connection?.isReadOnly || driver?.readOnlySessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeCore(sql) {
|
async function executeCore(sql, startLine = 0) {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
if (!sql || !sql.trim()) {
|
if (!sql || !sql.trim()) {
|
||||||
showSnackbarError('Skipped executing empty query');
|
showSnackbarError('Skipped executing empty query');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executeStartLine = startLine;
|
||||||
executeNumber++;
|
executeNumber++;
|
||||||
visibleResultTabs = true;
|
visibleResultTabs = true;
|
||||||
|
|
||||||
@@ -179,13 +181,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function executeCurrent() {
|
export async function executeCurrent() {
|
||||||
const sql = domEditor.getCurrentCommandText();
|
const cmd = domEditor.getCurrentCommandText();
|
||||||
await executeCore(sql);
|
await executeCore(cmd.text, cmd.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function execute() {
|
export async function execute() {
|
||||||
const selectedText = domEditor.getEditor().getSelectedText();
|
const selectedText = domEditor.getEditor().getSelectedText();
|
||||||
await executeCore(selectedText || $editorValue);
|
const startLine = domEditor.getEditor().getSelectionRange().start.row;
|
||||||
|
await executeCore(selectedText || $editorValue, selectedText ? startLine : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function kill() {
|
export async function kill() {
|
||||||
@@ -257,6 +260,10 @@
|
|||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleChangeErrors(errors) {
|
||||||
|
errorMessages = errors;
|
||||||
|
}
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
return [
|
return [
|
||||||
{ command: 'query.execute' },
|
{ command: 'query.execute' },
|
||||||
@@ -276,6 +283,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const quickExportHandlerRef = createQuickExportHandlerRef();
|
const quickExportHandlerRef = createQuickExportHandlerRef();
|
||||||
|
|
||||||
|
let isInitialized = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
@@ -286,21 +295,32 @@
|
|||||||
engine={$connection && $connection.engine}
|
engine={$connection && $connection.engine}
|
||||||
{conid}
|
{conid}
|
||||||
{database}
|
{database}
|
||||||
splitterOptions={driver?.getQuerySplitterOptions('script')}
|
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||||
value={$editorState.value || ''}
|
value={$editorState.value || ''}
|
||||||
menu={createMenu()}
|
menu={createMenu()}
|
||||||
on:input={e => setEditorData(e.detail)}
|
on:input={e => {
|
||||||
|
setEditorData(e.detail);
|
||||||
|
if (isInitialized) {
|
||||||
|
markTabUnsaved(tabid);
|
||||||
|
}
|
||||||
|
errorMessages = [];
|
||||||
|
}}
|
||||||
on:focus={() => {
|
on:focus={() => {
|
||||||
activator.activate();
|
activator.activate();
|
||||||
invalidateCommands();
|
invalidateCommands();
|
||||||
|
setTimeout(() => {
|
||||||
|
isInitialized = true;
|
||||||
|
}, 100);
|
||||||
}}
|
}}
|
||||||
bind:this={domEditor}
|
bind:this={domEditor}
|
||||||
|
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
||||||
|
{errorMessages}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode={driver?.editorMode || 'text'}
|
mode={driver?.editorMode || 'text'}
|
||||||
value={$editorState.value || ''}
|
value={$editorState.value || ''}
|
||||||
splitterOptions={driver?.getQuerySplitterOptions('script')}
|
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||||
menu={createMenu()}
|
menu={createMenu()}
|
||||||
on:input={e => setEditorData(e.detail)}
|
on:input={e => setEditorData(e.detail)}
|
||||||
on:focus={() => {
|
on:focus={() => {
|
||||||
@@ -318,8 +338,10 @@
|
|||||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||||
on:messageClick={handleMesageClick}
|
on:messageClick={handleMesageClick}
|
||||||
{executeNumber}
|
{executeNumber}
|
||||||
|
startLine={executeStartLine}
|
||||||
showProcedure
|
showProcedure
|
||||||
showLine
|
showLine
|
||||||
|
onChangeErrors={handleChangeErrors}
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ResultTabs>
|
</ResultTabs>
|
||||||
|
|||||||
8
packages/web/src/utility/SettingsListener.svelte
Normal file
8
packages/web/src/utility/SettingsListener.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { SqlDumper } from 'dbgate-tools';
|
||||||
|
import { useSettings } from './metadataLoaders';
|
||||||
|
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
|
$: SqlDumper.keywordsCase = $settings?.['sqlEditor.sqlCommandsCase'] || 'upperCase';
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { openedTabs } from '../stores';
|
import { getOpenedTabs, openedTabs } from '../stores';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
|
|
||||||
@@ -18,6 +18,16 @@ export function changeTab(tabid, changeFunc) {
|
|||||||
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? changeFunc(tab) : tab)));
|
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? changeFunc(tab) : tab)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function markTabUnsaved(tabid) {
|
||||||
|
const tab = getOpenedTabs().find(x => x.tabid == tabid);
|
||||||
|
if (tab.unsaved) return;
|
||||||
|
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: true } : tab)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markTabSaved(tabid) {
|
||||||
|
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: false } : tab)));
|
||||||
|
}
|
||||||
|
|
||||||
export function setSelectedTabFunc(files, tabid) {
|
export function setSelectedTabFunc(files, tabid) {
|
||||||
return [
|
return [
|
||||||
...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })),
|
...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { derived, get } from 'svelte/store';
|
import { derived, get } from 'svelte/store';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
import { openedTabs } from '../stores';
|
import { openedTabs } from '../stores';
|
||||||
import { changeTab } from './common';
|
import { changeTab, markTabSaved } from './common';
|
||||||
import SaveFileModal from '../modals/SaveFileModal.svelte';
|
import SaveFileModal from '../modals/SaveFileModal.svelte';
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
import { apiCall } from './api';
|
import { apiCall } from './api';
|
||||||
@@ -24,12 +24,14 @@ export default async function saveTabFile(editor, saveMode, folder, format, file
|
|||||||
if (savedFilePath) {
|
if (savedFilePath) {
|
||||||
await apiCall('files/save-as', { filePath: savedFilePath, data, format });
|
await apiCall('files/save-as', { filePath: savedFilePath, data, format });
|
||||||
}
|
}
|
||||||
|
markTabSaved(tabid);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSave = (title, newProps) => {
|
const onSave = (title, newProps) => {
|
||||||
changeTab(tabid, tab => ({
|
changeTab(tabid, tab => ({
|
||||||
...tab,
|
...tab,
|
||||||
title,
|
title,
|
||||||
|
unsaved: false,
|
||||||
props: {
|
props: {
|
||||||
...tab.props,
|
...tab.props,
|
||||||
savedFormat: format,
|
savedFormat: format,
|
||||||
|
|||||||
125
packages/web/src/utility/usePerspectiveDataPatterns.ts
Normal file
125
packages/web/src/utility/usePerspectiveDataPatterns.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import {
|
||||||
|
analyseDataPattern,
|
||||||
|
MultipleDatabaseInfo,
|
||||||
|
PerspectiveCache,
|
||||||
|
PerspectiveConfig,
|
||||||
|
PerspectiveDatabaseConfig,
|
||||||
|
PerspectiveDataLoadProps,
|
||||||
|
PerspectiveDataPattern,
|
||||||
|
PerspectiveDataPatternDict,
|
||||||
|
} from 'dbgate-datalib';
|
||||||
|
import { PerspectiveDataLoader } from 'dbgate-datalib/lib/PerspectiveDataLoader';
|
||||||
|
import { writable, Readable } from 'svelte/store';
|
||||||
|
|
||||||
|
export function getPerspectiveDataPatternsFromCache(
|
||||||
|
databaseConfig: PerspectiveDatabaseConfig,
|
||||||
|
config: PerspectiveConfig,
|
||||||
|
cache: PerspectiveCache,
|
||||||
|
dbInfos: MultipleDatabaseInfo
|
||||||
|
): PerspectiveDataPatternDict {
|
||||||
|
const res = {};
|
||||||
|
|
||||||
|
for (const node of config.nodes) {
|
||||||
|
const conid = node.conid || databaseConfig.conid;
|
||||||
|
const database = node.database || databaseConfig.database;
|
||||||
|
const { schemaName, pureName } = node;
|
||||||
|
|
||||||
|
const cached = cache.dataPatterns.find(
|
||||||
|
x => x.conid == conid && x.database == database && x.schemaName == schemaName && x.pureName == pureName
|
||||||
|
);
|
||||||
|
if (cached) {
|
||||||
|
res[node.designerId] = cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPerspectiveDataPatterns(
|
||||||
|
databaseConfig: PerspectiveDatabaseConfig,
|
||||||
|
config: PerspectiveConfig,
|
||||||
|
cache: PerspectiveCache,
|
||||||
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataLoader: PerspectiveDataLoader
|
||||||
|
): Promise<PerspectiveDataPatternDict> {
|
||||||
|
const res = {};
|
||||||
|
|
||||||
|
for (const node of config.nodes) {
|
||||||
|
const conid = node.conid || databaseConfig.conid;
|
||||||
|
const database = node.database || databaseConfig.database;
|
||||||
|
const { schemaName, pureName } = node;
|
||||||
|
|
||||||
|
const cached = cache.dataPatterns.find(
|
||||||
|
x => x.conid == conid && x.database == database && x.schemaName == schemaName && x.pureName == pureName
|
||||||
|
);
|
||||||
|
if (cached) {
|
||||||
|
res[node.designerId] = cached;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = dbInfos?.[conid]?.[database];
|
||||||
|
|
||||||
|
if (!db) continue;
|
||||||
|
|
||||||
|
const table = db.tables?.find(x => x.pureName == pureName && x.schemaName == schemaName);
|
||||||
|
const view = db.views?.find(x => x.pureName == pureName && x.schemaName == schemaName);
|
||||||
|
const collection = db.collections?.find(x => x.pureName == pureName && x.schemaName == schemaName);
|
||||||
|
if (!table && !view && !collection) continue;
|
||||||
|
|
||||||
|
// console.log('LOAD PATTERN FOR', pureName);
|
||||||
|
|
||||||
|
const props: PerspectiveDataLoadProps = {
|
||||||
|
databaseConfig: { conid, database },
|
||||||
|
engineType: collection ? 'docdb' : 'sqldb',
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
orderBy: table?.primaryKey
|
||||||
|
? table?.primaryKey.columns.map(x => ({ columnName: x.columnName, order: 'ASC' }))
|
||||||
|
: table || view
|
||||||
|
? [{ columnName: (table || view).columns[0].columnName, order: 'ASC' }]
|
||||||
|
: null,
|
||||||
|
range: {
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// console.log('LOAD PROPS', props);
|
||||||
|
const rows = await dataLoader.loadData(props);
|
||||||
|
|
||||||
|
if (rows.errorMessage) {
|
||||||
|
console.error('Error loading pattern for', pureName, ':', rows.errorMessage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('PATTERN ROWS', rows);
|
||||||
|
|
||||||
|
const pattern = analyseDataPattern(
|
||||||
|
{
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
pureName,
|
||||||
|
schemaName,
|
||||||
|
},
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.dataPatterns.push(pattern);
|
||||||
|
res[node.designerId] = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePerspectiveDataPatterns(
|
||||||
|
databaseConfig: PerspectiveDatabaseConfig,
|
||||||
|
config: PerspectiveConfig,
|
||||||
|
cache: PerspectiveCache,
|
||||||
|
dbInfos: MultipleDatabaseInfo,
|
||||||
|
dataLoader: PerspectiveDataLoader
|
||||||
|
): Readable<PerspectiveDataPatternDict> {
|
||||||
|
const cached = getPerspectiveDataPatternsFromCache(databaseConfig, config, cache, dbInfos);
|
||||||
|
const promise = getPerspectiveDataPatterns(databaseConfig, config, cache, dbInfos, dataLoader);
|
||||||
|
const res = writable(cached);
|
||||||
|
promise.then(value => res.set(value));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
const closeTabFunc = closeCondition => tabid => {
|
function allowCloseTabs(tabs) {
|
||||||
|
if (tabs.length == 0) return Promise.resolve(true);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
showModal(CloseTabModal, {
|
||||||
|
onCancel: () => resolve(false),
|
||||||
|
onConfirm: () => resolve(true),
|
||||||
|
tabs,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeTabFunc = closeCondition => async tabid => {
|
||||||
|
const activeCandidate = getOpenedTabs().find(x => x.tabid == tabid);
|
||||||
|
const closeCandidates = getOpenedTabs()
|
||||||
|
.filter(x => closeCondition(x, activeCandidate))
|
||||||
|
.filter(x => x.unsaved && x.closedTime == null);
|
||||||
|
|
||||||
|
if (!(await allowCloseTabs(closeCandidates))) return;
|
||||||
|
|
||||||
openedTabs.update(files => {
|
openedTabs.update(files => {
|
||||||
const active = files.find(x => x.tabid == tabid);
|
const active = files.find(x => x.tabid == tabid);
|
||||||
if (!active) return files;
|
if (!active) return files;
|
||||||
@@ -22,7 +40,13 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const closeMultipleTabs = (closeCondition, deleteFromHistory = false) => {
|
export const closeMultipleTabs = async (closeCondition, deleteFromHistory = false) => {
|
||||||
|
const closeCandidates = getOpenedTabs()
|
||||||
|
.filter(x => closeCondition(x))
|
||||||
|
.filter(x => x.unsaved && x.closedTime == null);
|
||||||
|
|
||||||
|
if (!(await allowCloseTabs(closeCandidates))) return;
|
||||||
|
|
||||||
openedTabs.update(files => {
|
openedTabs.update(files => {
|
||||||
const newFiles = deleteFromHistory
|
const newFiles = deleteFromHistory
|
||||||
? files.filter(x => !closeCondition(x))
|
? files.filter(x => !closeCondition(x))
|
||||||
@@ -45,7 +69,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeTab = closeTabFunc((x, active) => x.tabid == active.tabid);
|
const closeTab = closeTabFunc((x, active) => x.tabid == active.tabid);
|
||||||
const closeAll = () => {
|
const closeAll = async () => {
|
||||||
|
const closeCandidates = getOpenedTabs().filter(x => x.unsaved && x.closedTime == null);
|
||||||
|
|
||||||
|
if (!(await allowCloseTabs(closeCandidates))) return;
|
||||||
|
|
||||||
const closedTime = new Date().getTime();
|
const closedTime = new Date().getTime();
|
||||||
openedTabs.update(tabs =>
|
openedTabs.update(tabs =>
|
||||||
tabs.map(tab => ({
|
tabs.map(tab => ({
|
||||||
@@ -212,6 +240,8 @@
|
|||||||
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
||||||
import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab';
|
import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab';
|
||||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||||
|
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
||||||
|
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
||||||
|
|
||||||
$: connectionList = useConnectionList();
|
$: connectionList = useConnectionList();
|
||||||
|
|
||||||
@@ -434,7 +464,6 @@
|
|||||||
<FontIcon icon="icon lock" />
|
<FontIcon icon="icon lock" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="close-button-right tabCloseButton"
|
class="close-button-right tabCloseButton"
|
||||||
on:click={e => closeMultipleTabs(tab => tabGroup.tabs.find(x => x.tabid == tab.tabid))}
|
on:click={e => closeMultipleTabs(tab => tabGroup.tabs.find(x => x.tabid == tab.tabid))}
|
||||||
@@ -479,9 +508,7 @@
|
|||||||
<span class="file-name">
|
<span class="file-name">
|
||||||
{tab.title}
|
{tab.title}
|
||||||
</span>
|
</span>
|
||||||
<span class="close-button tabCloseButton" on:click={e => closeTab(tab.tabid)}>
|
<TabCloseButton unsaved={tab.unsaved} on:click={e => closeTab(tab.tabid)} />
|
||||||
<FontIcon icon="icon close" />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -582,19 +609,12 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
.close-button {
|
|
||||||
margin-left: 5px;
|
|
||||||
color: var(--theme-font-3);
|
|
||||||
}
|
|
||||||
.close-button-right {
|
.close-button-right {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
color: var(--theme-font-3);
|
color: var(--theme-font-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button:hover {
|
|
||||||
color: var(--theme-font-1);
|
|
||||||
}
|
|
||||||
.close-button-right:hover {
|
.close-button-right:hover {
|
||||||
color: var(--theme-font-1);
|
color: var(--theme-font-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^4.42.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ const AbstractCursor = require('mongodb').AbstractCursor;
|
|||||||
const createBulkInsertStream = require('./createBulkInsertStream');
|
const createBulkInsertStream = require('./createBulkInsertStream');
|
||||||
|
|
||||||
function transformMongoData(row) {
|
function transformMongoData(row) {
|
||||||
return _.mapValues(row, (v) => (v && v.constructor == ObjectId ? { $oid: v.toString() } : v));
|
return _.cloneDeepWith(row, (x) => {
|
||||||
|
if (x && x.constructor == ObjectId) return { $oid: x.toString() };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readCursor(cursor, options) {
|
async function readCursor(cursor, options) {
|
||||||
@@ -207,6 +209,10 @@ const driver = {
|
|||||||
if (options.countDocuments) {
|
if (options.countDocuments) {
|
||||||
const count = await collection.countDocuments(convertObjectId(options.condition) || {});
|
const count = await collection.countDocuments(convertObjectId(options.condition) || {});
|
||||||
return { count };
|
return { count };
|
||||||
|
} else if (options.aggregate) {
|
||||||
|
let cursor = await collection.aggregate(options.aggregate);
|
||||||
|
const rows = await cursor.toArray();
|
||||||
|
return { rows: rows.map(transformMongoData) };
|
||||||
} else {
|
} else {
|
||||||
// console.log('options.condition', JSON.stringify(options.condition, undefined, 2));
|
// console.log('options.condition', JSON.stringify(options.condition, undefined, 2));
|
||||||
let cursor = await collection.find(convertObjectId(options.condition) || {});
|
let cursor = await collection.find(convertObjectId(options.condition) || {});
|
||||||
@@ -276,6 +282,11 @@ const driver = {
|
|||||||
await db.createCollection('collection1');
|
await db.createCollection('collection1');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async dropDatabase(pool, name) {
|
||||||
|
const db = pool.db(name);
|
||||||
|
await db.dropDatabase();
|
||||||
|
},
|
||||||
|
|
||||||
async loadFieldValues(pool, name, field, search) {
|
async loadFieldValues(pool, name, field, search) {
|
||||||
try {
|
try {
|
||||||
const collection = pool.__getDatabase().collection(name.pureName);
|
const collection = pool.__getDatabase().collection(name.pureName);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^4.42.0",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ async function tediousStream(pool, sql, options) {
|
|||||||
const { message, lineNumber, procName } = info;
|
const { message, lineNumber, procName } = info;
|
||||||
options.info({
|
options.info({
|
||||||
message,
|
message,
|
||||||
line: lineNumber,
|
line: lineNumber != null && lineNumber > 0 ? lineNumber - 1 : lineNumber,
|
||||||
procedure: procName,
|
procedure: procName,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
@@ -144,7 +144,7 @@ async function tediousStream(pool, sql, options) {
|
|||||||
const { message, lineNumber, procName } = error;
|
const { message, lineNumber, procName } = error;
|
||||||
options.info({
|
options.info({
|
||||||
message,
|
message,
|
||||||
line: lineNumber,
|
line: lineNumber != null && lineNumber > 0 ? lineNumber - 1 : lineNumber,
|
||||||
procedure: procName,
|
procedure: procName,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ class MsSqlDumper extends SqlDumper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropDatabase(name) {
|
||||||
|
this.putCmd(
|
||||||
|
`USE master;
|
||||||
|
ALTER DATABASE %i SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
|
||||||
|
DROP DATABASE %i`,
|
||||||
|
name,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
autoIncrement() {
|
autoIncrement() {
|
||||||
this.put(' ^identity');
|
this.put(' ^identity');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const dialect = {
|
|||||||
rangeSelect: true,
|
rangeSelect: true,
|
||||||
offsetFetchRangeSyntax: true,
|
offsetFetchRangeSyntax: true,
|
||||||
rowNumberOverPaging: true,
|
rowNumberOverPaging: true,
|
||||||
|
defaultSchemaName: 'dbo',
|
||||||
stringEscapeChar: "'",
|
stringEscapeChar: "'",
|
||||||
fallbackDataType: 'nvarchar(max)',
|
fallbackDataType: 'nvarchar(max)',
|
||||||
explicitDropConstraint: false,
|
explicitDropConstraint: false,
|
||||||
@@ -130,7 +131,10 @@ const driver = {
|
|||||||
(field == 'trustServerCertificate' && values.authType != 'sql' && values.authType != 'sspi') ||
|
(field == 'trustServerCertificate' && values.authType != 'sql' && values.authType != 'sspi') ||
|
||||||
(field == 'windowsDomain' && values.authType != 'sql' && values.authType != 'sspi'),
|
(field == 'windowsDomain' && values.authType != 'sql' && values.authType != 'sspi'),
|
||||||
// (field == 'useDatabaseUrl' && values.authType != 'sql' && values.authType != 'sspi')
|
// (field == 'useDatabaseUrl' && values.authType != 'sql' && values.authType != 'sspi')
|
||||||
getQuerySplitterOptions: () => mssqlSplitterOptions,
|
getQuerySplitterOptions: usage =>
|
||||||
|
usage == 'editor'
|
||||||
|
? { ...mssqlSplitterOptions, adaptiveGoSplit: true, ignoreComments: true, preventSingleLineSplit: true }
|
||||||
|
: mssqlSplitterOptions,
|
||||||
|
|
||||||
engine: 'mssql@dbgate-plugin-mssql',
|
engine: 'mssql@dbgate-plugin-mssql',
|
||||||
title: 'Microsoft SQL Server',
|
title: 'Microsoft SQL Server',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"antares-mysql-dumper": "^0.0.1",
|
"antares-mysql-dumper": "^0.0.1",
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^4.42.0",
|
||||||
|
|||||||
@@ -112,11 +112,10 @@ const drivers = driverBases.map(driverBase => ({
|
|||||||
|
|
||||||
const handleError = error => {
|
const handleError = error => {
|
||||||
console.log('ERROR', error);
|
console.log('ERROR', error);
|
||||||
const { message, lineNumber, procName } = error;
|
const { message } = error;
|
||||||
options.info({
|
options.info({
|
||||||
message,
|
message,
|
||||||
line: lineNumber,
|
line: 0,
|
||||||
procedure: procName,
|
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,7 +108,11 @@ const mysqlDriverBase = {
|
|||||||
dumperClass: Dumper,
|
dumperClass: Dumper,
|
||||||
dialect,
|
dialect,
|
||||||
defaultPort: 3306,
|
defaultPort: 3306,
|
||||||
getQuerySplitterOptions: () => mysqlSplitterOptions,
|
getQuerySplitterOptions: usage =>
|
||||||
|
usage == 'editor'
|
||||||
|
? { ...mysqlSplitterOptions, ignoreComments: true, preventSingleLineSplit: true }
|
||||||
|
: mysqlSplitterOptions,
|
||||||
|
|
||||||
readOnlySessions: true,
|
readOnlySessions: true,
|
||||||
supportsDatabaseDump: true,
|
supportsDatabaseDump: true,
|
||||||
authTypeLabel: 'Connection mode',
|
authTypeLabel: 'Connection mode',
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
|
|||||||
@@ -147,10 +147,14 @@ const drivers = driverBases.map(driverBase => ({
|
|||||||
|
|
||||||
query.on('error', error => {
|
query.on('error', error => {
|
||||||
console.log('ERROR', error);
|
console.log('ERROR', error);
|
||||||
const { message, lineNumber, procName } = error;
|
const { message, position, procName } = error;
|
||||||
|
let line = null;
|
||||||
|
if (position) {
|
||||||
|
line = sql.substring(0, parseInt(position)).replace(/[^\n]/g, '').length;
|
||||||
|
}
|
||||||
options.info({
|
options.info({
|
||||||
message,
|
message,
|
||||||
line: lineNumber,
|
line,
|
||||||
procedure: procName,
|
procedure: procName,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ class Dumper extends SqlDumper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropDatabase(name) {
|
||||||
|
this.putCmd('^drop ^database %i ^with(^force)', name);
|
||||||
|
}
|
||||||
|
|
||||||
dropRecreatedTempTable(tmptable) {
|
dropRecreatedTempTable(tmptable) {
|
||||||
this.putCmd('^drop ^table %i ^cascade', tmptable);
|
this.putCmd('^drop ^table %i ^cascade', tmptable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const spatialTypes = ['GEOGRAPHY'];
|
|||||||
const dialect = {
|
const dialect = {
|
||||||
rangeSelect: true,
|
rangeSelect: true,
|
||||||
ilike: true,
|
ilike: true,
|
||||||
|
defaultSchemaName: 'public',
|
||||||
// stringEscapeChar: '\\',
|
// stringEscapeChar: '\\',
|
||||||
stringEscapeChar: "'",
|
stringEscapeChar: "'",
|
||||||
fallbackDataType: 'varchar',
|
fallbackDataType: 'varchar',
|
||||||
@@ -105,7 +106,10 @@ const postgresDriverBase = {
|
|||||||
dialect,
|
dialect,
|
||||||
// showConnectionField: (field, values) =>
|
// showConnectionField: (field, values) =>
|
||||||
// ['server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase'].includes(field),
|
// ['server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase'].includes(field),
|
||||||
getQuerySplitterOptions: () => postgreSplitterOptions,
|
getQuerySplitterOptions: usage =>
|
||||||
|
usage == 'editor'
|
||||||
|
? { ...postgreSplitterOptions, ignoreComments: true, preventSingleLineSplit: true }
|
||||||
|
: postgreSplitterOptions,
|
||||||
readOnlySessions: true,
|
readOnlySessions: true,
|
||||||
|
|
||||||
databaseUrlPlaceholder: 'e.g. postgresql://user:password@localhost:5432/default_database',
|
databaseUrlPlaceholder: 'e.g. postgresql://user:password@localhost:5432/default_database',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-plugin-tools": "^1.0.7",
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^4.42.0",
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function splitCommandLine(str) {
|
|||||||
const driver = {
|
const driver = {
|
||||||
...driverBase,
|
...driverBase,
|
||||||
analyserClass: Analyser,
|
analyserClass: Analyser,
|
||||||
async connect({ server, port, password, database, useDatabaseUrl, databaseUrl }) {
|
async connect({ server, port, password, database, useDatabaseUrl, databaseUrl, treeKeySeparator }) {
|
||||||
let db = 0;
|
let db = 0;
|
||||||
let pool;
|
let pool;
|
||||||
if (useDatabaseUrl) {
|
if (useDatabaseUrl) {
|
||||||
@@ -95,6 +95,7 @@ const driver = {
|
|||||||
password,
|
password,
|
||||||
db,
|
db,
|
||||||
});
|
});
|
||||||
|
pool.__treeKeySeparator = treeKeySeparator || ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
return pool;
|
return pool;
|
||||||
@@ -164,9 +165,9 @@ const driver = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async loadKeys(pool, root = '', filter = null) {
|
async loadKeys(pool, root = '', filter = null) {
|
||||||
const keys = await this.getKeys(pool, root ? `${root}:*` : '*');
|
const keys = await this.getKeys(pool, root ? `${root}${pool.__treeKeySeparator}*` : '*');
|
||||||
const keysFiltered = keys.filter((x) => filterName(filter, x));
|
const keysFiltered = keys.filter((x) => filterName(filter, x));
|
||||||
const res = this.extractKeysFromLevel(root, keysFiltered);
|
const res = this.extractKeysFromLevel(pool, root, keysFiltered);
|
||||||
await this.enrichKeyInfo(pool, res);
|
await this.enrichKeyInfo(pool, res);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
@@ -196,13 +197,13 @@ const driver = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
extractKeysFromLevel(root, keys) {
|
extractKeysFromLevel(pool, root, keys) {
|
||||||
const prefix = root ? `${root}:` : '';
|
const prefix = root ? `${root}${pool.__treeKeySeparator}` : '';
|
||||||
const rootSplit = _.compact(root.split(':'));
|
const rootSplit = _.compact(root.split(pool.__treeKeySeparator));
|
||||||
const res = {};
|
const res = {};
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (!key.startsWith(prefix)) continue;
|
if (!key.startsWith(prefix)) continue;
|
||||||
const keySplit = key.split(':');
|
const keySplit = key.split(pool.__treeKeySeparator);
|
||||||
if (keySplit.length > rootSplit.length) {
|
if (keySplit.length > rootSplit.length) {
|
||||||
const text = keySplit[rootSplit.length];
|
const text = keySplit[rootSplit.length];
|
||||||
if (keySplit.length == rootSplit.length + 1) {
|
if (keySplit.length == rootSplit.length + 1) {
|
||||||
@@ -216,9 +217,9 @@ const driver = {
|
|||||||
res[dctKey].count++;
|
res[dctKey].count++;
|
||||||
} else {
|
} else {
|
||||||
res[dctKey] = {
|
res[dctKey] = {
|
||||||
text: text + ':*',
|
text: text + pool.__treeKeySeparator + '*',
|
||||||
type: 'dir',
|
type: 'dir',
|
||||||
root: keySplit.slice(0, rootSplit.length + 1).join(':'),
|
root: keySplit.slice(0, rootSplit.length + 1).join(pool.__treeKeySeparator),
|
||||||
count: 1,
|
count: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ const driver = {
|
|||||||
showConnectionField: (field, values) => {
|
showConnectionField: (field, values) => {
|
||||||
if (field == 'useDatabaseUrl') return true;
|
if (field == 'useDatabaseUrl') return true;
|
||||||
if (values.useDatabaseUrl) {
|
if (values.useDatabaseUrl) {
|
||||||
return ['databaseUrl', 'isReadOnly'].includes(field);
|
return ['databaseUrl', 'isReadOnly', 'treeKeySeparator'].includes(field);
|
||||||
}
|
}
|
||||||
return ['server', 'port', 'password', 'isReadOnly'].includes(field);
|
return ['server', 'port', 'password', 'isReadOnly', 'treeKeySeparator'].includes(field);
|
||||||
},
|
},
|
||||||
|
|
||||||
showConnectionTab: (field) => field == 'sshTunnel',
|
showConnectionTab: (field) => field == 'sshTunnel',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dbgate-tools": "^5.0.0-alpha.1",
|
"dbgate-tools": "^5.0.0-alpha.1",
|
||||||
"dbgate-plugin-tools": "^1.0.4",
|
"dbgate-plugin-tools": "^1.0.4",
|
||||||
"dbgate-query-splitter": "^4.9.0",
|
"dbgate-query-splitter": "^4.9.2",
|
||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
"webpack": "^4.42.0",
|
"webpack": "^4.42.0",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ const driver = {
|
|||||||
inTransaction();
|
inTransaction();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('ERROR', error);
|
console.log('ERROR', error);
|
||||||
const { message, lineNumber, procName } = error;
|
const { message, procName } = error;
|
||||||
options.info({
|
options.info({
|
||||||
message,
|
message,
|
||||||
line: lineNumber,
|
line: 0,
|
||||||
procedure: procName,
|
procedure: procName,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
|
|||||||
@@ -50,7 +50,14 @@ const driver = {
|
|||||||
singleDatabase: true,
|
singleDatabase: true,
|
||||||
defaultDatabase: getDatabaseFileLabel(connection.databaseFile),
|
defaultDatabase: getDatabaseFileLabel(connection.databaseFile),
|
||||||
}),
|
}),
|
||||||
getQuerySplitterOptions: (usage) => (usage == 'stream' ? noSplitSplitterOptions : sqliteSplitterOptions),
|
|
||||||
|
getQuerySplitterOptions: (usage) =>
|
||||||
|
usage == 'editor'
|
||||||
|
? { ...sqliteSplitterOptions, ignoreComments: true, preventSingleLineSplit: true }
|
||||||
|
: usage == 'stream'
|
||||||
|
? noSplitSplitterOptions
|
||||||
|
: sqliteSplitterOptions,
|
||||||
|
|
||||||
// isFileDatabase: true,
|
// isFileDatabase: true,
|
||||||
isElectronOnly: true,
|
isElectronOnly: true,
|
||||||
|
|
||||||
|
|||||||
@@ -3309,10 +3309,10 @@ dbgate-plugin-xml@^5.0.0-alpha.1:
|
|||||||
resolved "https://registry.yarnpkg.com/dbgate-plugin-xml/-/dbgate-plugin-xml-5.0.9.tgz#c3abf6ed8cd1450c45058d35c9326458833ed27e"
|
resolved "https://registry.yarnpkg.com/dbgate-plugin-xml/-/dbgate-plugin-xml-5.0.9.tgz#c3abf6ed8cd1450c45058d35c9326458833ed27e"
|
||||||
integrity sha512-P8Em1A6HhF0BfxEDDEUyzdgFeJHEC5vbg12frANpWHjO3V1HGdygsT2z1ukLK8FS5BLW/vcCdOFldXZGh+wWvg==
|
integrity sha512-P8Em1A6HhF0BfxEDDEUyzdgFeJHEC5vbg12frANpWHjO3V1HGdygsT2z1ukLK8FS5BLW/vcCdOFldXZGh+wWvg==
|
||||||
|
|
||||||
dbgate-query-splitter@^4.9.0:
|
dbgate-query-splitter@^4.9.2:
|
||||||
version "4.9.0"
|
version "4.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/dbgate-query-splitter/-/dbgate-query-splitter-4.9.0.tgz#37475929b76ebe60436fcc44f223d4d47d6483af"
|
resolved "https://registry.yarnpkg.com/dbgate-query-splitter/-/dbgate-query-splitter-4.9.2.tgz#ab1a60e60887ca750dd263a59db66e82c6461a46"
|
||||||
integrity sha512-POifNiMDkeksA9YXaC82u5O6krYC21xyROoNjDh3ouKI4xeB37DG+cP/D4IdICWHYZudlgKiziQ4v3W+5+O1DA==
|
integrity sha512-MwZzNNLILdUv8rg6mFysLizIEdZsLLHEOL4lAHrvLPtHaLOAb275ogtgieLqjcnsXkPlV03i2t1b697aQYdfLQ==
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user