interaqt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/ActivityCall.d.ts +68 -0
- package/dist/runtime/ActivityCall.d.ts.map +1 -0
- package/dist/runtime/ActivityCall.js +379 -0
- package/dist/runtime/ActivityCall.js.map +1 -0
- package/dist/runtime/Controller.d.ts +60 -0
- package/dist/runtime/Controller.d.ts.map +1 -0
- package/dist/runtime/Controller.js +225 -0
- package/dist/runtime/Controller.js.map +1 -0
- package/dist/runtime/InteractionCall.d.ts +102 -0
- package/dist/runtime/InteractionCall.d.ts.map +1 -0
- package/dist/runtime/InteractionCall.js +385 -0
- package/dist/runtime/InteractionCall.js.map +1 -0
- package/dist/runtime/MonoSystem.d.ts +26 -0
- package/dist/runtime/MonoSystem.d.ts.map +1 -0
- package/dist/runtime/MonoSystem.js +331 -0
- package/dist/runtime/MonoSystem.js.map +1 -0
- package/dist/runtime/Mysql.d.ts +35 -0
- package/dist/runtime/Mysql.d.ts.map +1 -0
- package/dist/runtime/Mysql.js +171 -0
- package/dist/runtime/Mysql.js.map +1 -0
- package/dist/runtime/PostgreSQL.d.ts +36 -0
- package/dist/runtime/PostgreSQL.d.ts.map +1 -0
- package/dist/runtime/PostgreSQL.js +172 -0
- package/dist/runtime/PostgreSQL.js.map +1 -0
- package/dist/runtime/SQLite.d.ts +34 -0
- package/dist/runtime/SQLite.d.ts.map +1 -0
- package/dist/runtime/SQLite.js +146 -0
- package/dist/runtime/SQLite.js.map +1 -0
- package/dist/runtime/Scheduler.d.ts +81 -0
- package/dist/runtime/Scheduler.d.ts.map +1 -0
- package/dist/runtime/Scheduler.js +457 -0
- package/dist/runtime/Scheduler.js.map +1 -0
- package/dist/runtime/System.d.ts +312 -0
- package/dist/runtime/System.d.ts.map +1 -0
- package/dist/runtime/System.js +90 -0
- package/dist/runtime/System.js.map +1 -0
- package/dist/runtime/asyncInteractionContext.d.ts +3 -0
- package/dist/runtime/asyncInteractionContext.d.ts.map +1 -0
- package/dist/runtime/asyncInteractionContext.js +3 -0
- package/dist/runtime/asyncInteractionContext.js.map +1 -0
- package/dist/runtime/boolExpression.d.ts +23 -0
- package/dist/runtime/boolExpression.d.ts.map +1 -0
- package/dist/runtime/boolExpression.js +43 -0
- package/dist/runtime/boolExpression.js.map +1 -0
- package/dist/runtime/computedDataHandles/Any.d.ts +52 -0
- package/dist/runtime/computedDataHandles/Any.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/Any.js +152 -0
- package/dist/runtime/computedDataHandles/Any.js.map +1 -0
- package/dist/runtime/computedDataHandles/Computation.d.ts +108 -0
- package/dist/runtime/computedDataHandles/Computation.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/Computation.js +49 -0
- package/dist/runtime/computedDataHandles/Computation.js.map +1 -0
- package/dist/runtime/computedDataHandles/ComputedDataHandle.d.ts +42 -0
- package/dist/runtime/computedDataHandles/ComputedDataHandle.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/ComputedDataHandle.js +4 -0
- package/dist/runtime/computedDataHandles/ComputedDataHandle.js.map +1 -0
- package/dist/runtime/computedDataHandles/Count.d.ts +45 -0
- package/dist/runtime/computedDataHandles/Count.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/Count.js +85 -0
- package/dist/runtime/computedDataHandles/Count.js.map +1 -0
- package/dist/runtime/computedDataHandles/Every.d.ts +56 -0
- package/dist/runtime/computedDataHandles/Every.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/Every.js +178 -0
- package/dist/runtime/computedDataHandles/Every.js.map +1 -0
- package/dist/runtime/computedDataHandles/StateMachine.d.ts +74 -0
- package/dist/runtime/computedDataHandles/StateMachine.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/StateMachine.js +180 -0
- package/dist/runtime/computedDataHandles/StateMachine.js.map +1 -0
- package/dist/runtime/computedDataHandles/Transform.d.ts +26 -0
- package/dist/runtime/computedDataHandles/Transform.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/Transform.js +106 -0
- package/dist/runtime/computedDataHandles/Transform.js.map +1 -0
- package/dist/runtime/computedDataHandles/TransitionFinder.d.ts +57 -0
- package/dist/runtime/computedDataHandles/TransitionFinder.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/TransitionFinder.js +40 -0
- package/dist/runtime/computedDataHandles/TransitionFinder.js.map +1 -0
- package/dist/runtime/computedDataHandles/WeightedSummation.d.ts +57 -0
- package/dist/runtime/computedDataHandles/WeightedSummation.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/WeightedSummation.js +146 -0
- package/dist/runtime/computedDataHandles/WeightedSummation.js.map +1 -0
- package/dist/runtime/computedDataHandles/index.d.ts +7 -0
- package/dist/runtime/computedDataHandles/index.d.ts.map +1 -0
- package/dist/runtime/computedDataHandles/index.js +7 -0
- package/dist/runtime/computedDataHandles/index.js.map +1 -0
- package/dist/runtime/index.d.ts +15 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +15 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/server.d.ts +35 -0
- package/dist/runtime/server.d.ts.map +1 -0
- package/dist/runtime/server.js +171 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/runtime/types/boolExpression.d.ts +22 -0
- package/dist/runtime/types/boolExpression.d.ts.map +1 -0
- package/dist/runtime/types/boolExpression.js +6 -0
- package/dist/runtime/types/boolExpression.js.map +1 -0
- package/dist/runtime/util.d.ts +10 -0
- package/dist/runtime/util.d.ts.map +1 -0
- package/dist/runtime/util.js +39 -0
- package/dist/runtime/util.js.map +1 -0
- package/dist/shared/BoolExp.d.ts +97 -0
- package/dist/shared/BoolExp.d.ts.map +1 -0
- package/dist/shared/BoolExp.js +252 -0
- package/dist/shared/BoolExp.js.map +1 -0
- package/dist/shared/activity/Activity.d.ts +465 -0
- package/dist/shared/activity/Activity.d.ts.map +1 -0
- package/dist/shared/activity/Activity.js +264 -0
- package/dist/shared/activity/Activity.js.map +1 -0
- package/dist/shared/activity/Condition.d.ts +75 -0
- package/dist/shared/activity/Condition.d.ts.map +1 -0
- package/dist/shared/activity/Condition.js +51 -0
- package/dist/shared/activity/Condition.js.map +1 -0
- package/dist/shared/activity/Data.d.ts +115 -0
- package/dist/shared/activity/Data.d.ts.map +1 -0
- package/dist/shared/activity/Data.js +89 -0
- package/dist/shared/activity/Data.js.map +1 -0
- package/dist/shared/attributive.d.ts +93 -0
- package/dist/shared/attributive.d.ts.map +1 -0
- package/dist/shared/attributive.js +59 -0
- package/dist/shared/attributive.js.map +1 -0
- package/dist/shared/computed.d.ts +607 -0
- package/dist/shared/computed.d.ts.map +1 -0
- package/dist/shared/computed.js +202 -0
- package/dist/shared/computed.js.map +1 -0
- package/dist/shared/createClass.d.ts +102 -0
- package/dist/shared/createClass.d.ts.map +1 -0
- package/dist/shared/createClass.js +276 -0
- package/dist/shared/createClass.js.map +1 -0
- package/dist/shared/dictionary/Dictionary.d.ts +40 -0
- package/dist/shared/dictionary/Dictionary.d.ts.map +1 -0
- package/dist/shared/dictionary/Dictionary.js +51 -0
- package/dist/shared/dictionary/Dictionary.js.map +1 -0
- package/dist/shared/entity/Entity.d.ts +149 -0
- package/dist/shared/entity/Entity.d.ts.map +1 -0
- package/dist/shared/entity/Entity.js +226 -0
- package/dist/shared/entity/Entity.js.map +1 -0
- package/dist/shared/index.d.ts +11 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +11 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/user/User.d.ts +21 -0
- package/dist/shared/user/User.d.ts.map +1 -0
- package/dist/shared/user/User.js +11 -0
- package/dist/shared/user/User.js.map +1 -0
- package/dist/shared/utils.d.ts +11 -0
- package/dist/shared/utils.d.ts.map +1 -0
- package/dist/shared/utils.js +19 -0
- package/dist/shared/utils.js.map +1 -0
- package/dist/storage/erstorage/AttributeInfo.d.ts +40 -0
- package/dist/storage/erstorage/AttributeInfo.d.ts.map +1 -0
- package/dist/storage/erstorage/AttributeInfo.js +147 -0
- package/dist/storage/erstorage/AttributeInfo.js.map +1 -0
- package/dist/storage/erstorage/AttributeQuery.d.ts +33 -0
- package/dist/storage/erstorage/AttributeQuery.d.ts.map +1 -0
- package/dist/storage/erstorage/AttributeQuery.js +190 -0
- package/dist/storage/erstorage/AttributeQuery.js.map +1 -0
- package/dist/storage/erstorage/EntityQueryHandle.d.ts +29 -0
- package/dist/storage/erstorage/EntityQueryHandle.d.ts.map +1 -0
- package/dist/storage/erstorage/EntityQueryHandle.js +78 -0
- package/dist/storage/erstorage/EntityQueryHandle.js.map +1 -0
- package/dist/storage/erstorage/EntityToTableMap.d.ts +85 -0
- package/dist/storage/erstorage/EntityToTableMap.d.ts.map +1 -0
- package/dist/storage/erstorage/EntityToTableMap.js +262 -0
- package/dist/storage/erstorage/EntityToTableMap.js.map +1 -0
- package/dist/storage/erstorage/LinkInfo.d.ts +35 -0
- package/dist/storage/erstorage/LinkInfo.d.ts.map +1 -0
- package/dist/storage/erstorage/LinkInfo.js +89 -0
- package/dist/storage/erstorage/LinkInfo.js.map +1 -0
- package/dist/storage/erstorage/MatchExp.d.ts +37 -0
- package/dist/storage/erstorage/MatchExp.d.ts.map +1 -0
- package/dist/storage/erstorage/MatchExp.js +211 -0
- package/dist/storage/erstorage/MatchExp.js.map +1 -0
- package/dist/storage/erstorage/Modifier.d.ts +23 -0
- package/dist/storage/erstorage/Modifier.d.ts.map +1 -0
- package/dist/storage/erstorage/Modifier.js +24 -0
- package/dist/storage/erstorage/Modifier.js.map +1 -0
- package/dist/storage/erstorage/NewRecordData.d.ts +42 -0
- package/dist/storage/erstorage/NewRecordData.d.ts.map +1 -0
- package/dist/storage/erstorage/NewRecordData.js +178 -0
- package/dist/storage/erstorage/NewRecordData.js.map +1 -0
- package/dist/storage/erstorage/RecordInfo.d.ts +26 -0
- package/dist/storage/erstorage/RecordInfo.d.ts.map +1 -0
- package/dist/storage/erstorage/RecordInfo.js +111 -0
- package/dist/storage/erstorage/RecordInfo.js.map +1 -0
- package/dist/storage/erstorage/RecordQuery.d.ts +73 -0
- package/dist/storage/erstorage/RecordQuery.d.ts.map +1 -0
- package/dist/storage/erstorage/RecordQuery.js +158 -0
- package/dist/storage/erstorage/RecordQuery.js.map +1 -0
- package/dist/storage/erstorage/RecordQueryAgent.d.ts +84 -0
- package/dist/storage/erstorage/RecordQueryAgent.d.ts.map +1 -0
- package/dist/storage/erstorage/RecordQueryAgent.js +1130 -0
- package/dist/storage/erstorage/RecordQueryAgent.js.map +1 -0
- package/dist/storage/erstorage/Setup.d.ts +49 -0
- package/dist/storage/erstorage/Setup.d.ts.map +1 -0
- package/dist/storage/erstorage/Setup.js +400 -0
- package/dist/storage/erstorage/Setup.js.map +1 -0
- package/dist/storage/erstorage/util.d.ts +6 -0
- package/dist/storage/erstorage/util.d.ts.map +1 -0
- package/dist/storage/erstorage/util.js +25 -0
- package/dist/storage/erstorage/util.js.map +1 -0
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/utils.d.ts +10 -0
- package/dist/storage/utils.d.ts.map +1 -0
- package/dist/storage/utils.js +48 -0
- package/dist/storage/utils.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
import { BoolExp } from "@shared";
|
|
2
|
+
import { assert, setByPath } from "../utils.js";
|
|
3
|
+
import { MatchExp } from "./MatchExp.js";
|
|
4
|
+
import { AttributeQuery } from "./AttributeQuery.js";
|
|
5
|
+
import { LINK_SYMBOL, RecordQuery } from "./RecordQuery.js";
|
|
6
|
+
import { NewRecordData } from "./NewRecordData.js";
|
|
7
|
+
export const ROOT_LABEL = ':root';
|
|
8
|
+
export class RecursiveContext {
|
|
9
|
+
constructor(label, parent, stack = []) {
|
|
10
|
+
this.label = label;
|
|
11
|
+
this.parent = parent;
|
|
12
|
+
this.stack = stack;
|
|
13
|
+
}
|
|
14
|
+
concat(value) {
|
|
15
|
+
return new RecursiveContext(this.label, this.parent, [...this.stack, value]);
|
|
16
|
+
}
|
|
17
|
+
getStack(key) {
|
|
18
|
+
return [...this.stack];
|
|
19
|
+
}
|
|
20
|
+
spawn(label) {
|
|
21
|
+
return new RecursiveContext(label, this);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class RecordQueryRef {
|
|
25
|
+
constructor(recordQuery) {
|
|
26
|
+
this.recordQuery = recordQuery;
|
|
27
|
+
this.recordQueryByName = new Map();
|
|
28
|
+
this.set(ROOT_LABEL, recordQuery);
|
|
29
|
+
this.recursiveSaveLabelledRecordQuery(recordQuery);
|
|
30
|
+
}
|
|
31
|
+
recursiveSaveLabelledRecordQuery(recordQuery) {
|
|
32
|
+
recordQuery.attributeQuery?.relatedRecords.forEach((relatedRecordQuery) => {
|
|
33
|
+
if (relatedRecordQuery.label) {
|
|
34
|
+
this.set(relatedRecordQuery.label, relatedRecordQuery);
|
|
35
|
+
this.recursiveSaveLabelledRecordQuery(relatedRecordQuery);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
set(key, value) {
|
|
40
|
+
this.recordQueryByName.set(key, value);
|
|
41
|
+
}
|
|
42
|
+
get(key) {
|
|
43
|
+
return this.recordQueryByName.get(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class RecordQueryAgent {
|
|
47
|
+
constructor(map, database) {
|
|
48
|
+
this.map = map;
|
|
49
|
+
this.database = database;
|
|
50
|
+
this.getPlaceholder = database.getPlaceholder || (() => (name) => `?`);
|
|
51
|
+
}
|
|
52
|
+
// 有 prefix 说明是比人的子查询
|
|
53
|
+
buildXToOneFindQuery(recordQuery, prefix = '', parentP) {
|
|
54
|
+
// 从所有条件里面构建出 join clause
|
|
55
|
+
const fieldQueryTree = recordQuery.attributeQuery.xToOneQueryTree;
|
|
56
|
+
const matchQueryTree = recordQuery.matchExpression.xToOneQueryTree;
|
|
57
|
+
const finalQueryTree = fieldQueryTree.merge(matchQueryTree);
|
|
58
|
+
const joinTables = this.getJoinTables(finalQueryTree, [recordQuery.recordName]);
|
|
59
|
+
const p = parentP || this.getPlaceholder();
|
|
60
|
+
const fieldMatchExp = recordQuery.matchExpression.buildFieldMatchExpression(p, this.database);
|
|
61
|
+
const [whereClause, params] = this.buildWhereClause(this.parseMatchExpressionValue(recordQuery.recordName, fieldMatchExp, recordQuery.contextRootEntity, p), prefix, p);
|
|
62
|
+
return [`
|
|
63
|
+
SELECT
|
|
64
|
+
${this.buildSelectClause(recordQuery.attributeQuery.getValueAndXToOneRecordFields(), prefix)}
|
|
65
|
+
FROM
|
|
66
|
+
${this.buildFromClause(recordQuery.recordName, prefix)}
|
|
67
|
+
${this.buildJoinClause(joinTables, prefix)}
|
|
68
|
+
|
|
69
|
+
WHERE
|
|
70
|
+
${whereClause}
|
|
71
|
+
|
|
72
|
+
${this.buildModifierClause(recordQuery.modifier, prefix)}
|
|
73
|
+
`,
|
|
74
|
+
params];
|
|
75
|
+
}
|
|
76
|
+
buildModifierClause(modifier, prefix) {
|
|
77
|
+
const { limit, offset, orderBy } = modifier;
|
|
78
|
+
const clauses = [];
|
|
79
|
+
if (orderBy.length) {
|
|
80
|
+
clauses.push(`ORDER BY ${orderBy.map(({ attribute, recordName, order }) => `"${this.withPrefix(prefix)}${recordName}.${attribute}" ${order}`).join(',')}`);
|
|
81
|
+
}
|
|
82
|
+
if (limit) {
|
|
83
|
+
clauses.push(`LIMIT ${limit}`);
|
|
84
|
+
}
|
|
85
|
+
if (offset) {
|
|
86
|
+
clauses.push(`OFFSET ${offset}`);
|
|
87
|
+
}
|
|
88
|
+
return clauses.join('\n');
|
|
89
|
+
}
|
|
90
|
+
structureRawReturns(rawReturns, JSONFields) {
|
|
91
|
+
return rawReturns.map(rawReturn => {
|
|
92
|
+
const obj = {};
|
|
93
|
+
Object.entries(rawReturn).forEach(([key, value]) => {
|
|
94
|
+
// CAUTION 注意这里去掉了最开始的 entityName
|
|
95
|
+
const attributePath = key.split('.').slice(1, Infinity);
|
|
96
|
+
if (attributePath.length === 1 && JSONFields.includes(attributePath[0]) && typeof value === 'string') {
|
|
97
|
+
value = JSON.parse(value);
|
|
98
|
+
}
|
|
99
|
+
if (value !== null) {
|
|
100
|
+
setByPath(obj, attributePath, value);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return obj;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// 查 entity 和 查 relation 都是一样的。具体在 entityQuery 里面区别。
|
|
107
|
+
// TODO 为了性能,也可以把信息丢到客户端,然客户端去结构化???
|
|
108
|
+
// CAUTION findRelatedRecords 中的递归调用会使得 includeRelationData 变为 true
|
|
109
|
+
async findRecords(entityQuery, queryName = '', recordQueryRef, context = new RecursiveContext(ROOT_LABEL)) {
|
|
110
|
+
// 一定在一开始的时候就创建 findContext ,并且是通过直接遍历 entityQuery 拿到原始的 label 的 recordQuery,因为后面
|
|
111
|
+
// 拿不到原始的了,都会带上 parent 的 id,会产生叠加 parent id match 的问题
|
|
112
|
+
if (!recordQueryRef) {
|
|
113
|
+
recordQueryRef = new RecordQueryRef(entityQuery);
|
|
114
|
+
}
|
|
115
|
+
if (entityQuery.goto) {
|
|
116
|
+
// 执行用户的手动退出判断。
|
|
117
|
+
if (entityQuery.exit && await entityQuery.exit(context)) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const gotoQuery = recordQueryRef.get(entityQuery.goto);
|
|
121
|
+
assert(gotoQuery, `goto ${entityQuery.goto} not found`);
|
|
122
|
+
// 需要把 gotoQuery 和当前 query 中的 matchExpression 合并,因为当前 query 的 matchExpression 有递归的条件,例如 parent.id === xxx。
|
|
123
|
+
const matchExpWithParent = entityQuery.matchExpression.and(gotoQuery.matchExpression);
|
|
124
|
+
// 统一在这里面处理 gotoQuery 和当前的合并,这样其他的抵用就只需要管好递归中和 parent 的 id 的关系就行了。
|
|
125
|
+
const newQuery = gotoQuery.derive({
|
|
126
|
+
matchExpression: matchExpWithParent
|
|
127
|
+
});
|
|
128
|
+
return this.findRecords(newQuery, queryName, recordQueryRef, context);
|
|
129
|
+
}
|
|
130
|
+
// 检查一下是否已经产生了循环。因为所有的子查询都会以这个函数为入口,所以可以再这里判断。
|
|
131
|
+
if (entityQuery.label && context.label === entityQuery.label && context.stack.length > 1) {
|
|
132
|
+
if (context.stack[0].id === context.stack.at(-1).id) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// findRecords 的一个 join 语句里面只能一次性搞定 x:1 的关联实体,以及关系上的 x:1 关联实体。
|
|
137
|
+
// 1. 这里只通过合表或者 join 处理了 x:1 的关联查询。x:n 的查询是通过二次查询获取的。
|
|
138
|
+
const [querySQL, params] = this.buildXToOneFindQuery(entityQuery, '');
|
|
139
|
+
const records = this.structureRawReturns(await this.database.query(querySQL, params, queryName), this.map.getRecordInfo(entityQuery.recordName).JSONFields);
|
|
140
|
+
// 如果当前的 query 有 label,那么下面任何遍历 record 的地方都要 Push stack。
|
|
141
|
+
const nextRecursiveContext = (entityQuery.label && entityQuery.label !== context.label) ? context.spawn(entityQuery.label) : context;
|
|
142
|
+
// x:1 关系上的递归 字段查询。因为是递归所以可能不会 join,不会在 buildXToOneFindQuery 里。所以单独查询。
|
|
143
|
+
for (let subEntityQuery of entityQuery.attributeQuery.xToOneRecords) {
|
|
144
|
+
// FIXME 这里的判断逻辑和 goto 耦合太重了?其他地方都是用 关系的类型 去判断的。
|
|
145
|
+
if (subEntityQuery.goto) {
|
|
146
|
+
const info = this.map.getInfo(subEntityQuery.parentRecord, subEntityQuery.attributeName);
|
|
147
|
+
const reverseAttributeName = info.getReverseInfo()?.attributeName;
|
|
148
|
+
for (let record of records) {
|
|
149
|
+
const matchWithParentId = subEntityQuery.matchExpression.and({
|
|
150
|
+
key: `${reverseAttributeName}.id`,
|
|
151
|
+
value: ['=', record.id]
|
|
152
|
+
});
|
|
153
|
+
const subGotoQueryWithParentMatch = subEntityQuery.derive({
|
|
154
|
+
matchExpression: matchWithParentId
|
|
155
|
+
});
|
|
156
|
+
const nextContext = entityQuery.label ? nextRecursiveContext.concat(record) : nextRecursiveContext;
|
|
157
|
+
record[subEntityQuery.attributeName] = await this.findRecords(subGotoQueryWithParentMatch, queryName, recordQueryRef, nextContext);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 2. x:1 上的 关系的 x:many关联实体 查询
|
|
162
|
+
for (let subEntityQuery of entityQuery.attributeQuery.xToOneRecords) {
|
|
163
|
+
// x:1 上的关系
|
|
164
|
+
const subLinkRecordQuery = subEntityQuery.attributeQuery.parentLinkRecordQuery;
|
|
165
|
+
if (subLinkRecordQuery) {
|
|
166
|
+
// 关系上的 xToMany 查询
|
|
167
|
+
for (let subEntityQueryOfSubLink of subLinkRecordQuery.attributeQuery.xToManyRecords) {
|
|
168
|
+
const linkRecordAttributeInfo = this.map.getInfo(subEntityQueryOfSubLink.parentRecord, subEntityQueryOfSubLink.attributeName);
|
|
169
|
+
const linkRecordReverseAttributeName = linkRecordAttributeInfo.getReverseInfo()?.attributeName;
|
|
170
|
+
for (let record of records) {
|
|
171
|
+
// 限制 link.id
|
|
172
|
+
const linkRecordId = record[subEntityQuery.attributeName][LINK_SYMBOL].id;
|
|
173
|
+
const queryOfThisRecord = subEntityQueryOfSubLink.derive({
|
|
174
|
+
matchExpression: subEntityQueryOfSubLink.matchExpression.and({
|
|
175
|
+
key: `${linkRecordReverseAttributeName}.id`,
|
|
176
|
+
value: ['=', linkRecordId]
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
const nextContext = entityQuery.label ? nextRecursiveContext.concat(record) : nextRecursiveContext;
|
|
180
|
+
setByPath(record, [subEntityQuery.attributeName, LINK_SYMBOL, subEntityQueryOfSubLink.attributeName], await this.findRecords(queryOfThisRecord, `finding relation data: ${entityQuery.recordName}.${subEntityQuery.attributeName}.&.${subEntityQueryOfSubLink.attributeName}`, recordQueryRef, nextContext));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// 3. x:n 关联实体的查询
|
|
186
|
+
for (let subEntityQuery of entityQuery.attributeQuery.xToManyRecords) {
|
|
187
|
+
// XToMany 的 relationData 是在上面 buildFindQuery 一起查完了的
|
|
188
|
+
if (!subEntityQuery.onlyRelationData) {
|
|
189
|
+
for (let record of records) {
|
|
190
|
+
const nextContext = entityQuery.label ? nextRecursiveContext.concat(record) : nextRecursiveContext;
|
|
191
|
+
record[subEntityQuery.attributeName] = await this.findXToManyRelatedRecords(entityQuery.recordName, subEntityQuery.attributeName, record.id, subEntityQuery, recordQueryRef, nextContext);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return records;
|
|
196
|
+
}
|
|
197
|
+
// CAUTION 任何两个具体的实体之间只能有一条关系,但是可以在关系上有多条数据。1:n 的数据
|
|
198
|
+
async findXToManyRelatedRecords(recordName, attributeName, recordId, relatedRecordQuery, recordQueryRef, context) {
|
|
199
|
+
const info = this.map.getInfo(recordName, attributeName);
|
|
200
|
+
const reverseAttributeName = info.getReverseInfo()?.attributeName;
|
|
201
|
+
// FIXME 对 n:N 关联实体的查询中,也可能会引用主实体的值,例如:age < '$host.age'。这个时候值已经是确定的了,应该作为 context 传进来,替换掉原本的 matchExpression
|
|
202
|
+
const newMatch = relatedRecordQuery.matchExpression.and({
|
|
203
|
+
key: `${reverseAttributeName}.id`,
|
|
204
|
+
// 这里不能用 EXIST,因为 EXIST 会把 join 变成子查询,而我们还需要关系上的数据,不能用子查询
|
|
205
|
+
value: ['=', recordId]
|
|
206
|
+
});
|
|
207
|
+
const newAttributeQuery = relatedRecordQuery.attributeQuery.parentLinkRecordQuery ?
|
|
208
|
+
relatedRecordQuery.attributeQuery.withParentLinkData() :
|
|
209
|
+
relatedRecordQuery.attributeQuery;
|
|
210
|
+
const newSubQuery = relatedRecordQuery.derive({
|
|
211
|
+
matchExpression: newMatch,
|
|
212
|
+
attributeQuery: newAttributeQuery,
|
|
213
|
+
});
|
|
214
|
+
// CAUTION 注意这里的第二个参数。因为任何两个具体的实体之间只能有一条关系。所以即使是 n:n 和关系表关联上时,也只有一条关系数据,所以这里可以带上 relation data。
|
|
215
|
+
// 1. 查询 x:n 的实体,以及和父亲的关联关系上的 x:1 的数据
|
|
216
|
+
const data = (await this.findRecords(newSubQuery, `finding related record: ${relatedRecordQuery.parentRecord}.${relatedRecordQuery.attributeName}`, recordQueryRef, context));
|
|
217
|
+
// 1.1 这里再反向处理一下关系数据。因为在上一步 withParentLinkData 查出来的时候是用的是反向的关系名字
|
|
218
|
+
const records = relatedRecordQuery.attributeQuery.parentLinkRecordQuery ? data.map(item => {
|
|
219
|
+
let itemWithParentLinkData;
|
|
220
|
+
if (!info.isLinkManyToManySymmetric()) {
|
|
221
|
+
itemWithParentLinkData = {
|
|
222
|
+
...item,
|
|
223
|
+
[LINK_SYMBOL]: item[reverseAttributeName][LINK_SYMBOL]
|
|
224
|
+
};
|
|
225
|
+
delete itemWithParentLinkData[reverseAttributeName];
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// TODO 是不是有更优雅的判断???
|
|
229
|
+
// CAUTION 对称 n:n 关系,和父亲也只有一个方向是有的。
|
|
230
|
+
itemWithParentLinkData = {
|
|
231
|
+
...item,
|
|
232
|
+
[LINK_SYMBOL]: item[`${reverseAttributeName}:source`]?.[LINK_SYMBOL]?.id ?
|
|
233
|
+
item[`${reverseAttributeName}:source`]?.[LINK_SYMBOL] :
|
|
234
|
+
item[`${reverseAttributeName}:target`]?.[LINK_SYMBOL]
|
|
235
|
+
};
|
|
236
|
+
delete itemWithParentLinkData[`${reverseAttributeName}:source`];
|
|
237
|
+
delete itemWithParentLinkData[`${reverseAttributeName}:target`];
|
|
238
|
+
}
|
|
239
|
+
return itemWithParentLinkData;
|
|
240
|
+
}) : data;
|
|
241
|
+
const nextRecursiveContext = (newSubQuery.label && newSubQuery.label !== context.label) ? context.spawn(newSubQuery.label) : context;
|
|
242
|
+
// 1.2 和父亲的关联关系上的 x:n 的数据
|
|
243
|
+
const parentLinkRecordQuery = relatedRecordQuery.attributeQuery.parentLinkRecordQuery;
|
|
244
|
+
if (parentLinkRecordQuery) {
|
|
245
|
+
// 关系上的 xToMany 查询
|
|
246
|
+
for (let subEntityQueryOfLink of parentLinkRecordQuery.attributeQuery.xToManyRecords) {
|
|
247
|
+
for (let record of records) {
|
|
248
|
+
// 应该已经有了和父亲 link 的 id。
|
|
249
|
+
// CAUTION 注意这里用了上面处理过路径
|
|
250
|
+
const linkId = record[LINK_SYMBOL].id;
|
|
251
|
+
const nextContext = newSubQuery.label ? nextRecursiveContext.concat(record) : nextRecursiveContext;
|
|
252
|
+
// 查找这个 link 的 x:n 关联实体
|
|
253
|
+
setByPath(record, [LINK_SYMBOL, subEntityQueryOfLink.attributeName], await this.findXToManyRelatedRecords(subEntityQueryOfLink.parentRecord, subEntityQueryOfLink.attributeName, linkId, subEntityQueryOfLink, recordQueryRef, nextContext));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return records;
|
|
258
|
+
}
|
|
259
|
+
// 根据 queryTree 来获得 join table 的信息。因为 queryTree 是树形,所以这里也是个递归结构。
|
|
260
|
+
getJoinTables(queryTree, context = [], parentInfos) {
|
|
261
|
+
// 应该是深度 遍历?
|
|
262
|
+
const result = [];
|
|
263
|
+
if (!parentInfos) {
|
|
264
|
+
// context 里面至少会有 entityName 这一个值。
|
|
265
|
+
const parentNamePath = [context[0]];
|
|
266
|
+
const [parentAlias, parentIdField, parentTable] = this.map.getTableAliasAndFieldName(parentNamePath, 'id');
|
|
267
|
+
parentInfos = [parentIdField, parentTable, parentAlias];
|
|
268
|
+
}
|
|
269
|
+
const [parentIdField, ...parentTableAndAlias] = parentInfos;
|
|
270
|
+
queryTree.forEachRecords((subQueryTree) => {
|
|
271
|
+
const entityAttributeName = subQueryTree.attributeName;
|
|
272
|
+
const attributeInfo = subQueryTree.info;
|
|
273
|
+
assert(attributeInfo.isRecord, `${context.concat(entityAttributeName).join('.')} is not a record`);
|
|
274
|
+
const currentNamePath = context.concat(entityAttributeName);
|
|
275
|
+
const { table: currentTable, alias: currentTableAlias, linkTable: relationTable, linkAlias: relationTableAlias } = this.map.getTableAndAliasStack(currentNamePath).at(-1);
|
|
276
|
+
// CAUTION 特别注意最后一个参数,这是真的要连接实体表的时候就能拿用 shrink 的 id 了。
|
|
277
|
+
const [, idField] = this.map.getTableAliasAndFieldName(currentNamePath, 'id', true);
|
|
278
|
+
// 这里的目的是把 attribute 对应的 record table 找到,并且正确 join 进来。
|
|
279
|
+
// join 本质上是把当前的路径和上一级路径连起来。
|
|
280
|
+
// 这里只处理没有和上一个节点 三表合一 的情况。三表合一的情况不需要 join。复用 alias 就行
|
|
281
|
+
if (!attributeInfo.isMergedWithParent()) {
|
|
282
|
+
if (attributeInfo.isLinkMergedWithParent()) {
|
|
283
|
+
// CAUTION 如果只要获取 id, 不需要 join, map.getTableAliasAndFieldName 会自动解析到合并后的 field 上。
|
|
284
|
+
if (subQueryTree.onlyIdField())
|
|
285
|
+
return;
|
|
286
|
+
result.push({
|
|
287
|
+
for: currentNamePath,
|
|
288
|
+
joinSource: parentTableAndAlias,
|
|
289
|
+
joinIdField: [attributeInfo.linkField, idField],
|
|
290
|
+
joinTarget: [currentTable, currentTableAlias]
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else if (attributeInfo.isLinkMergedWithAttribute()) {
|
|
294
|
+
const reverseAttributeInfo = attributeInfo.getReverseInfo();
|
|
295
|
+
// 说明记录在对方的 field 里面
|
|
296
|
+
assert(!!reverseAttributeInfo.linkField, `${reverseAttributeInfo.parentEntityName}.${reverseAttributeInfo.attributeName} has no field`);
|
|
297
|
+
result.push({
|
|
298
|
+
for: currentNamePath,
|
|
299
|
+
joinSource: parentTableAndAlias,
|
|
300
|
+
// 这里要找当前实体中用什么 attributeName 指向上一个实体
|
|
301
|
+
joinIdField: [parentIdField, reverseAttributeInfo.linkField],
|
|
302
|
+
joinTarget: [currentTable, currentTableAlias]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
// 说明记录在了 relation record 的 source/target 中
|
|
307
|
+
const linkInfo = attributeInfo.getLinkInfo();
|
|
308
|
+
const isCurrentRelationSource = attributeInfo.isLinkManyToManySymmetric() ?
|
|
309
|
+
(attributeInfo.symmetricDirection === 'source') :
|
|
310
|
+
linkInfo.isRelationSource(attributeInfo.parentEntityName, attributeInfo.attributeName);
|
|
311
|
+
// 关系表独立
|
|
312
|
+
result.push({
|
|
313
|
+
for: currentNamePath,
|
|
314
|
+
joinSource: parentTableAndAlias,
|
|
315
|
+
// CAUTION sourceField 是用在合并了情况里面的,指的是 target 在 source 里面的名字!所以这里不能用
|
|
316
|
+
joinIdField: [parentIdField, isCurrentRelationSource ? linkInfo.record.attributes.source.field : linkInfo.record.attributes.target.field],
|
|
317
|
+
joinTarget: [relationTable, relationTableAlias]
|
|
318
|
+
});
|
|
319
|
+
// CAUTION 只有当还要继续获取除 id 的部分时,才要 join 实体表。
|
|
320
|
+
if (!subQueryTree.onlyIdField()) {
|
|
321
|
+
result.push({
|
|
322
|
+
for: currentNamePath,
|
|
323
|
+
joinSource: [relationTable, relationTableAlias],
|
|
324
|
+
joinIdField: [isCurrentRelationSource ? linkInfo.record.attributes.target.field : linkInfo.record.attributes.source.field, idField],
|
|
325
|
+
joinTarget: [currentTable, currentTableAlias]
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
result.push(...this.getJoinTables(subQueryTree, currentNamePath, [idField, currentTable, currentTableAlias]));
|
|
331
|
+
if (subQueryTree.parentLinkQueryTree) {
|
|
332
|
+
}
|
|
333
|
+
// 处理 link 上的 query。如果只要 id, 那么在上面实体链接的时候就已经有了
|
|
334
|
+
if (subQueryTree.parentLinkQueryTree && !subQueryTree.parentLinkQueryTree.onlyIdField()) {
|
|
335
|
+
// 连接 link 和它的子节点
|
|
336
|
+
const linkNamePath = currentNamePath.concat(LINK_SYMBOL);
|
|
337
|
+
const [, linkIdField] = this.map.getTableAliasAndFieldName(linkNamePath, 'id', true);
|
|
338
|
+
const linkParentInfo = [
|
|
339
|
+
linkIdField, // link 的 idField
|
|
340
|
+
relationTable, // link 的 tableName
|
|
341
|
+
relationTableAlias,
|
|
342
|
+
];
|
|
343
|
+
result.push(...this.getJoinTables(subQueryTree.parentLinkQueryTree, linkNamePath, linkParentInfo));
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
withPrefix(prefix = '') {
|
|
349
|
+
return prefix ? `${prefix}___` : '';
|
|
350
|
+
}
|
|
351
|
+
buildSelectClause(queryFields, prefix = '') {
|
|
352
|
+
if (!queryFields.length)
|
|
353
|
+
return '1';
|
|
354
|
+
// CAUTION 所有 entity 都要 select id
|
|
355
|
+
return queryFields.map(({ tableAliasAndField, attribute, nameContext }) => (`"${this.withPrefix(prefix)}${tableAliasAndField[0]}"."${tableAliasAndField[1]}" AS "${this.withPrefix(prefix)}${nameContext.join(".")}.${attribute}"`)).join(',\n');
|
|
356
|
+
}
|
|
357
|
+
buildFromClause(entityName, prefix = '') {
|
|
358
|
+
return `"${this.map.getRecordTable(entityName)}" AS "${this.withPrefix(prefix)}${entityName}"`;
|
|
359
|
+
}
|
|
360
|
+
buildJoinClause(joinTables, prefix = '') {
|
|
361
|
+
return joinTables.map(({ joinSource, joinIdField, joinTarget }) => {
|
|
362
|
+
return `LEFT JOIN "${joinTarget[0]}" AS
|
|
363
|
+
"${this.withPrefix(prefix)}${joinTarget[1]}" ON
|
|
364
|
+
"${this.withPrefix(prefix)}${joinSource[1]}"."${joinIdField[0]}" = "${this.withPrefix(prefix)}${joinTarget[1]}"."${joinIdField[1]}"
|
|
365
|
+
`;
|
|
366
|
+
}).join('\n');
|
|
367
|
+
}
|
|
368
|
+
buildWhereClause(fieldMatchExp, prefix = '', p) {
|
|
369
|
+
let sql = ``;
|
|
370
|
+
const values = [];
|
|
371
|
+
if (!fieldMatchExp)
|
|
372
|
+
return [`1=${p()}`, [1]];
|
|
373
|
+
if (fieldMatchExp.isAtom()) {
|
|
374
|
+
if (fieldMatchExp.data.isInnerQuery) {
|
|
375
|
+
sql = fieldMatchExp.data.fieldValue;
|
|
376
|
+
values.push(...fieldMatchExp.data.fieldParams);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
sql = `"${this.withPrefix(prefix)}${fieldMatchExp.data.fieldName[0]}"."${fieldMatchExp.data.fieldName[1]}" ${fieldMatchExp.data.fieldValue}`;
|
|
380
|
+
values.push(...fieldMatchExp.data.fieldParams);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
if (fieldMatchExp.isAnd()) {
|
|
385
|
+
const [leftSql, leftValues] = this.buildWhereClause(fieldMatchExp.left, prefix, p);
|
|
386
|
+
const [rightSql, rightValues] = this.buildWhereClause(fieldMatchExp.right, prefix, p);
|
|
387
|
+
sql = `(${leftSql} AND ${rightSql})`;
|
|
388
|
+
values.push(...leftValues, ...rightValues);
|
|
389
|
+
}
|
|
390
|
+
else if (fieldMatchExp.isOr()) {
|
|
391
|
+
const [leftSql, leftValues] = this.buildWhereClause(fieldMatchExp.left, prefix, p);
|
|
392
|
+
const [rightSql, rightValues] = this.buildWhereClause(fieldMatchExp.right, prefix, p);
|
|
393
|
+
sql = `(${leftSql} OR ${rightSql})`;
|
|
394
|
+
values.push(...leftValues, ...rightValues);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
const [leftSql, leftValues] = this.buildWhereClause(fieldMatchExp.left, prefix, p);
|
|
398
|
+
sql = `NOT (${leftSql})`;
|
|
399
|
+
values.push(...leftValues);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return [sql, values];
|
|
403
|
+
}
|
|
404
|
+
// 把 match 中的 exist 创建成子 sql
|
|
405
|
+
parseMatchExpressionValue(entityName, fieldMatchExp, contextRootEntity, p) {
|
|
406
|
+
if (!fieldMatchExp)
|
|
407
|
+
return null;
|
|
408
|
+
return fieldMatchExp.map((exp, context) => {
|
|
409
|
+
assert(Array.isArray(exp.data.value), `match value is not a array ${context.join('.')}`);
|
|
410
|
+
if (!exp.data.isFunctionMatch)
|
|
411
|
+
return { ...exp.data };
|
|
412
|
+
assert(exp.data.value[0].toLowerCase() === 'exist', `we only support Exist function match on entity for now. yours: ${exp.data.key} ${exp.data.value[0]} ${exp.data.value[1]}`);
|
|
413
|
+
const info = this.map.getInfoByPath(exp.data.namePath);
|
|
414
|
+
const { alias: currentAlias } = this.map.getTableAndAliasStack(exp.data.namePath).at(-1);
|
|
415
|
+
const reverseAttributeName = this.map.getReverseAttribute(info.parentEntityName, info.attributeName);
|
|
416
|
+
// 注意这里去掉了 namePath 里面根部的 entityName,因为后面计算 referenceValue 的时候会加上。
|
|
417
|
+
const parentAttributeNamePath = exp.data.namePath.slice(1, -1);
|
|
418
|
+
const existEntityQuery = RecordQuery.create(info.recordName, this.map, {
|
|
419
|
+
matchExpression: BoolExp.atom({
|
|
420
|
+
key: `${reverseAttributeName}.id`,
|
|
421
|
+
value: ['=', parentAttributeNamePath.concat('id').join('.')],
|
|
422
|
+
isReferenceValue: true
|
|
423
|
+
}).and(exp.data.value[1] instanceof BoolExp ? exp.data.value[1] : MatchExp.atom(exp.data.value[1]))
|
|
424
|
+
},
|
|
425
|
+
// 如果上层还有,就继承上层的,如果没有, context 就只这一层。这个变量是用来给 matchExpression 里面的 value 来引用上层的值的。
|
|
426
|
+
// 例如查询用户,要求他存在一个朋友的父母的年龄是小于这个用户。对朋友的父母的年龄匹配中,就需要引用最上层的 alias。
|
|
427
|
+
contextRootEntity || entityName);
|
|
428
|
+
const [innerQuerySQL, innerParams] = this.buildXToOneFindQuery(existEntityQuery, currentAlias, p);
|
|
429
|
+
return {
|
|
430
|
+
...exp.data,
|
|
431
|
+
isInnerQuery: true,
|
|
432
|
+
fieldValue: `
|
|
433
|
+
EXISTS (
|
|
434
|
+
${innerQuerySQL}
|
|
435
|
+
)
|
|
436
|
+
`,
|
|
437
|
+
fieldParams: innerParams
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async createRecordDependency(newRecordData, events) {
|
|
442
|
+
const newRecordDataWithDeps = {};
|
|
443
|
+
// 处理往自身合并的需要新建的关系和 record
|
|
444
|
+
for (let mergedLinkTargetRecord of newRecordData.mergedLinkTargetNewRecords.concat(newRecordData.mergedLinkTargetRecordIdRefs)) {
|
|
445
|
+
let newDepIdRef;
|
|
446
|
+
if (!mergedLinkTargetRecord.isRef()) {
|
|
447
|
+
newDepIdRef = await this.createRecord(mergedLinkTargetRecord, `create merged link dep record ${newRecordData.recordName}.${mergedLinkTargetRecord.info?.attributeName}`, events);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
newDepIdRef = mergedLinkTargetRecord.getRef();
|
|
451
|
+
}
|
|
452
|
+
newRecordDataWithDeps[mergedLinkTargetRecord.info.attributeName] = newDepIdRef;
|
|
453
|
+
if (mergedLinkTargetRecord.linkRecordData) {
|
|
454
|
+
// 为 link 也要把 dependency 准备好。
|
|
455
|
+
const newLinkRecordData = mergedLinkTargetRecord.linkRecordData.merge({
|
|
456
|
+
[mergedLinkTargetRecord.info.isRecordSource() ? 'target' : 'source']: newDepIdRef
|
|
457
|
+
});
|
|
458
|
+
// 所有 Link dep 也准备好了
|
|
459
|
+
const newLinkRecordDataWithDep = await this.createRecordDependency(newLinkRecordData);
|
|
460
|
+
newRecordDataWithDeps[mergedLinkTargetRecord.info.attributeName][LINK_SYMBOL] = newLinkRecordDataWithDep.getData();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// 处理和我三表合一的 link record 的 dependency
|
|
464
|
+
for (let combinedRecord of newRecordData.combinedNewRecords.concat(newRecordData.combinedRecordIdRefs)) {
|
|
465
|
+
if (combinedRecord.linkRecordData) {
|
|
466
|
+
const newLinkRecordDataWithDep = await this.createRecordDependency(combinedRecord.linkRecordData, events);
|
|
467
|
+
newRecordDataWithDeps[combinedRecord.info.attributeName] = {
|
|
468
|
+
// 注意这里原本的数据不能丢,因为下面的 merge 不是深度 merge。
|
|
469
|
+
...combinedRecord.getData(),
|
|
470
|
+
[LINK_SYMBOL]: newLinkRecordDataWithDep.getData()
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// 返回追备好 link 数据和准备好 record 数据的新 newRecordData
|
|
475
|
+
return newRecordData.merge(newRecordDataWithDeps);
|
|
476
|
+
}
|
|
477
|
+
async createRecord(newEntityData, queryName, events) {
|
|
478
|
+
const newEntityDataWithDep = await this.createRecordDependency(newEntityData, events);
|
|
479
|
+
const newRecordIdRef = await this.insertSameRowData(newEntityDataWithDep, queryName, events);
|
|
480
|
+
const relianceResult = await this.handleCreationReliance(newEntityDataWithDep.merge(newRecordIdRef), events);
|
|
481
|
+
// 更新 relianceResult 的信息到
|
|
482
|
+
return Object.assign(newRecordIdRef, relianceResult);
|
|
483
|
+
}
|
|
484
|
+
// CAUTION 因为这里分配了 id,并且所有的判断逻辑都在,所以事件也放在这里处理,而不是真实插入或者更新数据的时候。
|
|
485
|
+
async preprocessSameRowData(newEntityData, isUpdate = false, events, oldRecord) {
|
|
486
|
+
const newRawDataWithNewIds = newEntityData.getData();
|
|
487
|
+
// CAUTION 特别注意,我们是支持数据使用 外部 id,例如使用外部用户系统的时候,它的 id 就是外部分配的。
|
|
488
|
+
// 还有一种情况是 relocate record 的时候也用了这个函数,这个时候也是不要重新分配 id 的!
|
|
489
|
+
// 也正是因为如此,所以我们通过一个参数 isUpdate 显式声明到底是不是 update,不能用有没有 id 来判断!
|
|
490
|
+
if (!isUpdate && !newRawDataWithNewIds.id) {
|
|
491
|
+
// 为自己分配 id,一定要在最前面,因为后面记录link 事件的地方一定要有 target/source 的 id
|
|
492
|
+
newRawDataWithNewIds.id = await this.database.getAutoId(newEntityData.recordName);
|
|
493
|
+
}
|
|
494
|
+
else if (isUpdate && !newRawDataWithNewIds.id) {
|
|
495
|
+
// 因为用户传进来的 update 字段里面可能没有 id 字段,所以这里要加上。
|
|
496
|
+
// newRawDataWithNewIds 用在了后面的 event 里面,保证有 id 才正确。外部可能会从 event 里面读。
|
|
497
|
+
newRawDataWithNewIds.id = oldRecord.id;
|
|
498
|
+
}
|
|
499
|
+
if (!isUpdate) {
|
|
500
|
+
events?.push({
|
|
501
|
+
type: 'create',
|
|
502
|
+
recordName: newEntityData.recordName,
|
|
503
|
+
record: newRawDataWithNewIds
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// 可能只是更新关系,所以这里一定要有自身的 value 才算是 update 自己
|
|
508
|
+
if (newEntityData.valueAttributes.length) {
|
|
509
|
+
events?.push({
|
|
510
|
+
type: 'update',
|
|
511
|
+
recordName: newEntityData.recordName,
|
|
512
|
+
record: newEntityData.getData(),
|
|
513
|
+
oldRecord: oldRecord
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// 1. 先为三表合一的新数据分配 id
|
|
518
|
+
for (let record of newEntityData.combinedNewRecords) {
|
|
519
|
+
newRawDataWithNewIds[record.info.attributeName] = {
|
|
520
|
+
...newRawDataWithNewIds[record.info.attributeName],
|
|
521
|
+
id: await this.database.getAutoId(record.info.recordName),
|
|
522
|
+
};
|
|
523
|
+
events?.push({
|
|
524
|
+
type: 'create',
|
|
525
|
+
recordName: record.recordName,
|
|
526
|
+
record: newRawDataWithNewIds[record.info.attributeName]
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
// 2. 为我要新建 三表合一、或者我 mergedLink 的 的 关系 record 分配 id.
|
|
530
|
+
for (let record of newEntityData.mergedLinkTargetNewRecords.concat(newEntityData.mergedLinkTargetRecordIdRefs, newEntityData.combinedNewRecords)) {
|
|
531
|
+
if (newRawDataWithNewIds[record.info.attributeName].id !== oldRecord?.[record.info.attributeName]?.id) {
|
|
532
|
+
newRawDataWithNewIds[record.info.attributeName][LINK_SYMBOL] = {
|
|
533
|
+
...(newRawDataWithNewIds[record.info.attributeName][LINK_SYMBOL] || {}),
|
|
534
|
+
id: await this.database.getAutoId(record.info.linkName),
|
|
535
|
+
};
|
|
536
|
+
const linkRecord = { ...newRawDataWithNewIds[record.info.attributeName][LINK_SYMBOL] };
|
|
537
|
+
linkRecord[record.info.isRecordSource() ? 'target' : 'source'] = record.getData();
|
|
538
|
+
linkRecord[record.info.isRecordSource() ? 'source' : 'target'] = { ...newRawDataWithNewIds };
|
|
539
|
+
delete linkRecord.target[LINK_SYMBOL];
|
|
540
|
+
delete linkRecord.source[LINK_SYMBOL];
|
|
541
|
+
events?.push({
|
|
542
|
+
type: 'create',
|
|
543
|
+
recordName: record.info.linkName,
|
|
544
|
+
record: linkRecord
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const newEntityDataWithIds = newEntityData.merge(newRawDataWithNewIds);
|
|
549
|
+
// 2. 处理需要 flashOut 的数据
|
|
550
|
+
// TODO create 的情况下,有没可能不需要 flashout 已有的数据,直接更新到已有的 combined record 的行就行了。
|
|
551
|
+
const flashOutRecordRasData = await this.flashOutCombinedRecordsAndMergedLinks(newEntityData, events, `finding combined records for ${newEntityData.recordName} to flash out, for ${isUpdate ? 'updating' : 'creation'} with data ${JSON.stringify(newEntityDataWithIds.getData())}`);
|
|
552
|
+
return newEntityDataWithIds.merge(flashOutRecordRasData);
|
|
553
|
+
}
|
|
554
|
+
async flashOutCombinedRecordsAndMergedLinks(newEntityData, events, reason = '') {
|
|
555
|
+
const result = {};
|
|
556
|
+
// CAUTION 这里是从 newEntityData 里读,不是从 newEntityDataWithIds,那里面是刚分配id 的,还没数据。
|
|
557
|
+
let match;
|
|
558
|
+
// 这里的目的是抢夺 combined record 上的所有数据,那么一定穷尽 combined record 的同表数据才行。
|
|
559
|
+
const attributeQuery = AttributeQuery.getAttributeQueryDataForRecord(newEntityData.recordName, this.map, true, true, false, true);
|
|
560
|
+
for (let combinedRecordIdRef of newEntityData.combinedRecordIdRefs) {
|
|
561
|
+
const attributeIdMatchAtom = {
|
|
562
|
+
key: `${combinedRecordIdRef.info.attributeName}.id`,
|
|
563
|
+
value: ['=', combinedRecordIdRef.getRef().id]
|
|
564
|
+
};
|
|
565
|
+
if (!match) {
|
|
566
|
+
match = MatchExp.atom(attributeIdMatchAtom);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
match = match.or(attributeIdMatchAtom);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const recordQuery = RecordQuery.create(newEntityData.recordName, this.map, {
|
|
573
|
+
matchExpression: match,
|
|
574
|
+
attributeQuery: attributeQuery,
|
|
575
|
+
}, undefined, undefined, undefined, false, true);
|
|
576
|
+
const recordsWithCombined = await this.findRecords(recordQuery, reason, undefined);
|
|
577
|
+
// const hasNoConflict = recordsWithCombined.length === 1 && !recordsWithCombined[0].id
|
|
578
|
+
// 开始 merge 数据,并记录 unLink 事件
|
|
579
|
+
for (let recordWithCombined of recordsWithCombined) {
|
|
580
|
+
for (let combinedRecordIdRef of newEntityData.combinedRecordIdRefs) {
|
|
581
|
+
if (recordWithCombined[combinedRecordIdRef.info?.attributeName]) {
|
|
582
|
+
// TODO 如果没有冲突的话,可以不用删除原来的数据。外面直接更新这一行就行了
|
|
583
|
+
//1. 删掉 combined 原来的所有同行数据
|
|
584
|
+
await this.deleteRecordSameRowData(combinedRecordIdRef.recordName, [{ id: recordWithCombined[combinedRecordIdRef.info?.attributeName].id }]);
|
|
585
|
+
//2. 如果是抢夺,要记录一下事件。
|
|
586
|
+
if (recordWithCombined.id) {
|
|
587
|
+
events?.push({
|
|
588
|
+
type: 'delete',
|
|
589
|
+
recordName: combinedRecordIdRef.info.linkName,
|
|
590
|
+
record: recordWithCombined[combinedRecordIdRef.info?.attributeName][LINK_SYMBOL],
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
//3. merge 数据并建立定的关系。
|
|
594
|
+
assert(!result[combinedRecordIdRef.info?.attributeName], `should not have same combined record, conflict attribute: ${combinedRecordIdRef.info?.attributeName}`);
|
|
595
|
+
result[combinedRecordIdRef.info?.attributeName] = {
|
|
596
|
+
...recordWithCombined[combinedRecordIdRef.info?.attributeName]
|
|
597
|
+
};
|
|
598
|
+
// 相当于新建了关系。如果不是虚拟link 就要记录。
|
|
599
|
+
// TODO 要给出一个明确的 虚拟 link record 的差异
|
|
600
|
+
if (!combinedRecordIdRef.info.isLinkSourceRelation()) {
|
|
601
|
+
result[combinedRecordIdRef.info?.attributeName][LINK_SYMBOL] = {
|
|
602
|
+
id: await this.database.getAutoId(combinedRecordIdRef.info.linkName),
|
|
603
|
+
};
|
|
604
|
+
events?.push({
|
|
605
|
+
type: 'create',
|
|
606
|
+
recordName: combinedRecordIdRef.info.linkName,
|
|
607
|
+
record: result[combinedRecordIdRef.info?.attributeName][LINK_SYMBOL]
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
async relocateCombinedRecordDataForLink(linkName, matchExpressionData, moveSource = false, events) {
|
|
616
|
+
const attributeQuery = AttributeQuery.getAttributeQueryDataForRecord(linkName, this.map, true, true, false, true);
|
|
617
|
+
const moveAttribute = moveSource ? 'source' : 'target';
|
|
618
|
+
const records = await this.findRecords(RecordQuery.create(linkName, this.map, {
|
|
619
|
+
matchExpression: matchExpressionData,
|
|
620
|
+
attributeQuery: attributeQuery
|
|
621
|
+
}), `finding combined records for relocate ${linkName}.${moveAttribute}`, undefined);
|
|
622
|
+
const toMoveRecordInfo = this.map.getLinkInfoByName(linkName)[moveSource ? 'sourceRecordInfo' : 'targetRecordInfo'];
|
|
623
|
+
// 1. 把这些数据删除,在下面重新插入到新行
|
|
624
|
+
await this.deleteRecordSameRowData(toMoveRecordInfo.name, records.map(r => r[moveAttribute]));
|
|
625
|
+
// 2. 重新插入到新行
|
|
626
|
+
for (let record of records) {
|
|
627
|
+
const toMoveRecordData = new NewRecordData(this.map, toMoveRecordInfo.name, record[moveAttribute]);
|
|
628
|
+
await this.insertSameRowData(toMoveRecordData, undefined);
|
|
629
|
+
// 3. 增加 delete 关系的事件
|
|
630
|
+
events?.push({
|
|
631
|
+
type: 'delete',
|
|
632
|
+
recordName: linkName,
|
|
633
|
+
record: record
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async insertSameRowData(newEntityData, queryName, events) {
|
|
638
|
+
// 由于我们可以抢夺别人的关联实体,所以会产生一个 unlink 事件,所以 events 要传进去。
|
|
639
|
+
const newEntityDataWithIdsWithFlashOutRecords = await this.preprocessSameRowData(newEntityData, false, events);
|
|
640
|
+
// 3. 插入新行。
|
|
641
|
+
const sameRowNewFieldAndValue = newEntityDataWithIdsWithFlashOutRecords.getSameRowFieldAndValue();
|
|
642
|
+
const p = this.getPlaceholder();
|
|
643
|
+
const result = await this.database.insert(`
|
|
644
|
+
INSERT INTO "${this.map.getRecordTable(newEntityData.recordName)}"
|
|
645
|
+
(${sameRowNewFieldAndValue.map(f => `"${f.field}"`).join(',')})
|
|
646
|
+
VALUES
|
|
647
|
+
(${sameRowNewFieldAndValue.map(f => p()).join(',')})
|
|
648
|
+
`, sameRowNewFieldAndValue.map(f => this.prepareFieldValue(f.value)), queryName);
|
|
649
|
+
return Object.assign(result, newEntityDataWithIdsWithFlashOutRecords.getData());
|
|
650
|
+
}
|
|
651
|
+
prepareFieldValue(value) {
|
|
652
|
+
// return value === undefined ? 'null' : JSON.stringify(value)
|
|
653
|
+
return value;
|
|
654
|
+
}
|
|
655
|
+
async handleCreationReliance(newEntityData, events) {
|
|
656
|
+
const currentIdRef = newEntityData.getRef();
|
|
657
|
+
const newIdRefs = {};
|
|
658
|
+
// 1. 处理关系往 attribute 方向合并的新数据
|
|
659
|
+
for (let record of newEntityData.differentTableMergedLinkNewRecords) {
|
|
660
|
+
const reverseAttribute = record.info?.getReverseInfo()?.attributeName;
|
|
661
|
+
const newRecordDataWithMyId = record.merge({
|
|
662
|
+
[reverseAttribute]: currentIdRef
|
|
663
|
+
});
|
|
664
|
+
const newRecordIdRef = await this.createRecord(newRecordDataWithMyId, `create record ${newEntityData.recordName}.${record.info?.attributeName}`, events);
|
|
665
|
+
if (record.info.isXToMany) {
|
|
666
|
+
if (!newIdRefs[record.info.attributeName]) {
|
|
667
|
+
newIdRefs[record.info.attributeName] = [];
|
|
668
|
+
}
|
|
669
|
+
newIdRefs[record.info.attributeName].push({
|
|
670
|
+
...newRecordIdRef,
|
|
671
|
+
[LINK_SYMBOL]: newRecordIdRef[reverseAttribute][LINK_SYMBOL]
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
newIdRefs[record.info.attributeName] = {
|
|
676
|
+
...newRecordIdRef,
|
|
677
|
+
[LINK_SYMBOL]: newRecordIdRef[reverseAttribute][LINK_SYMBOL]
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
// 2. 处理关系往 attribute 方向合并的老数据
|
|
682
|
+
for (let record of newEntityData.differentTableMergedLinkRecordIdRefs) {
|
|
683
|
+
const reverseInfo = record.info.getReverseInfo();
|
|
684
|
+
const idMatch = MatchExp.atom({
|
|
685
|
+
key: 'id',
|
|
686
|
+
value: ['=', record.getRef().id]
|
|
687
|
+
});
|
|
688
|
+
const newData = {
|
|
689
|
+
[reverseInfo.attributeName]: currentIdRef,
|
|
690
|
+
[LINK_SYMBOL]: record.getData()[LINK_SYMBOL]
|
|
691
|
+
};
|
|
692
|
+
const [updatedRecord] = await this.updateRecord(reverseInfo.parentEntityName, idMatch, new NewRecordData(this.map, reverseInfo.parentEntityName, newData), events);
|
|
693
|
+
if (record.info.isXToMany) {
|
|
694
|
+
if (!newIdRefs[record.info.attributeName]) {
|
|
695
|
+
newIdRefs[record.info.attributeName] = [];
|
|
696
|
+
}
|
|
697
|
+
newIdRefs[record.info.attributeName].push({
|
|
698
|
+
...updatedRecord,
|
|
699
|
+
[LINK_SYMBOL]: updatedRecord[reverseInfo.attributeName][LINK_SYMBOL]
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
newIdRefs[record.info.attributeName] = {
|
|
704
|
+
...updatedRecord,
|
|
705
|
+
[LINK_SYMBOL]: updatedRecord[reverseInfo.attributeName][LINK_SYMBOL]
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// 3. 处理完全独立的新数据和关系
|
|
710
|
+
for (let record of newEntityData.isolatedNewRecords) {
|
|
711
|
+
const newRecordIdRef = await this.createRecord(record, `create isolated related record ${newEntityData.recordName}.${record.info?.attributeName}`, events);
|
|
712
|
+
const linkRawData = record.linkRecordData?.getData() || {};
|
|
713
|
+
Object.assign(linkRawData, {
|
|
714
|
+
source: record.info.isRecordSource() ? currentIdRef : newRecordIdRef,
|
|
715
|
+
target: record.info.isRecordSource() ? newRecordIdRef : currentIdRef
|
|
716
|
+
});
|
|
717
|
+
const newLinkData = new NewRecordData(this.map, record.info.linkName, linkRawData);
|
|
718
|
+
const newLinkRecord = await this.createRecord(newLinkData, `create isolated related link record ${newEntityData.recordName}.${record.info?.attributeName}`, events);
|
|
719
|
+
if (record.info.isXToMany) {
|
|
720
|
+
if (!newIdRefs[record.info.attributeName]) {
|
|
721
|
+
newIdRefs[record.info.attributeName] = [];
|
|
722
|
+
}
|
|
723
|
+
newIdRefs[record.info.attributeName].push({
|
|
724
|
+
...newRecordIdRef,
|
|
725
|
+
[LINK_SYMBOL]: newLinkRecord
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
newIdRefs[record.info.attributeName] = {
|
|
730
|
+
...newRecordIdRef,
|
|
731
|
+
[LINK_SYMBOL]: newLinkRecord
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// 4. 处理完全独立的老数据和的关系。
|
|
736
|
+
for (let key in newEntityData.isolatedRecordIdRefs) {
|
|
737
|
+
const record = newEntityData.isolatedRecordIdRefs[key];
|
|
738
|
+
// 针对 x:1 关系要先删除原来的关系
|
|
739
|
+
if (record.info.isXToOne) {
|
|
740
|
+
const match = MatchExp.atom({
|
|
741
|
+
key: record.info?.isRecordSource() ? 'target.id' : 'source.id',
|
|
742
|
+
value: ['=', record.getRef().id]
|
|
743
|
+
});
|
|
744
|
+
await this.unlink(record.info.linkName, match, false, 'unlink xToOne old link', events);
|
|
745
|
+
}
|
|
746
|
+
const linkRawData = record.linkRecordData?.getData() || {};
|
|
747
|
+
Object.assign(linkRawData, {
|
|
748
|
+
source: record.info.isRecordSource() ? currentIdRef : record.getRef(),
|
|
749
|
+
target: record.info.isRecordSource() ? record.getRef() : currentIdRef
|
|
750
|
+
});
|
|
751
|
+
const newLinkData = new NewRecordData(this.map, record.info.linkName, linkRawData);
|
|
752
|
+
const newLinkRecord = await this.createRecord(newLinkData, `create isolated related link record of old related ${newEntityData.recordName}.${record.info?.attributeName}`, events);
|
|
753
|
+
if (record.info.isXToMany) {
|
|
754
|
+
if (!newIdRefs[record.info.attributeName]) {
|
|
755
|
+
newIdRefs[record.info.attributeName] = [];
|
|
756
|
+
}
|
|
757
|
+
newIdRefs[record.info.attributeName][key] = {
|
|
758
|
+
...record.getData(),
|
|
759
|
+
[LINK_SYMBOL]: newLinkRecord
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
newIdRefs[record.info.attributeName] = {
|
|
764
|
+
...record.getData(),
|
|
765
|
+
[LINK_SYMBOL]: newLinkRecord
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return newIdRefs;
|
|
770
|
+
}
|
|
771
|
+
// CAUTION 除了 1:1 并且合表的关系,不能递归更新 relatedEntity,如果是传入了,说明是建立新的关系。
|
|
772
|
+
async updateRecordDataById(entityName, idRef, columnAndValue) {
|
|
773
|
+
if (!columnAndValue.length) {
|
|
774
|
+
return idRef;
|
|
775
|
+
}
|
|
776
|
+
const p = this.getPlaceholder();
|
|
777
|
+
const entityInfo = this.map.getRecordInfo(entityName);
|
|
778
|
+
await this.database.update(`
|
|
779
|
+
UPDATE "${entityInfo.table}"
|
|
780
|
+
SET ${columnAndValue.map(({ field }) => `"${field}" = ${p()}`).join(',')}
|
|
781
|
+
WHERE "${entityInfo.idField}" = (${p()})
|
|
782
|
+
`, [...columnAndValue.map(({ field, value }) => value), idRef.id], entityInfo.idField, `update record ${entityName} by id`);
|
|
783
|
+
// 注意这里,使用要返回匹配的类,虽然可能没有更新数据。这样才能保证外部的逻辑比较一致。
|
|
784
|
+
return idRef;
|
|
785
|
+
}
|
|
786
|
+
async updateSameRowData(entityName, matchedEntity, newEntityDataWithDep, events) {
|
|
787
|
+
// 跟自己合表实体的必须先断开关联,也就是移走。不然下面 updateRecordData 的时候就会把数据删除。
|
|
788
|
+
const sameRowEntityNullOrRefOrNewData = newEntityDataWithDep.combinedRecordIdRefs.concat(newEntityDataWithDep.combinedNewRecords, newEntityDataWithDep.combinedNullRecords, newEntityDataWithDep.mergedLinkTargetNullRecords, newEntityDataWithDep.mergedLinkTargetRecordIdRefs);
|
|
789
|
+
// 1. 删除旧的关系。出现null 或者新的管理数据,说明是建立新的关系,也要先删除关系。
|
|
790
|
+
for (let newRelatedEntityData of sameRowEntityNullOrRefOrNewData) {
|
|
791
|
+
const linkInfo = newRelatedEntityData.info.getLinkInfo();
|
|
792
|
+
const updatedEntityLinkAttr = linkInfo.isRelationSource(entityName, newRelatedEntityData.info.attributeName) ? 'source' : 'target';
|
|
793
|
+
if ((newRelatedEntityData.isRef() && matchedEntity[newRelatedEntityData.info?.attributeName]?.id === newRelatedEntityData.getData().id)) {
|
|
794
|
+
// 放过原来就是同样 related entity 的场景。可能是编程中为了方便没做检查,把原本的写了进来。
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
await this.unlink(linkInfo.name, MatchExp.atom({
|
|
798
|
+
key: `${updatedEntityLinkAttr}.id`,
|
|
799
|
+
value: ['=', matchedEntity.id],
|
|
800
|
+
}), !linkInfo.isRelationSource(entityName, newRelatedEntityData.info.attributeName), `unlink ${newRelatedEntityData.info?.parentEntityName} ${newRelatedEntityData.info?.attributeName} for update ${entityName}`, events);
|
|
801
|
+
}
|
|
802
|
+
// 2. 分配 id,处理需要 flash out 的数据等,事件也是这里面记录的。这里面会有抢夺关系,所以也可能会有删除事件。
|
|
803
|
+
const newEntityDataWithIdsWithFlashOutRecords = await this.preprocessSameRowData(newEntityDataWithDep, true, events, matchedEntity);
|
|
804
|
+
const allSameRowData = newEntityDataWithIdsWithFlashOutRecords.getSameRowFieldAndValue(matchedEntity);
|
|
805
|
+
const columnAndValue = allSameRowData.map(({ field, value }) => ({
|
|
806
|
+
field,
|
|
807
|
+
/// TODO value 要考虑引用自身或者 related entity 其他 field 的情况?例如 age+5
|
|
808
|
+
// value: JSON.stringify(value)
|
|
809
|
+
value: value
|
|
810
|
+
}));
|
|
811
|
+
// 3. 真实处理数据,这里面没有记录事件,事件是上面处理的。、
|
|
812
|
+
await this.updateRecordDataById(entityName, matchedEntity, columnAndValue);
|
|
813
|
+
return newEntityDataWithIdsWithFlashOutRecords;
|
|
814
|
+
}
|
|
815
|
+
async handleUpdateReliance(entityName, matchedEntity, newEntityData, events) {
|
|
816
|
+
// CAUTION update 里面的表达关联实体的语义统统认为是 replace。如果用户想要表达 xToMany 的情况下新增关系,应该自己拆成两步进行。既先更新数据,再用 addLink 去增加关系。
|
|
817
|
+
// 1. 断开自己和原来关联实体的关系。这里只要处理依赖我的,或者关系独立的,因为我依赖的在应该在 updateSameRowData 里面处理了。
|
|
818
|
+
const otherTableEntitiesData = newEntityData.differentTableMergedLinkRecordIdRefs.concat(newEntityData.differentTableMergedLinkNewRecords, newEntityData.differentTableMergedLinkNullRecords, newEntityData.isolatedRecordIdRefs, newEntityData.isolatedNewRecords, newEntityData.isolatedNullRecords);
|
|
819
|
+
// CAUTION 由于 xToMany 的数组情况会平铺处理,所以这里可能出现两次,所以这里记录一下排重
|
|
820
|
+
const removedLinkName = new Set();
|
|
821
|
+
for (let relatedEntityData of otherTableEntitiesData) {
|
|
822
|
+
const linkInfo = relatedEntityData.info.getLinkInfo();
|
|
823
|
+
if (removedLinkName.has(linkInfo.name)) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
removedLinkName.add(linkInfo.name);
|
|
827
|
+
const updatedEntityLinkAttr = linkInfo.isRelationSource(entityName, relatedEntityData.info.attributeName) ? 'source' : 'target';
|
|
828
|
+
await this.unlink(linkInfo.name, MatchExp.atom({
|
|
829
|
+
key: `${updatedEntityLinkAttr}.id`,
|
|
830
|
+
value: ['=', matchedEntity.id],
|
|
831
|
+
}), !linkInfo.isRelationSource(entityName, relatedEntityData.info.attributeName), 'unlink old reliance for update', events);
|
|
832
|
+
}
|
|
833
|
+
const result = { id: matchedEntity.id };
|
|
834
|
+
// 2. 建立新关系
|
|
835
|
+
// 处理和其他实体更新关系的情况。
|
|
836
|
+
for (let newRelatedEntityData of otherTableEntitiesData) {
|
|
837
|
+
// 跳过已显式设置为 null 的关系属性
|
|
838
|
+
if (newEntityData.rawData[newRelatedEntityData.info?.attributeName] === null) {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
// 这里只处理没有三表合并的场景。因为三表合并的数据在 sameTableFieldAndValues 已经有了
|
|
842
|
+
// 这里只需要处理 1)关系表独立 或者 2)关系表往另一个方向合了的情况。因为往本方向和的情况已经在前面 updateEntityData 里面处理了
|
|
843
|
+
let finalRelatedEntityRef;
|
|
844
|
+
if (newRelatedEntityData.isRef()) {
|
|
845
|
+
finalRelatedEntityRef = newRelatedEntityData.getRef();
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
finalRelatedEntityRef = await this.createRecord(newRelatedEntityData, `create new related record for update ${newEntityData.recordName}.${newRelatedEntityData.info?.attributeName}`, events);
|
|
849
|
+
}
|
|
850
|
+
// FIXME 这里没有在更新的时候一次性写入,而是又通过 addLinkFromRecord 建立的关系。需要优化
|
|
851
|
+
const linkRecord = await this.addLinkFromRecord(entityName, newRelatedEntityData.info?.attributeName, matchedEntity.id, finalRelatedEntityRef.id, undefined, events);
|
|
852
|
+
if (newRelatedEntityData.info.isXToMany) {
|
|
853
|
+
if (!result[newRelatedEntityData.info.attributeName]) {
|
|
854
|
+
result[newRelatedEntityData.info.attributeName] = [];
|
|
855
|
+
}
|
|
856
|
+
result[newRelatedEntityData.info.attributeName].push({
|
|
857
|
+
...finalRelatedEntityRef,
|
|
858
|
+
[LINK_SYMBOL]: linkRecord,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
result[newRelatedEntityData.info.attributeName] = {
|
|
863
|
+
...finalRelatedEntityRef,
|
|
864
|
+
[LINK_SYMBOL]: linkRecord,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
// 修改TODO注释以反映已实现的功能
|
|
871
|
+
async updateRecord(entityName, matchExpressionData, newEntityData, events) {
|
|
872
|
+
// 现在支持在 update 字段的同时,使用 null 来删除关系
|
|
873
|
+
const matchedEntities = await this.findRecords(RecordQuery.create(entityName, this.map, {
|
|
874
|
+
matchExpression: matchExpressionData,
|
|
875
|
+
attributeQuery: AttributeQuery.getAttributeQueryDataForRecord(entityName, this.map, true, true, true, true)
|
|
876
|
+
}), `find record for updating ${entityName}`, undefined);
|
|
877
|
+
const result = [];
|
|
878
|
+
for (let matchedEntity of matchedEntities) {
|
|
879
|
+
// 1. 创建我依赖的
|
|
880
|
+
const newEntityDataWithDep = await this.createRecordDependency(newEntityData, events);
|
|
881
|
+
// 2. 把同表的实体移出去,为新同表 Record 建立 id;可能有要删除的 reliance
|
|
882
|
+
const newEntityDataWithIdsWithFlashOutRecords = await this.updateSameRowData(entityName, matchedEntity, newEntityDataWithDep, events);
|
|
883
|
+
// 3. 更新依赖我的和关系表独立的
|
|
884
|
+
const relianceUpdatedResult = await this.handleUpdateReliance(entityName, matchedEntity, newEntityData, events);
|
|
885
|
+
result.push({ ...newEntityData.getData(), ...newEntityDataWithIdsWithFlashOutRecords.getData(), ...relianceUpdatedResult });
|
|
886
|
+
}
|
|
887
|
+
return result;
|
|
888
|
+
}
|
|
889
|
+
async deleteRecord(recordName, matchExp, events, inSameRowDataOp = false) {
|
|
890
|
+
const deleteQuery = RecordQuery.create(recordName, this.map, {
|
|
891
|
+
matchExpression: matchExp,
|
|
892
|
+
attributeQuery: AttributeQuery.getAttributeQueryDataForRecord(recordName, this.map, true, true, true)
|
|
893
|
+
});
|
|
894
|
+
const records = await this.findRecords(deleteQuery, `find record for deleting ${recordName}`, undefined);
|
|
895
|
+
// CAUTION 我们应该先删除关系,再删除关联实体。按照下面的顺序就能保证事件顺序的正确。
|
|
896
|
+
if (records.length) {
|
|
897
|
+
// 删除关系数据(独立表或者关系在另一边的关系数据)
|
|
898
|
+
await this.deleteNotReliantSeparateLinkRecords(recordName, records, events);
|
|
899
|
+
// 删除依赖我的实体(其他表中的)。注意, reliance 只可能是 1:x,不可能多个 n 个 record 被1个 reliace 依赖。
|
|
900
|
+
// 为什么这里要单独计算 events, 是因为 1:1 并且刚好关系数据分配到了当前 record 上 时,关系事件顺序会不正确了。
|
|
901
|
+
const relianceEvents = [];
|
|
902
|
+
await this.deleteDifferentTableReliance(recordName, records, relianceEvents);
|
|
903
|
+
// 删除自身、有生命周期依赖的合表 record、合表到当前 record 的关系数据。
|
|
904
|
+
const sameRowRecordEvents = [];
|
|
905
|
+
await this.deleteRecordSameRowData(recordName, records, sameRowRecordEvents, inSameRowDataOp);
|
|
906
|
+
// 1. recordEvents 除了最后一个外全都是关系删除事件。
|
|
907
|
+
// 2. relianceEvents 中都是 reliance 删除事件,可能包含关系删除事件。
|
|
908
|
+
// 3. 最后 recordEvents 是 record 删除事件。
|
|
909
|
+
const relationEvents = sameRowRecordEvents.slice(0, sameRowRecordEvents.length - records.length);
|
|
910
|
+
const recordEvents = sameRowRecordEvents.slice(sameRowRecordEvents.length - records.length);
|
|
911
|
+
events?.push(...relationEvents, ...relianceEvents, ...recordEvents);
|
|
912
|
+
}
|
|
913
|
+
return records;
|
|
914
|
+
}
|
|
915
|
+
// 这里会把通表的 reliance,以及 reliance 的 reliance 都删除掉。
|
|
916
|
+
// this method will delete all the reliance of the record, and the reliance of the reliance.
|
|
917
|
+
async deleteRecordSameRowData(recordName, records, events, inSameRowDataOp = false) {
|
|
918
|
+
const recordInfo = this.map.getRecordInfo(recordName);
|
|
919
|
+
for (let record of records) {
|
|
920
|
+
if (!inSameRowDataOp) {
|
|
921
|
+
const recordWithSameRowDataQuery = RecordQuery.create(recordName, this.map, {
|
|
922
|
+
matchExpression: MatchExp.atom({
|
|
923
|
+
key: `id`,
|
|
924
|
+
value: ['=', record.id]
|
|
925
|
+
}),
|
|
926
|
+
attributeQuery: AttributeQuery.getAttributeQueryDataForRecord(recordName, this.map, true, true, true, true),
|
|
927
|
+
modifier: { limit: 1 }
|
|
928
|
+
});
|
|
929
|
+
const recordWithSameRowData = await this.findRecords(recordWithSameRowDataQuery, `find record with same row data for delete ${recordName}`, undefined);
|
|
930
|
+
const hasSameRowData = recordInfo.notRelianceCombined.some(info => {
|
|
931
|
+
return !!recordWithSameRowData[0]?.[info.attributeName]?.id;
|
|
932
|
+
});
|
|
933
|
+
if (hasSameRowData) {
|
|
934
|
+
// 存在同行 record,只能用 update
|
|
935
|
+
const p = this.getPlaceholder();
|
|
936
|
+
const fields = recordInfo.sameRowFields;
|
|
937
|
+
await this.database.update(`
|
|
938
|
+
UPDATE "${recordInfo.table}"
|
|
939
|
+
SET ${fields.map(field => `"${field}" = ${p()}`).join(',')}
|
|
940
|
+
WHERE "${recordInfo.idField}" = ${p()}
|
|
941
|
+
`, [...fields.map(field => null), record.id], recordInfo.idField, `use update to delete ${recordName} because of sameRowData`);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
// 不存在同行数据 record ,可以 delete row
|
|
945
|
+
const p = this.getPlaceholder();
|
|
946
|
+
await this.database.delete(`
|
|
947
|
+
DELETE FROM "${recordInfo.table}"
|
|
948
|
+
WHERE "${recordInfo.idField}" = ${p()}
|
|
949
|
+
`, [record.id], `delete record ${recordInfo.name} as row`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
// 1. 一定先删除递归处理同表的 reliance tree
|
|
953
|
+
for (let relianceInfo of recordInfo.sameTableReliance) {
|
|
954
|
+
// 只要真正存在这个数据才要删除
|
|
955
|
+
if (record[relianceInfo.attributeName]?.id) {
|
|
956
|
+
// 和 reliance 的 link record 的事件
|
|
957
|
+
events?.push({
|
|
958
|
+
type: 'delete',
|
|
959
|
+
recordName: relianceInfo.linkName,
|
|
960
|
+
record: {
|
|
961
|
+
...record[relianceInfo.attributeName][LINK_SYMBOL],
|
|
962
|
+
[relianceInfo.isRecordSource() ? 'source' : 'target']: record[relianceInfo.attributeName]
|
|
963
|
+
},
|
|
964
|
+
});
|
|
965
|
+
await this.handleDeletedRecordReliance(relianceInfo.recordName, record[relianceInfo.attributeName], events);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
// 2. 接着先记录关系删除事件,再记录 record 删除事件。
|
|
969
|
+
recordInfo.mergedRecordAttributes.forEach(attributeInfo => {
|
|
970
|
+
if (record[attributeInfo.attributeName]?.id) {
|
|
971
|
+
// 记录和自己合并的 link 事件
|
|
972
|
+
events?.push({
|
|
973
|
+
type: 'delete',
|
|
974
|
+
recordName: attributeInfo.linkName,
|
|
975
|
+
// CAUTION 注意这里一定要增加 link 上对于原始 record 的引用。外部计算的时候可能需要,那时可能 record 也删了查询不到了。
|
|
976
|
+
record: {
|
|
977
|
+
...record[attributeInfo.attributeName][LINK_SYMBOL],
|
|
978
|
+
[attributeInfo.isRecordSource() ? 'source' : 'target']: record
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
events?.push(...records.map(record => ({
|
|
985
|
+
type: 'delete',
|
|
986
|
+
recordName: recordName,
|
|
987
|
+
record,
|
|
988
|
+
})));
|
|
989
|
+
}
|
|
990
|
+
async handleDeletedRecordReliance(recordName, record, events) {
|
|
991
|
+
// 删除独立表或者关系在另一边的关系数据
|
|
992
|
+
await this.deleteNotReliantSeparateLinkRecords(recordName, [record], events);
|
|
993
|
+
// 删除依赖我的实体
|
|
994
|
+
await this.deleteDifferentTableReliance(recordName, [record], events);
|
|
995
|
+
// 删除自身以及有生命周期依赖的合表 record
|
|
996
|
+
await this.deleteRecordSameRowData(recordName, [record], events, true);
|
|
997
|
+
return record;
|
|
998
|
+
}
|
|
999
|
+
async deleteNotReliantSeparateLinkRecords(recordName, records, events) {
|
|
1000
|
+
const recordInfo = this.map.getRecordInfo(recordName);
|
|
1001
|
+
for (let info of recordInfo.differentTableRecordAttributes) {
|
|
1002
|
+
if (!info.isReliance) {
|
|
1003
|
+
const key = info.isRecordSource() ? 'source.id' : 'target.id';
|
|
1004
|
+
const newMatch = MatchExp.atom({
|
|
1005
|
+
key,
|
|
1006
|
+
value: ['in', records.map(r => r.id)]
|
|
1007
|
+
});
|
|
1008
|
+
// 关系事件上全部都要增加原始 record 的引用。注意不能给所有 events 都去加,因为删除 link 时也可能有关联实体被删除事件。
|
|
1009
|
+
// 只有最后哪些 events 是删除 link 的事件。
|
|
1010
|
+
const deletedLinkRecords = await this.deleteRecord(info.linkName, newMatch, events);
|
|
1011
|
+
if (events) {
|
|
1012
|
+
const recordsById = new Map(deletedLinkRecords.map(r => [r.id, r]));
|
|
1013
|
+
const deletedLinkRecordEvents = events.slice(events.length - deletedLinkRecords.length);
|
|
1014
|
+
deletedLinkRecordEvents.forEach(event => {
|
|
1015
|
+
event.record[info.isRecordSource() ? 'source' : 'target'] = recordsById.get(event.record[info.isRecordSource() ? 'source' : 'target'].id);
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async deleteDifferentTableReliance(recordName, records, events) {
|
|
1022
|
+
const recordInfo = this.map.getRecordInfo(recordName);
|
|
1023
|
+
const recordsById = events ? new Map(records.map(r => [r.id, r])) : undefined;
|
|
1024
|
+
for (let info of recordInfo.differentTableReliance) {
|
|
1025
|
+
const matchInIds = MatchExp.atom({
|
|
1026
|
+
key: `${info.getReverseInfo()?.attributeName}.id`,
|
|
1027
|
+
value: ['in', records.map(r => r.id)]
|
|
1028
|
+
});
|
|
1029
|
+
await this.deleteRecord(info.recordName, matchInIds, events);
|
|
1030
|
+
if (events) {
|
|
1031
|
+
// 删除关系时,要增加上当前 record 的引用。
|
|
1032
|
+
// TODO 这里需要更加高效的方法
|
|
1033
|
+
events.forEach(event => {
|
|
1034
|
+
if (event.recordName === info.linkName) {
|
|
1035
|
+
const record = recordsById.get(event.record[info.isRecordSource() ? 'source' : 'target'].id);
|
|
1036
|
+
if (record) {
|
|
1037
|
+
event.record[info.isRecordSource() ? 'source' : 'target'] = record;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
addLinkFromRecord(entity, attribute, entityId, relatedEntityId, attributes = {}, events) {
|
|
1045
|
+
const linkInfo = this.map.getLinkInfo(entity, attribute);
|
|
1046
|
+
const isEntitySource = linkInfo.isRelationSource(entity, attribute);
|
|
1047
|
+
const sourceId = isEntitySource ? entityId : relatedEntityId;
|
|
1048
|
+
const targetId = isEntitySource ? relatedEntityId : entityId;
|
|
1049
|
+
return this.addLink(linkInfo.name, sourceId, targetId, attributes, !linkInfo.isRelationSource(entity, attribute), events);
|
|
1050
|
+
}
|
|
1051
|
+
async addLink(linkName, sourceId, targetId, attributes = {}, moveSource = false, events) {
|
|
1052
|
+
const existRecord = (await this.findRecords(RecordQuery.create(linkName, this.map, {
|
|
1053
|
+
matchExpression: MatchExp.atom({ key: 'source.id', value: ['=', sourceId] }).and({
|
|
1054
|
+
key: 'target.id',
|
|
1055
|
+
value: ['=', targetId]
|
|
1056
|
+
}),
|
|
1057
|
+
modifier: {
|
|
1058
|
+
limit: 1
|
|
1059
|
+
}
|
|
1060
|
+
}), `check if link exist for add link ${linkName}`, undefined))[0];
|
|
1061
|
+
assert(!existRecord, `cannot create ${linkName} for ${sourceId} ${targetId}, link already exist`);
|
|
1062
|
+
const linkInfo = this.map.getLinkInfoByName(linkName);
|
|
1063
|
+
if (!linkInfo.isCombined() && !linkInfo.isMerged() && (linkInfo.isManyToOne || linkInfo.isOneToMany)) {
|
|
1064
|
+
// n 方向要 unlink ?
|
|
1065
|
+
const unlinkAttr = linkInfo.isManyToOne ? 'source.id' : 'target.id';
|
|
1066
|
+
const unlinkId = linkInfo.isManyToOne ? sourceId : targetId;
|
|
1067
|
+
const match = MatchExp.atom({
|
|
1068
|
+
key: unlinkAttr,
|
|
1069
|
+
value: ['=', unlinkId]
|
|
1070
|
+
});
|
|
1071
|
+
await this.unlink(linkName, match, false, 'unlink combined record for add new link', events);
|
|
1072
|
+
}
|
|
1073
|
+
const newLinkData = new NewRecordData(this.map, linkInfo.name, {
|
|
1074
|
+
source: { id: sourceId },
|
|
1075
|
+
target: { id: targetId },
|
|
1076
|
+
...attributes
|
|
1077
|
+
});
|
|
1078
|
+
return this.createRecord(newLinkData, `create link record ${linkInfo.name}`, events);
|
|
1079
|
+
}
|
|
1080
|
+
async unlink(linkName, matchExpressionData, moveSource = false, reason = '', events) {
|
|
1081
|
+
const linkInfo = this.map.getLinkInfoByName(linkName);
|
|
1082
|
+
assert(!linkInfo.isTargetReliance, `cannot unlink reliance data, you can only delete record, ${linkName}`);
|
|
1083
|
+
if (linkInfo.isCombined()) {
|
|
1084
|
+
return this.relocateCombinedRecordDataForLink(linkName, matchExpressionData, moveSource, events);
|
|
1085
|
+
}
|
|
1086
|
+
return this.deleteRecord(linkName, matchExpressionData, events);
|
|
1087
|
+
}
|
|
1088
|
+
// 查找树形结构的两个数据见的 path
|
|
1089
|
+
async findPath(recordName, attributePathStr, startRecordId, endRecordId, limitLength) {
|
|
1090
|
+
const attributePathAndLast = attributePathStr.split('.');
|
|
1091
|
+
const endAttribute = attributePathAndLast.at(-1);
|
|
1092
|
+
const attributePath = attributePathAndLast.slice(0, -1);
|
|
1093
|
+
const match = MatchExp.atom({
|
|
1094
|
+
key: 'id',
|
|
1095
|
+
value: ['=', startRecordId]
|
|
1096
|
+
});
|
|
1097
|
+
const attributeQuery = AttributeQuery.getAttributeQueryDataForRecord(recordName, this.map, true, true, false, true);
|
|
1098
|
+
const recursiveLabel = attributePathStr;
|
|
1099
|
+
// 第一次使用路径先产生 label
|
|
1100
|
+
let base = attributeQuery;
|
|
1101
|
+
for (let attr of attributePath) {
|
|
1102
|
+
base.push([attr, { attributeQuery: ['*'] }]);
|
|
1103
|
+
base = base.at(-1)[1].attributeQuery;
|
|
1104
|
+
}
|
|
1105
|
+
base.push([endAttribute, { label: recursiveLabel, attributeQuery: ['*'] }]);
|
|
1106
|
+
base = base.at(-1)[1].attributeQuery;
|
|
1107
|
+
// 第二次使用路径产生 goto
|
|
1108
|
+
for (let attr of attributePath) {
|
|
1109
|
+
base.push([attr, { attributeQuery: ['*'] }]);
|
|
1110
|
+
base = base.at(-1)[1].attributeQuery;
|
|
1111
|
+
}
|
|
1112
|
+
let foundPath;
|
|
1113
|
+
const exit = async (context) => {
|
|
1114
|
+
if (foundPath)
|
|
1115
|
+
return true;
|
|
1116
|
+
if (context.stack.at(-1)?.id === endRecordId) {
|
|
1117
|
+
foundPath = [...context.stack];
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
base.push([endAttribute, { goto: recursiveLabel, exit }]);
|
|
1122
|
+
const record = (await this.findRecords(RecordQuery.create(recordName, this.map, {
|
|
1123
|
+
matchExpression: match,
|
|
1124
|
+
attributeQuery
|
|
1125
|
+
}), `find records for path ${recordName}.${attributePathStr}`))[0];
|
|
1126
|
+
// 如果找到了,把头也放进去。让数据格式整齐。
|
|
1127
|
+
return foundPath ? [record, ...foundPath] : undefined;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
//# sourceMappingURL=RecordQueryAgent.js.map
|