mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-28 23:26:00 +00:00
Merge branch 'develop'
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -8,6 +8,16 @@ Builds:
|
||||
- linux - application for linux
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.1.4",
|
||||
"version": "5.1.5-beta.4",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
||||
@@ -177,7 +177,7 @@ async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) {
|
||||
const res = await driver.query(systemConnection, sql);
|
||||
process.send({ msgtype: 'response', msgid, ...res });
|
||||
} catch (err) {
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||
process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import _zip from 'lodash/zip';
|
||||
import _difference from 'lodash/difference';
|
||||
import debug from 'debug';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { PerspectiveDataPattern } from './PerspectiveDataPattern';
|
||||
|
||||
const dbg = debug('dbgate:PerspectiveCache');
|
||||
|
||||
@@ -34,6 +35,7 @@ export class PerspectiveCacheTable {
|
||||
pureName: string;
|
||||
bindingColumns?: string[];
|
||||
dataColumns: string[];
|
||||
allColumns?: boolean;
|
||||
loadedAll: boolean;
|
||||
loadedRows: any[] = [];
|
||||
bindingGroups: { [bindingKey: string]: PerspectiveBindingGroup } = {};
|
||||
@@ -86,14 +88,23 @@ export class PerspectiveCache {
|
||||
constructor() {}
|
||||
|
||||
tables: { [tableKey: string]: PerspectiveCacheTable } = {};
|
||||
dataPatterns: PerspectiveDataPattern[] = [];
|
||||
|
||||
getTableCache(props: PerspectiveDataLoadProps) {
|
||||
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];
|
||||
|
||||
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);
|
||||
|
||||
// we have incomplete cache
|
||||
@@ -113,5 +124,6 @@ export class PerspectiveCache {
|
||||
|
||||
clear() {
|
||||
this.tables = {};
|
||||
this.dataPatterns = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,13 @@ import uuidv1 from 'uuid/v1';
|
||||
// uncheckedColumns: string[];
|
||||
// }
|
||||
|
||||
export type PerspectiveDatabaseEngineType = 'sqldb' | 'docdb';
|
||||
|
||||
export interface PerspectiveDatabaseConfig {
|
||||
conid: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
export interface PerspectiveCustomJoinConfig {
|
||||
refNodeDesignerId: string;
|
||||
referenceDesignerId: string;
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
||||
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
|
||||
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');
|
||||
|
||||
export class PerspectiveDataLoader {
|
||||
constructor(public apiCall) {}
|
||||
|
||||
buildCondition(props: PerspectiveDataLoadProps): Condition {
|
||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
||||
buildSqlCondition(props: PerspectiveDataLoadProps): Condition {
|
||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, sqlCondition } = props;
|
||||
|
||||
const conditions = [];
|
||||
|
||||
if (condition) {
|
||||
conditions.push(condition);
|
||||
if (sqlCondition) {
|
||||
conditions.push(sqlCondition);
|
||||
}
|
||||
|
||||
if (bindingColumns?.length == 1) {
|
||||
@@ -38,8 +59,26 @@ export class PerspectiveDataLoader {
|
||||
: null;
|
||||
}
|
||||
|
||||
async loadGrouping(props: PerspectiveDataLoadProps) {
|
||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props;
|
||||
buildMongoCondition(props: PerspectiveDataLoadProps): {} {
|
||||
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(
|
||||
columnName =>
|
||||
@@ -71,13 +110,13 @@ export class PerspectiveDataLoader {
|
||||
},
|
||||
...bindingColumnExpressions,
|
||||
],
|
||||
where: this.buildCondition(props),
|
||||
where: this.buildSqlCondition(props),
|
||||
};
|
||||
|
||||
select.groupBy = bindingColumnExpressions;
|
||||
|
||||
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', {
|
||||
@@ -93,8 +132,63 @@ export class PerspectiveDataLoader {
|
||||
}));
|
||||
}
|
||||
|
||||
async loadData(props: PerspectiveDataLoadProps) {
|
||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
||||
async loadGroupingDocDb(props: PerspectiveDataLoadProps) {
|
||||
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) {
|
||||
return [];
|
||||
@@ -113,16 +207,19 @@ export class PerspectiveDataLoader {
|
||||
},
|
||||
})),
|
||||
selectAll: !dataColumns,
|
||||
orderBy: orderBy?.map(({ columnName, order }) => ({
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
direction: order,
|
||||
source: {
|
||||
name: { schemaName, pureName },
|
||||
},
|
||||
})),
|
||||
orderBy:
|
||||
orderBy?.length > 0
|
||||
? orderBy?.map(({ columnName, order }) => ({
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
direction: order,
|
||||
source: {
|
||||
name: { schemaName, pureName },
|
||||
},
|
||||
}))
|
||||
: null,
|
||||
range: props.range,
|
||||
where: this.buildCondition(props),
|
||||
where: this.buildSqlCondition(props),
|
||||
};
|
||||
|
||||
if (dbg?.enabled) {
|
||||
@@ -143,8 +240,76 @@ export class PerspectiveDataLoader {
|
||||
return response.rows;
|
||||
}
|
||||
|
||||
async loadRowCount(props: PerspectiveDataLoadProps) {
|
||||
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
|
||||
getDocDbLoadOptions(props: PerspectiveDataLoadProps, useSort: boolean) {
|
||||
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 = {
|
||||
commandType: 'select',
|
||||
@@ -158,7 +323,7 @@ export class PerspectiveDataLoader {
|
||||
alias: 'count',
|
||||
},
|
||||
],
|
||||
where: this.buildCondition(props),
|
||||
where: this.buildSqlCondition(props),
|
||||
};
|
||||
|
||||
const response = await this.apiCall('database-connections/sql-select', {
|
||||
@@ -170,4 +335,39 @@ export class PerspectiveDataLoader {
|
||||
if (response.errorMessage) return response;
|
||||
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 { Condition } from 'dbgate-sqltree';
|
||||
import { RangeDefinition } from 'dbgate-types';
|
||||
import { format } from 'path';
|
||||
import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache';
|
||||
import { PerspectiveDataLoader } from './PerspectiveDataLoader';
|
||||
import { PerspectiveDataPatternDict } from './PerspectiveDataPattern';
|
||||
import { PerspectiveDatabaseConfig, PerspectiveDatabaseEngineType } from './PerspectiveConfig';
|
||||
|
||||
export const PERSPECTIVE_PAGE_SIZE = 100;
|
||||
|
||||
const dbg = debug('dbgate:PerspectiveDataProvider');
|
||||
|
||||
export interface PerspectiveDatabaseConfig {
|
||||
conid: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
export interface PerspectiveDataLoadProps {
|
||||
databaseConfig: PerspectiveDatabaseConfig;
|
||||
schemaName: string;
|
||||
schemaName?: string;
|
||||
pureName: string;
|
||||
dataColumns: string[];
|
||||
dataColumns?: string[];
|
||||
allColumns?: boolean;
|
||||
orderBy: {
|
||||
columnName: string;
|
||||
order: 'ASC' | 'DESC';
|
||||
@@ -27,11 +24,17 @@ export interface PerspectiveDataLoadProps {
|
||||
bindingValues?: any[][];
|
||||
range?: RangeDefinition;
|
||||
topCount?: number;
|
||||
condition?: Condition;
|
||||
sqlCondition?: Condition;
|
||||
mongoCondition?: any;
|
||||
engineType: PerspectiveDatabaseEngineType;
|
||||
}
|
||||
|
||||
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 }> {
|
||||
dbg('load data', props);
|
||||
// console.log('LOAD DATA', props);
|
||||
@@ -182,6 +185,7 @@ export class PerspectiveDataProvider {
|
||||
|
||||
// load missing rows
|
||||
tableCache.dataColumns = props.dataColumns;
|
||||
tableCache.allColumns = props.allColumns;
|
||||
|
||||
const nextRows = await this.loader.loadData({
|
||||
...props,
|
||||
|
||||
@@ -3,6 +3,8 @@ import _max from 'lodash/max';
|
||||
import _range from 'lodash/max';
|
||||
import _fill from 'lodash/fill';
|
||||
import _findIndex from 'lodash/findIndex';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
import _isArray from 'lodash/isArray';
|
||||
import debug from 'debug';
|
||||
|
||||
const dbg = debug('dbgate:PerspectiveDisplay');
|
||||
@@ -126,14 +128,14 @@ export class PerspectiveDisplay {
|
||||
|
||||
fillColumns(children: PerspectiveTreeNode[], parentNodes: PerspectiveTreeNode[]) {
|
||||
for (const child of children) {
|
||||
if (child.isCheckedColumn || child.isCheckedNode) {
|
||||
if (child.generatesHiearchicGridColumn || child.generatesDataGridColumn) {
|
||||
this.processColumn(child, parentNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processColumn(node: PerspectiveTreeNode, parentNodes: PerspectiveTreeNode[]) {
|
||||
if (node.isCheckedColumn) {
|
||||
if (node.generatesDataGridColumn) {
|
||||
const column = new PerspectiveDisplayColumn(this);
|
||||
column.title = node.columnTitle;
|
||||
column.dataField = node.dataField;
|
||||
@@ -145,7 +147,7 @@ export class PerspectiveDisplay {
|
||||
this.columns.push(column);
|
||||
}
|
||||
|
||||
if (node.isExpandable && node.isCheckedNode) {
|
||||
if (node.generatesHiearchicGridColumn) {
|
||||
const countBefore = this.columns.length;
|
||||
this.fillColumns(node.childNodes, [...parentNodes, node]);
|
||||
|
||||
@@ -167,13 +169,30 @@ export class PerspectiveDisplay {
|
||||
// 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[] {
|
||||
// console.log('********** COLLECT ROWS', sourceRows);
|
||||
const columnNodes = nodes.filter(x => x.isCheckedColumn);
|
||||
const treeNodes = nodes.filter(x => x.isCheckedNode);
|
||||
const columnNodes = nodes.filter(x => x.generatesDataGridColumn);
|
||||
const treeNodes = nodes.filter(x => x.generatesHiearchicGridColumn);
|
||||
|
||||
// console.log('columnNodes', columnNodes);
|
||||
// console.log('treeNodes', treeNodes);
|
||||
// console.log(
|
||||
// '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));
|
||||
|
||||
@@ -181,13 +200,14 @@ export class PerspectiveDisplay {
|
||||
for (const sourceRow of sourceRows) {
|
||||
// console.log('PROCESS SOURCE', sourceRow);
|
||||
// row.startIndex = startIndex;
|
||||
const rowData = columnNodes.map(node => sourceRow[node.codeName]);
|
||||
const rowData = columnNodes.map(node => sourceRow[node.columnName]);
|
||||
const subRowCollections = [];
|
||||
|
||||
for (const node of treeNodes) {
|
||||
// console.log('sourceRow[node.fieldName]', node.fieldName, sourceRow[node.fieldName]);
|
||||
if (sourceRow[node.fieldName]) {
|
||||
const subrows = {
|
||||
rows: this.collectRows(sourceRow[node.fieldName], node.childNodes),
|
||||
rows: this.collectRows(this.extractArray(sourceRow[node.fieldName]), node.childNodes),
|
||||
};
|
||||
subRowCollections.push(subrows);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CollectionInfo,
|
||||
ColumnInfo,
|
||||
DatabaseInfo,
|
||||
ForeignKeyInfo,
|
||||
@@ -7,13 +8,15 @@ import {
|
||||
TableInfo,
|
||||
ViewInfo,
|
||||
} from 'dbgate-types';
|
||||
import { equalFullName } from 'dbgate-tools';
|
||||
import { equalFullName, isCollectionInfo, isTableInfo, isViewInfo } from 'dbgate-tools';
|
||||
import {
|
||||
ChangePerspectiveConfigFunc,
|
||||
createPerspectiveNodeConfig,
|
||||
MultipleDatabaseInfo,
|
||||
PerspectiveConfig,
|
||||
PerspectiveCustomJoinConfig,
|
||||
PerspectiveDatabaseConfig,
|
||||
PerspectiveDatabaseEngineType,
|
||||
PerspectiveFilterColumnInfo,
|
||||
PerspectiveNodeConfig,
|
||||
PerspectiveReferenceConfig,
|
||||
@@ -27,17 +30,14 @@ import _uniqBy from 'lodash/uniqBy';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _cloneDeepWith from 'lodash/cloneDeepWith';
|
||||
import _findIndex from 'lodash/findIndex';
|
||||
import {
|
||||
PerspectiveDatabaseConfig,
|
||||
PerspectiveDataLoadProps,
|
||||
PerspectiveDataProvider,
|
||||
} from './PerspectiveDataProvider';
|
||||
import { PerspectiveDataLoadProps, PerspectiveDataProvider } from './PerspectiveDataProvider';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { getFilterType, parseFilter } from 'dbgate-filterparser';
|
||||
import { FilterType } from 'dbgate-filterparser/lib/types';
|
||||
import { Condition, Expression, Select } from 'dbgate-sqltree';
|
||||
// import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import { PerspectiveDataPatternColumn } from './PerspectiveDataPattern';
|
||||
|
||||
export interface PerspectiveDataLoadPropsWithNode {
|
||||
props: PerspectiveDataLoadProps;
|
||||
@@ -79,7 +79,7 @@ export abstract class PerspectiveTreeNode {
|
||||
this.parentNodeConfig = parentNode?.nodeConfig;
|
||||
}
|
||||
readonly nodeConfig: PerspectiveNodeConfig;
|
||||
readonly parentNodeConfig: PerspectiveNodeConfig;
|
||||
parentNodeConfig: PerspectiveNodeConfig;
|
||||
// defaultChecked: boolean;
|
||||
abstract get title();
|
||||
abstract get codeName();
|
||||
@@ -108,6 +108,18 @@ export abstract class PerspectiveTreeNode {
|
||||
get namedObject(): NamedObjectInfo {
|
||||
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;
|
||||
get isRoot() {
|
||||
return this.parentNode == null;
|
||||
@@ -119,6 +131,12 @@ export abstract class PerspectiveTreeNode {
|
||||
get isSortable() {
|
||||
return false;
|
||||
}
|
||||
get generatesHiearchicGridColumn() {
|
||||
return this.isExpandable && this.isCheckedNode;
|
||||
}
|
||||
get generatesDataGridColumn() {
|
||||
return this.isCheckedColumn;
|
||||
}
|
||||
matchChildRow(parentRow: any, childRow: any): boolean {
|
||||
return true;
|
||||
}
|
||||
@@ -271,14 +289,15 @@ export abstract class PerspectiveTreeNode {
|
||||
[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,
|
||||
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),
|
||||
..._flatten(this.childNodes.filter(x => x.isExpandable && x.isChecked).map(x => x.getChildMatchColumns())),
|
||||
...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([
|
||||
...this.childNodes.map(x => x.parseFilterCondition(source)),
|
||||
...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(
|
||||
this.childNodes.map(node => {
|
||||
const sort = this.nodeConfig?.sort?.find(x => x.columnName == node.columnName);
|
||||
@@ -325,11 +359,15 @@ export abstract class PerspectiveTreeNode {
|
||||
}
|
||||
})
|
||||
);
|
||||
return res.length > 0
|
||||
? res
|
||||
: (table as TableInfo)?.primaryKey?.columns.map(x => ({ columnName: x.columnName, order: 'ASC' })) || [
|
||||
{ columnName: table?.columns[0].columnName, order: 'ASC' },
|
||||
];
|
||||
if (res.length > 0) return res;
|
||||
const pkColumns = (table as TableInfo)?.primaryKey?.columns.map(x => ({
|
||||
columnName: x.columnName,
|
||||
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() {
|
||||
@@ -390,7 +428,9 @@ export abstract class PerspectiveTreeNode {
|
||||
return (
|
||||
(this.parentNode?.isRoot || this.parentNode?.supportsParentFilter) &&
|
||||
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',
|
||||
conditions: _compact([
|
||||
...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 {
|
||||
if (!this.foreignKey) return false;
|
||||
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(),
|
||||
databaseConfig: this.databaseConfig,
|
||||
orderBy: this.getOrderBy(this.refTable),
|
||||
condition: this.getChildrenCondition(),
|
||||
sqlCondition: this.getChildrenSqlCondition(),
|
||||
engineType: 'sqldb',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -573,6 +618,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
||||
|
||||
get fieldName() {
|
||||
return this.codeName + 'Ref';
|
||||
// return this.codeName ;
|
||||
}
|
||||
|
||||
get title() {
|
||||
@@ -670,6 +716,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
|
||||
pureName: this.foreignKey.refTableName,
|
||||
conid: this.databaseConfig.conid,
|
||||
database: this.databaseConfig.database,
|
||||
objectTypeField: this.table.objectTypeField,
|
||||
};
|
||||
}
|
||||
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 {
|
||||
constructor(
|
||||
public table: TableInfo | ViewInfo,
|
||||
public table: TableInfo | ViewInfo | CollectionInfo,
|
||||
dbs: MultipleDatabaseInfo,
|
||||
config: PerspectiveConfig,
|
||||
setConfig: ChangePerspectiveConfigFunc,
|
||||
@@ -707,14 +961,22 @@ export class PerspectiveTableNode extends PerspectiveTreeNode {
|
||||
super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId);
|
||||
}
|
||||
|
||||
get engineType(): PerspectiveDatabaseEngineType {
|
||||
return isCollectionInfo(this.table) ? 'docdb' : 'sqldb';
|
||||
}
|
||||
|
||||
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
||||
const isMongo = isCollectionInfo(this.table);
|
||||
return {
|
||||
schemaName: this.table.schemaName,
|
||||
pureName: this.table.pureName,
|
||||
dataColumns: this.getDataLoadColumns(),
|
||||
allColumns: isMongo,
|
||||
databaseConfig: this.databaseConfig,
|
||||
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,
|
||||
conid: this.databaseConfig.conid,
|
||||
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 {
|
||||
constructor(
|
||||
public foreignKey: ForeignKeyInfo,
|
||||
@@ -872,7 +1077,8 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode {
|
||||
dataColumns: this.getDataLoadColumns(),
|
||||
databaseConfig: this.databaseConfig,
|
||||
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 {
|
||||
constructor(
|
||||
public customJoin: PerspectiveCustomJoinConfig,
|
||||
table: TableInfo | ViewInfo,
|
||||
table: TableInfo | ViewInfo | CollectionInfo,
|
||||
dbs: MultipleDatabaseInfo,
|
||||
config: PerspectiveConfig,
|
||||
setConfig: ChangePerspectiveConfigFunc,
|
||||
@@ -966,6 +1172,8 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
||||
getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
|
||||
// console.log('CUSTOM JOIN', this.customJoin);
|
||||
// console.log('this.getDataLoadColumns()', this.getDataLoadColumns());
|
||||
const isMongo = isCollectionInfo(this.table);
|
||||
|
||||
return {
|
||||
schemaName: this.table.schemaName,
|
||||
pureName: this.table.pureName,
|
||||
@@ -975,9 +1183,12 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode {
|
||||
stableStringify
|
||||
),
|
||||
dataColumns: this.getDataLoadColumns(),
|
||||
allColumns: isMongo,
|
||||
databaseConfig: this.databaseConfig,
|
||||
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(
|
||||
table: TableInfo | ViewInfo,
|
||||
table: TableInfo | ViewInfo | CollectionInfo,
|
||||
dbs: MultipleDatabaseInfo,
|
||||
config: PerspectiveConfig,
|
||||
setConfig: ChangePerspectiveConfigFunc,
|
||||
@@ -1093,25 +1304,59 @@ export function getTableChildPerspectiveNodes(
|
||||
if (!table) return [];
|
||||
const db = parentNode.db;
|
||||
|
||||
const columnNodes = table.columns.map(col =>
|
||||
findDesignerIdForNode(
|
||||
config,
|
||||
parentNode,
|
||||
designerId =>
|
||||
new PerspectiveTableColumnNode(
|
||||
col,
|
||||
table,
|
||||
dbs,
|
||||
config,
|
||||
setConfig,
|
||||
dataProvider,
|
||||
databaseConfig,
|
||||
parentNode,
|
||||
designerId
|
||||
)
|
||||
)
|
||||
);
|
||||
const pattern = dataProvider?.dataPatterns?.[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)) {
|
||||
// const circularColumns = columnNodes.filter(x => x.isCircular).map(x => x.columnName);
|
||||
// const defaultColumns = getPerspectiveDefaultColumns(table, db, circularColumns);
|
||||
@@ -1173,6 +1418,7 @@ export function getTableChildPerspectiveNodes(
|
||||
const db = dbs?.[newConfig.conid]?.[newConfig.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);
|
||||
const collection = db?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||
|
||||
const join: PerspectiveCustomJoinConfig = {
|
||||
refNodeDesignerId: node.designerId,
|
||||
@@ -1189,11 +1435,11 @@ export function getTableChildPerspectiveNodes(
|
||||
: ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })),
|
||||
};
|
||||
|
||||
if (table || view) {
|
||||
if (table || view || collection) {
|
||||
customs.push(
|
||||
new PerspectiveCustomJoinTreeNode(
|
||||
join,
|
||||
table || view,
|
||||
table || view || collection,
|
||||
dbs,
|
||||
config,
|
||||
setConfig,
|
||||
@@ -1210,34 +1456,5 @@ export function getTableChildPerspectiveNodes(
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -19,3 +19,4 @@ export * from './PerspectiveDataProvider';
|
||||
export * from './PerspectiveCache';
|
||||
export * from './PerspectiveConfig';
|
||||
export * from './processPerspectiveDefaultColunns';
|
||||
export * from './PerspectiveDataPattern';
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { findForeignKeyForColumn } from 'dbgate-tools';
|
||||
import { DatabaseInfo, TableInfo, ViewInfo } from 'dbgate-types';
|
||||
import { createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig } from './PerspectiveConfig';
|
||||
import { PerspectiveDataPattern, PerspectiveDataPatternDict } from './PerspectiveDataPattern';
|
||||
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(
|
||||
table: TableInfo | ViewInfo,
|
||||
db: DatabaseInfo,
|
||||
@@ -10,13 +19,7 @@ function getPerspectiveDefaultColumns(
|
||||
): [string[], string[]] {
|
||||
const columns = table.columns.map(x => x.columnName);
|
||||
const predicates = [
|
||||
x => x.toLowerCase() == 'name',
|
||||
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'),
|
||||
...namePredicates,
|
||||
x =>
|
||||
table.columns
|
||||
.find(y => y.columnName == x)
|
||||
@@ -44,9 +47,20 @@ function getPerspectiveDefaultColumns(
|
||||
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(
|
||||
config: PerspectiveConfig,
|
||||
dbInfos: MultipleDatabaseInfo,
|
||||
dataPatterns: PerspectiveDataPatternDict,
|
||||
conid: 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 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;
|
||||
@@ -66,18 +82,20 @@ export function perspectiveNodesHaveStructure(
|
||||
export function shouldProcessPerspectiveDefaultColunns(
|
||||
config: PerspectiveConfig,
|
||||
dbInfos: MultipleDatabaseInfo,
|
||||
dataPatterns: PerspectiveDataPatternDict,
|
||||
conid: string,
|
||||
database: string
|
||||
) {
|
||||
const nodesNotProcessed = config.nodes.filter(x => !x.defaultColumnsProcessed);
|
||||
if (nodesNotProcessed.length == 0) return false;
|
||||
|
||||
return perspectiveNodesHaveStructure(config, dbInfos, conid, database);
|
||||
return perspectiveNodesHaveStructure(config, dbInfos, dataPatterns, conid, database);
|
||||
}
|
||||
|
||||
function processPerspectiveDefaultColunnsStep(
|
||||
config: PerspectiveConfig,
|
||||
dbInfos: MultipleDatabaseInfo,
|
||||
dataPatterns: PerspectiveDataPatternDict,
|
||||
conid: string,
|
||||
database: string
|
||||
) {
|
||||
@@ -107,6 +125,7 @@ function processPerspectiveDefaultColunnsStep(
|
||||
|
||||
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 collection = db.collections.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName);
|
||||
|
||||
if (table || view) {
|
||||
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;
|
||||
@@ -199,11 +234,12 @@ function markAllProcessed(config: PerspectiveConfig): PerspectiveConfig {
|
||||
export function processPerspectiveDefaultColunns(
|
||||
config: PerspectiveConfig,
|
||||
dbInfos: MultipleDatabaseInfo,
|
||||
dataPatterns: PerspectiveDataPatternDict,
|
||||
conid: string,
|
||||
database: string
|
||||
): PerspectiveConfig {
|
||||
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) {
|
||||
return markAllProcessed(config);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TableInfo } from 'dbgate-types';
|
||||
import { PerspectiveDisplay } from '../PerspectiveDisplay';
|
||||
import { PerspectiveTableNode } from '../PerspectiveTreeNode';
|
||||
import { chinookDbInfo } from './chinookDbInfo';
|
||||
@@ -13,6 +12,7 @@ test('test flat view', () => {
|
||||
const configColumns = processPerspectiveDefaultColunns(
|
||||
createPerspectiveConfig({ pureName: 'Artist' }),
|
||||
{ conid: { db: chinookDbInfo } },
|
||||
null,
|
||||
'conid',
|
||||
'db'
|
||||
);
|
||||
@@ -47,7 +47,7 @@ test('test one level nesting', () => {
|
||||
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' });
|
||||
// config.nodes[0].checkedColumns = ['Album'];
|
||||
@@ -107,7 +107,7 @@ test('test two level nesting', () => {
|
||||
designerId: '2',
|
||||
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(
|
||||
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']);
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
export function addTableDependencies(db: DatabaseInfo): DatabaseInfo {
|
||||
@@ -118,3 +118,15 @@ export function isTableColumnUnique(table: TableInfo, column: string) {
|
||||
}
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -345,6 +345,12 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Design perspective query',
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
|
||||
@@ -605,7 +605,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.replace',
|
||||
category,
|
||||
keyText: 'CtrlOrCommand+H',
|
||||
keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H',
|
||||
name: 'Replace',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().replace(),
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
id: 'dataGrid.hideColumn',
|
||||
category: 'Data grid',
|
||||
name: 'Hide column',
|
||||
keyText: 'CtrlOrCommand+H',
|
||||
keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H',
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().hideColumn(),
|
||||
});
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
}
|
||||
|
||||
$: sortOrderProps = settings?.getSortOrderProps ? settings?.getSortOrderProps(designerId, column.columnName) : null;
|
||||
$: iconOverride = settings?.getColumnIconOverride
|
||||
? settings?.getColumnIconOverride(designerId, column.columnName)
|
||||
: null;
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -144,7 +147,7 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<ColumnLabel {...column} {foreignKey} forceIcon />
|
||||
<ColumnLabel {...column} {foreignKey} forceIcon {iconOverride} />
|
||||
{#if designerColumn?.filter}
|
||||
<FontIcon icon="img filter" />
|
||||
{/if}
|
||||
|
||||
@@ -479,7 +479,7 @@
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
var json = JSON.parse(data);
|
||||
const { objectTypeField } = json;
|
||||
if (objectTypeField != 'tables' && objectTypeField != 'views') return;
|
||||
if (objectTypeField != 'tables' && objectTypeField != 'views' && objectTypeField != 'collections') return;
|
||||
json.designerId = `${json.pureName}-${uuidv1()}`;
|
||||
json.left = e.clientX - rect.left;
|
||||
json.top = e.clientY - rect.top;
|
||||
@@ -941,6 +941,7 @@
|
||||
.empty {
|
||||
margin: 50px;
|
||||
font-size: 20px;
|
||||
position: absolute;
|
||||
}
|
||||
.canvas {
|
||||
position: relative;
|
||||
|
||||
@@ -213,6 +213,8 @@
|
||||
!isMultipleTableSelection && [{ divider: true }, createDatabaseObjectMenu({ ...table, conid, database })],
|
||||
];
|
||||
}
|
||||
|
||||
// $: console.log('COLUMNS', columns);
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -238,6 +240,7 @@
|
||||
class:isGrayed
|
||||
class:isTable={objectTypeField == 'tables'}
|
||||
class:isView={objectTypeField == 'views'}
|
||||
class:isCollection={objectTypeField == 'collections'}
|
||||
use:moveDrag={settings?.canSelectColumns ? [handleMoveStart, handleMove, handleMoveEnd] : null}
|
||||
use:contextMenu={settings?.canSelectColumns ? createMenu : '__no_menu'}
|
||||
style={getTableColorStyle($currentThemeDefinition, table)}
|
||||
@@ -358,6 +361,10 @@
|
||||
.header.isView {
|
||||
background: var(--theme-bg-magenta);
|
||||
}
|
||||
.header.isCollection {
|
||||
background: var(--theme-bg-red);
|
||||
}
|
||||
|
||||
.header.isGrayed {
|
||||
background: var(--theme-bg-2);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
export let foreignKey;
|
||||
export let conid = undefined;
|
||||
export let database = undefined;
|
||||
export let iconOverride = undefined;
|
||||
|
||||
$: icon = getColumnIcon($$props, forceIcon);
|
||||
$: icon = iconOverride || getColumnIcon($$props, forceIcon);
|
||||
</script>
|
||||
|
||||
<span class="label" class:notNull>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{:else}
|
||||
<span class="null"> (no image)</span>
|
||||
{/if}
|
||||
{:else if _.isArray(value) || _.isPlainObject(value)}
|
||||
{:else if !value.$oid && (_.isArray(value) || _.isPlainObject(value))}
|
||||
<JSONTree {value} slicedKeyCount={1} disableContextMenu />
|
||||
{:else}
|
||||
<CellValue {rowData} {value} />
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
createPerspectiveNodeConfig,
|
||||
MultipleDatabaseInfo,
|
||||
PerspectiveConfig,
|
||||
PerspectiveDataPatternDict,
|
||||
perspectiveNodesHaveStructure,
|
||||
PerspectiveTreeNode,
|
||||
switchPerspectiveReferenceDirection,
|
||||
} from 'dbgate-datalib';
|
||||
import { CollectionInfo } from 'dbgate-types';
|
||||
import _ from 'lodash';
|
||||
import { tick } from 'svelte';
|
||||
import runCommand from '../commands/runCommand';
|
||||
@@ -18,6 +20,7 @@
|
||||
|
||||
export let config: PerspectiveConfig;
|
||||
export let dbInfos: MultipleDatabaseInfo;
|
||||
export let dataPatterns: PerspectiveDataPatternDict;
|
||||
export let root: PerspectiveTreeNode;
|
||||
|
||||
export let conid;
|
||||
@@ -27,22 +30,39 @@
|
||||
|
||||
export let onClickTableHeader = null;
|
||||
|
||||
function createDesignerModel(config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo) {
|
||||
function createDesignerModel(
|
||||
config: PerspectiveConfig,
|
||||
dbInfos: MultipleDatabaseInfo,
|
||||
dataPatterns: PerspectiveDataPatternDict
|
||||
) {
|
||||
return {
|
||||
...config,
|
||||
tables: _.compact(
|
||||
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
|
||||
);
|
||||
const view = dbInfos?.[node.conid || conid]?.[node.database || database]?.views?.find(
|
||||
x => x.pureName == node.pureName && x.schemaName == node.schemaName
|
||||
);
|
||||
if (!table && !view) return null;
|
||||
|
||||
if (collection) {
|
||||
const pattern = dataPatterns?.[node.designerId];
|
||||
if (!pattern) return null;
|
||||
collection = {
|
||||
...collection,
|
||||
columns:
|
||||
pattern?.columns.map(x => ({
|
||||
columnName: x.name,
|
||||
})) || [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!table && !view && !collection) return null;
|
||||
|
||||
const { designerId } = node;
|
||||
return {
|
||||
...(table || view),
|
||||
...(table || view || collection),
|
||||
left: node?.position?.x || 0,
|
||||
top: node?.position?.y || 0,
|
||||
alias: node.alias,
|
||||
@@ -55,7 +75,7 @@
|
||||
|
||||
function handleChange(value, skipUndoChain, settings) {
|
||||
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;
|
||||
if (settings?.isCalledFromArrange) {
|
||||
isArranged = true;
|
||||
@@ -122,11 +142,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function detectAutoArrange(config: PerspectiveConfig, dbInfos, root) {
|
||||
async function detectAutoArrange(config: PerspectiveConfig, dbInfos, dataPatterns, root) {
|
||||
if (
|
||||
root &&
|
||||
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))
|
||||
) {
|
||||
await tick();
|
||||
@@ -134,7 +154,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: detectAutoArrange(config, dbInfos, root);
|
||||
$: detectAutoArrange(config, dbInfos, dataPatterns, root);
|
||||
|
||||
// $: console.log('DESIGNER ROOT', root);
|
||||
</script>
|
||||
@@ -221,6 +241,14 @@
|
||||
const orderIndex = sort.length > 1 ? _.findIndex(sort, x => x.columnName == columnName) : -1;
|
||||
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) => {
|
||||
return !!config.nodes.find(x => x.designerId == designerId)?.filters?.[columnName];
|
||||
},
|
||||
@@ -277,6 +305,6 @@
|
||||
onClickTableHeader,
|
||||
}}
|
||||
referenceComponent={QueryDesignerReference}
|
||||
value={createDesignerModel(config, dbInfos)}
|
||||
value={createDesignerModel(config, dbInfos, dataPatterns)}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
ChangePerspectiveConfigFunc,
|
||||
PerspectiveConfig,
|
||||
PerspectiveDisplay,
|
||||
PerspectivePatternColumnNode,
|
||||
PerspectiveTableColumnNode,
|
||||
PerspectiveTreeNode,
|
||||
PERSPECTIVE_PAGE_SIZE,
|
||||
@@ -41,6 +42,24 @@
|
||||
import { getFilterValueExpression } from 'dbgate-filterparser';
|
||||
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');
|
||||
export const activator = createActivator('PerspectiveTable', true, ['Designer']);
|
||||
|
||||
@@ -210,24 +229,28 @@
|
||||
const tableNode = root?.findNodeByDesignerId(tableNodeDesignerId);
|
||||
|
||||
if (tableNode?.headerTableAttributes) {
|
||||
const { pureName, schemaName, conid, database } = tableNode?.headerTableAttributes;
|
||||
res.push({
|
||||
text: `Open table ${pureName}`,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: 'img table',
|
||||
tabComponent: 'TableDataTab',
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid: conid,
|
||||
database: database,
|
||||
objectTypeField: 'tables',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
const { pureName, schemaName, conid, database, objectTypeField } = tableNode?.headerTableAttributes;
|
||||
console.log('objectTypeField', objectTypeField);
|
||||
const tab = TABS_BY_FIELD[objectTypeField];
|
||||
if (tab) {
|
||||
res.push({
|
||||
text: `Open ${tab.text} ${pureName}`,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: pureName,
|
||||
icon: tab.icon,
|
||||
tabComponent: tab.tabComponent,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid: conid,
|
||||
database: database,
|
||||
objectTypeField,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const setColumnDisplay = type => {
|
||||
@@ -291,42 +314,39 @@
|
||||
const value = display.rows[rowIndex].rowData[columnIndex];
|
||||
const { dataNode } = column;
|
||||
|
||||
if (dataNode instanceof PerspectiveTableColumnNode) {
|
||||
if (
|
||||
dataNode.filterInfo &&
|
||||
(dataNode instanceof PerspectiveTableColumnNode || dataNode instanceof PerspectivePatternColumnNode)
|
||||
) {
|
||||
const { table } = dataNode;
|
||||
let tabComponent = null;
|
||||
let icon = null;
|
||||
let objectTypeField = null;
|
||||
if (dataNode.isTable) {
|
||||
tabComponent = 'TableDataTab';
|
||||
icon = 'img table';
|
||||
objectTypeField = 'tables';
|
||||
}
|
||||
if (dataNode.isView) {
|
||||
tabComponent = 'ViewDataTab';
|
||||
icon = 'img view';
|
||||
objectTypeField = 'views';
|
||||
}
|
||||
if (tabComponent) {
|
||||
|
||||
const tab = TABS_BY_FIELD[table.objectTypeField];
|
||||
const filterExpression = getFilterValueExpression(
|
||||
value,
|
||||
dataNode instanceof PerspectiveTableColumnNode ? dataNode.column.dataType : null
|
||||
);
|
||||
|
||||
if (tab) {
|
||||
res.push({
|
||||
text: 'Open filtered table',
|
||||
text: 'Open filtered grid',
|
||||
onClick: () => {
|
||||
openNewTab(
|
||||
{
|
||||
title: table.pureName,
|
||||
icon,
|
||||
tabComponent,
|
||||
icon: tab.icon,
|
||||
tabComponent: tab.tabComponent,
|
||||
props: {
|
||||
schemaName: table.schemaName,
|
||||
pureName: table.pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
objectTypeField: table.objectTypeField,
|
||||
},
|
||||
},
|
||||
{
|
||||
grid: {
|
||||
filters: {
|
||||
[dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType),
|
||||
[dataNode.columnName]: filterExpression,
|
||||
},
|
||||
// isFormView: true,
|
||||
},
|
||||
@@ -350,7 +370,7 @@
|
||||
...n,
|
||||
filters: {
|
||||
...n.filters,
|
||||
[dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType),
|
||||
[dataNode.columnName]: filterExpression,
|
||||
},
|
||||
}
|
||||
: n
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
import { sleep } from '../utility/common';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import InlineButton from '../buttons/InlineButton.svelte';
|
||||
import { usePerspectiveDataPatterns } from '../utility/usePerspectiveDataPatterns';
|
||||
|
||||
const dbg = debug('dbgate:PerspectiveView');
|
||||
|
||||
@@ -128,17 +129,21 @@
|
||||
}
|
||||
|
||||
$: dbInfos = useMultipleDatabaseInfo(perspectiveDatabases);
|
||||
$: loader = new PerspectiveDataLoader(apiCall);
|
||||
$: dataPatterns = usePerspectiveDataPatterns({ conid, database }, config, cache, $dbInfos, loader);
|
||||
$: rootObject = config?.nodes?.find(x => x.designerId == config?.rootDesignerId);
|
||||
$: rootDb = rootObject ? $dbInfos?.[rootObject.conid || conid]?.[rootObject.database || database] : null;
|
||||
$: 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);
|
||||
$: collectionInfo = rootDb?.collections.find(
|
||||
x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName
|
||||
);
|
||||
|
||||
$: loader = new PerspectiveDataLoader(apiCall);
|
||||
$: dataProvider = new PerspectiveDataProvider(cache, loader);
|
||||
$: dataProvider = new PerspectiveDataProvider(cache, loader, $dataPatterns);
|
||||
$: root =
|
||||
tableInfo || viewInfo
|
||||
tableInfo || viewInfo || collectionInfo
|
||||
? new PerspectiveTableNode(
|
||||
tableInfo || viewInfo,
|
||||
tableInfo || viewInfo || collectionInfo,
|
||||
$dbInfos,
|
||||
config,
|
||||
setConfig,
|
||||
@@ -151,13 +156,14 @@
|
||||
$: tempRoot = root?.findNodeByDesignerId(tempRootDesignerId);
|
||||
|
||||
$: {
|
||||
if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, conid, database)) {
|
||||
setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, conid, database));
|
||||
if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, $dataPatterns, conid, database)) {
|
||||
setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, $dataPatterns, conid, database));
|
||||
}
|
||||
}
|
||||
|
||||
// $: console.log('PERSPECTIVE', config);
|
||||
// $: console.log('VIEW ROOT', root);
|
||||
// $: console.log('dataPatterns', $dataPatterns);
|
||||
</script>
|
||||
|
||||
<HorizontalSplitter initialValue={getInitialManagerSize()} bind:size={managerSize} allowCollapseChild1>
|
||||
@@ -205,6 +211,7 @@
|
||||
{database}
|
||||
{setConfig}
|
||||
dbInfos={$dbInfos}
|
||||
dataPatterns={$dataPatterns}
|
||||
{root}
|
||||
onClickTableHeader={designerId => {
|
||||
sleep(100).then(() => {
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -209,6 +209,10 @@ const driver = {
|
||||
if (options.countDocuments) {
|
||||
const count = await collection.countDocuments(convertObjectId(options.condition) || {});
|
||||
return { count };
|
||||
} else if (options.aggregate) {
|
||||
let cursor = await collection.aggregate(options.aggregate);
|
||||
const rows = await cursor.toArray();
|
||||
return { rows: rows.map(transformMongoData) };
|
||||
} else {
|
||||
// console.log('options.condition', JSON.stringify(options.condition, undefined, 2));
|
||||
let cursor = await collection.find(convertObjectId(options.condition) || {});
|
||||
|
||||
Reference in New Issue
Block a user