oak-domain/lib/store/IntrinsicCheckers.js

403 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeIntrinsicCheckers = makeIntrinsicCheckers;
const tslib_1 = require("tslib");
const types_1 = require("../types");
const lodash_1 = require("../utils/lodash");
const filter_1 = require("./filter");
const modi_1 = require("./modi");
const checker_1 = require("./checker");
const action_1 = require("../actions/action");
const assert_1 = tslib_1.__importDefault(require("assert"));
function checkUniqueBetweenRows(rows, uniqAttrs) {
// 先检查这些行本身之间有无unique冲突
const dict = {};
for (const row of rows) {
let s = '';
for (const a of uniqAttrs) {
if (row[a] === null || row[a] === undefined) {
s += row.id;
}
else {
s += `-${row[a]}`;
}
}
if (dict[s]) {
throw new types_1.OakUniqueViolationException([{
id: row.id,
attrs: uniqAttrs,
}]);
}
else {
dict[s] = 1;
}
}
}
function checkCountLessThan(count, uniqAttrs, than = 0, id) {
if (count instanceof Promise) {
return count.then((count2) => {
if (count2 > than) {
throw new types_1.OakUniqueViolationException([{
id,
attrs: uniqAttrs,
}]);
}
});
}
if (count > than) {
throw new types_1.OakUniqueViolationException([{
id,
attrs: uniqAttrs,
}]);
}
}
function checkUnique(entity, row, context, uniqAttrs, extraFilter) {
const filter = (0, lodash_1.pick)(row, uniqAttrs);
for (const a in filter) {
if (filter[a] === null || filter[a] === undefined) {
delete filter[a];
}
}
if (Object.keys(filter).length < uniqAttrs.length) {
// 说明有null值不需要检查约束
return;
}
const filter2 = extraFilter ? (0, filter_1.combineFilters)(entity, context.getSchema(), [filter, extraFilter]) : filter;
const count = context.count(entity, { filter: filter2 }, { dontCollect: true });
return checkCountLessThan(count, uniqAttrs, 0, row.id);
}
function createUniqueCheckers(schema) {
const checkers = [];
for (const entity in schema) {
const { indexes } = schema[entity];
if (indexes) {
for (const index of indexes) {
if (index.config?.unique) {
const { attributes } = index;
const uniqAttrs = attributes.map(ele => ele.name);
checkers.push({
entity,
action: 'create',
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
checker: (operation, context) => {
const { data } = operation;
if (data instanceof Array) {
checkUniqueBetweenRows(data, uniqAttrs);
const checkResult = data.map(ele => checkUnique(entity, ele, context, uniqAttrs));
if (checkResult[0] instanceof Promise) {
return Promise.all(checkResult).then(() => undefined);
}
}
else if (data) {
return checkUnique(entity, data, context, uniqAttrs);
}
}
}, {
entity,
action: 'update', // 只检查update其它状态转换的action应该不会涉及unique约束的属性
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
checker: (operation, context) => {
const { data, filter: operationFilter } = operation;
if (data) {
const attrs = Object.keys(data);
const refAttrs = (0, lodash_1.intersection)(attrs, uniqAttrs);
if (refAttrs.length === 0) {
// 如果本次更新和unique约束的属性之间没有交集则直接返回
return;
}
for (const attr of refAttrs) {
// 如果有更新为null值不用再检查约束
if (data[attr] === null || data[attr] === undefined) {
return;
}
}
if (refAttrs.length === uniqAttrs.length) {
// 如果更新了全部属性,直接检查
const filter = (0, lodash_1.pick)(data, refAttrs);
// 在这些行以外的行不和更新后的键值冲突
const count = context.count(entity, {
filter: (0, filter_1.combineFilters)(entity, context.getSchema(), [filter, {
$not: operationFilter,
}]),
}, { dontCollect: true });
const checkCount = checkCountLessThan(count, uniqAttrs, 0, operationFilter?.id);
// 更新的行只能有一行
const rowCount = context.count(entity, {
filter: operationFilter,
}, { dontCollect: true });
const checkRowCount = checkCountLessThan(rowCount, uniqAttrs, 1, operationFilter?.id);
// 如果更新的行数为零似乎也可以但这应该不可能出现吧by Xc 20230131
if (checkRowCount instanceof Promise) {
return Promise.all([checkCount, checkRowCount]).then(() => undefined);
}
}
// 否则需要结合本行现有的属性来进行检查
const projection = { id: 1 };
for (const attr of uniqAttrs) {
Object.assign(projection, {
[attr]: 1,
});
}
const checkWithRows = (rows2) => {
const rows22 = rows2.map(ele => Object.assign(ele, data));
// 先检查这些行本身之间是否冲突
checkUniqueBetweenRows(rows22, uniqAttrs);
const checkResults = rows22.map((row) => checkUnique(entity, row, context, uniqAttrs, {
$not: operationFilter
}));
if (checkResults[0] instanceof Promise) {
return Promise.all(checkResults).then(() => undefined);
}
};
const currentRows = context.select(entity, {
data: projection,
filter: operationFilter,
}, { dontCollect: true });
if (currentRows instanceof Promise) {
return currentRows.then((row2) => checkWithRows(row2));
}
return checkWithRows(currentRows);
}
}
});
}
}
}
}
return checkers;
}
function createActionTransformerCheckers(actionDefDict) {
const checkers = [];
for (const entity in actionDefDict) {
for (const attr in actionDefDict[entity]) {
const def = actionDefDict[entity][attr];
const { stm, is } = def;
for (const action in stm) {
const actionStm = stm[action];
const conditionalFilter = typeof actionStm[0] === 'string' ? {
[attr]: actionStm[0],
} : {
[attr]: {
$in: actionStm[0],
},
};
checkers.push({
action: action,
type: 'row',
entity,
filter: conditionalFilter,
errMsg: '数据状态已经改变',
inconsistentRows: {
entity,
selection: (filter) => ({
data: {
id: 1,
[attr]: 1,
},
filter,
}),
},
});
// 这里用data类型的checker改数据了不太好先这样
checkers.push({
action: action,
type: 'logical',
entity,
checker: (operation) => {
const { data } = operation;
if (data) {
if (data instanceof Array) {
data.forEach((d) => Object.assign(d, {
[attr]: stm[action][1],
}));
}
else {
Object.assign(data, {
[attr]: stm[action][1],
});
}
}
}
});
}
if (is) {
checkers.push({
action: 'create',
type: 'logical',
entity,
priority: 10, // 优先级要高先于真正的data检查进行
checker: (operation) => {
const { data } = operation;
if (data instanceof Array) {
data.forEach(ele => {
if (!ele[attr]) {
Object.assign(ele, {
[attr]: is,
});
}
});
}
else if (data) {
if (!data[attr]) {
Object.assign(data, {
[attr]: is,
});
}
}
}
});
}
}
}
return checkers;
}
/**
* 检查一次更新是否有关联通过的可能
* 例如更新A的条件是B = 1此时行上的B并不等于1但由于更新数据是 { B: 1, A: .. }
* 此时如果B更新可以成功则A也可以成功
* @param entity
* @param data
* @param filters
* @param context
*/
function cascadelyCheckUpdateFilters(entity, schema, action, data, filter, matrix, restAttrs, context) {
const successAttrs = (0, lodash_1.difference)(Object.keys(data), restAttrs);
const successAttrFilter = (0, lodash_1.pick)(data, successAttrs);
const checkConditionalFilter = (cf) => {
// 此时看应用了success的attributes更新后能否消除掉f中的部分条件
const result = (0, filter_1.analyzeFilterRelation)(entity, schema, successAttrFilter, cf, true);
if (typeof result === 'boolean') {
return result;
}
const { sureAttributes } = result;
const f2 = (0, lodash_1.omit)(cf, sureAttributes);
return (0, filter_1.checkFilterContains)(entity, context, f2, filter, true);
};
/**
* 先找到能直接更新成功的属性
*/
const legalAttrResult = restAttrs.map((attr) => {
const { filter: f } = matrix[attr] || {};
if (!f) {
return true;
}
if (typeof f === 'function') {
const cf = f({ action, data, filter }, context);
if (cf instanceof Promise) {
return cf.then(
// @oak-ignore
(cf2) => cf2 ? checkConditionalFilter(cf2) : true);
}
return cf ? checkConditionalFilter(cf) : true;
}
return checkConditionalFilter(f);
});
const checkResult1 = (lar) => {
const legalAttrs = [];
const illegalAttrs = [];
(0, assert_1.default)(lar.length === restAttrs.length);
lar.forEach((ele, idx) => {
if (ele) {
legalAttrs.push(restAttrs[idx]);
}
else {
illegalAttrs.push(restAttrs[idx]);
}
});
if (illegalAttrs.length === 0) {
return;
}
if (legalAttrs.length === 0) {
throw new types_1.OakAttrCantUpdateException(entity, illegalAttrs, '更新的行当前属性不满足约束,请仔细检查数据');
}
return cascadelyCheckUpdateFilters(entity, schema, action, data, filter, matrix, illegalAttrs, context);
};
if (legalAttrResult.find(ele => ele instanceof Promise)) {
return Promise.all(legalAttrResult).then((lar) => checkResult1(lar));
}
return checkResult1(legalAttrResult);
}
function createAttrUpdateCheckers(schema, attrUpdateMatrix) {
const checkers = [];
for (const entity in attrUpdateMatrix) {
const matrix = attrUpdateMatrix[entity];
const updateAttrs = [types_1.UpdateAtAttribute, types_1.TriggerDataAttribute, types_1.TriggerUuidAttribute].concat(Object.keys(matrix));
const { actions } = schema[entity];
const updateActions = actions.filter((a) => !action_1.readOnlyActions.concat(['create', 'remove']).includes(a));
/**
* 如果一个entity定义了attrUpdateMatrix则必须严格遵循定义未出现在matrix中的属性不允许更新
*/
const updateChecker = {
entity,
action: updateActions,
type: 'logicalData',
checker({ data, filter, action }, context) {
const attrs = Object.keys(data);
const extras = (0, lodash_1.difference)(attrs, updateAttrs);
if (extras.length > 0) {
throw new types_1.OakAttrCantUpdateException(entity, extras);
}
const condition = attrs.map(ele => matrix[ele]);
const actions = condition.map(ele => ele?.actions).filter(ele => !!ele);
const a = actions.length > 0 && (0, lodash_1.intersection)(actions.flat());
if (a) {
(0, assert_1.default)(action);
if (!a.includes(action)) {
// 找到不满足的那个attr
const attrsIllegal = attrs.filter((attr) => matrix[attr]?.actions && !matrix[attr]?.actions?.includes(action));
throw new types_1.OakAttrCantUpdateException(entity, attrsIllegal);
}
}
const filters = condition.map(ele => {
if (typeof ele?.filter === 'function') {
return (ele.filter)({ action: action || 'select', data: data, filter }, context);
}
return ele?.filter;
});
const checkFiltersInner = (filters2) => {
const filters3 = filters2.filter(ele => !!ele);
const f = filters3.length > 0 && (0, filter_1.combineFilters)(entity, schema, filters3);
const checkResultInner = (result2) => {
if (!result2) {
if (attrs.length > 1) {
return cascadelyCheckUpdateFilters(entity, schema, action || 'select', data, filter, matrix, attrs, context);
}
throw new types_1.OakAttrCantUpdateException(entity, attrs);
}
};
if (f) {
const result = (0, filter_1.checkFilterContains)(entity, context, f, filter, true);
if (result instanceof Promise) {
return result.then((r) => checkResultInner(r));
}
return checkResultInner(result);
}
};
if (filters.find(ele => ele instanceof Promise)) {
return Promise.all(filters).then((ff) => checkFiltersInner(ff));
}
return checkFiltersInner(filters);
}
};
checkers.push(updateChecker);
}
return checkers;
}
function makeIntrinsicCheckers(schema, actionDefDict, attrUpdateMatrix) {
const checkers = [];
checkers.push(...(0, modi_1.createModiRelatedCheckers)(schema));
checkers.push(...(0, checker_1.createRemoveCheckers)(schema));
checkers.push(...(0, checker_1.createCreateCheckers)(schema));
// action状态转换矩阵相应的checker
checkers.push(...createActionTransformerCheckers(actionDefDict));
// unique索引相应的checker
checkers.push(...createUniqueCheckers(schema));
if (attrUpdateMatrix) {
// attrUpdateMatrix相应的checker
checkers.push(...createAttrUpdateCheckers(schema, attrUpdateMatrix));
}
return checkers;
}