Merge branch 'dev' into release
This commit is contained in:
commit
e0eb69df75
|
|
@ -91,8 +91,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
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 aggregateAbjointRowSync<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 aggregateAbjointRowAsync<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;
|
||||
|
|
|
|||
119
es/store.js
119
es/store.js
|
|
@ -1,4 +1,4 @@
|
|||
import { cloneDeep, get, groupBy, set, unset, differenceBy, intersectionBy, pull, pick } from 'oak-domain/lib/utils/lodash';
|
||||
import { cloneDeep, get, groupBy, set, unset, uniqBy, uniq, differenceBy, intersectionBy, pull, pick } from 'oak-domain/lib/utils/lodash';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { DeleteAtAttribute, CreateAtAttribute, UpdateAtAttribute } from "oak-domain/lib/types/Entity";
|
||||
import { EXPRESSION_PREFIX, SUB_QUERY_PREDICATE_KEYWORD } from 'oak-domain/lib/types/Demand';
|
||||
|
|
@ -445,6 +445,12 @@ export default class TreeStore extends CascadeStore {
|
|||
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$mod': {
|
||||
return (row) => {
|
||||
const data = get(row, path);
|
||||
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$startsWith': {
|
||||
return (row) => {
|
||||
const data = get(row, path);
|
||||
|
|
@ -561,7 +567,7 @@ export default class TreeStore extends CascadeStore {
|
|||
const attr2 = attr.startsWith('.') ? attr.slice(1) : attr;
|
||||
const path2 = path ? `${path}.${attr2}` : attr2;
|
||||
if (typeof p[attr] !== 'object') {
|
||||
fns2.push(this.translatePredicate(path2, '$eq', filter[attr]));
|
||||
fns2.push(this.translatePredicate(path2, '$eq', p[attr]));
|
||||
}
|
||||
else {
|
||||
translatePredicateInner(p[attr], path2, fns2);
|
||||
|
|
@ -1332,7 +1338,7 @@ export default class TreeStore extends CascadeStore {
|
|||
});
|
||||
}
|
||||
// 先计算projection,formResult只处理abjoint的行,不需要考虑expression和一对多多对一关系
|
||||
const rows2 = [];
|
||||
let rows2 = [];
|
||||
const incompletedRowIds = [];
|
||||
const { data: projection } = selection;
|
||||
for (const row of rows) {
|
||||
|
|
@ -1385,20 +1391,22 @@ export default class TreeStore extends CascadeStore {
|
|||
}
|
||||
}
|
||||
// 这三个属性在前台cache中可能表达特殊语义的,需要返回
|
||||
if (row[DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[DeleteAtAttribute]: row[DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[UpdateAtAttribute]: row[UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[CreateAtAttribute]: row[CreateAtAttribute],
|
||||
});
|
||||
if (!selection.distinct) {
|
||||
if (row[DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[DeleteAtAttribute]: row[DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[UpdateAtAttribute]: row[UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[CreateAtAttribute]: row[CreateAtAttribute],
|
||||
});
|
||||
}
|
||||
}
|
||||
rows2.push(result);
|
||||
}
|
||||
|
|
@ -1430,13 +1438,15 @@ export default class TreeStore extends CascadeStore {
|
|||
const sorterFn = this.translateSorter(entity, sorter, context, option);
|
||||
rows2.sort(sorterFn);
|
||||
}
|
||||
// 最后用indexFrom和count来截断
|
||||
// 用indexFrom和count来截断
|
||||
if (typeof indexFrom === 'number') {
|
||||
return rows2.slice(indexFrom, indexFrom + count);
|
||||
rows2 = rows2.slice(indexFrom, indexFrom + count);
|
||||
}
|
||||
else {
|
||||
return rows2;
|
||||
// 如果有distinct再计算distinct
|
||||
if (selection.distinct) {
|
||||
rows2 = uniqBy(rows2, (ele) => JSON.stringify(ele));
|
||||
}
|
||||
return rows2;
|
||||
}
|
||||
/**
|
||||
* 本函数把结果中的相应属性映射成一个字符串,用于GroupBy
|
||||
|
|
@ -1481,13 +1491,20 @@ export default class TreeStore extends CascadeStore {
|
|||
};
|
||||
}
|
||||
calcAggregation(entity, rows, aggregationData) {
|
||||
const ops = Object.keys(aggregationData).filter(ele => ele !== '#aggr');
|
||||
const ops = Object.keys(aggregationData).filter(ele => ele !== '#aggr' && ele.startsWith('#'));
|
||||
const result = {};
|
||||
const results = {};
|
||||
for (const row of rows) {
|
||||
for (const op of ops) {
|
||||
const { values } = this.mappingProjectionOnRow(entity, row, aggregationData[op]);
|
||||
assert(values.length === 1, `聚合运算中,${op}的目标属性多于1个`);
|
||||
if (op.startsWith('#max')) {
|
||||
if (results[op]) {
|
||||
results[op].push(values[0]);
|
||||
}
|
||||
else {
|
||||
results[op] = [values[0]];
|
||||
}
|
||||
/* if (op.startsWith('#max')) {
|
||||
if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] < values[0])) {
|
||||
result[op] = values[0];
|
||||
}
|
||||
|
|
@ -1533,20 +1550,52 @@ export default class TreeStore extends CascadeStore {
|
|||
result[op].count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
for (const op of ops) {
|
||||
if (!result[op]) {
|
||||
if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
}
|
||||
else {
|
||||
result[op] = null;
|
||||
}
|
||||
const { distinct } = aggregationData;
|
||||
for (const op in results) {
|
||||
if (op.startsWith('#max')) {
|
||||
result[op] = null;
|
||||
results[op].forEach((ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] < ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#min')) {
|
||||
result[op] = null;
|
||||
results[op].forEach((ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] > ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#sum')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? uniq(results[op]) : results[op];
|
||||
data.forEach((ele) => {
|
||||
assert(typeof ele === 'number', '只有number类型的属性才可以计算sum');
|
||||
result[op] += ele;
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? uniq(results[op]) : results[op];
|
||||
data.forEach((ele) => {
|
||||
if (![undefined, null].includes(ele)) {
|
||||
result[op] += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#avg')) {
|
||||
result[op] = result[op].total / result[op].count;
|
||||
result[op] = 0;
|
||||
const data = (distinct ? uniq(results[op]) : results[op]).filter(ele => ![undefined, null].includes(ele));
|
||||
data.forEach((ele) => {
|
||||
assert(typeof ele === 'number', '只有number类型的属性才可以计算avg');
|
||||
result[op] += ele;
|
||||
});
|
||||
result[op] = result[op] / data.length;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
@ -1583,7 +1632,7 @@ export default class TreeStore extends CascadeStore {
|
|||
result.forEach((ele) => this.formExprInResult(entity, selection.data, ele, {}, context));
|
||||
return result;
|
||||
}
|
||||
aggregateSync(entity, aggregation, context, option) {
|
||||
aggregateAbjointRowSync(entity, aggregation, context, option) {
|
||||
assert(context.getCurrentTxnId());
|
||||
const { data, filter, sorter, indexFrom, count } = aggregation;
|
||||
const p = {};
|
||||
|
|
@ -1605,7 +1654,7 @@ export default class TreeStore extends CascadeStore {
|
|||
// 最后计算Aggregation
|
||||
return this.formAggregation(entity, result, aggregation.data);
|
||||
}
|
||||
async aggregateAsync(entity, aggregation, context, option) {
|
||||
async aggregateAbjointRowAsync(entity, aggregation, context, option) {
|
||||
assert(context.getCurrentTxnId());
|
||||
const { data, filter, sorter, indexFrom, count } = aggregation;
|
||||
const p = {};
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
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 aggregateAbjointRowSync<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 aggregateAbjointRowAsync<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;
|
||||
|
|
|
|||
123
lib/store.js
123
lib/store.js
|
|
@ -447,6 +447,12 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$mod': {
|
||||
return (row) => {
|
||||
const data = (0, lodash_1.get)(row, path);
|
||||
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$startsWith': {
|
||||
return (row) => {
|
||||
const data = (0, lodash_1.get)(row, path);
|
||||
|
|
@ -563,7 +569,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
const attr2 = attr.startsWith('.') ? attr.slice(1) : attr;
|
||||
const path2 = path ? `${path}.${attr2}` : attr2;
|
||||
if (typeof p[attr] !== 'object') {
|
||||
fns2.push(this.translatePredicate(path2, '$eq', filter[attr]));
|
||||
fns2.push(this.translatePredicate(path2, '$eq', p[attr]));
|
||||
}
|
||||
else {
|
||||
translatePredicateInner(p[attr], path2, fns2);
|
||||
|
|
@ -1334,7 +1340,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
});
|
||||
}
|
||||
// 先计算projection,formResult只处理abjoint的行,不需要考虑expression和一对多多对一关系
|
||||
const rows2 = [];
|
||||
let rows2 = [];
|
||||
const incompletedRowIds = [];
|
||||
const { data: projection } = selection;
|
||||
for (const row of rows) {
|
||||
|
|
@ -1387,20 +1393,22 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
}
|
||||
}
|
||||
// 这三个属性在前台cache中可能表达特殊语义的,需要返回
|
||||
if (row[Entity_1.DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.DeleteAtAttribute]: row[Entity_1.DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[Entity_1.UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.UpdateAtAttribute]: row[Entity_1.UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[Entity_1.CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.CreateAtAttribute]: row[Entity_1.CreateAtAttribute],
|
||||
});
|
||||
if (!selection.distinct) {
|
||||
if (row[Entity_1.DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.DeleteAtAttribute]: row[Entity_1.DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[Entity_1.UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.UpdateAtAttribute]: row[Entity_1.UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[Entity_1.CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[Entity_1.CreateAtAttribute]: row[Entity_1.CreateAtAttribute],
|
||||
});
|
||||
}
|
||||
}
|
||||
rows2.push(result);
|
||||
}
|
||||
|
|
@ -1432,13 +1440,15 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
const sorterFn = this.translateSorter(entity, sorter, context, option);
|
||||
rows2.sort(sorterFn);
|
||||
}
|
||||
// 最后用indexFrom和count来截断
|
||||
// 用indexFrom和count来截断
|
||||
if (typeof indexFrom === 'number') {
|
||||
return rows2.slice(indexFrom, indexFrom + count);
|
||||
rows2 = rows2.slice(indexFrom, indexFrom + count);
|
||||
}
|
||||
else {
|
||||
return rows2;
|
||||
// 如果有distinct再计算distinct
|
||||
if (selection.distinct) {
|
||||
rows2 = (0, lodash_1.uniqBy)(rows2, (ele) => JSON.stringify(ele));
|
||||
}
|
||||
return rows2;
|
||||
}
|
||||
/**
|
||||
* 本函数把结果中的相应属性映射成一个字符串,用于GroupBy
|
||||
|
|
@ -1483,13 +1493,20 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
};
|
||||
}
|
||||
calcAggregation(entity, rows, aggregationData) {
|
||||
const ops = Object.keys(aggregationData).filter(ele => ele !== '#aggr');
|
||||
const ops = Object.keys(aggregationData).filter(ele => ele !== '#aggr' && ele.startsWith('#'));
|
||||
const result = {};
|
||||
const results = {};
|
||||
for (const row of rows) {
|
||||
for (const op of ops) {
|
||||
const { values } = this.mappingProjectionOnRow(entity, row, aggregationData[op]);
|
||||
(0, assert_1.assert)(values.length === 1, `聚合运算中,${op}的目标属性多于1个`);
|
||||
if (op.startsWith('#max')) {
|
||||
if (results[op]) {
|
||||
results[op].push(values[0]);
|
||||
}
|
||||
else {
|
||||
results[op] = [values[0]];
|
||||
}
|
||||
/* if (op.startsWith('#max')) {
|
||||
if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] < values[0])) {
|
||||
result[op] = values[0];
|
||||
}
|
||||
|
|
@ -1501,7 +1518,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
}
|
||||
else if (op.startsWith('#sum')) {
|
||||
if (![undefined, null].includes(values[0])) {
|
||||
(0, assert_1.assert)(typeof values[0] === 'number', '只有number类型的属性才可以计算sum');
|
||||
assert(typeof values[0] === 'number', '只有number类型的属性才可以计算sum');
|
||||
if (!result.hasOwnProperty(op)) {
|
||||
result[op] = values[0];
|
||||
}
|
||||
|
|
@ -1521,9 +1538,9 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
}
|
||||
}
|
||||
else {
|
||||
(0, assert_1.assert)(op.startsWith('#avg'));
|
||||
assert(op.startsWith('#avg'));
|
||||
if (![undefined, null].includes(values[0])) {
|
||||
(0, assert_1.assert)(typeof values[0] === 'number', '只有number类型的属性才可以计算avg');
|
||||
assert(typeof values[0] === 'number', '只有number类型的属性才可以计算avg');
|
||||
if (!result.hasOwnProperty(op)) {
|
||||
result[op] = {
|
||||
total: values[0],
|
||||
|
|
@ -1535,20 +1552,52 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
result[op].count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
for (const op of ops) {
|
||||
if (!result[op]) {
|
||||
if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
}
|
||||
else {
|
||||
result[op] = null;
|
||||
}
|
||||
const { distinct } = aggregationData;
|
||||
for (const op in results) {
|
||||
if (op.startsWith('#max')) {
|
||||
result[op] = null;
|
||||
results[op].forEach((ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] < ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#min')) {
|
||||
result[op] = null;
|
||||
results[op].forEach((ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] > ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#sum')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? (0, lodash_1.uniq)(results[op]) : results[op];
|
||||
data.forEach((ele) => {
|
||||
(0, assert_1.assert)(typeof ele === 'number', '只有number类型的属性才可以计算sum');
|
||||
result[op] += ele;
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? (0, lodash_1.uniq)(results[op]) : results[op];
|
||||
data.forEach((ele) => {
|
||||
if (![undefined, null].includes(ele)) {
|
||||
result[op] += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (op.startsWith('#avg')) {
|
||||
result[op] = result[op].total / result[op].count;
|
||||
result[op] = 0;
|
||||
const data = (distinct ? (0, lodash_1.uniq)(results[op]) : results[op]).filter(ele => ![undefined, null].includes(ele));
|
||||
data.forEach((ele) => {
|
||||
(0, assert_1.assert)(typeof ele === 'number', '只有number类型的属性才可以计算avg');
|
||||
result[op] += ele;
|
||||
});
|
||||
result[op] = result[op] / data.length;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
@ -1585,7 +1634,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
result.forEach((ele) => this.formExprInResult(entity, selection.data, ele, {}, context));
|
||||
return result;
|
||||
}
|
||||
aggregateSync(entity, aggregation, context, option) {
|
||||
aggregateAbjointRowSync(entity, aggregation, context, option) {
|
||||
(0, assert_1.assert)(context.getCurrentTxnId());
|
||||
const { data, filter, sorter, indexFrom, count } = aggregation;
|
||||
const p = {};
|
||||
|
|
@ -1607,7 +1656,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
|
|||
// 最后计算Aggregation
|
||||
return this.formAggregation(entity, result, aggregation.data);
|
||||
}
|
||||
async aggregateAsync(entity, aggregation, context, option) {
|
||||
async aggregateAbjointRowAsync(entity, aggregation, context, option) {
|
||||
(0, assert_1.assert)(context.getCurrentTxnId());
|
||||
const { data, filter, sorter, indexFrom, count } = aggregation;
|
||||
const p = {};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
{
|
||||
"name": "oak-memory-tree-store",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"description": "oak框架中内存级store的实现",
|
||||
"author": {
|
||||
"name": "XuChang"
|
||||
},
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
"lib/**/*",
|
||||
"es/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"oak-domain": "^4.0.0",
|
||||
"oak-domain": "^4.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
140
src/store.ts
140
src/store.ts
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
cloneDeep, get, groupBy, set, unset,
|
||||
cloneDeep, get, groupBy, set, unset, uniqBy, uniq,
|
||||
differenceBy, intersectionBy, pull, pick
|
||||
} from 'oak-domain/lib/utils/lodash';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
|
|
@ -78,7 +78,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
private getNextSeq(entity: keyof ED) {
|
||||
if (this.seq[entity]) {
|
||||
const seq = this.seq[entity];
|
||||
this.seq[entity] ++;
|
||||
this.seq[entity]! ++;
|
||||
return seq;
|
||||
}
|
||||
this.seq[entity] = 2;
|
||||
|
|
@ -87,7 +87,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
|
||||
private setMaxSeq(entity: keyof ED, seq: number) {
|
||||
if (this.seq[entity]) {
|
||||
if (this.seq[entity] < seq) {
|
||||
if (this.seq[entity]! < seq) {
|
||||
this.seq[entity] = seq;
|
||||
}
|
||||
}
|
||||
|
|
@ -582,6 +582,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$mod': {
|
||||
return (row) => {
|
||||
const data = get(row, path);
|
||||
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
|
||||
};
|
||||
}
|
||||
case '$startsWith': {
|
||||
return (row) => {
|
||||
const data = get(row, path);
|
||||
|
|
@ -709,7 +715,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
const attr2 = attr.startsWith('.') ? attr.slice(1) : attr;
|
||||
const path2 = path ? `${path}.${attr2}` : attr2;
|
||||
if (typeof p[attr] !== 'object') {
|
||||
fns2.push(this.translatePredicate(path2, '$eq', filter[attr]));
|
||||
fns2.push(this.translatePredicate(path2, '$eq', p[attr]));
|
||||
}
|
||||
else {
|
||||
translatePredicateInner(p[attr], path2, fns2);
|
||||
|
|
@ -1598,7 +1604,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
}
|
||||
|
||||
// 先计算projection,formResult只处理abjoint的行,不需要考虑expression和一对多多对一关系
|
||||
const rows2: Array<Partial<ED[T]['Schema']>> = [];
|
||||
let rows2: Array<Partial<ED[T]['Schema']>> = [];
|
||||
const incompletedRowIds: string[] = [];
|
||||
const { data: projection } = selection;
|
||||
for (const row of rows) {
|
||||
|
|
@ -1654,21 +1660,23 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
}
|
||||
}
|
||||
// 这三个属性在前台cache中可能表达特殊语义的,需要返回
|
||||
if (row[DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[DeleteAtAttribute]: row[DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[UpdateAtAttribute]: row[UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[CreateAtAttribute]: row[CreateAtAttribute],
|
||||
});
|
||||
|
||||
if (!selection.distinct) {
|
||||
if (row[DeleteAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[DeleteAtAttribute]: row[DeleteAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[UpdateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[UpdateAtAttribute]: row[UpdateAtAttribute],
|
||||
});
|
||||
}
|
||||
if (row[CreateAtAttribute]) {
|
||||
Object.assign(result, {
|
||||
[CreateAtAttribute]: row[CreateAtAttribute],
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
rows2.push(result);
|
||||
}
|
||||
|
|
@ -1702,13 +1710,17 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
rows2.sort(sorterFn);
|
||||
}
|
||||
|
||||
// 最后用indexFrom和count来截断
|
||||
// 用indexFrom和count来截断
|
||||
if (typeof indexFrom === 'number') {
|
||||
return rows2.slice(indexFrom, indexFrom! + count!);
|
||||
rows2 = rows2.slice(indexFrom, indexFrom! + count!);
|
||||
}
|
||||
else {
|
||||
return rows2;
|
||||
|
||||
// 如果有distinct再计算distinct
|
||||
if (selection.distinct) {
|
||||
rows2 = uniqBy(rows2, (ele) => JSON.stringify(ele));
|
||||
}
|
||||
|
||||
return rows2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1769,14 +1781,21 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
aggregationData: ED[T]['Aggregation']['data']
|
||||
) {
|
||||
const ops = Object.keys(aggregationData).filter(
|
||||
ele => ele !== '#aggr'
|
||||
ele => ele !== '#aggr' && ele.startsWith('#')
|
||||
) as AggregationOp[];
|
||||
const result = {} as Record<string, any>;
|
||||
const results = {} 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 (results[op]) {
|
||||
results[op].push(values[0]);
|
||||
}
|
||||
else {
|
||||
results[op] = [values[0]];
|
||||
}
|
||||
/* if (op.startsWith('#max')) {
|
||||
if (![undefined, null].includes(values[0]) && (!result.hasOwnProperty(op) || result[op] < values[0])) {
|
||||
result[op] = values[0];
|
||||
}
|
||||
|
|
@ -1822,21 +1841,64 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
result[op].count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
for (const op of ops) {
|
||||
if (!result[op]) {
|
||||
if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
}
|
||||
else {
|
||||
result[op] = null;
|
||||
}
|
||||
const { distinct } = aggregationData;
|
||||
for (const op in results) {
|
||||
if (op.startsWith('#max')) {
|
||||
result[op] = null;
|
||||
results[op].forEach(
|
||||
(ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] < ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (op.startsWith('#min')) {
|
||||
result[op] = null;
|
||||
results[op].forEach(
|
||||
(ele) => {
|
||||
if (![undefined, null].includes(ele) && (result[op] === null || result[op] > ele)) {
|
||||
result[op] = ele;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (op.startsWith('#sum')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? uniq(results[op]) : results[op];
|
||||
data.forEach(
|
||||
(ele) => {
|
||||
assert(typeof ele === 'number', '只有number类型的属性才可以计算sum');
|
||||
result[op] += ele;
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (op.startsWith('#count')) {
|
||||
result[op] = 0;
|
||||
const data = distinct ? uniq(results[op]) : results[op];
|
||||
data.forEach(
|
||||
(ele) => {
|
||||
if (![undefined, null].includes(ele)) {
|
||||
result[op] += 1;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (op.startsWith('#avg')) {
|
||||
result[op] = result[op].total / result[op].count;
|
||||
result[op] = 0;
|
||||
const data = (distinct ? uniq(results[op]) : results[op]).filter(
|
||||
ele => ![undefined, null].includes(ele)
|
||||
);
|
||||
data.forEach(
|
||||
(ele) => {
|
||||
assert(typeof ele === 'number', '只有number类型的属性才可以计算avg');
|
||||
result[op] += ele;
|
||||
}
|
||||
);
|
||||
result[op] = result[op]/data.length;
|
||||
}
|
||||
}
|
||||
return result as AggregationResult<ED[T]['Schema']>[number];
|
||||
|
|
@ -1895,7 +1957,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
return result;
|
||||
}
|
||||
|
||||
protected aggregateSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(
|
||||
protected aggregateAbjointRowSync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends SyncContext<ED>>(
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
|
|
@ -1926,7 +1988,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
|
|||
return this.formAggregation(entity, result, aggregation.data);
|
||||
}
|
||||
|
||||
protected async aggregateAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(
|
||||
protected async aggregateAbjointRowAsync<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends AsyncContext<ED>>(
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
|
|
|
|||
49
test/test.ts
49
test/test.ts
|
|
@ -172,6 +172,19 @@ describe('基础测试', function () {
|
|||
},
|
||||
}, context, {});
|
||||
assert(modeEntities4.length === 1);
|
||||
|
||||
const modeEntities5 = store.select('modiEntity', {
|
||||
data: {
|
||||
entity: 1,
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: [id1, id2],
|
||||
},
|
||||
},
|
||||
distinct: true
|
||||
}, context, {});
|
||||
// console.log(modeEntities5);
|
||||
context.commit();
|
||||
});
|
||||
|
||||
|
|
@ -831,7 +844,19 @@ describe('基础测试', function () {
|
|||
}
|
||||
},
|
||||
}, context, {});
|
||||
console.log(result);
|
||||
// console.log(result);
|
||||
|
||||
// distinct
|
||||
const result2 = store.aggregate('modiEntity', {
|
||||
data: {
|
||||
'#count-1': {
|
||||
entity: 1,
|
||||
},
|
||||
distinct: true,
|
||||
},
|
||||
}, context, {});
|
||||
|
||||
console.log(result2);
|
||||
context.commit();
|
||||
});
|
||||
|
||||
|
|
@ -1293,7 +1318,29 @@ describe('基础测试', function () {
|
|||
},
|
||||
}, context, {});
|
||||
|
||||
const rows2 = store.select('oper', {
|
||||
data: {
|
||||
id: 1,
|
||||
},
|
||||
filter: {
|
||||
id,
|
||||
data: {
|
||||
'.$or': [
|
||||
{
|
||||
name: 'xc',
|
||||
},
|
||||
{
|
||||
name: {
|
||||
'.$includes': 'xc',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
}, context, {});
|
||||
|
||||
assert(rows1.length === 1);
|
||||
assert(rows2.length === 1);
|
||||
context.commit();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue