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
@@ -21,26 +21,26 @@ const assertNever = (value: never): never => {
21
21
  * @param relation - Relation definition
22
22
  * @returns Expression node representing the join condition
23
23
  */
24
- const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
25
- const rootTable = rootAlias || root.name;
26
- const defaultLocalKey =
27
- relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
28
- ? findPrimaryKey(root)
29
- : findPrimaryKey(relation.target);
30
- const localKey = relation.localKey || defaultLocalKey;
24
+ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
25
+ const rootTable = rootAlias || root.name;
26
+ const defaultLocalKey =
27
+ relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
28
+ ? findPrimaryKey(root)
29
+ : findPrimaryKey(relation.target);
30
+ const localKey = relation.localKey || defaultLocalKey;
31
31
 
32
32
  switch (relation.type) {
33
- case RelationKinds.HasMany:
34
- case RelationKinds.HasOne:
35
- return eq(
36
- { type: 'Column', table: relation.target.name, name: relation.foreignKey },
37
- { type: 'Column', table: rootTable, name: localKey }
38
- );
39
- case RelationKinds.BelongsTo:
40
- return eq(
41
- { type: 'Column', table: relation.target.name, name: localKey },
42
- { type: 'Column', table: rootTable, name: relation.foreignKey }
43
- );
33
+ case RelationKinds.HasMany:
34
+ case RelationKinds.HasOne:
35
+ return eq(
36
+ { type: 'Column', table: relation.target.name, name: relation.foreignKey },
37
+ { type: 'Column', table: rootTable, name: localKey }
38
+ );
39
+ case RelationKinds.BelongsTo:
40
+ return eq(
41
+ { type: 'Column', table: relation.target.name, name: localKey },
42
+ { type: 'Column', table: rootTable, name: relation.foreignKey }
43
+ );
44
44
  case RelationKinds.BelongsToMany:
45
45
  throw new Error('BelongsToMany relations do not support the standard join condition builder');
46
46
  default:
@@ -50,25 +50,36 @@ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?
50
50
 
51
51
  /**
52
52
  * Builds the join nodes required to include a BelongsToMany relation.
53
+ * @param root - The root table definition
54
+ * @param relationName - Name of the relation being joined
55
+ * @param relation - The BelongsToMany relation definition
56
+ * @param joinKind - The type of join to perform
57
+ * @param extra - Optional additional conditions for the target join
58
+ * @param rootAlias - Optional alias for the root table
59
+ * @returns Array of join nodes for the pivot and target tables
53
60
  */
54
- export const buildBelongsToManyJoins = (
55
- root: TableDef,
56
- relationName: string,
57
- relation: BelongsToManyRelation,
58
- joinKind: JoinKind,
59
- extra?: ExpressionNode,
60
- rootAlias?: string
61
- ): JoinNode[] => {
62
- const rootKey = relation.localKey || findPrimaryKey(root);
63
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
64
- const rootTable = rootAlias || root.name;
65
-
66
- const pivotCondition = eq(
67
- { type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
68
- { type: 'Column', table: rootTable, name: rootKey }
69
- );
61
+ export const buildBelongsToManyJoins = (
62
+ root: TableDef,
63
+ relationName: string,
64
+ relation: BelongsToManyRelation,
65
+ joinKind: JoinKind,
66
+ extra?: ExpressionNode,
67
+ rootAlias?: string
68
+ ): JoinNode[] => {
69
+ const rootKey = relation.localKey || findPrimaryKey(root);
70
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
71
+ const rootTable = rootAlias || root.name;
70
72
 
71
- const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
73
+ const pivotCondition = eq(
74
+ { type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
75
+ { type: 'Column', table: rootTable, name: rootKey }
76
+ );
77
+
78
+ const pivotJoin = createJoinNode(
79
+ joinKind,
80
+ { type: 'Table', name: relation.pivotTable.name, schema: relation.pivotTable.schema },
81
+ pivotCondition
82
+ );
72
83
 
73
84
  let targetCondition: ExpressionNode = eq(
74
85
  { type: 'Column', table: relation.target.name, name: targetKey },
@@ -81,7 +92,7 @@ export const buildBelongsToManyJoins = (
81
92
 
82
93
  const targetJoin = createJoinNode(
83
94
  joinKind,
84
- relation.target.name,
95
+ { type: 'Table', name: relation.target.name, schema: relation.target.schema },
85
96
  targetCondition,
86
97
  relationName
87
98
  );
@@ -94,24 +105,26 @@ export const buildBelongsToManyJoins = (
94
105
  * @param root - Root table definition
95
106
  * @param relation - Relation definition
96
107
  * @param extra - Optional additional expression to combine with AND
108
+ * @param rootAlias - Optional alias for the root table
97
109
  * @returns Expression node representing the complete join condition
98
110
  */
99
- export const buildRelationJoinCondition = (
100
- root: TableDef,
101
- relation: RelationDef,
102
- extra?: ExpressionNode,
103
- rootAlias?: string
104
- ): ExpressionNode => {
105
- const base = baseRelationCondition(root, relation, rootAlias);
106
- return extra ? and(base, extra) : base;
107
- };
111
+ export const buildRelationJoinCondition = (
112
+ root: TableDef,
113
+ relation: RelationDef,
114
+ extra?: ExpressionNode,
115
+ rootAlias?: string
116
+ ): ExpressionNode => {
117
+ const base = baseRelationCondition(root, relation, rootAlias);
118
+ return extra ? and(base, extra) : base;
119
+ };
108
120
 
109
121
  /**
110
122
  * Builds a relation correlation condition for subqueries
111
123
  * @param root - Root table definition
112
124
  * @param relation - Relation definition
125
+ * @param rootAlias - Optional alias for the root table
113
126
  * @returns Expression node representing the correlation condition
114
127
  */
115
- export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
116
- return baseRelationCondition(root, relation, rootAlias);
117
- };
128
+ export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
129
+ return baseRelationCondition(root, relation, rootAlias);
130
+ };
@@ -20,8 +20,8 @@ import {
20
20
  } from './relation-conditions.js';
21
21
  import { JoinKind, JOIN_KINDS } from '../core/sql/sql.js';
22
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';
23
+ import { createJoinNode } from '../core/ast/join-node.js';
24
+ import { getJoinRelationName } from '../core/ast/join-metadata.js';
25
25
  import { makeRelationAlias } from './relation-alias.js';
26
26
  import { buildDefaultPivotColumns } from './relation-utils.js';
27
27
 
@@ -70,17 +70,17 @@ export class RelationService {
70
70
  * @param predicate - Optional predicate expression
71
71
  * @returns Relation result with updated state and hydration
72
72
  */
73
- match(
74
- relationName: string,
75
- predicate?: ExpressionNode
76
- ): RelationResult {
77
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
78
- const pk = findPrimaryKey(this.table);
79
- const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
80
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
81
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
82
- return { state: nextState, hydration: joined.hydration };
83
- }
73
+ match(
74
+ relationName: string,
75
+ predicate?: ExpressionNode
76
+ ): RelationResult {
77
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
78
+ const pk = findPrimaryKey(this.table);
79
+ const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
80
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
81
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
82
+ return { state: nextState, hydration: joined.hydration };
83
+ }
84
84
 
85
85
  /**
86
86
  * Includes a relation in the query result
@@ -94,7 +94,7 @@ export class RelationService {
94
94
 
95
95
  const relation = this.getRelation(relationName);
96
96
  const aliasPrefix = options?.aliasPrefix ?? relationName;
97
- const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
97
+ const alreadyJoined = state.ast.joins.some(j => getJoinRelationName(j) === relationName);
98
98
 
99
99
  if (!alreadyJoined) {
100
100
  const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
@@ -114,7 +114,7 @@ export class RelationService {
114
114
  prefix: string,
115
115
  keys: string[],
116
116
  missingMsg: (col: string) => string
117
- ) : Record<string, ColumnDef> => {
117
+ ): Record<string, ColumnDef> => {
118
118
  return keys.reduce((acc, key) => {
119
119
  const def = columns[key];
120
120
  if (!def) {
@@ -190,22 +190,22 @@ export class RelationService {
190
190
  * @param ast - Query AST to modify
191
191
  * @returns Modified query AST with relation correlation
192
192
  */
193
- applyRelationCorrelation(
194
- relationName: string,
195
- ast: SelectQueryNode,
196
- additionalCorrelation?: ExpressionNode
197
- ): SelectQueryNode {
198
- const relation = this.getRelation(relationName);
199
- const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
200
- let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
201
- if (additionalCorrelation) {
202
- correlation = and(correlation, additionalCorrelation);
203
- }
204
- const whereInSubquery = ast.where
205
- ? and(correlation, ast.where)
206
- : correlation;
207
-
208
- return {
193
+ applyRelationCorrelation(
194
+ relationName: string,
195
+ ast: SelectQueryNode,
196
+ additionalCorrelation?: ExpressionNode
197
+ ): SelectQueryNode {
198
+ const relation = this.getRelation(relationName);
199
+ const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
200
+ let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
201
+ if (additionalCorrelation) {
202
+ correlation = and(correlation, additionalCorrelation);
203
+ }
204
+ const whereInSubquery = ast.where
205
+ ? and(correlation, ast.where)
206
+ : correlation;
207
+
208
+ return {
209
209
  ...ast,
210
210
  where: whereInSubquery
211
211
  };
@@ -219,28 +219,33 @@ export class RelationService {
219
219
  * @param extraCondition - Additional join condition
220
220
  * @returns Updated query state with join
221
221
  */
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(joinKind, relation.target.name, condition, relationName);
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
+ );
244
249
 
245
250
  return this.astService(state).withJoin(joinNode);
246
251
  }
@@ -284,15 +289,15 @@ export class RelationService {
284
289
  * @param state - Current query state
285
290
  * @returns QueryAstService instance
286
291
  */
287
- private astService(state: SelectQueryState = this.state): QueryAstService {
288
- return this.createQueryAstService(this.table, state);
289
- }
290
-
291
- private rootTableName(): string {
292
- const from = this.state.ast.from;
293
- if (from.type === 'Table' && from.alias) return from.alias;
294
- return this.table.name;
295
- }
296
- }
292
+ private astService(state: SelectQueryState = this.state): QueryAstService {
293
+ return this.createQueryAstService(this.table, state);
294
+ }
295
+
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
+ }
297
302
 
298
303
  export type { RelationResult } from './relation-projection-helper.js';
@@ -2,6 +2,9 @@ import { BelongsToManyRelation } from '../schema/relation.js';
2
2
 
3
3
  /**
4
4
  * Builds a default set of pivot columns, excluding keys used for joins.
5
+ * @param rel - The BelongsToMany relation definition
6
+ * @param pivotPk - The primary key column name of the pivot table
7
+ * @returns Array of column names that can be included in pivot table selections
5
8
  */
6
9
  export const buildDefaultPivotColumns = (
7
10
  rel: BelongsToManyRelation,
@@ -0,0 +1,40 @@
1
+ import { SelectQueryNode } from '../../core/ast/query.js';
2
+ import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
3
+ import { QueryAstService } from '../query-ast-service.js';
4
+ import { SelectQueryState } from '../select-query-state.js';
5
+
6
+ /**
7
+ * Facet responsible for Common Table Expressions (WITH clauses)
8
+ */
9
+ export class SelectCTEFacet {
10
+ /**
11
+ * Creates a new SelectCTEFacet instance
12
+ * @param env - Query builder environment
13
+ * @param createAstService - Function to create AST service
14
+ */
15
+ constructor(
16
+ private readonly env: SelectQueryBuilderEnvironment,
17
+ private readonly createAstService: (state: SelectQueryState) => QueryAstService
18
+ ) { }
19
+
20
+ /**
21
+ * Adds a Common Table Expression to the query
22
+ * @param context - Current query context
23
+ * @param name - CTE name
24
+ * @param subAst - CTE query AST
25
+ * @param columns - Optional column names
26
+ * @param recursive - Whether the CTE is recursive
27
+ * @returns Updated query context with CTE
28
+ */
29
+ withCTE(
30
+ context: SelectQueryBuilderContext,
31
+ name: string,
32
+ subAst: SelectQueryNode,
33
+ columns: string[] | undefined,
34
+ recursive: boolean
35
+ ): SelectQueryBuilderContext {
36
+ const astService = this.createAstService(context.state);
37
+ const nextState = astService.withCte(name, subAst, columns, recursive);
38
+ return { state: nextState, hydration: context.hydration };
39
+ }
40
+ }
@@ -0,0 +1,80 @@
1
+ import { SelectQueryNode } from '../../core/ast/query.js';
2
+ import { OperandNode } from '../../core/ast/expression.js';
3
+ import { derivedTable, fnTable } from '../../core/ast/builders.js';
4
+ import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
5
+ import { QueryAstService } from '../query-ast-service.js';
6
+ import { SelectQueryState } from '../select-query-state.js';
7
+
8
+ /**
9
+ * Facet responsible for FROM clause operations
10
+ */
11
+ export class SelectFromFacet {
12
+ /**
13
+ * Creates a new SelectFromFacet instance
14
+ * @param env - Query builder environment
15
+ * @param createAstService - Function to create AST service
16
+ */
17
+ constructor(
18
+ private readonly env: SelectQueryBuilderEnvironment,
19
+ private readonly createAstService: (state: SelectQueryState) => QueryAstService
20
+ ) { }
21
+
22
+ /**
23
+ * Applies an alias to the FROM table
24
+ * @param context - Current query context
25
+ * @param alias - Alias to apply
26
+ * @returns Updated query context with aliased FROM
27
+ */
28
+ as(context: SelectQueryBuilderContext, alias: string): SelectQueryBuilderContext {
29
+ const from = context.state.ast.from;
30
+ if (from.type !== 'Table') {
31
+ throw new Error('Cannot alias non-table FROM sources');
32
+ }
33
+ const nextFrom = { ...from, alias };
34
+ const astService = this.createAstService(context.state);
35
+ const nextState = astService.withFrom(nextFrom);
36
+ return { state: nextState, hydration: context.hydration };
37
+ }
38
+
39
+ /**
40
+ * Sets the FROM clause to a subquery
41
+ * @param context - Current query context
42
+ * @param subAst - Subquery AST
43
+ * @param alias - Alias for the subquery
44
+ * @param columnAliases - Optional column aliases
45
+ * @returns Updated query context with subquery FROM
46
+ */
47
+ fromSubquery(
48
+ context: SelectQueryBuilderContext,
49
+ subAst: SelectQueryNode,
50
+ alias: string,
51
+ columnAliases?: string[]
52
+ ): SelectQueryBuilderContext {
53
+ const fromNode = derivedTable(subAst, alias, columnAliases);
54
+ const astService = this.createAstService(context.state);
55
+ const nextState = astService.withFrom(fromNode);
56
+ return { state: nextState, hydration: context.hydration };
57
+ }
58
+
59
+ /**
60
+ * Sets the FROM clause to a function table
61
+ * @param context - Current query context
62
+ * @param name - Function name
63
+ * @param args - Function arguments
64
+ * @param alias - Optional alias for the function table
65
+ * @param options - Optional function table options
66
+ * @returns Updated query context with function table FROM
67
+ */
68
+ fromFunctionTable(
69
+ context: SelectQueryBuilderContext,
70
+ name: string,
71
+ args: OperandNode[],
72
+ alias?: string,
73
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
74
+ ): SelectQueryBuilderContext {
75
+ const functionTable = fnTable(name, args, alias, options);
76
+ const astService = this.createAstService(context.state);
77
+ const nextState = astService.withFrom(functionTable);
78
+ return { state: nextState, hydration: context.hydration };
79
+ }
80
+ }
@@ -0,0 +1,62 @@
1
+ import { TableDef } from '../../schema/table.js';
2
+ import { BinaryExpressionNode } from '../../core/ast/expression.js';
3
+ import { SelectQueryNode } from '../../core/ast/query.js';
4
+ import { JoinKind } from '../../core/sql/sql.js';
5
+ import { derivedTable, fnTable } from '../../core/ast/builders.js';
6
+ import { createJoinNode } from '../../core/ast/join-node.js';
7
+ import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
8
+ import { QueryAstService } from '../query-ast-service.js';
9
+ import { OperandNode } from '../../core/ast/expression.js';
10
+ import { SelectQueryState } from '../select-query-state.js';
11
+
12
+ /**
13
+ * Facet responsible for JOIN operations
14
+ */
15
+ export class SelectJoinFacet {
16
+ constructor(
17
+ private readonly env: SelectQueryBuilderEnvironment,
18
+ private readonly createAstService: (state: SelectQueryState) => QueryAstService
19
+ ) { }
20
+
21
+ applyJoin(
22
+ context: SelectQueryBuilderContext,
23
+ table: TableDef,
24
+ condition: BinaryExpressionNode,
25
+ kind: JoinKind
26
+ ): SelectQueryBuilderContext {
27
+ const joinNode = createJoinNode(kind, { type: 'Table', name: table.name, schema: table.schema }, condition);
28
+ const astService = this.createAstService(context.state);
29
+ const nextState = astService.withJoin(joinNode);
30
+ return { state: nextState, hydration: context.hydration };
31
+ }
32
+
33
+ joinSubquery(
34
+ context: SelectQueryBuilderContext,
35
+ subAst: SelectQueryNode,
36
+ alias: string,
37
+ condition: BinaryExpressionNode,
38
+ joinKind: JoinKind,
39
+ columnAliases?: string[]
40
+ ): SelectQueryBuilderContext {
41
+ const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
42
+ const astService = this.createAstService(context.state);
43
+ const nextState = astService.withJoin(joinNode);
44
+ return { state: nextState, hydration: context.hydration };
45
+ }
46
+
47
+ joinFunctionTable(
48
+ context: SelectQueryBuilderContext,
49
+ name: string,
50
+ args: OperandNode[],
51
+ alias: string,
52
+ condition: BinaryExpressionNode,
53
+ joinKind: JoinKind,
54
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
55
+ ): SelectQueryBuilderContext {
56
+ const functionTable = fnTable(name, args, alias, options);
57
+ const joinNode = createJoinNode(joinKind, functionTable, condition);
58
+ const astService = this.createAstService(context.state);
59
+ const nextState = astService.withJoin(joinNode);
60
+ return { state: nextState, hydration: context.hydration };
61
+ }
62
+ }
@@ -0,0 +1,103 @@
1
+ import { ColumnDef } from '../../schema/column.js';
2
+ import { ExpressionNode } from '../../core/ast/expression.js';
3
+ import { OrderingTerm } from '../../core/ast/query.js';
4
+ import { OrderDirection } from '../../core/sql/sql.js';
5
+ import { SelectQueryBuilderContext, SelectQueryBuilderEnvironment } from '../select-query-builder-deps.js';
6
+ import { QueryAstService } from '../query-ast-service.js';
7
+ import { SelectQueryState } from '../select-query-state.js';
8
+
9
+ /**
10
+ * Facet responsible for filtering and ordering operations
11
+ */
12
+ export class SelectPredicateFacet {
13
+ /**
14
+ * Creates a new SelectPredicateFacet instance
15
+ * @param env - Query builder environment
16
+ * @param createAstService - Function to create AST service
17
+ */
18
+ constructor(
19
+ private readonly env: SelectQueryBuilderEnvironment,
20
+ private readonly createAstService: (state: SelectQueryState) => QueryAstService
21
+ ) { }
22
+
23
+ /**
24
+ * Adds a WHERE condition to the query
25
+ * @param context - Current query context
26
+ * @param expr - WHERE expression
27
+ * @returns Updated query context with WHERE condition
28
+ */
29
+ where(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
30
+ const astService = this.createAstService(context.state);
31
+ const nextState = astService.withWhere(expr);
32
+ return { state: nextState, hydration: context.hydration };
33
+ }
34
+
35
+ /**
36
+ * Adds a GROUP BY clause to the query
37
+ * @param context - Current query context
38
+ * @param term - Column or ordering term to group by
39
+ * @returns Updated query context with GROUP BY clause
40
+ */
41
+ groupBy(context: SelectQueryBuilderContext, term: ColumnDef | OrderingTerm): SelectQueryBuilderContext {
42
+ const astService = this.createAstService(context.state);
43
+ const nextState = astService.withGroupBy(term);
44
+ return { state: nextState, hydration: context.hydration };
45
+ }
46
+
47
+ /**
48
+ * Adds a HAVING condition to the query
49
+ * @param context - Current query context
50
+ * @param expr - HAVING expression
51
+ * @returns Updated query context with HAVING condition
52
+ */
53
+ having(context: SelectQueryBuilderContext, expr: ExpressionNode): SelectQueryBuilderContext {
54
+ const astService = this.createAstService(context.state);
55
+ const nextState = astService.withHaving(expr);
56
+ return { state: nextState, hydration: context.hydration };
57
+ }
58
+
59
+ /**
60
+ * Adds an ORDER BY clause to the query
61
+ * @param context - Current query context
62
+ * @param term - Column or ordering term to order by
63
+ * @param direction - Order direction
64
+ * @param nulls - Nulls ordering
65
+ * @param collation - Collation
66
+ * @returns Updated query context with ORDER BY clause
67
+ */
68
+ orderBy(
69
+ context: SelectQueryBuilderContext,
70
+ term: ColumnDef | OrderingTerm,
71
+ direction: OrderDirection,
72
+ nulls?: 'FIRST' | 'LAST',
73
+ collation?: string
74
+ ): SelectQueryBuilderContext {
75
+ const astService = this.createAstService(context.state);
76
+ const nextState = astService.withOrderBy(term, direction, nulls, collation);
77
+ return { state: nextState, hydration: context.hydration };
78
+ }
79
+
80
+ /**
81
+ * Adds a LIMIT clause to the query
82
+ * @param context - Current query context
83
+ * @param n - Maximum number of rows
84
+ * @returns Updated query context with LIMIT clause
85
+ */
86
+ limit(context: SelectQueryBuilderContext, n: number): SelectQueryBuilderContext {
87
+ const astService = this.createAstService(context.state);
88
+ const nextState = astService.withLimit(n);
89
+ return { state: nextState, hydration: context.hydration };
90
+ }
91
+
92
+ /**
93
+ * Adds an OFFSET clause to the query
94
+ * @param context - Current query context
95
+ * @param n - Number of rows to skip
96
+ * @returns Updated query context with OFFSET clause
97
+ */
98
+ offset(context: SelectQueryBuilderContext, n: number): SelectQueryBuilderContext {
99
+ const astService = this.createAstService(context.state);
100
+ const nextState = astService.withOffset(n);
101
+ return { state: nextState, hydration: context.hydration };
102
+ }
103
+ }