metal-orm 1.0.43 → 1.0.45

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 (85) hide show
  1. package/README.md +700 -557
  2. package/dist/index.cjs +896 -476
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1146 -275
  5. package/dist/index.d.ts +1146 -275
  6. package/dist/index.js +896 -474
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/core/ast/adapters.ts +8 -2
  10. package/src/core/ast/builders.ts +105 -81
  11. package/src/core/ast/expression-builders.ts +430 -390
  12. package/src/core/ast/expression-visitor.ts +47 -8
  13. package/src/core/ast/helpers.ts +23 -0
  14. package/src/core/ast/join-node.ts +17 -1
  15. package/src/core/ddl/dialects/base-schema-dialect.ts +7 -1
  16. package/src/core/ddl/dialects/index.ts +1 -0
  17. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -0
  18. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -0
  19. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -0
  20. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -0
  21. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  22. package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
  23. package/src/core/ddl/introspect/context.ts +6 -0
  24. package/src/core/ddl/introspect/functions/postgres.ts +13 -0
  25. package/src/core/ddl/introspect/mssql.ts +11 -0
  26. package/src/core/ddl/introspect/mysql.ts +2 -0
  27. package/src/core/ddl/introspect/postgres.ts +14 -0
  28. package/src/core/ddl/introspect/registry.ts +14 -0
  29. package/src/core/ddl/introspect/run-select.ts +13 -0
  30. package/src/core/ddl/introspect/sqlite.ts +22 -0
  31. package/src/core/ddl/introspect/utils.ts +18 -0
  32. package/src/core/ddl/naming-strategy.ts +6 -0
  33. package/src/core/ddl/schema-dialect.ts +19 -6
  34. package/src/core/ddl/schema-diff.ts +22 -0
  35. package/src/core/ddl/schema-generator.ts +22 -0
  36. package/src/core/ddl/schema-plan-executor.ts +6 -0
  37. package/src/core/ddl/schema-types.ts +6 -0
  38. package/src/core/dialect/abstract.ts +2 -2
  39. package/src/core/execution/pooling/pool.ts +12 -7
  40. package/src/core/functions/datetime.ts +57 -33
  41. package/src/core/functions/numeric.ts +95 -30
  42. package/src/core/functions/standard-strategy.ts +35 -0
  43. package/src/core/functions/text.ts +83 -22
  44. package/src/core/functions/types.ts +23 -8
  45. package/src/decorators/bootstrap.ts +16 -4
  46. package/src/decorators/column.ts +17 -0
  47. package/src/decorators/decorator-metadata.ts +27 -0
  48. package/src/decorators/entity.ts +8 -0
  49. package/src/decorators/index.ts +3 -0
  50. package/src/decorators/relations.ts +32 -0
  51. package/src/orm/als.ts +34 -9
  52. package/src/orm/entity-context.ts +54 -0
  53. package/src/orm/entity-metadata.ts +122 -9
  54. package/src/orm/execute.ts +15 -0
  55. package/src/orm/lazy-batch.ts +158 -98
  56. package/src/orm/relations/has-many.ts +44 -0
  57. package/src/orm/save-graph.ts +45 -0
  58. package/src/query/index.ts +74 -0
  59. package/src/query/target.ts +46 -0
  60. package/src/query-builder/delete-query-state.ts +30 -0
  61. package/src/query-builder/delete.ts +64 -19
  62. package/src/query-builder/hydration-manager.ts +46 -0
  63. package/src/query-builder/insert-query-state.ts +30 -0
  64. package/src/query-builder/insert.ts +46 -2
  65. package/src/query-builder/query-ast-service.ts +5 -0
  66. package/src/query-builder/query-resolution.ts +78 -0
  67. package/src/query-builder/raw-column-parser.ts +5 -0
  68. package/src/query-builder/relation-alias.ts +7 -0
  69. package/src/query-builder/relation-conditions.ts +61 -48
  70. package/src/query-builder/relation-service.ts +68 -63
  71. package/src/query-builder/relation-utils.ts +3 -0
  72. package/src/query-builder/select/cte-facet.ts +40 -0
  73. package/src/query-builder/select/from-facet.ts +80 -0
  74. package/src/query-builder/select/join-facet.ts +62 -0
  75. package/src/query-builder/select/predicate-facet.ts +103 -0
  76. package/src/query-builder/select/projection-facet.ts +69 -0
  77. package/src/query-builder/select/relation-facet.ts +81 -0
  78. package/src/query-builder/select/setop-facet.ts +36 -0
  79. package/src/query-builder/select-helpers.ts +13 -0
  80. package/src/query-builder/select-query-builder-deps.ts +19 -1
  81. package/src/query-builder/select-query-state.ts +2 -1
  82. package/src/query-builder/select.ts +795 -1163
  83. package/src/query-builder/update-query-state.ts +52 -0
  84. package/src/query-builder/update.ts +69 -19
  85. package/src/schema/table-guards.ts +31 -0
