Merge branch 'release'

This commit is contained in:
Xu Chang 2024-12-23 16:39:33 +08:00
commit 24b454c75b
6 changed files with 317 additions and 385 deletions

1
es/store.d.ts vendored
View File

@ -9,6 +9,7 @@ import { Context } from 'oak-domain/lib/types';
export interface TreeStoreSelectOption extends SelectOption {
nodeDict?: NodeDict;
disableSubQueryHashjoin?: boolean;
warnWhenAttributeMiss?: boolean;
}
export interface TreeStoreOperateOption extends OperateOption {
}

View File

@ -2,7 +2,7 @@ import { cloneDeep, get, groupBy, set, unset, uniqBy, uniq, differenceBy, inters
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';
import { OakCongruentRowExists, OakException, OakRowUnexistedException } from 'oak-domain/lib/types/Exception';
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';
import { execOp, isExpression, opMultipleParams } from 'oak-domain/lib/types/Expression';
@ -12,11 +12,11 @@ import { SeqAttribute } from 'oak-domain/lib/types';
import { combineFilters, getRelevantIds } from 'oak-domain/lib/store/filter';
;
;
function obscurePass(value, option) {
return !!(option?.obscure && value === undefined);
}
class OakExpressionUnresolvedException extends OakException {
}
function showWarningAttributeMiss(entity, attr) {
console.warn(`attribute miss: entity: ${entity}, attr: ${attr}`);
}
;
export default class TreeStore extends CascadeStore {
store;
@ -70,8 +70,12 @@ export default class TreeStore extends CascadeStore {
resetInitialData(data, stat) {
this.store = {};
const now = Date.now();
const schema = this.getSchema();
for (const entity in data) {
const { attributes } = this.getSchema()[entity];
if (!schema[entity]) {
throw new Error(`reset unknown entity:[${entity}]`);
}
const { attributes } = schema[entity];
this.store[entity] = {};
for (const row of data[entity]) {
for (const key in attributes) {
@ -146,7 +150,8 @@ export default class TreeStore extends CascadeStore {
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
if (option?.includedDeleted) {
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
[DeleteAtAttribute]: 1,
});
@ -304,7 +309,7 @@ export default class TreeStore extends CascadeStore {
return ele;
});
if (!later) {
return execOp(op, results, option2.obscure);
return execOp(op, results);
}
const laterCheckFn = (nodeDict2) => {
results = results.map((ele) => {
@ -317,7 +322,7 @@ export default class TreeStore extends CascadeStore {
if (results.find(ele => typeof ele === 'function')) {
return laterCheckFn;
}
return execOp(op, results, option && option.obscure);
return execOp(op, results);
};
return laterCheckFn;
};
@ -337,12 +342,12 @@ export default class TreeStore extends CascadeStore {
};
return laterCheckFn;
}
return execOp(op, result, option2.obscure);
return execOp(op, result);
};
}
else {
return () => {
return execOp(op, paramsTranslated, option2.obscure);
return execOp(op, paramsTranslated);
};
}
}
@ -352,7 +357,11 @@ export default class TreeStore extends CascadeStore {
return (row, nodeDict) => {
if (expression.hasOwnProperty('#attr')) {
// 说明是本结点的属性;
return row[expression['#attr']];
const attr = row[expression['#attr']];
if (attr === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, expression['#attr']);
}
return attr;
}
else {
assert(expression.hasOwnProperty('#refId'));
@ -394,92 +403,109 @@ export default class TreeStore extends CascadeStore {
const { $search } = filter;
return (node) => {
const row = this.constructRow(node, context, option);
for (const attr of attributes) {
const { name } = attr;
if (row && row[name] && (typeof row[name] === 'string' && row[name].includes($search) || obscurePass(row[name], option))) {
return true;
if (row) {
for (const attr of attributes) {
const { name } = attr;
const value = row[name];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, name);
}
if (typeof value === 'string' && row[name].includes($search)) {
return true;
}
}
}
return false;
};
}
translatePredicate(path, predicate, value, option) {
translatePredicate(entity, path, predicate, value, option) {
switch (predicate) {
case '$gt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data > value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data > value;
};
}
case '$lt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data < value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data < value;
};
}
case '$gte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value;
};
}
case '$lte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data <= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data <= value;
};
}
case '$eq': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data === value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data === value;
};
}
case '$ne': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data !== value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data !== value;
};
}
case '$between': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1];
};
}
case '$mod': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return typeof data === 'number' && data % value[0] === value[1];
};
}
case '$startsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.startsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.startsWith(value);
};
}
case '$endsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.endsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.endsWith(value);
};
}
case '$includes': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.includes(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.includes(value);
};
}
case '$exists': {
@ -487,11 +513,12 @@ export default class TreeStore extends CascadeStore {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
if (value) {
return ![null, undefined].includes(data) || obscurePass(data, option);
return ![null, undefined].includes(data);
}
else {
return [null, undefined].includes(data) || obscurePass(data, option);
return [null, undefined].includes(data);
}
};
}
@ -499,14 +526,16 @@ export default class TreeStore extends CascadeStore {
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return value.includes(data);
};
}
case '$nin': {
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return !value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return !value.includes(data);
};
}
case '$contains': {
@ -514,12 +543,13 @@ export default class TreeStore extends CascadeStore {
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return differenceBy(array, data, (value) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length === 0 || obscurePass(data, option);
}).length === 0;
};
}
case '$overlaps': {
@ -527,12 +557,13 @@ export default class TreeStore extends CascadeStore {
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return intersectionBy(array, data, (value) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length > 0 || obscurePass(data, option);
}).length > 0;
};
}
default: {
@ -540,7 +571,7 @@ export default class TreeStore extends CascadeStore {
}
}
}
translateObjectPredicate(filter) {
translateObjectPredicate(entity, filter) {
const fns = [];
const translatePredicateInner = (p, path, fns2) => {
if (p instanceof Array) {
@ -548,7 +579,7 @@ export default class TreeStore extends CascadeStore {
const path2 = `${path}[${idx}]`;
if (typeof ele !== 'object') {
if (![null, undefined].includes(ele)) {
fns2.push(this.translatePredicate(path2, '$eq', ele));
fns2.push(this.translatePredicate(entity, path2, '$eq', ele));
}
}
else {
@ -575,13 +606,13 @@ export default class TreeStore extends CascadeStore {
}
else if (attr.startsWith('$')) {
assert(Object.keys(p).length === 1);
fns2.push(this.translatePredicate(path, attr, p[attr]));
fns2.push(this.translatePredicate(entity, path, attr, p[attr]));
}
else {
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', p[attr]));
fns2.push(this.translatePredicate(entity, path2, '$eq', p[attr]));
}
else {
translatePredicateInner(p[attr], path2, fns2);
@ -601,15 +632,18 @@ export default class TreeStore extends CascadeStore {
};
}
translateAttribute(entity, filter, attr, context, option) {
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row) {
return obscurePass(row[attr], option);
}
if (!['object', 'array'].includes(this.getSchema()[entity].attributes[attr]?.type)) {
if (typeof filter !== 'object') {
return (node) => {
const row = this.constructRow(node, context, option);
return row ? row[attr] === filter || obscurePassLocal(row) : false;
if (row) {
const value = row[attr];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, attr);
}
return value === filter;
}
return false;
};
}
else {
@ -619,7 +653,7 @@ export default class TreeStore extends CascadeStore {
throw new Error('子查询已经改用一对多的外键连接方式');
}
else {
const fn = this.translatePredicate(attr, predicate, filter[predicate], option);
const fn = this.translatePredicate(entity, attr, predicate, filter[predicate], option);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
@ -644,13 +678,13 @@ export default class TreeStore extends CascadeStore {
};
}
else {
const fn = this.translateObjectPredicate(filter);
const fn = this.translateObjectPredicate(entity, filter);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn(row[attr]) || obscurePassLocal(row);
return fn(row[attr]);
};
}
}
@ -703,27 +737,11 @@ export default class TreeStore extends CascadeStore {
if (!row) {
return false;
}
if (obscurePass(row.entity, option) || obscurePass(row.entityId, option)) {
return true;
}
if (row.entityId === undefined || row.entity === undefined) {
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的entity/entityId不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, 'entity/entityId');
}
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
return false; // 若不能确定,认定为条件不满足
}
if (row.entity !== attr) {
return false;
@ -733,9 +751,6 @@ export default class TreeStore extends CascadeStore {
}
const node2 = get(this.store, `${attr}.${row.entityId}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
@ -749,40 +764,18 @@ export default class TreeStore extends CascadeStore {
if (!row) {
return false;
}
if (obscurePass(row[`${attr}Id`], option)) {
return true;
}
if (row[`${attr}Id`]) {
const node2 = get(this.store, `${relation}.${row[`${attr}Id`]}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
}
if (row[`${attr}Id`] === undefined) {
// 说明一对多的外键没有取出来需要抛出RowUnexists异常
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的${attr}Id不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, `${attr}Id`);
}
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
}
assert(row[`${attr}Id`] === null);
return false;
});
}
@ -790,13 +783,6 @@ export default class TreeStore extends CascadeStore {
// 一对多的子查询
const [otmEntity, otmForeignKey] = relation;
const predicate = filter[attr][SUB_QUERY_PREDICATE_KEYWORD] || 'in';
if (option?.obscure) {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”或“并不全部是”所以可以直接返回true
if (['in', 'not all'].includes(predicate)) {
self.push(() => true);
continue;
}
}
const fk = otmForeignKey || 'entityId';
const otmProjection = {
id: 1,
@ -837,13 +823,17 @@ export default class TreeStore extends CascadeStore {
}
});
}
const option2 = Object.assign({}, option, { nodeDict, dontCollect: true });
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, option2)).map((ele) => {
}, context, {
...option,
nodeDict,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map((ele) => {
return (ele)[fk];
});
switch (predicate) {
@ -883,7 +873,11 @@ export default class TreeStore extends CascadeStore {
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, { dontCollect: true })).map((ele) => {
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map((ele) => {
return (ele)[fk];
});
self.push((node) => {
@ -919,12 +913,15 @@ export default class TreeStore extends CascadeStore {
/**
* 尝试用hashjoin将内表数组取出因为memory中的表都不会太大且用不了索引未来优化了可能可以用id直接取值因而用hash应当会更快
*/
const option2 = Object.assign({}, option, { dontCollect: true });
try {
const subQueryRows = this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: filter[attr],
}, context, option2);
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
});
const buckets = groupBy(subQueryRows, fk);
otm.push((node, nodeDict) => {
const row = this.constructRow(node, context, option);
@ -1355,7 +1352,6 @@ export default class TreeStore extends CascadeStore {
}
// 先计算projectionformResult只处理abjoint的行不需要考虑expression和一对多多对一关系
let rows2 = [];
const incompletedRowIds = [];
const { data: projection } = selection;
for (const row of rows) {
const result = {};
@ -1363,7 +1359,9 @@ export default class TreeStore extends CascadeStore {
const rel = this.judgeRelation(entity, attr);
if (rel === 1) {
if (row[attr] === undefined) {
incompletedRowIds.push(row.id);
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的属性${attr}缺失,可能会影响上层使用结果,请确定`);
}
// break;
}
else if (typeof projection[attr] === 'number') {
@ -1426,29 +1424,6 @@ export default class TreeStore extends CascadeStore {
}
rows2.push(result);
}
if (incompletedRowIds.length > 0) {
// 如果有缺失属性的行则报OakRowUnexistedException错误
// fixed: 这里不报了。按约定框架应当保证取到要访问的属性
// fixed: 和外键缺失一样还是报上层在知道框架会保证取到的情况下用allowMiss忽略此错误
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上有属性缺失,可能会影响上层使用结果,请确定`);
}
}
else {
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: {
$in: incompletedRowIds,
},
},
},
}]);
}
}
// 再计算sorter
if (sorter) {
const sorterFn = this.translateSorter(entity, sorter, context, option);

1
lib/store.d.ts vendored
View File

@ -9,6 +9,7 @@ import { Context } from 'oak-domain/lib/types';
export interface TreeStoreSelectOption extends SelectOption {
nodeDict?: NodeDict;
disableSubQueryHashjoin?: boolean;
warnWhenAttributeMiss?: boolean;
}
export interface TreeStoreOperateOption extends OperateOption {
}

View File

@ -14,11 +14,11 @@ const types_1 = require("oak-domain/lib/types");
const filter_1 = require("oak-domain/lib/store/filter");
;
;
function obscurePass(value, option) {
return !!(option?.obscure && value === undefined);
}
class OakExpressionUnresolvedException extends Exception_1.OakException {
}
function showWarningAttributeMiss(entity, attr) {
console.warn(`attribute miss: entity: ${entity}, attr: ${attr}`);
}
;
class TreeStore extends CascadeStore_1.CascadeStore {
store;
@ -72,8 +72,12 @@ class TreeStore extends CascadeStore_1.CascadeStore {
resetInitialData(data, stat) {
this.store = {};
const now = Date.now();
const schema = this.getSchema();
for (const entity in data) {
const { attributes } = this.getSchema()[entity];
if (!schema[entity]) {
throw new Error(`reset unknown entity:[${entity}]`);
}
const { attributes } = schema[entity];
this.store[entity] = {};
for (const row of data[entity]) {
for (const key in attributes) {
@ -148,7 +152,8 @@ class TreeStore extends CascadeStore_1.CascadeStore {
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
if (option?.includedDeleted) {
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
[Entity_1.DeleteAtAttribute]: 1,
});
@ -306,7 +311,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
return ele;
});
if (!later) {
return (0, Expression_1.execOp)(op, results, option2.obscure);
return (0, Expression_1.execOp)(op, results);
}
const laterCheckFn = (nodeDict2) => {
results = results.map((ele) => {
@ -319,7 +324,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
if (results.find(ele => typeof ele === 'function')) {
return laterCheckFn;
}
return (0, Expression_1.execOp)(op, results, option && option.obscure);
return (0, Expression_1.execOp)(op, results);
};
return laterCheckFn;
};
@ -339,12 +344,12 @@ class TreeStore extends CascadeStore_1.CascadeStore {
};
return laterCheckFn;
}
return (0, Expression_1.execOp)(op, result, option2.obscure);
return (0, Expression_1.execOp)(op, result);
};
}
else {
return () => {
return (0, Expression_1.execOp)(op, paramsTranslated, option2.obscure);
return (0, Expression_1.execOp)(op, paramsTranslated);
};
}
}
@ -354,7 +359,11 @@ class TreeStore extends CascadeStore_1.CascadeStore {
return (row, nodeDict) => {
if (expression.hasOwnProperty('#attr')) {
// 说明是本结点的属性;
return row[expression['#attr']];
const attr = row[expression['#attr']];
if (attr === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, expression['#attr']);
}
return attr;
}
else {
(0, assert_1.assert)(expression.hasOwnProperty('#refId'));
@ -396,92 +405,109 @@ class TreeStore extends CascadeStore_1.CascadeStore {
const { $search } = filter;
return (node) => {
const row = this.constructRow(node, context, option);
for (const attr of attributes) {
const { name } = attr;
if (row && row[name] && (typeof row[name] === 'string' && row[name].includes($search) || obscurePass(row[name], option))) {
return true;
if (row) {
for (const attr of attributes) {
const { name } = attr;
const value = row[name];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, name);
}
if (typeof value === 'string' && row[name].includes($search)) {
return true;
}
}
}
return false;
};
}
translatePredicate(path, predicate, value, option) {
translatePredicate(entity, path, predicate, value, option) {
switch (predicate) {
case '$gt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data > value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data > value;
};
}
case '$lt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data < value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data < value;
};
}
case '$gte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value;
};
}
case '$lte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data <= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data <= value;
};
}
case '$eq': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data === value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data === value;
};
}
case '$ne': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data !== value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data !== value;
};
}
case '$between': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1];
};
}
case '$mod': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return typeof data === 'number' && data % value[0] === value[1];
};
}
case '$startsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['string'].includes(typeof data) && data.startsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.startsWith(value);
};
}
case '$endsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['string'].includes(typeof data) && data.endsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.endsWith(value);
};
}
case '$includes': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
return ['string'].includes(typeof data) && data.includes(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return ['string'].includes(typeof data) && data.includes(value);
};
}
case '$exists': {
@ -489,11 +515,12 @@ class TreeStore extends CascadeStore_1.CascadeStore {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? (0, lodash_1.get)(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
if (value) {
return ![null, undefined].includes(data) || obscurePass(data, option);
return ![null, undefined].includes(data);
}
else {
return [null, undefined].includes(data) || obscurePass(data, option);
return [null, undefined].includes(data);
}
};
}
@ -501,14 +528,16 @@ class TreeStore extends CascadeStore_1.CascadeStore {
(0, assert_1.assert)(value instanceof Array);
return (row) => {
const data = (0, lodash_1.get)(row, path);
return value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return value.includes(data);
};
}
case '$nin': {
(0, assert_1.assert)(value instanceof Array);
return (row) => {
const data = (0, lodash_1.get)(row, path);
return !value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return !value.includes(data);
};
}
case '$contains': {
@ -516,12 +545,13 @@ class TreeStore extends CascadeStore_1.CascadeStore {
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? (0, lodash_1.get)(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return (0, lodash_1.differenceBy)(array, data, (value) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length === 0 || obscurePass(data, option);
}).length === 0;
};
}
case '$overlaps': {
@ -529,12 +559,13 @@ class TreeStore extends CascadeStore_1.CascadeStore {
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? (0, lodash_1.get)(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss(entity, path);
return (0, lodash_1.intersectionBy)(array, data, (value) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length > 0 || obscurePass(data, option);
}).length > 0;
};
}
default: {
@ -542,7 +573,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
}
}
translateObjectPredicate(filter) {
translateObjectPredicate(entity, filter) {
const fns = [];
const translatePredicateInner = (p, path, fns2) => {
if (p instanceof Array) {
@ -550,7 +581,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
const path2 = `${path}[${idx}]`;
if (typeof ele !== 'object') {
if (![null, undefined].includes(ele)) {
fns2.push(this.translatePredicate(path2, '$eq', ele));
fns2.push(this.translatePredicate(entity, path2, '$eq', ele));
}
}
else {
@ -577,13 +608,13 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
else if (attr.startsWith('$')) {
(0, assert_1.assert)(Object.keys(p).length === 1);
fns2.push(this.translatePredicate(path, attr, p[attr]));
fns2.push(this.translatePredicate(entity, path, attr, p[attr]));
}
else {
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', p[attr]));
fns2.push(this.translatePredicate(entity, path2, '$eq', p[attr]));
}
else {
translatePredicateInner(p[attr], path2, fns2);
@ -603,15 +634,18 @@ class TreeStore extends CascadeStore_1.CascadeStore {
};
}
translateAttribute(entity, filter, attr, context, option) {
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row) {
return obscurePass(row[attr], option);
}
if (!['object', 'array'].includes(this.getSchema()[entity].attributes[attr]?.type)) {
if (typeof filter !== 'object') {
return (node) => {
const row = this.constructRow(node, context, option);
return row ? row[attr] === filter || obscurePassLocal(row) : false;
if (row) {
const value = row[attr];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, attr);
}
return value === filter;
}
return false;
};
}
else {
@ -621,7 +655,7 @@ class TreeStore extends CascadeStore_1.CascadeStore {
throw new Error('子查询已经改用一对多的外键连接方式');
}
else {
const fn = this.translatePredicate(attr, predicate, filter[predicate], option);
const fn = this.translatePredicate(entity, attr, predicate, filter[predicate], option);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
@ -646,13 +680,13 @@ class TreeStore extends CascadeStore_1.CascadeStore {
};
}
else {
const fn = this.translateObjectPredicate(filter);
const fn = this.translateObjectPredicate(entity, filter);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn(row[attr]) || obscurePassLocal(row);
return fn(row[attr]);
};
}
}
@ -705,27 +739,11 @@ class TreeStore extends CascadeStore_1.CascadeStore {
if (!row) {
return false;
}
if (obscurePass(row.entity, option) || obscurePass(row.entityId, option)) {
return true;
}
if (row.entityId === undefined || row.entity === undefined) {
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的entity/entityId不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, 'entity/entityId');
}
throw new Exception_1.OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
return false; // 若不能确定,认定为条件不满足
}
if (row.entity !== attr) {
return false;
@ -735,9 +753,6 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
const node2 = (0, lodash_1.get)(this.store, `${attr}.${row.entityId}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
@ -751,40 +766,18 @@ class TreeStore extends CascadeStore_1.CascadeStore {
if (!row) {
return false;
}
if (obscurePass(row[`${attr}Id`], option)) {
return true;
}
if (row[`${attr}Id`]) {
const node2 = (0, lodash_1.get)(this.store, `${relation}.${row[`${attr}Id`]}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
}
if (row[`${attr}Id`] === undefined) {
// 说明一对多的外键没有取出来需要抛出RowUnexists异常
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的${attr}Id不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss(entity, `${attr}Id`);
}
throw new Exception_1.OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
}
(0, assert_1.assert)(row[`${attr}Id`] === null);
return false;
});
}
@ -792,13 +785,6 @@ class TreeStore extends CascadeStore_1.CascadeStore {
// 一对多的子查询
const [otmEntity, otmForeignKey] = relation;
const predicate = filter[attr][Demand_1.SUB_QUERY_PREDICATE_KEYWORD] || 'in';
if (option?.obscure) {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”或“并不全部是”所以可以直接返回true
if (['in', 'not all'].includes(predicate)) {
self.push(() => true);
continue;
}
}
const fk = otmForeignKey || 'entityId';
const otmProjection = {
id: 1,
@ -839,13 +825,17 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
});
}
const option2 = Object.assign({}, option, { nodeDict, dontCollect: true });
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, option2)).map((ele) => {
}, context, {
...option,
nodeDict,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map((ele) => {
return (ele)[fk];
});
switch (predicate) {
@ -885,7 +875,11 @@ class TreeStore extends CascadeStore_1.CascadeStore {
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, { dontCollect: true })).map((ele) => {
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map((ele) => {
return (ele)[fk];
});
self.push((node) => {
@ -921,12 +915,15 @@ class TreeStore extends CascadeStore_1.CascadeStore {
/**
* 尝试用hashjoin将内表数组取出因为memory中的表都不会太大且用不了索引未来优化了可能可以用id直接取值因而用hash应当会更快
*/
const option2 = Object.assign({}, option, { dontCollect: true });
try {
const subQueryRows = this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: filter[attr],
}, context, option2);
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
});
const buckets = (0, lodash_1.groupBy)(subQueryRows, fk);
otm.push((node, nodeDict) => {
const row = this.constructRow(node, context, option);
@ -1357,7 +1354,6 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
// 先计算projectionformResult只处理abjoint的行不需要考虑expression和一对多多对一关系
let rows2 = [];
const incompletedRowIds = [];
const { data: projection } = selection;
for (const row of rows) {
const result = {};
@ -1365,7 +1361,9 @@ class TreeStore extends CascadeStore_1.CascadeStore {
const rel = this.judgeRelation(entity, attr);
if (rel === 1) {
if (row[attr] === undefined) {
incompletedRowIds.push(row.id);
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上的属性${attr}缺失,可能会影响上层使用结果,请确定`);
}
// break;
}
else if (typeof projection[attr] === 'number') {
@ -1428,29 +1426,6 @@ class TreeStore extends CascadeStore_1.CascadeStore {
}
rows2.push(result);
}
if (incompletedRowIds.length > 0) {
// 如果有缺失属性的行则报OakRowUnexistedException错误
// fixed: 这里不报了。按约定框架应当保证取到要访问的属性
// fixed: 和外键缺失一样还是报上层在知道框架会保证取到的情况下用allowMiss忽略此错误
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity}上有属性缺失,可能会影响上层使用结果,请确定`);
}
}
else {
throw new Exception_1.OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: {
$in: incompletedRowIds,
},
},
},
}]);
}
}
// 再计算sorter
if (sorter) {
const sorterFn = this.translateSorter(entity, sorter, context, option);

View File

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

View File

@ -9,7 +9,7 @@ import {
EntityDict, SelectOption, DeleteAtAttribute, AggregationResult, AggregationOp, CreateAtAttribute, UpdateAtAttribute, UpdateOperation
} from "oak-domain/lib/types/Entity";
import { ExpressionKey, EXPRESSION_PREFIX, NodeId, RefAttr, SUB_QUERY_PREDICATE_KEYWORD, SubQueryPredicateMetadata } from 'oak-domain/lib/types/Demand';
import { OakCongruentRowExists, OakException, OakRowUnexistedException } from 'oak-domain/lib/types/Exception';
import { OakCongruentRowExists, OakException } 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';
import { ExprResolveFn, NodeDict, RowNode } from "./types/type";
@ -31,9 +31,6 @@ interface ExprNodeTranslator {
(row: any, nodeDict: NodeDict): ExpressionConstant | ExprLaterCheckFn;
};
function obscurePass(value: any, option?: SelectOption): boolean {
return !!(option?.obscure && value === undefined);
}
class OakExpressionUnresolvedException<ED extends EntityDict & BaseEntityDict> extends OakException<ED> {
@ -42,6 +39,11 @@ class OakExpressionUnresolvedException<ED extends EntityDict & BaseEntityDict> e
export interface TreeStoreSelectOption extends SelectOption {
nodeDict?: NodeDict;
disableSubQueryHashjoin?: boolean; // 禁用hashjoin优化可能会导致性能下降严重对比测试用
warnWhenAttributeMiss?: boolean; // 当有属性miss时报一个Warningdebug使用
}
function showWarningAttributeMiss<ED extends EntityDict & BaseEntityDict>(entity: keyof ED, attr: string) {
console.warn(`attribute miss: entity: ${entity as string}, attr: ${attr}`);
}
export interface TreeStoreOperateOption extends OperateOption {
@ -134,8 +136,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}) {
this.store = {};
const now = Date.now();
const schema = this.getSchema();
for (const entity in data) {
const { attributes } = this.getSchema()[entity];
if (!schema[entity]) {
throw new Error(`reset unknown entity:[${entity as string}]`);
}
const { attributes } = schema[entity];
this.store[entity] = {};
for (const row of data[entity]!) {
for (const key in attributes) {
@ -217,7 +223,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
if (context.getCurrentTxnId() && node.$txnId === context.getCurrentTxnId()) {
if (!node.$next) {
// 如果要求返回delete数据返回带$$deleteAt$$的行
if (option?.includedDeleted) {
// bug fixed这里如果是自己create再删除data也是null
if (data && option?.includedDeleted) {
return Object.assign({}, data, {
[DeleteAtAttribute]: 1,
});
@ -419,7 +426,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
);
if (!later) {
return execOp(op, results, option2.obscure);
return execOp(op, results);
}
const laterCheckFn = (nodeDict2: NodeDict) => {
results = results.map(
@ -434,7 +441,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
if (results.find(ele => typeof ele === 'function')) {
return laterCheckFn;
}
return execOp(op, results, option && option.obscure);
return execOp(op, results);
};
return laterCheckFn;
}
@ -454,12 +461,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
return laterCheckFn;
}
return execOp(op, result, option2.obscure);
return execOp(op, result);
}
}
else {
return () => {
return execOp(op, paramsTranslated, option2.obscure);
return execOp(op, paramsTranslated);
};
}
}
@ -469,9 +476,15 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return (row, nodeDict) => {
if (expression.hasOwnProperty('#attr')) {
// 说明是本结点的属性;
return row[(expression as {
const attr = row[(expression as {
'#attr': keyof ED[T]['Schema'];
})['#attr']] as ExpressionConstant;
if (attr === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, (expression as {
'#attr': string;
})['#attr']);
}
return attr;
}
else {
assert(expression.hasOwnProperty('#refId'));
@ -530,92 +543,110 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return (node) => {
const row = this.constructRow(node, context, option) as any;
for (const attr of attributes) {
const { name } = attr;
if (row && row[name] && (typeof row[name] === 'string' && row[name].includes($search) || obscurePass(row[name], option))) {
return true;
if (row) {
for (const attr of attributes) {
const { name } = attr;
const value = row[name];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, name as string);
}
if (typeof value === 'string' && row[name].includes($search)) {
return true;
}
}
}
return false;
};
}
private translatePredicate<OP extends TreeStoreSelectOption>(path: string, predicate: string, value: any, option?: OP): (row: Record<string, any>) => boolean {
private translatePredicate<OP extends TreeStoreSelectOption>(entity: keyof ED, path: string, predicate: string, value: any, option?: OP): (row: Record<string, any>) => boolean {
switch (predicate) {
case '$gt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data > value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data > value;
};
}
case '$lt': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data < value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data < value;
};
} case '$gte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value;
};
}
case '$lte': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data <= value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data <= value;
};
}
case '$eq': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data === value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data === value;
};
}
case '$ne': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data !== value || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data !== value;
};
}
case '$between': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['number', 'string'].includes(typeof data) && data >= value[0] && data <= value[1];
};
}
case '$mod': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return typeof data === 'number' && data % value[0] === value[1] || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return typeof data === 'number' && data % value[0] === value[1];
};
}
case '$startsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.startsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['string'].includes(typeof data) && data.startsWith(value);
};
}
case '$endsWith': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.endsWith(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['string'].includes(typeof data) && data.endsWith(value);
};
}
case '$includes': {
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
return ['string'].includes(typeof data) && data.includes(value) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return ['string'].includes(typeof data) && data.includes(value);
};
}
case '$exists': {
@ -623,11 +654,12 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return (row) => {
// JsonFilter有可能是根结点path为空
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
if (value) {
return ![null, undefined].includes(data) || obscurePass(data, option);
return ![null, undefined].includes(data);
}
else {
return [null, undefined].includes(data) || obscurePass(data, option);
return [null, undefined].includes(data);
}
};
}
@ -635,14 +667,16 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return value.includes(data);
};
}
case '$nin': {
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return !value.includes(data) || obscurePass(data, option);
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return !value.includes(data);
};
}
case '$contains': {
@ -650,12 +684,13 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return differenceBy(array, data, (value: any) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length === 0 || obscurePass(data, option);
}).length === 0;
};
}
case '$overlaps': {
@ -663,12 +698,13 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const array = value instanceof Array ? value : [value];
return (row) => {
const data = path ? get(row, path) : row;
data === undefined && option.warnWhenAttributeMiss && showWarningAttributeMiss<ED>(entity, path);
return intersectionBy(array, data, (value: any) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}).length > 0 || obscurePass(data, option);
}).length > 0;
};
}
default: {
@ -677,7 +713,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
}
private translateObjectPredicate(filter: Record<string, any>) {
private translateObjectPredicate(entity: keyof ED, filter: Record<string, any>) {
const fns: Array<(value: any) => boolean> = [];
const translatePredicateInner = (p: Record<string, any> | Array<any>, path: string, fns2: Array<(value: any) => boolean>) => {
if (p instanceof Array) {
@ -686,7 +722,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const path2 = `${path}[${idx}]`;
if (typeof ele !== 'object') {
if (![null, undefined].includes(ele)) {
fns2.push(this.translatePredicate(path2, '$eq', ele));
fns2.push(this.translatePredicate(entity, path2, '$eq', ele));
}
}
else {
@ -721,14 +757,14 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
else if (attr.startsWith('$')) {
assert(Object.keys(p).length === 1);
fns2.push(
this.translatePredicate(path, attr, p[attr])
this.translatePredicate(entity, path, attr, p[attr])
);
}
else {
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', p[attr]));
fns2.push(this.translatePredicate(entity, path2, '$eq', p[attr]));
}
else {
translatePredicateInner(p[attr], path2, fns2);
@ -756,15 +792,18 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
attr: string,
context: Cxt,
option?: OP): (node: RowNode, nodeDict: NodeDict, exprResolveFns: Array<ExprResolveFn>) => boolean {
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row: any) {
return obscurePass(row[attr], option);
}
if (!['object', 'array'].includes(this.getSchema()[entity].attributes[attr]?.type)) {
if (typeof filter !== 'object') {
return (node) => {
const row = this.constructRow(node, context, option);
return row ? (row as any)[attr] === filter || obscurePassLocal(row) : false;
if (row) {
const value = row[attr];
if (value === undefined && option.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, attr);
}
return value === filter;
}
return false;
};
}
else {
@ -774,7 +813,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
throw new Error('子查询已经改用一对多的外键连接方式');
}
else {
const fn = this.translatePredicate(attr, predicate, (filter as Record<string, any>)[predicate], option);
const fn = this.translatePredicate(entity, attr, predicate, (filter as Record<string, any>)[predicate], option);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
@ -799,13 +838,13 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
}
else {
const fn = this.translateObjectPredicate(filter);
const fn = this.translateObjectPredicate(entity, filter);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn((row as any)[attr]) || obscurePassLocal(row);
return fn((row as any)[attr]);
}
}
}
@ -876,27 +915,11 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
if (!row) {
return false;
}
if (obscurePass((row as any).entity, option) || obscurePass((row as any).entityId, option)) {
return true;
}
if ((row as any).entityId === undefined || (row as any).entity === undefined) {
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity as string}上的entity/entityId不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, 'entity/entityId');
}
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
return false; // 若不能确定,认定为条件不满足
}
if ((row as any).entity !== attr) {
return false;
@ -906,9 +929,6 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
const node2 = get(this.store, `${attr}.${(row as any).entityId}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
@ -924,40 +944,18 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
if (!row) {
return false;
}
if (obscurePass((row as any)[`${attr}Id`], option)) {
return true;
}
if ((row as any)[`${attr}Id`]) {
const node2 = get(this.store, `${relation}.${(row as any)[`${attr}Id`]}`);
if (!node2) {
if (option?.obscure) {
return true;
}
return false;
}
return filterFn(node2, nodeDict, exprResolveFns);
}
if ((row as any)[`${attr}Id`] === undefined) {
// 说明一对多的外键没有取出来需要抛出RowUnexists异常
// 这个assert在count的情况下不满足 by Xc 20240205
// assert(typeof projection[attr] === 'object');
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity as string}上的${attr}Id不能确定值可能会影响判定结果`);
}
return false; // 若不能确定,认定为条件不满足
if (option.warnWhenAttributeMiss) {
showWarningAttributeMiss<ED>(entity, `${attr}Id`);
}
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: row.id,
},
},
}]);
}
assert((row as any)[`${attr}Id`] === null);
return false;
}
);
@ -967,13 +965,6 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const [otmEntity, otmForeignKey] = relation;
const predicate: NonNullable<SubQueryPredicateMetadata['#sqp']> = filter[attr][SUB_QUERY_PREDICATE_KEYWORD] || 'in';
if (option?.obscure) {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”或“并不全部是”所以可以直接返回true
if (['in', 'not all'].includes(predicate)) {
self.push(() => true);
continue;
}
}
const fk = otmForeignKey || 'entityId';
const otmProjection = {
id: 1,
@ -1014,13 +1005,17 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
});
}
const option2 = Object.assign({}, option, { nodeDict, dontCollect: true });
const subQuerySet = (this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, option2)).map(
}, context, {
...option,
nodeDict,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map(
(ele) => {
return (ele)[fk] as string | null;
}
@ -1063,7 +1058,11 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
filter: otmFilter,
indexFrom: 0,
count: 1,
}, context, { dontCollect: true })).map(
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
})).map(
(ele) => {
return (ele)[fk] as string | null;
}
@ -1102,12 +1101,15 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
/**
* hashjoin将内表数组取出memory中的表都不会太大id直接取值hash应当会更快
*/
const option2 = Object.assign({}, option, { dontCollect: true });
try {
const subQueryRows = this.selectAbjointRow(otmEntity, {
data: otmProjection,
filter: filter[attr],
}, context, option2);
}, context, {
...option,
dontCollect: true,
warnWhenAttributeMiss: false, // 一对多连接不必要考虑这个属性缺失
});
const buckets = groupBy(subQueryRows, fk);
@ -1620,7 +1622,6 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
// 先计算projectionformResult只处理abjoint的行不需要考虑expression和一对多多对一关系
let rows2: Array<Partial<ED[T]['Schema']>> = [];
const incompletedRowIds: string[] = [];
const { data: projection } = selection;
for (const row of rows) {
const result = {} as Partial<ED[T]['Schema']>;
@ -1628,7 +1629,9 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const rel = this.judgeRelation(entity, attr);
if (rel === 1) {
if (row[attr] === undefined) {
incompletedRowIds.push(row.id!);
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity as string}上的属性${attr}缺失,可能会影响上层使用结果,请确定`);
}
// break;
}
else if (typeof projection[attr] === 'number') {
@ -1695,29 +1698,6 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
rows2.push(result);
}
if (incompletedRowIds.length > 0) {
// 如果有缺失属性的行则报OakRowUnexistedException错误
// fixed: 这里不报了。按约定框架应当保证取到要访问的属性
// fixed: 和外键缺失一样还是报上层在知道框架会保证取到的情况下用allowMiss忽略此错误
if (option?.ignoreAttrMiss) {
if (process.env.NODE_ENV === 'development') {
console.warn(`对象${entity as string}上有属性缺失,可能会影响上层使用结果,请确定`);
}
}
else {
throw new OakRowUnexistedException([{
entity,
selection: {
data: projection,
filter: {
id: {
$in: incompletedRowIds,
},
},
},
}]);
}
}
// 再计算sorter
if (sorter) {