Compare commits

...

23 Commits
3.3.9 ... dev

Author SHA1 Message Date
Xu Chang 2fd9a46011 3.3.15-dev 2025-12-02 15:22:08 +08:00
Xu Chang afe81e3fbb 3.3.14-pub 2025-12-02 15:21:00 +08:00
Pan Qiancheng 0fc00a02b4 fix: 修复一处类型错误 2025-12-02 15:13:53 +08:00
Pan Qiancheng a547f1d890 Merge branch 'dev' of https://gitea.51mars.com/Oak-Team/oak-memory-tree-store into dev 2025-12-02 15:09:16 +08:00
Pan Qiancheng b51889c9a9 fix: 临时修复Expression过于复杂导致的编译报错 2025-12-02 15:09:14 +08:00
Xu Chang c82bf99fdb 修改了constructRow中的无条件cloneDeep 2025-12-02 14:22:57 +08:00
Xu Chang 5b5621216f 3.3.14-dev 2025-06-16 12:16:03 +08:00
Xu Chang 44d0df03cb 3.3.13-pub 2025-06-16 12:15:01 +08:00
Xu Chang aa77a03f30 笔误 2025-06-09 17:17:05 +08:00
Xu Chang ff0fcc8492 丰富了,支持更多语义 2025-06-09 15:42:37 +08:00
Xu Chang e36cf430cf 增加了 2025-06-09 15:23:01 +08:00
Xu Chang a70f584e08 3.3.13-dev 2025-05-08 16:25:52 +08:00
Xu Chang b87d912388 3.3.12-pub 2025-05-08 16:25:05 +08:00
Xu Chang 0f1b9168ac 判定条件有误 2025-05-08 15:30:13 +08:00
Xu Chang 17380fa8f9 sync的时候有个边界没判断对 2025-05-08 14:00:54 +08:00
Xu Chang 3b6ddbe317 3.3.12-dev 2025-03-17 14:38:19 +08:00
Xu Chang 8cb729ad26 3.3.11-pub 2025-03-17 14:37:26 +08:00
Xu Chang 7a5cfda18c aggr支持了数据为null的情形 2025-03-05 18:55:08 +08:00
Xu Chang 2dd4736907 3.3.11-dev 2025-02-27 14:30:19 +08:00
Xu Chang e59cbd5caf 3.3.10-pub 2025-02-27 14:29:29 +08:00
Xu Chang f84ef1724d 更新了all/not all原来的错误判定 2025-02-14 18:19:13 +08:00
Xu Chang 7c8368c6d2 当aggr有连接表属性时,原来不能正确加以处理 2025-02-13 16:52:00 +08:00
Xu Chang 4776865ece 3.3.10-dev 2024-12-30 20:44:58 +08:00
5 changed files with 293 additions and 171 deletions

View File