@@ -1,1163 +1,795 @@
1
- import { TableDef } from '../schema/table.js';
2
-
3
- import { ColumnDef } from '../schema/column.js';
4
-
5
- import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
6
-
7
- import { HydrationPlan } from '../core/hydration/types.js';
8
-
9
- import {
10
-
11
- ColumnNode,
12
-
13
- ExpressionNode,
14
-
15
- FunctionNode,
16
-
17
- LiteralNode,
18
-
19
- BinaryExpressionNode,
20
-
21
- CaseExpressionNode,
22
-
23
- WindowFunctionNode,
24
-
25
- and,
26
-
27
- exists,
28
-
29
- notExists
30
-
31
- } from '../core/ast/expression.js';
32
- import { derivedTable } from '../core/ast/builders.js';
33
-
34
- import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
35
-
36
- import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
37
-
38
-
39
-
40
- type SelectDialectInput = Dialect | DialectKey;
41
-
42
- import { SelectQueryState } from './select-query-state.js';
43
-
44
- import { HydrationManager } from './hydration-manager.js';
45
-
46
- import {
47
-
48
- resolveSelectQueryBuilderDependencies,
49
-
50
- SelectQueryBuilderContext,
51
-
52
- SelectQueryBuilderDependencies,
53
-
54
- SelectQueryBuilderEnvironment
55
-
56
- } from './select-query-builder-deps.js';
57
-
58
- import { QueryAstService } from './query-ast-service.js';
59
-
60
- import { ColumnSelector } from './column-selector.js';
61
-
62
- import { RelationManager } from './relation-manager.js';
63
-
64
- import { RelationIncludeOptions } from './relation-types.js';
65
-
66
- import type { RelationDef } from '../schema/relation.js';
67
-
68
- import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
69
-
70
- import { EntityInstance, RelationMap, RelationTargetTable } from '../schema/types.js';
71
-
72
- import { OrmSession } from '../orm/orm-session.ts';
73
-
74
- import { ExecutionContext } from '../orm/execution-context.js';
75
-
76
- import { HydrationContext } from '../orm/hydration-context.js';
77
-
78
- import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
79
-
80
- import { createJoinNode } from '../core/ast/join-node.js';
81
-
82
-
83
- type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
84
-
85
- type DeepSelectConfig<TTable extends TableDef> = {
86
- root?: (keyof TTable['columns'] & string)[];
87
- } & {
88
- [K in keyof TTable['relations'] & string]?: (
89
- keyof RelationTargetTable<TTable['relations'][K]>['columns'] & string
90
- )[];
91
- };
92
-
93
- type WhereHasOptions = {
94
- correlate?: ExpressionNode;
95
- };
96
-
97
- type RelationCallback = <TChildTable extends TableDef>(
98
- qb: SelectQueryBuilder<unknown, TChildTable>
99
- ) => SelectQueryBuilder<unknown, TChildTable>;
100
-
101
-
102
- /**
103
-
104
- * Main query builder class for constructing SQL SELECT queries
105
-
106
- * @typeParam T - Result type for projections (unused)
107
-
108
- * @typeParam TTable - Table definition being queried
109
-
110
- */
111
-
112
- export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
113
-
114
- private readonly env: SelectQueryBuilderEnvironment;
115
-
116
- private readonly context: SelectQueryBuilderContext;
117
-
118
- private readonly columnSelector: ColumnSelector;
119
-
120
- private readonly relationManager: RelationManager;
121
-
122
- private readonly lazyRelations: Set<string>;
123
-
124
-
125
-
126
- /**
127
-
128
- * Creates a new SelectQueryBuilder instance
129
-
130
- * @param table - Table definition to query
131
-
132
- * @param state - Optional initial query state
133
-
134
- * @param hydration - Optional hydration manager
135
-
136
- * @param dependencies - Optional query builder dependencies
137
-
138
- */
139
-
140
- constructor(
141
-
142
- table: TTable,
143
-
144
- state?: SelectQueryState,
145
-
146
- hydration?: HydrationManager,
147
-
148
- dependencies?: Partial<SelectQueryBuilderDependencies>,
149
-
150
- lazyRelations?: Set<string>
151
-
152
- ) {
153
-
154
- const deps = resolveSelectQueryBuilderDependencies(dependencies);
155
-
156
- this.env = { table, deps };
157
-
158
- const initialState = state ?? deps.createState(table);
159
-
160
- const initialHydration = hydration ?? deps.createHydration(table);
161
-
162
- this.context = {
163
-
164
- state: initialState,
165
-
166
- hydration: initialHydration
167
-
168
- };
169
-
170
- this.lazyRelations = new Set(lazyRelations ?? []);
171
-
172
- this.columnSelector = new ColumnSelector(this.env);
173
-
174
- this.relationManager = new RelationManager(this.env);
175
-
176
- }
177
-
178
-
179
-
180
- private clone(
181
-
182
- context: SelectQueryBuilderContext = this.context,
183
-
184
- lazyRelations = new Set(this.lazyRelations)
185
-
186
- ): SelectQueryBuilder<T, TTable> {
187
-
188
- return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
189
-
190
- }
191
-
192
- /**
193
- * Applies an alias to the root FROM table.
194
- * @param alias - Alias to apply
195
- */
196
- as(alias: string): SelectQueryBuilder<T, TTable> {
197
- const from = this.context.state.ast.from;
198
- if (from.type !== 'Table') {
199
- throw new Error('Cannot alias non-table FROM sources');
200
- }
201
- const nextFrom = { ...from, alias };
202
- const nextContext = this.applyAst(this.context, service => service.withFrom(nextFrom));
203
- return this.clone(nextContext);
204
- }
205
-
206
-
207
-
208
- private resolveQueryNode<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryNode {
209
-
210
- const candidate = query as { getAST?: () => SelectQueryNode };
211
- return typeof candidate.getAST === 'function' && candidate.getAST
212
- ? candidate.getAST()
213
- : (query as SelectQueryNode);
214
-
215
- }
216
-
217
- private applyCorrelation(ast: SelectQueryNode, correlation?: ExpressionNode): SelectQueryNode {
218
- if (!correlation) return ast;
219
- const combinedWhere = ast.where ? and(correlation, ast.where) : correlation;
220
- return {
221
- ...ast,
222
- where: combinedWhere
223
- };
224
- }
225
-
226
-
227
-
228
- private createChildBuilder<R, TChild extends TableDef>(table: TChild): SelectQueryBuilder<R, TChild> {
229
-
230
- return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
231
-
232
- }
233
-
234
-
235
-
236
- private applyAst(
237
-
238
- context: SelectQueryBuilderContext,
239
-
240
- mutator: (service: QueryAstService) => SelectQueryState
241
-
242
- ): SelectQueryBuilderContext {
243
-
244
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
245
-
246
- const nextState = mutator(astService);
247
-
248
- return { state: nextState, hydration: context.hydration };
249
-
250
- }
251
-
252
-
253
-
254
- private applyJoin(
255
-
256
- context: SelectQueryBuilderContext,
257
-
258
- table: TableDef,
259
-
260
- condition: BinaryExpressionNode,
261
-
262
- kind: JoinKind
263
-
264
- ): SelectQueryBuilderContext {
265
-
266
- const joinNode = createJoinNode(kind, table.name, condition);
267
-
268
- return this.applyAst(context, service => service.withJoin(joinNode));
269
-
270
- }
271
-
272
-
273
-
274
- private applySetOperation<TSub extends TableDef>(
275
-
276
- operator: SetOperationKind,
277
-
278
- query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
279
-
280
- ): SelectQueryBuilderContext {
281
-
282
- const subAst = this.resolveQueryNode(query);
283
-
284
- return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
285
-
286
- }
287
-
288
-
289
-
290
- /**
291
-
292
- * Selects specific columns for the query
293
-
294
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
295
-
296
- * @returns New query builder instance with selected columns
297
-
298
- */
299
-
300
- select(columns: Record<string, ColumnSelectionValue>): SelectQueryBuilder<T, TTable> {
301
-
302
- return this.clone(this.columnSelector.select(this.context, columns));
303
-
304
- }
305
-
306
-
307
- /**
308
- * Selects columns from the root table by name (typed).
309
- * @param cols - Column names on the root table
310
- */
311
- selectColumns<K extends keyof TTable['columns'] & string>(...cols: K[]): SelectQueryBuilder<T, TTable> {
312
- const selection: Record<string, ColumnDef> = {};
313
-
314
- for (const key of cols) {
315
- const col = this.env.table.columns[key];
316
- if (!col) {
317
- throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
318
- }
319
- selection[key] = col;
320
- }
321
-
322
- return this.select(selection);
323
- }
324
-
325
-
326
-
327
- /**
328
-
329
- * Selects raw column expressions
330
-
331
- * @param cols - Column expressions as strings
332
-
333
- * @returns New query builder instance with raw column selections
334
-
335
- */
336
-
337
- selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
338
-
339
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
340
-
341
- }
342
-
343
-
344
-
345
- /**
346
-
347
- * Adds a Common Table Expression (CTE) to the query
348
-
349
- * @param name - Name of the CTE
350
-
351
- * @param query - Query builder or query node for the CTE
352
-
353
- * @param columns - Optional column names for the CTE
354
-
355
- * @returns New query builder instance with the CTE
356
-
357
- */
358
-
359
- with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
360
-
361
- const subAst = this.resolveQueryNode(query);
362
-
363
- const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, false));
364
-
365
- return this.clone(nextContext);
366
-
367
- }
368
-
369
-
370
-
371
- /**
372
-
373
- * Adds a recursive Common Table Expression (CTE) to the query
374
-
375
- * @param name - Name of the CTE
376
-
377
- * @param query - Query builder or query node for the CTE
378
-
379
- * @param columns - Optional column names for the CTE
380
-
381
- * @returns New query builder instance with the recursive CTE
382
-
383
- */
384
-
385
- withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
386
-
387
- const subAst = this.resolveQueryNode(query);
388
-
389
- const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, true));
390
-
391
- return this.clone(nextContext);
392
-
393
- }
394
-
395
-
396
- /**
397
- * Replaces the FROM clause with a derived table (subquery with alias)
398
- * @param subquery - Subquery to use as the FROM source
399
- * @param alias - Alias for the derived table
400
- * @param columnAliases - Optional column alias list
401
- * @returns New query builder instance with updated FROM
402
- */
403
- fromSubquery<TSub extends TableDef>(
404
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
405
- alias: string,
406
- columnAliases?: string[]
407
- ): SelectQueryBuilder<T, TTable> {
408
- const subAst = this.resolveQueryNode(subquery);
409
- const fromNode = derivedTable(subAst, alias, columnAliases);
410
- const nextContext = this.applyAst(this.context, service => service.withFrom(fromNode));
411
- return this.clone(nextContext);
412
- }
413
-
414
-
415
-
416
- /**
417
-
418
- * Selects a subquery as a column
419
-
420
- * @param alias - Alias for the subquery column
421
-
422
- * @param sub - Query builder or query node for the subquery
423
-
424
- * @returns New query builder instance with the subquery selection
425
-
426
- */
427
-
428
- selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
429
-
430
- const query = this.resolveQueryNode(sub);
431
-
432
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
433
-
434
- }
435
-
436
-
437
- /**
438
- * Adds a JOIN against a derived table (subquery with alias)
439
- * @param subquery - Subquery to join
440
- * @param alias - Alias for the derived table
441
- * @param condition - Join condition expression
442
- * @param joinKind - Join kind (defaults to INNER)
443
- * @param columnAliases - Optional column alias list for the derived table
444
- * @returns New query builder instance with the derived-table join
445
- */
446
- joinSubquery<TSub extends TableDef>(
447
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
448
- alias: string,
449
- condition: BinaryExpressionNode,
450
- joinKind: JoinKind = JOIN_KINDS.INNER,
451
- columnAliases?: string[]
452
- ): SelectQueryBuilder<T, TTable> {
453
- const subAst = this.resolveQueryNode(subquery);
454
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
455
- const nextContext = this.applyAst(this.context, service => service.withJoin(joinNode));
456
- return this.clone(nextContext);
457
- }
458
-
459
-
460
-
461
- /**
462
-
463
- * Adds an INNER JOIN to the query
464
-
465
- * @param table - Table to join
466
-
467
- * @param condition - Join condition expression
468
-
469
- * @returns New query builder instance with the INNER JOIN
470
-
471
- */
472
-
473
- innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
474
-
475
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
476
-
477
- return this.clone(nextContext);
478
-
479
- }
480
-
481
-
482
-
483
- /**
484
-
485
- * Adds a LEFT JOIN to the query
486
-
487
- * @param table - Table to join
488
-
489
- * @param condition - Join condition expression
490
-
491
- * @returns New query builder instance with the LEFT JOIN
492
-
493
- */
494
-
495
- leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
496
-
497
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
498
-
499
- return this.clone(nextContext);
500
-
501
- }
502
-
503
-
504
-
505
- /**
506
-
507
- * Adds a RIGHT JOIN to the query
508
-
509
- * @param table - Table to join
510
-
511
- * @param condition - Join condition expression
512
-
513
- * @returns New query builder instance with the RIGHT JOIN
514
-
515
- */
516
-
517
- rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
518
-
519
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
520
-
521
- return this.clone(nextContext);
522
-
523
- }
524
-
525
-
526
-
527
- /**
528
-
529
- * Matches records based on a relationship
530
-
531
- * @param relationName - Name of the relationship to match
532
-
533
- * @param predicate - Optional predicate expression
534
-
535
- * @returns New query builder instance with the relationship match
536
-
537
- */
538
-
539
- match(relationName: string, predicate?: ExpressionNode): SelectQueryBuilder<T, TTable> {
540
-
541
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
542
-
543
- return this.clone(nextContext);
544
-
545
- }
546
-
547
-
548
-
549
- /**
550
-
551
- * Joins a related table
552
-
553
- * @param relationName - Name of the relationship to join
554
-
555
- * @param joinKind - Type of join (defaults to INNER)
556
-
557
- * @param extraCondition - Optional additional join condition
558
-
559
- * @returns New query builder instance with the relationship join
560
-
561
- */
562
-
563
- joinRelation(
564
-
565
- relationName: string,
566
-
567
- joinKind: JoinKind = JOIN_KINDS.INNER,
568
-
569
- extraCondition?: ExpressionNode
570
-
571
- ): SelectQueryBuilder<T, TTable> {
572
-
573
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
574
-
575
- return this.clone(nextContext);
576
-
577
- }
578
-
579
-
580
-
581
- /**
582
-
583
- * Includes related data in the query results
584
-
585
- * @param relationName - Name of the relationship to include
586
-
587
- * @param options - Optional include options
588
-
589
- * @returns New query builder instance with the relationship inclusion
590
-
591
- */
592
-
593
- include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T, TTable> {
594
-
595
- const nextContext = this.relationManager.include(this.context, relationName, options);
596
-
597
- return this.clone(nextContext);
598
-
599
- }
600
-
601
-
602
-
603
- includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
604
-
605
- const nextLazy = new Set(this.lazyRelations);
606
-
607
- nextLazy.add(relationName as string);
608
-
609
- return this.clone(this.context, nextLazy);
610
-
611
- }
612
-
613
- /**
614
- * Selects columns for a related table in a single hop.
615
- */
616
- selectRelationColumns<
617
- K extends keyof TTable['relations'] & string,
618
- TRel extends RelationDef = TTable['relations'][K],
619
- TTarget extends TableDef = RelationTargetTable<TRel>,
620
- C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
621
- >(relationName: K, ...cols: C[]): SelectQueryBuilder<T, TTable> {
622
- const relation = this.env.table.relations[relationName] as RelationDef | undefined;
623
- if (!relation) {
624
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
625
- }
626
- const target = relation.target;
627
-
628
- for (const col of cols) {
629
- if (!target.columns[col]) {
630
- throw new Error(
631
- `Column '${col}' not found on related table '${target.name}' for relation '${relationName}'`
632
- );
633
- }
634
- }
635
-
636
- return this.include(relationName as string, { columns: cols as string[] });
637
- }
638
-
639
-
640
- /**
641
- * Convenience alias for selecting specific columns from a relation.
642
- */
643
- includePick<
644
- K extends keyof TTable['relations'] & string,
645
- TRel extends RelationDef = TTable['relations'][K],
646
- TTarget extends TableDef = RelationTargetTable<TRel>,
647
- C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
648
- >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
649
- return this.selectRelationColumns(relationName, ...cols);
650
- }
651
-
652
-
653
- /**
654
- * Selects columns for the root table and relations from a single config object.
655
- */
656
- selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable> {
657
- // eslint-disable-next-line @typescript-eslint/no-this-alias
658
- let currBuilder: SelectQueryBuilder<T, TTable> = this;
659
-
660
- if (config.root?.length) {
661
- currBuilder = currBuilder.selectColumns(...config.root);
662
- }
663
-
664
- for (const key of Object.keys(config) as (keyof typeof config)[]) {
665
- if (key === 'root') continue;
666
- const relName = key as keyof TTable['relations'] & string;
667
- const cols = config[relName as keyof DeepSelectConfig<TTable>] as string[] | undefined;
668
- if (!cols || !cols.length) continue;
669
- currBuilder = currBuilder.selectRelationColumns(relName, ...(cols as string[]));
670
- }
671
-
672
- return currBuilder;
673
- }
674
-
675
-
676
-
677
- getLazyRelations(): (keyof RelationMap<TTable>)[] {
678
-
679
- return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
680
-
681
- }
682
-
683
-
684
-
685
- getTable(): TTable {
686
-
687
- return this.env.table as TTable;
688
-
689
- }
690
-
691
-
692
-
693
- async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
694
-
695
- return executeHydrated(ctx, this);
696
-
697
- }
698
-
699
-
700
-
701
- async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
702
-
703
- return executeHydratedWithContexts(execCtx, hydCtx, this);
704
-
705
- }
706
-
707
-
708
-
709
- /**
710
-
711
- * Adds a WHERE condition to the query
712
-
713
- * @param expr - Expression for the WHERE clause
714
-
715
- * @returns New query builder instance with the WHERE condition
716
-
717
- */
718
-
719
- where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
720
-
721
- const nextContext = this.applyAst(this.context, service => service.withWhere(expr));
722
-
723
- return this.clone(nextContext);
724
-
725
- }
726
-
727
-
728
-
729
- /**
730
- * Adds a GROUP BY clause to the query
731
- * @param term - Column definition or ordering term to group by
732
- * @returns New query builder instance with the GROUP BY clause
733
- */
734
- groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
735
- const nextContext = this.applyAst(this.context, service => service.withGroupBy(term));
736
- return this.clone(nextContext);
737
- }
738
-
739
-
740
-
741
- /**
742
-
743
- * Adds a HAVING condition to the query
744
-
745
- * @param expr - Expression for the HAVING clause
746
-
747
- * @returns New query builder instance with the HAVING condition
748
-
749
- */
750
-
751
- having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
752
-
753
- const nextContext = this.applyAst(this.context, service => service.withHaving(expr));
754
-
755
- return this.clone(nextContext);
756
-
757
- }
758
-
759
-
760
-
761
- /**
762
- * Adds an ORDER BY clause to the query
763
- * @param term - Column definition or ordering term to order by
764
- * @param directionOrOptions - Order direction or options (defaults to ASC)
765
- * @returns New query builder instance with the ORDER BY clause
766
- */
767
- orderBy(
768
- term: ColumnDef | OrderingTerm,
769
- directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
770
- ): SelectQueryBuilder<T, TTable> {
771
- const options = typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
772
- const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
773
-
774
- const nextContext = this.applyAst(this.context, service =>
775
- service.withOrderBy(term, dir, options.nulls, options.collation)
776
- );
777
-
778
- return this.clone(nextContext);
779
- }
780
-
781
-
782
-
783
- /**
784
-
785
- * Adds a DISTINCT clause to the query
786
-
787
- * @param cols - Columns to make distinct
788
-
789
- * @returns New query builder instance with the DISTINCT clause
790
-
791
- */
792
-
793
- distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
794
-
795
- return this.clone(this.columnSelector.distinct(this.context, cols));
796
-
797
- }
798
-
799
-
800
-
801
- /**
802
-
803
- * Adds a LIMIT clause to the query
804
-
805
- * @param n - Maximum number of rows to return
806
-
807
- * @returns New query builder instance with the LIMIT clause
808
-
809
- */
810
-
811
- limit(n: number): SelectQueryBuilder<T, TTable> {
812
-
813
- const nextContext = this.applyAst(this.context, service => service.withLimit(n));
814
-
815
- return this.clone(nextContext);
816
-
817
- }
818
-
819
-
820
-
821
- /**
822
-
823
- * Adds an OFFSET clause to the query
824
-
825
- * @param n - Number of rows to skip
826
-
827
- * @returns New query builder instance with the OFFSET clause
828
-
829
- */
830
-
831
- offset(n: number): SelectQueryBuilder<T, TTable> {
832
-
833
- const nextContext = this.applyAst(this.context, service => service.withOffset(n));
834
-
835
- return this.clone(nextContext);
836
-
837
- }
838
-
839
-
840
-
841
- /**
842
-
843
- * Combines this query with another using UNION
844
-
845
- * @param query - Query to union with
846
-
847
- * @returns New query builder instance with the set operation
848
-
849
- */
850
-
851
- union<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
852
-
853
- return this.clone(this.applySetOperation('UNION', query));
854
-
855
- }
856
-
857
-
858
-
859
- /**
860
-
861
- * Combines this query with another using UNION ALL
862
-
863
- * @param query - Query to union with
864
-
865
- * @returns New query builder instance with the set operation
866
-
867
- */
868
-
869
- unionAll<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
870
-
871
- return this.clone(this.applySetOperation('UNION ALL', query));
872
-
873
- }
874
-
875
-
876
-
877
- /**
878
-
879
- * Combines this query with another using INTERSECT
880
-
881
- * @param query - Query to intersect with
882
-
883
- * @returns New query builder instance with the set operation
884
-
885
- */
886
-
887
- intersect<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
888
-
889
- return this.clone(this.applySetOperation('INTERSECT', query));
890
-
891
- }
892
-
893
-
894
-
895
- /**
896
-
897
- * Combines this query with another using EXCEPT
898
-
899
- * @param query - Query to subtract
900
-
901
- * @returns New query builder instance with the set operation
902
-
903
- */
904
-
905
- except<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
906
-
907
- return this.clone(this.applySetOperation('EXCEPT', query));
908
-
909
- }
910
-
911
-
912
-
913
- /**
914
-
915
- * Adds a WHERE EXISTS condition to the query
916
-
917
- * @param subquery - Subquery to check for existence
918
-
919
- * @returns New query builder instance with the WHERE EXISTS condition
920
-
921
- */
922
-
923
- whereExists<TSub extends TableDef>(
924
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
925
- correlate?: ExpressionNode
926
- ): SelectQueryBuilder<T, TTable> {
927
- const subAst = this.resolveQueryNode(subquery);
928
- const correlated = this.applyCorrelation(subAst, correlate);
929
- return this.where(exists(correlated));
930
- }
931
-
932
-
933
-
934
- /**
935
-
936
- * Adds a WHERE NOT EXISTS condition to the query
937
-
938
- * @param subquery - Subquery to check for non-existence
939
-
940
- * @returns New query builder instance with the WHERE NOT EXISTS condition
941
-
942
- */
943
-
944
- whereNotExists<TSub extends TableDef>(
945
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
946
- correlate?: ExpressionNode
947
- ): SelectQueryBuilder<T, TTable> {
948
- const subAst = this.resolveQueryNode(subquery);
949
- const correlated = this.applyCorrelation(subAst, correlate);
950
- return this.where(notExists(correlated));
951
- }
952
-
953
-
954
-
955
- /**
956
-
957
- * Adds a WHERE EXISTS condition based on a relationship
958
-
959
- * @param relationName - Name of the relationship to check
960
-
961
- * @param callback - Optional callback to modify the relationship query
962
-
963
- * @returns New query builder instance with the relationship existence check
964
-
965
- */
966
-
967
- whereHas(
968
-
969
- relationName: string,
970
-
971
- callbackOrOptions?: RelationCallback | WhereHasOptions,
972
-
973
- maybeOptions?: WhereHasOptions
974
-
975
- ): SelectQueryBuilder<T, TTable> {
976
-
977
- const relation = this.env.table.relations[relationName];
978
-
979
- if (!relation) {
980
-
981
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
982
-
983
- }
984
-
985
-
986
-
987
- const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
988
- const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
989
-
990
- let subQb = this.createChildBuilder<unknown, typeof relation.target>(relation.target);
991
-
992
- if (callback) {
993
-
994
- subQb = callback(subQb);
995
-
996
- }
997
-
998
-
999
-
1000
- const subAst = subQb.getAST();
1001
-
1002
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
1003
-
1004
- return this.where(exists(finalSubAst));
1005
-
1006
- }
1007
-
1008
-
1009
-
1010
- /**
1011
-
1012
- * Adds a WHERE NOT EXISTS condition based on a relationship
1013
-
1014
- * @param relationName - Name of the relationship to check
1015
-
1016
- * @param callback - Optional callback to modify the relationship query
1017
-
1018
- * @returns New query builder instance with the relationship non-existence check
1019
-
1020
- */
1021
-
1022
- whereHasNot(
1023
-
1024
- relationName: string,
1025
-
1026
- callbackOrOptions?: RelationCallback | WhereHasOptions,
1027
-
1028
- maybeOptions?: WhereHasOptions
1029
-
1030
- ): SelectQueryBuilder<T, TTable> {
1031
-
1032
- const relation = this.env.table.relations[relationName];
1033
-
1034
- if (!relation) {
1035
-
1036
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
1037
-
1038
- }
1039
-
1040
-
1041
-
1042
- const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
1043
- const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
1044
-
1045
- let subQb = this.createChildBuilder<unknown, typeof relation.target>(relation.target);
1046
-
1047
- if (callback) {
1048
-
1049
- subQb = callback(subQb);
1050
-
1051
- }
1052
-
1053
-
1054
-
1055
- const subAst = subQb.getAST();
1056
-
1057
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
1058
-
1059
- return this.where(notExists(finalSubAst));
1060
-
1061
- }
1062
-
1063
-
1064
-
1065
- /**
1066
-
1067
- * Compiles the query to SQL for a specific dialect
1068
-
1069
- * @param dialect - Database dialect to compile for
1070
-
1071
- * @returns Compiled query with SQL and parameters
1072
-
1073
- */
1074
-
1075
- compile(dialect: SelectDialectInput): CompiledQuery {
1076
-
1077
- const resolved = resolveDialectInput(dialect);
1078
-
1079
- return resolved.compileSelect(this.context.state.ast);
1080
-
1081
- }
1082
-
1083
-
1084
-
1085
- /**
1086
-
1087
- * Converts the query to SQL string for a specific dialect
1088
-
1089
- * @param dialect - Database dialect to generate SQL for
1090
-
1091
- * @returns SQL string representation of the query
1092
-
1093
- */
1094
-
1095
- toSql(dialect: SelectDialectInput): string {
1096
-
1097
- return this.compile(dialect).sql;
1098
-
1099
- }
1100
-
1101
-
1102
-
1103
- /**
1104
-
1105
- * Gets the hydration plan for the query
1106
-
1107
- * @returns Hydration plan or undefined if none exists
1108
-
1109
- */
1110
-
1111
- getHydrationPlan(): HydrationPlan | undefined {
1112
-
1113
- return this.context.hydration.getPlan();
1114
-
1115
- }
1116
-
1117
-
1118
-
1119
- /**
1120
-
1121
- * Gets the Abstract Syntax Tree (AST) representation of the query
1122
-
1123
- * @returns Query AST with hydration applied
1124
-
1125
- */
1126
-
1127
- getAST(): SelectQueryNode {
1128
-
1129
- return this.context.hydration.applyToAst(this.context.state.ast);
1130
-
1131
- }
1132
-
1133
- }
1134
-
1135
-
1136
-
1137
- /**
1138
-
1139
- * Creates a column node for use in expressions
1140
-
1141
- * @param table - Table name
1142
-
1143
- * @param name - Column name
1144
-
1145
- * @returns ColumnNode with the specified table and name
1146
-
1147
- */
1148
-
1149
- export const createColumn = (table: string, name: string): ColumnNode => ({ type: 'Column', table, name });
1150
-
1151
-
1152
-
1153
- /**
1154
-
1155
- * Creates a literal value node for use in expressions
1156
-
1157
- * @param val - Literal value (string or number)
1158
-
1159
- * @returns LiteralNode with the specified value
1160
-
1161
- */
1162
-
1163
- export const createLiteral = (val: string | number): LiteralNode => ({ type: 'Literal', value: val });
1
+ import { TableDef } from '../schema/table.js';
2
+ import { ColumnDef } from '../schema/column.js';
3
+ import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
4
+ import { HydrationPlan } from '../core/hydration/types.js';
5
+ import {
6
+ ColumnNode,
7
+ ExpressionNode,
8
+ FunctionNode,
9
+ BinaryExpressionNode,
10
+ CaseExpressionNode,
11
+ WindowFunctionNode,
12
+ and,
13
+ exists,
14
+ notExists,
15
+ OperandNode
16
+ } from '../core/ast/expression.js';
17
+ import { derivedTable, fnTable } from '../core/ast/builders.js';
18
+ import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
19
+ import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
20
+
21
+ type SelectDialectInput = Dialect | DialectKey;
22
+
23
+ import { SelectQueryState } from './select-query-state.js';
24
+ import { HydrationManager } from './hydration-manager.js';
25
+ import {
26
+ resolveSelectQueryBuilderDependencies,
27
+ SelectQueryBuilderContext,
28
+ SelectQueryBuilderDependencies,
29
+ SelectQueryBuilderEnvironment
30
+ } from './select-query-builder-deps.js';
31
+ import { QueryAstService } from './query-ast-service.js';
32
+ import { ColumnSelector } from './column-selector.js';
33
+ import { RelationManager } from './relation-manager.js';
34
+ import { RelationIncludeOptions } from './relation-types.js';
35
+ import type { RelationDef } from '../schema/relation.js';
36
+ import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
37
+ import { EntityInstance, RelationMap, RelationTargetTable } from '../schema/types.js';
38
+ import { OrmSession } from '../orm/orm-session.ts';
39
+ import { ExecutionContext } from '../orm/execution-context.js';
40
+ import { HydrationContext } from '../orm/hydration-context.js';
41
+ import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
42
+ import { createJoinNode } from '../core/ast/join-node.js';
43
+ import { resolveSelectQuery } from './query-resolution.js';
44
+
45
+
46
+ type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
47
+
48
+ type DeepSelectEntry<TTable extends TableDef> = {
49
+ type: 'root';
50
+ columns: (keyof TTable['columns'] & string)[];
51
+ } | {
52
+ type: 'relation';
53
+ relationName: keyof TTable['relations'] & string;
54
+ columns: string[];
55
+ };
56
+
57
+ type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
58
+
59
+ type WhereHasOptions = {
60
+ correlate?: ExpressionNode;
61
+ };
62
+
63
+ type RelationCallback = <TChildTable extends TableDef>(
64
+ qb: SelectQueryBuilder<unknown, TChildTable>
65
+ ) => SelectQueryBuilder<unknown, TChildTable>;
66
+
67
+
68
+ /**
69
+ * Main query builder class for constructing SQL SELECT queries
70
+ * @typeParam T - Result type for projections (unused)
71
+ * @typeParam TTable - Table definition being queried
72
+ */
73
+ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
74
+ private readonly env: SelectQueryBuilderEnvironment;
75
+ private readonly context: SelectQueryBuilderContext;
76
+ private readonly columnSelector: ColumnSelector;
77
+ private readonly relationManager: RelationManager;
78
+ private readonly lazyRelations: Set<string>;
79
+
80
+ /**
81
+ * Creates a new SelectQueryBuilder instance
82
+ * @param table - Table definition to query
83
+ * @param state - Optional initial query state
84
+ * @param hydration - Optional hydration manager
85
+ * @param dependencies - Optional query builder dependencies
86
+ */
87
+ constructor(
88
+ table: TTable,
89
+ state?: SelectQueryState,
90
+ hydration?: HydrationManager,
91
+ dependencies?: Partial<SelectQueryBuilderDependencies>,
92
+ lazyRelations?: Set<string>
93
+ ) {
94
+ const deps = resolveSelectQueryBuilderDependencies(dependencies);
95
+ this.env = { table, deps };
96
+ const initialState = state ?? deps.createState(table);
97
+ const initialHydration = hydration ?? deps.createHydration(table);
98
+ this.context = {
99
+ state: initialState,
100
+ hydration: initialHydration
101
+ };
102
+ this.lazyRelations = new Set(lazyRelations ?? []);
103
+ this.columnSelector = deps.createColumnSelector(this.env);
104
+ this.relationManager = deps.createRelationManager(this.env);
105
+ }
106
+
107
+ /**
108
+ * Creates a new SelectQueryBuilder instance with updated context and lazy relations
109
+ * @param context - Updated query context
110
+ * @param lazyRelations - Updated lazy relations set
111
+ * @returns New SelectQueryBuilder instance
112
+ */
113
+ private clone(
114
+ context: SelectQueryBuilderContext = this.context,
115
+ lazyRelations = new Set(this.lazyRelations)
116
+ ): SelectQueryBuilder<T, TTable> {
117
+ return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
118
+ }
119
+
120
+ /**
121
+ * Applies an alias to the root FROM table.
122
+ * @param alias - Alias to apply
123
+ */
124
+ as(alias: string): SelectQueryBuilder<T, TTable> {
125
+ const from = this.context.state.ast.from;
126
+ if (from.type !== 'Table') {
127
+ throw new Error('Cannot alias non-table FROM sources');
128
+ }
129
+ const nextFrom = { ...from, alias };
130
+ const nextContext = this.applyAst(this.context, service => service.withFrom(nextFrom));
131
+ return this.clone(nextContext);
132
+ }
133
+
134
+
135
+
136
+ /**
137
+ * Applies correlation expression to the query AST
138
+ * @param ast - Query AST to modify
139
+ * @param correlation - Correlation expression
140
+ * @returns Modified AST with correlation applied
141
+ */
142
+ private applyCorrelation(ast: SelectQueryNode, correlation?: ExpressionNode): SelectQueryNode {
143
+ if (!correlation) return ast;
144
+ const combinedWhere = ast.where ? and(correlation, ast.where) : correlation;
145
+ return {
146
+ ...ast,
147
+ where: combinedWhere
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Creates a new child query builder for a related table
153
+ * @param table - Table definition for the child builder
154
+ * @returns New SelectQueryBuilder instance for the child table
155
+ */
156
+ private createChildBuilder<R, TChild extends TableDef>(table: TChild): SelectQueryBuilder<R, TChild> {
157
+ return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
158
+ }
159
+
160
+ /**
161
+ * Applies an AST mutation using the query AST service
162
+ * @param context - Current query context
163
+ * @param mutator - Function that mutates the AST
164
+ * @returns Updated query context
165
+ */
166
+ private applyAst(
167
+ context: SelectQueryBuilderContext,
168
+ mutator: (service: QueryAstService) => SelectQueryState
169
+ ): SelectQueryBuilderContext {
170
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
171
+ const nextState = mutator(astService);
172
+ return { state: nextState, hydration: context.hydration };
173
+ }
174
+
175
+ /**
176
+ * Applies a join to the query context
177
+ * @param context - Current query context
178
+ * @param table - Table to join
179
+ * @param condition - Join condition
180
+ * @param kind - Join kind
181
+ * @returns Updated query context with join applied
182
+ */
183
+ private applyJoin(
184
+ context: SelectQueryBuilderContext,
185
+ table: TableDef,
186
+ condition: BinaryExpressionNode,
187
+ kind: JoinKind
188
+ ): SelectQueryBuilderContext {
189
+ const joinNode = createJoinNode(kind, { type: 'Table', name: table.name, schema: table.schema }, condition);
190
+ return this.applyAst(context, service => service.withJoin(joinNode));
191
+ }
192
+
193
+ /**
194
+ * Applies a set operation to the query
195
+ * @param operator - Set operation kind
196
+ * @param query - Query to combine with
197
+ * @returns Updated query context with set operation
198
+ */
199
+ private applySetOperation<TSub extends TableDef>(
200
+ operator: SetOperationKind,
201
+ query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
202
+ ): SelectQueryBuilderContext {
203
+ const subAst = resolveSelectQuery(query);
204
+ return this.applyAst(this.context, service => service.withSetOperation(operator, subAst));
205
+ }
206
+
207
+
208
+ /**
209
+ * Selects columns for the query (unified overloaded method).
210
+ * Can be called with column names or a projection object.
211
+ * @param args - Column names or projection object
212
+ * @returns New query builder instance with selected columns
213
+ */
214
+ select<K extends keyof TTable['columns'] & string>(
215
+ ...args: K[]
216
+ ): SelectQueryBuilder<T, TTable>;
217
+ select(columns: Record<string, ColumnSelectionValue>): SelectQueryBuilder<T, TTable>;
218
+ select<K extends keyof TTable['columns'] & string>(
219
+ ...args: K[] | [Record<string, ColumnSelectionValue>]
220
+ ): SelectQueryBuilder<T, TTable> {
221
+ // If first arg is an object (not a string), treat as projection map
222
+ if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
223
+ const columns = args[0] as Record<string, ColumnSelectionValue>;
224
+ return this.clone(this.columnSelector.select(this.context, columns));
225
+ }
226
+
227
+ // Otherwise, treat as column names
228
+ const cols = args as K[];
229
+ const selection: Record<string, ColumnDef> = {};
230
+ for (const key of cols) {
231
+ const col = this.env.table.columns[key];
232
+ if (!col) {
233
+ throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
234
+ }
235
+ selection[key] = col;
236
+ }
237
+
238
+ return this.clone(this.columnSelector.select(this.context, selection));
239
+ }
240
+
241
+ /**
242
+ * Selects raw column expressions
243
+ * @param cols - Column expressions as strings
244
+ * @returns New query builder instance with raw column selections
245
+ */
246
+ selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
247
+ return this.clone(this.columnSelector.selectRaw(this.context, cols));
248
+ }
249
+
250
+ /**
251
+ * Adds a Common Table Expression (CTE) to the query
252
+ * @param name - Name of the CTE
253
+ * @param query - Query builder or query node for the CTE
254
+ * @param columns - Optional column names for the CTE
255
+ * @returns New query builder instance with the CTE
256
+ */
257
+ with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
258
+ const subAst = resolveSelectQuery(query);
259
+ const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, false));
260
+ return this.clone(nextContext);
261
+ }
262
+
263
+ /**
264
+ * Adds a recursive Common Table Expression (CTE) to the query
265
+ * @param name - Name of the CTE
266
+ * @param query - Query builder or query node for the CTE
267
+ * @param columns - Optional column names for the CTE
268
+ * @returns New query builder instance with the recursive CTE
269
+ */
270
+ withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
271
+ const subAst = resolveSelectQuery(query);
272
+ const nextContext = this.applyAst(this.context, service => service.withCte(name, subAst, columns, true));
273
+ return this.clone(nextContext);
274
+ }
275
+
276
+ /**
277
+ * Replaces the FROM clause with a derived table (subquery with alias)
278
+ * @param subquery - Subquery to use as the FROM source
279
+ * @param alias - Alias for the derived table
280
+ * @param columnAliases - Optional column alias list
281
+ * @returns New query builder instance with updated FROM
282
+ */
283
+ fromSubquery<TSub extends TableDef>(
284
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
285
+ alias: string,
286
+ columnAliases?: string[]
287
+ ): SelectQueryBuilder<T, TTable> {
288
+ const subAst = resolveSelectQuery(subquery);
289
+ const fromNode = derivedTable(subAst, alias, columnAliases);
290
+ const nextContext = this.applyAst(this.context, service => service.withFrom(fromNode));
291
+ return this.clone(nextContext);
292
+ }
293
+
294
+ /**
295
+ * Replaces the FROM clause with a function table expression.
296
+ * @param name - Function name
297
+ * @param args - Optional function arguments
298
+ * @param alias - Optional alias for the function table
299
+ * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
300
+ */
301
+ fromFunctionTable(
302
+ name: string,
303
+ args: OperandNode[] = [],
304
+ alias?: string,
305
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
306
+ ): SelectQueryBuilder<T, TTable> {
307
+ const functionTable = fnTable(name, args, alias, options);
308
+ const nextContext = this.applyAst(this.context, service => service.withFrom(functionTable));
309
+ return this.clone(nextContext);
310
+ }
311
+
312
+ /**
313
+ * Selects a subquery as a column
314
+ * @param alias - Alias for the subquery column
315
+ * @param sub - Query builder or query node for the subquery
316
+ * @returns New query builder instance with the subquery selection
317
+ */
318
+ selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
319
+ const query = resolveSelectQuery(sub);
320
+ return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
321
+ }
322
+
323
+ /**
324
+ * Adds a JOIN against a derived table (subquery with alias)
325
+ * @param subquery - Subquery to join
326
+ * @param alias - Alias for the derived table
327
+ * @param condition - Join condition expression
328
+ * @param joinKind - Join kind (defaults to INNER)
329
+ * @param columnAliases - Optional column alias list for the derived table
330
+ * @returns New query builder instance with the derived-table join
331
+ */
332
+ joinSubquery<TSub extends TableDef>(
333
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
334
+ alias: string,
335
+ condition: BinaryExpressionNode,
336
+ joinKind: JoinKind = JOIN_KINDS.INNER,
337
+ columnAliases?: string[]
338
+ ): SelectQueryBuilder<T, TTable> {
339
+ const subAst = resolveSelectQuery(subquery);
340
+ const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
341
+ const nextContext = this.applyAst(this.context, service => service.withJoin(joinNode));
342
+ return this.clone(nextContext);
343
+ }
344
+
345
+ /**
346
+ * Adds a join against a function table (e.g., `generate_series`) using `fnTable` internally.
347
+ * @param name - Function name
348
+ * @param args - Optional arguments passed to the function
349
+ * @param alias - Alias for the function table so columns can be referenced
350
+ * @param condition - Join condition expression
351
+ * @param joinKind - Kind of join (defaults to INNER)
352
+ * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
353
+ */
354
+ joinFunctionTable(
355
+ name: string,
356
+ args: OperandNode[] = [],
357
+ alias: string,
358
+ condition: BinaryExpressionNode,
359
+ joinKind: JoinKind = JOIN_KINDS.INNER,
360
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
361
+ ): SelectQueryBuilder<T, TTable> {
362
+ const functionTable = fnTable(name, args, alias, options);
363
+ const joinNode = createJoinNode(joinKind, functionTable, condition);
364
+ const nextContext = this.applyAst(this.context, service => service.withJoin(joinNode));
365
+ return this.clone(nextContext);
366
+ }
367
+
368
+ /**
369
+ * Adds an INNER JOIN to the query
370
+ * @param table - Table to join
371
+ * @param condition - Join condition expression
372
+ * @returns New query builder instance with the INNER JOIN
373
+ */
374
+ innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
375
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
376
+ return this.clone(nextContext);
377
+ }
378
+
379
+ /**
380
+ * Adds a LEFT JOIN to the query
381
+ * @param table - Table to join
382
+ * @param condition - Join condition expression
383
+ * @returns New query builder instance with the LEFT JOIN
384
+ */
385
+ leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
386
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
387
+ return this.clone(nextContext);
388
+ }
389
+
390
+ /**
391
+ * Adds a RIGHT JOIN to the query
392
+ * @param table - Table to join
393
+ * @param condition - Join condition expression
394
+ * @returns New query builder instance with the RIGHT JOIN
395
+ */
396
+ rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
397
+ const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
398
+ return this.clone(nextContext);
399
+ }
400
+
401
+ /**
402
+ * Matches records based on a relationship
403
+ * @param relationName - Name of the relationship to match
404
+ * @param predicate - Optional predicate expression
405
+ * @returns New query builder instance with the relationship match
406
+ */
407
+ match<K extends keyof TTable['relations'] & string>(
408
+ relationName: K,
409
+ predicate?: ExpressionNode
410
+ ): SelectQueryBuilder<T, TTable> {
411
+ const nextContext = this.relationManager.match(this.context, relationName, predicate);
412
+ return this.clone(nextContext);
413
+ }
414
+
415
+ /**
416
+ * Joins a related table
417
+ * @param relationName - Name of the relationship to join
418
+ * @param joinKind - Type of join (defaults to INNER)
419
+ * @param extraCondition - Optional additional join condition
420
+ * @returns New query builder instance with the relationship join
421
+ */
422
+ joinRelation<K extends keyof TTable['relations'] & string>(
423
+ relationName: K,
424
+ joinKind: JoinKind = JOIN_KINDS.INNER,
425
+ extraCondition?: ExpressionNode
426
+ ): SelectQueryBuilder<T, TTable> {
427
+ const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
428
+ return this.clone(nextContext);
429
+ }
430
+
431
+ /**
432
+ * Includes related data in the query results
433
+ * @param relationName - Name of the relationship to include
434
+ * @param options - Optional include options
435
+ * @returns New query builder instance with the relationship inclusion
436
+ */
437
+ include<K extends keyof TTable['relations'] & string>(
438
+ relationName: K,
439
+ options?: RelationIncludeOptions
440
+ ): SelectQueryBuilder<T, TTable> {
441
+ const nextContext = this.relationManager.include(this.context, relationName, options);
442
+ return this.clone(nextContext);
443
+ }
444
+
445
+ /**
446
+ * Includes a relation lazily in the query results
447
+ * @param relationName - Name of the relation to include lazily
448
+ * @returns New query builder instance with lazy relation inclusion
449
+ */
450
+ includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
451
+ const nextLazy = new Set(this.lazyRelations);
452
+ nextLazy.add(relationName as string);
453
+ return this.clone(this.context, nextLazy);
454
+ }
455
+
456
+ /**
457
+ * Selects columns for a related table in a single hop.
458
+ */
459
+ selectRelationColumns<
460
+ K extends keyof TTable['relations'] & string,
461
+ TRel extends RelationDef = TTable['relations'][K],
462
+ TTarget extends TableDef = RelationTargetTable<TRel>,
463
+ C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
464
+ >(relationName: K, ...cols: C[]): SelectQueryBuilder<T, TTable> {
465
+ const relation = this.env.table.relations[relationName] as RelationDef | undefined;
466
+ if (!relation) {
467
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
468
+ }
469
+ const target = relation.target;
470
+
471
+ for (const col of cols) {
472
+ if (!target.columns[col]) {
473
+ throw new Error(
474
+ `Column '${col}' not found on related table '${target.name}' for relation '${relationName}'`
475
+ );
476
+ }
477
+ }
478
+
479
+ return this.include(relationName as string, { columns: cols as string[] });
480
+ }
481
+
482
+ /**
483
+ * Convenience alias for selecting specific columns from a relation.
484
+ */
485
+ includePick<
486
+ K extends keyof TTable['relations'] & string,
487
+ TRel extends RelationDef = TTable['relations'][K],
488
+ TTarget extends TableDef = RelationTargetTable<TRel>,
489
+ C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
490
+ >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
491
+ return this.selectRelationColumns(relationName, ...cols);
492
+ }
493
+
494
+
495
+ /**
496
+ * Selects columns for the root table and relations from an array of entries
497
+ * @param config - Configuration array for deep column selection
498
+ * @returns New query builder instance with deep column selections
499
+ */
500
+ selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable> {
501
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
502
+ let currBuilder: SelectQueryBuilder<T, TTable> = this;
503
+
504
+ for (const entry of config) {
505
+ if (entry.type === 'root') {
506
+ currBuilder = currBuilder.select(...entry.columns);
507
+ } else {
508
+ currBuilder = currBuilder.selectRelationColumns(entry.relationName, ...(entry.columns as string[]));
509
+ }
510
+ }
511
+
512
+ return currBuilder;
513
+ }
514
+
515
+ /**
516
+ * Gets the list of lazy relations
517
+ * @returns Array of lazy relation names
518
+ */
519
+ getLazyRelations(): (keyof RelationMap<TTable>)[] {
520
+ return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
521
+ }
522
+
523
+ /**
524
+ * Gets the table definition for this query builder
525
+ * @returns Table definition
526
+ */
527
+ getTable(): TTable {
528
+ return this.env.table as TTable;
529
+ }
530
+
531
+ /**
532
+ * Executes the query and returns hydrated results
533
+ * @param ctx - ORM session context
534
+ * @returns Promise of entity instances
535
+ */
536
+ async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
537
+ return executeHydrated(ctx, this);
538
+ }
539
+
540
+ /**
541
+ * Executes the query with provided execution and hydration contexts
542
+ * @param execCtx - Execution context
543
+ * @param hydCtx - Hydration context
544
+ * @returns Promise of entity instances
545
+ */
546
+ async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
547
+ return executeHydratedWithContexts(execCtx, hydCtx, this);
548
+ }
549
+
550
+ /**
551
+ * Adds a WHERE condition to the query
552
+ * @param expr - Expression for the WHERE clause
553
+ * @returns New query builder instance with the WHERE condition
554
+ */
555
+ where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
556
+ const nextContext = this.applyAst(this.context, service => service.withWhere(expr));
557
+ return this.clone(nextContext);
558
+ }
559
+
560
+ /**
561
+ * Adds a GROUP BY clause to the query
562
+ * @param term - Column definition or ordering term to group by
563
+ * @returns New query builder instance with the GROUP BY clause
564
+ */
565
+ groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
566
+ const nextContext = this.applyAst(this.context, service => service.withGroupBy(term));
567
+ return this.clone(nextContext);
568
+ }
569
+
570
+ /**
571
+ * Adds a HAVING condition to the query
572
+ * @param expr - Expression for the HAVING clause
573
+ * @returns New query builder instance with the HAVING condition
574
+ */
575
+ having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
576
+ const nextContext = this.applyAst(this.context, service => service.withHaving(expr));
577
+ return this.clone(nextContext);
578
+ }
579
+
580
+
581
+
582
+ /**
583
+ * Adds an ORDER BY clause to the query
584
+ * @param term - Column definition or ordering term to order by
585
+ * @param directionOrOptions - Order direction or options (defaults to ASC)
586
+ * @returns New query builder instance with the ORDER BY clause
587
+ */
588
+ orderBy(
589
+ term: ColumnDef | OrderingTerm,
590
+ directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
591
+ ): SelectQueryBuilder<T, TTable> {
592
+ const options = typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
593
+ const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
594
+
595
+ const nextContext = this.applyAst(this.context, service =>
596
+ service.withOrderBy(term, dir, options.nulls, options.collation)
597
+ );
598
+
599
+ return this.clone(nextContext);
600
+ }
601
+
602
+ /**
603
+ * Adds a DISTINCT clause to the query
604
+ * @param cols - Columns to make distinct
605
+ * @returns New query builder instance with the DISTINCT clause
606
+ */
607
+ distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
608
+ return this.clone(this.columnSelector.distinct(this.context, cols));
609
+ }
610
+
611
+ /**
612
+ * Adds a LIMIT clause to the query
613
+ * @param n - Maximum number of rows to return
614
+ * @returns New query builder instance with the LIMIT clause
615
+ */
616
+ limit(n: number): SelectQueryBuilder<T, TTable> {
617
+ const nextContext = this.applyAst(this.context, service => service.withLimit(n));
618
+ return this.clone(nextContext);
619
+ }
620
+
621
+ /**
622
+ * Adds an OFFSET clause to the query
623
+ * @param n - Number of rows to skip
624
+ * @returns New query builder instance with the OFFSET clause
625
+ */
626
+ offset(n: number): SelectQueryBuilder<T, TTable> {
627
+ const nextContext = this.applyAst(this.context, service => service.withOffset(n));
628
+ return this.clone(nextContext);
629
+ }
630
+
631
+ /**
632
+ * Combines this query with another using UNION
633
+ * @param query - Query to union with
634
+ * @returns New query builder instance with the set operation
635
+ */
636
+ union<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
637
+ return this.clone(this.applySetOperation('UNION', query));
638
+ }
639
+
640
+ /**
641
+ * Combines this query with another using UNION ALL
642
+ * @param query - Query to union with
643
+ * @returns New query builder instance with the set operation
644
+ */
645
+ unionAll<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
646
+ return this.clone(this.applySetOperation('UNION ALL', query));
647
+ }
648
+
649
+ /**
650
+ * Combines this query with another using INTERSECT
651
+ * @param query - Query to intersect with
652
+ * @returns New query builder instance with the set operation
653
+ */
654
+ intersect<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
655
+ return this.clone(this.applySetOperation('INTERSECT', query));
656
+ }
657
+
658
+ /**
659
+ * Combines this query with another using EXCEPT
660
+ * @param query - Query to subtract
661
+ * @returns New query builder instance with the set operation
662
+ */
663
+ except<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
664
+ return this.clone(this.applySetOperation('EXCEPT', query));
665
+ }
666
+
667
+ /**
668
+ * Adds a WHERE EXISTS condition to the query
669
+ * @param subquery - Subquery to check for existence
670
+ * @returns New query builder instance with the WHERE EXISTS condition
671
+ */
672
+ whereExists<TSub extends TableDef>(
673
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
674
+ correlate?: ExpressionNode
675
+ ): SelectQueryBuilder<T, TTable> {
676
+ const subAst = resolveSelectQuery(subquery);
677
+ const correlated = this.applyCorrelation(subAst, correlate);
678
+ return this.where(exists(correlated));
679
+ }
680
+
681
+ /**
682
+ * Adds a WHERE NOT EXISTS condition to the query
683
+ * @param subquery - Subquery to check for non-existence
684
+ * @returns New query builder instance with the WHERE NOT EXISTS condition
685
+ */
686
+ whereNotExists<TSub extends TableDef>(
687
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
688
+ correlate?: ExpressionNode
689
+ ): SelectQueryBuilder<T, TTable> {
690
+ const subAst = resolveSelectQuery(subquery);
691
+ const correlated = this.applyCorrelation(subAst, correlate);
692
+ return this.where(notExists(correlated));
693
+ }
694
+
695
+ /**
696
+ * Adds a WHERE EXISTS condition based on a relationship
697
+ * @param relationName - Name of the relationship to check
698
+ * @param callback - Optional callback to modify the relationship query
699
+ * @returns New query builder instance with the relationship existence check
700
+ */
701
+ whereHas<K extends keyof TTable['relations'] & string>(
702
+ relationName: K,
703
+ callbackOrOptions?: RelationCallback | WhereHasOptions,
704
+ maybeOptions?: WhereHasOptions
705
+ ): SelectQueryBuilder<T, TTable> {
706
+ const relation = this.env.table.relations[relationName];
707
+
708
+ if (!relation) {
709
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
710
+ }
711
+
712
+ const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
713
+ const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
714
+
715
+ let subQb = this.createChildBuilder<unknown, typeof relation.target>(relation.target);
716
+
717
+ if (callback) {
718
+ subQb = callback(subQb);
719
+ }
720
+
721
+ const subAst = subQb.getAST();
722
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
723
+
724
+ return this.where(exists(finalSubAst));
725
+ }
726
+
727
+ /**
728
+ * Adds a WHERE NOT EXISTS condition based on a relationship
729
+ * @param relationName - Name of the relationship to check
730
+ * @param callback - Optional callback to modify the relationship query
731
+ * @returns New query builder instance with the relationship non-existence check
732
+ */
733
+ whereHasNot<K extends keyof TTable['relations'] & string>(
734
+ relationName: K,
735
+ callbackOrOptions?: RelationCallback | WhereHasOptions,
736
+ maybeOptions?: WhereHasOptions
737
+ ): SelectQueryBuilder<T, TTable> {
738
+ const relation = this.env.table.relations[relationName];
739
+
740
+ if (!relation) {
741
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
742
+ }
743
+
744
+ const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions as RelationCallback : undefined;
745
+ const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as WhereHasOptions | undefined;
746
+
747
+ let subQb = this.createChildBuilder<unknown, typeof relation.target>(relation.target);
748
+
749
+ if (callback) {
750
+ subQb = callback(subQb);
751
+ }
752
+
753
+ const subAst = subQb.getAST();
754
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
755
+
756
+ return this.where(notExists(finalSubAst));
757
+ }
758
+
759
+
760
+
761
+ /**
762
+ * Compiles the query to SQL for a specific dialect
763
+ * @param dialect - Database dialect to compile for
764
+ * @returns Compiled query with SQL and parameters
765
+ */
766
+ compile(dialect: SelectDialectInput): CompiledQuery {
767
+ const resolved = resolveDialectInput(dialect);
768
+ return resolved.compileSelect(this.getAST());
769
+ }
770
+
771
+ /**
772
+ * Converts the query to SQL string for a specific dialect
773
+ * @param dialect - Database dialect to generate SQL for
774
+ * @returns SQL string representation of the query
775
+ */
776
+ toSql(dialect: SelectDialectInput): string {
777
+ return this.compile(dialect).sql;
778
+ }
779
+
780
+ /**
781
+ * Gets the hydration plan for the query
782
+ * @returns Hydration plan or undefined if none exists
783
+ */
784
+ getHydrationPlan(): HydrationPlan | undefined {
785
+ return this.context.hydration.getPlan();
786
+ }
787
+
788
+ /**
789
+ * Gets the Abstract Syntax Tree (AST) representation of the query
790
+ * @returns Query AST with hydration applied
791
+ */
792
+ getAST(): SelectQueryNode {
793
+ return this.context.hydration.applyToAst(this.context.state.ast);
794
+ }
795
+ }