@atscript/db 0.1.146 → 0.1.147
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/dist/agg.cjs +1 -1
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/agg.mjs +1 -1
- package/dist/{aggregate-fns-CfsveE1w.mjs → aggregate-fns-C0BymEqa.mjs} +3 -1
- package/dist/{aggregate-fns-CGBv3E8S.cjs → aggregate-fns-CtsRbZn9.cjs} +8 -0
- package/dist/{buckets-DYFu0eZ8.d.cts → buckets-CjdPipCC.d.cts} +522 -17
- package/dist/{buckets-DRycmhOW.d.mts → buckets-DYopfd2Y.d.mts} +522 -17
- package/dist/{column-diff-e2oHc71_.mjs → column-diff-CO06iQvI.mjs} +862 -115
- package/dist/{column-diff-D_Kyuh0S.cjs → column-diff-Dt7EDJsA.cjs} +872 -113
- package/dist/{column-diff-Q9UmWn5x.d.mts → fk-diff-BtOmL4OW.d.mts} +48 -2
- package/dist/{column-diff-w-Mym_3w.d.cts → fk-diff-DLqwVkKG.d.cts} +48 -2
- package/dist/index.cjs +28 -33
- package/dist/index.d.cts +40 -15
- package/dist/index.d.mts +40 -15
- package/dist/index.mjs +5 -33
- package/dist/{nested-writer-CnOOAehr.mjs → nested-writer-D35UsDBT.mjs} +542 -2
- package/dist/{nested-writer-xfQwxplL.cjs → nested-writer-SqtYJHF0.cjs} +666 -0
- package/dist/plugin.cjs +266 -5
- package/dist/plugin.mjs +267 -6
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-B-0NRKat.d.mts → relation-helpers-C2bLtE-t.d.mts} +1 -1
- package/dist/{relation-helpers-BOMm_HUI.d.cts → relation-helpers-DH3LvwLV.d.cts} +1 -1
- package/dist/relation-loader-BFhVuJ-B.cjs +369 -0
- package/dist/relation-loader-C0qE8xgR.mjs +370 -0
- package/dist/shared.cjs +1 -1
- package/dist/shared.d.cts +4 -3
- package/dist/shared.d.mts +4 -3
- package/dist/shared.mjs +1 -1
- package/dist/sync.cjs +10 -6
- package/dist/sync.d.cts +2 -25
- package/dist/sync.d.mts +2 -25
- package/dist/sync.mjs +10 -6
- package/dist/{validation-utils-DOsB4e6G.cjs → validation-utils-Da2GjobR.cjs} +35 -24
- package/dist/{validation-utils-CMR4fe2M.mjs → validation-utils-Dq0uZ7ef.mjs} +35 -24
- package/dist/{validator-Bw6ks9Hy.d.mts → validator-CMnTI6r7.d.cts} +9 -6
- package/dist/{validator-Bw6ks9Hy.d.cts → validator-CMnTI6r7.d.mts} +9 -6
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/package.json +8 -8
- package/dist/relation-loader-CBPY6kM7.cjs +0 -461
- package/dist/relation-loader-D9XuXaMv.mjs +0 -462
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { a as bucketTimeZoneUnavailable, n as CasMismatchError, r as DbError, t as CasExhaustedError } from "./db-error-D5uilS_A.mjs";
|
|
2
|
-
import { $ as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { $ as UniquSelect, A as geoIndexNotFoundMessage, B as classifyQueryPath, C as ALL_VIEW_CAPABILITIES, D as NativeIntegrity, E as IntegrityStrategy, F as acceptedOperatorsHint, G as guardPaths, H as guardAggregate, I as assertGeoPoint, J as sortFieldNames, K as guardQuery, L as bucketSourceVerdict, M as vectorIndexNotFoundMessage, N as ADAPTER_FILTER_REASON, O as AtscriptDbReadable, P as ENCRYPTED_REASON, Q as FieldMappingStrategy, R as canFilterLeaf, S as ALL_BUCKET_UNITS, T as createFailureCollector, U as guardFilter, V as collectQueryPaths, W as guardPath, X as RelationalFieldMapper, Y as unsupportedOperatorMessage, Z as DocumentFieldMapper, _ as isViewType, at as isJsonValueField, b as decomposePatch, ct as resolveCalendarBuckets, et as TableMetadata, f as fkColumns, g as isAtscriptDbView, h as AtscriptDbView, it as isBucketableField, j as searchIndexNotFoundMessage, k as resolveDesignType, lt as NoopLogger, n as isColumnTypeChanged, nt as isGeoPointType, ot as jsonValueAncestor, q as narrowerFilterOps, rt as aliasTargetOf, st as normalizeComputedSelect, tt as isGeoIndexableType, v as AtscriptDbTable, w as BaseDbAdapter, x as ApplicationIntegrity, y as assertNoVersionWrites, z as checkHavingKeys } from "./column-diff-CO06iQvI.mjs";
|
|
3
|
+
import { n as ALL_AGGREGATE_FNS } from "./aggregate-fns-C0BymEqa.mjs";
|
|
4
|
+
import { B as tableNameOf, C as ResolvedRelationFilter, D as forEachResolvedRelation, M as relationStaticFilter, O as hasRelationOp, S as REL_FILTER_MAX_NODES, T as containsRelationFilter, U as isFieldRef, W as translateQueryTree, k as isResolvedRelationFilter, w as andFilters, x as REL_FILTER_MAX_DEPTH } from "./nested-writer-D35UsDBT.mjs";
|
|
5
5
|
import { t as DB_ENTITY_ANNOTATIONS } from "./derived-rules-0sKn4f5C.mjs";
|
|
6
6
|
import { a as isPlainObject, i as isEmptyObject, n as findAncestorInSet, o as selfOrAncestor, r as getPath, t as deletePath } from "./object-TkiJQ-Dp.mjs";
|
|
7
7
|
import { t as DEFAULT_DB_SPACE } from "./consts-C_-5_pFq.mjs";
|
|
8
8
|
import { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, getDbFieldOp, isDbFieldOp, reconcileCas, separateCas, separateFieldOps } from "./ops.mjs";
|
|
9
9
|
import { a as forceNavNonOptional, c as getKeyProps, o as isNavRelation, r as buildValidationContext, s as createDbValidatorPlugin, t as buildDbValidator } from "./validator-Clu2q_7z.mjs";
|
|
10
10
|
import { isAnnotatedType } from "@atscript/typescript/utils";
|
|
11
|
-
import { computeInsights, isPrimitive, walkFilter } from "@uniqu/core";
|
|
11
|
+
import { RELATION_OPS, computeInsights, isPrimitive, isRelationOp, walkFilter } from "@uniqu/core";
|
|
12
12
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
13
13
|
//#region src/encryption.ts
|
|
14
14
|
const ENVELOPE_VERSION = "aes1";
|
|
@@ -376,34 +376,6 @@ var DbSpace = class {
|
|
|
376
376
|
}
|
|
377
377
|
};
|
|
378
378
|
//#endregion
|
|
379
|
-
//#region src/query/query-tree.ts
|
|
380
|
-
/**
|
|
381
|
-
* `true` when a query-tree operand is a field reference (`{ field, type? }`)
|
|
382
|
-
* rather than a literal — e.g. the right side of a field-to-field comparison.
|
|
383
|
-
* @since 0.1.136
|
|
384
|
-
*/
|
|
385
|
-
function isFieldRef(value) {
|
|
386
|
-
return value !== null && typeof value === "object" && "field" in value;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Translates a JS-emitted query tree into a FilterExpr.
|
|
390
|
-
* Resolves field references (type + field path) to physical column names
|
|
391
|
-
* via the provided resolver function.
|
|
392
|
-
*/
|
|
393
|
-
function translateQueryTree(node, resolveField) {
|
|
394
|
-
if ("$and" in node) return { $and: node.$and.map((n) => translateQueryTree(n, resolveField)) };
|
|
395
|
-
if ("$or" in node) return { $or: node.$or.map((n) => translateQueryTree(n, resolveField)) };
|
|
396
|
-
if ("$not" in node) return { $not: translateQueryTree(node.$not, resolveField) };
|
|
397
|
-
const comp = node;
|
|
398
|
-
const leftField = resolveField(comp.left);
|
|
399
|
-
if (isFieldRef(comp.right)) {
|
|
400
|
-
const rightField = resolveField(comp.right);
|
|
401
|
-
return { [leftField]: { [comp.op]: { $field: rightField } } };
|
|
402
|
-
}
|
|
403
|
-
if (comp.op === "$exists") return { [leftField]: { $exists: comp.right !== false } };
|
|
404
|
-
return { [leftField]: { [comp.op]: comp.right } };
|
|
405
|
-
}
|
|
406
|
-
//#endregion
|
|
407
379
|
//#region src/table/db-entity.ts
|
|
408
380
|
/**
|
|
409
381
|
* Whether `value` is a compiled type a `DbSpace` accepts: an annotated type
|
|
@@ -422,4 +394,4 @@ function isDbEntityType(value) {
|
|
|
422
394
|
return DB_ENTITY_ANNOTATIONS.some((name) => value.metadata.has(name));
|
|
423
395
|
}
|
|
424
396
|
//#endregion
|
|
425
|
-
export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ApplicationIntegrity, AtscriptDbReadable, AtscriptDbTable, AtscriptDbView, BaseDbAdapter, CasExhaustedError, CasMismatchError, DEFAULT_DB_SPACE, DbEncryption, DbError, DbSpace, DocumentFieldMapper, ENCRYPTED_REASON, FieldMappingStrategy, IntegrityStrategy, NativeIntegrity, NoopLogger, RelationalFieldMapper, TableMetadata, UniquSelect, acceptedOperatorsHint, aliasTargetOf, assertGeoPoint, assertNoVersionWrites, bucketSourceVerdict, bucketTimeZoneUnavailable, buildDbValidator, buildValidationContext, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, computeInsights, createDbValidatorPlugin, createFailureCollector, decomposePatch, deletePath, findAncestorInSet, forceNavNonOptional, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, guardAggregate, guardFilter, guardPath, guardPaths, guardQuery, isAtscriptDbView, isBucketableField, isColumnTypeChanged, isDbEntityType, isDbFieldOp, isEmptyObject, isFieldRef, isGeoIndexableType, isGeoPointType, isJsonValueField, isNavRelation, isPlainObject, isPrimitive, isViewType, jsonValueAncestor, narrowerFilterOps, normalizeComputedSelect, reconcileCas, resolveCalendarBuckets, resolveDesignType, searchIndexNotFoundMessage, selfOrAncestor, separateCas, separateFieldOps, sortFieldNames, tableNameOf, translateQueryTree, unsupportedOperatorMessage, vectorIndexNotFoundMessage, walkFilter, withOptimisticRetry };
|
|
397
|
+
export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_VIEW_CAPABILITIES, ApplicationIntegrity, AtscriptDbReadable, AtscriptDbTable, AtscriptDbView, BaseDbAdapter, CasExhaustedError, CasMismatchError, DEFAULT_DB_SPACE, DbEncryption, DbError, DbSpace, DocumentFieldMapper, ENCRYPTED_REASON, FieldMappingStrategy, IntegrityStrategy, NativeIntegrity, NoopLogger, RELATION_OPS, REL_FILTER_MAX_DEPTH, REL_FILTER_MAX_NODES, RelationalFieldMapper, ResolvedRelationFilter, TableMetadata, UniquSelect, acceptedOperatorsHint, aliasTargetOf, andFilters, assertGeoPoint, assertNoVersionWrites, bucketSourceVerdict, bucketTimeZoneUnavailable, buildDbValidator, buildValidationContext, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, computeInsights, containsRelationFilter, createDbValidatorPlugin, createFailureCollector, decomposePatch, deletePath, findAncestorInSet, fkColumns, forEachResolvedRelation, forceNavNonOptional, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, guardAggregate, guardFilter, guardPath, guardPaths, guardQuery, hasRelationOp, isAtscriptDbView, isBucketableField, isColumnTypeChanged, isDbEntityType, isDbFieldOp, isEmptyObject, isFieldRef, isGeoIndexableType, isGeoPointType, isJsonValueField, isNavRelation, isPlainObject, isPrimitive, isRelationOp, isResolvedRelationFilter, isViewType, jsonValueAncestor, narrowerFilterOps, normalizeComputedSelect, reconcileCas, relationStaticFilter, resolveCalendarBuckets, resolveDesignType, searchIndexNotFoundMessage, selfOrAncestor, separateCas, separateFieldOps, sortFieldNames, tableNameOf, translateQueryTree, unsupportedOperatorMessage, vectorIndexNotFoundMessage, walkFilter, withOptimisticRetry };
|
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
import { i as DepthLimitExceededError, r as DbError } from "./db-error-D5uilS_A.mjs";
|
|
2
|
-
import { r as getPath } from "./object-TkiJQ-Dp.mjs";
|
|
2
|
+
import { a as isPlainObject, r as getPath } from "./object-TkiJQ-Dp.mjs";
|
|
3
3
|
import { ValidatorError } from "@atscript/typescript/utils";
|
|
4
|
+
import { isRelationOp } from "@uniqu/core";
|
|
5
|
+
//#region src/query/query-tree.ts
|
|
6
|
+
/**
|
|
7
|
+
* `true` when a query-tree operand is a field reference (`{ field, type? }`)
|
|
8
|
+
* rather than a literal — e.g. the right side of a field-to-field comparison.
|
|
9
|
+
* @since 0.1.136
|
|
10
|
+
*/
|
|
11
|
+
function isFieldRef(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && "field" in value;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Translates a JS-emitted query tree into a FilterExpr.
|
|
16
|
+
* Resolves field references (type + field path) to physical column names
|
|
17
|
+
* via the provided resolver function.
|
|
18
|
+
*/
|
|
19
|
+
function translateQueryTree(node, resolveField) {
|
|
20
|
+
if ("$and" in node) return { $and: node.$and.map((n) => translateQueryTree(n, resolveField)) };
|
|
21
|
+
if ("$or" in node) return { $or: node.$or.map((n) => translateQueryTree(n, resolveField)) };
|
|
22
|
+
if ("$not" in node) return { $not: translateQueryTree(node.$not, resolveField) };
|
|
23
|
+
const comp = node;
|
|
24
|
+
const leftField = resolveField(comp.left);
|
|
25
|
+
if (isFieldRef(comp.right)) {
|
|
26
|
+
const rightField = resolveField(comp.right);
|
|
27
|
+
return { [leftField]: { [comp.op]: { $field: rightField } } };
|
|
28
|
+
}
|
|
29
|
+
if (comp.op === "$exists") return { [leftField]: { $exists: comp.right !== false } };
|
|
30
|
+
return { [leftField]: { [comp.op]: comp.right } };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calls `leaf` with the field path of every field-reference leaf of a
|
|
34
|
+
* computed-column expression (`@db.compute`), in source order — leaves name
|
|
35
|
+
* the view's own fields.
|
|
36
|
+
* @since 0.1.147
|
|
37
|
+
*/
|
|
38
|
+
function walkViewExpr(expr, leaf) {
|
|
39
|
+
if (typeof expr === "number") return;
|
|
40
|
+
if ("field" in expr) {
|
|
41
|
+
leaf(expr.field);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const arg of expr.args) walkViewExpr(arg, leaf);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether a computed-column expression may yield NULL: a `/` (division by
|
|
48
|
+
* zero is NULL), a leaf for which `nullableLeaf` holds, or an operation over
|
|
49
|
+
* a nullable operand — `coalesce` only when every argument is nullable.
|
|
50
|
+
* @since 0.1.147
|
|
51
|
+
*/
|
|
52
|
+
function viewExprNullable(expr, nullableLeaf) {
|
|
53
|
+
if (typeof expr === "number") return false;
|
|
54
|
+
if ("field" in expr) return nullableLeaf(expr.field);
|
|
55
|
+
if (expr.op === "/") return true;
|
|
56
|
+
if (expr.op === "coalesce") return expr.args.every((a) => viewExprNullable(a, nullableLeaf));
|
|
57
|
+
return expr.args.some((a) => viewExprNullable(a, nullableLeaf));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The `@db.compute` expression of a view field, if any.
|
|
61
|
+
* @since 0.1.147
|
|
62
|
+
*/
|
|
63
|
+
function computeOf(fieldType) {
|
|
64
|
+
return fieldType?.metadata.get("db.compute");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The transitive non-computed operands of the `@db.compute` field `field` of
|
|
68
|
+
* `viewType`: its leaves, a computed leaf replaced by its own operands, and
|
|
69
|
+
* (`via`) the computed fields passed through. `undefined` when `field` is not
|
|
70
|
+
* computed. Cycles are not followed (they are rejected where the view's
|
|
71
|
+
* columns are built).
|
|
72
|
+
* @since 0.1.147
|
|
73
|
+
*/
|
|
74
|
+
function computedOperands(viewType, field) {
|
|
75
|
+
const props = viewType.type.kind === "object" ? viewType.type.props : void 0;
|
|
76
|
+
const exprOf = (name) => computeOf(props?.get(name));
|
|
77
|
+
const root = exprOf(field);
|
|
78
|
+
if (root === void 0) return void 0;
|
|
79
|
+
const out = /* @__PURE__ */ new Set();
|
|
80
|
+
const via = [];
|
|
81
|
+
const seen = new Set([field]);
|
|
82
|
+
const visit = (expr) => walkViewExpr(expr, (path) => {
|
|
83
|
+
const nested = exprOf(path);
|
|
84
|
+
if (nested === void 0) out.add(path);
|
|
85
|
+
else if (!seen.has(path)) {
|
|
86
|
+
seen.add(path);
|
|
87
|
+
via.push(path);
|
|
88
|
+
visit(nested);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
visit(root);
|
|
92
|
+
return {
|
|
93
|
+
operands: [...out],
|
|
94
|
+
via
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
4
98
|
//#region src/rel/relation-helpers.ts
|
|
5
99
|
/**
|
|
6
100
|
* Finds the FK entry that connects a `@db.rel.to` relation to its target.
|
|
@@ -52,6 +146,452 @@ function tableNameOf(type) {
|
|
|
52
146
|
function resolveRelationTargetTable(relation) {
|
|
53
147
|
return tableNameOf(relation.targetType());
|
|
54
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* A string key of `fields`' values on `obj` — equal for rows that agree on
|
|
151
|
+
* every field (`null` / `undefined` key distinctly from any string). `read`
|
|
152
|
+
* reads one field (default: a top-level property).
|
|
153
|
+
*/
|
|
154
|
+
function compositeKey(fields, obj, read = (o, f) => o[f]) {
|
|
155
|
+
let key = "";
|
|
156
|
+
for (let i = 0; i < fields.length; i++) {
|
|
157
|
+
if (i > 0) key += "\0\0";
|
|
158
|
+
const v = read(obj, fields[i]);
|
|
159
|
+
key += v === null || v === void 0 ? "\0" : String(v);
|
|
160
|
+
}
|
|
161
|
+
return key;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Keeps, of each group of `rows` sharing `keyOf(row)`, the rows `page`
|
|
165
|
+
* selects — `$skip` / `$limit` applied per group, in the order of `rows`.
|
|
166
|
+
* The order of the kept rows is preserved.
|
|
167
|
+
*/
|
|
168
|
+
function slicePerGroup(rows, keyOf, page) {
|
|
169
|
+
const skip = page.skip ?? 0;
|
|
170
|
+
const end = page.limit === void 0 ? Infinity : skip + page.limit;
|
|
171
|
+
const seen = /* @__PURE__ */ new Map();
|
|
172
|
+
const kept = [];
|
|
173
|
+
for (const row of rows) {
|
|
174
|
+
const key = keyOf(row);
|
|
175
|
+
const index = seen.get(key) ?? 0;
|
|
176
|
+
seen.set(key, index + 1);
|
|
177
|
+
if (index >= skip && index < end) kept.push(row);
|
|
178
|
+
}
|
|
179
|
+
return kept;
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/query/relation-filter.ts
|
|
183
|
+
/**
|
|
184
|
+
* Relational filter predicates (`{ nav: { $some | $none: <filter on the
|
|
185
|
+
* related table> } }`) — resolution against the related tables.
|
|
186
|
+
*
|
|
187
|
+
* Semantics: `rel(r, nav)` is exactly the set of related rows `$with=nav`
|
|
188
|
+
* loads for row `r` (same foreign-key pairing, the relation's
|
|
189
|
+
* `@db.rel.filter` included). `$some: F` holds when one of them matches `F`,
|
|
190
|
+
* `$none: F` when none does; a NULL foreign-key component means "no related
|
|
191
|
+
* row". The core resolves each predicate into a {@link ResolvedRelationFilter}
|
|
192
|
+
* (physical names on every side, the inner filter translated by the related
|
|
193
|
+
* table's own field mapper) before the adapter sees the filter — adapters
|
|
194
|
+
* only render it.
|
|
195
|
+
*
|
|
196
|
+
* @since 0.1.147
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Maximum nesting of relational predicates in one filter, server-added ones
|
|
200
|
+
* included (a predicate inside a predicate's operand counts one level). The
|
|
201
|
+
* core backstop for every caller; higher than moost-db's per-client limit
|
|
202
|
+
* (`REL_FILTER_CLIENT_MAX_DEPTH`) so server overlays (row scopes,
|
|
203
|
+
* `transformRelationFilter`) have headroom above what a client may send.
|
|
204
|
+
*/
|
|
205
|
+
const REL_FILTER_MAX_DEPTH = 4;
|
|
206
|
+
/** Maximum number of relational predicates in one filter (nested and server-added ones included). See {@link REL_FILTER_MAX_DEPTH}. */
|
|
207
|
+
const REL_FILTER_MAX_NODES = 16;
|
|
208
|
+
/**
|
|
209
|
+
* Cross-realm brand of {@link ResolvedRelationFilter}: `instanceof` fails when
|
|
210
|
+
* two copies of `@atscript/db` are loaded (ESM + CJS, or nested installs) and
|
|
211
|
+
* an adapter from one sees nodes built by the other.
|
|
212
|
+
*/
|
|
213
|
+
const RESOLVED_BRAND = Symbol.for("@atscript/db:ResolvedRelationFilter");
|
|
214
|
+
/**
|
|
215
|
+
* A relational predicate as adapters receive it — the operand of
|
|
216
|
+
* `FilterVisitor.relation(field, op, operand)` once the core translated the
|
|
217
|
+
* filter. Every name is physical:
|
|
218
|
+
*
|
|
219
|
+
* - `to` / `from`: `pairs` correlate a SOURCE column with a TARGET column
|
|
220
|
+
* (`target.<pair.target> = source.<pair.source>`, one pair per composite
|
|
221
|
+
* key part);
|
|
222
|
+
* - `via`: `pairs` is empty — `junction.toSource` correlates the junction
|
|
223
|
+
* with the source row, `junction.toTarget` with the target row.
|
|
224
|
+
*
|
|
225
|
+
* `filter` is the inner filter on the TARGET (already translated by the
|
|
226
|
+
* target's field mapper: renames, flattening, value formatters; nested
|
|
227
|
+
* predicates resolved the same way), conjoined with the target part of the
|
|
228
|
+
* relation's `@db.rel.filter`. `{}` matches every related row.
|
|
229
|
+
*
|
|
230
|
+
* @since 0.1.147
|
|
231
|
+
*/
|
|
232
|
+
var ResolvedRelationFilter = class {
|
|
233
|
+
/** @internal cross-realm brand (see {@link isResolvedRelationFilter}). */
|
|
234
|
+
[RESOLVED_BRAND] = true;
|
|
235
|
+
kind;
|
|
236
|
+
/** Logical navigation field name on the source table. */
|
|
237
|
+
nav;
|
|
238
|
+
source;
|
|
239
|
+
target;
|
|
240
|
+
pairs;
|
|
241
|
+
junction;
|
|
242
|
+
filter;
|
|
243
|
+
constructor(init) {
|
|
244
|
+
this.kind = init.kind;
|
|
245
|
+
this.nav = init.nav;
|
|
246
|
+
this.source = init.source;
|
|
247
|
+
this.target = init.target;
|
|
248
|
+
this.pairs = init.pairs;
|
|
249
|
+
this.junction = init.junction;
|
|
250
|
+
this.filter = init.filter;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
/** `true` for a {@link ResolvedRelationFilter} (the resolved operand of a predicate). */
|
|
254
|
+
function isResolvedRelationFilter(value) {
|
|
255
|
+
return typeof value === "object" && value !== null && value[RESOLVED_BRAND] === true;
|
|
256
|
+
}
|
|
257
|
+
/** `true` when `value` (a filter entry's value) is an operator map with a `$some` / `$none` key. */
|
|
258
|
+
function hasRelationOp(value) {
|
|
259
|
+
if (!isPlainObject(value)) return false;
|
|
260
|
+
for (const key in value) if (isRelationOp(key)) return true;
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Pre-scan results of filters the core BUILT (translated / resolved
|
|
265
|
+
* filters, never mutated after) — adapters scan the same translated filter
|
|
266
|
+
* several times per operation. Caller-owned filters are never cached: they
|
|
267
|
+
* may be mutated between two queries.
|
|
268
|
+
*/
|
|
269
|
+
const relationFilterCache = /* @__PURE__ */ new WeakMap();
|
|
270
|
+
/** @internal Records the pre-scan result of a filter the core built. */
|
|
271
|
+
function noteRelationFilter(filter, has) {
|
|
272
|
+
if (filter && typeof filter === "object") relationFilterCache.set(filter, has);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* `true` when `filter` holds a relational predicate anywhere outside
|
|
276
|
+
* predicate operands (through `$and` / `$or` / `$not`) — the cheap pre-scan
|
|
277
|
+
* the field mappers and renderers use to keep predicate-free filters on
|
|
278
|
+
* their fast paths. Results for filters the core translated are cached.
|
|
279
|
+
*/
|
|
280
|
+
function containsRelationFilter(filter) {
|
|
281
|
+
if (!filter || typeof filter !== "object") return false;
|
|
282
|
+
const cached = relationFilterCache.get(filter);
|
|
283
|
+
if (cached !== void 0) return cached;
|
|
284
|
+
for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") {
|
|
285
|
+
if (Array.isArray(value) && value.some((child) => containsRelationFilter(child))) return true;
|
|
286
|
+
} else if (key === "$not") {
|
|
287
|
+
if (containsRelationFilter(value)) return true;
|
|
288
|
+
} else if (!key.startsWith("$") && hasRelationOp(value)) return true;
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Calls `visit` for every resolved predicate of a TRANSLATED filter,
|
|
293
|
+
* depth-first: the top-level ones, then (with `nested`) those inside each
|
|
294
|
+
* operand — target filters and junction filters alike. Adapters use it to
|
|
295
|
+
* prepare per-predicate data (memory snapshots, self-referencing checks).
|
|
296
|
+
*/
|
|
297
|
+
function forEachResolvedRelation(filter, visit, nested = true) {
|
|
298
|
+
if (!filter || typeof filter !== "object") return;
|
|
299
|
+
for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") {
|
|
300
|
+
if (Array.isArray(value)) for (const child of value) forEachResolvedRelation(child, visit, nested);
|
|
301
|
+
} else if (key === "$not") forEachResolvedRelation(value, visit, nested);
|
|
302
|
+
else if (!key.startsWith("$") && isPlainObject(value)) for (const [op, operand] of Object.entries(value)) {
|
|
303
|
+
if (!isRelationOp(op) || !isResolvedRelationFilter(operand)) continue;
|
|
304
|
+
visit(operand, op);
|
|
305
|
+
if (nested) {
|
|
306
|
+
forEachResolvedRelation(operand.filter, visit, nested);
|
|
307
|
+
forEachResolvedRelation(operand.junction?.filter, visit, nested);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** A fresh {@link TRelGuardState} for one query. */
|
|
312
|
+
function relGuardState(write = false) {
|
|
313
|
+
return {
|
|
314
|
+
depth: 0,
|
|
315
|
+
counter: { nodes: 0 },
|
|
316
|
+
write,
|
|
317
|
+
path: ""
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function invalid(path, message) {
|
|
321
|
+
return new DbError("INVALID_QUERY", [{
|
|
322
|
+
path,
|
|
323
|
+
message
|
|
324
|
+
}]);
|
|
325
|
+
}
|
|
326
|
+
function notSupported(path, message) {
|
|
327
|
+
return new DbError("REL_FILTER_NOT_SUPPORTED", [{
|
|
328
|
+
path,
|
|
329
|
+
message
|
|
330
|
+
}]);
|
|
331
|
+
}
|
|
332
|
+
/** The present, non-empty `parts` ANDed (`{}` when none, a single one as is). */
|
|
333
|
+
function andFilters(...parts) {
|
|
334
|
+
const present = parts.filter((p) => p != null && Object.keys(p).length > 0);
|
|
335
|
+
if (present.length === 0) return {};
|
|
336
|
+
if (present.length === 1) return present[0];
|
|
337
|
+
return { $and: present };
|
|
338
|
+
}
|
|
339
|
+
const NO_STATIC_FILTER = {};
|
|
340
|
+
const staticFilterCache = /* @__PURE__ */ new WeakMap();
|
|
341
|
+
/**
|
|
342
|
+
* A relation's `@db.rel.filter` as logical filters per side — what `$with`
|
|
343
|
+
* loading and relational predicates both AND into the related rows (the
|
|
344
|
+
* filter is part of the relation's meaning). Top-level `and` conditions are
|
|
345
|
+
* split by side: an unqualified field and the related type's fields go to
|
|
346
|
+
* `target`, the `@db.rel.via` junction's to `junction`. A single condition
|
|
347
|
+
* that reads both sides (e.g. an `or` across them) or compares two fields is
|
|
348
|
+
* rejected with `INVALID_QUERY` — `name` is the navigation field (error path).
|
|
349
|
+
*
|
|
350
|
+
* @since 0.1.147
|
|
351
|
+
*/
|
|
352
|
+
function relationStaticFilter(relation, name = "") {
|
|
353
|
+
if (!relation.filter) return NO_STATIC_FILTER;
|
|
354
|
+
const cached = staticFilterCache.get(relation);
|
|
355
|
+
if (cached) return cached;
|
|
356
|
+
const targetType = relation.targetType();
|
|
357
|
+
const junctionType = relation.viaType?.();
|
|
358
|
+
const targetName = tableNameOf(targetType);
|
|
359
|
+
const junctionName = junctionType ? tableNameOf(junctionType) : void 0;
|
|
360
|
+
const sideOf = (ref) => {
|
|
361
|
+
const type = ref.type?.();
|
|
362
|
+
if (!type || type === targetType || tableNameOf(type) === targetName) return "target";
|
|
363
|
+
if (junctionType && (type === junctionType || tableNameOf(type) === junctionName)) return "junction";
|
|
364
|
+
throw invalid(name, `@db.rel.filter on "${name}" references a type other than the related type` + (junctionType ? " and the junction" : "") + " — only those can be referenced");
|
|
365
|
+
};
|
|
366
|
+
const target = [];
|
|
367
|
+
const junction = [];
|
|
368
|
+
const conjuncts = "$and" in relation.filter ? relation.filter.$and : [relation.filter];
|
|
369
|
+
for (const conjunct of conjuncts) {
|
|
370
|
+
const sides = /* @__PURE__ */ new Set();
|
|
371
|
+
collectSides(conjunct, (ref) => sides.add(sideOf(ref)), name);
|
|
372
|
+
if (sides.size > 1) throw invalid(name, `@db.rel.filter on "${name}" has a condition reading both the junction and the related type — split it into separate "and" conditions`);
|
|
373
|
+
const filter = translateQueryTree(conjunct, (ref) => ref.field);
|
|
374
|
+
(sides.has("junction") ? junction : target).push(filter);
|
|
375
|
+
}
|
|
376
|
+
const result = {
|
|
377
|
+
target: target.length > 0 ? andFilters(...target) : void 0,
|
|
378
|
+
junction: junction.length > 0 ? andFilters(...junction) : void 0
|
|
379
|
+
};
|
|
380
|
+
staticFilterCache.set(relation, result);
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
function collectSides(node, visit, name) {
|
|
384
|
+
if ("$and" in node) {
|
|
385
|
+
for (const child of node.$and) collectSides(child, visit, name);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if ("$or" in node) {
|
|
389
|
+
for (const child of node.$or) collectSides(child, visit, name);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if ("$not" in node) {
|
|
393
|
+
collectSides(node.$not, visit, name);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
visit(node.left);
|
|
397
|
+
if (isFieldRef(node.right)) throw invalid(name, `@db.rel.filter on "${name}" compares two fields — only field-to-value conditions are supported`);
|
|
398
|
+
}
|
|
399
|
+
function zip(left, right, a, b) {
|
|
400
|
+
return left.map((value, i) => ({
|
|
401
|
+
[a]: value,
|
|
402
|
+
[b]: right[i]
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The {@link TRelationFilterHost} of a readable: resolves a navigation
|
|
407
|
+
* field's related table (and junction) through `resolve` (the readable's
|
|
408
|
+
* table resolver), pairing foreign keys exactly like `$with` loading does
|
|
409
|
+
* (`findFKEntryForRelation` / `findRemoteFK`).
|
|
410
|
+
*/
|
|
411
|
+
function createRelationFilterHost(owner, resolve) {
|
|
412
|
+
const plans = /* @__PURE__ */ new Map();
|
|
413
|
+
const sameAdapter = (other, path, what) => {
|
|
414
|
+
if (!owner.getAdapter().sharesStoreWith(other.getAdapter())) throw notSupported(path, `Relational predicate on "${path}": the ${what} table lives in a different database or adapter`);
|
|
415
|
+
};
|
|
416
|
+
const planOf = (nav, path) => {
|
|
417
|
+
const cached = plans.get(nav);
|
|
418
|
+
if (cached) return cached;
|
|
419
|
+
const meta = owner.getMetadata();
|
|
420
|
+
const relation = meta.relations.get(nav);
|
|
421
|
+
if (!relation) throw invalid(path, `"$some" / "$none" are only valid on a navigation relation — "${nav}" is not one`);
|
|
422
|
+
const target = resolve(relation.targetType());
|
|
423
|
+
if (!target) throw notSupported(path, `Relational predicate on "${path}": the related table is not available`);
|
|
424
|
+
sameAdapter(target, path, "related");
|
|
425
|
+
let plan;
|
|
426
|
+
if (relation.direction === "to") {
|
|
427
|
+
const fk = findFKEntryForRelation(relation, meta.foreignKeys);
|
|
428
|
+
if (!fk) throw invalid(path, `Relation "${nav}" has no foreign key to filter by`);
|
|
429
|
+
plan = {
|
|
430
|
+
kind: "to",
|
|
431
|
+
relation,
|
|
432
|
+
target,
|
|
433
|
+
pairs: zip(fk.fields, fk.targetFields, "source", "target")
|
|
434
|
+
};
|
|
435
|
+
} else if (relation.direction === "from") {
|
|
436
|
+
const fk = findRemoteFK(target.getMetadata(), owner.tableName, relation.alias);
|
|
437
|
+
if (!fk) throw invalid(path, `Relation "${nav}" has no foreign key to filter by`);
|
|
438
|
+
plan = {
|
|
439
|
+
kind: "from",
|
|
440
|
+
relation,
|
|
441
|
+
target,
|
|
442
|
+
pairs: zip(fk.targetFields, fk.fields, "source", "target")
|
|
443
|
+
};
|
|
444
|
+
} else {
|
|
445
|
+
const junctionType = relation.viaType?.();
|
|
446
|
+
const junction = junctionType ? resolve(junctionType) : void 0;
|
|
447
|
+
if (!junction) throw notSupported(path, `Relational predicate on "${path}": the junction table is not available`);
|
|
448
|
+
sameAdapter(junction, path, "junction");
|
|
449
|
+
const junctionMeta = junction.getMetadata();
|
|
450
|
+
const fkToThis = findRemoteFK(junctionMeta, owner.tableName);
|
|
451
|
+
const fkToTarget = findRemoteFK(junctionMeta, tableNameOf(relation.targetType()));
|
|
452
|
+
if (!fkToThis || !fkToTarget) throw invalid(path, `Relation "${nav}" has no junction foreign keys to filter by`);
|
|
453
|
+
if (fkToThis === fkToTarget) throw invalid(path, `Relation "${nav}" is a self-referencing many-to-many — relational predicates cannot tell its two junction keys apart`);
|
|
454
|
+
plan = {
|
|
455
|
+
kind: "via",
|
|
456
|
+
relation,
|
|
457
|
+
target,
|
|
458
|
+
pairs: [],
|
|
459
|
+
junction: {
|
|
460
|
+
owner: junction,
|
|
461
|
+
toSource: zip(fkToThis.fields, fkToThis.targetFields, "junction", "source"),
|
|
462
|
+
toTarget: zip(fkToTarget.fields, fkToTarget.targetFields, "junction", "target")
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
plans.set(nav, plan);
|
|
467
|
+
return plan;
|
|
468
|
+
};
|
|
469
|
+
const tableOf = (o) => {
|
|
470
|
+
const adapter = o.getAdapter();
|
|
471
|
+
return {
|
|
472
|
+
table: adapter.resolveTableName(),
|
|
473
|
+
name: adapter.resolveTableName(false),
|
|
474
|
+
adapter
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
guard(nav, op, inner, state) {
|
|
479
|
+
const path = state.path ? `${state.path}.${nav}` : nav;
|
|
480
|
+
const depth = state.depth + 1;
|
|
481
|
+
if (depth > 4) throw invalid("", `Relational predicates nest at most 4 levels deep`);
|
|
482
|
+
if (++state.counter.nodes > 16) throw invalid("", `At most 16 relational predicates per query`);
|
|
483
|
+
if (!isPlainObject(inner)) throw invalid(path, `"${op}" on "${path}" expects a filter object`);
|
|
484
|
+
const plan = planOf(nav, path);
|
|
485
|
+
relationStaticFilter(plan.relation, path);
|
|
486
|
+
try {
|
|
487
|
+
plan.target._guardRelationOperand(inner, {
|
|
488
|
+
...state,
|
|
489
|
+
depth,
|
|
490
|
+
path
|
|
491
|
+
});
|
|
492
|
+
} catch (error) {
|
|
493
|
+
throw prefixError(error, path);
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
resolve(nav, _op, inner, depth) {
|
|
497
|
+
if (depth > 4) throw invalid("", `Relational predicates nest at most 4 levels deep`);
|
|
498
|
+
const plan = planOf(nav, nav);
|
|
499
|
+
const ownerMeta = owner.getMetadata();
|
|
500
|
+
const targetMeta = plan.target.getMetadata();
|
|
501
|
+
const statics = relationStaticFilter(plan.relation, nav);
|
|
502
|
+
const filter = plan.target._resolveRelationOperand(andFilters(statics.target, inner), depth);
|
|
503
|
+
let junction;
|
|
504
|
+
if (plan.junction) {
|
|
505
|
+
const junctionMeta = plan.junction.owner.getMetadata();
|
|
506
|
+
junction = {
|
|
507
|
+
...tableOf(plan.junction.owner),
|
|
508
|
+
toSource: plan.junction.toSource.map((p) => ({
|
|
509
|
+
junction: junctionMeta.physicalPath(p.junction),
|
|
510
|
+
source: ownerMeta.physicalPath(p.source)
|
|
511
|
+
})),
|
|
512
|
+
toTarget: plan.junction.toTarget.map((p) => ({
|
|
513
|
+
junction: junctionMeta.physicalPath(p.junction),
|
|
514
|
+
target: targetMeta.physicalPath(p.target)
|
|
515
|
+
})),
|
|
516
|
+
...statics.junction ? { filter: plan.junction.owner._resolveRelationOperand(statics.junction, depth) } : {}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return new ResolvedRelationFilter({
|
|
520
|
+
kind: plan.kind,
|
|
521
|
+
nav,
|
|
522
|
+
source: tableOf(owner),
|
|
523
|
+
target: tableOf(plan.target),
|
|
524
|
+
pairs: plan.pairs.map((p) => ({
|
|
525
|
+
source: ownerMeta.physicalPath(p.source),
|
|
526
|
+
target: targetMeta.physicalPath(p.target)
|
|
527
|
+
})),
|
|
528
|
+
junction,
|
|
529
|
+
filter
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/** Re-throws a related table's guard error with its paths under the navigation chain. */
|
|
535
|
+
function prefixError(error, path) {
|
|
536
|
+
if (!(error instanceof DbError)) return error;
|
|
537
|
+
if (error.errors.every((e) => !e.path)) return error;
|
|
538
|
+
if (error.errors.every((e) => e.path === path || e.path.startsWith(`${path}.`))) return error;
|
|
539
|
+
const errors = error.errors.map((e) => ({
|
|
540
|
+
path: e.path ? `${path}.${e.path}` : path,
|
|
541
|
+
message: e.message
|
|
542
|
+
}));
|
|
543
|
+
return new DbError(error.code, errors, `${errors[0]?.message ?? error.message} (in "${path}")`);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Resolves every relational predicate of a (logical) filter through
|
|
547
|
+
* `meta.relationFilters` — the step every field-mapper entry point runs
|
|
548
|
+
* first. Predicates at the top of `filter` are at level `depth + 1`.
|
|
549
|
+
* Already-resolved operands pass through, so translating twice is safe.
|
|
550
|
+
* One pass: unchanged subtrees (and `filter` itself, when nothing needed
|
|
551
|
+
* resolving) are returned as they are.
|
|
552
|
+
*/
|
|
553
|
+
function resolveRelationFilterTree(filter, meta, depth) {
|
|
554
|
+
let out;
|
|
555
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
556
|
+
let next = value;
|
|
557
|
+
if (key === "$and" || key === "$or") {
|
|
558
|
+
if (Array.isArray(value)) {
|
|
559
|
+
let children;
|
|
560
|
+
for (let i = 0; i < value.length; i++) {
|
|
561
|
+
const child = value[i];
|
|
562
|
+
if (!child || typeof child !== "object") continue;
|
|
563
|
+
const resolved = resolveRelationFilterTree(child, meta, depth);
|
|
564
|
+
if (resolved === child) continue;
|
|
565
|
+
children ??= [...value];
|
|
566
|
+
children[i] = resolved;
|
|
567
|
+
}
|
|
568
|
+
if (children) next = children;
|
|
569
|
+
}
|
|
570
|
+
} else if (key === "$not") {
|
|
571
|
+
if (value && typeof value === "object") next = resolveRelationFilterTree(value, meta, depth);
|
|
572
|
+
} else if (!key.startsWith("$") && hasRelationOp(value)) next = resolvePredicateMap(key, value, meta, depth);
|
|
573
|
+
if (next === value) continue;
|
|
574
|
+
out ??= { ...filter };
|
|
575
|
+
out[key] = next;
|
|
576
|
+
}
|
|
577
|
+
if (!out) return filter;
|
|
578
|
+
noteRelationFilter(out, true);
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
/** One `{ $some | $none: … }` map of `key` resolved (operands already resolved pass through). */
|
|
582
|
+
function resolvePredicateMap(key, value, meta, depth) {
|
|
583
|
+
const host = meta.relationFilters;
|
|
584
|
+
if (!host) {
|
|
585
|
+
if (!meta.navFields.has(key)) throw invalid(key, `"$some" / "$none" are only valid on a navigation relation — "${key}" is not one`);
|
|
586
|
+
throw notSupported(key, `Relational predicate on "${key}" needs the table to come from a DbSpace (no table resolver)`);
|
|
587
|
+
}
|
|
588
|
+
const ops = {};
|
|
589
|
+
for (const [op, operand] of Object.entries(value)) {
|
|
590
|
+
if (!isRelationOp(op)) throw invalid(key, `Cannot mix "$some" / "$none" with "${op}" on "${key}"`);
|
|
591
|
+
ops[op] = isResolvedRelationFilter(operand) ? operand : host.resolve(key, op, operand, depth + 1);
|
|
592
|
+
}
|
|
593
|
+
return ops;
|
|
594
|
+
}
|
|
55
595
|
//#endregion
|
|
56
596
|
//#region src/shared/keys.ts
|
|
57
597
|
/** String form of a key value (an ObjectId stringifies to its hex, a bigint to its digits). */
|
|
@@ -877,4 +1417,4 @@ async function viaReplace(via, targets, parentPK, maxDepth, depth) {
|
|
|
877
1417
|
await viaLinkTargets(via, targets, parentPK, maxDepth, depth);
|
|
878
1418
|
}
|
|
879
1419
|
//#endregion
|
|
880
|
-
export {
|
|
1420
|
+
export { noteRelationFilter as A, tableNameOf as B, ResolvedRelationFilter as C, forEachResolvedRelation as D, createRelationFilterHost as E, findFKEntryForRelation as F, viewExprNullable as G, computedOperands as H, findFKForRelation as I, walkViewExpr as K, findRemoteFK as L, relationStaticFilter as M, resolveRelationFilterTree as N, hasRelationOp as O, compositeKey as P, resolveRelationTargetTable as R, REL_FILTER_MAX_NODES as S, containsRelationFilter as T, isFieldRef as U, computeOf as V, translateQueryTree as W, remapDeleteFkViolation as _, batchPatchNestedFrom as a, sameKey as b, batchReplaceNestedFrom as c, checkDepthOverflow as d, planNestedFromVia as f, enrichFkViolation as g, validateBatch as h, batchInsertNestedVia as i, relGuardState as j, isResolvedRelationFilter as k, batchReplaceNestedTo as l, preValidateNestedFrom as m, batchInsertNestedFrom as n, batchPatchNestedTo as o, planPatchNestedTo as p, batchInsertNestedTo as r, batchPatchNestedVia as s, applyPatchNestedTo as t, batchReplaceNestedVia as u, pkTupleKey as v, andFilters as w, REL_FILTER_MAX_DEPTH as x, rowMatchesKey as y, slicePerGroup as z };
|