diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16fe37753..f78fe074f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,13 @@ Builds:
- linux - application for linux
- win - application for Windows
+### 5.0.6
+- ADDED: Search in columns
+- CHANGED: Upgraded mongodb driver
+- ADDED: Ability to reset view, when data load fails
+- FIXED: Filtering works for complex types (geography, xml under MSSQL)
+- FIXED: Fixed some NPM package problems
+
### 5.0.5
- ADDED: Visualisation geographics objects on map #288
- ADDED: Support for native SQL as default value inside yaml files #296
diff --git a/package.json b/package.json
index a18ca090c..42c9040dc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "5.0.6-beta.6",
+ "version": "5.0.7-beta.4",
"name": "dbgate-all",
"workspaces": [
"packages/*",
@@ -10,6 +10,10 @@
"scripts": {
"start:api": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start",
+ "start:api:debug": "cross-env DEBUG=* yarn workspace dbgate-api start",
+ "start:app:debug": "cd app && cross-env DEBUG=* yarn start",
+ "start:api:debug:ssh": "cross-env DEBUG=ssh yarn workspace dbgate-api start",
+ "start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
"start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
"start:web": "yarn workspace dbgate-web dev",
diff --git a/packages/api/package.json b/packages/api/package.json
index e084d3497..25cd1c693 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -28,6 +28,7 @@
"dbgate-query-splitter": "^4.9.0",
"dbgate-sqltree": "^5.0.0-alpha.1",
"dbgate-tools": "^5.0.0-alpha.1",
+ "debug": "^4.3.4",
"diff": "^5.0.0",
"diff2html": "^3.4.13",
"eslint": "^6.8.0",
@@ -45,9 +46,9 @@
"lodash": "^4.17.21",
"ncp": "^2.0.0",
"node-cron": "^2.0.3",
- "node-ssh-forward": "^0.7.2",
"portfinder": "^1.0.28",
"simple-encryptor": "^4.0.0",
+ "ssh2": "^1.11.0",
"tar": "^6.0.5",
"uuid": "^3.4.0"
},
diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js
index 4935b3374..f96877d5d 100644
--- a/packages/api/src/controllers/config.js
+++ b/packages/api/src/controllers/config.js
@@ -59,13 +59,10 @@ module.exports = {
getSettings_meta: true,
async getSettings() {
- try {
- return this.fillMissingSettings(
- JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }))
- );
- } catch (err) {
- return this.fillMissingSettings({});
- }
+ const res = await lock.acquire('settings', async () => {
+ return await this.loadSettings();
+ });
+ return res;
},
fillMissingSettings(value) {
@@ -79,12 +76,21 @@ module.exports = {
return res;
},
+ async loadSettings() {
+ try {
+ const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
+ return this.fillMissingSettings(JSON.parse(settingsText));
+ } catch (err) {
+ return this.fillMissingSettings({});
+ }
+ },
+
updateSettings_meta: true,
async updateSettings(values, req) {
if (!hasPermission(`settings/change`, req)) return false;
- const res = await lock.acquire('update', async () => {
- const currentValue = await this.getSettings();
+ const res = await lock.acquire('settings', async () => {
+ const currentValue = await this.loadSettings();
try {
const updated = {
...currentValue,
diff --git a/packages/api/src/proc/sshForwardProcess.js b/packages/api/src/proc/sshForwardProcess.js
index 4fcd5e4cc..75a5a85a1 100644
--- a/packages/api/src/proc/sshForwardProcess.js
+++ b/packages/api/src/proc/sshForwardProcess.js
@@ -1,8 +1,8 @@
const fs = require('fs-extra');
const platformInfo = require('../utility/platformInfo');
const childProcessChecker = require('../utility/childProcessChecker');
-const { SSHConnection } = require('node-ssh-forward');
const { handleProcessCommunication } = require('../utility/processComm');
+const { SSHConnection } = require('../utility/SSHConnection');
async function getSshConnection(connection) {
const sshConfig = {
@@ -35,6 +35,8 @@ async function handleStart({ connection, tunnelConfig }) {
tunnelConfig,
});
} catch (err) {
+ console.log('Error creating SSH tunnel connection:', err.message);
+
process.send({
msgtype: 'error',
connection,
diff --git a/packages/api/src/utility/SSHConnection.js b/packages/api/src/utility/SSHConnection.js
new file mode 100644
index 000000000..1561ad633
--- /dev/null
+++ b/packages/api/src/utility/SSHConnection.js
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2018 Stocard GmbH.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { Client } = require('ssh2');
+const net = require('net');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const debug = require('debug');
+
+// interface Options {
+// username?: string;
+// password?: string;
+// privateKey?: string | Buffer;
+// agentForward?: boolean;
+// bastionHost?: string;
+// passphrase?: string;
+// endPort?: number;
+// endHost: string;
+// agentSocket?: string;
+// skipAutoPrivateKey?: boolean;
+// noReadline?: boolean;
+// }
+
+// interface ForwardingOptions {
+// fromPort: number;
+// toPort: number;
+// toHost?: string;
+// }
+
+class SSHConnection {
+ constructor(options) {
+ this.options = options;
+ this.debug = debug('ssh');
+ this.connections = [];
+ this.isWindows = process.platform === 'win32';
+ if (!options.username) {
+ this.options.username = process.env['SSH_USERNAME'] || process.env['USER'];
+ }
+ if (!options.endPort) {
+ this.options.endPort = 22;
+ }
+ if (!options.privateKey && !options.agentForward && !options.skipAutoPrivateKey) {
+ const defaultFilePath = path.join(os.homedir(), '.ssh', 'id_rsa');
+ if (fs.existsSync(defaultFilePath)) {
+ this.options.privateKey = fs.readFileSync(defaultFilePath);
+ }
+ }
+ }
+
+ async shutdown() {
+ this.debug('Shutdown connections');
+ for (const connection of this.connections) {
+ connection.removeAllListeners();
+ connection.end();
+ }
+ return new Promise(resolve => {
+ if (this.server) {
+ this.server.close(resolve);
+ }
+ return resolve();
+ });
+ }
+
+ async tty() {
+ const connection = await this.establish();
+ this.debug('Opening tty');
+ await this.shell(connection);
+ }
+
+ async executeCommand(command) {
+ const connection = await this.establish();
+ this.debug('Executing command "%s"', command);
+ await this.shell(connection, command);
+ }
+
+ async shell(connection, command) {
+ return new Promise((resolve, reject) => {
+ connection.shell((err, stream) => {
+ if (err) {
+ return reject(err);
+ }
+ stream
+ .on('close', async () => {
+ stream.end();
+ process.stdin.unpipe(stream);
+ process.stdin.destroy();
+ connection.end();
+ await this.shutdown();
+ return resolve();
+ })
+ .stderr.on('data', data => {
+ return reject(data);
+ });
+ stream.pipe(process.stdout);
+
+ if (command) {
+ stream.end(`${command}\nexit\n`);
+ } else {
+ process.stdin.pipe(stream);
+ }
+ });
+ });
+ }
+
+ async establish() {
+ let connection;
+ if (this.options.bastionHost) {
+ connection = await this.connectViaBastion(this.options.bastionHost);
+ } else {
+ connection = await this.connect(this.options.endHost);
+ }
+ return connection;
+ }
+
+ async connectViaBastion(bastionHost) {
+ this.debug('Connecting to bastion host "%s"', bastionHost);
+ const connectionToBastion = await this.connect(bastionHost);
+ return new Promise((resolve, reject) => {
+ connectionToBastion.forwardOut(
+ '127.0.0.1',
+ 22,
+ this.options.endHost,
+ this.options.endPort || 22,
+ async (err, stream) => {
+ if (err) {
+ return reject(err);
+ }
+ const connection = await this.connect(this.options.endHost, stream);
+ return resolve(connection);
+ }
+ );
+ });
+ }
+
+ async connect(host, stream) {
+ this.debug('Connecting to "%s"', host);
+ const connection = new Client();
+ return new Promise(async (resolve, reject) => {
+ const options = {
+ host,
+ port: this.options.endPort,
+ username: this.options.username,
+ password: this.options.password,
+ privateKey: this.options.privateKey,
+ };
+ if (this.options.agentForward) {
+ options['agentForward'] = true;
+
+ // see https://github.com/mscdex/ssh2#client for agents on Windows
+ // guaranteed to give the ssh agent sock if the agent is running (posix)
+ let agentDefault = process.env['SSH_AUTH_SOCK'];
+ if (this.isWindows) {
+ // null or undefined
+ if (agentDefault == null) {
+ agentDefault = 'pageant';
+ }
+ }
+
+ const agentSock = this.options.agentSocket ? this.options.agentSocket : agentDefault;
+ if (agentSock == null) {
+ throw new Error('SSH Agent Socket is not provided, or is not set in the SSH_AUTH_SOCK env variable');
+ }
+ options['agent'] = agentSock;
+ }
+ if (stream) {
+ options['sock'] = stream;
+ }
+ // PPK private keys can be encrypted, but won't contain the word 'encrypted'
+ // in fact they always contain a `encryption` header, so we can't do a simple check
+ options['passphrase'] = this.options.passphrase;
+ const looksEncrypted = this.options.privateKey
+ ? this.options.privateKey.toString().toLowerCase().includes('encrypted')
+ : false;
+ if (looksEncrypted && !options['passphrase'] && !this.options.noReadline) {
+ // options['passphrase'] = await this.getPassphrase();
+ }
+ connection.on('ready', () => {
+ this.connections.push(connection);
+ return resolve(connection);
+ });
+
+ connection.on('error', error => {
+ reject(error);
+ });
+ try {
+ connection.connect(options);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ // private async getPassphrase() {
+ // return new Promise(resolve => {
+ // const rl = readline.createInterface({
+ // input: process.stdin,
+ // output: process.stdout,
+ // });
+ // rl.question('Please type in the passphrase for your private key: ', answer => {
+ // return resolve(answer);
+ // });
+ // });
+ // }
+
+ async forward(options) {
+ const connection = await this.establish();
+ return new Promise((resolve, reject) => {
+ this.server = net
+ .createServer(socket => {
+ this.debug(
+ 'Forwarding connection from "localhost:%d" to "%s:%d"',
+ options.fromPort,
+ options.toHost,
+ options.toPort
+ );
+ connection.forwardOut(
+ 'localhost',
+ options.fromPort,
+ options.toHost || 'localhost',
+ options.toPort,
+ (error, stream) => {
+ if (error) {
+ return reject(error);
+ }
+ socket.pipe(stream);
+ stream.pipe(socket);
+ }
+ );
+ })
+ .listen(options.fromPort, 'localhost', () => {
+ return resolve();
+ });
+ });
+ }
+}
+
+module.exports = { SSHConnection };
diff --git a/packages/api/src/utility/connectUtility.js b/packages/api/src/utility/connectUtility.js
index 5fa33ff6e..52ef41831 100644
--- a/packages/api/src/utility/connectUtility.js
+++ b/packages/api/src/utility/connectUtility.js
@@ -1,8 +1,5 @@
-const { SSHConnection } = require('node-ssh-forward');
-const portfinder = require('portfinder');
const fs = require('fs-extra');
const { decryptConnection } = require('./crypting');
-const { getSshTunnel } = require('./sshTunnel');
const { getSshTunnelProxy } = require('./sshTunnelProxy');
const platformInfo = require('../utility/platformInfo');
const connections = require('../controllers/connections');
diff --git a/packages/filterparser/src/datetimeParser.ts b/packages/filterparser/src/datetimeParser.ts
new file mode 100644
index 000000000..93e22cf36
--- /dev/null
+++ b/packages/filterparser/src/datetimeParser.ts
@@ -0,0 +1,279 @@
+import P from 'parsimmon';
+import moment from 'moment';
+import { FilterType } from './types';
+import { Condition } from 'dbgate-sqltree';
+import { TransformType } from 'dbgate-types';
+import { interpretEscapes, token, word, whitespace } from './common';
+
+const compoudCondition = conditionType => conditions => {
+ if (conditions.length == 1) return conditions[0];
+ return {
+ [conditionType]: conditions,
+ };
+};
+
+function getTransformCondition(transform: TransformType, value) {
+ return {
+ conditionType: 'binary',
+ operator: '=',
+ left: {
+ exprType: 'transform',
+ transform,
+ expr: {
+ exprType: 'placeholder',
+ },
+ },
+ right: {
+ exprType: 'value',
+ value,
+ },
+ };
+}
+
+const yearCondition = () => value => {
+ return getTransformCondition('YEAR', value);
+};
+
+const yearMonthCondition = () => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)/);
+
+ return {
+ conditionType: 'and',
+ conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])],
+ };
+};
+
+const yearMonthDayCondition = () => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
+
+ return {
+ conditionType: 'and',
+ conditions: [
+ getTransformCondition('YEAR', m[1]),
+ getTransformCondition('MONTH', m[2]),
+ getTransformCondition('DAY', m[3]),
+ ],
+ };
+};
+
+const yearEdge = edgeFunction => value => {
+ return moment(new Date(parseInt(value), 0, 1))
+ [edgeFunction]('year')
+ .format('YYYY-MM-DDTHH:mm:ss.SSS');
+};
+
+const yearMonthEdge = edgeFunction => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)/);
+
+ return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, 1))
+ [edgeFunction]('month')
+ .format('YYYY-MM-DDTHH:mm:ss.SSS');
+};
+
+const yearMonthDayEdge = edgeFunction => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
+
+ return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3])))
+ [edgeFunction]('day')
+ .format('YYYY-MM-DDTHH:mm:ss.SSS');
+};
+
+const yearMonthDayMinuteEdge = edgeFunction => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
+ const year = m[1];
+ const month = m[2];
+ const day = m[3];
+ const hour = m[4];
+ const minute = m[5];
+ const dateObject = new Date(year, month - 1, day, hour, minute);
+
+ return moment(dateObject)[edgeFunction]('minute').format('YYYY-MM-DDTHH:mm:ss.SSS');
+};
+
+const yearMonthDayMinuteSecondEdge = edgeFunction => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
+ const year = m[1];
+ const month = m[2];
+ const day = m[3];
+ const hour = m[5];
+ const minute = m[6];
+ const second = m[7];
+ const dateObject = new Date(year, month - 1, day, hour, minute, second);
+
+ return moment(dateObject)[edgeFunction]('second').format('YYYY-MM-DDTHH:mm:ss.SSS');
+};
+
+const createIntervalCondition = (start, end) => {
+ return {
+ conditionType: 'and',
+ conditions: [
+ {
+ conditionType: 'binary',
+ operator: '>=',
+ left: {
+ exprType: 'placeholder',
+ },
+ right: {
+ exprType: 'value',
+ value: start,
+ },
+ },
+ {
+ conditionType: 'binary',
+ operator: '<=',
+ left: {
+ exprType: 'placeholder',
+ },
+ right: {
+ exprType: 'value',
+ value: end,
+ },
+ },
+ ],
+ };
+};
+
+const createDateIntervalCondition = (start, end) => {
+ return createIntervalCondition(start.format('YYYY-MM-DDTHH:mm:ss.SSS'), end.format('YYYY-MM-DDTHH:mm:ss.SSS'));
+};
+
+const fixedMomentIntervalCondition = (intervalType, diff) => () => {
+ return createDateIntervalCondition(
+ moment().add(intervalType, diff).startOf(intervalType),
+ moment().add(intervalType, diff).endOf(intervalType)
+ );
+};
+
+const yearMonthDayMinuteCondition = () => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
+ const year = m[1];
+ const month = m[2];
+ const day = m[3];
+ const hour = m[4];
+ const minute = m[5];
+ const dateObject = new Date(year, month - 1, day, hour, minute);
+
+ return createDateIntervalCondition(moment(dateObject).startOf('minute'), moment(dateObject).endOf('minute'));
+};
+
+const yearMonthDaySecondCondition = () => value => {
+ const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
+ const year = m[1];
+ const month = m[2];
+ const day = m[3];
+ const hour = m[5];
+ const minute = m[6];
+ const second = m[7];
+ const dateObject = new Date(year, month - 1, day, hour, minute, second);
+
+ return createDateIntervalCondition(moment(dateObject).startOf('second'), moment(dateObject).endOf('second'));
+};
+
+const binaryCondition = operator => value => ({
+ conditionType: 'binary',
+ operator,
+ left: {
+ exprType: 'placeholder',
+ },
+ right: {
+ exprType: 'value',
+ value,
+ },
+});
+
+const createParser = () => {
+ const langDef = {
+ comma: () => word(','),
+
+ yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()),
+ yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()),
+ yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()),
+ yearMonthDayMinute: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteCondition()),
+ yearMonthDaySecond: () =>
+ P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()),
+
+ yearNumStart: () => P.regexp(/\d\d\d\d/).map(yearEdge('startOf')),
+ yearNumEnd: () => P.regexp(/\d\d\d\d/).map(yearEdge('endOf')),
+ yearMonthStart: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('startOf')),
+ yearMonthEnd: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('endOf')),
+ yearMonthDayStart: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('startOf')),
+ yearMonthDayEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('endOf')),
+ yearMonthDayMinuteStart: () =>
+ P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('startOf')),
+ yearMonthDayMinuteEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('endOf')),
+ yearMonthDayMinuteSecondStart: () =>
+ P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('startOf')),
+ yearMonthDayMinuteSecondEnd: () =>
+ P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('endOf')),
+
+ this: () => word('THIS'),
+ last: () => word('LAST'),
+ next: () => word('NEXT'),
+ week: () => word('WEEK'),
+ month: () => word('MONTH'),
+ year: () => word('YEAR'),
+
+ yesterday: () => word('YESTERDAY').map(fixedMomentIntervalCondition('day', -1)),
+ today: () => word('TODAY').map(fixedMomentIntervalCondition('day', 0)),
+ tomorrow: () => word('TOMORROW').map(fixedMomentIntervalCondition('day', 1)),
+
+ lastWeek: r => r.last.then(r.week).map(fixedMomentIntervalCondition('week', -1)),
+ thisWeek: r => r.this.then(r.week).map(fixedMomentIntervalCondition('week', 0)),
+ nextWeek: r => r.next.then(r.week).map(fixedMomentIntervalCondition('week', 1)),
+
+ lastMonth: r => r.last.then(r.month).map(fixedMomentIntervalCondition('month', -1)),
+ thisMonth: r => r.this.then(r.month).map(fixedMomentIntervalCondition('month', 0)),
+ nextMonth: r => r.next.then(r.month).map(fixedMomentIntervalCondition('month', 1)),
+
+ lastYear: r => r.last.then(r.year).map(fixedMomentIntervalCondition('year', -1)),
+ thisYear: r => r.this.then(r.year).map(fixedMomentIntervalCondition('year', 0)),
+ nextYear: r => r.next.then(r.year).map(fixedMomentIntervalCondition('year', 1)),
+
+ valueStart: r =>
+ P.alt(
+ r.yearMonthDayMinuteSecondStart,
+ r.yearMonthDayMinuteStart,
+ r.yearMonthDayStart,
+ r.yearMonthStart,
+ r.yearNumStart
+ ),
+ valueEnd: r =>
+ P.alt(r.yearMonthDayMinuteSecondEnd, r.yearMonthDayMinuteEnd, r.yearMonthDayEnd, r.yearMonthEnd, r.yearNumEnd),
+
+ le: r => word('<=').then(r.valueEnd).map(binaryCondition('<=')),
+ ge: r => word('>=').then(r.valueStart).map(binaryCondition('>=')),
+ lt: r => word('<').then(r.valueStart).map(binaryCondition('<')),
+ gt: r => word('>').then(r.valueEnd).map(binaryCondition('>')),
+
+ element: r =>
+ P.alt(
+ r.yearMonthDaySecond,
+ r.yearMonthDayMinute,
+ r.yearMonthDayNum,
+ r.yearMonthNum,
+ r.yearNum,
+ r.yesterday,
+ r.today,
+ r.tomorrow,
+ r.lastWeek,
+ r.thisWeek,
+ r.nextWeek,
+ r.lastMonth,
+ r.thisMonth,
+ r.nextMonth,
+ r.lastYear,
+ r.thisYear,
+ r.nextYear,
+ r.le,
+ r.lt,
+ r.ge,
+ r.gt
+ ).trim(whitespace),
+ factor: r => r.element.sepBy(whitespace).map(compoudCondition('$and')),
+ list: r => r.factor.sepBy(r.comma).map(compoudCondition('$or')),
+ };
+
+ return P.createLanguage(langDef);
+};
+
+export const datetimeParser = createParser();
diff --git a/packages/filterparser/src/parseFilter.ts b/packages/filterparser/src/parseFilter.ts
index 9d170a6f5..a7740779b 100644
--- a/packages/filterparser/src/parseFilter.ts
+++ b/packages/filterparser/src/parseFilter.ts
@@ -5,6 +5,7 @@ import { Condition } from 'dbgate-sqltree';
import { TransformType } from 'dbgate-types';
import { interpretEscapes, token, word, whitespace } from './common';
import { mongoParser } from './mongoParser';
+import { datetimeParser } from './datetimeParser';
const binaryCondition = operator => value => ({
conditionType: 'binary',
@@ -67,116 +68,6 @@ const negateCondition = condition => {
};
};
-function getTransformCondition(transform: TransformType, value) {
- return {
- conditionType: 'binary',
- operator: '=',
- left: {
- exprType: 'transform',
- transform,
- expr: {
- exprType: 'placeholder',
- },
- },
- right: {
- exprType: 'value',
- value,
- },
- };
-}
-
-const yearCondition = () => value => {
- return getTransformCondition('YEAR', value);
-};
-
-const yearMonthCondition = () => value => {
- const m = value.match(/(\d\d\d\d)-(\d\d?)/);
-
- return {
- conditionType: 'and',
- conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])],
- };
-};
-
-const yearMonthDayCondition = () => value => {
- const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
-
- return {
- conditionType: 'and',
- conditions: [
- getTransformCondition('YEAR', m[1]),
- getTransformCondition('MONTH', m[2]),
- getTransformCondition('DAY', m[3]),
- ],
- };
-};
-
-const createIntervalCondition = (start, end) => {
- return {
- conditionType: 'and',
- conditions: [
- {
- conditionType: 'binary',
- operator: '>=',
- left: {
- exprType: 'placeholder',
- },
- right: {
- exprType: 'value',
- value: start,
- },
- },
- {
- conditionType: 'binary',
- operator: '<=',
- left: {
- exprType: 'placeholder',
- },
- right: {
- exprType: 'value',
- value: end,
- },
- },
- ],
- };
-};
-
-const createDateIntervalCondition = (start, end) => {
- return createIntervalCondition(start.format('YYYY-MM-DDTHH:mm:ss.SSS'), end.format('YYYY-MM-DDTHH:mm:ss.SSS'));
-};
-
-const fixedMomentIntervalCondition = (intervalType, diff) => () => {
- return createDateIntervalCondition(
- moment().add(intervalType, diff).startOf(intervalType),
- moment().add(intervalType, diff).endOf(intervalType)
- );
-};
-
-const yearMonthDayMinuteCondition = () => value => {
- const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/);
- const year = m[1];
- const month = m[2];
- const day = m[3];
- const hour = m[4];
- const minute = m[5];
- const dateObject = new Date(year, month - 1, day, hour, minute);
-
- return createDateIntervalCondition(moment(dateObject).startOf('minute'), moment(dateObject).endOf('minute'));
-};
-
-const yearMonthDaySecondCondition = () => value => {
- const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/);
- const year = m[1];
- const month = m[2];
- const day = m[3];
- const hour = m[5];
- const minute = m[6];
- const second = m[7];
- const dateObject = new Date(year, month - 1, day, hour, minute, second);
-
- return createDateIntervalCondition(moment(dateObject).startOf('second'), moment(dateObject).endOf('second'));
-};
-
const createParser = (filterType: FilterType) => {
const langDef = {
string1: () =>
@@ -206,13 +97,6 @@ const createParser = (filterType: FilterType) => {
noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'),
- yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()),
- yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()),
- yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()),
- yearMonthDayMinute: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteCondition()),
- yearMonthDaySecond: () =>
- P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()),
-
value: r => P.alt(...allowedValues.map(x => r[x])),
valueTestEq: r => r.value.map(binaryCondition('=')),
valueTestStr: r => r.value.map(likeCondition('like', '%#VALUE#%')),
@@ -228,29 +112,6 @@ const createParser = (filterType: FilterType) => {
trueNum: () => word('1').map(binaryFixedValueCondition('1')),
falseNum: () => word('0').map(binaryFixedValueCondition('0')),
- this: () => word('THIS'),
- last: () => word('LAST'),
- next: () => word('NEXT'),
- week: () => word('WEEK'),
- month: () => word('MONTH'),
- year: () => word('YEAR'),
-
- yesterday: () => word('YESTERDAY').map(fixedMomentIntervalCondition('day', -1)),
- today: () => word('TODAY').map(fixedMomentIntervalCondition('day', 0)),
- tomorrow: () => word('TOMORROW').map(fixedMomentIntervalCondition('day', 1)),
-
- lastWeek: r => r.last.then(r.week).map(fixedMomentIntervalCondition('week', -1)),
- thisWeek: r => r.this.then(r.week).map(fixedMomentIntervalCondition('week', 0)),
- nextWeek: r => r.next.then(r.week).map(fixedMomentIntervalCondition('week', 1)),
-
- lastMonth: r => r.last.then(r.month).map(fixedMomentIntervalCondition('month', -1)),
- thisMonth: r => r.this.then(r.month).map(fixedMomentIntervalCondition('month', 0)),
- nextMonth: r => r.next.then(r.month).map(fixedMomentIntervalCondition('month', 1)),
-
- lastYear: r => r.last.then(r.year).map(fixedMomentIntervalCondition('year', -1)),
- thisYear: r => r.this.then(r.year).map(fixedMomentIntervalCondition('year', 0)),
- nextYear: r => r.next.then(r.year).map(fixedMomentIntervalCondition('year', 1)),
-
eq: r => word('=').then(r.value).map(binaryCondition('=')),
ne: r => word('!=').then(r.value).map(binaryCondition('<>')),
ne2: r => word('<>').then(r.value).map(binaryCondition('<>')),
@@ -294,27 +155,7 @@ const createParser = (filterType: FilterType) => {
if (filterType == 'eval') {
allowedElements.push('true', 'false');
}
- if (filterType == 'datetime') {
- allowedElements.push(
- 'yearMonthDaySecond',
- 'yearMonthDayMinute',
- 'yearMonthDayNum',
- 'yearMonthNum',
- 'yearNum',
- 'yesterday',
- 'today',
- 'tomorrow',
- 'lastWeek',
- 'thisWeek',
- 'nextWeek',
- 'lastMonth',
- 'thisMonth',
- 'nextMonth',
- 'lastYear',
- 'thisYear',
- 'nextYear'
- );
- }
+
// must be last
if (filterType == 'string' || filterType == 'eval') {
allowedElements.push('valueTestStr');
@@ -328,10 +169,10 @@ const createParser = (filterType: FilterType) => {
const parsers = {
number: createParser('number'),
string: createParser('string'),
- datetime: createParser('datetime'),
logical: createParser('logical'),
eval: createParser('eval'),
mongo: mongoParser,
+ datetime: datetimeParser,
};
export function parseFilter(value: string, filterType: FilterType): Condition {
diff --git a/packages/web/src/buttons/ToolStripCommandButton.svelte b/packages/web/src/buttons/ToolStripCommandButton.svelte
index ef56c0c2e..107fb46c3 100644
--- a/packages/web/src/buttons/ToolStripCommandButton.svelte
+++ b/packages/web/src/buttons/ToolStripCommandButton.svelte
@@ -16,6 +16,7 @@
export let command;
export let component = ToolStripButton;
export let hideDisabled = false;
+ export let buttonLabel = null;
$: cmd = Object.values($commandsCustomized).find((x: any) => x.id == command) as any;
@@ -29,6 +30,6 @@
disabled={!cmd.enabled}
{...$$restProps}
>
- {cmd.toolbarName || cmd.name}
+ {buttonLabel || cmd.toolbarName || cmd.name}
{/if}
diff --git a/packages/web/src/buttons/ToolStripCommandSplitButton.svelte b/packages/web/src/buttons/ToolStripCommandSplitButton.svelte
index 2810e7ab9..933b5fe51 100644
--- a/packages/web/src/buttons/ToolStripCommandSplitButton.svelte
+++ b/packages/web/src/buttons/ToolStripCommandSplitButton.svelte
@@ -5,7 +5,16 @@
import ToolStripSplitDropDownButton from './ToolStripSplitDropDownButton.svelte';
export let commands;
- $: menu = _.compact(commands).map(command => ({ command }));
+ export let hideDisabled = false;
+ export let buttonLabel = null;
+
+ $: menu = _.compact(commands).map(command => (_.isString(command) ? { command } : command));
-