diff --git a/es/store.d.ts b/es/store.d.ts index 0de1ea2..43e4d76 100644 --- a/es/store.d.ts +++ b/es/store.d.ts @@ -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 { } diff --git a/es/store.js b/es/store.js index 281a314..ea8ddd2 100644 --- a/es/store.js +++ b/es/store.js @@ -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 { } // 先计算projection,formResult只处理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); diff --git a/lib/store.d.ts b/lib/store.d.ts index 0de1ea2..43e4d76 100644 --- a/lib/store.d.ts +++ b/lib/store.d.ts @@ -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 { } diff --git a/lib/store.js b/lib/store.js index 8e55816..d329380 100644 --- a/lib/store.js +++ b/lib/store.js @@ -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 { } // 先计算projection,formResult只处理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); diff --git a/package.json b/package.json index 98fbccb..3d3d380 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/store.ts b/src/store.ts index 046d71a..7f9f9f7 100644 --- a/src/store.ts +++ b/src/store.ts @@ -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 extends OakException { @@ -42,6 +39,11 @@ class OakExpressionUnresolvedException e export interface TreeStoreSelectOption extends SelectOption { nodeDict?: NodeDict; disableSubQueryHashjoin?: boolean; // 禁用hashjoin优化,可能会导致性能下降严重(对比测试用) + warnWhenAttributeMiss?: boolean; // 当有属性miss时,报一个Warning(debug使用) +} + +function showWarningAttributeMiss(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 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 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 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 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 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 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(entity, (expression as { + '#attr': string; + })['#attr']); + } + return attr; } else { assert(expression.hasOwnProperty('#refId')); @@ -530,92 +543,110 @@ export default class TreeStore 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(entity, name as string); + } + + if (typeof value === 'string' && row[name].includes($search)) { + return true; + } } } return false; }; } - private translatePredicate(path: string, predicate: string, value: any, option?: OP): (row: Record) => boolean { + private translatePredicate(entity: keyof ED, path: string, predicate: string, value: any, option?: OP): (row: Record) => 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(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': { @@ -623,11 +654,12 @@ export default class TreeStore extends C 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); } }; } @@ -635,14 +667,16 @@ export default class TreeStore 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(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': { @@ -650,12 +684,13 @@ export default class TreeStore extends C 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: 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 extends C 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: 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 extends C } } - private translateObjectPredicate(filter: Record) { + private translateObjectPredicate(entity: keyof ED, filter: Record) { const fns: Array<(value: any) => boolean> = []; const translatePredicateInner = (p: Record | Array, path: string, fns2: Array<(value: any) => boolean>) => { if (p instanceof Array) { @@ -686,7 +722,7 @@ export default class TreeStore 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 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 extends C attr: string, context: Cxt, option?: OP): (node: RowNode, nodeDict: NodeDict, exprResolveFns: Array) => 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(entity, attr); + } + return value === filter; + } + return false; }; } else { @@ -774,7 +813,7 @@ export default class TreeStore extends C throw new Error('子查询已经改用一对多的外键连接方式'); } else { - const fn = this.translatePredicate(attr, predicate, (filter as Record)[predicate], option); + const fn = this.translatePredicate(entity, attr, predicate, (filter as Record)[predicate], option); return (node) => { const row = this.constructRow(node, context, option); if (!row) { @@ -799,13 +838,13 @@ export default class TreeStore 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 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(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 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 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(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 extends C const [otmEntity, otmForeignKey] = relation; const predicate: NonNullable = 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 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 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 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 extends C // 先计算projection,formResult只处理abjoint的行,不需要考虑expression和一对多多对一关系 let rows2: Array> = []; - const incompletedRowIds: string[] = []; const { data: projection } = selection; for (const row of rows) { const result = {} as Partial; @@ -1628,7 +1629,9 @@ export default class TreeStore 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 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) {