metal-orm 1.0.56 → 1.0.58

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.
Files changed (82) hide show
  1. package/README.md +41 -33
  2. package/dist/index.cjs +1461 -195
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +541 -114
  5. package/dist/index.d.ts +541 -114
  6. package/dist/index.js +1424 -195
  7. package/dist/index.js.map +1 -1
  8. package/package.json +69 -69
  9. package/src/codegen/naming-strategy.ts +3 -1
  10. package/src/codegen/typescript.ts +20 -10
  11. package/src/core/ast/aggregate-functions.ts +14 -0
  12. package/src/core/ast/builders.ts +38 -20
  13. package/src/core/ast/expression-builders.ts +70 -2
  14. package/src/core/ast/expression-nodes.ts +305 -274
  15. package/src/core/ast/expression-visitor.ts +11 -1
  16. package/src/core/ast/expression.ts +4 -0
  17. package/src/core/ast/query.ts +3 -0
  18. package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
  19. package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
  20. package/src/core/ddl/introspect/functions/mssql.ts +13 -0
  21. package/src/core/ddl/introspect/mssql.ts +4 -0
  22. package/src/core/ddl/introspect/mysql.ts +4 -0
  23. package/src/core/ddl/introspect/sqlite.ts +4 -0
  24. package/src/core/dialect/abstract.ts +552 -531
  25. package/src/core/dialect/base/function-table-formatter.ts +9 -30
  26. package/src/core/dialect/base/sql-dialect.ts +24 -0
  27. package/src/core/dialect/mssql/functions.ts +40 -2
  28. package/src/core/dialect/mysql/functions.ts +16 -2
  29. package/src/core/dialect/postgres/functions.ts +66 -2
  30. package/src/core/dialect/postgres/index.ts +17 -4
  31. package/src/core/dialect/postgres/table-functions.ts +27 -0
  32. package/src/core/dialect/sqlite/functions.ts +34 -0
  33. package/src/core/dialect/sqlite/index.ts +17 -1
  34. package/src/core/driver/database-driver.ts +9 -1
  35. package/src/core/driver/mssql-driver.ts +3 -0
  36. package/src/core/driver/mysql-driver.ts +3 -0
  37. package/src/core/driver/postgres-driver.ts +3 -0
  38. package/src/core/driver/sqlite-driver.ts +3 -0
  39. package/src/core/execution/executors/mssql-executor.ts +5 -0
  40. package/src/core/execution/executors/mysql-executor.ts +5 -0
  41. package/src/core/execution/executors/postgres-executor.ts +5 -0
  42. package/src/core/execution/executors/sqlite-executor.ts +5 -0
  43. package/src/core/functions/array.ts +26 -0
  44. package/src/core/functions/control-flow.ts +69 -0
  45. package/src/core/functions/datetime.ts +50 -0
  46. package/src/core/functions/definitions/aggregate.ts +16 -0
  47. package/src/core/functions/definitions/control-flow.ts +24 -0
  48. package/src/core/functions/definitions/datetime.ts +36 -0
  49. package/src/core/functions/definitions/helpers.ts +29 -0
  50. package/src/core/functions/definitions/json.ts +49 -0
  51. package/src/core/functions/definitions/numeric.ts +55 -0
  52. package/src/core/functions/definitions/string.ts +43 -0
  53. package/src/core/functions/function-registry.ts +48 -0
  54. package/src/core/functions/group-concat-helpers.ts +57 -0
  55. package/src/core/functions/json.ts +38 -0
  56. package/src/core/functions/numeric.ts +14 -0
  57. package/src/core/functions/standard-strategy.ts +86 -115
  58. package/src/core/functions/standard-table-strategy.ts +13 -0
  59. package/src/core/functions/table-types.ts +15 -0
  60. package/src/core/functions/text.ts +57 -0
  61. package/src/core/sql/sql.ts +59 -38
  62. package/src/decorators/bootstrap.ts +41 -4
  63. package/src/index.ts +18 -11
  64. package/src/orm/entity-meta.ts +6 -3
  65. package/src/orm/entity.ts +81 -14
  66. package/src/orm/execute.ts +87 -20
  67. package/src/orm/hydration-context.ts +10 -0
  68. package/src/orm/identity-map.ts +19 -0
  69. package/src/orm/interceptor-pipeline.ts +4 -0
  70. package/src/orm/lazy-batch.ts +237 -54
  71. package/src/orm/relations/belongs-to.ts +19 -2
  72. package/src/orm/relations/has-many.ts +23 -9
  73. package/src/orm/relations/has-one.ts +19 -2
  74. package/src/orm/relations/many-to-many.ts +59 -4
  75. package/src/orm/save-graph-types.ts +2 -2
  76. package/src/orm/save-graph.ts +18 -18
  77. package/src/query-builder/relation-conditions.ts +80 -59
  78. package/src/query-builder/relation-service.ts +399 -95
  79. package/src/query-builder/relation-types.ts +2 -2
  80. package/src/query-builder/select.ts +124 -106
  81. package/src/schema/table-guards.ts +6 -0
  82. package/src/schema/types.ts +109 -85
