diff --git a/lib/store.d.ts b/lib/store.d.ts index 94b886f..05cf27e 100644 --- a/lib/store.d.ts +++ b/lib/store.d.ts @@ -1,4 +1,4 @@ -import { DeduceCreateSingleOperation, DeduceRemoveOperation, DeduceUpdateOperation, OperationResult, OperateOption, OpRecord, EntityDict, SelectOption } from "oak-domain/lib/types/Entity"; +import { DeduceCreateSingleOperation, DeduceRemoveOperation, DeduceUpdateOperation, OperationResult, OperateOption, OpRecord, EntityDict, SelectOption, AggregationResult } from "oak-domain/lib/types/Entity"; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { StorageSchema } from 'oak-domain/lib/types/Storage'; import { NodeDict } from "./types/type"; @@ -53,8 +53,8 @@ export default class TreeStore extends C protected updateAbjointRow(entity: T, operation: DeduceCreateSingleOperation | DeduceUpdateOperation | DeduceRemoveOperation, context: Cxt, option?: OP): number; protected selectAbjointRowAsync(entity: T, selection: ED[T]['Selection'], context: Cxt, option?: OP): Promise[]>; protected updateAbjointRowAsync(entity: T, operation: DeduceCreateSingleOperation | DeduceUpdateOperation | DeduceRemoveOperation, context: Cxt, option?: OP): Promise; - operateSync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult; - operateAsync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): Promise>; + protected operateSync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult; + protected operateAsync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): Promise>; /** * 计算最终结果集当中的函数,这个函数可能测试不够充分 * @param entity @@ -63,12 +63,23 @@ export default class TreeStore extends C * @param nodeDict * @param context */ - protected formExprInResult(entity: T, projection: ED[T]['Selection']['data'], data: Partial, nodeDict: NodeDict, context: Cxt): void; + private formExprInResult; private formResult; - selectSync>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[]; - selectAsync>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise[]>; - countSync>(entity: T, selection: Pick, context: Cxt, option: OP): number; - countAsync>(entity: T, selection: Pick, context: Cxt, option: OP): Promise; + /** + * 本函数把结果中的相应属性映射成一个字符串,用于GroupBy + * @param entity + * @param row + * @param projection + */ + private mappingProjectionOnRow; + private calcAggregation; + private formAggregation; + protected selectSync>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[]; + protected selectAsync>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise[]>; + protected aggregateSync>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): AggregationResult; + protected aggregateAsync>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise>; + protected countSync>(entity: T, selection: Pick, context: Cxt, option: OP): number; + protected countAsync>(entity: T, selection: Pick, context: Cxt, option: OP): Promise; private addToTxnNode; getStat(): { create: number; diff --git a/lib/store.js b/lib/store.js index 89b0368..c420b1b 100644 --- a/lib/store.js +++ b/lib/store.js @@ -5,6 +5,7 @@ var lodash_1 = require("oak-domain/lib/utils/lodash"); var assert_1 = require("oak-domain/lib/utils/assert"); var Entity_1 = require("oak-domain/lib/types/Entity"); var Demand_1 = require("oak-domain/lib/types/Demand"); +var selection_1 = require("oak-domain/lib/store/selection"); var Exception_1 = require("oak-domain/lib/types/Exception"); var Demand_2 = require("oak-domain/lib/types/Demand"); var relation_1 = require("oak-domain/lib/store/relation"); @@ -1118,6 +1119,176 @@ var TreeStore = /** @class */ (function (_super) { return rows2; } }; + /** + * 本函数把结果中的相应属性映射成一个字符串,用于GroupBy + * @param entity + * @param row + * @param projection + */ + TreeStore.prototype.mappingProjectionOnRow = function (entity, row, projection) { + var _this = this; + var key = ''; + var result = {}; + var values = []; + var mappingIter = function (entity2, row2, p2, result2) { + var e_12, _a; + var keys = Object.keys(p2).sort(function (ele1, ele2) { return ele1 < ele2 ? -1 : 1; }); + try { + for (var keys_1 = tslib_1.__values(keys), keys_1_1 = keys_1.next(); !keys_1_1.done; keys_1_1 = keys_1.next()) { + var k = keys_1_1.value; + var rel = _this.judgeRelation(entity2, k); + if (rel === 2) { + result2[k] = {}; + if (row2[k]) { + mappingIter(k, row2[k], p2[k], result2[k]); + } + } + else if (typeof rel === 'string') { + result2[k] = {}; + if (row2[k]) { + mappingIter(rel, row2[k], p2[k], result2[k]); + } + } + else { + (0, assert_1.assert)([0, 1].includes(rel)); + result2[k] = row2[k]; + (0, assert_1.assert)(['string', 'number', 'boolean'].includes(typeof row2[k])); + key += "".concat(row2[k]); + values.push(row2[k]); + } + } + } + catch (e_12_1) { e_12 = { error: e_12_1 }; } + finally { + try { + if (keys_1_1 && !keys_1_1.done && (_a = keys_1.return)) _a.call(keys_1); + } + finally { if (e_12) throw e_12.error; } + } + }; + mappingIter(entity, row, projection, result); + return { + result: result, + key: key, + values: values, + }; + }; + TreeStore.prototype.calcAggregation = function (entity, rows, aggregationData) { + var e_13, _a, e_14, _b, e_15, _c; + var ops = Object.keys(aggregationData).filter(function (ele) { return ele !== '$aggr'; }); + var result = {}; + try { + for (var rows_2 = tslib_1.__values(rows), rows_2_1 = rows_2.next(); !rows_2_1.done; rows_2_1 = rows_2.next()) { + var row = rows_2_1.value; + try { + for (var ops_1 = (e_14 = void 0, tslib_1.__values(ops)), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) { + var op = ops_1_1.value; + var values = this.mappingProjectionOnRow(entity, row, aggregationData[op]).values; + (0, assert_1.assert)(values.length === 1, "\u805A\u5408\u8FD0\u7B97\u4E2D\uFF0C".concat(op, "\u7684\u76EE\u6807\u5C5E\u6027\u591A\u4E8E1\u4E2A")); + if (op.startsWith('$max')) { + if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] < values[0])) { + result[op] = values[0]; + } + } + else if (op.startsWith('$min')) { + if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] > values[0])) { + result[op] = values[0]; + } + } + else if (op.startsWith('$sum')) { + if (![undefined, null].includes(values[0])) { + (0, assert_1.assert)(typeof values[0] === 'number', '只有number类型的属性才可以计算sum'); + if (!result.hasOwnProperty(op)) { + result[op] = values[0]; + } + else { + result[op] += values[0]; + } + } + } + else if (op.startsWith('$count')) { + if (![undefined, null].includes(values[0])) { + if (!result.hasOwnProperty(op)) { + result[op] = 1; + } + else { + result[op] += 1; + } + } + } + else { + (0, assert_1.assert)(op.startsWith('$avg')); + if (![undefined, null].includes(values[0])) { + (0, assert_1.assert)(typeof values[0] === 'number', '只有number类型的属性才可以计算avg'); + if (!result.hasOwnProperty(op)) { + result[op] = { + total: values[0], + count: 1, + }; + } + else { + result[op].total += values[0]; + result[op].count += 1; + } + } + } + } + } + catch (e_14_1) { e_14 = { error: e_14_1 }; } + finally { + try { + if (ops_1_1 && !ops_1_1.done && (_b = ops_1.return)) _b.call(ops_1); + } + finally { if (e_14) throw e_14.error; } + } + } + } + catch (e_13_1) { e_13 = { error: e_13_1 }; } + finally { + try { + if (rows_2_1 && !rows_2_1.done && (_a = rows_2.return)) _a.call(rows_2); + } + finally { if (e_13) throw e_13.error; } + } + try { + for (var ops_2 = tslib_1.__values(ops), ops_2_1 = ops_2.next(); !ops_2_1.done; ops_2_1 = ops_2.next()) { + var op = ops_2_1.value; + if (!result[op]) { + result[op] = null; + } + else if (op.startsWith('$avg')) { + result[op] = result[op].total / result[op].count; + } + } + } + catch (e_15_1) { e_15 = { error: e_15_1 }; } + finally { + try { + if (ops_2_1 && !ops_2_1.done && (_c = ops_2.return)) _c.call(ops_2); + } + finally { if (e_15) throw e_15.error; } + } + return result; + }; + TreeStore.prototype.formAggregation = function (entity, rows, aggregationData) { + var _this = this; + var $aggr = aggregationData.$aggr; + if ($aggr) { + var groups_1 = (0, lodash_1.groupBy)(rows, function (row) { + var key = _this.mappingProjectionOnRow(entity, row, $aggr).key; + return key; + }); + var result = Object.keys(groups_1).map(function (ele) { + var aggr = _this.calcAggregation(entity, groups_1[ele], aggregationData); + var r = _this.mappingProjectionOnRow(entity, groups_1[ele][0], $aggr).result; + aggr.data = r; + return aggr; + }); + return result; + } + var aggr = this.calcAggregation(entity, rows, aggregationData); + return [aggr]; + }; TreeStore.prototype.selectSync = function (entity, selection, context, option) { var _this = this; (0, assert_1.assert)(context.getCurrentTxnId()); @@ -1144,6 +1315,64 @@ var TreeStore = /** @class */ (function (_super) { }); }); }; + TreeStore.prototype.aggregateSync = function (entity, aggregation, context, option) { + var _this = this; + (0, assert_1.assert)(context.getCurrentTxnId()); + var data = aggregation.data, filter = aggregation.filter, sorter = aggregation.sorter, indexFrom = aggregation.indexFrom, count = aggregation.count; + var p = {}; + for (var k in data) { + Object.assign(p, (0, lodash_1.cloneDeep)(data[k])); + } + var selection = { + data: p, + filter: filter, + sorter: sorter, + indexFrom: indexFrom, + count: count, + }; + (0, selection_1.reinforceSelection)(this.storageSchema, entity, selection); + var result = this.cascadeSelect(entity, selection, context, Object.assign({}, option, { + dontCollect: true, + })); + // 在这里再计算所有的表达式 + result.forEach(function (ele) { return _this.formExprInResult(entity, selection.data, ele, {}, context); }); + // 最后计算Aggregation + return this.formAggregation(entity, result, aggregation.data); + }; + TreeStore.prototype.aggregateAsync = function (entity, aggregation, context, option) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var data, filter, sorter, indexFrom, count, p, k, selection, result; + var _this = this; + return tslib_1.__generator(this, function (_a) { + switch (_a.label) { + case 0: + (0, assert_1.assert)(context.getCurrentTxnId()); + data = aggregation.data, filter = aggregation.filter, sorter = aggregation.sorter, indexFrom = aggregation.indexFrom, count = aggregation.count; + p = {}; + for (k in data) { + Object.assign(p, (0, lodash_1.cloneDeep)(data[k])); + } + selection = { + data: p, + filter: filter, + sorter: sorter, + indexFrom: indexFrom, + count: count, + }; + (0, selection_1.reinforceSelection)(this.storageSchema, entity, selection); + return [4 /*yield*/, this.cascadeSelectAsync(entity, selection, context, Object.assign({}, option, { + dontCollect: true, + }))]; + case 1: + result = _a.sent(); + // 在这里再计算所有的表达式 + result.forEach(function (ele) { return _this.formExprInResult(entity, selection.data, ele, {}, context); }); + // 最后计算Aggregation + return [2 /*return*/, this.formAggregation(entity, result, aggregation.data)]; + } + }); + }); + }; TreeStore.prototype.countSync = function (entity, selection, context, option) { var result = this.selectSync(entity, Object.assign({}, selection, { data: { @@ -1206,7 +1435,7 @@ var TreeStore = /** @class */ (function (_super) { return uuid; }; TreeStore.prototype.commitSync = function (uuid) { - var e_12, _a; + var e_16, _a; (0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid), uuid); var node = this.activeTxnDict[uuid].nodeHeader; while (node) { @@ -1246,17 +1475,17 @@ var TreeStore = /** @class */ (function (_super) { waiter.fn(); } } - catch (e_12_1) { e_12 = { error: e_12_1 }; } + catch (e_16_1) { e_16 = { error: e_16_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } - finally { if (e_12) throw e_12.error; } + finally { if (e_16) throw e_16.error; } } (0, lodash_1.unset)(this.activeTxnDict, uuid); }; TreeStore.prototype.rollbackSync = function (uuid) { - var e_13, _a; + var e_17, _a; (0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid)); var node = this.activeTxnDict[uuid].nodeHeader; while (node) { @@ -1289,12 +1518,12 @@ var TreeStore = /** @class */ (function (_super) { waiter.fn(); } } - catch (e_13_1) { e_13 = { error: e_13_1 }; } + catch (e_17_1) { e_17 = { error: e_17_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } - finally { if (e_13) throw e_13.error; } + finally { if (e_17) throw e_17.error; } } (0, lodash_1.unset)(this.activeTxnDict, uuid); }; @@ -1321,7 +1550,7 @@ var TreeStore = /** @class */ (function (_super) { }; // 将输入的OpRecord同步到数据中 TreeStore.prototype.sync = function (opRecords, context, option) { - var e_14, _a, e_15, _b; + var e_18, _a, e_19, _b; var option2 = Object.assign({}, option, { dontCollect: true, dontCreateOper: true, @@ -1334,7 +1563,7 @@ var TreeStore = /** @class */ (function (_super) { var e = record.e, d = record.d; if (d instanceof Array) { try { - for (var d_1 = (e_15 = void 0, tslib_1.__values(d)), d_1_1 = d_1.next(); !d_1_1.done; d_1_1 = d_1.next()) { + for (var d_1 = (e_19 = void 0, tslib_1.__values(d)), d_1_1 = d_1.next(); !d_1_1.done; d_1_1 = d_1.next()) { var dd = d_1_1.value; if (this.store[e] && this.store[e][dd.id]) { this.updateAbjointRow(e, { @@ -1355,12 +1584,12 @@ var TreeStore = /** @class */ (function (_super) { } } } - catch (e_15_1) { e_15 = { error: e_15_1 }; } + catch (e_19_1) { e_19 = { error: e_19_1 }; } finally { try { if (d_1_1 && !d_1_1.done && (_b = d_1.return)) _b.call(d_1); } - finally { if (e_15) throw e_15.error; } + finally { if (e_19) throw e_19.error; } } } else { @@ -1435,12 +1664,12 @@ var TreeStore = /** @class */ (function (_super) { } } } - catch (e_14_1) { e_14 = { error: e_14_1 }; } + catch (e_18_1) { e_18 = { error: e_18_1 }; } finally { try { if (opRecords_1_1 && !opRecords_1_1.done && (_a = opRecords_1.return)) _a.call(opRecords_1); } - finally { if (e_14) throw e_14.error; } + finally { if (e_18) throw e_18.error; } } }; return TreeStore; diff --git a/src/store.ts b/src/store.ts index af7d95c..529dc2a 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,12 +1,13 @@ -import { cloneDeep, get, set, unset } from 'oak-domain/lib/utils/lodash'; +import { cloneDeep, get, groupBy, set, unset } from 'oak-domain/lib/utils/lodash'; import { assert } from 'oak-domain/lib/utils/assert'; import { DeduceCreateSingleOperation, DeduceFilter, DeduceSelection, EntityShape, DeduceRemoveOperation, DeduceUpdateOperation, DeduceSorter, DeduceSorterAttr, OperationResult, OperateOption, OpRecord, DeduceCreateOperationData, UpdateOpResult, RemoveOpResult, SelectOpResult, - EntityDict, SelectOption, DeleteAtAttribute + EntityDict, SelectOption, DeleteAtAttribute, AggregationResult, AggregationOp } from "oak-domain/lib/types/Entity"; import { ExpressionKey, EXPRESSION_PREFIX, NodeId, RefAttr } from 'oak-domain/lib/types/Demand'; +import { reinforceSelection } from 'oak-domain/lib/store/selection'; import { OakCongruentRowExists, OakException, OakRowUnexistedException } from 'oak-domain/lib/types/Exception'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { StorageSchema } from 'oak-domain/lib/types/Storage'; @@ -973,12 +974,12 @@ export default class TreeStore extends C } - operateSync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult { + protected operateSync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult { assert(context.getCurrentTxnId()); return this.cascadeUpdate(entity, operation, context, option); } - async operateAsync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP) { + protected async operateAsync>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP) { assert(context.getCurrentTxnId()); return this.cascadeUpdateAsync(entity, operation, context, option); } @@ -991,7 +992,7 @@ export default class TreeStore extends C * @param nodeDict * @param context */ - protected formExprInResult( + private formExprInResult( entity: T, projection: ED[T]['Selection']['data'], data: Partial, @@ -1172,7 +1173,158 @@ export default class TreeStore extends C } } - selectSync>( + /** + * 本函数把结果中的相应属性映射成一个字符串,用于GroupBy + * @param entity + * @param row + * @param projection + */ + private mappingProjectionOnRow( + entity: T, + row: Partial, + projection: ED[T]['Selection']['data'] + ) { + let key = ''; + let result = {} as Partial; + const values = [] as any[]; + const mappingIter = ( + entity2: T2, + row2: Partial, + p2: ED[T2]['Selection']['data'], + result2: Partial) => { + const keys = Object.keys(p2).sort((ele1, ele2) => ele1 < ele2 ? -1 : 1); + for (const k of keys) { + const rel = this.judgeRelation(entity2, k); + if (rel === 2) { + (result2 as any)[k] = {}; + if (row2[k]) { + mappingIter(k, row2[k]!, p2[k], result2[k]!); + } + } + else if (typeof rel === 'string') { + (result2 as any)[k] = {}; + if(row2[k]) { + mappingIter(rel, row2[k]!, p2[k], result2[k]!); + } + } + else { + assert([0, 1].includes(rel as number)); + (result2 as any)[k] = row2[k]; + assert(['string', 'number', 'boolean'].includes(typeof row2[k])); + key += `${row2[k]}`; + values.push(row2[k]); + } + } + }; + + mappingIter(entity, row, projection, result); + return { + result, + key, + values, + }; + } + + private calcAggregation( + entity: T, + rows: Partial[], + aggregationData: ED[T]['Aggregation']['data'] + ) { + const ops = Object.keys(aggregationData).filter( + ele => ele !== '$aggr' + ) as AggregationOp []; + const result = {} as Record; + for (const row of rows) { + for (const op of ops) { + const { values } = this.mappingProjectionOnRow(entity, row, (aggregationData as any)[op]); + assert(values.length === 1, `聚合运算中,${op}的目标属性多于1个`); + if (op.startsWith('$max')) { + if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] < values[0])) { + result[op] = values[0]; + } + } + else if (op.startsWith('$min')) { + if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] > values[0])) { + result[op] = values[0]; + } + } + else if (op.startsWith('$sum')) { + if (![undefined, null].includes(values[0])) { + assert(typeof values[0] === 'number', '只有number类型的属性才可以计算sum'); + if (!result.hasOwnProperty(op)) { + result[op] = values[0]; + } + else { + result[op] += values[0]; + } + } + } + else if (op.startsWith('$count')) { + if (![undefined, null].includes(values[0])) { + if (!result.hasOwnProperty(op)) { + result[op] = 1; + } + else { + result[op] += 1; + } + } + } + else { + assert(op.startsWith('$avg')); + if (![undefined, null].includes(values[0])) { + assert(typeof values[0] === 'number', '只有number类型的属性才可以计算avg'); + if (!result.hasOwnProperty(op)) { + result[op] = { + total: values[0], + count: 1, + }; + } + else { + result[op].total += values[0]; + result[op].count += 1; + } + } + + } + } + } + for (const op of ops) { + if (!result[op]) { + result[op] = null; + } + else if (op.startsWith('$avg')) { + result[op] = result[op].total/result[op].count; + } + } + return result as AggregationResult[number]; + } + + private formAggregation( + entity: T, + rows: Array>, + aggregationData: ED[T]['Aggregation']['data']) { + const { $aggr } = aggregationData; + if ($aggr) { + const groups = groupBy(rows, (row) => { + const { key } = this.mappingProjectionOnRow(entity, row, $aggr); + return key; + }); + const result = Object.keys(groups).map( + (ele) => { + const aggr = this.calcAggregation(entity, groups[ele], aggregationData); + const { result: r } = this.mappingProjectionOnRow(entity, groups[ele][0], $aggr); + aggr.data = r; + return aggr; + } + ); + + return result; + } + const aggr = this.calcAggregation(entity, rows, aggregationData); + return [ aggr ]; + } + + protected selectSync>( entity: T, selection: ED[T]['Selection'], context: Cxt, @@ -1186,7 +1338,7 @@ export default class TreeStore extends C return result; } - async selectAsync>( + protected async selectAsync>( entity: T, selection: ED[T]['Selection'], context: Cxt, @@ -1200,7 +1352,71 @@ export default class TreeStore extends C return result; } - countSync>( + protected aggregateSync>( + entity: T, + aggregation: ED[T]['Aggregation'], + context: Cxt, + option: OP): AggregationResult { + assert(context.getCurrentTxnId()); + const { data, filter, sorter, indexFrom, count } = aggregation; + const p: ED[T]['Selection']['data'] = {}; + for (const k in data) { + Object.assign(p, cloneDeep((data as any)[k])); + } + const selection: ED[T]['Selection'] = { + data: p, + filter, + sorter, + indexFrom, + count, + }; + reinforceSelection(this.storageSchema, entity, selection); + + const result = this.cascadeSelect(entity, selection, context, Object.assign({}, option, { + dontCollect: true, + })); + // 在这里再计算所有的表达式 + result.forEach( + (ele) => this.formExprInResult(entity, selection.data, ele, {}, context) + ); + + // 最后计算Aggregation + return this.formAggregation(entity, result, aggregation.data); + } + + protected async aggregateAsync>( + entity: T, + aggregation: ED[T]['Aggregation'], + context: Cxt, + option: OP): Promise> { + assert(context.getCurrentTxnId()); + const { data, filter, sorter, indexFrom, count } = aggregation; + const p: ED[T]['Selection']['data'] = {}; + for (const k in data) { + Object.assign(p, cloneDeep((data as any)[k])); + } + const selection: ED[T]['Selection'] = { + data: p, + filter, + sorter, + indexFrom, + count, + }; + reinforceSelection(this.storageSchema, entity, selection); + + const result = await this.cascadeSelectAsync(entity, selection, context, Object.assign({}, option, { + dontCollect: true, + })); + // 在这里再计算所有的表达式 + result.forEach( + (ele) => this.formExprInResult(entity, selection.data, ele, {}, context) + ); + + // 最后计算Aggregation + return this.formAggregation(entity, result, aggregation.data); + } + + protected countSync>( entity: T, selection: Pick, context: Cxt, option: OP): number { @@ -1215,7 +1431,7 @@ export default class TreeStore extends C return typeof selection.count === 'number' ? Math.min(result.length, selection.count) : result.length; } - async countAsync>( + protected async countAsync>( entity: T, selection: Pick, context: Cxt, option: OP) { diff --git a/test/Context.ts b/test/Context.ts index 8afc9ca..bfe9589 100644 --- a/test/Context.ts +++ b/test/Context.ts @@ -2,6 +2,7 @@ import TreeStore from '../src/store'; import { EntityDict } from 'oak-domain/lib/base-app-domain'; import { SyncContext, SyncRowStore } from "oak-domain/lib/store/SyncRowStore"; import { OperateOption, OperationResult, SelectOption, AggregationResult, TxnOption, StorageSchema } from "oak-domain/lib/types"; +import { reinforceSelection } from 'oak-domain/lib/store/selection'; /** * 实现一个同步的context和store用于测试 @@ -26,13 +27,14 @@ export class FrontendStore extends TreeStore implements SyncRowStore return this.operateSync(entity, operation, context, option); } select(entity: T, selection: EntityDict[T]["Selection"], context: FrontendRuntimeContext, option: OP): Partial[] { + reinforceSelection(this.storageSchema, entity, selection); return this.selectSync(entity, selection, context, option); } count(entity: T, selection: Pick, context: FrontendRuntimeContext, option: OP): number { return this.countSync(entity, selection, context, option); } aggregate(entity: T, aggregation: EntityDict[T]["Aggregation"], context: FrontendRuntimeContext, option: OP): AggregationResult { - throw new Error("Method not implemented."); + return this.aggregateSync(entity, aggregation, context, option); } begin(option?: TxnOption | undefined): string { return this.beginSync(); diff --git a/test/test.ts b/test/test.ts index 2002795..171e91f 100644 --- a/test/test.ts +++ b/test/test.ts @@ -81,7 +81,6 @@ describe('基础测试', function () { context.commit(); }); - it('[1.1]子查询', () => { const store = new FrontendStore(storageSchema); const context = new FrontendRuntimeContext(store); @@ -154,7 +153,7 @@ describe('基础测试', function () { context.commit(); }); - it('[1.2]行内属性上的表达式', async () => { + it('[1.2]行内属性上的表达式', () => { const store = new FrontendStore(storageSchema); const context = new FrontendRuntimeContext(store); context.begin(); @@ -218,7 +217,7 @@ describe('基础测试', function () { context.commit(); }); - it('[1.3]跨filter结点的表达式', async () => { + it('[1.3]跨filter结点的表达式', () => { const store = new FrontendStore(storageSchema); const context = new FrontendRuntimeContext(store); context.begin(); @@ -433,7 +432,6 @@ describe('基础测试', function () { entity: 1, modi: { id: 1, - entity: 1, $expr: { $eq: [ { @@ -526,7 +524,7 @@ describe('基础测试', function () { data: { id: 1, entity: 1, - modiId: 1, + // modiId: 1, $expr: { $eq: [ { @@ -554,7 +552,7 @@ describe('基础测试', function () { context.commit(); }); - it('[1.7]事务性测试', async () => { + it('[1.7]事务性测试', () => { const store = new FrontendStore(storageSchema); const context = new FrontendRuntimeContext(store); context.begin(); @@ -643,5 +641,90 @@ describe('基础测试', function () { context.commit(); }); + + it('[1.8]aggregate', () => { + const store = new FrontendStore(storageSchema); + const context = new FrontendRuntimeContext(store); + context.begin(); + store.operate('modi', { + id: generateNewId(), + action: 'create', + data: [{ + id: generateNewId(), + targetEntity: 'ddd', + entity: 'user', + entityId: 'user-id-1', + action: 'create', + data: {}, + modiEntity$modi: { + action: 'create', + data: [{ + id: generateNewId(), + entity: 'user', + entityId: 'user-id-1', + }, { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-1', + }, { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-1', + }, { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-1', + }] + } + }, { + id: generateNewId(), + targetEntity: 'ddd2', + entity: 'user', + entityId: 'user-id-2', + action: 'create', + data: {}, + modiEntity$modi: { + action: 'create', + data: [ + { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-2', + }, + { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-2', + }, + { + id: generateNewId(), + entity: 'user', + entityId: 'user-id-2', + } + ], + }, + }], + }, context, {}); + context.commit(); + + context.begin(); + const result = store.aggregate('modiEntity', { + data: { + '$count-1': { + id: 1, + }, + '$avg-1': { + $$createAt$$: 1, + }, + $aggr: { + modi: { + targetEntity: 1, + } + } + }, + }, context, {}); + // console.log(result); + context.commit(); + }); });