metal-orm 1.0.42 → 1.0.44

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 (122) hide show
  1. package/README.md +195 -37
  2. package/dist/index.cjs +1014 -538
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1267 -371
  5. package/dist/index.d.ts +1267 -371
  6. package/dist/index.js +1012 -536
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -2
  9. package/scripts/run-eslint.mjs +34 -0
  10. package/src/codegen/typescript.ts +32 -15
  11. package/src/core/ast/adapters.ts +8 -2
  12. package/src/core/ast/builders.ts +105 -76
  13. package/src/core/ast/expression-builders.ts +430 -392
  14. package/src/core/ast/expression-nodes.ts +14 -5
  15. package/src/core/ast/expression-visitor.ts +56 -14
  16. package/src/core/ast/helpers.ts +23 -0
  17. package/src/core/ast/join-node.ts +18 -2
  18. package/src/core/ast/query.ts +6 -6
  19. package/src/core/ast/window-functions.ts +10 -2
  20. package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
  21. package/src/core/ddl/dialects/index.ts +1 -0
  22. package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
  23. package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
  24. package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
  25. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  26. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
  27. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  28. package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
  29. package/src/core/ddl/introspect/context.ts +6 -0
  30. package/src/core/ddl/introspect/functions/postgres.ts +13 -0
  31. package/src/core/ddl/introspect/mssql.ts +53 -8
  32. package/src/core/ddl/introspect/mysql.ts +32 -6
  33. package/src/core/ddl/introspect/postgres.ts +102 -34
  34. package/src/core/ddl/introspect/registry.ts +14 -0
  35. package/src/core/ddl/introspect/run-select.ts +19 -4
  36. package/src/core/ddl/introspect/sqlite.ts +78 -11
  37. package/src/core/ddl/introspect/types.ts +0 -1
  38. package/src/core/ddl/introspect/utils.ts +21 -3
  39. package/src/core/ddl/naming-strategy.ts +6 -0
  40. package/src/core/ddl/schema-dialect.ts +20 -6
  41. package/src/core/ddl/schema-diff.ts +22 -0
  42. package/src/core/ddl/schema-generator.ts +26 -12
  43. package/src/core/ddl/schema-plan-executor.ts +6 -0
  44. package/src/core/ddl/schema-types.ts +6 -0
  45. package/src/core/ddl/sql-writing.ts +4 -4
  46. package/src/core/dialect/abstract.ts +19 -7
  47. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  48. package/src/core/dialect/base/join-compiler.ts +5 -3
  49. package/src/core/dialect/base/returning-strategy.ts +1 -0
  50. package/src/core/dialect/base/sql-dialect.ts +3 -3
  51. package/src/core/dialect/mssql/functions.ts +24 -25
  52. package/src/core/dialect/mssql/index.ts +1 -4
  53. package/src/core/dialect/mysql/functions.ts +0 -1
  54. package/src/core/dialect/postgres/functions.ts +33 -34
  55. package/src/core/dialect/postgres/index.ts +1 -0
  56. package/src/core/dialect/sqlite/functions.ts +18 -19
  57. package/src/core/dialect/sqlite/index.ts +2 -0
  58. package/src/core/execution/db-executor.ts +1 -1
  59. package/src/core/execution/executors/mysql-executor.ts +2 -2
  60. package/src/core/execution/executors/postgres-executor.ts +1 -1
  61. package/src/core/execution/pooling/pool.ts +12 -5
  62. package/src/core/functions/datetime.ts +58 -34
  63. package/src/core/functions/numeric.ts +96 -31
  64. package/src/core/functions/standard-strategy.ts +35 -0
  65. package/src/core/functions/text.ts +84 -23
  66. package/src/core/functions/types.ts +23 -8
  67. package/src/decorators/bootstrap.ts +42 -11
  68. package/src/decorators/column.ts +20 -11
  69. package/src/decorators/decorator-metadata.ts +30 -9
  70. package/src/decorators/entity.ts +29 -5
  71. package/src/decorators/index.ts +3 -0
  72. package/src/decorators/relations.ts +34 -11
  73. package/src/orm/als.ts +34 -9
  74. package/src/orm/entity-context.ts +62 -8
  75. package/src/orm/entity-meta.ts +8 -8
  76. package/src/orm/entity-metadata.ts +131 -16
  77. package/src/orm/entity.ts +28 -29
  78. package/src/orm/execute.ts +19 -4
  79. package/src/orm/hydration.ts +42 -39
  80. package/src/orm/identity-map.ts +1 -1
  81. package/src/orm/lazy-batch.ts +74 -104
  82. package/src/orm/orm-session.ts +24 -23
  83. package/src/orm/orm.ts +2 -5
  84. package/src/orm/relation-change-processor.ts +12 -11
  85. package/src/orm/relations/belongs-to.ts +11 -11
  86. package/src/orm/relations/has-many.ts +54 -10
  87. package/src/orm/relations/has-one.ts +8 -7
  88. package/src/orm/relations/many-to-many.ts +13 -13
  89. package/src/orm/runtime-types.ts +4 -4
  90. package/src/orm/save-graph.ts +31 -25
  91. package/src/orm/unit-of-work.ts +17 -17
  92. package/src/query/index.ts +74 -0
  93. package/src/query/target.ts +46 -0
  94. package/src/query-builder/delete-query-state.ts +30 -0
  95. package/src/query-builder/delete.ts +64 -18
  96. package/src/query-builder/hydration-manager.ts +52 -5
  97. package/src/query-builder/insert-query-state.ts +30 -0
  98. package/src/query-builder/insert.ts +58 -10
  99. package/src/query-builder/query-ast-service.ts +7 -2
  100. package/src/query-builder/query-resolution.ts +78 -0
  101. package/src/query-builder/raw-column-parser.ts +7 -1
  102. package/src/query-builder/relation-alias.ts +7 -0
  103. package/src/query-builder/relation-conditions.ts +61 -48
  104. package/src/query-builder/relation-service.ts +68 -63
  105. package/src/query-builder/relation-utils.ts +3 -0
  106. package/src/query-builder/select/cte-facet.ts +40 -0
  107. package/src/query-builder/select/from-facet.ts +80 -0
  108. package/src/query-builder/select/join-facet.ts +62 -0
  109. package/src/query-builder/select/predicate-facet.ts +103 -0
  110. package/src/query-builder/select/projection-facet.ts +69 -0
  111. package/src/query-builder/select/relation-facet.ts +81 -0
  112. package/src/query-builder/select/setop-facet.ts +36 -0
  113. package/src/query-builder/select-helpers.ts +15 -2
  114. package/src/query-builder/select-query-builder-deps.ts +19 -1
  115. package/src/query-builder/select-query-state.ts +2 -1
  116. package/src/query-builder/select.ts +795 -1163
  117. package/src/query-builder/update-query-state.ts +52 -0
  118. package/src/query-builder/update.ts +69 -18
  119. package/src/schema/column.ts +26 -26
  120. package/src/schema/table-guards.ts +31 -0
  121. package/src/schema/table.ts +47 -18
  122. package/src/schema/types.ts +22 -22
