482 lines
21 KiB
JavaScript
482 lines
21 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.createCreateCheckers = exports.createRemoveCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0;
|
||
const tslib_1 = require("tslib");
|
||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||
const filter_1 = require("../store/filter");
|
||
const Exception_1 = require("../types/Exception");
|
||
const types_1 = require("../types");
|
||
const lodash_1 = require("../utils/lodash");
|
||
const action_1 = require("../actions/action");
|
||
function getFullProjection(entity, schema) {
|
||
const { attributes } = schema[entity];
|
||
const projection = {
|
||
id: 1,
|
||
$$createAt$$: 1,
|
||
$$updateAt$$: 1,
|
||
$$deleteAt$$: 1,
|
||
};
|
||
Object.keys(attributes).forEach((k) => Object.assign(projection, {
|
||
[k]: 1,
|
||
}));
|
||
return projection;
|
||
}
|
||
/**
|
||
*
|
||
* @param checker 要翻译的checker
|
||
* @param silent 如果silent,则row和relation类型的checker只会把限制条件加到查询上,而不报错(除掉create动作)
|
||
* @returns
|
||
*/
|
||
function translateCheckerInAsyncContext(checker, schema) {
|
||
const { entity, type } = checker;
|
||
const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查
|
||
switch (type) {
|
||
case 'data': {
|
||
const { checker: checkerFn } = checker;
|
||
const fn = (async ({ operation }, context) => {
|
||
const { data } = operation;
|
||
await checkerFn(data, context);
|
||
return 0;
|
||
});
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
case 'row': {
|
||
const { filter, errMsg, err, inconsistentRows } = checker;
|
||
const fn = (async ({ operation }, context, option) => {
|
||
const { filter: operationFilter, data, action, bornAt } = operation;
|
||
const filter2 = typeof filter === 'function' ? await filter(operation, context, option) : filter;
|
||
if (!filter2) {
|
||
return 0;
|
||
}
|
||
if (['select', 'count', 'stat'].includes(action)) {
|
||
operation.filter = operationFilter ? (0, filter_1.combineFilters)(entity, context.getSchema(), [operationFilter, filter2]) : filter2;
|
||
return 0;
|
||
}
|
||
else {
|
||
const checkSingle = async (f) => {
|
||
if (await (0, filter_1.checkFilterContains)(entity, context, filter2, f, true)) {
|
||
return;
|
||
}
|
||
if (inconsistentRows) {
|
||
const { entity: entity2, selection: selection2 } = inconsistentRows;
|
||
const rows2 = await context.select(entity2, selection2(operationFilter), {
|
||
dontCollect: true,
|
||
blockTrigger: true,
|
||
});
|
||
const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg);
|
||
e.addData(entity2, rows2, context.getSchema());
|
||
throw e;
|
||
}
|
||
else {
|
||
// 可能会暴露隐私信息,暂时不能这样做。by Xc
|
||
/* const rows2 = await context.select(entity, {
|
||
data: getFullProjection(entity, context.getSchema()),
|
||
filter: Object.assign({}, operationFilter, {
|
||
$not: filter2,
|
||
})
|
||
}, {
|
||
dontCollect: true,
|
||
blockTrigger: true,
|
||
}); */
|
||
const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg);
|
||
// e.addData(entity, rows2);
|
||
throw e;
|
||
}
|
||
};
|
||
let operationFilter2 = operationFilter;
|
||
if (action === 'create') {
|
||
// 后台进行创建检查时,以传入的data为准
|
||
(0, assert_1.default)(data);
|
||
if (data instanceof Array) {
|
||
for (const d of data) {
|
||
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, d, !!bornAt));
|
||
}
|
||
}
|
||
else {
|
||
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt));
|
||
}
|
||
return;
|
||
}
|
||
(0, assert_1.default)(operationFilter2, 'row类型的checker遇到了操作的filter未定义');
|
||
await checkSingle(operationFilter2);
|
||
return 0;
|
||
}
|
||
});
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
case 'logical':
|
||
case 'logicalData': {
|
||
const { checker: checkerFn } = checker;
|
||
const fn = (async ({ operation }, context, option) => {
|
||
await checkerFn(operation, context, option);
|
||
return 0;
|
||
});
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
default: {
|
||
(0, assert_1.default)(false);
|
||
}
|
||
}
|
||
}
|
||
exports.translateCheckerInAsyncContext = translateCheckerInAsyncContext;
|
||
function translateCheckerInSyncContext(checker, schema) {
|
||
const { entity, type } = checker;
|
||
const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查
|
||
switch (type) {
|
||
case 'data': {
|
||
const { checker: checkerFn } = checker;
|
||
const fn = (operation, context) => checkerFn(operation.data, context);
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
case 'row': {
|
||
const { filter, errMsg, entity } = checker;
|
||
const fn = (operation, context, option) => {
|
||
const { filter: operationFilter, data, action, bornAt } = operation;
|
||
const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter;
|
||
if (!filter2) {
|
||
return;
|
||
}
|
||
let operationFilter2 = operationFilter;
|
||
if (action === 'create') {
|
||
if (data) {
|
||
// 前端的策略是,有data用data,无data用filter
|
||
// 目前前端应该不可能制造出来createMultiple
|
||
(0, assert_1.default)(!(data instanceof Array));
|
||
operationFilter2 = (0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt);
|
||
}
|
||
}
|
||
(0, assert_1.default)(!(filter2 instanceof Promise));
|
||
(0, assert_1.default)(operationFilter2, '定义了row类型的checker但却进行了无filter操作');
|
||
if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter2, true)) {
|
||
return;
|
||
}
|
||
const e = new Exception_1.OakRowInconsistencyException(errMsg || 'row checker condition illegal');
|
||
throw e;
|
||
};
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
case 'logical':
|
||
case 'logicalData': {
|
||
const { checker: checkerFn } = checker;
|
||
const fn = (operation, context, option) => {
|
||
checkerFn(operation, context, option);
|
||
};
|
||
return {
|
||
fn,
|
||
when,
|
||
};
|
||
}
|
||
default: {
|
||
(0, assert_1.default)(false);
|
||
}
|
||
}
|
||
}
|
||
exports.translateCheckerInSyncContext = translateCheckerInSyncContext;
|
||
/**
|
||
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
|
||
* @param schema
|
||
* @returns
|
||
* 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功
|
||
*/
|
||
function createRemoveCheckers(schema) {
|
||
const checkers = [];
|
||
// 先建立所有的一对多的关系
|
||
const OneToManyMatrix = {};
|
||
const OneToManyOnEntityMatrix = {};
|
||
const addToMto = (e, f, attr) => {
|
||
if (OneToManyMatrix[f]) {
|
||
OneToManyMatrix[f]?.push([e, attr]);
|
||
}
|
||
else {
|
||
OneToManyMatrix[f] = [[e, attr]];
|
||
}
|
||
};
|
||
const addToMtoEntity = (e, fs) => {
|
||
for (const f of fs) {
|
||
if (!OneToManyOnEntityMatrix[f]) {
|
||
OneToManyOnEntityMatrix[f] = [e];
|
||
}
|
||
else {
|
||
OneToManyOnEntityMatrix[f]?.push(e);
|
||
}
|
||
}
|
||
};
|
||
for (const entity in schema) {
|
||
if (['operEntity', 'modiEntity', 'userEntityGrant'].includes(entity)) {
|
||
continue; // 系统功能性数据,不用处理
|
||
}
|
||
const { attributes } = schema[entity];
|
||
for (const attr in attributes) {
|
||
if (attributes[attr].type === 'ref') {
|
||
addToMto(entity, attributes[attr].ref, attr);
|
||
}
|
||
else if (attr === 'entity') {
|
||
if (attributes[attr].ref) {
|
||
addToMtoEntity(entity, attributes[attr].ref);
|
||
}
|
||
else if (process.env.NODE_ENV === 'development') {
|
||
console.warn(`${entity}的entity反指指针找不到有效的对象`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 当删除一时,要确认多上面没有指向一的数据
|
||
const entities = (0, lodash_1.union)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix));
|
||
for (const entity of entities) {
|
||
checkers.push({
|
||
entity: entity,
|
||
action: 'remove',
|
||
type: 'logical',
|
||
priority: types_1.CHECKER_MAX_PRIORITY,
|
||
checker: (operation, context, option) => {
|
||
const promises = [];
|
||
if (OneToManyMatrix[entity]) {
|
||
for (const otm of OneToManyMatrix[entity]) {
|
||
const [e, attr] = otm;
|
||
const proj = {
|
||
id: 1,
|
||
[attr]: 1,
|
||
};
|
||
const filter = operation.filter && {
|
||
[attr.slice(0, attr.length - 2)]: operation.filter
|
||
};
|
||
const result = context.select(e, {
|
||
data: proj,
|
||
filter,
|
||
indexFrom: 0,
|
||
count: 1
|
||
}, { dontCollect: true, ignoreAttrMiss: true });
|
||
if (result instanceof Promise) {
|
||
promises.push(result.then(([row]) => {
|
||
if (row) {
|
||
const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`);
|
||
err.addData(e, [row], context.getSchema());
|
||
throw err;
|
||
}
|
||
}));
|
||
}
|
||
else {
|
||
const [row] = result;
|
||
if (row) {
|
||
const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`);
|
||
err.addData(e, [row], context.getSchema());
|
||
throw err;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (OneToManyOnEntityMatrix[entity]) {
|
||
for (const otm of OneToManyOnEntityMatrix[entity]) {
|
||
const proj = {
|
||
id: 1,
|
||
entity: 1,
|
||
entityId: 1,
|
||
};
|
||
const filter = operation.filter && {
|
||
[entity]: operation.filter
|
||
};
|
||
const result = context.select(otm, {
|
||
data: proj,
|
||
filter,
|
||
indexFrom: 0,
|
||
count: 1
|
||
}, { dontCollect: true, ignoreAttrMiss: true });
|
||
if (result instanceof Promise) {
|
||
promises.push(result.then(([row]) => {
|
||
if (row) {
|
||
const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`);
|
||
e.addData(otm, [row], context.getSchema());
|
||
throw e;
|
||
}
|
||
}));
|
||
}
|
||
else {
|
||
const [row] = result;
|
||
if (row) {
|
||
const record = {
|
||
a: 's',
|
||
d: {
|
||
[otm]: {
|
||
[row.id]: row,
|
||
}
|
||
}
|
||
};
|
||
const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`);
|
||
e.addData(otm, [row], context.getSchema());
|
||
throw e;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (promises.length > 0) {
|
||
return Promise.all(promises).then(() => undefined);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
return checkers;
|
||
}
|
||
exports.createRemoveCheckers = createRemoveCheckers;
|
||
function checkAttributeLegal(schema, entity, data) {
|
||
const { attributes } = schema[entity];
|
||
for (const attr in data) {
|
||
if (attributes[attr]) {
|
||
const { type, params, default: defaultValue, enumeration, notNull } = attributes[attr];
|
||
if (data[attr] === null || data[attr] === undefined) {
|
||
if (notNull && defaultValue === undefined) {
|
||
throw new Exception_1.OakAttrNotNullException(entity, [attr]);
|
||
}
|
||
if (defaultValue !== undefined) {
|
||
Object.assign(data, {
|
||
[attr]: defaultValue,
|
||
});
|
||
}
|
||
continue;
|
||
}
|
||
switch (type) {
|
||
case 'char':
|
||
case 'varchar': {
|
||
if (typeof data[attr] !== 'string') {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', {
|
||
attribtues: attr,
|
||
format: 'string',
|
||
});
|
||
}
|
||
const { length } = params;
|
||
if (length && data[attr].length > length) {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesTooLong', 'oak-domain', {
|
||
attributes: attr,
|
||
length,
|
||
});
|
||
}
|
||
break;
|
||
}
|
||
case 'int':
|
||
case 'smallint':
|
||
case 'tinyint':
|
||
case 'bigint':
|
||
case 'decimal':
|
||
case 'money': {
|
||
if (typeof data[attr] !== 'number') {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', {
|
||
attribtues: attr,
|
||
format: 'number',
|
||
});
|
||
}
|
||
const { min, max } = params || {};
|
||
if (typeof min === 'number' && data[attr] < min) {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesUnderMin', 'oak-domain', {
|
||
attributes: attr,
|
||
threshold: `${min}`,
|
||
});
|
||
}
|
||
if (typeof max === 'number' && data[attr] > max) {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesOverMax', 'oak-domain', {
|
||
attributes: attr,
|
||
threshold: `${max}`,
|
||
});
|
||
}
|
||
break;
|
||
}
|
||
case 'enum': {
|
||
(0, assert_1.default)(enumeration);
|
||
if (!enumeration.includes(data[attr])) {
|
||
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesNotInEnumeration');
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
// 这里似乎还有一种update中带cascade remove的case,等遇到再说(貌似cascadeUpdate没有处理完整这种情况) by Xc
|
||
if (typeof data[attr] === 'object' && data[attr]?.action === 'remove') {
|
||
console.warn('cascade remove可能是未处理的边界,请注意');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function createCreateCheckers(schema) {
|
||
const checkers = [];
|
||
for (const entity in schema) {
|
||
const { attributes, actions } = schema[entity];
|
||
const notNullAttrs = Object.keys(attributes).filter(ele => attributes[ele].notNull);
|
||
const updateActions = (0, lodash_1.difference)(actions, action_1.excludeUpdateActions);
|
||
checkers.push({
|
||
entity,
|
||
type: 'data',
|
||
action: 'create',
|
||
checker: (data) => {
|
||
const checkData = (data2) => {
|
||
const illegalNullAttrs = (0, lodash_1.difference)(notNullAttrs, Object.keys(data2).filter(ele => data2[ele] !== null));
|
||
if (illegalNullAttrs.length > 0) {
|
||
const emtpyAttrs = [];
|
||
// 要处理多对一的cascade create
|
||
for (const attr of illegalNullAttrs) {
|
||
if (attr === 'entityId') {
|
||
if (illegalNullAttrs.includes('entity')) {
|
||
continue;
|
||
}
|
||
}
|
||
else if (attr === 'entity' && attributes[attr].ref) {
|
||
let hasCascadeCreate = false;
|
||
for (const ref of attributes[attr].ref) {
|
||
if (data2[ref] && data2[ref].action === 'create') {
|
||
hasCascadeCreate = true;
|
||
break;
|
||
}
|
||
}
|
||
if (hasCascadeCreate) {
|
||
continue;
|
||
}
|
||
}
|
||
else if (attributes[attr].type === 'ref') {
|
||
const ref = attributes[attr].ref;
|
||
const attr2 = attr.slice(0, attr.length - 2);
|
||
if (data2[attr2] && data2[attr2].action === 'create') {
|
||
continue;
|
||
}
|
||
}
|
||
// 到这里说明确实是有not null的属性没有赋值
|
||
emtpyAttrs.push(attr);
|
||
}
|
||
if (emtpyAttrs.length > 0) {
|
||
throw new Exception_1.OakAttrNotNullException(entity, emtpyAttrs);
|
||
}
|
||
}
|
||
checkAttributeLegal(schema, entity, data2);
|
||
};
|
||
if (data instanceof Array) {
|
||
data.forEach(ele => checkData(ele));
|
||
}
|
||
else {
|
||
checkData(data);
|
||
}
|
||
}
|
||
}, {
|
||
entity,
|
||
type: 'data',
|
||
action: updateActions,
|
||
checker: (data) => {
|
||
checkAttributeLegal(schema, entity, data);
|
||
}
|
||
});
|
||
}
|
||
return checkers;
|
||
}
|
||
exports.createCreateCheckers = createCreateCheckers;
|