重新编写了测试代码,增加了aggregate的实现

This commit is contained in:
Xu Chang 2023-01-03 19:13:34 +08:00
parent 7a6d66fb61
commit 4ab09a9bde
5 changed files with 577 additions and 36 deletions

27
lib/store.d.ts vendored
View File

@ -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 { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { StorageSchema } from 'oak-domain/lib/types/Storage'; import { StorageSchema } from 'oak-domain/lib/types/Storage';
import { NodeDict } from "./types/type"; import { NodeDict } from "./types/type";
@ -53,8 +53,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
protected updateAbjointRow<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends Context>(entity: T, operation: DeduceCreateSingleOperation<ED[T]['Schema']> | DeduceUpdateOperation<ED[T]['Schema']> | DeduceRemoveOperation<ED[T]['Schema']>, context: Cxt, option?: OP): number; protected updateAbjointRow<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends Context>(entity: T, operation: DeduceCreateSingleOperation<ED[T]['Schema']> | DeduceUpdateOperation<ED[T]['Schema']> | DeduceRemoveOperation<ED[T]['Schema']>, context: Cxt, option?: OP): number;
protected selectAbjointRowAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends Context>(entity: T, selection: ED[T]['Selection'], context: Cxt, option?: OP): Promise<Partial<ED[T]["Schema"]>[]>; protected selectAbjointRowAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends Context>(entity: T, selection: ED[T]['Selection'], context: Cxt, option?: OP): Promise<Partial<ED[T]["Schema"]>[]>;
protected updateAbjointRowAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends Context>(entity: T, operation: DeduceCreateSingleOperation<ED[T]['Schema']> | DeduceUpdateOperation<ED[T]['Schema']> | DeduceRemoveOperation<ED[T]['Schema']>, context: Cxt, option?: OP): Promise<number>; protected updateAbjointRowAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends Context>(entity: T, operation: DeduceCreateSingleOperation<ED[T]['Schema']> | DeduceUpdateOperation<ED[T]['Schema']> | DeduceRemoveOperation<ED[T]['Schema']>, context: Cxt, option?: OP): Promise<number>;
operateSync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED>; protected operateSync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED>;
operateAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): Promise<OperationResult<ED>>; protected operateAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): Promise<OperationResult<ED>>;
/** /**
* *
* @param entity * @param entity
@ -63,12 +63,23 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
* @param nodeDict * @param nodeDict
* @param context * @param context
*/ */
protected formExprInResult<T extends keyof ED, Cxt extends Context>(entity: T, projection: ED[T]['Selection']['data'], data: Partial<ED[T]['Schema']>, nodeDict: NodeDict, context: Cxt): void; private formExprInResult;
private formResult; private formResult;
selectSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]['Schema']>[]; /**
selectAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise<Partial<ED[T]["Schema"]>[]>; * GroupBy
countSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): number; * @param entity
countAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): Promise<number>; * @param row
* @param projection
*/
private mappingProjectionOnRow;
private calcAggregation;
private formAggregation;
protected selectSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]['Schema']>[];
protected selectAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise<Partial<ED[T]["Schema"]>[]>;
protected aggregateSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): AggregationResult<ED[T]['Schema']>;
protected aggregateAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP): Promise<AggregationResult<ED[T]['Schema']>>;
protected countSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): number;
protected countAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: OP): Promise<number>;
private addToTxnNode; private addToTxnNode;
getStat(): { getStat(): {
create: number; create: number;

View File

@ -5,6 +5,7 @@ var lodash_1 = require("oak-domain/lib/utils/lodash");
var assert_1 = require("oak-domain/lib/utils/assert"); var assert_1 = require("oak-domain/lib/utils/assert");
var Entity_1 = require("oak-domain/lib/types/Entity"); var Entity_1 = require("oak-domain/lib/types/Entity");
var Demand_1 = require("oak-domain/lib/types/Demand"); 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 Exception_1 = require("oak-domain/lib/types/Exception");
var Demand_2 = require("oak-domain/lib/types/Demand"); var Demand_2 = require("oak-domain/lib/types/Demand");
var relation_1 = require("oak-domain/lib/store/relation"); var relation_1 = require("oak-domain/lib/store/relation");
@ -1118,6 +1119,176 @@ var TreeStore = /** @class */ (function (_super) {
return rows2; 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) { TreeStore.prototype.selectSync = function (entity, selection, context, option) {
var _this = this; var _this = this;
(0, assert_1.assert)(context.getCurrentTxnId()); (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) { TreeStore.prototype.countSync = function (entity, selection, context, option) {
var result = this.selectSync(entity, Object.assign({}, selection, { var result = this.selectSync(entity, Object.assign({}, selection, {
data: { data: {
@ -1206,7 +1435,7 @@ var TreeStore = /** @class */ (function (_super) {
return uuid; return uuid;
}; };
TreeStore.prototype.commitSync = function (uuid) { TreeStore.prototype.commitSync = function (uuid) {
var e_12, _a; var e_16, _a;
(0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid), uuid); (0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid), uuid);
var node = this.activeTxnDict[uuid].nodeHeader; var node = this.activeTxnDict[uuid].nodeHeader;
while (node) { while (node) {
@ -1246,17 +1475,17 @@ var TreeStore = /** @class */ (function (_super) {
waiter.fn(); waiter.fn();
} }
} }
catch (e_12_1) { e_12 = { error: e_12_1 }; } catch (e_16_1) { e_16 = { error: e_16_1 }; }
finally { finally {
try { try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); 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); (0, lodash_1.unset)(this.activeTxnDict, uuid);
}; };
TreeStore.prototype.rollbackSync = function (uuid) { TreeStore.prototype.rollbackSync = function (uuid) {
var e_13, _a; var e_17, _a;
(0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid)); (0, assert_1.assert)(this.activeTxnDict.hasOwnProperty(uuid));
var node = this.activeTxnDict[uuid].nodeHeader; var node = this.activeTxnDict[uuid].nodeHeader;
while (node) { while (node) {
@ -1289,12 +1518,12 @@ var TreeStore = /** @class */ (function (_super) {
waiter.fn(); waiter.fn();
} }
} }
catch (e_13_1) { e_13 = { error: e_13_1 }; } catch (e_17_1) { e_17 = { error: e_17_1 }; }
finally { finally {
try { try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); 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); (0, lodash_1.unset)(this.activeTxnDict, uuid);
}; };
@ -1321,7 +1550,7 @@ var TreeStore = /** @class */ (function (_super) {
}; };
// 将输入的OpRecord同步到数据中 // 将输入的OpRecord同步到数据中
TreeStore.prototype.sync = function (opRecords, context, option) { 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, { var option2 = Object.assign({}, option, {
dontCollect: true, dontCollect: true,
dontCreateOper: true, dontCreateOper: true,
@ -1334,7 +1563,7 @@ var TreeStore = /** @class */ (function (_super) {
var e = record.e, d = record.d; var e = record.e, d = record.d;
if (d instanceof Array) { if (d instanceof Array) {
try { 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; var dd = d_1_1.value;
if (this.store[e] && this.store[e][dd.id]) { if (this.store[e] && this.store[e][dd.id]) {
this.updateAbjointRow(e, { 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 { finally {
try { try {
if (d_1_1 && !d_1_1.done && (_b = d_1.return)) _b.call(d_1); 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 { 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 { finally {
try { try {
if (opRecords_1_1 && !opRecords_1_1.done && (_a = opRecords_1.return)) _a.call(opRecords_1); 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; return TreeStore;

View File

@ -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 { assert } from 'oak-domain/lib/utils/assert';
import { import {
DeduceCreateSingleOperation, DeduceFilter, DeduceSelection, EntityShape, DeduceRemoveOperation, DeduceCreateSingleOperation, DeduceFilter, DeduceSelection, EntityShape, DeduceRemoveOperation,
DeduceUpdateOperation, DeduceSorter, DeduceSorterAttr, OperationResult, OperateOption, OpRecord, DeduceUpdateOperation, DeduceSorter, DeduceSorterAttr, OperationResult, OperateOption, OpRecord,
DeduceCreateOperationData, UpdateOpResult, RemoveOpResult, SelectOpResult, DeduceCreateOperationData, UpdateOpResult, RemoveOpResult, SelectOpResult,
EntityDict, SelectOption, DeleteAtAttribute EntityDict, SelectOption, DeleteAtAttribute, AggregationResult, AggregationOp
} from "oak-domain/lib/types/Entity"; } from "oak-domain/lib/types/Entity";
import { ExpressionKey, EXPRESSION_PREFIX, NodeId, RefAttr } from 'oak-domain/lib/types/Demand'; 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 { OakCongruentRowExists, OakException, OakRowUnexistedException } from 'oak-domain/lib/types/Exception';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { StorageSchema } from 'oak-domain/lib/types/Storage'; import { StorageSchema } from 'oak-domain/lib/types/Storage';
@ -973,12 +974,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
} }
operateSync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED> { protected operateSync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED> {
assert(context.getCurrentTxnId()); assert(context.getCurrentTxnId());
return this.cascadeUpdate(entity, operation, context, option); return this.cascadeUpdate(entity, operation, context, option);
} }
async operateAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP) { protected async operateAsync<T extends keyof ED, OP extends TreeStoreOperateOption, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP) {
assert(context.getCurrentTxnId()); assert(context.getCurrentTxnId());
return this.cascadeUpdateAsync(entity, operation, context, option); return this.cascadeUpdateAsync(entity, operation, context, option);
} }
@ -991,7 +992,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
* @param nodeDict * @param nodeDict
* @param context * @param context
*/ */
protected formExprInResult<T extends keyof ED, Cxt extends Context>( private formExprInResult<T extends keyof ED, Cxt extends Context>(
entity: T, entity: T,
projection: ED[T]['Selection']['data'], projection: ED[T]['Selection']['data'],
data: Partial<ED[T]['Schema']>, data: Partial<ED[T]['Schema']>,
@ -1172,7 +1173,158 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
} }
} }
selectSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>( /**
* GroupBy
* @param entity
* @param row
* @param projection
*/
private mappingProjectionOnRow<T extends keyof ED>(
entity: T,
row: Partial<ED[T]['Schema']>,
projection: ED[T]['Selection']['data']
) {
let key = '';
let result = {} as Partial<ED[T]['Schema']>;
const values = [] as any[];
const mappingIter = <T2 extends keyof ED>(
entity2: T2,
row2: Partial<ED[T2]['Schema']>,
p2: ED[T2]['Selection']['data'],
result2: Partial<ED[T2]['Schema']>) => {
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<T extends keyof ED>(
entity: T,
rows: Partial<ED[T]['Schema']>[],
aggregationData: ED[T]['Aggregation']['data']
) {
const ops = Object.keys(aggregationData).filter(
ele => ele !== '$aggr'
) as AggregationOp [];
const result = {} as Record<string, any>;
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<ED[T]['Schema']>[number];
}
private formAggregation<T extends keyof ED, Cxt extends Context>(
entity: T,
rows: Array<Partial<ED[T]['Schema']>>,
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<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(
entity: T, entity: T,
selection: ED[T]['Selection'], selection: ED[T]['Selection'],
context: Cxt, context: Cxt,
@ -1186,7 +1338,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return result; return result;
} }
async selectAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>( protected async selectAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(
entity: T, entity: T,
selection: ED[T]['Selection'], selection: ED[T]['Selection'],
context: Cxt, context: Cxt,
@ -1200,7 +1352,71 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return result; return result;
} }
countSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>( protected aggregateSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(
entity: T,
aggregation: ED[T]['Aggregation'],
context: Cxt,
option: OP): AggregationResult<ED[T]['Schema']> {
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<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(
entity: T,
aggregation: ED[T]['Aggregation'],
context: Cxt,
option: OP): Promise<AggregationResult<ED[T]['Schema']>> {
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<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(
entity: T, entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt, option: OP): number { context: Cxt, option: OP): number {
@ -1215,7 +1431,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return typeof selection.count === 'number' ? Math.min(result.length, selection.count) : result.length; return typeof selection.count === 'number' ? Math.min(result.length, selection.count) : result.length;
} }
async countAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>( protected async countAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(
entity: T, entity: T,
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
context: Cxt, option: OP) { context: Cxt, option: OP) {

View File

@ -2,6 +2,7 @@ import TreeStore from '../src/store';
import { EntityDict } from 'oak-domain/lib/base-app-domain'; import { EntityDict } from 'oak-domain/lib/base-app-domain';
import { SyncContext, SyncRowStore } from "oak-domain/lib/store/SyncRowStore"; import { SyncContext, SyncRowStore } from "oak-domain/lib/store/SyncRowStore";
import { OperateOption, OperationResult, SelectOption, AggregationResult, TxnOption, StorageSchema } from "oak-domain/lib/types"; import { OperateOption, OperationResult, SelectOption, AggregationResult, TxnOption, StorageSchema } from "oak-domain/lib/types";
import { reinforceSelection } from 'oak-domain/lib/store/selection';
/** /**
* context和store用于测试 * context和store用于测试
@ -26,13 +27,14 @@ export class FrontendStore extends TreeStore<EntityDict> implements SyncRowStore
return this.operateSync(entity, operation, context, option); return this.operateSync(entity, operation, context, option);
} }
select<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: EntityDict[T]["Selection"], context: FrontendRuntimeContext, option: OP): Partial<EntityDict[T]["Schema"]>[] { select<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: EntityDict[T]["Selection"], context: FrontendRuntimeContext, option: OP): Partial<EntityDict[T]["Schema"]>[] {
reinforceSelection(this.storageSchema, entity, selection);
return this.selectSync(entity, selection, context, option); return this.selectSync(entity, selection, context, option);
} }
count<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: Pick<EntityDict[T]["Selection"], "count" | "filter">, context: FrontendRuntimeContext, option: OP): number { count<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: Pick<EntityDict[T]["Selection"], "count" | "filter">, context: FrontendRuntimeContext, option: OP): number {
return this.countSync(entity, selection, context, option); return this.countSync(entity, selection, context, option);
} }
aggregate<T extends keyof EntityDict, OP extends SelectOption>(entity: T, aggregation: EntityDict[T]["Aggregation"], context: FrontendRuntimeContext, option: OP): AggregationResult<EntityDict[T]["Schema"]> { aggregate<T extends keyof EntityDict, OP extends SelectOption>(entity: T, aggregation: EntityDict[T]["Aggregation"], context: FrontendRuntimeContext, option: OP): AggregationResult<EntityDict[T]["Schema"]> {
throw new Error("Method not implemented."); return this.aggregateSync(entity, aggregation, context, option);
} }
begin(option?: TxnOption | undefined): string { begin(option?: TxnOption | undefined): string {
return this.beginSync(); return this.beginSync();

View File

@ -81,7 +81,6 @@ describe('基础测试', function () {
context.commit(); context.commit();
}); });
it('[1.1]子查询', () => { it('[1.1]子查询', () => {
const store = new FrontendStore(storageSchema); const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store); const context = new FrontendRuntimeContext(store);
@ -154,7 +153,7 @@ describe('基础测试', function () {
context.commit(); context.commit();
}); });
it('[1.2]行内属性上的表达式', async () => { it('[1.2]行内属性上的表达式', () => {
const store = new FrontendStore(storageSchema); const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store); const context = new FrontendRuntimeContext(store);
context.begin(); context.begin();
@ -218,7 +217,7 @@ describe('基础测试', function () {
context.commit(); context.commit();
}); });
it('[1.3]跨filter结点的表达式', async () => { it('[1.3]跨filter结点的表达式', () => {
const store = new FrontendStore(storageSchema); const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store); const context = new FrontendRuntimeContext(store);
context.begin(); context.begin();
@ -433,7 +432,6 @@ describe('基础测试', function () {
entity: 1, entity: 1,
modi: { modi: {
id: 1, id: 1,
entity: 1,
$expr: { $expr: {
$eq: [ $eq: [
{ {
@ -526,7 +524,7 @@ describe('基础测试', function () {
data: { data: {
id: 1, id: 1,
entity: 1, entity: 1,
modiId: 1, // modiId: 1,
$expr: { $expr: {
$eq: [ $eq: [
{ {
@ -554,7 +552,7 @@ describe('基础测试', function () {
context.commit(); context.commit();
}); });
it('[1.7]事务性测试', async () => { it('[1.7]事务性测试', () => {
const store = new FrontendStore(storageSchema); const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store); const context = new FrontendRuntimeContext(store);
context.begin(); context.begin();
@ -643,5 +641,90 @@ describe('基础测试', function () {
context.commit(); 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();
});
}); });