@@ -1,392 +1,430 @@
1
- import { SelectQueryNode } from './query.js';
2
- import { SqlOperator } from '../sql/sql.js';
3
- import { ColumnRef } from './types.js';
4
- import {
5
- ColumnNode,
6
- FunctionNode,
7
- LiteralNode,
8
- JsonPathNode,
9
- OperandNode,
10
- CaseExpressionNode,
11
- WindowFunctionNode,
12
- BinaryExpressionNode,
13
- ExpressionNode,
14
- LogicalExpressionNode,
15
- NullExpressionNode,
16
- InExpressionNode,
17
- ExistsExpressionNode,
18
- InExpressionRight,
19
- ScalarSubqueryNode,
20
- BetweenExpressionNode,
21
- isOperandNode,
22
- AliasRefNode,
23
- ArithmeticExpressionNode
24
- } from './expression-nodes.js';
25
-
26
- export type LiteralValue = LiteralNode['value'];
27
- export type ValueOperandInput = OperandNode | LiteralValue;
28
-
29
- /**
30
- * Converts a primitive or existing operand into an operand node
31
- * @param value - Value or operand to normalize
32
- * @returns OperandNode representing the value
33
- */
34
- export const valueToOperand = (value: ValueOperandInput): OperandNode => {
35
- if (isOperandNode(value)) {
36
- return value;
37
- }
38
-
39
- return {
40
- type: 'Literal',
41
- value
42
- } as LiteralNode;
43
- };
44
-
45
- const toNode = (col: ColumnRef | OperandNode): OperandNode => {
46
- if (isOperandNode(col)) return col as OperandNode;
47
- const def = col as ColumnRef;
48
- return { type: 'Column', table: def.table || 'unknown', name: def.name };
49
- };
50
-
51
- const toLiteralNode = (value: string | number | boolean | null): LiteralNode => ({
52
- type: 'Literal',
53
- value
54
- });
55
-
56
- const isLiteralValue = (value: unknown): value is LiteralValue =>
57
- value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
58
-
59
- export const isValueOperandInput = (value: unknown): value is ValueOperandInput =>
60
- isOperandNode(value) || isLiteralValue(value);
61
-
62
- const toOperand = (val: OperandNode | ColumnRef | LiteralValue): OperandNode => {
63
- if (isLiteralValue(val)) {
64
- return valueToOperand(val);
65
- }
66
-
67
- return toNode(val);
68
- };
69
-
70
- export type SelectQueryInput = SelectQueryNode | { getAST(): SelectQueryNode };
71
-
72
- const hasQueryAst = (value: SelectQueryInput): value is { getAST(): SelectQueryNode } =>
73
- typeof (value as { getAST?: unknown }).getAST === 'function';
74
-
75
- const resolveSelectQueryNode = (query: SelectQueryInput): SelectQueryNode =>
76
- hasQueryAst(query) ? query.getAST() : query;
77
-
78
- const toScalarSubqueryNode = (query: SelectQueryInput): ScalarSubqueryNode => ({
79
- type: 'ScalarSubquery',
80
- query: resolveSelectQueryNode(query)
81
- });
82
-
83
- export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
84
- /**
85
- * Marks a column reference as an outer-scope reference for correlated subqueries.
86
- * Primarily semantic; SQL rendering still uses the provided table/alias name.
87
- */
88
- export const outerRef = (col: ColumnRef | ColumnNode): ColumnNode => ({
89
- ...columnOperand(col),
90
- scope: 'outer'
91
- });
92
-
93
- /**
94
- * References a SELECT alias (useful for ORDER BY / GROUP BY).
95
- */
96
- export const aliasRef = (name: string): AliasRefNode => ({
97
- type: 'AliasRef',
98
- name
99
- });
100
-
101
- /**
102
- * Creates an outer-scoped column reference using a specific table or alias name.
103
- */
104
- export const correlateBy = (table: string, column: string): ColumnNode => outerRef({ name: column, table });
105
-
106
- const createBinaryExpression = (
107
- operator: SqlOperator,
108
- left: OperandNode | ColumnRef,
109
- right: OperandNode | ColumnRef | string | number | boolean | null,
110
- escape?: string
111
- ): BinaryExpressionNode => {
112
- const node: BinaryExpressionNode = {
113
- type: 'BinaryExpression',
114
- left: toNode(left),
115
- operator,
116
- right: toOperand(right)
117
- };
118
-
119
- if (escape !== undefined) {
120
- node.escape = toLiteralNode(escape);
121
- }
122
-
123
- return node;
124
- };
125
-
126
- /**
127
- * Creates an equality expression (left = right)
128
- * @param left - Left operand
129
- * @param right - Right operand
130
- * @returns Binary expression node with equality operator
131
- */
132
- export const eq = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number | boolean): BinaryExpressionNode =>
133
- createBinaryExpression('=', left, right);
134
-
135
- /**
136
- * Creates a not equal expression (left != right)
137
- */
138
- export const neq = (
139
- left: OperandNode | ColumnRef,
140
- right: OperandNode | ColumnRef | string | number | boolean
141
- ): BinaryExpressionNode => createBinaryExpression('!=', left, right);
142
-
143
- /**
144
- * Creates a greater-than expression (left > right)
145
- * @param left - Left operand
146
- * @param right - Right operand
147
- * @returns Binary expression node with greater-than operator
148
- */
149
- export const gt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
150
- createBinaryExpression('>', left, right);
151
-
152
- /**
153
- * Creates a greater than or equal expression (left >= right)
154
- */
155
- export const gte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
156
- createBinaryExpression('>=', left, right);
157
-
158
- /**
159
- * Creates a less-than expression (left < right)
160
- * @param left - Left operand
161
- * @param right - Right operand
162
- * @returns Binary expression node with less-than operator
163
- */
164
- export const lt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
165
- createBinaryExpression('<', left, right);
166
-
167
- /**
168
- * Creates a less than or equal expression (left <= right)
169
- */
170
- export const lte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
171
- createBinaryExpression('<=', left, right);
172
-
173
- /**
174
- * Creates a LIKE pattern matching expression
175
- * @param left - Left operand
176
- * @param pattern - Pattern to match
177
- * @param escape - Optional escape character
178
- * @returns Binary expression node with LIKE operator
179
- */
180
- export const like = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
181
- createBinaryExpression('LIKE', left, pattern, escape);
182
-
183
- /**
184
- * Creates a NOT LIKE pattern matching expression
185
- * @param left - Left operand
186
- * @param pattern - Pattern to match
187
- * @param escape - Optional escape character
188
- * @returns Binary expression node with NOT LIKE operator
189
- */
190
- export const notLike = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
191
- createBinaryExpression('NOT LIKE', left, pattern, escape);
192
-
193
- /**
194
- * Creates a logical AND expression
195
- * @param operands - Expressions to combine with AND
196
- * @returns Logical expression node with AND operator
197
- */
198
- export const and = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
199
- type: 'LogicalExpression',
200
- operator: 'AND',
201
- operands
202
- });
203
-
204
- /**
205
- * Creates a logical OR expression
206
- * @param operands - Expressions to combine with OR
207
- * @returns Logical expression node with OR operator
208
- */
209
- export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
210
- type: 'LogicalExpression',
211
- operator: 'OR',
212
- operands
213
- });
214
-
215
- /**
216
- * Creates an IS NULL expression
217
- * @param left - Operand to check for null
218
- * @returns Null expression node with IS NULL operator
219
- */
220
- export const isNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
221
- type: 'NullExpression',
222
- left: toNode(left),
223
- operator: 'IS NULL'
224
- });
225
-
226
- /**
227
- * Creates an IS NOT NULL expression
228
- * @param left - Operand to check for non-null
229
- * @returns Null expression node with IS NOT NULL operator
230
- */
231
- export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
232
- type: 'NullExpression',
233
- left: toNode(left),
234
- operator: 'IS NOT NULL'
235
- });
236
-
237
- const createInExpression = (
238
- operator: 'IN' | 'NOT IN',
239
- left: OperandNode | ColumnRef,
240
- right: InExpressionRight
241
- ): InExpressionNode => ({
242
- type: 'InExpression',
243
- left: toNode(left),
244
- operator,
245
- right
246
- });
247
-
248
- /**
249
- * Creates an IN expression (value IN list)
250
- * @param left - Operand to check
251
- * @param values - Values to check against
252
- * @returns IN expression node
253
- */
254
- export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
255
- createInExpression('IN', left, values.map(v => toOperand(v)));
256
-
257
- /**
258
- * Creates a NOT IN expression (value NOT IN list)
259
- * @param left - Operand to check
260
- * @param values - Values to check against
261
- * @returns NOT IN expression node
262
- */
263
- export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
264
- createInExpression('NOT IN', left, values.map(v => toOperand(v)));
265
-
266
- export const inSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
267
- createInExpression('IN', left, toScalarSubqueryNode(subquery));
268
-
269
- export const notInSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
270
- createInExpression('NOT IN', left, toScalarSubqueryNode(subquery));
271
-
272
- const createBetweenExpression = (
273
- operator: 'BETWEEN' | 'NOT BETWEEN',
274
- left: OperandNode | ColumnRef,
275
- lower: OperandNode | ColumnRef | string | number,
276
- upper: OperandNode | ColumnRef | string | number
277
- ): BetweenExpressionNode => ({
278
- type: 'BetweenExpression',
279
- left: toNode(left),
280
- operator,
281
- lower: toOperand(lower),
282
- upper: toOperand(upper)
283
- });
284
-
285
- /**
286
- * Creates a BETWEEN expression (value BETWEEN lower AND upper)
287
- * @param left - Operand to check
288
- * @param lower - Lower bound
289
- * @param upper - Upper bound
290
- * @returns BETWEEN expression node
291
- */
292
- export const between = (
293
- left: OperandNode | ColumnRef,
294
- lower: OperandNode | ColumnRef | string | number,
295
- upper: OperandNode | ColumnRef | string | number
296
- ): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
297
-
298
- /**
299
- * Creates a NOT BETWEEN expression (value NOT BETWEEN lower AND upper)
300
- * @param left - Operand to check
301
- * @param lower - Lower bound
302
- * @param upper - Upper bound
303
- * @returns NOT BETWEEN expression node
304
- */
305
- export const notBetween = (
306
- left: OperandNode | ColumnRef,
307
- lower: OperandNode | ColumnRef | string | number,
308
- upper: OperandNode | ColumnRef | string | number
309
- ): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
310
-
311
- const createArithmeticExpression = (
312
- operator: '+' | '-' | '*' | '/',
313
- left: OperandNode | ColumnRef,
314
- right: OperandNode | ColumnRef | string | number
315
- ): ArithmeticExpressionNode => ({
316
- type: 'ArithmeticExpression',
317
- left: toOperand(left),
318
- operator,
319
- right: toOperand(right)
320
- });
321
-
322
- export const add = (
323
- left: OperandNode | ColumnRef,
324
- right: OperandNode | ColumnRef | string | number
325
- ): ArithmeticExpressionNode => createArithmeticExpression('+', left, right);
326
-
327
- export const sub = (
328
- left: OperandNode | ColumnRef,
329
- right: OperandNode | ColumnRef | string | number
330
- ): ArithmeticExpressionNode => createArithmeticExpression('-', left, right);
331
-
332
- export const mul = (
333
- left: OperandNode | ColumnRef,
334
- right: OperandNode | ColumnRef | string | number
335
- ): ArithmeticExpressionNode => createArithmeticExpression('*', left, right);
336
-
337
- export const div = (
338
- left: OperandNode | ColumnRef,
339
- right: OperandNode | ColumnRef | string | number
340
- ): ArithmeticExpressionNode => createArithmeticExpression('/', left, right);
341
-
342
- /**
343
- * Creates a JSON path expression
344
- * @param col - Source column
345
- * @param path - JSON path expression
346
- * @returns JSON path node
347
- */
348
- export const jsonPath = (col: ColumnRef | ColumnNode, path: string): JsonPathNode => ({
349
- type: 'JsonPath',
350
- column: columnOperand(col),
351
- path
352
- });
353
-
354
- /**
355
- * Creates a CASE expression
356
- * @param conditions - Array of WHEN-THEN conditions
357
- * @param elseValue - Optional ELSE value
358
- * @returns CASE expression node
359
- */
360
- export const caseWhen = (
361
- conditions: { when: ExpressionNode; then: OperandNode | ColumnRef | string | number | boolean | null }[],
362
- elseValue?: OperandNode | ColumnRef | string | number | boolean | null
363
- ): CaseExpressionNode => ({
364
- type: 'CaseExpression',
365
- conditions: conditions.map(c => ({
366
- when: c.when,
367
- then: toOperand(c.then)
368
- })),
369
- else: elseValue !== undefined ? toOperand(elseValue) : undefined
370
- });
371
-
372
- /**
373
- * Creates an EXISTS expression
374
- * @param subquery - Subquery to check for existence
375
- * @returns EXISTS expression node
376
- */
377
- export const exists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
378
- type: 'ExistsExpression',
379
- operator: 'EXISTS',
380
- subquery
381
- });
382
-
383
- /**
384
- * Creates a NOT EXISTS expression
385
- * @param subquery - Subquery to check for non-existence
386
- * @returns NOT EXISTS expression node
387
- */
388
- export const notExists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
389
- type: 'ExistsExpression',
390
- operator: 'NOT EXISTS',
391
- subquery
392
- });
1
+ import { SelectQueryNode } from './query.js';
2
+ import { SqlOperator } from '../sql/sql.js';
3
+ import { ColumnRef } from './types.js';
4
+ import {
5
+ ColumnNode,
6
+ LiteralNode,
7
+ JsonPathNode,
8
+ OperandNode,
9
+ CaseExpressionNode,
10
+ BinaryExpressionNode,
11
+ ExpressionNode,
12
+ LogicalExpressionNode,
13
+ NullExpressionNode,
14
+ InExpressionNode,
15
+ ExistsExpressionNode,
16
+ InExpressionRight,
17
+ ScalarSubqueryNode,
18
+ BetweenExpressionNode,
19
+ isOperandNode,
20
+ AliasRefNode,
21
+ ArithmeticExpressionNode
22
+ } from './expression-nodes.js';
23
+
24
+ export type LiteralValue = LiteralNode['value'];
25
+ export type ValueOperandInput = OperandNode | LiteralValue;
26
+
27
+ /**
28
+ * Type guard to check if a value is a literal value
29
+ */
30
+ const isLiteralValue = (value: unknown): value is LiteralValue =>
31
+ value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
32
+
33
+
34
+ /**
35
+ * Converts a primitive value to a LiteralNode
36
+ */
37
+ const toLiteralNode = (value: string | number | boolean | null): LiteralNode => ({
38
+ type: 'Literal',
39
+ value
40
+ });
41
+
42
+ /**
43
+ * Converts a ColumnRef to a ColumnNode
44
+ * @throws Error if the ColumnRef doesn't have a table specified
45
+ */
46
+ const columnRefToNode = (col: ColumnRef): ColumnNode => {
47
+ if (!col.table) {
48
+ throw new Error(
49
+ `Column "${col.name}" requires a table reference. ` +
50
+ `Use columnOperand with a fully qualified ColumnRef or ColumnNode.`
51
+ );
52
+ }
53
+ return { type: 'Column', table: col.table, name: col.name };
54
+ };
55
+
56
+ /**
57
+ * Unified conversion function: converts any valid input to an OperandNode
58
+ * @param value - Value to convert (OperandNode, ColumnRef, or literal value)
59
+ * @returns OperandNode representing the value
60
+ */
61
+ const toOperandNode = (value: OperandNode | ColumnRef | LiteralValue): OperandNode => {
62
+ // Already an operand node
63
+ if (isOperandNode(value)) {
64
+ return value;
65
+ }
66
+
67
+ // Literal value
68
+ if (isLiteralValue(value)) {
69
+ return toLiteralNode(value);
70
+ }
71
+
72
+ // Must be ColumnRef
73
+ return columnRefToNode(value as ColumnRef);
74
+ };
75
+
76
+ /**
77
+ * Converts a primitive or existing operand into an operand node
78
+ * @param value - Value or operand to normalize
79
+ * @returns OperandNode representing the value
80
+ */
81
+ export const valueToOperand = (value: ValueOperandInput): OperandNode => {
82
+ if (isOperandNode(value)) {
83
+ return value;
84
+ }
85
+ return toLiteralNode(value);
86
+ };
87
+
88
+ /**
89
+ * Converts various input types to an OperandNode
90
+ */
91
+ const toOperand = (val: OperandNode | ColumnRef | LiteralValue): OperandNode => toOperandNode(val);
92
+
93
+ export const isValueOperandInput = (value: unknown): value is ValueOperandInput =>
94
+ isOperandNode(value) || isLiteralValue(value);
95
+
96
+ export type SelectQueryInput = SelectQueryNode | { getAST(): SelectQueryNode };
97
+
98
+ const hasQueryAst = (value: SelectQueryInput): value is { getAST(): SelectQueryNode } =>
99
+ typeof (value as { getAST?: unknown }).getAST === 'function';
100
+
101
+ const resolveSelectQueryNode = (query: SelectQueryInput): SelectQueryNode =>
102
+ hasQueryAst(query) ? query.getAST() : query;
103
+
104
+ const toScalarSubqueryNode = (query: SelectQueryInput): ScalarSubqueryNode => ({
105
+ type: 'ScalarSubquery',
106
+ query: resolveSelectQueryNode(query)
107
+ });
108
+
109
+ /**
110
+ * Converts a ColumnRef or ColumnNode to a ColumnNode
111
+ * @param col - Column reference or node
112
+ * @returns ColumnNode
113
+ * @throws Error if ColumnRef doesn't have a table specified
114
+ */
115
+ export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => {
116
+ if (isOperandNode(col) && col.type === 'Column') {
117
+ return col;
118
+ }
119
+ return columnRefToNode(col as ColumnRef);
120
+ };
121
+
122
+ /**
123
+ * Marks a column reference as an outer-scope reference for correlated subqueries.
124
+ * Primarily semantic; SQL rendering still uses the provided table/alias name.
125
+ */
126
+ export const outerRef = (col: ColumnRef | ColumnNode): ColumnNode => ({
127
+ ...columnOperand(col),
128
+ scope: 'outer'
129
+ });
130
+
131
+ /**
132
+ * References a SELECT alias (useful for ORDER BY / GROUP BY).
133
+ */
134
+ export const aliasRef = (name: string): AliasRefNode => ({
135
+ type: 'AliasRef',
136
+ name
137
+ });
138
+
139
+ /**
140
+ * Creates an outer-scoped column reference using a specific table or alias name.
141
+ */
142
+ export const correlateBy = (table: string, column: string): ColumnNode => outerRef({ name: column, table });
143
+
144
+ const createBinaryExpression = (
145
+ operator: SqlOperator,
146
+ left: OperandNode | ColumnRef,
147
+ right: OperandNode | ColumnRef | string | number | boolean | null,
148
+ escape?: string
149
+ ): BinaryExpressionNode => {
150
+ const node: BinaryExpressionNode = {
151
+ type: 'BinaryExpression',
152
+ left: toOperandNode(left),
153
+ operator,
154
+ right: toOperand(right)
155
+ };
156
+
157
+ if (escape !== undefined) {
158
+ node.escape = toLiteralNode(escape);
159
+ }
160
+
161
+ return node;
162
+ };
163
+
164
+ /**
165
+ * Creates an equality expression (left = right)
166
+ * @param left - Left operand
167
+ * @param right - Right operand
168
+ * @returns Binary expression node with equality operator
169
+ */
170
+ export const eq = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number | boolean): BinaryExpressionNode =>
171
+ createBinaryExpression('=', left, right);
172
+
173
+ /**
174
+ * Creates a not equal expression (left != right)
175
+ */
176
+ export const neq = (
177
+ left: OperandNode | ColumnRef,
178
+ right: OperandNode | ColumnRef | string | number | boolean
179
+ ): BinaryExpressionNode => createBinaryExpression('!=', left, right);
180
+
181
+ /**
182
+ * Creates a greater-than expression (left > right)
183
+ * @param left - Left operand
184
+ * @param right - Right operand
185
+ * @returns Binary expression node with greater-than operator
186
+ */
187
+ export const gt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
188
+ createBinaryExpression('>', left, right);
189
+
190
+ /**
191
+ * Creates a greater than or equal expression (left >= right)
192
+ */
193
+ export const gte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
194
+ createBinaryExpression('>=', left, right);
195
+
196
+ /**
197
+ * Creates a less-than expression (left < right)
198
+ * @param left - Left operand
199
+ * @param right - Right operand
200
+ * @returns Binary expression node with less-than operator
201
+ */
202
+ export const lt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
203
+ createBinaryExpression('<', left, right);
204
+
205
+ /**
206
+ * Creates a less than or equal expression (left <= right)
207
+ */
208
+ export const lte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
209
+ createBinaryExpression('<=', left, right);
210
+
211
+ /**
212
+ * Creates a LIKE pattern matching expression
213
+ * @param left - Left operand
214
+ * @param pattern - Pattern to match
215
+ * @param escape - Optional escape character
216
+ * @returns Binary expression node with LIKE operator
217
+ */
218
+ export const like = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
219
+ createBinaryExpression('LIKE', left, pattern, escape);
220
+
221
+ /**
222
+ * Creates a NOT LIKE pattern matching expression
223
+ * @param left - Left operand
224
+ * @param pattern - Pattern to match
225
+ * @param escape - Optional escape character
226
+ * @returns Binary expression node with NOT LIKE operator
227
+ */
228
+ export const notLike = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
229
+ createBinaryExpression('NOT LIKE', left, pattern, escape);
230
+
231
+ /**
232
+ * Creates a logical AND expression
233
+ * @param operands - Expressions to combine with AND
234
+ * @returns Logical expression node with AND operator
235
+ */
236
+ export const and = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
237
+ type: 'LogicalExpression',
238
+ operator: 'AND',
239
+ operands
240
+ });
241
+
242
+ /**
243
+ * Creates a logical OR expression
244
+ * @param operands - Expressions to combine with OR
245
+ * @returns Logical expression node with OR operator
246
+ */
247
+ export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
248
+ type: 'LogicalExpression',
249
+ operator: 'OR',
250
+ operands
251
+ });
252
+
253
+ /**
254
+ * Creates an IS NULL expression
255
+ * @param left - Operand to check for null
256
+ * @returns Null expression node with IS NULL operator
257
+ */
258
+ export const isNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
259
+ type: 'NullExpression',
260
+ left: toOperandNode(left),
261
+ operator: 'IS NULL'
262
+ });
263
+
264
+ /**
265
+ * Creates an IS NOT NULL expression
266
+ * @param left - Operand to check for non-null
267
+ * @returns Null expression node with IS NOT NULL operator
268
+ */
269
+ export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
270
+ type: 'NullExpression',
271
+ left: toOperandNode(left),
272
+ operator: 'IS NOT NULL'
273
+ });
274
+
275
+ const createInExpression = (
276
+ operator: 'IN' | 'NOT IN',
277
+ left: OperandNode | ColumnRef,
278
+ right: InExpressionRight
279
+ ): InExpressionNode => ({
280
+ type: 'InExpression',
281
+ left: toOperandNode(left),
282
+ operator,
283
+ right
284
+ });
285
+
286
+ /**
287
+ * Creates an IN expression (value IN list)
288
+ * @param left - Operand to check
289
+ * @param values - Values to check against
290
+ * @returns IN expression node
291
+ */
292
+ export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
293
+ createInExpression('IN', left, values.map(v => toOperand(v)));
294
+
295
+ /**
296
+ * Creates a NOT IN expression (value NOT IN list)
297
+ * @param left - Operand to check
298
+ * @param values - Values to check against
299
+ * @returns NOT IN expression node
300
+ */
301
+ export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
302
+ createInExpression('NOT IN', left, values.map(v => toOperand(v)));
303
+
304
+ export const inSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
305
+ createInExpression('IN', left, toScalarSubqueryNode(subquery));
306
+
307
+ export const notInSubquery = (left: OperandNode | ColumnRef, subquery: SelectQueryInput): InExpressionNode =>
308
+ createInExpression('NOT IN', left, toScalarSubqueryNode(subquery));
309
+
310
+ const createBetweenExpression = (
311
+ operator: 'BETWEEN' | 'NOT BETWEEN',
312
+ left: OperandNode | ColumnRef,
313
+ lower: OperandNode | ColumnRef | string | number,
314
+ upper: OperandNode | ColumnRef | string | number
315
+ ): BetweenExpressionNode => ({
316
+ type: 'BetweenExpression',
317
+ left: toOperandNode(left),
318
+ operator,
319
+ lower: toOperand(lower),
320
+ upper: toOperand(upper)
321
+ });
322
+
323
+ /**
324
+ * Creates a BETWEEN expression (value BETWEEN lower AND upper)
325
+ * @param left - Operand to check
326
+ * @param lower - Lower bound
327
+ * @param upper - Upper bound
328
+ * @returns BETWEEN expression node
329
+ */
330
+ export const between = (
331
+ left: OperandNode | ColumnRef,
332
+ lower: OperandNode | ColumnRef | string | number,
333
+ upper: OperandNode | ColumnRef | string | number
334
+ ): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
335
+
336
+ /**
337
+ * Creates a NOT BETWEEN expression (value NOT BETWEEN lower AND upper)
338
+ * @param left - Operand to check
339
+ * @param lower - Lower bound
340
+ * @param upper - Upper bound
341
+ * @returns NOT BETWEEN expression node
342
+ */
343
+ export const notBetween = (
344
+ left: OperandNode | ColumnRef,
345
+ lower: OperandNode | ColumnRef | string | number,
346
+ upper: OperandNode | ColumnRef | string | number
347
+ ): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
348
+
349
+ const createArithmeticExpression = (
350
+ operator: '+' | '-' | '*' | '/',
351
+ left: OperandNode | ColumnRef,
352
+ right: OperandNode | ColumnRef | string | number
353
+ ): ArithmeticExpressionNode => ({
354
+ type: 'ArithmeticExpression',
355
+ left: toOperand(left),
356
+ operator,
357
+ right: toOperand(right)
358
+ });
359
+
360
+ export const add = (
361
+ left: OperandNode | ColumnRef,
362
+ right: OperandNode | ColumnRef | string | number
363
+ ): ArithmeticExpressionNode => createArithmeticExpression('+', left, right);
364
+
365
+ export const sub = (
366
+ left: OperandNode | ColumnRef,
367
+ right: OperandNode | ColumnRef | string | number
368
+ ): ArithmeticExpressionNode => createArithmeticExpression('-', left, right);
369
+
370
+ export const mul = (
371
+ left: OperandNode | ColumnRef,
372
+ right: OperandNode | ColumnRef | string | number
373
+ ): ArithmeticExpressionNode => createArithmeticExpression('*', left, right);
374
+
375
+ export const div = (
376
+ left: OperandNode | ColumnRef,
377
+ right: OperandNode | ColumnRef | string | number
378
+ ): ArithmeticExpressionNode => createArithmeticExpression('/', left, right);
379
+
380
+ /**
381
+ * Creates a JSON path expression
382
+ * @param col - Source column
383
+ * @param path - JSON path expression
384
+ * @returns JSON path node
385
+ */
386
+ export const jsonPath = (col: ColumnRef | ColumnNode, path: string): JsonPathNode => ({
387
+ type: 'JsonPath',
388
+ column: columnOperand(col),
389
+ path
390
+ });
391
+
392
+ /**
393
+ * Creates a CASE expression
394
+ * @param conditions - Array of WHEN-THEN conditions
395
+ * @param elseValue - Optional ELSE value
396
+ * @returns CASE expression node
397
+ */
398
+ export const caseWhen = (
399
+ conditions: { when: ExpressionNode; then: OperandNode | ColumnRef | string | number | boolean | null }[],
400
+ elseValue?: OperandNode | ColumnRef | string | number | boolean | null
401
+ ): CaseExpressionNode => ({
402
+ type: 'CaseExpression',
403
+ conditions: conditions.map(c => ({
404
+ when: c.when,
405
+ then: toOperand(c.then)
406
+ })),
407
+ else: elseValue !== undefined ? toOperand(elseValue) : undefined
408
+ });
409
+
410
+ /**
411
+ * Creates an EXISTS expression
412
+ * @param subquery - Subquery to check for existence
413
+ * @returns EXISTS expression node
414
+ */
415
+ export const exists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
416
+ type: 'ExistsExpression',
417
+ operator: 'EXISTS',
418
+ subquery
419
+ });
420
+
421
+ /**
422
+ * Creates a NOT EXISTS expression
423
+ * @param subquery - Subquery to check for non-existence
424
+ * @returns NOT EXISTS expression node
425
+ */
426
+ export const notExists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
427
+ type: 'ExistsExpression',
428
+ operator: 'NOT EXISTS',
429
+ subquery
430
+ });