@ -5,9 +5,10 @@ import { EXPRESSION_PREFIX, SUB_QUERY_PREDICATE_KEYWORD } from 'oak-domain/lib/t
import { OakCongruentRowExists, OakException } from 'oak-domain/lib/types/Exception';
import { isRefAttrNode } from 'oak-domain/lib/types/Demand';
import { judgeRelation } from 'oak-domain/lib/store/relation';
// Expression引入下方声明改为any防止报错
import { execOp, isExpression, opMultipleParams } from 'oak-domain/lib/types/Expression';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
import { CascadeStore, polishSelection } from 'oak-domain/lib/store/CascadeStore';
import { SeqAttribute } from 'oak-domain/lib/types';
import { combineFilters, getRelevantIds } from 'oak-domain/lib/store/filter';
;
@ -146,13 +147,12 @@ export default class TreeStore extends CascadeStore {
this.seq = {};
}
constructRow(node, context, option) {
let data = cloneDeep(node.$current);
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
if (node.$current && option?.includedDeleted) {
return Object.assign({}, node.$current, {
[DeleteAtAttribute]: 1,
});
}
@ -160,19 +160,21 @@ export default class TreeStore extends CascadeStore {
}
else if (!node.$current) {
// 本事务创建的若在cache中$$createAt$$和$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncContext && {
[CreateAtAttribute]: 1,
[UpdateAtAttribute]: 1,
});
}
else {
// 本事务更新的若在cache中$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncContext && {
[UpdateAtAttribute]: 1,
});
}
}
return data;
return {
...node.$current,
};
}
testFilterFns(node, nodeDict, exprResolveFns, fns) {
const { self, otm, mto } = fns;
@ -290,12 +292,16 @@ export default class TreeStore extends CascadeStore {
* @param context
* @returns
*/
translateExpressionNode(entity, expression, context, option) {
translateExpressionNode(entity,
// expression: Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']> | ExpressionConstant,
expression, context, option) {
if (isExpression(expression)) {
const op = Object.keys(expression)[0];
const option2 = expression[op];
if (opMultipleParams(op)) {
const paramsTranslated = option2.map(ele => this.translateExpressionNode(entity, ele, context, option2));
const paramsTranslated = option2.map(
// const paramsTranslated = (option2 as (Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']>)[]).map(
ele => this.translateExpressionNode(entity, ele, context, option2));
return (row, nodeDict) => {
let later = false;
let results = paramsTranslated.map((ele) => {
@ -385,7 +391,9 @@ export default class TreeStore extends CascadeStore {
return expression;
}
}
translateExpression(entity, expression, context, option) {
translateExpression(entity,
// expression: Expression<keyof ED[T]['Schema']>,
expression, context, option) {
const expr = this.translateExpressionNode(entity, expression, context, option);
return (row, nodeDict) => {
if (typeof expr !== 'function') {
@ -566,6 +574,21 @@ export default class TreeStore extends CascadeStore {
}).length > 0;
};
}
case '$length': {
// json中的数组长度查询
const length = value;
return (row) => {
const data = path ? get(row, path) : row;
assert(data instanceof Array, '$length operator can only used on array attribute');
if (typeof length === 'number') {
return data.length === length;
}
else {
const op = Object.keys(length)[0];
return this.translatePredicate(entity, 'length', op, length[op], option)(data);
}
};
}
default: {
throw new Error(`predicate ${predicate} is not recoganized`);
}
@ -791,8 +814,8 @@ export default class TreeStore extends CascadeStore {
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反外键连接条件的至少有一行
* all代表反连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反连接条件的至少有一行
*
* 此时还没有确定父行只有查询中明确带有id的查询可以先执行否则不执行暂先这个逻辑 by Xc 20230725
*/
@ -805,24 +828,20 @@ export default class TreeStore extends CascadeStore {
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反外键连接条件的至少有一行
* all代表反连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反连接条件的至少有一行
*/
const otmFilter = !otmForeignKey ? Object.assign({
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : cloneDeep(filter[attr]);
if (['noobscuret in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, cloneDeep(filter[attr])) : cloneDeep(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? combineFilters(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: row.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: row.id,
}
});
}
}
]) : {
$not: otmOriginFilter,
[fk]: row.id,
};
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: otmFilter,
@ -852,21 +871,17 @@ export default class TreeStore extends CascadeStore {
});
};
if (filter.id && typeof filter.id === 'string') {
const otmFilter = !otmForeignKey ? Object.assign({
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : cloneDeep(filter[attr]);
if (['not in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, cloneDeep(filter[attr])) : cloneDeep(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? combineFilters(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: filter.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: filter.id,
}
});
}
}
]) : {
$not: otmOriginFilter,
[fk]: filter.id,
};
try {
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
@ -1468,7 +1483,7 @@ export default class TreeStore extends CascadeStore {
else {
assert([0, 1].includes(rel));
result2[k] = row2[k];
assert(['string', 'number', 'boolean'].includes(typeof row2[k]));
assert(['string', 'number', 'boolean'].includes(typeof row2[k]) || row2[k] === null);
key += `${row2[k]}`;
values.push(row2[k]);
}
@ -1566,8 +1581,9 @@ export default class TreeStore extends CascadeStore {
result[op] = 0;
const data = distinct ? uniq(results[op]) : results[op];
data.forEach((ele) => {
assert(typeof ele === 'number', '只有number类型的属性才可以计算sum');
result[op] += ele;
if (typeof ele === 'number') {
result[op] += ele;
}
});
}
else if (op.startsWith('#count')) {
@ -1582,11 +1598,14 @@ export default class TreeStore extends CascadeStore {
else if (op.startsWith('#avg')) {
result[op] = 0;
const data = (distinct ? uniq(results[op]) : results[op]).filter(ele => ![undefined, null].includes(ele));
let count = 0;
data.forEach((ele) => {
assert(typeof ele === 'number', '只有number类型的属性才可以计算avg');
result[op] += ele;
if (typeof ele === 'number') {
result[op] += ele;
count += 1;
}
});
result[op] = result[op] / data.length;
result[op] = result[op] / count;
}
}
return result;
@ -1659,6 +1678,7 @@ export default class TreeStore extends CascadeStore {
indexFrom,
count,
};
polishSelection(this.getSchema(), entity, selection);
const result = await this.cascadeSelectAsync(entity, selection, context, Object.assign({}, option, {
dontCollect: true,
}));
@ -1905,16 +1925,19 @@ export default class TreeStore extends CascadeStore {
}, context, option2);
this.addToOperationResult(result, entity, 'create');
}
else if (this.store[entity][id].$current?.[UpdateAtAttribute] <= d[entity][id][UpdateAtAttribute]) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity][id],
filter: {
id,
},
}, context, option2);
this.addToOperationResult(result, entity, 'update');
else if (this.store[entity]?.[id]) {
const row = this.constructRow(this.store[entity][id], context);
if (row[UpdateAtAttribute] <= d[entity][id][UpdateAtAttribute]) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity][id],
filter: {
id,
},
}, context, option2);
this.addToOperationResult(result, entity, 'update');
}
}
}
}