@@ -1,12 +1,21 @@
1
1
  import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column-types.js';
3
- import { RelationDef, RelationKinds, BelongsToManyRelation } from '../schema/relation.js';
4
- import { SelectQueryNode } from '../core/ast/query.js';
5
- import {
6
- ColumnNode,
7
- ExpressionNode,
8
- and
9
- } from '../core/ast/expression.js';
3
+ import {
4
+ RelationDef,
5
+ RelationKinds,
6
+ BelongsToManyRelation,
7
+ HasManyRelation,
8
+ HasOneRelation,
9
+ BelongsToRelation
10
+ } from '../schema/relation.js';
11
+ import { SelectQueryNode, TableSourceNode, TableNode, OrderingTerm } from '../core/ast/query.js';
12
+ import {
13
+ ColumnNode,
14
+ ExpressionNode,
15
+ OperandNode,
16
+ and,
17
+ isOperandNode
18
+ } from '../core/ast/expression.js';
10
19
  import { SelectQueryState } from './select-query-state.js';
11
20
  import { HydrationManager } from './hydration-manager.js';
12
21
  import { QueryAstService } from './query-ast-service.js';
@@ -19,16 +28,29 @@ import {
19
28
  buildBelongsToManyJoins
20
29
  } from './relation-conditions.js';
21
30
  import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
