diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index d8875ed89..e1e82f984 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -57,8 +57,8 @@ export class PerspectiveDataLoader { return conditions.length == 1 ? conditions[0] : conditions.length > 0 ? { $and: conditions } : null; } - async loadGrouping(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props; + async loadGroupingSqlDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns } = props; const bindingColumnExpressions = bindingColumns.map( columnName => @@ -96,7 +96,7 @@ export class PerspectiveDataLoader { 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', { @@ -112,6 +112,52 @@ export class PerspectiveDataLoader { })); } + 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, diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index f86a3d3d2..746f54b93 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -8,7 +8,7 @@ import { TableInfo, ViewInfo, } from 'dbgate-types'; -import { equalFullName } from 'dbgate-tools'; +import { equalFullName, isCollectionInfo, isTableInfo, isViewInfo } from 'dbgate-tools'; import { ChangePerspectiveConfigFunc, createPerspectiveNodeConfig, @@ -879,7 +879,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { export class PerspectiveTableNode extends PerspectiveTreeNode { constructor( - public table: TableInfo | ViewInfo, + public table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -892,14 +892,16 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { } getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { + const isMongo = isCollectionInfo(this.table); return { schemaName: this.table.schemaName, pureName: this.table.pureName, dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - sqlCondition: this.getChildrenSqlCondition(), - engineType: 'sqldb', + sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), + mongoCondition: isMongo ? this.getChildrenMongoCondition() : null, + engineType: isMongo ? 'docdb' : 'sqldb', }; } @@ -956,87 +958,87 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { } } -export class PerspectiveCollectionNode extends PerspectiveTreeNode { - constructor( - public collection: CollectionInfo, - dbs: MultipleDatabaseInfo, - config: PerspectiveConfig, - setConfig: ChangePerspectiveConfigFunc, - public dataProvider: PerspectiveDataProvider, - databaseConfig: PerspectiveDatabaseConfig, - parentNode: PerspectiveTreeNode, - designerId: string - ) { - super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); - } +// export class PerspectiveCollectionNode extends PerspectiveTreeNode { +// constructor( +// public collection: CollectionInfo, +// dbs: MultipleDatabaseInfo, +// config: PerspectiveConfig, +// setConfig: ChangePerspectiveConfigFunc, +// public dataProvider: PerspectiveDataProvider, +// databaseConfig: PerspectiveDatabaseConfig, +// parentNode: PerspectiveTreeNode, +// designerId: string +// ) { +// super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); +// } - getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - dataColumns: this.getDataLoadColumns(), - databaseConfig: this.databaseConfig, - orderBy: this.getOrderBy(this.collection), - mongoCondition: this.getChildrenMongoCondition(), - engineType: 'docdb', - }; - } +// getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// dataColumns: this.getDataLoadColumns(), +// databaseConfig: this.databaseConfig, +// orderBy: this.getOrderBy(this.collection), +// mongoCondition: this.getChildrenMongoCondition(), +// engineType: 'docdb', +// }; +// } - get codeName() { - return this.collection.schemaName - ? `${this.collection.schemaName}:${this.collection.pureName}` - : this.collection.pureName; - } +// get codeName() { +// return this.collection.schemaName +// ? `${this.collection.schemaName}:${this.collection.pureName}` +// : this.collection.pureName; +// } - get title() { - return this.nodeConfig?.alias || this.collection.pureName; - } +// get title() { +// return this.nodeConfig?.alias || this.collection.pureName; +// } - get isExpandable() { - return true; - } +// get isExpandable() { +// return true; +// } - generateChildNodes(): PerspectiveTreeNode[] { - return getCollectionChildPerspectiveNodes( - this.designerId, - this.collection, - this.dbs, - this.config, - this.setConfig, - this.dataProvider, - this.databaseConfig, - this - ); - } +// generateChildNodes(): PerspectiveTreeNode[] { +// return getCollectionChildPerspectiveNodes( +// this.designerId, +// this.collection, +// this.dbs, +// this.config, +// this.setConfig, +// this.dataProvider, +// this.databaseConfig, +// this +// ); +// } - get icon() { - return 'img collection'; - } +// get icon() { +// return 'img collection'; +// } - getBaseTableFromThis() { - return this.collection; - } +// getBaseTableFromThis() { +// return this.collection; +// } - get headerTableAttributes() { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - conid: this.databaseConfig.conid, - database: this.databaseConfig.database, - }; - } +// get headerTableAttributes() { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// conid: this.databaseConfig.conid, +// database: this.databaseConfig.database, +// }; +// } - get tableCode() { - return `${this.collection.schemaName}|${this.collection.pureName}`; - } +// get tableCode() { +// return `${this.collection.schemaName}|${this.collection.pureName}`; +// } - get namedObject(): NamedObjectInfo { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - }; - } -} +// get namedObject(): NamedObjectInfo { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// }; +// } +// } // export class PerspectiveViewNode extends PerspectiveTreeNode { // constructor( @@ -1202,7 +1204,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, @@ -1234,6 +1236,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, @@ -1245,8 +1249,9 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - sqlCondition: this.getChildrenSqlCondition(), - engineType: 'sqldb', + sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), + mongoCondition: isMongo ? this.getChildrenMongoCondition() : null, + engineType: isMongo ? 'docdb' : 'sqldb', }; } @@ -1389,7 +1394,7 @@ export function getCollectionChildPerspectiveNodes( } export function getTableChildPerspectiveNodes( - table: TableInfo | ViewInfo, + table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -1400,25 +1405,48 @@ 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 => + new PerspectiveTableColumnNode( + col, + tableOrView, + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) + ) + ) || + pattern?.columns?.map(col => + findDesignerIdForNode( + config, + parentNode, + designerId => + new PerspectivePatternColumnNode( + table, + col, + 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); @@ -1480,6 +1508,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, @@ -1496,11 +1525,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, diff --git a/packages/tools/src/structureTools.ts b/packages/tools/src/structureTools.ts index f3d083514..660c5779c 100644 --- a/packages/tools/src/structureTools.ts +++ b/packages/tools/src/structureTools.ts @@ -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'; +} diff --git a/packages/web/src/perspectives/PerspectiveView.svelte b/packages/web/src/perspectives/PerspectiveView.svelte index db5e47184..a260f296b 100644 --- a/packages/web/src/perspectives/PerspectiveView.svelte +++ b/packages/web/src/perspectives/PerspectiveView.svelte @@ -28,7 +28,6 @@ import { ChangePerspectiveConfigFunc, extractPerspectiveDatabases, - PerspectiveCollectionNode, PerspectiveConfig, PerspectiveDataProvider, PerspectiveTableNode, @@ -142,20 +141,9 @@ $: dataProvider = new PerspectiveDataProvider(cache, loader, $dataPatterns); $: root = - tableInfo || viewInfo + tableInfo || viewInfo || collectionInfo ? new PerspectiveTableNode( - tableInfo || viewInfo, - $dbInfos, - config, - setConfig, - dataProvider, - { conid, database }, - null, - config.rootDesignerId - ) - : collectionInfo - ? new PerspectiveCollectionNode( - collectionInfo, + tableInfo || viewInfo || collectionInfo, $dbInfos, config, setConfig, diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/driver.js index 756c6a5a1..a120145de 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/driver.js @@ -207,6 +207,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) || {});