View File

@ -7,6 +7,7 @@ const Demand_1 = require("oak-domain/lib/types/Demand");
const Exception_1 = require("oak-domain/lib/types/Exception");
const Demand_2 = require("oak-domain/lib/types/Demand");
const relation_1 = require("oak-domain/lib/store/relation");
// Expression引入下方声明改为any防止报错
const Expression_1 = require("oak-domain/lib/types/Expression");
const SyncRowStore_1 = require("oak-domain/lib/store/SyncRowStore");
const CascadeStore_1 = require("oak-domain/lib/store/CascadeStore");
@ -148,13 +149,12 @@ class TreeStore extends CascadeStore_1.CascadeStore {
this.seq = {};
}
constructRow(node, context, option) {
let data = (0, lodash_1.cloneDeep)(node.$current);
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
if (node.$current && option?.includedDeleted) {
return Object.assign({}, node.$current, {
[Entity_1.DeleteAtAttribute]: 1,
});
}
@ -162,19 +162,21 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
else if (!node.$current) {
// 本事务创建的若在cache中$$createAt$$和$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncRowStore_1.SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncRowStore_1.SyncContext && {
[Entity_1.CreateAtAttribute]: 1,
[Entity_1.UpdateAtAttribute]: 1,
});
}
else {
// 本事务更新的若在cache中$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncRowStore_1.SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncRowStore_1.SyncContext && {
[Entity_1.UpdateAtAttribute]: 1,
});
}
}
return data;
return {
...node.$current,
};
}
testFilterFns(node, nodeDict, exprResolveFns, fns) {
const { self, otm, mto } = fns;
@ -292,12 +294,16 @@ class TreeStore extends CascadeStore_1.CascadeStore {
* @param context
* @returns
*/
translateExpressionNode(entity, expression, context, option) {
translateExpressionNode(entity,
// expression: Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']> | ExpressionConstant,
expression, context, option) {
if ((0, Expression_1.isExpression)(expression)) {
const op = Object.keys(expression)[0];
const option2 = expression[op];
if ((0, Expression_1.opMultipleParams)(op)) {
const paramsTranslated = option2.map(ele => this.translateExpressionNode(entity, ele, context, option2));
const paramsTranslated = option2.map(
// const paramsTranslated = (option2 as (Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']>)[]).map(
ele => this.translateExpressionNode(entity, ele, context, option2));
return (row, nodeDict) => {
let later = false;
let results = paramsTranslated.map((ele) => {
@ -387,7 +393,9 @@ class TreeStore extends CascadeStore_1.CascadeStore {
return expression;
}
}
translateExpression(entity, expression, context, option) {
translateExpression(entity,
// expression: Expression<keyof ED[T]['Schema']>,
expression, context, option) {
const expr = this.translateExpressionNode(entity, expression, context, option);
return (row, nodeDict) => {
if (typeof expr !== 'function') {
@ -568,6 +576,21 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}).length > 0;
};
}
case '$length': {
// json中的数组长度查询
const length = value;
return (row) => {
const data = path ? (0, lodash_1.get)(row, path) : row;
(0, assert_1.assert)(data instanceof Array, '$length operator can only used on array attribute');
if (typeof length === 'number') {
return data.length === length;
}
else {
const op = Object.keys(length)[0];
return this.translatePredicate(entity, 'length', op, length[op], option)(data);
}
};
}
default: {
throw new Error(`predicate ${predicate} is not recoganized`);
}
@ -793,8 +816,8 @@ class TreeStore extends CascadeStore_1.CascadeStore {
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反外键连接条件的至少有一行
* all代表反连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反连接条件的至少有一行
*
* 此时还没有确定父行只有查询中明确带有id的查询可以先执行否则不执行暂先这个逻辑 by Xc 20230725
*/
@ -807,24 +830,20 @@ class TreeStore extends CascadeStore_1.CascadeStore {
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反外键连接条件的至少有一行
* all代表反连接条件的一行也不能有符合的是否至少要有一行直觉上没这个限制
* not all 代表反连接条件的至少有一行
*/
const otmFilter = !otmForeignKey ? Object.assign({
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : (0, lodash_1.cloneDeep)(filter[attr]);
if (['noobscuret in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, (0, lodash_1.cloneDeep)(filter[attr])) : (0, lodash_1.cloneDeep)(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? (0, filter_1.combineFilters)(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: row.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: row.id,
}
});
}
}
]) : {
$not: otmOriginFilter,
[fk]: row.id,
};
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: otmFilter,
@ -854,21 +873,17 @@ class TreeStore extends CascadeStore_1.CascadeStore {
});
};
if (filter.id && typeof filter.id === 'string') {
const otmFilter = !otmForeignKey ? Object.assign({
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : (0, lodash_1.cloneDeep)(filter[attr]);
if (['not in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, (0, lodash_1.cloneDeep)(filter[attr])) : (0, lodash_1.cloneDeep)(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? (0, filter_1.combineFilters)(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: filter.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: filter.id,
}
});
}
}
]) : {
$not: otmOriginFilter,
[fk]: filter.id,
};
try {
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
@ -1470,7 +1485,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
else {
(0, assert_1.assert)([0, 1].includes(rel));
result2[k] = row2[k];
(0, assert_1.assert)(['string', 'number', 'boolean'].includes(typeof row2[k]));
(0, assert_1.assert)(['string', 'number', 'boolean'].includes(typeof row2[k]) || row2[k] === null);
key += `${row2[k]}`;
values.push(row2[k]);
}
@ -1568,8 +1583,9 @@ class TreeStore extends CascadeStore_1.CascadeStore {
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;
if (typeof ele === 'number') {
result[op] += ele;
}
});
}
else if (op.startsWith('#count')) {
@ -1584,11 +1600,14 @@ class TreeStore extends CascadeStore_1.CascadeStore {
else if (op.startsWith('#avg')) {
result[op] = 0;
const data = (distinct ? (0, lodash_1.uniq)(results[op]) : results[op]).filter(ele => ![undefined, null].includes(ele));
let count = 0;
data.forEach((ele) => {
(0, assert_1.assert)(typeof ele === 'number', '只有number类型的属性才可以计算avg');
result[op] += ele;
if (typeof ele === 'number') {
result[op] += ele;
count += 1;
}
});
result[op] = result[op] / data.length;
result[op] = result[op] / count;
}
}
return result;
@ -1661,6 +1680,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
indexFrom,
count,
};
(0, CascadeStore_1.polishSelection)(this.getSchema(), entity, selection);
const result = await this.cascadeSelectAsync(entity, selection, context, Object.assign({}, option, {
dontCollect: true,
}));
@ -1907,16 +1927,19 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}, context, option2);
this.addToOperationResult(result, entity, 'create');
}
else if (this.store[entity][id].$current?.[Entity_1.UpdateAtAttribute] <= d[entity][id][Entity_1.UpdateAtAttribute]) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity][id],
filter: {
id,
},
}, context, option2);
this.addToOperationResult(result, entity, 'update');
else if (this.store[entity]?.[id]) {
const row = this.constructRow(this.store[entity][id], context);
if (row[Entity_1.UpdateAtAttribute] <= d[entity][id][Entity_1.UpdateAtAttribute]) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity][id],
filter: {
id,
},
}, context, option2);
this.addToOperationResult(result, entity, 'update');
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "oak-memory-tree-store",
"version": "3.3.9",
"version": "3.3.15",
"description": "oak框架中内存级store的实现",
"author": {
"name": "XuChang"
@ -10,7 +10,7 @@
"es/**/*"
],
"dependencies": {
"oak-domain": "^5.1.15",
"oak-domain": "file:../oak-domain",
"uuid": "^8.3.2"
},
"scripts": {

View File

@ -15,10 +15,11 @@ import { StorageSchema } from 'oak-domain/lib/types/Storage';
import { ExprResolveFn, NodeDict, RowNode } from "./types/type";
import { isRefAttrNode, Q_BooleanValue, Q_FullTextValue, Q_NumberValue, Q_StringValue } from 'oak-domain/lib/types/Demand';
import { judgeRelation } from 'oak-domain/lib/store/relation';
// Expression引入下方声明改为any防止报错
import { execOp, Expression, ExpressionConstant, isExpression, opMultipleParams } from 'oak-domain/lib/types/Expression';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
import { CascadeStore, polishSelection } from 'oak-domain/lib/store/CascadeStore';
import { Context, CreateOpResult, SeqAttribute } from 'oak-domain/lib/types';
import { combineFilters, getRelevantIds } from 'oak-domain/lib/store/filter';
@ -219,13 +220,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
private constructRow<Cxt extends Context, OP extends TreeStoreSelectOption>(node: RowNode, context: Cxt, option?: OP) {
let data = cloneDeep(node.$current);
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
if (node.$current && option?.includedDeleted) {
return Object.assign({}, node.$current, {
[DeleteAtAttribute]: 1,
});
}
@ -233,19 +233,21 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
else if (!node.$current) {
// 本事务创建的若在cache中$$createAt$$和$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncContext && {
[CreateAtAttribute]: 1,
[UpdateAtAttribute]: 1,
});
}
else {
// 本事务更新的若在cache中$$updateAt$$置为1
return Object.assign({}, data, node.$next, context instanceof SyncContext && {
return Object.assign({}, node.$current, node.$next, context instanceof SyncContext && {
[UpdateAtAttribute]: 1,
});
})
}
}
return data;
return {
...node.$current,
};
}
private testFilterFns(
@ -397,7 +399,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
*/
private translateExpressionNode<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends Context>(
entity: T,
expression: Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']> | ExpressionConstant,
// expression: Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']> | ExpressionConstant,
expression: any,
context: Cxt,
option?: OP): ExprNodeTranslator | ExpressionConstant {
@ -406,7 +409,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const option2 = (expression as any)[op];
if (opMultipleParams(op)) {
const paramsTranslated = (option2 as (Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']>)[]).map(
const paramsTranslated = (option2 as any[]).map(
// const paramsTranslated = (option2 as (Expression<keyof ED[T]['Schema']> | RefAttr<keyof ED[T]['Schema']>)[]).map(
ele => this.translateExpressionNode(entity, ele, context, option2)
);
@ -514,7 +518,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
private translateExpression<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends Context>(
entity: T,
expression: Expression<keyof ED[T]['Schema']>,
// expression: Expression<keyof ED[T]['Schema']>,
expression: any,
context: Cxt, option?: OP): (row: Partial<ED[T]['OpSchema']>, nodeDict: NodeDict) => ExpressionConstant | ExprLaterCheckFn {
const expr = this.translateExpressionNode(entity, expression, context, option);
@ -707,6 +712,21 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}).length > 0;
};
}
case '$length': {
// json中的数组长度查询
const length = value;
return (row) => {
const data = path ? get(row, path) : row;
assert(data instanceof Array, '$length operator can only used on array attribute');
if (typeof length === 'number') {
return data.length === length;
}
else {
const op = Object.keys(length)[0];
return this.translatePredicate(entity, 'length', op, length[op], option)(data);
}
}
}
default: {
throw new Error(`predicate ${predicate} is not recoganized`);
}
@ -797,7 +817,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return (node) => {
const row = this.constructRow(node, context, option);
if (row) {
const value = row[attr];
const value = (row as any)[attr];
if (value === undefined && option?.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, attr);
}
@ -973,8 +993,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有
* not all
* all代表反连接条件的一行也不能有
* not all
*
* id的查询可以先执行 by Xc 20230725
*/
@ -987,23 +1007,20 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
/**
* in代表外键连接后至少有一行数据
* not in代表外键连接后一行也不能有
* all代表反外键连接条件的一行也不能有
* not all
* all代表反连接条件的一行也不能有
* not all
*/
const otmFilter = !otmForeignKey ? Object.assign({
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : cloneDeep(filter[attr]);
if (['noobscuret in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, cloneDeep(filter[attr])) : cloneDeep(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? combineFilters(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: row.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: row.id,
}
});
}
]) : {
$not: otmOriginFilter,
[fk]: row.id,
}
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
@ -1036,21 +1053,18 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
});
};
if (filter.id && typeof filter.id === 'string') {
const otmFilter = !otmForeignKey ? Object.assign({
if (filter.id && typeof filter.id === 'string') {
const otmOriginFilter = !otmForeignKey ? Object.assign({
entity,
}, filter[attr]) : cloneDeep(filter[attr]);
if (['not in', 'in'].includes(predicate)) {
Object.assign(otmFilter, {
}, cloneDeep(filter[attr])) : cloneDeep(filter[attr]);
const otmFilter = ['not in', 'in'].includes(predicate) ? combineFilters(otmEntity, this.getSchema(), [
otmOriginFilter, {
[fk]: filter.id,
});
}
else {
Object.assign(otmFilter, {
[fk]: {
$ne: filter.id,
}
});
}
]) : {
$not: otmOriginFilter,
[fk]: filter.id,
}
try {
const subQuerySet = (this.selectAbjointRow(otmEntity, {
@ -1755,7 +1769,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
else {
assert([0, 1].includes(rel as number));
(result2 as any)[k] = row2[k];
assert(['string', 'number', 'boolean'].includes(typeof row2[k]));
assert(['string', 'number', 'boolean'].includes(typeof row2[k]) || row2[k] === null);
key += `${row2[k]}`;
values.push(row2[k]);
}
@ -1866,8 +1880,9 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const data = distinct ? uniq(results[op]) : results[op];
data.forEach(
(ele) => {
assert(typeof ele === 'number', '只有number类型的属性才可以计算sum');
result[op] += ele;
if (typeof ele === 'number') {
result[op] += ele;
}
}
);
}
@ -1887,13 +1902,16 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const data = (distinct ? uniq(results[op]) : results[op]).filter(
ele => ![undefined, null].includes(ele)
);
let count = 0;
data.forEach(
(ele) => {
assert(typeof ele === 'number', '只有number类型的属性才可以计算avg');
result[op] += ele;
if (typeof ele === 'number') {
result[op] += ele;
count += 1;
}
}
);
result[op] = result[op]/data.length;
result[op] = result[op]/count;
}
}
return result as AggregationResult<ED[T]['Schema']>[number];
@ -2001,6 +2019,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
indexFrom,
count,
};
polishSelection(this.getSchema(), entity, selection);
const result = await this.cascadeSelectAsync(entity, selection, context, Object.assign({}, option, {
dontCollect: true,
@ -2280,16 +2299,19 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
} as ED[keyof ED]['CreateSingle'], context, option2);
this.addToOperationResult(result, entity, 'create');
}
else if (this.store[entity]![id].$current?.[UpdateAtAttribute] <= d[entity]![id]![UpdateAtAttribute]) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity]![id] as any,
filter: {
id,
} as any,
}, context, option2);
this.addToOperationResult(result, entity, 'update');
else if (this.store[entity]?.[id]) {
const row = this.constructRow(this.store[entity]![id]!, context);
if ((row![UpdateAtAttribute] as number) <= (d[entity]![id]![UpdateAtAttribute] as number)) {
this.updateAbjointRow(entity, {
id: 'dummy',
action: 'update',
data: d[entity]![id] as any,
filter: {
id,
} as any,
}, context, option2);
this.addToOperationResult(result, entity, 'update');
}
}
}
}
@ -2304,7 +2326,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
id: 'dummy',
action: 'update',
data: d,
filter: combineFilters(e, this.getSchema(), [f, {
filter: combineFilters(e, this.getSchema(), [f!, {
[UpdateAtAttribute]: {
$lte: d[UpdateAtAttribute],
}

View File

@ -24,6 +24,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-2',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -39,6 +40,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -201,6 +203,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -216,6 +219,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -267,6 +271,7 @@ describe('基础测试', function () {
entity: 'user-id-1',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -282,6 +287,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -358,6 +364,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -373,6 +380,7 @@ describe('基础测试', function () {
entity: 'user3',
entityId: 'user3-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -440,6 +448,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -455,6 +464,7 @@ describe('基础测试', function () {
entity: 'user3',
entityId: 'user3-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -518,6 +528,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -533,6 +544,7 @@ describe('基础测试', function () {
entity: 'user3',
entityId: 'user3-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -621,6 +633,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -686,6 +699,7 @@ describe('基础测试', function () {
entity: 'user',
entityId: 'user-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -701,6 +715,7 @@ describe('基础测试', function () {
entity: 'user3',
entityId: 'user3-id-1',
modi: {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
@ -1302,6 +1317,43 @@ describe('基础测试', function () {
}
}, context, {});
const row12 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: {
$length: 3,
},
}
}
}, context, {});
const row13 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: {
$length: {
$gt: 3,
},
},
}
}
}, context, {});
context.commit();
assert(row.length === 1);
assert(row2.length === 0);
@ -1314,6 +1366,8 @@ describe('基础测试', function () {
assert(row9.length === 1);
assert(row10.length === 1);
assert(row11.length === 0);
assert(row12.length === 1);
assert(row13.length === 0);
// console.log(JSON.stringify(row7));
});