重新编写了测试代码,增加了aggregate的实现
This commit is contained in:
parent
7a6d66fb61
commit
4ab09a9bde
|
|
@ -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<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 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>;
|
||||
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 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 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
|
||||
|
|
@ -63,12 +63,23 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
* @param nodeDict
|
||||
* @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;
|
||||
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"]>[]>;
|
||||
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;
|
||||
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>;
|
||||
/**
|
||||
* 本函数把结果中的相应属性映射成一个字符串,用于GroupBy
|
||||
* @param entity
|
||||
* @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;
|
||||
getStat(): {
|
||||
create: number;
|
||||
|
|
|
|||
253
lib/store.js
253
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;
|
||||
|
|
|
|||
234
src/store.ts
234
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<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());
|
||||
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());
|
||||
return this.cascadeUpdateAsync(entity, operation, context, option);
|
||||
}
|
||||
|
|
@ -991,7 +992,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
* @param nodeDict
|
||||
* @param context
|
||||
*/
|
||||
protected formExprInResult<T extends keyof ED, Cxt extends Context>(
|
||||
private formExprInResult<T extends keyof ED, Cxt extends Context>(
|
||||
entity: T,
|
||||
projection: ED[T]['Selection']['data'],
|
||||
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,
|
||||
selection: ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
|
|
@ -1186,7 +1338,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
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,
|
||||
selection: ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
|
|
@ -1200,7 +1352,71 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
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,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: Cxt, option: OP) {
|
||||
|
|
|
|||
|
|
@ -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<EntityDict> implements SyncRowStore
|
|||
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"]>[] {
|
||||
reinforceSelection(this.storageSchema, entity, selection);
|
||||
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 {
|
||||
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"]> {
|
||||
throw new Error("Method not implemented.");
|
||||
return this.aggregateSync(entity, aggregation, context, option);
|
||||
}
|
||||
begin(option?: TxnOption | undefined): string {
|
||||
return this.beginSync();
|
||||
|
|
|
|||
95
test/test.ts
95
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue