支持了对object的内部投影和查询

This commit is contained in:
Xu Chang 2023-04-28 19:58:28 +08:00
parent ae472cc5b9
commit 7d6456ac8e
5 changed files with 878 additions and 343 deletions

2
lib/store.d.ts vendored
View File

@ -46,6 +46,8 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
private translateExpressionNode;
private translateExpression;
private translateFulltext;
private translatePredicate;
private translateObjectPredicate;
private translateAttribute;
private translateFilter;
private translateSorter;

View File

@ -12,8 +12,8 @@ var Expression_1 = require("oak-domain/lib/types/Expression");
var CascadeStore_1 = require("oak-domain/lib/store/CascadeStore");
;
;
function obscurePass(row, attr, option) {
return !!((option === null || option === void 0 ? void 0 : option.obscure) && row[attr] === undefined);
function obscurePass(value, option) {
return !!((option === null || option === void 0 ? void 0 : option.obscure) && value === undefined);
}
var OakExpressionUnresolvedException = /** @class */ (function (_super) {
tslib_1.__extends(OakExpressionUnresolvedException, _super);
@ -350,7 +350,7 @@ var TreeStore = /** @class */ (function (_super) {
for (var attributes_1 = tslib_1.__values(attributes), attributes_1_1 = attributes_1.next(); !attributes_1_1.done; attributes_1_1 = attributes_1.next()) {
var attr = attributes_1_1.value;
var name_1 = attr.name;
if (row && row[name_1] && (typeof row[name_1] === 'string' && row[name_1].includes($search) || obscurePass(row, name_1, option))) {
if (row && row[name_1] && (typeof row[name_1] === 'string' && row[name_1].includes($search) || obscurePass(row[name_1], option))) {
return true;
}
}
@ -365,188 +365,158 @@ var TreeStore = /** @class */ (function (_super) {
return false;
};
};
TreeStore.prototype.translateAttribute = function (entity, filter, attr, context, option) {
TreeStore.prototype.translatePredicate = function (path, predicate, value, option) {
switch (predicate) {
case '$gt': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data > value || obscurePass(data, option);
};
}
case '$lt': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data < value || obscurePass(data, option);
};
}
case '$gte': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data >= value || obscurePass(data, option);
};
}
case '$lte': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data <= value || obscurePass(data, option);
};
}
case '$eq': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data === value || obscurePass(data, option);
};
}
case '$ne': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data !== value || obscurePass(data, option);
};
}
case '$between': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data >= value[0] && data <= value[1] || obscurePass(data, option);
};
}
case '$startsWith': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data.startsWith(value) || obscurePass(data, option);
};
}
case '$endsWith': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data.endsWith(value) || obscurePass(data, option);
};
}
case '$includes': {
return function (row) {
var data = (0, lodash_1.get)(row, path);
return data.includes(value) || obscurePass(data, option);
};
}
case '$exists': {
(0, assert_1.assert)(typeof value === 'boolean');
return function (row) {
var data = (0, lodash_1.get)(row, path);
if (value) {
return ![null, undefined].includes(data) || obscurePass(data, option);
}
else {
return [null, undefined].includes(data) || obscurePass(data, option);
}
};
}
case '$in': {
(0, assert_1.assert)(value instanceof Array);
return function (row) {
var data = (0, lodash_1.get)(row, path);
return value.includes(data) || obscurePass(data, option);
};
}
case '$nin': {
(0, assert_1.assert)(value instanceof Array);
return function (row) {
var data = (0, lodash_1.get)(row, path);
return !value.includes(data) || obscurePass(data, option);
};
}
case '$contains': {
// json中的多值查询
(0, assert_1.assert)(value instanceof Array);
return function (row) {
var data = (0, lodash_1.get)(row, path);
return (0, lodash_1.difference)(value, data).length === 0 || obscurePass(data, option);
};
}
case '$overlaps': {
// json中的多值查询
(0, assert_1.assert)(value instanceof Array);
return function (row) {
var data = (0, lodash_1.get)(row, path);
return (0, lodash_1.intersection)(value, data).length > 0 || obscurePass(data, option);
};
}
default: {
throw new Error("predicate ".concat(predicate, " is not recoganized"));
}
}
};
TreeStore.prototype.translateObjectPredicate = function (filter) {
var _this = this;
var _a;
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row) {
return obscurePass(row, attr, option);
}
if (typeof filter !== 'object') {
return function (node) {
var row = _this.constructRow(node, context, option);
return row ? row[attr] === filter || obscurePassLocal(row) : false;
};
}
else if (((_a = this.getSchema()[entity].attributes[attr]) === null || _a === void 0 ? void 0 : _a.type) === 'object') {
// 如果查询的目标就是object则转化成object的比较
return function (node) {
var row = _this.constructRow(node, context, option);
return row ? JSON.stringify(row[attr]) === JSON.stringify(filter) || obscurePassLocal(row) : false;
};
}
var fns = [];
var _loop_1 = function (op) {
switch (op) {
case '$gt': {
fns.push(function (row) { return row && (row[attr] > filter[op]) || obscurePassLocal(row); });
break;
}
case '$lt': {
fns.push(function (row) { return row && (row[attr] < filter[op]) || obscurePassLocal(row); });
break;
}
case '$gte': {
fns.push(function (row) { return row && (row[attr] >= filter[op]) || obscurePassLocal(row); });
break;
}
case '$lte': {
fns.push(function (row) { return row && (row[attr] <= filter[op]) || obscurePassLocal(row); });
break;
}
case '$eq': {
fns.push(function (row) { return row && (row[attr] === filter[op]) || obscurePassLocal(row); });
break;
}
case '$ne': {
fns.push(function (row) { return row && (row[attr] !== filter[op]) || obscurePassLocal(row); });
break;
}
case '$between': {
fns.push(function (row) {
return row && (row[attr] >= filter[op][0] && row[attr] <= filter[op][1] || obscurePassLocal(row));
});
break;
}
case '$startsWith': {
fns.push(function (row) {
var _a;
return row && (((_a = row[attr]) === null || _a === void 0 ? void 0 : _a.startsWith(filter[op])) || obscurePassLocal(row));
});
break;
}
case '$endsWith': {
fns.push(function (row) {
var _a;
return row && (((_a = row[attr]) === null || _a === void 0 ? void 0 : _a.$endsWith(filter[op])) || obscurePassLocal(row));
});
break;
}
case '$includes': {
fns.push(function (row) {
var _a;
return row && (((_a = row[attr]) === null || _a === void 0 ? void 0 : _a.includes(filter[op])) || obscurePassLocal(row));
});
break;
}
case '$exists': {
var exists_1 = filter[op];
(0, assert_1.assert)(typeof exists_1 === 'boolean');
fns.push(function (row) {
if (exists_1) {
return ![null, undefined].includes(row[attr]) || obscurePassLocal(row);
var translatePredicateInner = function (p, path) {
var predicate = Object.keys(p)[0];
if (predicate.startsWith('$')) {
(0, assert_1.assert)(Object.keys(p).length === 1);
fns.push(_this.translatePredicate(path, predicate, p[predicate]));
}
else {
if (p instanceof Array) {
p.forEach(function (ele, idx) {
var path2 = "".concat(path, "[").concat(idx, "]");
if (typeof ele !== 'object') {
if (![null, undefined].includes(ele)) {
fns.push(_this.translatePredicate(path2, '$eq', ele));
}
}
else {
return [null, undefined].includes(row[attr]) || obscurePassLocal(row);
translatePredicateInner(ele, path2);
}
});
break;
}
case '$in': {
var inData_1 = filter[op];
(0, assert_1.assert)(typeof inData_1 === 'object');
if (inData_1 instanceof Array) {
fns.push(function (row) { return inData_1.includes(row[attr]) || obscurePassLocal(row); });
}
else {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”所以可以直接返回true
if (option === null || option === void 0 ? void 0 : option.obscure) {
fns.push(function () { return true; });
else {
for (var attr in p) {
var path2 = path ? "".concat(path, ".").concat(attr) : attr;
if (typeof p[attr] !== 'object') {
fns.push(_this.translatePredicate(path2, '$eq', filter[attr]));
}
else {
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
try {
var legalSets_1 = (this_1.selectAbjointRow(inData_1.entity, inData_1, context, option)).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
fns.push(function (row) { return legalSets_1.includes(row[attr]); });
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
fns.push(function (row, nodeDict) {
var option2 = Object.assign({}, option, { nodeDict: nodeDict });
var legalSets = _this.selectAbjointRow(inData_1.entity, inData_1, context, option2).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
return legalSets.includes(row[attr]);
});
}
else {
throw err;
}
}
translatePredicateInner(p[attr], path2);
}
}
break;
}
case '$nin': {
var inData_2 = filter[op];
(0, assert_1.assert)(typeof inData_2 === 'object');
if (inData_2 instanceof Array) {
fns.push(function (row) { return !inData_2.includes(row[attr]) || obscurePassLocal(row); });
}
else {
// obscure对nin没有影响如果返回的子查询结果中包含此行就一定是false否则一定为trueobscure只考虑数据不完整不考虑不准确但若相应属性为undefined则任然可以认为true
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
try {
var legalSets_2 = this_1.selectAbjointRow(inData_2.entity, inData_2, context, option).map(function (ele) {
var data = inData_2.data;
var key = Object.keys(data)[0];
return ele[key];
});
fns.push(function (row) { return !legalSets_2.includes(row[attr]) || obscurePassLocal(row); });
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
fns.push(function (row, nodeDict) {
var option2 = Object.assign({}, option, { nodeDict: nodeDict });
var legalSets = _this.selectAbjointRow(inData_2.entity, inData_2, context, option2).map(function (ele) {
var data = inData_2.data;
var key = Object.keys(data)[0];
return ele[key];
});
return !legalSets.includes(row[attr]) || obscurePassLocal(row);
});
}
else {
throw err;
}
}
}
break;
}
default:
(0, assert_1.assert)(false, "\u76EE\u524D\u4E0D\u652F\u6301\u7684\u7B97\u5B50".concat(op));
}
};
var this_1 = this;
for (var op in filter) {
_loop_1(op);
}
return function (node, nodeDict, exprResolveFns) {
translatePredicateInner(filter, '');
return function (value) {
var e_5, _a;
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
try {
for (var fns_5 = tslib_1.__values(fns), fns_5_1 = fns_5.next(); !fns_5_1.done; fns_5_1 = fns_5.next()) {
var fn = fns_5_1.value;
if (fn(row, nodeDict, exprResolveFns) === false) {
if (!fn(value)) {
return false;
}
}
@ -561,25 +531,150 @@ var TreeStore = /** @class */ (function (_super) {
return true;
};
};
TreeStore.prototype.translateAttribute = function (entity, filter, attr, context, option) {
var _this = this;
var _a;
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row) {
return obscurePass(row[attr], option);
}
if (typeof filter !== 'object') {
return function (node) {
var row = _this.constructRow(node, context, option);
return row ? row[attr] === filter || obscurePassLocal(row) : false;
};
}
else {
var predicate = Object.keys(filter)[0];
if (predicate.startsWith('$')) {
if (['$in', '$nin'].includes(predicate) && !(filter[predicate] instanceof Array)) {
var inData_1 = filter[predicate];
if (predicate === '$in') {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”所以可以直接返回true
if (option === null || option === void 0 ? void 0 : option.obscure) {
return function () { return true; };
}
else {
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
try {
var legalSets_1 = (this.selectAbjointRow(inData_1.entity, inData_1, context, option)).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
return function (node) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
return legalSets_1.includes(row[attr]);
};
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
return function (node, nodeDict) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
var option2 = Object.assign({}, option, { nodeDict: nodeDict });
var legalSets = _this.selectAbjointRow(inData_1.entity, inData_1, context, option2).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
return legalSets.includes(row[attr]);
};
}
else {
throw err;
}
}
}
}
else {
// obscure对nin没有影响如果返回的子查询结果中包含此行就一定是false否则一定为trueobscure只考虑数据不完整不考虑不准确但若相应属性为undefined则任然可以认为true
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
try {
var legalSets_2 = this.selectAbjointRow(inData_1.entity, inData_1, context, option).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
return function (node) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
return !legalSets_2.includes(row[attr]) || obscurePassLocal(row);
};
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
return function (node, nodeDict) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
var option2 = Object.assign({}, option, { nodeDict: nodeDict });
var legalSets = _this.selectAbjointRow(inData_1.entity, inData_1, context, option2).map(function (ele) {
var data = inData_1.data;
var key = Object.keys(data)[0];
return ele[key];
});
return !legalSets.includes(row[attr]) || obscurePassLocal(row);
};
}
else {
throw err;
}
}
}
}
else {
var fn_2 = this.translatePredicate(attr, predicate, filter[predicate], option);
return function (node) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn_2(row);
};
}
}
else {
// 对象的内部查询
(0, assert_1.assert)(((_a = this.getSchema()[entity].attributes[attr]) === null || _a === void 0 ? void 0 : _a.type) === 'object');
var fn_3 = this.translateObjectPredicate(filter);
return function (node) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn_3(row[attr]) || obscurePassLocal(row);
};
}
}
};
TreeStore.prototype.translateFilter = function (entity, filter, context, option) {
var _this = this;
var fns = [];
var nodeId;
var _loop_2 = function (attr) {
var _loop_1 = function (attr) {
if (attr === '#id') {
nodeId = filter['#id'];
}
else if (['$and', '$or', '$xor', '$not'].includes(attr)) {
fns.push(this_2.translateLogicFilter(entity, filter, attr, context, option));
fns.push(this_1.translateLogicFilter(entity, filter, attr, context, option));
}
else if (attr.toLowerCase().startsWith(Demand_1.EXPRESSION_PREFIX)) {
var fn_2 = this_2.translateExpression(entity, filter[attr], context, option);
var fn_4 = this_1.translateExpression(entity, filter[attr], context, option);
fns.push(function (node, nodeDict, exprResolveFns) {
var row = _this.constructRow(node, context, option);
if (!row) {
return false;
}
var result = fn_2(row, nodeDict);
var result = fn_4(row, nodeDict);
if (typeof result === 'function') {
exprResolveFns.push(result);
}
@ -587,21 +682,21 @@ var TreeStore = /** @class */ (function (_super) {
});
}
else if (attr.toLowerCase() === '$text') {
fns.push(this_2.translateFulltext(entity, filter[attr], context, option));
fns.push(this_1.translateFulltext(entity, filter[attr], context, option));
}
else {
// 属性级过滤
var relation_2 = (0, relation_1.judgeRelation)(this_2.getSchema(), entity, attr);
var relation_2 = (0, relation_1.judgeRelation)(this_1.getSchema(), entity, attr);
if (relation_2 === 1) {
// 行本身的属性
fns.push(this_2.translateAttribute(entity, filter[attr], attr, context, option));
fns.push(this_1.translateAttribute(entity, filter[attr], attr, context, option));
}
else if (relation_2 === 2) {
// 基于entity/entityId的指针
var fn_3 = this_2.translateFilter(attr, filter[attr], context, option);
var fn_5 = this_1.translateFilter(attr, filter[attr], context, option);
fns.push(function (node, nodeDict, exprResolveFns) {
var row = _this.constructRow(node, context, option);
if (obscurePass(row, 'entity', option) || obscurePass(row, 'entityId', option)) {
if (obscurePass(row.entity, option) || obscurePass(row.entityId, option)) {
return true;
}
if (row.entity !== attr || !row.entityId) {
@ -614,15 +709,15 @@ var TreeStore = /** @class */ (function (_super) {
}
return false;
}
return fn_3(node2, nodeDict, exprResolveFns);
return fn_5(node2, nodeDict, exprResolveFns);
});
}
else if (typeof relation_2 === 'string') {
// 只能是基于普通属性的外键
var fn_4 = this_2.translateFilter(relation_2, filter[attr], context, option);
var fn_6 = this_1.translateFilter(relation_2, filter[attr], context, option);
fns.push(function (node, nodeDict, exprResolveFns) {
var row = _this.constructRow(node, context, option);
if (obscurePass(row, "".concat(attr, "Id"), option)) {
if (obscurePass(row["".concat(attr, "Id")], option)) {
return true;
}
if (row["".concat(attr, "Id")]) {
@ -633,7 +728,7 @@ var TreeStore = /** @class */ (function (_super) {
}
return false;
}
return fn_4(node2, nodeDict, exprResolveFns);
return fn_6(node2, nodeDict, exprResolveFns);
}
return false;
});
@ -644,9 +739,9 @@ var TreeStore = /** @class */ (function (_super) {
}
}
};
var this_2 = this;
var this_1 = this;
for (var attr in filter) {
_loop_2(attr);
_loop_1(attr);
}
return function (node, nodeDict, exprResolveFns) {
var _a, e_6, _b;
@ -988,19 +1083,19 @@ var TreeStore = /** @class */ (function (_super) {
_c));
}
}
var _loop_3 = function (attr) {
var rel = this_3.judgeRelation(entity, attr);
var _loop_2 = function (attr) {
var rel = this_2.judgeRelation(entity, attr);
if (rel === 1) {
}
else if (rel === 2) {
if (data[attr]) {
this_3.formExprInResult(attr, projection[attr], data[attr], nodeDict, context);
this_2.formExprInResult(attr, projection[attr], data[attr], nodeDict, context);
}
}
else if (typeof rel === 'string') {
if (data[attr]) {
var result2 = {};
this_3.formExprInResult(rel, projection[attr], data[attr], nodeDict, context);
this_2.formExprInResult(rel, projection[attr], data[attr], nodeDict, context);
}
}
else if (rel instanceof Array) {
@ -1011,9 +1106,9 @@ var TreeStore = /** @class */ (function (_super) {
}
}
};
var this_3 = this;
var this_2 = this;
for (var attr in projection) {
_loop_3(attr);
_loop_2(attr);
}
for (var attr in laterExprDict) {
var exprResult = laterExprDict[attr](nodeDict);
@ -1025,7 +1120,7 @@ var TreeStore = /** @class */ (function (_super) {
}
};
TreeStore.prototype.formResult = function (entity, rows, selection, context, option) {
var e_11, _a, _b, _c;
var e_11, _a, _b;
var _this = this;
var data = selection.data, sorter = selection.sorter, indexFrom = selection.indexFrom, count = selection.count;
var findAvailableExprName = function (current) {
@ -1081,24 +1176,64 @@ var TreeStore = /** @class */ (function (_super) {
for (var rows_1 = tslib_1.__values(rows), rows_1_1 = rows_1.next(); !rows_1_1.done; rows_1_1 = rows_1.next()) {
var row = rows_1_1.value;
var result = {};
for (var attr in projection) {
var rel = this.judgeRelation(entity, attr);
var _loop_3 = function (attr) {
var _c, _d;
var rel = this_3.judgeRelation(entity, attr);
if (rel === 1) {
if (row[attr] === undefined) {
incompletedRowIds.push(row.id);
break;
return "break";
}
else if (typeof projection[attr] === 'number') {
Object.assign(result, (_c = {},
_c[attr] = row[attr],
_c));
}
else {
Object.assign(result, (_b = {},
_b[attr] = row[attr],
_b));
// object数据的深层次select
Object.assign(result, (_d = {},
_d[attr] = {},
_d));
var assignIner_1 = function (dest, proj, source) {
if (proj instanceof Array) {
(0, assert_1.assert)(dest instanceof Array);
(0, assert_1.assert)(source instanceof Array);
proj.forEach(function (attr, idx) {
if (typeof attr === 'number') {
dest[idx] = source[idx];
}
else if (typeof attr === 'object') {
dest[idx] = {};
assignIner_1(dest[idx], attr, source[idx]);
}
});
}
else {
for (var attr_1 in proj) {
if (typeof proj[attr_1] === 'number') {
dest[attr_1] = source[attr_1];
}
else if (typeof proj[attr_1] === 'object') {
dest[attr_1] = proj[attr_1] instanceof Array ? [] : {};
assignIner_1(dest[attr_1], proj[attr_1], source[attr_1]);
}
}
}
};
assignIner_1(result[attr], projection[attr], row[attr]);
}
}
};
var this_3 = this;
for (var attr in projection) {
var state_1 = _loop_3(attr);
if (state_1 === "break")
break;
}
if (row.$$deleteAt$$) {
Object.assign(result, (_c = {},
_c[Entity_1.DeleteAtAttribute] = row.$$deleteAt$$,
_c));
Object.assign(result, (_b = {},
_b[Entity_1.DeleteAtAttribute] = row.$$deleteAt$$,
_b));
}
rows2.push(result);
}

View File

@ -1,4 +1,4 @@
import { cloneDeep, get, groupBy, set, unset } from 'oak-domain/lib/utils/lodash';
import { cloneDeep, get, groupBy, set, unset, difference, intersection } from 'oak-domain/lib/utils/lodash';
import { assert } from 'oak-domain/lib/utils/assert';
import {
EntityShape, OperationResult, OperateOption, OpRecord,
@ -27,8 +27,8 @@ interface ExprNodeTranslator {
(row: any, nodeDict: NodeDict): ExpressionConstant | ExprLaterCheckFn;
};
function obscurePass(row: any, attr: string, option?: SelectOption): boolean {
return !!(option?.obscure && row[attr] === undefined);
function obscurePass(value: any, option?: SelectOption): boolean {
return !!(option?.obscure && value === undefined);
}
class OakExpressionUnresolvedException<ED extends EntityDict & BaseEntityDict> extends OakException<ED> {
@ -412,7 +412,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
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 as string, option))) {
if (row && row[name] && (typeof row[name] === 'string' && row[name].includes($search) || obscurePass(row[name], option))) {
return true;
}
}
@ -420,6 +420,165 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
};
}
private translatePredicate<OP extends TreeStoreSelectOption>(path: string, predicate: string, value: any, option?: OP): (row: Record<string, any>) => boolean {
switch (predicate) {
case '$gt': {
return (row) => {
const data = get(row, path);
return data > value || obscurePass(data, option);
};
}
case '$lt': {
return (row) => {
const data = get(row, path);
return data < value || obscurePass(data, option);
};
}case '$gte': {
return (row) => {
const data = get(row, path);
return data >= value || obscurePass(data, option);
};
}
case '$lte': {
return (row) => {
const data = get(row, path);
return data <= value || obscurePass(data, option);
};
}
case '$eq': {
return (row) => {
const data = get(row, path);
return data === value || obscurePass(data, option);
};
}
case '$ne': {
return (row) => {
const data = get(row, path);
return data !== value || obscurePass(data, option);
};
}
case '$between': {
return (row) => {
const data = get(row, path);
return data >= value[0] && data <= value[1]|| obscurePass(data, option);
};
}
case '$startsWith': {
return (row) => {
const data = get(row, path);
return data.startsWith(value) || obscurePass(data, option);
};
}
case '$endsWith': {
return (row) => {
const data = get(row, path);
return data.endsWith(value) || obscurePass(data, option);
};
}
case '$includes': {
return (row) => {
const data = get(row, path);
return data.includes(value) || obscurePass(data, option);
};
}
case '$exists': {
assert(typeof value === 'boolean');
return (row) => {
const data = get(row, path);
if (value) {
return ![null, undefined].includes(data) || obscurePass(data, option);
}
else {
return [null, undefined].includes(data) || obscurePass(data, option);
}
};
}
case '$in': {
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return value.includes(data) || obscurePass(data, option);
};
}
case '$nin': {
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return !value.includes(data) || obscurePass(data, option);
};
}
case '$contains': {
// json中的多值查询
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return difference(value, data).length === 0 || obscurePass(data, option);
};
}
case '$overlaps': {
// json中的多值查询
assert(value instanceof Array);
return (row) => {
const data = get(row, path);
return intersection(value, data).length > 0 || obscurePass(data, option);
};
}
default: {
throw new Error(`predicate ${predicate} is not recoganized`);
}
}
}
private translateObjectPredicate(filter: Record<string, any>) {
const fns: Array<(value: any) => boolean> = [];
const translatePredicateInner = (p: Record<string, any>, path: string) => {
const predicate = Object.keys(p)[0];
if (predicate.startsWith('$')) {
assert(Object.keys(p).length === 1);
fns.push(
this.translatePredicate(path, predicate, p[predicate])
);
}
else {
if (p instanceof Array) {
p.forEach(
(ele, idx) => {
const path2 = `${path}[${idx}]`;
if (typeof ele !== 'object') {
if (![null, undefined].includes(ele)) {
fns.push(this.translatePredicate(path2, '$eq', ele));
}
}
else {
translatePredicateInner(ele, path2);
}
}
);
}
else {
for (const attr in p) {
const path2 = path ? `${path}.${attr}` : attr;
if (typeof p[attr] !== 'object') {
fns.push(this.translatePredicate(path2, '$eq', filter[attr]));
}
else {
translatePredicateInner(p[attr], path2);
}
}
}
}
};
translatePredicateInner(filter, '');
return (value: any) => {
for (const fn of fns) {
if (!fn(value)) {
return false;
}
}
return true;
}
}
private translateAttribute<T extends keyof ED, OP extends TreeStoreSelectOption, Cxt extends Context>(
entity: T,
filter: Q_NumberValue | Q_StringValue | Q_BooleanValue | Object | ED[T]['Selection'] & {
@ -430,7 +589,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
option?: OP): (node: RowNode, nodeDict: NodeDict, exprResolveFns: Array<ExprResolveFn>) => boolean {
// 如果是模糊查询且该属性为undefined说明没取到返回true
function obscurePassLocal(row: any) {
return obscurePass(row, attr, option);
return obscurePass(row[attr], option);
}
if (typeof filter !== 'object') {
return (node) => {
@ -438,87 +597,15 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return row ? (row as any)[attr] === filter || obscurePassLocal(row) : false;
};
}
else if (this.getSchema()[entity].attributes[attr]?.type === 'object') {
// 如果查询的目标就是object则转化成object的比较
return (node) => {
const row = this.constructRow(node, context, option);
return row ? JSON.stringify((row as any)[attr]) === JSON.stringify(filter) || obscurePassLocal(row) : false;
};
}
const fns: Array<(row: any, nodeDict: NodeDict, exprResolveFns: Array<ExprResolveFn>) => boolean> = [];
for (const op in filter) {
switch (op) {
case '$gt': {
fns.push((row) => row && (row[attr] > (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$lt': {
fns.push((row) => row && (row[attr] < (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$gte': {
fns.push((row) => row && (row[attr] >= (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$lte': {
fns.push((row) => row && (row[attr] <= (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$eq': {
fns.push((row) => row && (row[attr] === (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$ne': {
fns.push((row) => row && (row[attr] !== (filter as any)[op]) || obscurePassLocal(row));
break;
}
case '$between': {
fns.push((row) => {
return row && (row[attr] >= (filter as any)[op][0] && row[attr] <= (filter as any)[op][1] || obscurePassLocal(row));
});
break;
}
case '$startsWith': {
fns.push((row) => {
return row && (row[attr]?.startsWith((filter as any)[op]) || obscurePassLocal(row));
});
break;
}
case '$endsWith': {
fns.push((row) => {
return row && (row[attr]?.$endsWith((filter as any)[op]) || obscurePassLocal(row));
});
break;
}
case '$includes': {
fns.push((row) => {
return row && (row[attr]?.includes((filter as any)[op]) || obscurePassLocal(row));
});
break;
}
case '$exists': {
const exists = (filter as any)[op];
assert(typeof exists === 'boolean');
fns.push((row) => {
if (exists) {
return ![null, undefined].includes(row[attr]) || obscurePassLocal(row);
}
else {
return [null, undefined].includes(row[attr]) || obscurePassLocal(row);
}
});
break;
}
case '$in': {
const inData = (filter as any)[op];
assert(typeof inData === 'object');
if (inData instanceof Array) {
fns.push((row) => inData.includes(row[attr]) || obscurePassLocal(row));
}
else {
else {
const predicate = Object.keys(filter)[0];
if (predicate.startsWith('$')) {
if (['$in', '$nin'].includes(predicate) && !((filter as Record<string, any>)[predicate] instanceof Array)) {
const inData = (filter as Record<string, any>)[predicate];
if (predicate === '$in') {
// 如果是obscure则返回的集合中有没有都不能否决“可能有”所以可以直接返回true
if (option?.obscure) {
fns.push(() => true);
return () => true;
}
else {
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
@ -531,25 +618,31 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
);
fns.push(
(row) => legalSets.includes(row[attr])
);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return legalSets.includes((row as any)[attr]);
};
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
fns.push(
(row, nodeDict) => {
const option2 = Object.assign({}, option, { nodeDict });
const legalSets = this.selectAbjointRow(inData.entity, inData, context, option2).map(
(ele) => {
const { data } = inData as ED[keyof ED]['Selection'];
const key = Object.keys(data)[0];
return (ele as any)[key];
}
);
return legalSets.includes(row[attr]);
return (node, nodeDict) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
);
const option2 = Object.assign({}, option, { nodeDict });
const legalSets = this.selectAbjointRow(inData.entity, inData, context, option2).map(
(ele) => {
const { data } = inData as ED[keyof ED]['Selection'];
const key = Object.keys(data)[0];
return (ele as any)[key];
}
);
return legalSets.includes((row as any)[attr]);
}
}
else {
throw err;
@ -557,14 +650,6 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
}
}
}
break;
}
case '$nin': {
const inData = (filter as any)[op];
assert(typeof inData === 'object');
if (inData instanceof Array) {
fns.push((row) => !inData.includes(row[attr]) || obscurePassLocal(row));
}
else {
// obscure对nin没有影响如果返回的子查询结果中包含此行就一定是false否则一定为trueobscure只考虑数据不完整不考虑不准确但若相应属性为undefined则任然可以认为true
// 这里只有当子查询中的filter不包含引用外部的子查询时才可以提前计算否则必须等到执行时再计算
@ -576,49 +661,61 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
return (ele as any)[key];
}
);
fns.push(
(row) => !legalSets.includes(row[attr]) || obscurePassLocal(row)
);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return !legalSets.includes((row as any)[attr]) || obscurePassLocal(row);
};
}
catch (err) {
if (err instanceof OakExpressionUnresolvedException) {
fns.push(
(row, nodeDict) => {
const option2 = Object.assign({}, option, { nodeDict });
const legalSets = this.selectAbjointRow(inData.entity, inData, context, option2).map(
(ele) => {
const { data } = inData as ED[keyof ED]['Selection'];
const key = Object.keys(data)[0];
return (ele as any)[key];
}
);
return !legalSets.includes(row[attr]) || obscurePassLocal(row);
return (node, nodeDict) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
);
const option2 = Object.assign({}, option, { nodeDict });
const legalSets = this.selectAbjointRow(inData.entity, inData, context, option2).map(
(ele) => {
const { data } = inData as ED[keyof ED]['Selection'];
const key = Object.keys(data)[0];
return (ele as any)[key];
}
);
return !legalSets.includes((row as any)[attr]) || obscurePassLocal(row);
};
}
else {
throw err;
}
}
}
break;
}
default:
assert(false, `目前不支持的算子${op}`);
}
}
return (node, nodeDict, exprResolveFns) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
for (const fn of fns) {
if (fn(row, nodeDict, exprResolveFns) === false) {
return false;
else {
const fn = this.translatePredicate(attr, predicate, (filter as Record<string, any>)[predicate], option);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn(row);
};
}
}
return true;
else {
// 对象的内部查询
assert(this.getSchema()[entity].attributes[attr]?.type === 'object');
const fn = this.translateObjectPredicate(filter);
return (node) => {
const row = this.constructRow(node, context, option);
if (!row) {
return false;
}
return fn((row as any)[attr]) || obscurePassLocal(row);
}
}
}
}
@ -672,7 +769,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
fns.push(
(node, nodeDict, exprResolveFns) => {
const row = this.constructRow(node, context, option);
if (obscurePass(row, 'entity', option) || obscurePass(row, 'entityId', option)) {
if (obscurePass((row as any).entity, option) || obscurePass((row as any).entityId, option)) {
return true;
}
if ((row as any).entity !== attr || !(row as any).entityId) {
@ -695,7 +792,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
fns.push(
(node, nodeDict, exprResolveFns) => {
const row = this.constructRow(node, context, option);
if (obscurePass(row, `${attr}Id`, option)) {
if (obscurePass((row as any)[`${attr}Id`], option)) {
return true;
}
if ((row as any)[`${attr}Id`]) {
@ -1142,7 +1239,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
const incompletedRowIds: string[] = [];
const { data: projection } = selection;
for (const row of rows) {
const result = {};
const result = {} as Partial<ED[T]['Schema']>;
for (const attr in projection) {
const rel = this.judgeRelation(entity, attr);
if (rel === 1) {
@ -1150,11 +1247,47 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
incompletedRowIds.push(row.id!);
break;
}
else {
else if (typeof projection[attr] === 'number') {
Object.assign(result, {
[attr]: row[attr],
});
}
else {
// object数据的深层次select
Object.assign(result, {
[attr]: {},
});
const assignIner = (dest: Record<string, any> | Array<any>, proj: Record<string, any> | Array<any>, source: Record<string, any> | Array<any>) => {
if (proj instanceof Array) {
assert(dest instanceof Array);
assert(source instanceof Array);
proj.forEach(
(attr, idx) => {
if (typeof attr === 'number') {
dest[idx] = source[idx];
}
else if (typeof attr === 'object') {
dest[idx] = {};
assignIner(dest[idx], attr, source[idx]);
}
}
);
}
else {
for (const attr in proj) {
if (typeof proj[attr] === 'number') {
(<Record<string, any>>dest)[attr] = (<Record<string, any>>source)[attr];
}
else if (typeof proj[attr] === 'object') {
(<Record<string, any>>dest)[attr] = proj[attr] instanceof Array ? [] : {};
assignIner((<Record<string, any>>dest)[attr], proj[attr], (<Record<string, any>>source)[attr]);
}
}
}
};
assignIner(result[attr]!, projection[attr], row[attr]!);
}
}
}
if (row.$$deleteAt$$) {
@ -1162,7 +1295,7 @@ export default class TreeStore<ED extends EntityDict & BaseEntityDict> extends C
[DeleteAtAttribute]: row.$$deleteAt$$,
})
}
rows2.push(result as Partial<ED[T]['Schema']>);
rows2.push(result);
}
if (incompletedRowIds.length > 0) {
// 如果有缺失属性的行则报OakRowUnexistedException错误

View File

@ -2,7 +2,6 @@ import TreeStore from '../src/store';
import { EntityDict } from 'oak-domain/lib/base-app-domain';
import { SyncContext, SyncRowStore } from "oak-domain/lib/store/SyncRowStore";
import { OperateOption, OperationResult, SelectOption, AggregationResult, TxnOption, StorageSchema } from "oak-domain/lib/types";
import { reinforceSelection } from 'oak-domain/lib/store/selection';
/**
* context和store用于测试
@ -27,7 +26,6 @@ export class FrontendStore extends TreeStore<EntityDict> implements SyncRowStore
return this.operateSync(entity, operation, context, option);
}
select<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: EntityDict[T]["Selection"], context: FrontendRuntimeContext, option: OP): Partial<EntityDict[T]["Schema"]>[] {
reinforceSelection(this.storageSchema, entity, selection);
return this.selectSync(entity, selection, context, option);
}
count<T extends keyof EntityDict, OP extends SelectOption>(entity: T, selection: Pick<EntityDict[T]["Selection"], "count" | "filter">, context: FrontendRuntimeContext, option: OP): number {

View File

@ -612,6 +612,7 @@ describe('基础测试', function () {
assert(modies.length === 2 && modies[0].modiEntity$modi!.length === 1);
store.operate('modiEntity', {
id: generateNewId(),
action: 'remove',
data: {},
filter: {
@ -818,6 +819,272 @@ describe('基础测试', function () {
}, context, {});
console.log(result);
context.commit();
})
});
it('[1.10]select json', () => {
const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store);
context.begin();
const id = generateNewId();
store.operate('oper', {
id: generateNewId(),
action: 'create',
data: {
id,
action: 'test',
data: {
name: 'xc',
books: [{
title: 'mathmatics',
price: 1,
}, {
title: 'english',
price: 2,
}]
},
targetEntity: 'bbb',
}
}, context, {});
const row = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
books: [undefined, {
title: 1,
price: 1,
}],
},
},
}, context, {});
context.commit();
console.log(JSON.stringify(row));
});
it('[1.11]json as filter', () => {
const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store);
context.begin();
const id = generateNewId();
store.operate('oper', {
id: generateNewId(),
action: 'create',
data: {
id,
action: 'test',
data: {
name: 'xc',
books: [{
title: 'mathmatics',
price: 1,
}, {
title: 'english',
price: 2,
}]
},
targetEntity: 'bbb',
}
}, context, {});
const row = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
books: [undefined, {
title: 1,
price: 1,
}],
},
},
filter: {
data: {
name: 'xc',
}
}
}, context, {});
const row2 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
books: [undefined, {
title: 1,
price: 1,
}],
},
},
filter: {
data: {
name: 'xc2',
}
}
}, context, {});
context.commit();
// console.log(JSON.stringify(row));
assert (row.length === 1);
assert (row2.length === 0);
});
it('[1.12]complicated json filter', () => {
const store = new FrontendStore(storageSchema);
const context = new FrontendRuntimeContext(store);
context.begin();
const id = generateNewId();
store.operate('oper', {
id: generateNewId(),
action: 'create',
data: {
id,
action: 'test',
data: {
name: 'xc',
price: [100, 400, 1000],
},
targetEntity: 'bbb',
}
}, context, {});
const row = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: [undefined, 400],
}
}
}, context, {});
const row2 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: [undefined, 200],
}
}
}, context, {});
const row3 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: [undefined, {
$gt: 300,
}],
}
}
}, context, {});
const row4 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: {
$contains: [200, 500],
},
}
}
}, context, {});
const row5 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: {
$contains: [100, 400],
},
}
}
}, context, {});
const row6 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
price: {
$contains: ['xc'],
},
}
}
}, context, {});
const row7 = store.select('oper', {
data: {
id: 1,
data: {
name: 1,
price: 1,
},
},
filter: {
id,
data: {
name: {
$includes: 'xc',
},
price: {
$overlaps: [200, 400, 800],
},
}
}
}, context, {});
context.commit();
assert(row.length === 1);
assert(row2.length === 0);
assert(row3.length === 1);
assert(row4.length === 0);
assert(row5.length === 1);
assert(row6.length === 0);
assert(row7.length === 1);
// console.log(JSON.stringify(row7));
});
});