@danceroutine/tango-orm 1.4.0 → 1.5.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/dist/index.js +2 -2
- package/dist/manager/ModelManager.d.ts +11 -11
- package/dist/manager/index.js +2 -2
- package/dist/manager-C6oJ2tAF.js +1 -1
- package/dist/query/QuerySet.d.ts +17 -13
- package/dist/query/compiler/QueryCompiler.d.ts +14 -21
- package/dist/query/domain/CompiledQuery.d.ts +26 -9
- package/dist/query/domain/RelationMeta.d.ts +37 -0
- package/dist/query/domain/RelationTyping.d.ts +42 -0
- package/dist/query/domain/TableMeta.d.ts +9 -0
- package/dist/query/domain/TableMetaFactory.d.ts +23 -0
- package/dist/query/domain/index.d.ts +2 -1
- package/dist/query/index.js +1 -1
- package/dist/query/planning/QueryPlanner.d.ts +16 -0
- package/dist/query/planning/domain/QueryHydrationPlan.d.ts +20 -0
- package/dist/query/planning/index.d.ts +2 -0
- package/dist/{query-CWZ1cfjo.js → query-DYiJ5m_B.js} +434 -165
- package/dist/query-DYiJ5m_B.js.map +1 -0
- package/dist/{registerModelObjects-Bva_f-Qh.js → registerModelObjects-B1VzZ072.js} +4 -28
- package/dist/registerModelObjects-B1VzZ072.js.map +1 -0
- package/dist/runtime/index.js +2 -2
- package/dist/runtime-ByXbpVBS.js +1 -1
- package/package.json +6 -6
- package/dist/query-CWZ1cfjo.js.map +0 -1
- package/dist/registerModelObjects-Bva_f-Qh.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __export } from "./chunk-DLY2FNSh.js";
|
|
2
|
-
import { SqlSafetyEngine } from "@danceroutine/tango-core";
|
|
2
|
+
import { SqlSafetyEngine, isError } from "@danceroutine/tango-core";
|
|
3
|
+
import { ModelRegistry } from "@danceroutine/tango-schema";
|
|
3
4
|
|
|
4
5
|
//#region src/query/domain/internal/InternalRelationKind.ts
|
|
5
6
|
const InternalRelationKind = {
|
|
@@ -9,6 +10,72 @@ const InternalRelationKind = {
|
|
|
9
10
|
MANY_TO_MANY: "manyToMany"
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/query/domain/TableMetaFactory.ts
|
|
15
|
+
var TableMetaFactory = class TableMetaFactory {
|
|
16
|
+
static create(model) {
|
|
17
|
+
const owner = model.metadata.key ? ModelRegistry.getOwner(model) : undefined;
|
|
18
|
+
const cache = new Map();
|
|
19
|
+
return TableMetaFactory.createWithCache(model, owner, cache);
|
|
20
|
+
}
|
|
21
|
+
static createWithCache(model, owner, cache) {
|
|
22
|
+
if (model.metadata.key) {
|
|
23
|
+
const cached = cache.get(model.metadata.key);
|
|
24
|
+
if (cached) return cached;
|
|
25
|
+
}
|
|
26
|
+
const pkField = model.metadata.fields.find((field) => field.primaryKey);
|
|
27
|
+
if (!pkField) throw new Error(`Model '${model.metadata.name}' cannot attach a manager without a primary key field.`);
|
|
28
|
+
const tableMeta = {
|
|
29
|
+
modelKey: model.metadata.key,
|
|
30
|
+
table: model.metadata.table,
|
|
31
|
+
pk: pkField.name,
|
|
32
|
+
columns: Object.fromEntries(model.metadata.fields.map((field) => [field.name, field.type]))
|
|
33
|
+
};
|
|
34
|
+
if (model.metadata.key) cache.set(model.metadata.key, tableMeta);
|
|
35
|
+
if (!model.metadata.key || !owner) return tableMeta;
|
|
36
|
+
const relations = owner.getResolvedRelationGraph().byModel.get(model.metadata.key);
|
|
37
|
+
if (!relations || relations.size === 0) return tableMeta;
|
|
38
|
+
tableMeta.relations = Object.fromEntries(Array.from(relations.entries()).filter(([, relation]) => relation.capabilities.queryable && relation.capabilities.hydratable).map(([name, relation]) => {
|
|
39
|
+
const targetModel = owner.getByKey(relation.targetModelKey);
|
|
40
|
+
const targetMeta = TableMetaFactory.createWithCache(targetModel, owner, cache);
|
|
41
|
+
const { queryable, hydratable } = relation.capabilities;
|
|
42
|
+
const isSingleRelation = relation.kind === InternalRelationKind.BELONGS_TO || relation.kind === InternalRelationKind.HAS_ONE;
|
|
43
|
+
const sourceKey = relation.kind === InternalRelationKind.BELONGS_TO ? relation.localFieldName : relation.targetFieldName;
|
|
44
|
+
const targetKey = relation.kind === InternalRelationKind.BELONGS_TO ? relation.targetFieldName : relation.localFieldName;
|
|
45
|
+
const targetColumns = Object.fromEntries(targetModel.metadata.fields.map((field) => [field.name, field.type]));
|
|
46
|
+
const capabilities = {
|
|
47
|
+
queryable,
|
|
48
|
+
hydratable,
|
|
49
|
+
joinable: isSingleRelation && queryable && hydratable,
|
|
50
|
+
prefetchable: queryable && hydratable
|
|
51
|
+
};
|
|
52
|
+
return [name, {
|
|
53
|
+
edgeId: relation.edgeId,
|
|
54
|
+
sourceModelKey: relation.sourceModelKey,
|
|
55
|
+
targetModelKey: relation.targetModelKey,
|
|
56
|
+
kind: relation.kind,
|
|
57
|
+
cardinality: isSingleRelation ? "single" : "many",
|
|
58
|
+
capabilities,
|
|
59
|
+
table: targetModel.metadata.table,
|
|
60
|
+
sourceKey,
|
|
61
|
+
targetKey,
|
|
62
|
+
targetPrimaryKey: targetMeta.pk,
|
|
63
|
+
targetColumns,
|
|
64
|
+
alias: relation.alias,
|
|
65
|
+
targetMeta
|
|
66
|
+
}];
|
|
67
|
+
}));
|
|
68
|
+
return tableMeta;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/query/domain/RelationMeta.ts
|
|
74
|
+
const InternalRelationHydrationLoadMode = {
|
|
75
|
+
JOIN: "join",
|
|
76
|
+
PREFETCH: "prefetch"
|
|
77
|
+
};
|
|
78
|
+
|
|
12
79
|
//#endregion
|
|
13
80
|
//#region src/query/domain/internal/InternalDialect.ts
|
|
14
81
|
const InternalDialect = {
|
|
@@ -179,39 +246,129 @@ var OrmSqlSafetyAdapter = class OrmSqlSafetyAdapter {
|
|
|
179
246
|
}
|
|
180
247
|
};
|
|
181
248
|
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/query/domain/RelationTyping.ts
|
|
251
|
+
const InternalRelationHydrationCardinality = {
|
|
252
|
+
SINGLE: "single",
|
|
253
|
+
MANY: "many"
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/query/planning/QueryPlanner.ts
|
|
258
|
+
var QueryPlanner = class QueryPlanner {
|
|
259
|
+
static BRAND = "tango.orm.query_planner";
|
|
260
|
+
__tangoBrand = QueryPlanner.BRAND;
|
|
261
|
+
constructor(meta) {
|
|
262
|
+
this.meta = meta;
|
|
263
|
+
}
|
|
264
|
+
static isQueryPlanner(value) {
|
|
265
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === QueryPlanner.BRAND;
|
|
266
|
+
}
|
|
267
|
+
plan(state) {
|
|
268
|
+
const requestedPaths = Array.from(new Set([...state.selectRelated ?? [], ...state.prefetchRelated ?? []]));
|
|
269
|
+
if (requestedPaths.length === 0) return {
|
|
270
|
+
joinNodes: [],
|
|
271
|
+
prefetchNodes: [],
|
|
272
|
+
requestedPaths: []
|
|
273
|
+
};
|
|
274
|
+
const rootChildren = new Map();
|
|
275
|
+
for (const relationPath of new Set(state.selectRelated ?? [])) this.addPath(rootChildren, relationPath, "select");
|
|
276
|
+
for (const relationPath of new Set(state.prefetchRelated ?? [])) this.addPath(rootChildren, relationPath, "prefetch");
|
|
277
|
+
const { joinNodes, prefetchNodes } = this.buildPlannedChildren(rootChildren);
|
|
278
|
+
return {
|
|
279
|
+
joinNodes,
|
|
280
|
+
prefetchNodes,
|
|
281
|
+
requestedPaths
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
addPath(rootChildren, relationPath, mode) {
|
|
285
|
+
const segments = relationPath.split("__").filter(Boolean);
|
|
286
|
+
if (segments.length === 0) throw new Error(`Invalid empty relation path '${relationPath}'.`);
|
|
287
|
+
let currentMeta = this.meta;
|
|
288
|
+
let currentChildren = rootChildren;
|
|
289
|
+
let builtPath = "";
|
|
290
|
+
let containsCollection = false;
|
|
291
|
+
for (const segment of segments) {
|
|
292
|
+
const relation = currentMeta.relations?.[segment];
|
|
293
|
+
if (!relation) throw new Error(`Unknown relation path '${relationPath}' for table '${currentMeta.table}'.`);
|
|
294
|
+
if (segment in currentMeta.columns && relation.sourceKey !== segment) throw new Error(`Relation path '${relationPath}' collides with an existing field on table '${currentMeta.table}'.`);
|
|
295
|
+
if (relation.kind === InternalRelationKind.MANY_TO_MANY) throw new Error(`Relation path '${relationPath}' uses unsupported many-to-many hydration.`);
|
|
296
|
+
if (!relation.capabilities.queryable || !relation.capabilities.hydratable) throw new Error(`Relation path '${relationPath}' cannot be hydrated.`);
|
|
297
|
+
if (mode === "select") {
|
|
298
|
+
if (relation.cardinality !== InternalRelationHydrationCardinality.SINGLE || !relation.capabilities.joinable) throw new Error(`Relation path '${relationPath}' cannot be loaded with selectRelated(...).`);
|
|
299
|
+
} else if (relation.cardinality === InternalRelationHydrationCardinality.MANY) {
|
|
300
|
+
if (!relation.capabilities.prefetchable) throw new Error(`Relation path '${relationPath}' cannot be loaded with prefetchRelated(...).`);
|
|
301
|
+
containsCollection = true;
|
|
302
|
+
} else if (!relation.capabilities.joinable) throw new Error(`Relation path '${relationPath}' cannot be loaded with prefetchRelated(...).`);
|
|
303
|
+
const targetMeta = relation.targetMeta;
|
|
304
|
+
if (!targetMeta) throw new Error(`Relation path '${relationPath}' is missing target metadata.`);
|
|
305
|
+
builtPath = builtPath.length > 0 ? `${builtPath}__${segment}` : segment;
|
|
306
|
+
const existing = currentChildren.get(segment);
|
|
307
|
+
const nextNode = existing ?? {
|
|
308
|
+
segment,
|
|
309
|
+
relationEdge: relation,
|
|
310
|
+
relationPath: builtPath,
|
|
311
|
+
targetMeta,
|
|
312
|
+
provenance: new Set(),
|
|
313
|
+
children: new Map()
|
|
314
|
+
};
|
|
315
|
+
nextNode.provenance.add(relationPath);
|
|
316
|
+
currentChildren.set(segment, nextNode);
|
|
317
|
+
currentChildren = nextNode.children;
|
|
318
|
+
currentMeta = targetMeta;
|
|
319
|
+
}
|
|
320
|
+
if (mode === "prefetch" && !containsCollection) throw new Error(`Relation path '${relationPath}' cannot be loaded with prefetchRelated(...).`);
|
|
321
|
+
}
|
|
322
|
+
buildPlannedChildren(children) {
|
|
323
|
+
const joinNodes = [];
|
|
324
|
+
const prefetchNodes = [];
|
|
325
|
+
for (const child of children.values()) {
|
|
326
|
+
const { joinNodes: joinChildren, prefetchNodes: prefetchChildren } = this.buildPlannedChildren(child.children);
|
|
327
|
+
const plannedNode = {
|
|
328
|
+
nodeId: child.relationPath,
|
|
329
|
+
relationName: child.segment,
|
|
330
|
+
relationPath: child.relationPath,
|
|
331
|
+
ownerModelKey: child.relationEdge.sourceModelKey,
|
|
332
|
+
relationEdge: child.relationEdge,
|
|
333
|
+
targetModelKey: child.relationEdge.targetModelKey,
|
|
334
|
+
loadMode: child.relationEdge.cardinality === InternalRelationHydrationCardinality.SINGLE ? InternalRelationHydrationLoadMode.JOIN : InternalRelationHydrationLoadMode.PREFETCH,
|
|
335
|
+
cardinality: child.relationEdge.cardinality,
|
|
336
|
+
provenance: [...child.provenance],
|
|
337
|
+
joinChildren,
|
|
338
|
+
prefetchChildren
|
|
339
|
+
};
|
|
340
|
+
if (plannedNode.loadMode === InternalRelationHydrationLoadMode.JOIN) joinNodes.push(plannedNode);
|
|
341
|
+
else prefetchNodes.push(plannedNode);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
joinNodes,
|
|
345
|
+
prefetchNodes
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
182
350
|
//#endregion
|
|
183
351
|
//#region src/query/compiler/QueryCompiler.ts
|
|
184
352
|
const sqlSafetyAdapter = new OrmSqlSafetyAdapter();
|
|
185
353
|
var QueryCompiler = class QueryCompiler {
|
|
186
354
|
static BRAND = "tango.orm.query_compiler";
|
|
187
355
|
__tangoBrand = QueryCompiler.BRAND;
|
|
188
|
-
/**
|
|
189
|
-
* Build a compiler for the given repository metadata and SQL dialect.
|
|
190
|
-
*/
|
|
191
356
|
constructor(meta, dialect = InternalDialect.POSTGRES) {
|
|
192
357
|
this.meta = meta;
|
|
193
358
|
this.dialect = dialect;
|
|
194
359
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Narrow an unknown value to `QueryCompiler`.
|
|
197
|
-
*/
|
|
198
360
|
static isQueryCompiler(value) {
|
|
199
361
|
return typeof value === "object" && value !== null && value.__tangoBrand === QueryCompiler.BRAND;
|
|
200
362
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Compile a query state tree into a SQL statement and bound parameters.
|
|
203
|
-
*/
|
|
204
363
|
compile(state) {
|
|
205
|
-
const
|
|
206
|
-
const prefetchRelationNames = state.prefetchRelated ?? [];
|
|
207
|
-
const relationNames = [...new Set([...selectRelationNames, ...prefetchRelationNames])];
|
|
364
|
+
const hydrationPlan = new QueryPlanner(this.meta).plan(state);
|
|
208
365
|
const validatedPlan = sqlSafetyAdapter.validate({
|
|
209
366
|
kind: "select",
|
|
210
367
|
meta: this.meta,
|
|
211
368
|
selectFields: state.select?.map(String),
|
|
212
369
|
filterKeys: this.collectStateFilterKeys(state),
|
|
213
370
|
orderFields: state.order?.map((order) => String(order.by)),
|
|
214
|
-
relationNames
|
|
371
|
+
relationNames: []
|
|
215
372
|
});
|
|
216
373
|
const table = validatedPlan.meta.table;
|
|
217
374
|
const whereParts = [];
|
|
@@ -233,87 +390,188 @@ var QueryCompiler = class QueryCompiler {
|
|
|
233
390
|
params.push(...result.params);
|
|
234
391
|
}
|
|
235
392
|
});
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
const
|
|
393
|
+
const baseSelects = state.select?.length ? state.select.map((field) => validatedPlan.selectFields[String(field)]) : [`${table}.*`];
|
|
394
|
+
const joinCollection = {
|
|
395
|
+
selects: [],
|
|
396
|
+
joins: []
|
|
397
|
+
};
|
|
398
|
+
const hiddenRootAliases = [];
|
|
399
|
+
const compiledJoinNodes = hydrationPlan.joinNodes.map((node) => this.compileHydrationNode(node, {
|
|
400
|
+
rootTable: table,
|
|
401
|
+
ownerMeta: this.meta,
|
|
402
|
+
ownerAlias: table,
|
|
403
|
+
collectRootJoins: true,
|
|
404
|
+
rootSelectedFields: state.select?.map(String) ?? undefined,
|
|
405
|
+
hiddenRootAliases,
|
|
406
|
+
joinCollection
|
|
407
|
+
}));
|
|
408
|
+
const compiledPrefetchNodes = hydrationPlan.prefetchNodes.map((node) => this.compileHydrationNode(node, {
|
|
409
|
+
rootTable: table,
|
|
410
|
+
ownerMeta: this.meta,
|
|
411
|
+
ownerAlias: table,
|
|
412
|
+
collectRootJoins: false,
|
|
413
|
+
rootSelectedFields: state.select?.map(String) ?? undefined,
|
|
414
|
+
hiddenRootAliases,
|
|
415
|
+
joinCollection
|
|
416
|
+
}));
|
|
242
417
|
const select = [
|
|
243
|
-
|
|
244
|
-
...
|
|
245
|
-
...
|
|
418
|
+
...baseSelects,
|
|
419
|
+
...joinCollection.selects,
|
|
420
|
+
...this.buildRootHiddenSelects(compiledPrefetchNodes, table)
|
|
246
421
|
].join(", ");
|
|
247
|
-
const joins = selectRelationNames.map((rel) => {
|
|
248
|
-
const relation = validatedPlan.relations[rel];
|
|
249
|
-
if (relation.kind !== InternalRelationKind.BELONGS_TO && relation.kind !== InternalRelationKind.HAS_ONE) throw new Error(`Relation '${rel}' cannot be loaded with selectRelated(...).`);
|
|
250
|
-
return `LEFT JOIN ${relation.table} ${relation.alias} ON ${relation.alias}.${relation.targetKey} = ${table}.${relation.sourceKey}`;
|
|
251
|
-
}).filter(Boolean).join(" ");
|
|
252
|
-
for (const rel of prefetchRelationNames) {
|
|
253
|
-
const relation = validatedPlan.relations[rel];
|
|
254
|
-
if (relation?.kind !== InternalRelationKind.HAS_MANY) throw new Error(`Relation '${rel}' cannot be loaded with prefetchRelated(...).`);
|
|
255
|
-
}
|
|
256
422
|
const whereSQL = whereParts.length ? ` WHERE ${whereParts.join(" AND ")}` : "";
|
|
257
423
|
const orderSQL = ` ORDER BY ${state.order?.length ? state.order.map((order) => `${validatedPlan.orderFields[String(order.by)]} ${order.dir.toUpperCase()}`).join(", ") : `${table}.${validatedPlan.meta.pk} ASC`}`;
|
|
258
424
|
const limitSQL = state.limit ? ` LIMIT ${state.limit}` : "";
|
|
259
425
|
const offsetSQL = state.offset ? ` OFFSET ${state.offset}` : "";
|
|
260
|
-
const sql = `SELECT ${select} FROM ${table}${joins ? ` ${joins}` : ""}${whereSQL}${orderSQL}${limitSQL}${offsetSQL}`;
|
|
426
|
+
const sql = `SELECT ${select} FROM ${table}${joinCollection.joins.length ? ` ${joinCollection.joins.join(" ")}` : ""}${whereSQL}${orderSQL}${limitSQL}${offsetSQL}`;
|
|
427
|
+
const compiledHydrationPlan = compiledJoinNodes.length > 0 || compiledPrefetchNodes.length > 0 ? {
|
|
428
|
+
requestedPaths: hydrationPlan.requestedPaths,
|
|
429
|
+
hiddenRootAliases: [...new Set(hiddenRootAliases)],
|
|
430
|
+
joinNodes: compiledJoinNodes,
|
|
431
|
+
prefetchNodes: compiledPrefetchNodes
|
|
432
|
+
} : undefined;
|
|
261
433
|
return {
|
|
262
434
|
sql,
|
|
263
435
|
params,
|
|
264
|
-
|
|
265
|
-
const relation = validatedPlan.relations[relationName];
|
|
266
|
-
return {
|
|
267
|
-
relationName,
|
|
268
|
-
alias: relation.alias,
|
|
269
|
-
columns: Object.fromEntries(Object.keys(relation.targetColumns).map((column) => [column, this.buildHydrationColumnAlias(relation.alias, column)]))
|
|
270
|
-
};
|
|
271
|
-
}),
|
|
272
|
-
prefetches: prefetchRelationNames.map((relationName) => {
|
|
273
|
-
const relation = validatedPlan.relations[relationName];
|
|
274
|
-
const needsAlias = !!state.select?.length && !state.select.map(String).includes(relation.sourceKey);
|
|
275
|
-
return {
|
|
276
|
-
relationName,
|
|
277
|
-
sourceKey: relation.sourceKey,
|
|
278
|
-
sourceKeyAlias: needsAlias ? this.buildPrefetchSourceAlias(relation.sourceKey) : undefined,
|
|
279
|
-
table: relation.table,
|
|
280
|
-
targetKey: relation.targetKey,
|
|
281
|
-
targetColumns: relation.targetColumns
|
|
282
|
-
};
|
|
283
|
-
})
|
|
436
|
+
hydrationPlan: compiledHydrationPlan
|
|
284
437
|
};
|
|
285
438
|
}
|
|
286
|
-
|
|
287
|
-
* Compile the follow-up query used by `prefetchRelated(...)`.
|
|
288
|
-
*
|
|
289
|
-
* The base query cannot bind source values until after it has returned rows,
|
|
290
|
-
* but SQL rendering and validation still belong to the compiler.
|
|
291
|
-
*/
|
|
292
|
-
compilePrefetch(prefetch, sourceValues) {
|
|
293
|
-
const validatedPlan = sqlSafetyAdapter.validate({
|
|
294
|
-
kind: "select",
|
|
295
|
-
meta: this.meta,
|
|
296
|
-
relationNames: [prefetch.relationName]
|
|
297
|
-
});
|
|
298
|
-
const relation = validatedPlan.relations[prefetch.relationName];
|
|
299
|
-
const compiledTargetColumns = Object.keys(prefetch.targetColumns).sort();
|
|
300
|
-
const validatedTargetColumns = Object.keys(relation.targetColumns).sort();
|
|
301
|
-
const compiledMatchesValidated = prefetch.sourceKey === relation.sourceKey && prefetch.table === relation.table && prefetch.targetKey === relation.targetKey && compiledTargetColumns.length === validatedTargetColumns.length && compiledTargetColumns.every((column, index) => column === validatedTargetColumns[index] && prefetch.targetColumns[column] === relation.targetColumns[column]);
|
|
302
|
-
if (!compiledMatchesValidated) throw new Error(`Compiled prefetch metadata for relation '${prefetch.relationName}' failed validation.`);
|
|
303
|
-
const columns = Object.keys(relation.targetColumns);
|
|
439
|
+
compilePrefetch(node, sourceValues) {
|
|
304
440
|
const placeholders = this.dialect === InternalDialect.POSTGRES ? sourceValues.map((_, index) => `$${index + 1}`).join(", ") : sourceValues.map(() => "?").join(", ");
|
|
441
|
+
const validatedTarget = this.validatePrefetchTarget(node);
|
|
442
|
+
const baseAlias = this.buildPrefetchBaseAlias(node.relationPath);
|
|
443
|
+
const joinCollection = {
|
|
444
|
+
selects: [],
|
|
445
|
+
joins: []
|
|
446
|
+
};
|
|
447
|
+
for (const joinChild of node.joinChildren) this.collectNestedJoinSql(joinChild, baseAlias, validatedTarget.columns, joinCollection);
|
|
448
|
+
const baseSelects = Object.keys(validatedTarget.columns).map((column) => `${baseAlias}.${column} AS ${column}`);
|
|
305
449
|
return {
|
|
306
|
-
sql: `SELECT ${
|
|
450
|
+
sql: `SELECT ${[...baseSelects, ...joinCollection.selects].join(", ")} FROM ${validatedTarget.table} ${baseAlias}${joinCollection.joins.length ? ` ${joinCollection.joins.join(" ")}` : ""} WHERE ${baseAlias}.${validatedTarget.targetKey} IN (${placeholders}) ORDER BY ${baseAlias}.${validatedTarget.targetKey} ASC, ${baseAlias}.${validatedTarget.primaryKey} ASC`,
|
|
307
451
|
params: sourceValues,
|
|
308
|
-
targetKey:
|
|
309
|
-
targetColumns:
|
|
452
|
+
targetKey: validatedTarget.targetKey,
|
|
453
|
+
targetColumns: validatedTarget.columns
|
|
310
454
|
};
|
|
311
455
|
}
|
|
312
|
-
|
|
313
|
-
|
|
456
|
+
compileHydrationNode(node, context) {
|
|
457
|
+
const validatedRelation = this.validateHydrationRelation(context.ownerMeta, node.relationName);
|
|
458
|
+
const targetColumns = validatedRelation.targetColumns;
|
|
459
|
+
const targetMeta = node.relationEdge.targetMeta;
|
|
460
|
+
if (!targetMeta) throw new Error(`Relation path '${node.relationPath}' is missing target metadata.`);
|
|
461
|
+
const compiledJoinChildren = node.joinChildren.map((child) => this.compileHydrationNode(child, {
|
|
462
|
+
...context,
|
|
463
|
+
ownerMeta: targetMeta,
|
|
464
|
+
ownerAlias: this.buildJoinAlias(node.relationPath),
|
|
465
|
+
collectRootJoins: context.collectRootJoins
|
|
466
|
+
}));
|
|
467
|
+
const compiledPrefetchChildren = node.prefetchChildren.map((child) => this.compileHydrationNode(child, {
|
|
468
|
+
...context,
|
|
469
|
+
ownerMeta: targetMeta,
|
|
470
|
+
ownerAlias: this.buildJoinAlias(node.relationPath),
|
|
471
|
+
collectRootJoins: false
|
|
472
|
+
}));
|
|
473
|
+
let joinDescriptor;
|
|
474
|
+
if (node.loadMode === InternalRelationHydrationLoadMode.JOIN) {
|
|
475
|
+
joinDescriptor = {
|
|
476
|
+
alias: this.buildJoinAlias(node.relationPath),
|
|
477
|
+
columns: Object.fromEntries(Object.keys(targetColumns).map((column) => [column, this.buildHydrationColumnAlias(node.relationPath, column)]))
|
|
478
|
+
};
|
|
479
|
+
if (context.collectRootJoins) {
|
|
480
|
+
context.joinCollection.joins.push(`LEFT JOIN ${validatedRelation.table} ${joinDescriptor.alias} ON ${joinDescriptor.alias}.${validatedRelation.targetKey} = ${context.ownerAlias}.${validatedRelation.sourceKey}`);
|
|
481
|
+
context.joinCollection.selects.push(...Object.entries(joinDescriptor.columns).map(([column, alias]) => `${joinDescriptor.alias}.${column} AS ${alias}`));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const ownerSourceAccessor = node.loadMode === InternalRelationHydrationLoadMode.PREFETCH && context.collectRootJoins === false && context.ownerAlias === context.rootTable && context.rootSelectedFields?.length && !context.rootSelectedFields.includes(validatedRelation.sourceKey) ? this.buildPrefetchSourceAlias(node.relationPath, validatedRelation.sourceKey) : validatedRelation.sourceKey;
|
|
485
|
+
if (node.loadMode === InternalRelationHydrationLoadMode.PREFETCH && ownerSourceAccessor !== validatedRelation.sourceKey) context.hiddenRootAliases.push(ownerSourceAccessor);
|
|
486
|
+
return {
|
|
487
|
+
nodeId: node.nodeId,
|
|
488
|
+
relationName: node.relationName,
|
|
489
|
+
relationPath: node.relationPath,
|
|
490
|
+
ownerModelKey: node.ownerModelKey,
|
|
491
|
+
targetModelKey: node.targetModelKey,
|
|
492
|
+
loadMode: node.loadMode,
|
|
493
|
+
cardinality: node.cardinality,
|
|
494
|
+
sourceKey: validatedRelation.sourceKey,
|
|
495
|
+
ownerSourceAccessor,
|
|
496
|
+
targetKey: validatedRelation.targetKey,
|
|
497
|
+
targetTable: validatedRelation.table,
|
|
498
|
+
targetPrimaryKey: node.relationEdge.targetPrimaryKey,
|
|
499
|
+
targetColumns,
|
|
500
|
+
provenance: node.provenance,
|
|
501
|
+
joinChildren: compiledJoinChildren,
|
|
502
|
+
prefetchChildren: compiledPrefetchChildren,
|
|
503
|
+
join: joinDescriptor
|
|
504
|
+
};
|
|
314
505
|
}
|
|
315
|
-
|
|
316
|
-
return
|
|
506
|
+
validateHydrationRelation(ownerMeta, relationName) {
|
|
507
|
+
return sqlSafetyAdapter.validate({
|
|
508
|
+
kind: "select",
|
|
509
|
+
meta: ownerMeta,
|
|
510
|
+
relationNames: [relationName]
|
|
511
|
+
}).relations[relationName];
|
|
512
|
+
}
|
|
513
|
+
buildRootHiddenSelects(nodes, table) {
|
|
514
|
+
return nodes.flatMap((node) => {
|
|
515
|
+
const select = node.ownerSourceAccessor !== node.sourceKey ? [`${table}.${node.sourceKey} AS ${node.ownerSourceAccessor}`] : [];
|
|
516
|
+
return [...select, ...this.buildRootHiddenSelects(node.prefetchChildren, table)];
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
validatePrefetchTarget(node) {
|
|
520
|
+
try {
|
|
521
|
+
const validated = sqlSafetyAdapter.validate({
|
|
522
|
+
kind: "select",
|
|
523
|
+
meta: {
|
|
524
|
+
table: node.targetTable,
|
|
525
|
+
pk: node.targetPrimaryKey,
|
|
526
|
+
columns: node.targetColumns
|
|
527
|
+
},
|
|
528
|
+
filterKeys: [node.targetKey]
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
table: validated.meta.table,
|
|
532
|
+
primaryKey: validated.meta.pk,
|
|
533
|
+
targetKey: validated.filterKeys[node.targetKey].field,
|
|
534
|
+
columns: validated.meta.columns
|
|
535
|
+
};
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const message = isError(error) ? error.message : String(error);
|
|
538
|
+
throw new Error(`Compiled prefetch query failed validation: ${message}`, { cause: error });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
collectNestedJoinSql(node, ownerAlias, ownerColumns, collection) {
|
|
542
|
+
if (!node.join) return;
|
|
543
|
+
const validatedTarget = this.validatePrefetchJoinTarget(node, ownerColumns);
|
|
544
|
+
const validatedJoinAlias = this.validateInternalAlias(node.join.alias);
|
|
545
|
+
const validatedJoinColumns = Object.fromEntries(Object.entries(node.join.columns).map(([column, alias]) => {
|
|
546
|
+
if (!(column in validatedTarget.columns)) throw new Error(`Compiled prefetch query failed validation: unknown nested join column '${column}'.`);
|
|
547
|
+
return [column, this.validateInternalAlias(alias)];
|
|
548
|
+
}));
|
|
549
|
+
collection.joins.push(`LEFT JOIN ${validatedTarget.table} ${validatedJoinAlias} ON ${validatedJoinAlias}.${validatedTarget.targetKey} = ${ownerAlias}.${node.sourceKey}`);
|
|
550
|
+
collection.selects.push(...Object.entries(validatedJoinColumns).map(([column, alias]) => `${validatedJoinAlias}.${column} AS ${alias}`));
|
|
551
|
+
for (const child of node.joinChildren) this.collectNestedJoinSql(child, validatedJoinAlias, validatedTarget.columns, collection);
|
|
552
|
+
}
|
|
553
|
+
validatePrefetchJoinTarget(node, ownerColumns) {
|
|
554
|
+
if (!(node.sourceKey in ownerColumns)) throw new Error(`Compiled prefetch query failed validation: unknown owner column '${node.sourceKey}' for nested join.`);
|
|
555
|
+
return this.validatePrefetchTarget(node);
|
|
556
|
+
}
|
|
557
|
+
validateInternalAlias(alias) {
|
|
558
|
+
if (!/^__tango_[A-Za-z0-9_]+$/.test(alias)) throw new Error(`Compiled prefetch query failed validation: invalid internal alias '${alias}'.`);
|
|
559
|
+
return alias;
|
|
560
|
+
}
|
|
561
|
+
buildJoinAlias(relationPath) {
|
|
562
|
+
return this.assertInternalAliasDoesNotCollide(`__tango_join_${this.sanitizeRelationPath(relationPath)}`);
|
|
563
|
+
}
|
|
564
|
+
buildPrefetchBaseAlias(relationPath) {
|
|
565
|
+
return this.assertInternalAliasDoesNotCollide(`__tango_prefetch_base_${this.sanitizeRelationPath(relationPath)}`);
|
|
566
|
+
}
|
|
567
|
+
buildHydrationColumnAlias(relationPath, column) {
|
|
568
|
+
return this.assertInternalAliasDoesNotCollide(`__tango_hydrate_${this.sanitizeRelationPath(relationPath)}_${column}`);
|
|
569
|
+
}
|
|
570
|
+
buildPrefetchSourceAlias(relationPath, sourceKey) {
|
|
571
|
+
return this.assertInternalAliasDoesNotCollide(`__tango_prefetch_${this.sanitizeRelationPath(relationPath)}_${sourceKey}`);
|
|
572
|
+
}
|
|
573
|
+
sanitizeRelationPath(relationPath) {
|
|
574
|
+
return relationPath.replace(/[^a-zA-Z0-9]+/g, "_");
|
|
317
575
|
}
|
|
318
576
|
assertInternalAliasDoesNotCollide(alias) {
|
|
319
577
|
if (alias in this.meta.columns) throw new Error(`Internal query alias '${alias}' collides with a field on table '${this.meta.table}'.`);
|
|
@@ -497,17 +755,13 @@ var QueryCompiler = class QueryCompiler {
|
|
|
497
755
|
var compiler_exports = {};
|
|
498
756
|
__export(compiler_exports, { QueryCompiler: () => QueryCompiler });
|
|
499
757
|
|
|
500
|
-
//#endregion
|
|
501
|
-
//#region src/query/domain/RelationTyping.ts
|
|
502
|
-
const InternalRelationHydrationCardinality = {
|
|
503
|
-
SINGLE: "single",
|
|
504
|
-
MANY: "many"
|
|
505
|
-
};
|
|
506
|
-
|
|
507
758
|
//#endregion
|
|
508
759
|
//#region src/query/domain/index.ts
|
|
509
760
|
var domain_exports = {};
|
|
510
|
-
__export(domain_exports, {
|
|
761
|
+
__export(domain_exports, {
|
|
762
|
+
InternalRelationHydrationCardinality: () => InternalRelationHydrationCardinality,
|
|
763
|
+
TableMetaFactory: () => TableMetaFactory
|
|
764
|
+
});
|
|
511
765
|
|
|
512
766
|
//#endregion
|
|
513
767
|
//#region src/query/domain/internal/InternalDirection.ts
|
|
@@ -655,12 +909,13 @@ var QuerySet = class QuerySet {
|
|
|
655
909
|
});
|
|
656
910
|
}
|
|
657
911
|
/**
|
|
658
|
-
* Hydrate single-valued
|
|
912
|
+
* Hydrate single-valued relation paths through SQL joins.
|
|
659
913
|
*
|
|
660
914
|
* Forward `belongsTo` relations can be inferred from the source model's
|
|
661
915
|
* field-authored relation metadata. Reverse `hasOne` relations can be
|
|
662
916
|
* selected with a target model generic when the target model points back to
|
|
663
|
-
* the source model.
|
|
917
|
+
* the source model. Generated relation typing also enables nested `__`
|
|
918
|
+
* path keys for applications that keep the app-local registry current.
|
|
664
919
|
*/
|
|
665
920
|
selectRelated(...rels) {
|
|
666
921
|
return new QuerySet(this.executor, {
|
|
@@ -669,10 +924,12 @@ var QuerySet = class QuerySet {
|
|
|
669
924
|
});
|
|
670
925
|
}
|
|
671
926
|
/**
|
|
672
|
-
* Hydrate collection
|
|
927
|
+
* Hydrate collection-rooted relation paths with follow-up queries.
|
|
673
928
|
*
|
|
674
929
|
* Reverse `hasMany` relations can be prefetched with a target model generic
|
|
675
|
-
* when the target model points back to the source model.
|
|
930
|
+
* when the target model points back to the source model. Generated relation
|
|
931
|
+
* typing also enables nested `__` path keys for applications that keep the
|
|
932
|
+
* app-local registry current.
|
|
676
933
|
*/
|
|
677
934
|
prefetchRelated(...rels) {
|
|
678
935
|
return new QuerySet(this.executor, {
|
|
@@ -681,7 +938,6 @@ var QuerySet = class QuerySet {
|
|
|
681
938
|
});
|
|
682
939
|
}
|
|
683
940
|
async fetch(shape) {
|
|
684
|
-
this.validateHydrationState();
|
|
685
941
|
const compiler = new QueryCompiler(this.executor.meta, this.executor.dialect);
|
|
686
942
|
const compiled = compiler.compile(this.state);
|
|
687
943
|
const rows = await this.executor.run(compiled);
|
|
@@ -703,9 +959,8 @@ var QuerySet = class QuerySet {
|
|
|
703
959
|
* Execute a `COUNT(*)` query for the current filtered state.
|
|
704
960
|
*/
|
|
705
961
|
async count() {
|
|
706
|
-
this.validateHydrationState();
|
|
707
962
|
const compiler = new QueryCompiler(this.executor.meta, this.executor.dialect);
|
|
708
|
-
const compiled = compiler.compile(this.
|
|
963
|
+
const compiled = compiler.compile(this.withoutHydrationState());
|
|
709
964
|
const countQuery = `SELECT COUNT(*) as count FROM (${compiled.sql}) AS tango_count_subquery`;
|
|
710
965
|
const rows = await this.executor.client.query(countQuery, compiled.params);
|
|
711
966
|
return Number(rows.rows[0]?.count ?? 0);
|
|
@@ -723,84 +978,96 @@ var QuerySet = class QuerySet {
|
|
|
723
978
|
if (booleanColumns.length === 0) return rows;
|
|
724
979
|
return rows.map((row) => this.normalizeBooleanColumns(row, booleanColumns));
|
|
725
980
|
}
|
|
726
|
-
validateHydrationState() {
|
|
727
|
-
const seen = new Set();
|
|
728
|
-
for (const relationName of [...this.state.selectRelated ?? [], ...this.state.prefetchRelated ?? []]) {
|
|
729
|
-
if (seen.has(relationName)) throw new Error(`Relation '${relationName}' was requested more than once.`);
|
|
730
|
-
seen.add(relationName);
|
|
731
|
-
const relation = this.executor.meta.relations?.[relationName];
|
|
732
|
-
if (!relation) throw new Error(`Unknown relation '${relationName}' for table '${this.executor.meta.table}'.`);
|
|
733
|
-
if (relation.kind === InternalRelationKind.MANY_TO_MANY) throw new Error(`Relation '${relationName}' is many-to-many and cannot be hydrated yet.`);
|
|
734
|
-
if (relationName in this.executor.meta.columns && !(relation.kind === InternalRelationKind.BELONGS_TO && relationName === relation.sourceKey)) throw new Error(`Relation '${relationName}' on table '${this.executor.meta.table}' collides with an existing field.`);
|
|
735
|
-
}
|
|
736
|
-
for (const relationName of this.state.selectRelated ?? []) {
|
|
737
|
-
const relation = this.executor.meta.relations[relationName];
|
|
738
|
-
if (relation.kind !== InternalRelationKind.BELONGS_TO && relation.kind !== InternalRelationKind.HAS_ONE) throw new Error(`Relation '${relationName}' cannot be loaded with selectRelated(...).`);
|
|
739
|
-
}
|
|
740
|
-
for (const relationName of this.state.prefetchRelated ?? []) {
|
|
741
|
-
const relation = this.executor.meta.relations[relationName];
|
|
742
|
-
if (relation.kind !== InternalRelationKind.HAS_MANY) throw new Error(`Relation '${relationName}' cannot be loaded with prefetchRelated(...).`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
981
|
async hydrateRows(rows, compiled) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
let hasTargetValue = false;
|
|
757
|
-
for (const [column, alias] of Object.entries(hydration.columns)) {
|
|
758
|
-
const value = next[alias];
|
|
759
|
-
delete next[alias];
|
|
760
|
-
target[column] = this.normalizeTargetValue(hydration.relationName, column, value);
|
|
761
|
-
if (value !== null && value !== undefined) hasTargetValue = true;
|
|
762
|
-
}
|
|
763
|
-
next[hydration.relationName] = hasTargetValue ? target : null;
|
|
764
|
-
}
|
|
765
|
-
return next;
|
|
766
|
-
});
|
|
982
|
+
if (!compiled.hydrationPlan) return rows;
|
|
983
|
+
const hydratedRows = rows.map((row) => ({ ...row }));
|
|
984
|
+
const canonicalEntities = new Map();
|
|
985
|
+
const queuedJoinPrefetchOwners = new Map();
|
|
986
|
+
const compiler = new QueryCompiler(this.executor.meta, this.executor.dialect);
|
|
987
|
+
for (const row of hydratedRows) this.hydrateJoinNodesForOwner(row, row, compiled.hydrationPlan.joinNodes, canonicalEntities, queuedJoinPrefetchOwners);
|
|
988
|
+
for (const node of compiled.hydrationPlan.prefetchNodes) await this.hydratePrefetchNode(node, hydratedRows, canonicalEntities, compiler);
|
|
989
|
+
for (const [node, owners] of queuedJoinPrefetchOwners.entries()) await this.hydratePrefetchNode(node, [...owners], canonicalEntities, compiler);
|
|
990
|
+
for (const row of hydratedRows) for (const alias of compiled.hydrationPlan.hiddenRootAliases) delete row[alias];
|
|
991
|
+
return hydratedRows;
|
|
767
992
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const hiddenSourceAliases = new Set(compiled.prefetches.map((prefetch) => prefetch.sourceKeyAlias).filter((alias) => typeof alias === "string"));
|
|
779
|
-
return rows.map((row) => {
|
|
780
|
-
const next = { ...row };
|
|
781
|
-
for (const { prefetch, grouped } of prefetchGroups) {
|
|
782
|
-
const sourceValue = row[prefetch.sourceKeyAlias ?? prefetch.sourceKey];
|
|
783
|
-
next[prefetch.relationName] = typeof sourceValue === "string" || typeof sourceValue === "number" ? grouped.get(sourceValue) ?? [] : [];
|
|
993
|
+
hydrateJoinNodesForOwner(owner, rawRow, nodes, canonicalEntities, queuedJoinPrefetchOwners) {
|
|
994
|
+
for (const node of nodes) {
|
|
995
|
+
if (!node.join) continue;
|
|
996
|
+
const target = {};
|
|
997
|
+
let hasTargetValue = false;
|
|
998
|
+
for (const [column, alias] of Object.entries(node.join.columns)) {
|
|
999
|
+
const value = rawRow[alias];
|
|
1000
|
+
delete rawRow[alias];
|
|
1001
|
+
target[column] = this.normalizeColumnValue(node.targetColumns[column], value);
|
|
1002
|
+
if (value !== null && value !== undefined) hasTargetValue = true;
|
|
784
1003
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1004
|
+
if (!hasTargetValue) {
|
|
1005
|
+
owner[node.relationName] = null;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const canonical = this.canonicalizeEntity(node, target, canonicalEntities);
|
|
1009
|
+
owner[node.relationName] = canonical;
|
|
1010
|
+
for (const childNode of node.prefetchChildren) {
|
|
1011
|
+
const queuedOwners = queuedJoinPrefetchOwners?.get(childNode);
|
|
1012
|
+
if (queuedOwners) {
|
|
1013
|
+
queuedOwners.add(canonical);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
queuedJoinPrefetchOwners?.set(childNode, new Set([canonical]));
|
|
1017
|
+
}
|
|
1018
|
+
this.hydrateJoinNodesForOwner(canonical, rawRow, node.joinChildren, canonicalEntities, queuedJoinPrefetchOwners);
|
|
1019
|
+
}
|
|
788
1020
|
}
|
|
789
|
-
async
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
1021
|
+
async hydratePrefetchNode(node, owners, canonicalEntities, compiler) {
|
|
1022
|
+
if (owners.length === 0) return;
|
|
1023
|
+
const groupedOwners = this.groupOwnersByAccessor(owners, node.ownerSourceAccessor);
|
|
1024
|
+
const sourceValues = [...groupedOwners.keys()];
|
|
1025
|
+
for (const owner of owners) owner[node.relationName] = node.cardinality === InternalRelationHydrationCardinality.MANY ? [] : null;
|
|
1026
|
+
if (sourceValues.length === 0) return;
|
|
1027
|
+
const compiledPrefetch = compiler.compilePrefetch(node, sourceValues);
|
|
793
1028
|
const result = await this.executor.client.query(compiledPrefetch.sql, compiledPrefetch.params);
|
|
794
|
-
|
|
795
|
-
|
|
1029
|
+
const groupedTargets = new Map();
|
|
1030
|
+
const canonicalChildren = new Map();
|
|
1031
|
+
for (const rawResultRow of result.rows) {
|
|
1032
|
+
const normalized = this.normalizeTargetRow(compiledPrefetch, rawResultRow);
|
|
1033
|
+
const canonical = this.canonicalizeEntity(node, normalized, canonicalEntities);
|
|
1034
|
+
this.hydrateJoinNodesForOwner(canonical, normalized, node.joinChildren, canonicalEntities);
|
|
796
1035
|
const key = normalized[compiledPrefetch.targetKey];
|
|
797
1036
|
if (typeof key !== "string" && typeof key !== "number") continue;
|
|
1037
|
+
const bucket = groupedTargets.get(key) ?? [];
|
|
1038
|
+
bucket.push(canonical);
|
|
1039
|
+
groupedTargets.set(key, bucket);
|
|
1040
|
+
const childPrimaryKey = canonical[node.targetPrimaryKey];
|
|
1041
|
+
if (typeof childPrimaryKey === "string" || typeof childPrimaryKey === "number") canonicalChildren.set(childPrimaryKey, canonical);
|
|
1042
|
+
}
|
|
1043
|
+
for (const [sourceValue, grouped] of groupedTargets.entries()) for (const owner of groupedOwners.get(sourceValue) ?? []) owner[node.relationName] = node.cardinality === InternalRelationHydrationCardinality.MANY ? grouped : grouped[0];
|
|
1044
|
+
const childOwners = [...canonicalChildren.values()];
|
|
1045
|
+
for (const childNode of node.prefetchChildren) await this.hydratePrefetchNode(childNode, childOwners, canonicalEntities, compiler);
|
|
1046
|
+
}
|
|
1047
|
+
groupOwnersByAccessor(owners, accessor) {
|
|
1048
|
+
const grouped = new Map();
|
|
1049
|
+
for (const owner of owners) {
|
|
1050
|
+
const key = owner[accessor];
|
|
1051
|
+
if (typeof key !== "string" && typeof key !== "number") continue;
|
|
798
1052
|
const bucket = grouped.get(key) ?? [];
|
|
799
|
-
bucket.push(
|
|
1053
|
+
bucket.push(owner);
|
|
800
1054
|
grouped.set(key, bucket);
|
|
801
1055
|
}
|
|
802
1056
|
return grouped;
|
|
803
1057
|
}
|
|
1058
|
+
canonicalizeEntity(node, row, canonicalEntities) {
|
|
1059
|
+
const primaryKeyValue = row[node.targetPrimaryKey];
|
|
1060
|
+
if (typeof primaryKeyValue !== "string" && typeof primaryKeyValue !== "number") return row;
|
|
1061
|
+
const byModel = canonicalEntities.get(node.targetModelKey) ?? new Map();
|
|
1062
|
+
const existing = byModel.get(primaryKeyValue);
|
|
1063
|
+
if (existing) {
|
|
1064
|
+
Object.assign(existing, row);
|
|
1065
|
+
return existing;
|
|
1066
|
+
}
|
|
1067
|
+
byModel.set(primaryKeyValue, row);
|
|
1068
|
+
canonicalEntities.set(node.targetModelKey, byModel);
|
|
1069
|
+
return row;
|
|
1070
|
+
}
|
|
804
1071
|
normalizeTargetRow(prefetch, row) {
|
|
805
1072
|
if (this.executor.dialect !== InternalDialect.SQLITE) return row;
|
|
806
1073
|
let normalized = null;
|
|
@@ -813,10 +1080,8 @@ var QuerySet = class QuerySet {
|
|
|
813
1080
|
}
|
|
814
1081
|
return normalized ?? row;
|
|
815
1082
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const relation = this.executor.meta.relations[relationName];
|
|
819
|
-
return this.isBooleanColumnType(relation.targetColumns[column]) ? this.normalizeSqliteBoolean(value) : value;
|
|
1083
|
+
normalizeColumnValue(columnType, value) {
|
|
1084
|
+
return this.executor.dialect === InternalDialect.SQLITE && this.isBooleanColumnType(columnType) ? this.normalizeSqliteBoolean(value) : value;
|
|
820
1085
|
}
|
|
821
1086
|
isBooleanColumnType(value) {
|
|
822
1087
|
return typeof value === "string" && ["bool", "boolean"].includes(value.trim().toLowerCase());
|
|
@@ -837,6 +1102,10 @@ var QuerySet = class QuerySet {
|
|
|
837
1102
|
}
|
|
838
1103
|
return normalized ?? row;
|
|
839
1104
|
}
|
|
1105
|
+
withoutHydrationState() {
|
|
1106
|
+
const { selectRelated: _selectRelated, prefetchRelated: _prefetchRelated,...rest } = this.state;
|
|
1107
|
+
return rest;
|
|
1108
|
+
}
|
|
840
1109
|
};
|
|
841
1110
|
|
|
842
1111
|
//#endregion
|
|
@@ -852,5 +1121,5 @@ __export(query_exports, {
|
|
|
852
1121
|
});
|
|
853
1122
|
|
|
854
1123
|
//#endregion
|
|
855
|
-
export {
|
|
856
|
-
//# sourceMappingURL=query-
|
|
1124
|
+
export { OrmSqlSafetyAdapter, QBuilder, QueryCompiler, QuerySet, TableMetaFactory, compiler_exports, domain_exports, query_exports };
|
|
1125
|
+
//# sourceMappingURL=query-DYiJ5m_B.js.map
|