oak-domain/src/store/checker.ts

973 lines
42 KiB
TypeScript
Raw 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.

import assert from 'assert';
import { addFilterSegment, checkFilterContains, combineFilters } from "../store/filter";
import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception';
import {
AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn,
EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult, REMOVE_CASCADE_PRIORITY, RefOrExpression, SyncOrAsync
} from "../types";
import { EntityDict as BaseEntityDict } from '../base-app-domain';
import { AsyncContext } from "./AsyncRowStore";
import { getFullProjection } from './actionDef';
import { SyncContext } from './SyncRowStore';
import { firstLetterUpperCase } from '../utils/string';
import { union, uniq, difference } from '../utils/lodash';
import { judgeRelation } from './relation';
import { generateNewId } from '../utils/uuid';
export function translateCheckerInAsyncContext<
ED extends EntityDict & BaseEntityDict,
T extends keyof ED,
Cxt extends AsyncContext<ED>
>(checker: Checker<ED, T, Cxt>): {
fn: Trigger<ED, T, Cxt>['fn'];
when: 'before' | 'after';
} {
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;
}) as CreateTriggerInTxn<ED, keyof ED, Cxt>['fn'];
return {
fn,
when,
};
}
case 'row': {
const { filter, errMsg, inconsistentRows } = checker;
const fn = (async ({ operation }, context, option) => {
const { filter: operationFilter, action } = operation;
const filter2 = typeof filter === 'function' ? await (filter as Function)(operation, context, option) : filter;
if (['select', 'count', 'stat'].includes(action)) {
operation.filter = addFilterSegment(operationFilter || {}, filter2);
return 0;
}
else {
if (await checkFilterContains<ED, keyof ED, Cxt>(entity, context, filter2, operationFilter || {}, true)) {
return 0;
}
if (inconsistentRows) {
const { entity: entity2, selection: selection2 } = inconsistentRows;
const rows2 = await context.select(entity2, selection2(operationFilter), {
dontCollect: true,
blockTrigger: true,
});
const e = new OakRowInconsistencyException<ED>(undefined, errMsg);
e.addData(entity2, rows2);
throw e;
}
else {
const rows2 = await context.select(entity, {
data: getFullProjection(entity, context.getSchema()),
filter: Object.assign({}, operationFilter, {
$not: filter2,
})
}, {
dontCollect: true,
blockTrigger: true,
});
const e = new OakRowInconsistencyException<ED>(undefined, errMsg);
e.addData(entity, rows2);
throw e;
}
}
}) as UpdateTriggerInTxn<ED, T, Cxt>['fn'];
return {
fn,
when,
};
}
case 'relation': {
const { relationFilter, errMsg } = checker;
const fn = (async ({ operation }, context, option) => {
if (context.isRoot()) {
return 0;
}
// assert(operation.action !== 'create', `${entity as string}上的create动作定义了relation类型的checker,请使用expressionRelation替代`);
// 对后台而言将生成的relationFilter加到filter之上(select可以在此加以权限的过滤)
const result = typeof relationFilter === 'function' ? await relationFilter(operation, context, option) : relationFilter;
if (result) {
if (operation.action === 'create') {
console.warn(`${entity as string}对象的create类型的checker中存在无法转换为表达式形式的情况请尽量使用authDef格式定义这类checker`);
}
else {
operation.filter = combineFilters([operation.filter, result as ED[T]['Selection']['filter']]);
}
}
return 0;
}) as UpdateTriggerInTxn<ED, T, Cxt>['fn'];
return {
fn,
when,
};
}
case 'logical':
case 'logicalRelation': {
const { checker: checkerFn } = checker;
const fn = (async ({ operation }, context, option) => {
if (context.isRoot() && type === 'logicalRelation') {
return 0;
}
await checkerFn(operation, context, option);
return 0;
}) as UpdateTriggerInTxn<ED, T, Cxt>['fn'];
return {
fn,
when,
};
}
default: {
assert(false);
}
}
}
export function translateCheckerInSyncContext<
ED extends EntityDict & BaseEntityDict,
T extends keyof ED,
Cxt extends SyncContext<ED>
>(checker: Checker<ED, T, Cxt>): {
fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void;
when: 'before' | 'after';
} {
const { entity, type } = checker;
const when = 'before'; // 现在create的relation改成提前的expression检查了原先是先插入再后检查性能不行而且select也需要实现前检查
switch (type) {
case 'data': {
const { checker: checkerFn } = checker;
const fn = (operation: ED[T]['Operation'], context: Cxt) => checkerFn(operation.data, context);
return {
fn,
when,
}
}
case 'row': {
const { filter, errMsg } = checker;
const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => {
const { filter: operationFilter, action } = operation;
const filter2 = typeof filter === 'function' ? (filter as Function)(operation, context, option) : filter;
assert(operationFilter);
if (['select', 'count', 'stat'].includes(action)) {
operation.filter = addFilterSegment(operationFilter, filter2);
return 0;
}
else {
assert(!(filter2 instanceof Promise));
if (checkFilterContains<ED, T, Cxt>(entity, context, filter2, operationFilter, true)) {
return;
}
const e = new OakRowInconsistencyException(undefined, errMsg);
throw e;
}
};
return {
fn,
when,
};
}
case 'relation': {
const { relationFilter, errMsg } = checker;
const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => {
if (context.isRoot()) {
return;
}
const result = typeof relationFilter === 'function' ? relationFilter(operation, context, option) : relationFilter;
assert(!(result instanceof Promise));
if (result) {
const { filter, action } = operation;
if (action === 'create') {
console.warn(`${entity as string}对象的create类型的checker中存在无法转换为表达式形式的情况请尽量使用authDef格式定义这类checker`);
return;
}
assert(filter);
if (checkFilterContains<ED, T, Cxt>(entity, context, result as ED[T]['Selection']['filter'], filter, true)) {
return;
}
throw new OakUserUnpermittedException(errMsg);
}
};
return {
fn,
when,
};
}
case 'logical':
case 'logicalRelation': {
const { checker: checkerFn } = checker;
const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => {
if (context.isRoot() && type === 'logicalRelation') {
return;
}
checkerFn(operation, context, option);
};
return {
fn,
when,
};
}
default: {
assert(false);
}
}
}
type FilterMakeFn<ED extends EntityDict & BaseEntityDict> = (operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], userId: string) => ED[keyof ED]['Selection']['filter'] | {
$entity: keyof ED;
$filter?: ED[keyof ED]['Selection']['filter'];
$count?: number;
};
function translateCascadeRelationFilterMaker<ED extends EntityDict & BaseEntityDict>(
schema: StorageSchema<ED>,
lch: CascadeRelationItem,
entity2: keyof ED,
pathPrefix?: string): FilterMakeFn<ED> {
const { cascadePath, relations } = lch;
const paths = cascadePath ? cascadePath.split('.') : [];
if (pathPrefix) {
paths.unshift(pathPrefix);
}
const translateRelationFilter = <T extends keyof ED>(entity: T): (userId: string) => ED[T]['Selection']['filter'] => {
// 有两种情况此entity和user有Relation定义或是此entity已经指向user
if (entity === 'user') {
return (userId) => ({
id: userId,
});
}
else if (schema[entity].relation) {
if (relations) {
const diff = difference(relations, schema[entity].relation!);
if (diff.length > 0) {
throw new Error(`${entity2 as string}上某auth定义的relations中含有不可识别的关系定义${diff.join(',')} 请仔细检查`);
}
}
const relationEntityName = `user${firstLetterUpperCase(entity as string)}`;
return (userId) => {
const filter = relations ? {
userId,
relation: {
$in: relations,
},
} : {
userId,
};
return {
id: {
$in: {
entity: relationEntityName,
data: {
[`${entity as string}Id`]: 1,
},
filter,
},
},
}
};
}
else {
assert(false, `${entity2 as string}上某auth定义的cascadePath${cascadePath}不能定位到User对象或者和User关联的关系对象 请仔细检查`);
}
};
const translateFilterMakerIter = <T extends keyof ED>(entity: T, iter: number): (userId: string) => ED[T]['Selection']['filter'] => {
const relation = judgeRelation(schema, entity, paths[iter]);
assert(relation === 2 || typeof relation === 'string');
if (iter === paths.length - 1) {
if (relation === 2) {
const filterMaker2 = translateRelationFilter(paths[iter]);
return (userId) => {
const filter = filterMaker2(userId)!;
assert(filter.id);
return {
entity: paths[iter],
entityId: filter.id,
};
}
}
const filterMaker2 = translateRelationFilter(relation);
return (userId) => {
const filter = filterMaker2(userId)!;
assert(filter.id);
return {
[`${paths[iter]}Id`]: filter.id,
};
}
}
else {
const filterMaker = relation === 2 ? translateFilterMakerIter(paths[iter], iter + 1) : translateFilterMakerIter(relation, iter + 1);
return (userId) => ({
[paths[iter]]: filterMaker(userId),
});
}
};
const filterMaker = paths.length ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2);
if (!paths.length) {
return (oper, userId) => filterMaker(userId);
}
/**
* 针对第一层做一下特别优化比如对象A指向对象B多对一如果A的cascadePath是 'B'
* 当create A时会带有Bid。此时生成该B对象上的相关表达式查询返回可以避免必须将此判定在对象创建之后再做
* 另一使用场景是在查询A时如果带有Bid在对象跳一对多子对象场景下很常见可以提前判定这个查询对某些用户一定返回空集
*/
const [attr] = paths;
const relation = judgeRelation(schema, entity2, attr);
assert(relation === 2 || typeof relation === 'string');
const filterMaker2 = paths.length > 1
? (relation === 2 ? translateFilterMakerIter(attr, 1) : translateFilterMakerIter(relation, 1))
: (relation === 2 ? translateRelationFilter(attr) : translateRelationFilter(relation));
return (operation, userId) => {
const { action } = operation as ED[keyof ED]['Operation'];
if (action === 'create') {
const { data } = operation as ED[keyof ED]['Create'];
const getForeignKeyId = (d: ED[keyof ED]['CreateSingle']['data']) => {
if (relation === 2) {
if (d.entity === attr && typeof d.entityId === 'string') {
return d.entitId as string;
}
throw new OakUserUnpermittedException();
}
else {
assert(typeof relation === 'string');
if (typeof d[`${attr}Id`] === 'string') {
return d[`${attr}Id`] as string;
}
throw new OakUserUnpermittedException();
}
};
if (relation === 2) {
if (data instanceof Array) {
const fkIds = uniq(data.map(d => getForeignKeyId(d)));
return {
$entity: attr,
$filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }),
$count: fkIds.length,
};
}
const fkId = getForeignKeyId(data);
return {
$entity: attr,
$filter: addFilterSegment(filterMaker2(userId), { id: fkId }),
};
}
assert(typeof relation === 'string');
if (data instanceof Array) {
const fkIds = uniq(data.map(d => getForeignKeyId(d)));
return {
$entity: relation,
$filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }),
$count: fkIds.length,
};
}
const fkId = getForeignKeyId(data);
return {
$entity: relation,
$filter: addFilterSegment(filterMaker2(userId), { id: fkId }),
};
}
const { filter } = operation;
if (relation === 2 && filter?.entity === attr && filter?.entityId) {
if (typeof filter.entityId === 'string') {
return {
$entity: attr,
$filter: addFilterSegment(filterMaker2(userId), { id: filter.entityId }),
};
}
else if (filter.entityId.$in && filter.entityId.$in instanceof Array) {
const entityIds = uniq(filter.entityId.$in);
return {
$entity: relation,
$filter: addFilterSegment(filterMaker2(userId), { id: { $in: entityIds } }),
$count: entityIds.length,
};
}
}
else if (filter && filter[`${attr}Id`]) {
if (typeof filter[`${attr}Id`] === 'string') {
return {
$entity: attr,
$filter: addFilterSegment(filterMaker2(userId), { id: filter[`${attr}Id`] }),
};
}
else if (filter[`${attr}Id`].$in && filter[`${attr}Id`].$in instanceof Array) {
const entityIds = uniq(filter[`${attr}Id`].$in);
return {
$entity: relation,
$filter: addFilterSegment(filterMaker2(userId), { id: { $in: entityIds } }),
$count: entityIds.length,
};
}
}
return filterMaker(userId);
};
}
function translateActionAuthFilterMaker<ED extends EntityDict & BaseEntityDict>(
schema: StorageSchema<ED>,
relationItem: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[],
entity: keyof ED,
pathPrefix?: string,
): FilterMakeFn<ED> | (FilterMakeFn<ED> | FilterMakeFn<ED>[])[] {
if (relationItem instanceof Array) {
const maker = relationItem.map(
ele => {
if (ele instanceof Array) {
return ele.map(
ele2 => translateCascadeRelationFilterMaker(schema, ele2, entity, pathPrefix)
);
}
return translateCascadeRelationFilterMaker(schema, ele, entity, pathPrefix);
}
);
return maker;
}
const filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity, pathPrefix);
return filterMaker;
}
function makePotentialFilter<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>> (
operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'],
context: Cxt,
filterMaker: FilterMakeFn<ED> | (FilterMakeFn<ED> | FilterMakeFn<ED>[])[]): SyncOrAsync<ED[keyof ED]['Selection']['filter']> {
const userId = context.getCurrentUserId();
assert(userId!);
const filters = filterMaker instanceof Array ? filterMaker.map(
ele => {
if (ele instanceof Array) {
return ele.map(
ele2 => ele2(operation, userId),
);
}
return ele(operation, userId);
}
) : [filterMaker(operation, userId)];
/**
* 在下面的逻辑中如果某个maker返回的是$entity类型则检查是否有满足条件的项没有就要抛出异常有就返回undefined
* undefined项即意味着该条件通过
* 再加上and和or的布尔逻辑判断得到最终结果
* 还要考虑同步和异步……
* 代码比较复杂,因为原先没有$entity这种返回结果的设计
* by Xc 20130219
*/
const filtersOr: (SyncOrAsync<ED[keyof ED]['Selection']['filter'] | OakUserUnpermittedException<ED>>)[] = [];
let isAsyncOr = false;
for (const f of filters) {
if (f instanceof Array) {
let isAsyncAnd = true;
const filtersAnd: (SyncOrAsync<ED[keyof ED]['Selection']['filter'] | OakUserUnpermittedException<ED>>)[] = [];
for (const ff of f) {
if (ff?.$entity) {
const { $entity, $filter, $count = 1 } = ff!;
const count = context.count($entity, {
filter: $filter,
}, {});
if (count instanceof Promise) {
isAsyncAnd = true;
filtersAnd.push(
count.then(
(c2) => {
if (c2 >= $count) {
return undefined;
}
return new OakUserUnpermittedException();
}
)
);
}
else {
filtersAnd.push(count >= $count ? undefined : new OakUserUnpermittedException());
}
}
else if (ff) {
filtersAnd.push(ff as ED[keyof ED]['Selection']['filter']);
}
}
if (isAsyncAnd = true) {
isAsyncOr = true;
filtersOr.push(isAsyncAnd ? Promise.all(filtersAnd).then(
(fa) => {
const faR: ED[keyof ED]['Selection']['filter'][] = [];
for (const faItem of fa) {
if (faItem instanceof OakUserUnpermittedException) {
return faItem;
}
else if (faItem) {
faR.push(faItem);
}
}
if (faR.length > 0) {
return {
$and: faR,
};
}
}
) : ({
$and: filtersAnd,
} as ED[keyof ED]['Selection']['filter']));
}
}
else {
if (f?.$entity) {
const { $entity, $filter, $count = 1 } = f!;
const count = context.count($entity, {
filter: $filter,
}, {});
if (count instanceof Promise) {
isAsyncOr = true;
filtersOr.push(
count.then(
(c2) => c2 >= $count ? undefined : new OakUserUnpermittedException()
)
);
}
else {
filtersOr.push(count >= $count ? undefined : new OakUserUnpermittedException());
}
}
else if (f) {
filtersOr.push(f as ED[keyof ED]['Selection']['filter']);
}
}
}
// or的逻辑是有一个成功就直接通过
const returnOrFilters = (filters: (ED[keyof ED]['Selection']['filter'] | OakUserUnpermittedException<ED>)[]) => {
if (filters.length === 0 || filters.includes(undefined)) {
return undefined;
}
const foFilters = filters.filter(
ele => ele !== undefined && !(ele instanceof OakUserUnpermittedException)
);
if (foFilters.length > 0) {
return {
$or: foFilters,
};
}
throw new OakUserUnpermittedException();
};
if (isAsyncOr) {
return Promise.all(filtersOr)
.then(
(filters) => returnOrFilters(filters)
);
}
return returnOrFilters(filtersOr);
}
/**
* 根据权限定义创建出相应的checker
* @param schema
* @param authDict
* @returns
*/
export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(
schema: StorageSchema<ED>,
authDict: AuthDefDict<ED>) {
const checkers: Checker<ED, keyof ED, Cxt>[] = [];
for (const entity in schema) {
if (authDict[entity]) {
const { relationAuth, actionAuth } = authDict[entity]!;
if (relationAuth) {
const raFilterMakerDict = {} as Record<string, FilterMakeFn<ED> | (FilterMakeFn<ED> | FilterMakeFn<ED>[])[]>;
const userEntityName = `user${firstLetterUpperCase(entity)}`;
for (const r in relationAuth) {
Object.assign(raFilterMakerDict, {
[r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable<ED[keyof ED]['Relation']>]!, userEntityName, entity),
});
}
const entityIdAttr = `${entity}Id`;
checkers.push({
entity: userEntityName as keyof ED,
action: 'create',
type: 'relation',
relationFilter: (operation, context) => {
const { data } = operation as ED[keyof ED]['Create'];
assert(!(data instanceof Array));
const { relation, [entityIdAttr]: entityId } = data;
if (!raFilterMakerDict[relation]) {
return;
}
const filter = makePotentialFilter(operation, context, raFilterMakerDict[relation]);
return filter;
},
errMsg: '越权操作',
});
checkers.push({
entity: userEntityName as keyof ED,
action: 'remove' as ED[keyof ED]['Action'],
type: 'relation',
relationFilter: (operation: any, context: Cxt) => {
// 目前过不去
return undefined;
/* const userId = context.getCurrentUserId();
const { filter } = operation as ED[keyof ED]['Remove'];
const makeFilterFromRows = (rows: Partial<ED[keyof ED]['Schema']>[]): SyncOrAsync<ED[keyof ED]['Selection']['filter']> => {
const relations = uniq(rows.map(ele => ele.relation));
const entityIds = uniq(rows.map(ele => ele[entityIdAttr]));
assert(entityIds.length === 1, `在回收${userEntityName}上权限时,单次回收涉及到了不同的对象,此操作不被允许`);
// const entityId = entityIds[0]!;
// 所有的relation条件要同时满足and关系注意这里的filter翻译出来是在entity对象上不是在userEntity对象上
const filtersAnd = relations.map(
(relation) => raFilterMakerDict[relation!]
).filter(
ele => !!ele
).map(
ele => makePotentialFilter(operation, context, ele)
);
if (filtersAnd.find(ele => ele instanceof Promise)) {
return Promise.all(filtersAnd).then(
(fa) => {
if (fa.length > 0) {
return {
$and: fa,
} as ED[keyof ED]['Selection']['filter'];
}
}
);
}
if (filtersAnd.length > 0) {
return {
$and: filtersAnd
} as ED[keyof ED]['Selection']['filter'];
}
};
const toBeRemoved = context.select(userEntityName, {
data: {
id: 1,
relation: 1,
[entityIdAttr]: 1,
},
filter,
}, { dontCollect: true });
if (toBeRemoved instanceof Promise) {
return toBeRemoved.then(
(rows) => makeFilterFromRows(rows)
);
}
return makeFilterFromRows(toBeRemoved); */
},
errMsg: '越权操作',
});
// 转让权限现在用update动作只允许update userId给其它人
// todo 等实现的时候再写
}
if (actionAuth) {
for (const a in actionAuth) {
const filterMaker = translateActionAuthFilterMaker(schema, actionAuth[a as ED[keyof ED]['Action']]!, entity);
checkers.push({
entity,
action: a as ED[keyof ED]['Action'],
type: 'relation',
relationFilter: (operation, context) => {
// const { filter } = operation;
const filter = makePotentialFilter(operation, context, filterMaker);
return filter;
},
errMsg: '定义的actionAuth中检查出来越权操作',
});
}
}
}
}
return checkers;
}
/**
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
* @param schema
* @returns
* 如果有的对象允许删除需要使用trigger来处理其相关联的外键对象这些trigger写作before则会在checker之前执行仍然可以删除成功
*/
export function createRemoveCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>, authDict?: AuthDefDict<ED>) {
const checkers: Checker<ED, keyof ED, Cxt>[] = [];
// 先建立所有的一对多的关系
const OneToManyMatrix: Partial<Record<keyof ED, Array<[keyof ED, string]>>> = {};
const OneToManyOnEntityMatrix: Partial<Record<keyof ED, Array<keyof ED>>> = {};
const addToMto = (e: keyof ED, f: keyof ED, attr: string) => {
if (OneToManyMatrix[f]) {
OneToManyMatrix[f]?.push([e, attr]);
}
else {
OneToManyMatrix[f] = [[e, attr]];
}
};
const addToMtoEntity = (e: keyof ED, fs: Array<keyof ED>) => {
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 as keyof ED, attr);
}
else if (attr === 'entity') {
if (attributes[attr].ref) {
addToMtoEntity(entity, attributes[attr].ref as Array<keyof ED>);
}
else if (process.env.NODE_ENV === 'development') {
console.warn(`${entity}的entity反指指针找不到有效的对象`);
}
}
}
}
// 当删除一时,要确认多上面没有指向一的数据
const entities = union(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix));
for (const entity of entities) {
checkers.push({
entity: entity as keyof ED,
action: 'remove',
type: 'logical',
checker: (operation, context, option) => {
const promises: Promise<void>[] = [];
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 });
if (result instanceof Promise) {
promises.push(
result.then(
([row]) => {
if (row) {
const err = new OakRowInconsistencyException<ED>(undefined, `您无法删除存在有效数据「${e as string}」关联的行`);
err.addData(e, [row]);
throw err;
}
}
)
);
}
else {
const [row] = result;
if (row) {
const err = new OakRowInconsistencyException<ED>(undefined, `您无法删除存在有效数据「${e as string}」关联的行`);
err.addData(e, [row]);
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 });
if (result instanceof Promise) {
promises.push(
result.then(
([row]) => {
if (row) {
const e = new OakRowInconsistencyException<ED>(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`);
e.addData(otm, [row]);
throw e;
}
}
)
);
}
else {
const [row] = result;
if (row) {
const record = {
a: 's',
d: {
[otm]: {
[row.id!]: row,
}
}
} as SelectOpResult<ED>;
const e = new OakRowInconsistencyException<ED>(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`);
e.addData(otm, [row]);
throw e;
}
}
}
}
if (promises.length > 0) {
return Promise.all(promises).then(
() => undefined
);
}
}
})
}
// 注入声明的cascade删除时的外键处理动作
for (const entity in authDict) {
const { cascadeRemove } = authDict[entity]!;
if (cascadeRemove) {
const entitiesOnEntityAttr = [] as Array<keyof ED>;
let hasAllEntity = false;
for (const attr in cascadeRemove) {
if (attr === '@entity') {
hasAllEntity = true;
continue;
}
const rel = judgeRelation(schema, entity, attr);
if (rel === 2) {
entitiesOnEntityAttr.push(attr);
checkers.push({
entity: attr,
action: 'remove',
type: 'logical',
priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行否则无法完成
checker: (operation, context) => {
const { filter } = operation;
if (cascadeRemove[attr] === 'remove') {
return context.operate(entity, {
id: generateNewId(),
action: 'remove',
data: {},
filter: filter ? {
[attr]: filter,
} : undefined,
}, { dontCollect: true });
}
return context.operate(entity, {
id: generateNewId(),
action: 'update',
data: {
entity: null,
entityId: null,
},
filter: filter ? {
[attr]: filter,
} : undefined,
}, { dontCollect: true });
}
});
}
else {
assert(typeof rel === 'string');
checkers.push({
entity: rel,
action: 'remove',
type: 'logical',
priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行否则无法完成
checker: (operation, context) => {
const { filter } = operation;
if (cascadeRemove[attr] === 'remove') {
return context.operate(entity, {
id: generateNewId(),
action: 'remove',
data: {},
filter: filter ? {
[attr]: filter,
} : undefined,
}, { dontCollect: true });
}
return context.operate(entity, {
id: generateNewId(),
action: 'update',
data: {
[`${attr}Id`]: null,
},
filter: filter ? {
[attr]: filter,
} : undefined,
}, { dontCollect: true });
}
});
}
}
if (hasAllEntity) {
const { attributes } = schema[entity];
const { ref } = attributes.entity;
const restEntities = difference(ref, entitiesOnEntityAttr);
for (const e of restEntities) {
checkers.push({
entity: e,
action: 'remove',
type: 'logical',
priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行否则无法完成
checker: (operation, context) => {
const { filter } = operation;
if (cascadeRemove['@entity'] === 'remove') {
return context.operate(entity, {
id: generateNewId(),
action: 'remove',
data: {},
filter: filter ? {
[e]: filter,
} : undefined,
}, { dontCollect: true });
}
return context.operate(entity, {
id: generateNewId(),
action: 'update',
data: {
entity: null,
entityId: null,
},
filter: filter ? {
[e]: filter,
} : undefined,
}, { dontCollect: true });
}
});
}
}
}
}
return checkers;
}