diff --git a/CHANGELOG.md b/CHANGELOG.md
index f78fe074f..921e1835a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,23 @@ Builds:
- linux - application for linux
- win - application for Windows
+### 5.0.8
+- ADDED: SQL Server - support using domain logins under Linux and Mac #305
+- ADDED: Permissions for connections #318
+- ADDED: Ability to change editor front #308
+- ADDED: Custom expression in query designer #306
+- ADDED: OR conditions in query designer #321
+- ADDED: Ability to configure settings view environment variables #304
+
+### 5.0.7
+- FIXED: Fixed some problems with SSH tunnel (upgraded SSH client) #315
+- FIXED: Fixed MognoDB executing find query #312
+- ADDED: Interval filters for date/time columns #311
+- ADDED: Ability to clone rows #309
+- ADDED: connecting option Trust server certificate for SQL Server #305
+- ADDED: Autorefresh, reload table every x second #303
+- FIXED(app): Changing editor theme and font size in Editor Themes #300
+
### 5.0.6
- ADDED: Search in columns
- CHANGED: Upgraded mongodb driver
diff --git a/package.json b/package.json
index 42c9040dc..ff4b11d4d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "5.0.7-beta.4",
+ "version": "5.0.8",
"name": "dbgate-all",
"workspaces": [
"packages/*",
diff --git a/packages/api/env/portal/.env b/packages/api/env/portal/.env
index 6d85d69ec..578102633 100644
--- a/packages/api/env/portal/.env
+++ b/packages/api/env/portal/.env
@@ -48,4 +48,15 @@ PASSWORD_relational=relational
ENGINE_relational=mariadb@dbgate-plugin-mysql
READONLY_relational=1
+# SETTINGS_dataGrid.showHintColumns=1
+
# docker run -p 3000:3000 -e CONNECTIONS=mongo -e URL_mongo=mongodb://localhost:27017 -e ENGINE_mongo=mongo@dbgate-plugin-mongo -e LABEL_mongo=mongo dbgate/dbgate:beta
+
+# LOGINS=x,y
+# LOGIN_PASSWORD_x=x
+# LOGIN_PASSWORD_y=LOGIN_PASSWORD_y
+# LOGIN_PERMISSIONS_x=~*
+# LOGIN_PERMISSIONS_y=~*
+
+# PERMISSIONS=~*,connections/relational
+# PERMISSIONS=~*
diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js
index f96877d5d..34e3e3c8b 100644
--- a/packages/api/src/controllers/config.js
+++ b/packages/api/src/controllers/config.js
@@ -29,7 +29,7 @@ module.exports = {
async get(_params, req) {
const logins = getLogins();
const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
- const permissions = login ? login.permissions : null;
+ const permissions = login ? login.permissions : process.env.PERMISSIONS;
return {
runAsPortal: !!connections.portalConnections,
@@ -73,6 +73,14 @@ module.exports = {
// res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
res['app.useNativeMenu'] = false;
}
+ for (const envVar in process.env) {
+ if (envVar.startsWith('SETTINGS_')) {
+ const key = envVar.substring('SETTINGS_'.length);
+ if (!res[key]) {
+ res[key] = process.env[envVar];
+ }
+ }
+ }
return res;
},
diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js
index 4c64177de..7aad6ebb1 100644
--- a/packages/api/src/controllers/connections.js
+++ b/packages/api/src/controllers/connections.js
@@ -13,6 +13,7 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
const processArgs = require('../utility/processArgs');
const { safeJsonParse } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo');
+const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
function getNamedArgs() {
const res = {};
@@ -165,12 +166,12 @@ module.exports = {
},
list_meta: true,
- async list() {
+ async list(_params, req) {
if (portalConnections) {
if (platformInfo.allowShellConnection) return portalConnections;
- return portalConnections.map(maskConnection);
+ return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req));
}
- return this.datastore.find();
+ return (await this.datastore.find()).filter(x => connectionHasPermission(x, req));
},
test_meta: true,
@@ -217,16 +218,18 @@ module.exports = {
},
update_meta: true,
- async update({ _id, values }) {
+ async update({ _id, values }, req) {
if (portalConnections) return;
+ testConnectionPermission(_id, req);
const res = await this.datastore.patch(_id, values);
socket.emitChanged('connection-list-changed');
return res;
},
updateDatabase_meta: true,
- async updateDatabase({ conid, database, values }) {
+ async updateDatabase({ conid, database, values }, req) {
if (portalConnections) return;
+ testConnectionPermission(conid, req);
const conn = await this.datastore.get(conid);
let databases = (conn && conn.databases) || [];
if (databases.find(x => x.name == database)) {
@@ -242,8 +245,9 @@ module.exports = {
},
delete_meta: true,
- async delete(connection) {
+ async delete(connection, req) {
if (portalConnections) return;
+ testConnectionPermission(connection, req);
const res = await this.datastore.remove(connection._id);
socket.emitChanged('connection-list-changed');
return res;
@@ -260,7 +264,8 @@ module.exports = {
},
get_meta: true,
- async get({ conid }) {
+ async get({ conid }, req) {
+ testConnectionPermission(conid, req);
return this.getCore({ conid, mask: true });
},
diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js
index 1078d4416..6be1a25c7 100644
--- a/packages/api/src/controllers/databaseConnections.js
+++ b/packages/api/src/controllers/databaseConnections.js
@@ -26,6 +26,7 @@ const generateDeploySql = require('../shell/generateDeploySql');
const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs');
+const { testConnectionPermission } = require('../utility/hasPermission');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -130,7 +131,8 @@ module.exports = {
},
queryData_meta: true,
- async queryData({ conid, database, sql }) {
+ async queryData({ conid, database, sql }, req) {
+ testConnectionPermission(conid, req);
console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`);
const opened = await this.ensureOpened(conid, database);
// if (opened && opened.status && opened.status.name == 'error') {
@@ -141,14 +143,16 @@ module.exports = {
},
sqlSelect_meta: true,
- async sqlSelect({ conid, database, select }) {
+ async sqlSelect({ conid, database, select }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
return res;
},
runScript_meta: true,
- async runScript({ conid, database, sql }) {
+ async runScript({ conid, database, sql }, req) {
+ testConnectionPermission(conid, req);
console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql });
@@ -156,13 +160,15 @@ module.exports = {
},
collectionData_meta: true,
- async collectionData({ conid, database, options }) {
+ async collectionData({ conid, database, options }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
return res.result || null;
},
- async loadDataCore(msgtype, { conid, database, ...args }) {
+ async loadDataCore(msgtype, { conid, database, ...args }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype, ...args });
if (res.errorMessage) {
@@ -176,32 +182,38 @@ module.exports = {
},
loadKeys_meta: true,
- async loadKeys({ conid, database, root, filter }) {
+ async loadKeys({ conid, database, root, filter }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('loadKeys', { conid, database, root, filter });
},
exportKeys_meta: true,
- async exportKeys({ conid, database, options }) {
+ async exportKeys({ conid, database, options }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('exportKeys', { conid, database, options });
},
loadKeyInfo_meta: true,
- async loadKeyInfo({ conid, database, key }) {
+ async loadKeyInfo({ conid, database, key }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('loadKeyInfo', { conid, database, key });
},
loadKeyTableRange_meta: true,
- async loadKeyTableRange({ conid, database, key, cursor, count }) {
+ async loadKeyTableRange({ conid, database, key, cursor, count }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
},
loadFieldValues_meta: true,
- async loadFieldValues({ conid, database, schemaName, pureName, field, search }) {
+ async loadFieldValues({ conid, database, schemaName, pureName, field, search }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search });
},
callMethod_meta: true,
- async callMethod({ conid, database, method, args }) {
+ async callMethod({ conid, database, method, args }, req) {
+ testConnectionPermission(conid, req);
return this.loadDataCore('callMethod', { conid, database, method, args });
// const opened = await this.ensureOpened(conid, database);
@@ -213,7 +225,8 @@ module.exports = {
},
updateCollection_meta: true,
- async updateCollection({ conid, database, changeSet }) {
+ async updateCollection({ conid, database, changeSet }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
if (res.errorMessage) {
@@ -225,7 +238,14 @@ module.exports = {
},
status_meta: true,
- async status({ conid, database }) {
+ async status({ conid, database }, req) {
+ if (!conid) {
+ return {
+ name: 'error',
+ message: 'No connection',
+ };
+ }
+ testConnectionPermission(conid, req);
const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
return {
@@ -247,7 +267,8 @@ module.exports = {
},
ping_meta: true,
- async ping({ conid, database }) {
+ async ping({ conid, database }, req) {
+ testConnectionPermission(conid, req);
let existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) {
@@ -263,7 +284,8 @@ module.exports = {
},
refresh_meta: true,
- async refresh({ conid, database, keepOpen }) {
+ async refresh({ conid, database, keepOpen }, req) {
+ testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid, database);
await this.ensureOpened(conid, database);
@@ -271,7 +293,8 @@ module.exports = {
},
syncModel_meta: true,
- async syncModel({ conid, database, isFullRefresh }) {
+ async syncModel({ conid, database, isFullRefresh }, req) {
+ testConnectionPermission(conid, req);
const conn = await this.ensureOpened(conid, database);
conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh });
return { status: 'ok' };
@@ -301,13 +324,15 @@ module.exports = {
},
disconnect_meta: true,
- async disconnect({ conid, database }) {
+ async disconnect({ conid, database }, req) {
+ testConnectionPermission(conid, req);
await this.close(conid, database, true);
return { status: 'ok' };
},
structure_meta: true,
- async structure({ conid, database }) {
+ async structure({ conid, database }, req) {
+ testConnectionPermission(conid, req);
if (conid == '__model') {
const model = await importDbModel(database);
return model;
@@ -324,14 +349,19 @@ module.exports = {
},
serverVersion_meta: true,
- async serverVersion({ conid, database }) {
+ async serverVersion({ conid, database }, req) {
+ if (!conid) {
+ return null;
+ }
+ testConnectionPermission(conid, req);
if (!conid) return null;
const opened = await this.ensureOpened(conid, database);
return opened.serverVersion || null;
},
sqlPreview_meta: true,
- async sqlPreview({ conid, database, objects, options }) {
+ async sqlPreview({ conid, database, objects, options }, req) {
+ testConnectionPermission(conid, req);
// wait for structure
await this.structure({ conid, database });
@@ -341,7 +371,8 @@ module.exports = {
},
exportModel_meta: true,
- async exportModel({ conid, database }) {
+ async exportModel({ conid, database }, req) {
+ testConnectionPermission(conid, req);
const archiveFolder = await archive.getNewArchiveFolder({ database });
await fs.mkdir(path.join(archivedir(), archiveFolder));
const model = await this.structure({ conid, database });
@@ -351,7 +382,8 @@ module.exports = {
},
generateDeploySql_meta: true,
- async generateDeploySql({ conid, database, archiveFolder }) {
+ async generateDeploySql({ conid, database, archiveFolder }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, {
msgtype: 'generateDeploySql',
diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js
index 28fd52cde..7f6ad2b6d 100644
--- a/packages/api/src/controllers/serverConnections.js
+++ b/packages/api/src/controllers/serverConnections.js
@@ -7,6 +7,7 @@ const { handleProcessCommunication } = require('../utility/processComm');
const lock = new AsyncLock();
const config = require('./config');
const processArgs = require('../utility/processArgs');
+const { testConnectionPermission } = require('../utility/hasPermission');
module.exports = {
opened: [],
@@ -90,19 +91,22 @@ module.exports = {
},
disconnect_meta: true,
- async disconnect({ conid }) {
+ async disconnect({ conid }, req) {
+ testConnectionPermission(conid, req);
await this.close(conid, true);
return { status: 'ok' };
},
listDatabases_meta: true,
- async listDatabases({ conid }) {
+ async listDatabases({ conid }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.databases;
},
version_meta: true,
- async version({ conid }) {
+ async version({ conid }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
return opened.version;
},
@@ -132,7 +136,8 @@ module.exports = {
},
refresh_meta: true,
- async refresh({ conid, keepOpen }) {
+ async refresh({ conid, keepOpen }, req) {
+ testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid);
await this.ensureOpened(conid);
@@ -140,7 +145,8 @@ module.exports = {
},
createDatabase_meta: true,
- async createDatabase({ conid, name }) {
+ async createDatabase({ conid, name }, req) {
+ testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid);
if (opened.connection.isReadOnly) return false;
opened.subprocess.send({ msgtype: 'createDatabase', name });
diff --git a/packages/api/src/utility/hasPermission.js b/packages/api/src/utility/hasPermission.js
index 54696b5b1..04d28112e 100644
--- a/packages/api/src/utility/hasPermission.js
+++ b/packages/api/src/utility/hasPermission.js
@@ -4,12 +4,21 @@ const _ = require('lodash');
const userPermissions = {};
function hasPermission(tested, req) {
+ if (!req) {
+ // request object not available, allow all
+ return true;
+ }
const { user } = (req && req.auth) || {};
const key = user || '';
const logins = getLogins();
- if (!userPermissions[key] && logins) {
- const login = logins.find(x => x.login == user);
- userPermissions[key] = compilePermissions(login ? login.permissions : null);
+
+ if (!userPermissions[key]) {
+ if (logins) {
+ const login = logins.find(x => x.login == user);
+ userPermissions[key] = compilePermissions(login ? login.permissions : null);
+ } else {
+ userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
+ }
}
return testPermission(tested, userPermissions[key]);
}
@@ -50,7 +59,26 @@ function getLogins() {
return loginsCache;
}
+function connectionHasPermission(connection, req) {
+ if (!connection) {
+ return true;
+ }
+ if (_.isString(connection)) {
+ return hasPermission(`connections/${connection}`, req);
+ } else {
+ return hasPermission(`connections/${connection._id}`, req);
+ }
+}
+
+function testConnectionPermission(connection, req) {
+ if (!connectionHasPermission(connection, req)) {
+ throw new Error('Connection permission not granted');
+ }
+}
+
module.exports = {
hasPermission,
getLogins,
+ connectionHasPermission,
+ testConnectionPermission,
};
diff --git a/packages/api/src/utility/useController.js b/packages/api/src/utility/useController.js
index 6ab16c676..8ee431a42 100644
--- a/packages/api/src/utility/useController.js
+++ b/packages/api/src/utility/useController.js
@@ -47,7 +47,6 @@ module.exports = function useController(app, electron, route, controller) {
let method = 'post';
let raw = false;
- let rawParams = false;
// if (_.isString(meta)) {
// method = meta;
@@ -55,7 +54,6 @@ module.exports = function useController(app, electron, route, controller) {
if (_.isPlainObject(meta)) {
method = meta.method;
raw = meta.raw;
- rawParams = meta.rawParams;
}
if (raw) {
@@ -67,9 +65,7 @@ module.exports = function useController(app, electron, route, controller) {
// controller._init_called = true;
// }
try {
- let params = [{ ...req.body, ...req.query }, req];
- if (rawParams) params = [req, res];
- const data = await controller[key](...params);
+ const data = await controller[key]({ ...req.body, ...req.query }, req);
res.json(data);
} catch (e) {
console.log(e);
diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte
index 775df46ed..ae241f989 100644
--- a/packages/web/src/datagrid/DataGridCore.svelte
+++ b/packages/web/src/datagrid/DataGridCore.svelte
@@ -56,7 +56,7 @@
id: 'dataGrid.cloneRows',
category: 'Data grid',
name: 'Clone rows',
- toolbarName: 'Clone',
+ toolbarName: 'Clone row(s)',
keyText: 'CtrlOrCommand+Shift+C',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
onClick: () => getCurrentDataGrid().cloneRows(),
diff --git a/packages/web/src/designer/Designer.svelte b/packages/web/src/designer/Designer.svelte
index 815338cc4..b879050a8 100644
--- a/packages/web/src/designer/Designer.svelte
+++ b/packages/web/src/designer/Designer.svelte
@@ -167,8 +167,8 @@
async function detectSize(tables, domTables) {
await tick();
const rects = _.values(domTables).map(x => x.getRect());
- const maxX = _.max(rects.map(x => x.right));
- const maxY = _.max(rects.map(x => x.bottom));
+ const maxX = rects.length > 0 ? _.max(rects.map(x => x.right)) : 0;
+ const maxY = rects.length > 0 ? _.max(rects.map(x => x.bottom)) : 0;
canvasWidth = Math.max(3000, maxX + 50);
canvasHeight = Math.max(3000, maxY + 50);
diff --git a/packages/web/src/designer/DesignerQueryDumper.ts b/packages/web/src/designer/DesignerQueryDumper.ts
index d25bb2f22..c1930861c 100644
--- a/packages/web/src/designer/DesignerQueryDumper.ts
+++ b/packages/web/src/designer/DesignerQueryDumper.ts
@@ -8,6 +8,7 @@ import {
mergeConditions,
Source,
ResultField,
+ Expression,
} from 'dbgate-sqltree';
import { EngineDriver } from 'dbgate-types';
import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types';
@@ -78,25 +79,27 @@ export class DesignerQueryDumper {
return select;
}
- addConditions(select: Select, tables: DesignerTableInfo[]) {
+ buildConditionFromFilterField(tables: DesignerTableInfo[], filterField: string, getExpression?: Function): Condition {
+ const conditions = [];
+
for (const column of this.designer.columns || []) {
- if (!column.filter) continue;
- const table = (this.designer.tables || []).find(x => x.designerId == column.designerId);
- if (!table) continue;
- if (!tables.find(x => x.designerId == table.designerId)) continue;
+ if (!column[filterField]) continue;
+
+ if (!column.isCustomExpression) {
+ const table = (this.designer.tables || []).find(x => x.designerId == column.designerId);
+ if (!table) continue;
+ if (!tables.find(x => x.designerId == table.designerId)) continue;
+ }
try {
- const condition = parseFilter(column.filter, findDesignerFilterType(column, this.designer));
+ const condition = parseFilter(column[filterField], findDesignerFilterType(column, this.designer));
if (condition) {
- select.where = mergeConditions(
- select.where,
+ conditions.push(
_.cloneDeepWith(condition, expr => {
- if (expr.exprType == 'placeholder')
- return {
- exprType: 'column',
- columnName: column.columnName,
- source: findQuerySource(this.designer, column.designerId),
- };
+ if (expr.exprType == 'placeholder') {
+ if (getExpression) return getExpression(column);
+ return this.getColumnExpression(column);
+ }
})
);
}
@@ -105,33 +108,79 @@ export class DesignerQueryDumper {
continue;
}
}
+
+ if (conditions.length == 0) {
+ return null;
+ }
+
+ if (conditions.length == 1) {
+ return conditions[0];
+ }
+
+ return {
+ conditionType: 'and',
+ conditions,
+ };
+ }
+
+ addConditionsCore(select: Select, tables: DesignerTableInfo[], filterFields, selectField, getExpression?) {
+ const conditions: Condition[] = _.compact(
+ filterFields.map(field => this.buildConditionFromFilterField(tables, field, getExpression))
+ );
+
+ if (conditions.length == 0) {
+ return;
+ }
+ if (conditions.length == 0) {
+ select[selectField] = mergeConditions(select[selectField], conditions[0]);
+ return;
+ }
+ select[selectField] = mergeConditions(select[selectField], {
+ conditionType: 'or',
+ conditions,
+ });
+ }
+
+ addConditions(select: Select, tables: DesignerTableInfo[]) {
+ const additionalFilterCount = this.designer.settings?.additionalFilterCount || 0;
+ const filterFields = ['filter', ..._.range(additionalFilterCount).map(index => `additionalFilter${index + 1}`)];
+ this.addConditionsCore(select, tables, filterFields, 'where');
}
addGroupConditions(select: Select, tables: DesignerTableInfo[], selectIsGrouped: boolean) {
- for (const column of this.designer.columns || []) {
- if (!column.groupFilter) continue;
- const table = (this.designer.tables || []).find(x => x.designerId == column.designerId);
- if (!table) continue;
- if (!tables.find(x => x.designerId == table.designerId)) continue;
-
- const condition = parseFilter(column.groupFilter, findDesignerFilterType(column, this.designer));
- if (condition) {
- select.having = mergeConditions(
- select.having,
- _.cloneDeepWith(condition, expr => {
- if (expr.exprType == 'placeholder') {
- return this.getColumnOutputExpression(column, selectIsGrouped);
- }
- })
- );
- }
- }
+ const additionalGroupFilterCount = this.designer.settings?.additionalGroupFilterCount || 0;
+ const filterFields = [
+ 'groupFilter',
+ ..._.range(additionalGroupFilterCount).map(index => `additionalGroupFilter${index + 1}`),
+ ];
+ this.addConditionsCore(select, tables, filterFields, 'having', column =>
+ this.getColumnResultField(column, selectIsGrouped)
+ );
}
- getColumnOutputExpression(col, selectIsGrouped): ResultField {
+ getColumnExpression(col): Expression {
const source = findQuerySource(this.designer, col.designerId);
+ const { columnName, isCustomExpression, customExpression } = col;
+
+ const res: Expression = isCustomExpression
+ ? {
+ exprType: 'raw',
+ sql: customExpression,
+ }
+ : {
+ exprType: 'column',
+ columnName,
+ source,
+ };
+ return res;
+ }
+
+ getColumnResultField(col, selectIsGrouped): ResultField {
const { columnName } = col;
let { alias } = col;
+
+ const exprCore = this.getColumnExpression(col);
+
if (selectIsGrouped && !col.isGrouped) {
// use aggregate
const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate;
@@ -142,20 +191,12 @@ export class DesignerQueryDumper {
func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate,
argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null,
alias,
- args: [
- {
- exprType: 'column',
- columnName,
- source,
- },
- ],
+ args: [exprCore],
};
} else {
return {
- exprType: 'column',
- columnName,
+ ...exprCore,
alias,
- source,
};
}
}
@@ -179,24 +220,21 @@ export class DesignerQueryDumper {
}
}
- const topLevelColumns = (this.designer.columns || []).filter(col =>
- topLevelTables.find(tbl => tbl.designerId == col.designerId)
+ const topLevelColumns = (this.designer.columns || []).filter(
+ col =>
+ topLevelTables.find(tbl => tbl.designerId == col.designerId) || (col.isCustomExpression && col.customExpression)
);
const selectIsGrouped = !!topLevelColumns.find(x => x.isGrouped || (x.aggregate && x.aggregate != '---'));
const outputColumns = topLevelColumns.filter(x => x.isOutput);
if (outputColumns.length == 0) {
res.selectAll = true;
} else {
- res.columns = outputColumns.map(col => this.getColumnOutputExpression(col, selectIsGrouped));
+ res.columns = outputColumns.map(col => this.getColumnResultField(col, selectIsGrouped));
}
const groupedColumns = topLevelColumns.filter(x => x.isGrouped);
if (groupedColumns.length > 0) {
- res.groupBy = groupedColumns.map(col => ({
- exprType: 'column',
- columnName: col.columnName,
- source: findQuerySource(this.designer, col.designerId),
- }));
+ res.groupBy = groupedColumns.map(col => this.getColumnExpression(col));
}
const orderColumns = _.sortBy(
@@ -205,10 +243,8 @@ export class DesignerQueryDumper {
);
if (orderColumns.length > 0) {
res.orderBy = orderColumns.map(col => ({
- exprType: 'column',
+ ...this.getColumnExpression(col),
direction: col.sortOrder < 0 ? 'DESC' : 'ASC',
- columnName: col.columnName,
- source: findQuerySource(this.designer, col.designerId),
}));
}
diff --git a/packages/web/src/designer/types.ts b/packages/web/src/designer/types.ts
index 000549de2..d42f2e076 100644
--- a/packages/web/src/designer/types.ts
+++ b/packages/web/src/designer/types.ts
@@ -31,10 +31,14 @@ export type DesignerColumnInfo = {
sortOrder?: number;
filter?: string;
groupFilter?: string;
+ isCustomExpression?: boolean;
+ customExpression?: string;
};
export type DesignerSettings = {
isDistinct?: boolean;
+ additionalFilterCount?: number;
+ additionalGroupFilterCount?: number;
};
export type DesignerInfo = {
diff --git a/packages/web/src/elements/QueryDesignColumns.svelte b/packages/web/src/elements/QueryDesignColumns.svelte
index c643d2ec1..d9bc26d4d 100644
--- a/packages/web/src/elements/QueryDesignColumns.svelte
+++ b/packages/web/src/elements/QueryDesignColumns.svelte
@@ -13,8 +13,11 @@
import SelectField from '../forms/SelectField.svelte';
import TextField from '../forms/TextField.svelte';
import InlineButton from '../buttons/InlineButton.svelte';
+ import uuidv1 from 'uuid/v1';
import TableControl from './TableControl.svelte';
+ import FormStyledButton from '../buttons/FormStyledButton.svelte';
+ import _ from 'lodash';
export let value;
export let onChange;
@@ -35,8 +38,56 @@
}));
};
+ const addExpressionColumn = () => {
+ onChange(current => ({
+ ...current,
+ columns: [...(current.columns || []), { isCustomExpression: true, isOutput: true, designerId: uuidv1() }],
+ }));
+ };
+
+ const addOrCondition = () => {
+ onChange(current => ({
+ ...current,
+ settings: {
+ ...current?.settings,
+ additionalFilterCount: (current?.settings?.additionalFilterCount ?? 0) + 1,
+ },
+ }));
+ };
+
+ const removeOrCondition = () => {
+ onChange(current => ({
+ ...current,
+ settings: {
+ ...current?.settings,
+ additionalFilterCount: (current?.settings?.additionalFilterCount ?? 1) - 1,
+ },
+ }));
+ };
+
+ const addGroupOrCondition = () => {
+ onChange(current => ({
+ ...current,
+ settings: {
+ ...current?.settings,
+ additionalGroupFilterCount: (current?.settings?.additionalGroupFilterCount ?? 0) + 1,
+ },
+ }));
+ };
+
+ const removeGroupOrCondition = () => {
+ onChange(current => ({
+ ...current,
+ settings: {
+ ...current?.settings,
+ additionalGroupFilterCount: (current?.settings?.additionalGroupFilterCount ?? 1) - 1,
+ },
+ }));
+ };
+
$: columns = value?.columns;
$: tables = value?.tables;
+ $: settings = value?.settings;
$: hasGroupedColumn = !!(columns || []).find(x => x.isGrouped);
@@ -44,18 +95,49 @@