22
- import { RelationIncludeOptions } from './relation-types.js';
23
- import { createJoinNode } from '../core/ast/join-node.js';
24
- import { getJoinRelationName } from '../core/ast/join-metadata.js';
25
- import { makeRelationAlias } from './relation-alias.js';
26
- import { buildDefaultPivotColumns } from './relation-utils.js';
27
-
28
- /**
29
- * Service for handling relation operations (joins, includes, etc.)
30
- */
31
- export class RelationService {
31
+ import { RelationIncludeOptions } from './relation-types.js';
32
+ import { createJoinNode } from '../core/ast/join-node.js';
33
+ import { getJoinRelationName } from '../core/ast/join-metadata.js';
34
+ import { makeRelationAlias } from './relation-alias.js';
35
+ import { buildDefaultPivotColumns } from './relation-utils.js';
36
+
37
+ type FilterTableCollector = {
38
+ tables: Set<string>;
39
+ hasSubquery: boolean;
40
+ };
41
+
42
+ type RelationWithForeignKey =
43
+ | HasManyRelation
44
+ | HasOneRelation
45
+ | BelongsToRelation;
46
+
47
+ const hasRelationForeignKey = (relation: RelationDef): relation is RelationWithForeignKey =>
48
+ relation.type !== RelationKinds.BelongsToMany;
49
+
50
+ /**
51
+ * Service for handling relation operations (joins, includes, etc.)
52
+ */
53
+ export class RelationService {
32
54
  private readonly projectionHelper: RelationProjectionHelper;
33
55
 
34
56
  /**
@@ -55,14 +77,15 @@ export class RelationService {
55
77
  * @param extraCondition - Additional join condition
56
78
  * @returns Relation result with updated state and hydration
57
79
  */
58
- joinRelation(
59
- relationName: string,
60
- joinKind: JoinKind,
61
- extraCondition?: ExpressionNode
62
- ): RelationResult {
63
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
64
- return { state: nextState, hydration: this.hydration };
65
- }
80
+ joinRelation(
81
+ relationName: string,
82
+ joinKind: JoinKind,
83
+ extraCondition?: ExpressionNode,
84
+ tableSource?: TableSourceNode
85
+ ): RelationResult {
86
+ const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition, tableSource);
87
+ return { state: nextState, hydration: this.hydration };
88
+ }
66
89
 
67
90
  /**
68
91
  * Matches records based on a relation with an optional predicate
@@ -88,26 +111,73 @@ export class RelationService {
88
111
  * @param options - Options for relation inclusion
89
112
  * @returns Relation result with updated state and hydration
90
113
  */
91
- include(relationName: string, options?: RelationIncludeOptions): RelationResult {
92
- let state = this.state;
93
- let hydration = this.hydration;
94
-
95
- const relation = this.getRelation(relationName);
96
- const aliasPrefix = options?.aliasPrefix ?? relationName;
97
- const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
98
-
99
- if (!alreadyJoined) {
100
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
101
- state = joined.state;
102
- }
103
-
104
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
105
- state = projectionResult.state;
106
- hydration = projectionResult.hydration;
107
-
108
- const targetColumns = options?.columns?.length
109
- ? options.columns
110
- : Object.keys(relation.target.columns);
114
+ include(relationName: string, options?: RelationIncludeOptions): RelationResult {
115
+ let state = this.state;
116
+ let hydration = this.hydration;
117
+
118
+ const relation = this.getRelation(relationName);
119
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
120
+ const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
121
+ const { selfFilters, crossFilters } = this.splitFilterExpressions(
122
+ options?.filter,
123
+ new Set([relation.target.name])
124
+ );
125
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
126
+ const joinFilters = [...crossFilters];
127
+ if (!canUseCte) {
128
+ joinFilters.push(...selfFilters);
129
+ }
130
+ const joinCondition = this.combineWithAnd(joinFilters);
131
+
132
+ let tableSourceOverride: TableNode | undefined;
133
+ if (canUseCte) {
134
+ const cteInfo = this.createFilteredRelationCte(state, relationName, relation, selfFilters);
135
+ state = cteInfo.state;
136
+ tableSourceOverride = cteInfo.table;
137
+ }
138
+
139
+ if (!alreadyJoined) {
140
+ state = this.withJoin(
141
+ state,
142
+ relationName,
143
+ options?.joinKind ?? JOIN_KINDS.LEFT,
144
+ joinCondition,
145
+ tableSourceOverride
146
+ );
147
+ }
148
+
149
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
150
+ state = projectionResult.state;
151
+ hydration = projectionResult.hydration;
152
+
153
+ if (hasRelationForeignKey(relation)) {
154
+ const fkColumn = this.table.columns[relation.foreignKey];
155
+ if (fkColumn) {
156
+ const hasForeignKeySelected = state.ast.columns.some(col => {
157
+ if ((col as ColumnNode).type !== 'Column') return false;
158
+ const node = col as ColumnNode;
159
+ const alias = node.alias ?? node.name;
160
+ return alias === relation.foreignKey;
161
+ });
162
+
163
+ if (!hasForeignKeySelected) {
164
+ const fkSelectionResult = this.selectColumns(state, hydration, {
165
+ [relation.foreignKey]: fkColumn
166
+ });
167
+ state = fkSelectionResult.state;
168
+ hydration = fkSelectionResult.hydration;
169
+ }
170
+ }
171
+ }
172
+
173
+ const requestedColumns = options?.columns?.length
174
+ ? [...options.columns]
175
+ : Object.keys(relation.target.columns);
176
+ const targetPrimaryKey = findPrimaryKey(relation.target);
177
+ if (!requestedColumns.includes(targetPrimaryKey)) {
178
+ requestedColumns.push(targetPrimaryKey);
179
+ }
180
+ const targetColumns = requestedColumns;
111
181
 
112
182
  const buildTypedSelection = (
113
183
  columns: Record<string, ColumnDef>,
@@ -219,36 +289,52 @@ export class RelationService {
219
289
  * @param extraCondition - Additional join condition
220
290
  * @returns Updated query state with join
221
291
  */
222
- private withJoin(
223
- state: SelectQueryState,
224
- relationName: string,
225
- joinKind: JoinKind,
226
- extraCondition?: ExpressionNode
227
- ): SelectQueryState {
228
- const relation = this.getRelation(relationName);
229
- const rootAlias = state.ast.from.type === 'Table' ? state.ast.from.alias : undefined;
230
- if (relation.type === RelationKinds.BelongsToMany) {
231
- const joins = buildBelongsToManyJoins(
232
- this.table,
233
- relationName,
234
- relation as BelongsToManyRelation,
235
- joinKind,
236
- extraCondition,
237
- rootAlias
238
- );
239
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
240
- }
241
-
242
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
243
- const joinNode = createJoinNode(
244
- joinKind,
245
- { type: 'Table', name: relation.target.name, schema: relation.target.schema },
246
- condition,
247
- relationName
248
- );
249
-
250
- return this.astService(state).withJoin(joinNode);
251
- }
292
+ private withJoin(
293
+ state: SelectQueryState,
294
+ relationName: string,
295
+ joinKind: JoinKind,
296
+ extraCondition?: ExpressionNode,
297
+ tableSource?: TableSourceNode
298
+ ): SelectQueryState {
299
+ const relation = this.getRelation(relationName);
300
+ const rootAlias = state.ast.from.type === 'Table' ? state.ast.from.alias : undefined;
301
+ if (relation.type === RelationKinds.BelongsToMany) {
302
+ const targetTableSource: TableSourceNode = tableSource ?? {
303
+ type: 'Table',
304
+ name: relation.target.name,
305
+ schema: relation.target.schema
306
+ };
307
+ const targetName = this.resolveTargetTableName(targetTableSource, relation);
308
+ const joins = buildBelongsToManyJoins(
309
+ this.table,
310
+ relationName,
311
+ relation as BelongsToManyRelation,
312
+ joinKind,
313
+ extraCondition,
314
+ rootAlias,
315
+ targetTableSource,
316
+ targetName
317
+ );
318
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
319
+ }
320
+
321
+ const targetTable: TableSourceNode = tableSource ?? {
322
+ type: 'Table',
323
+ name: relation.target.name,
324
+ schema: relation.target.schema
325
+ };
326
+ const targetName = this.resolveTargetTableName(targetTable, relation);
327
+ const condition = buildRelationJoinCondition(
328
+ this.table,
329
+ relation,
330
+ extraCondition,
331
+ rootAlias,
332
+ targetName
333
+ );
334
+ const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
335
+
336
+ return this.astService(state).withJoin(joinNode);
337
+ }
252
338
 
253
339
  /**
254
340
  * Selects columns for a relation
@@ -257,18 +343,236 @@ export class RelationService {
257
343
  * @param columns - Columns to select
258
344
  * @returns Relation result with updated state and hydration
259
345
  */
260
- private selectColumns(
261
- state: SelectQueryState,
262
- hydration: HydrationManager,
263
- columns: Record<string, ColumnDef>
264
- ): RelationResult {
265
- const { state: nextState, addedColumns } = this.astService(state).select(columns);
266
- return {
267
- state: nextState,
268
- hydration: hydration.onColumnsSelected(nextState, addedColumns)
269
- };
270
- }
271
-
346
+ private selectColumns(
347
+ state: SelectQueryState,
348
+ hydration: HydrationManager,
349
+ columns: Record<string, ColumnDef>
350
+ ): RelationResult {
351
+ const { state: nextState, addedColumns } = this.astService(state).select(columns);
352
+ return {
353
+ state: nextState,
354
+ hydration: hydration.onColumnsSelected(nextState, addedColumns)
355
+ };
356
+ }
357
+
358
+
359
+ private combineWithAnd(expressions: ExpressionNode[]): ExpressionNode | undefined {
360
+ if (expressions.length === 0) return undefined;
361
+ if (expressions.length === 1) return expressions[0];
362
+ return {
363
+ type: 'LogicalExpression',
364
+ operator: 'AND',
365
+ operands: expressions
366
+ };
367
+ }
368
+
369
+ private splitFilterExpressions(
370
+ filter: ExpressionNode | undefined,
371
+ allowedTables: Set<string>
372
+ ): { selfFilters: ExpressionNode[]; crossFilters: ExpressionNode[] } {
373
+ const terms = this.flattenAnd(filter);
374
+ const selfFilters: ExpressionNode[] = [];
375
+ const crossFilters: ExpressionNode[] = [];
376
+
377
+ for (const term of terms) {
378
+ if (this.isExpressionSelfContained(term, allowedTables)) {
379
+ selfFilters.push(term);
380
+ } else {
381
+ crossFilters.push(term);
382
+ }
383
+ }
384
+
385
+ return { selfFilters, crossFilters };
386
+ }
387
+
388
+ private flattenAnd(node?: ExpressionNode): ExpressionNode[] {
389
+ if (!node) return [];
390
+ if (node.type === 'LogicalExpression' && node.operator === 'AND') {
391
+ return node.operands.flatMap(operand => this.flattenAnd(operand));
392
+ }
393
+ return [node];
394
+ }
395
+
396
+ private isExpressionSelfContained(expr: ExpressionNode, allowedTables: Set<string>): boolean {
397
+ const collector = this.collectReferencedTables(expr);
398
+ if (collector.hasSubquery) return false;
399
+ if (collector.tables.size === 0) return true;
400
+ for (const table of collector.tables) {
401
+ if (!allowedTables.has(table)) {
402
+ return false;
403
+ }
404
+ }
405
+ return true;
406
+ }
407
+
408
+ private collectReferencedTables(expr: ExpressionNode): FilterTableCollector {
409
+ const collector: FilterTableCollector = {
410
+ tables: new Set(),
411
+ hasSubquery: false
412
+ };
413
+ this.collectFromExpression(expr, collector);
414
+ return collector;
415
+ }
416
+
417
+ private collectFromExpression(expr: ExpressionNode, collector: FilterTableCollector): void {
418
+ switch (expr.type) {
419
+ case 'BinaryExpression':
420
+ this.collectFromOperand(expr.left, collector);
421
+ this.collectFromOperand(expr.right, collector);
422
+ break;
423
+ case 'LogicalExpression':
424
+ expr.operands.forEach(operand => this.collectFromExpression(operand, collector));
425
+ break;
426
+ case 'NullExpression':
427
+ this.collectFromOperand(expr.left, collector);
428
+ break;
429
+ case 'InExpression':
430
+ this.collectFromOperand(expr.left, collector);
431
+ if (Array.isArray(expr.right)) {
432
+ expr.right.forEach(value => this.collectFromOperand(value, collector));
433
+ } else {
434
+ collector.hasSubquery = true;
435
+ }
436
+ break;
437
+ case 'ExistsExpression':
438
+ collector.hasSubquery = true;
439
+ break;
440
+ case 'BetweenExpression':
441
+ this.collectFromOperand(expr.left, collector);
442
+ this.collectFromOperand(expr.lower, collector);
443
+ this.collectFromOperand(expr.upper, collector);
444
+ break;
445
+ case 'ArithmeticExpression':
446
+ case 'BitwiseExpression':
447
+ this.collectFromOperand(expr.left, collector);
448
+ this.collectFromOperand(expr.right, collector);
449
+ break;
450
+ default:
451
+ break;
452
+ }
453
+ }
454
+
455
+ private collectFromOperand(node: OperandNode, collector: FilterTableCollector): void {
456
+ switch (node.type) {
457
+ case 'Column':
458
+ collector.tables.add(node.table);
459
+ break;
460
+ case 'Function':
461
+ node.args.forEach(arg => this.collectFromOperand(arg, collector));
462
+ if (node.separator) {
463
+ this.collectFromOperand(node.separator, collector);
464
+ }
465
+ if (node.orderBy) {
466
+ node.orderBy.forEach(order => this.collectFromOrderingTerm(order.term, collector));
467
+ }
468
+ break;
469
+ case 'JsonPath':
470
+ this.collectFromOperand(node.column, collector);
471
+ break;
472
+ case 'ScalarSubquery':
473
+ collector.hasSubquery = true;
474
+ break;
475
+ case 'CaseExpression':
476
+ node.conditions.forEach(({ when, then }) => {
477
+ this.collectFromExpression(when, collector);
478
+ this.collectFromOperand(then, collector);
479
+ });
480
+ if (node.else) {
481
+ this.collectFromOperand(node.else, collector);
482
+ }
483
+ break;
484
+ case 'Cast':
485
+ this.collectFromOperand(node.expression, collector);
486
+ break;
487
+ case 'WindowFunction':
488
+ node.args.forEach(arg => this.collectFromOperand(arg, collector));
489
+ node.partitionBy?.forEach(part => this.collectFromOperand(part, collector));
490
+ node.orderBy?.forEach(order => this.collectFromOrderingTerm(order.term, collector));
491
+ break;
492
+ case 'Collate':
493
+ this.collectFromOperand(node.expression, collector);
494
+ break;
495
+ case 'ArithmeticExpression':
496
+ case 'BitwiseExpression':
497
+ this.collectFromOperand(node.left, collector);
498
+ this.collectFromOperand(node.right, collector);
499
+ break;
500
+ case 'Literal':
501
+ case 'AliasRef':
502
+ break;
503
+ default:
504
+ break;
505
+ }
506
+ }
507
+
508
+ private collectFromOrderingTerm(term: OrderingTerm, collector: FilterTableCollector): void {
509
+ if (isOperandNode(term)) {
510
+ this.collectFromOperand(term, collector);
511
+ return;
512
+ }
513
+ this.collectFromExpression(term, collector);
514
+ }
515
+
516
+ private createFilteredRelationCte(
517
+ state: SelectQueryState,
518
+ relationName: string,
519
+ relation: RelationDef,
520
+ filters: ExpressionNode[]
521
+ ): { state: SelectQueryState; table: TableNode } {
522
+ const cteName = this.generateUniqueCteName(state, relationName);
523
+ const predicate = this.combineWithAnd(filters);
524
+ if (!predicate) {
525
+ throw new Error('Unable to build filter CTE without predicates.');
526
+ }
527
+
528
+ const columns: ColumnNode[] = Object.keys(relation.target.columns).map(name => ({
529
+ type: 'Column',
530
+ table: relation.target.name,
531
+ name
532
+ }));
533
+
534
+ const cteQuery: SelectQueryNode = {
535
+ type: 'SelectQuery',
536
+ from: { type: 'Table', name: relation.target.name, schema: relation.target.schema },
537
+ columns,
538
+ joins: [],
539
+ where: predicate
540
+ };
541
+
542
+ const nextState = this.astService(state).withCte(cteName, cteQuery);
543
+ const tableNode: TableNode = {
544
+ type: 'Table',
545
+ name: cteName,
546
+ alias: relation.target.name
547
+ };
548
+
549
+ return { state: nextState, table: tableNode };
550
+ }
551
+
552
+ private generateUniqueCteName(state: SelectQueryState, relationName: string): string {
553
+ const existing = new Set((state.ast.ctes ?? []).map(cte => cte.name));
554
+ let candidate = `${relationName}__filtered`;
555
+ let suffix = 1;
556
+ while (existing.has(candidate)) {
557
+ candidate = `${relationName}__filtered_${suffix}`;
558
+ suffix += 1;
559
+ }
560
+ return candidate;
561
+ }
562
+
563
+ private resolveTargetTableName(target: TableSourceNode, relation: RelationDef): string {
564
+ if (target.type === 'Table') {
565
+ return target.alias ?? target.name;
566
+ }
567
+ if (target.type === 'DerivedTable') {
568
+ return target.alias;
569
+ }
570
+ if (target.type === 'FunctionTable') {
571
+ return target.alias ?? relation.target.name;
572
+ }
573
+ return relation.target.name;
574
+ }
575
+
272
576
  /**
273
577
  * Gets a relation definition by name
274
578
  * @param relationName - Name of the relation
@@ -293,12 +597,12 @@ export class RelationService {
293
597
  return this.createQueryAstService(this.table, state);
294
598
  }
295
599
 
296
- private rootTableName(): string {
297
- const from = this.state.ast.from;
298
- if (from.type === 'Table' && from.alias) return from.alias;
299
- return this.table.name;
300
- }
301
- }
302
-
303
- export type { RelationResult } from './relation-projection-helper.js';
600
+ private rootTableName(): string {
601
+ const from = this.state.ast.from;
602
+ if (from.type === 'Table' && from.alias) return from.alias;
603
+ return this.table.name;
604
+ }
605
+ }
606
+
607
+ export type { RelationResult } from './relation-projection-helper.js';
304
608
 
@@ -9,8 +9,8 @@ export type RelationIncludeJoinKind = typeof JOIN_KINDS.LEFT | typeof JOIN_KINDS
9
9
  /**
10
10
  * Options for including a relation in a query
11
11
  */
12
- export interface RelationIncludeOptions {
13
- columns?: string[];
12
+ export interface RelationIncludeOptions {
13
+ columns?: readonly string[];
14
14
  aliasPrefix?: string;
15
15
  filter?: ExpressionNode;
16
16
  joinKind?: RelationIncludeJoinKind;