metal-orm 1.0.4 → 1.0.6

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 (57) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +77 -3
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +630 -592
  15. package/src/ast/query.ts +110 -49
  16. package/src/builder/delete-query-state.ts +42 -0
  17. package/src/builder/delete.ts +57 -0
  18. package/src/builder/hydration-manager.ts +3 -2
  19. package/src/builder/hydration-planner.ts +163 -107
  20. package/src/builder/insert-query-state.ts +62 -0
  21. package/src/builder/insert.ts +59 -0
  22. package/src/builder/operations/relation-manager.ts +1 -23
  23. package/src/builder/relation-conditions.ts +45 -1
  24. package/src/builder/relation-service.ts +81 -18
  25. package/src/builder/relation-types.ts +15 -0
  26. package/src/builder/relation-utils.ts +12 -0
  27. package/src/builder/select.ts +427 -394
  28. package/src/builder/update-query-state.ts +59 -0
  29. package/src/builder/update.ts +61 -0
  30. package/src/constants/sql-operator-config.ts +3 -0
  31. package/src/constants/sql.ts +38 -32
  32. package/src/dialect/abstract.ts +107 -47
  33. package/src/dialect/mssql/index.ts +31 -6
  34. package/src/dialect/mysql/index.ts +31 -6
  35. package/src/dialect/postgres/index.ts +45 -6
  36. package/src/dialect/sqlite/index.ts +45 -6
  37. package/src/index.ts +22 -11
  38. package/src/playground/features/playground/data/scenarios/hydration.ts +23 -11
  39. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  40. package/src/playground/features/playground/data/schema.ts +6 -2
  41. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  42. package/src/runtime/entity-meta.ts +52 -0
  43. package/src/runtime/entity.ts +252 -0
  44. package/src/runtime/execute.ts +36 -0
  45. package/src/runtime/hydration.ts +100 -38
  46. package/src/runtime/lazy-batch.ts +205 -0
  47. package/src/runtime/orm-context.ts +539 -0
  48. package/src/runtime/relations/belongs-to.ts +92 -0
  49. package/src/runtime/relations/has-many.ts +111 -0
  50. package/src/runtime/relations/many-to-many.ts +149 -0
  51. package/src/schema/column.ts +15 -1
  52. package/src/schema/relation.ts +105 -40
  53. package/src/schema/table.ts +34 -22
  54. package/src/schema/types.ts +76 -0
  55. package/tests/belongs-to-many.test.ts +57 -0
  56. package/tests/dml.test.ts +206 -0
  57. package/tests/orm-runtime.test.ts +254 -0
@@ -1,269 +1,301 @@
1
- import { ColumnDef } from '../schema/column';
2
- import type { SelectQueryNode, OrderByNode } from './query';
3
- import { OrderDirection, SqlOperator } from '../constants/sql';
4
-
5
- /**
6
- * AST node representing a literal value
7
- */
8
- export interface LiteralNode {
9
- type: 'Literal';
10
- /** The literal value (string, number, boolean, or null) */
11
- value: string | number | boolean | null;
12
- }
13
-
14
- /**
15
- * AST node representing a column reference
16
- */
17
- export interface ColumnNode {
18
- type: 'Column';
19
- /** Table name the column belongs to */
20
- table: string;
21
- /** Column name */
22
- name: string;
23
- /** Optional alias for the column */
24
- alias?: string;
25
- }
26
-
27
- /**
28
- * AST node representing a function call
29
- */
30
- export interface FunctionNode {
31
- type: 'Function';
32
- /** Function name (e.g., COUNT, SUM) */
33
- name: string;
34
- /** Function arguments */
35
- args: (ColumnNode | LiteralNode | JsonPathNode)[];
36
- /** Optional alias for the function result */
37
- alias?: string;
38
- }
39
-
40
- /**
41
- * AST node representing a JSON path expression
42
- */
43
- export interface JsonPathNode {
44
- type: 'JsonPath';
45
- /** Source column */
46
- column: ColumnNode;
47
- /** JSON path expression */
48
- path: string;
49
- /** Optional alias for the result */
50
- alias?: string;
51
- }
52
-
53
- /**
54
- * AST node representing a scalar subquery
55
- */
56
- export interface ScalarSubqueryNode {
57
- type: 'ScalarSubquery';
58
- /** Subquery to execute */
59
- query: SelectQueryNode;
60
- /** Optional alias for the subquery result */
61
- alias?: string;
62
- }
63
-
64
- /**
65
- * AST node representing a CASE expression
66
- */
67
- export interface CaseExpressionNode {
68
- type: 'CaseExpression';
69
- /** WHEN-THEN conditions */
70
- conditions: { when: ExpressionNode; then: OperandNode }[];
71
- /** Optional ELSE clause */
72
- else?: OperandNode;
73
- /** Optional alias for the result */
74
- alias?: string;
75
- }
76
-
77
- /**
78
- * AST node representing a window function
79
- */
80
- export interface WindowFunctionNode {
81
- type: 'WindowFunction';
82
- /** Window function name (e.g., ROW_NUMBER, RANK) */
83
- name: string;
84
- /** Function arguments */
85
- args: (ColumnNode | LiteralNode | JsonPathNode)[];
86
- /** Optional PARTITION BY clause */
87
- partitionBy?: ColumnNode[];
88
- /** Optional ORDER BY clause */
89
- orderBy?: OrderByNode[];
90
- /** Optional alias for the result */
91
- alias?: string;
92
- }
93
-
94
- /**
95
- * Union type representing any operand that can be used in expressions
96
- */
1
+ import { ColumnDef } from '../schema/column';
2
+ import type { SelectQueryNode, OrderByNode } from './query';
3
+ import { OrderDirection, SqlOperator } from '../constants/sql';
4
+
5
+ /**
6
+ * AST node representing a literal value
7
+ */
8
+ export interface LiteralNode {
9
+ type: 'Literal';
10
+ /** The literal value (string, number, boolean, or null) */
11
+ value: string | number | boolean | null;
12
+ }
13
+
14
+ /**
15
+ * AST node representing a column reference
16
+ */
17
+ export interface ColumnNode {
18
+ type: 'Column';
19
+ /** Table name the column belongs to */
20
+ table: string;
21
+ /** Column name */
22
+ name: string;
23
+ /** Optional alias for the column */
24
+ alias?: string;
25
+ }
26
+
27
+ /**
28
+ * AST node representing a function call
29
+ */
30
+ export interface FunctionNode {
31
+ type: 'Function';
32
+ /** Function name (e.g., COUNT, SUM) */
33
+ name: string;
34
+ /** Function arguments */
35
+ args: (ColumnNode | LiteralNode | JsonPathNode)[];
36
+ /** Optional alias for the function result */
37
+ alias?: string;
38
+ }
39
+
40
+ /**
41
+ * AST node representing a JSON path expression
42
+ */
43
+ export interface JsonPathNode {
44
+ type: 'JsonPath';
45
+ /** Source column */
46
+ column: ColumnNode;
47
+ /** JSON path expression */
48
+ path: string;
49
+ /** Optional alias for the result */
50
+ alias?: string;
51
+ }
52
+
53
+ /**
54
+ * AST node representing a scalar subquery
55
+ */
56
+ export interface ScalarSubqueryNode {
57
+ type: 'ScalarSubquery';
58
+ /** Subquery to execute */
59
+ query: SelectQueryNode;
60
+ /** Optional alias for the subquery result */
61
+ alias?: string;
62
+ }
63
+
64
+ /**
65
+ * AST node representing a CASE expression
66
+ */
67
+ export interface CaseExpressionNode {
68
+ type: 'CaseExpression';
69
+ /** WHEN-THEN conditions */
70
+ conditions: { when: ExpressionNode; then: OperandNode }[];
71
+ /** Optional ELSE clause */
72
+ else?: OperandNode;
73
+ /** Optional alias for the result */
74
+ alias?: string;
75
+ }
76
+
77
+ /**
78
+ * AST node representing a window function
79
+ */
80
+ export interface WindowFunctionNode {
81
+ type: 'WindowFunction';
82
+ /** Window function name (e.g., ROW_NUMBER, RANK) */
83
+ name: string;
84
+ /** Function arguments */
85
+ args: (ColumnNode | LiteralNode | JsonPathNode)[];
86
+ /** Optional PARTITION BY clause */
87
+ partitionBy?: ColumnNode[];
88
+ /** Optional ORDER BY clause */
89
+ orderBy?: OrderByNode[];
90
+ /** Optional alias for the result */
91
+ alias?: string;
92
+ }
93
+
94
+ /**
95
+ * Union type representing any operand that can be used in expressions
96
+ */
97
97
  export type OperandNode = ColumnNode | LiteralNode | FunctionNode | JsonPathNode | ScalarSubqueryNode | CaseExpressionNode | WindowFunctionNode;
98
98
 
99
99
  /**
100
- * AST node representing a binary expression (e.g., column = value)
101
- */
102
- export interface BinaryExpressionNode {
103
- type: 'BinaryExpression';
104
- /** Left operand */
105
- left: OperandNode;
106
- /** Comparison operator */
107
- operator: SqlOperator;
108
- /** Right operand */
109
- right: OperandNode;
110
- /** Optional escape character for LIKE expressions */
111
- escape?: LiteralNode;
112
- }
113
-
114
- /**
115
- * AST node representing a logical expression (AND/OR)
116
- */
117
- export interface LogicalExpressionNode {
118
- type: 'LogicalExpression';
119
- /** Logical operator (AND or OR) */
120
- operator: 'AND' | 'OR';
121
- /** Operands to combine */
122
- operands: ExpressionNode[];
123
- }
124
-
125
- /**
126
- * AST node representing a null check expression
127
- */
128
- export interface NullExpressionNode {
129
- type: 'NullExpression';
130
- /** Operand to check for null */
131
- left: OperandNode;
132
- /** Null check operator */
133
- operator: 'IS NULL' | 'IS NOT NULL';
134
- }
135
-
136
- /**
137
- * AST node representing an IN/NOT IN expression
138
- */
139
- export interface InExpressionNode {
140
- type: 'InExpression';
141
- /** Left operand to check */
142
- left: OperandNode;
143
- /** IN/NOT IN operator */
144
- operator: 'IN' | 'NOT IN';
145
- /** Values to check against */
146
- right: OperandNode[];
147
- }
148
-
149
- /**
150
- * AST node representing an EXISTS/NOT EXISTS expression
151
- */
152
- export interface ExistsExpressionNode {
153
- type: 'ExistsExpression';
154
- /** EXISTS/NOT EXISTS operator */
155
- operator: SqlOperator;
156
- /** Subquery to check */
157
- subquery: SelectQueryNode;
158
- }
159
-
160
- /**
161
- * AST node representing a BETWEEN/NOT BETWEEN expression
162
- */
163
- export interface BetweenExpressionNode {
164
- type: 'BetweenExpression';
165
- /** Operand to check */
166
- left: OperandNode;
167
- /** BETWEEN/NOT BETWEEN operator */
168
- operator: 'BETWEEN' | 'NOT BETWEEN';
169
- /** Lower bound */
170
- lower: OperandNode;
171
- /** Upper bound */
172
- upper: OperandNode;
173
- }
174
-
175
- /**
176
- * Union type representing any supported expression node
100
+ * Converts a primitive or existing operand into an operand node
101
+ * @param value - Value or operand to normalize
102
+ * @returns OperandNode representing the value
177
103
  */
178
- export type ExpressionNode =
179
- | BinaryExpressionNode
180
- | LogicalExpressionNode
181
- | NullExpressionNode
182
- | InExpressionNode
183
- | ExistsExpressionNode
184
- | BetweenExpressionNode;
185
-
186
- const operandTypes = new Set<OperandNode['type']>([
187
- 'Column',
188
- 'Literal',
189
- 'Function',
190
- 'JsonPath',
191
- 'ScalarSubquery',
192
- 'CaseExpression',
193
- 'WindowFunction'
194
- ]);
195
-
196
- const isOperandNode = (node: any): node is OperandNode => {
197
- return node && operandTypes.has(node.type);
198
- };
199
-
200
- export const isFunctionNode = (node: any): node is FunctionNode => node?.type === 'Function';
201
- export const isCaseExpressionNode = (node: any): node is CaseExpressionNode => node?.type === 'CaseExpression';
202
- export const isWindowFunctionNode = (node: any): node is WindowFunctionNode => node?.type === 'WindowFunction';
203
- export const isExpressionSelectionNode = (
204
- node: ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode
205
- ): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
206
- isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
207
-
208
- // Helper to convert Schema definition to AST Node
209
- const toNode = (col: ColumnDef | OperandNode): OperandNode => {
210
- if (isOperandNode(col)) return col as OperandNode;
211
- const def = col as ColumnDef;
212
- return { type: 'Column', table: def.table || 'unknown', name: def.name };
213
- };
214
-
215
- const toLiteralNode = (value: string | number | boolean | null): LiteralNode => ({
216
- type: 'Literal',
217
- value
218
- });
219
-
220
- const toOperand = (val: OperandNode | ColumnDef | string | number | boolean | null): OperandNode => {
221
- if (val === null) return { type: 'Literal', value: null };
222
- if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
223
- return { type: 'Literal', value: val };
104
+ export const valueToOperand = (value: unknown): OperandNode => {
105
+ if (
106
+ value === null ||
107
+ value === undefined ||
108
+ typeof value === 'string' ||
109
+ typeof value === 'number' ||
110
+ typeof value === 'boolean'
111
+ ) {
112
+ return { type: 'Literal', value: value === undefined ? null : value } as LiteralNode;
224
113
  }
225
- return toNode(val as OperandNode | ColumnDef);
114
+ return value as OperandNode;
226
115
  };
227
-
228
- // Factories
229
- const createBinaryExpression = (
230
- operator: SqlOperator,
231
- left: OperandNode | ColumnDef,
232
- right: OperandNode | ColumnDef | string | number | boolean | null,
233
- escape?: string
234
- ): BinaryExpressionNode => {
235
- const node: BinaryExpressionNode = {
236
- type: 'BinaryExpression',
237
- left: toNode(left),
238
- operator,
239
- right: toOperand(right)
240
- };
241
-
242
- if (escape !== undefined) {
243
- node.escape = toLiteralNode(escape);
244
- }
245
-
246
- return node;
247
- };
248
-
249
- /**
250
- * Creates an equality expression (left = right)
251
- * @param left - Left operand
252
- * @param right - Right operand
253
- * @returns Binary expression node with equality operator
254
- */
116
+
117
+ /**
118
+ * AST node representing a binary expression (e.g., column = value)
119
+ */
120
+ export interface BinaryExpressionNode {
121
+ type: 'BinaryExpression';
122
+ /** Left operand */
123
+ left: OperandNode;
124
+ /** Comparison operator */
125
+ operator: SqlOperator;
126
+ /** Right operand */
127
+ right: OperandNode;
128
+ /** Optional escape character for LIKE expressions */
129
+ escape?: LiteralNode;
130
+ }
131
+
132
+ /**
133
+ * AST node representing a logical expression (AND/OR)
134
+ */
135
+ export interface LogicalExpressionNode {
136
+ type: 'LogicalExpression';
137
+ /** Logical operator (AND or OR) */
138
+ operator: 'AND' | 'OR';
139
+ /** Operands to combine */
140
+ operands: ExpressionNode[];
141
+ }
142
+
143
+ /**
144
+ * AST node representing a null check expression
145
+ */
146
+ export interface NullExpressionNode {
147
+ type: 'NullExpression';
148
+ /** Operand to check for null */
149
+ left: OperandNode;
150
+ /** Null check operator */
151
+ operator: 'IS NULL' | 'IS NOT NULL';
152
+ }
153
+
154
+ /**
155
+ * AST node representing an IN/NOT IN expression
156
+ */
157
+ export interface InExpressionNode {
158
+ type: 'InExpression';
159
+ /** Left operand to check */
160
+ left: OperandNode;
161
+ /** IN/NOT IN operator */
162
+ operator: 'IN' | 'NOT IN';
163
+ /** Values to check against */
164
+ right: OperandNode[];
165
+ }
166
+
167
+ /**
168
+ * AST node representing an EXISTS/NOT EXISTS expression
169
+ */
170
+ export interface ExistsExpressionNode {
171
+ type: 'ExistsExpression';
172
+ /** EXISTS/NOT EXISTS operator */
173
+ operator: SqlOperator;
174
+ /** Subquery to check */
175
+ subquery: SelectQueryNode;
176
+ }
177
+
178
+ /**
179
+ * AST node representing a BETWEEN/NOT BETWEEN expression
180
+ */
181
+ export interface BetweenExpressionNode {
182
+ type: 'BetweenExpression';
183
+ /** Operand to check */
184
+ left: OperandNode;
185
+ /** BETWEEN/NOT BETWEEN operator */
186
+ operator: 'BETWEEN' | 'NOT BETWEEN';
187
+ /** Lower bound */
188
+ lower: OperandNode;
189
+ /** Upper bound */
190
+ upper: OperandNode;
191
+ }
192
+
193
+ /**
194
+ * Union type representing any supported expression node
195
+ */
196
+ export type ExpressionNode =
197
+ | BinaryExpressionNode
198
+ | LogicalExpressionNode
199
+ | NullExpressionNode
200
+ | InExpressionNode
201
+ | ExistsExpressionNode
202
+ | BetweenExpressionNode;
203
+
204
+ const operandTypes = new Set<OperandNode['type']>([
205
+ 'Column',
206
+ 'Literal',
207
+ 'Function',
208
+ 'JsonPath',
209
+ 'ScalarSubquery',
210
+ 'CaseExpression',
211
+ 'WindowFunction'
212
+ ]);
213
+
214
+ const isOperandNode = (node: any): node is OperandNode => {
215
+ return node && operandTypes.has(node.type);
216
+ };
217
+
218
+ export const isFunctionNode = (node: any): node is FunctionNode => node?.type === 'Function';
219
+ export const isCaseExpressionNode = (node: any): node is CaseExpressionNode => node?.type === 'CaseExpression';
220
+ export const isWindowFunctionNode = (node: any): node is WindowFunctionNode => node?.type === 'WindowFunction';
221
+ export const isExpressionSelectionNode = (
222
+ node: ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode
223
+ ): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
224
+ isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
225
+
226
+ // Helper to convert Schema definition to AST Node
227
+ const toNode = (col: ColumnDef | OperandNode): OperandNode => {
228
+ if (isOperandNode(col)) return col as OperandNode;
229
+ const def = col as ColumnDef;
230
+ return { type: 'Column', table: def.table || 'unknown', name: def.name };
231
+ };
232
+
233
+ const toLiteralNode = (value: string | number | boolean | null): LiteralNode => ({
234
+ type: 'Literal',
235
+ value
236
+ });
237
+
238
+ const toOperand = (val: OperandNode | ColumnDef | string | number | boolean | null): OperandNode => {
239
+ if (val === null) return { type: 'Literal', value: null };
240
+ if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
241
+ return { type: 'Literal', value: val };
242
+ }
243
+ return toNode(val as OperandNode | ColumnDef);
244
+ };
245
+
246
+ // Factories
247
+ const createBinaryExpression = (
248
+ operator: SqlOperator,
249
+ left: OperandNode | ColumnDef,
250
+ right: OperandNode | ColumnDef | string | number | boolean | null,
251
+ escape?: string
252
+ ): BinaryExpressionNode => {
253
+ const node: BinaryExpressionNode = {
254
+ type: 'BinaryExpression',
255
+ left: toNode(left),
256
+ operator,
257
+ right: toOperand(right)
258
+ };
259
+
260
+ if (escape !== undefined) {
261
+ node.escape = toLiteralNode(escape);
262
+ }
263
+
264
+ return node;
265
+ };
266
+
267
+ /**
268
+ * Creates an equality expression (left = right)
269
+ * @param left - Left operand
270
+ * @param right - Right operand
271
+ * @returns Binary expression node with equality operator
272
+ */
255
273
  export const eq = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
256
274
  createBinaryExpression('=', left, right);
257
275
 
258
276
  /**
259
- * Creates a greater-than expression (left > right)
260
- * @param left - Left operand
261
- * @param right - Right operand
262
- * @returns Binary expression node with greater-than operator
277
+ * Creates a not equal expression (left != right)
263
278
  */
279
+ export const neq = (
280
+ left: OperandNode | ColumnDef,
281
+ right: OperandNode | ColumnDef | string | number
282
+ ): BinaryExpressionNode => createBinaryExpression('!=', left, right);
283
+
284
+ /**
285
+ * Creates a greater-than expression (left > right)
286
+ * @param left - Left operand
287
+ * @param right - Right operand
288
+ * @returns Binary expression node with greater-than operator
289
+ */
264
290
  export const gt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
265
291
  createBinaryExpression('>', left, right);
266
292
 
293
+ /**
294
+ * Creates a greater than or equal expression (left >= right)
295
+ */
296
+ export const gte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
297
+ createBinaryExpression('>=', left, right);
298
+
267
299
  /**
268
300
  * Creates a less-than expression (left < right)
269
301
  * @param left - Left operand
@@ -274,347 +306,353 @@ export const lt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef
274
306
  createBinaryExpression('<', left, right);
275
307
 
276
308
  /**
277
- * Creates a LIKE pattern matching expression
278
- * @param left - Left operand
279
- * @param pattern - Pattern to match
280
- * @param escape - Optional escape character
281
- * @returns Binary expression node with LIKE operator
309
+ * Creates a less than or equal expression (left <= right)
282
310
  */
283
- export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
284
- createBinaryExpression('LIKE', left, pattern, escape);
311
+ export const lte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
312
+ createBinaryExpression('<=', left, right);
285
313
 
286
314
  /**
287
- * Creates a NOT LIKE pattern matching expression
315
+ * Creates a LIKE pattern matching expression
288
316
  * @param left - Left operand
289
- * @param pattern - Pattern to match
290
- * @param escape - Optional escape character
291
- * @returns Binary expression node with NOT LIKE operator
292
- */
293
- export const notLike = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
294
- createBinaryExpression('NOT LIKE', left, pattern, escape);
295
-
296
- /**
297
- * Creates a logical AND expression
298
- * @param operands - Expressions to combine with AND
299
- * @returns Logical expression node with AND operator
300
- */
301
- export const and = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
302
- type: 'LogicalExpression',
303
- operator: 'AND',
304
- operands
305
- });
306
-
307
- /**
308
- * Creates a logical OR expression
309
- * @param operands - Expressions to combine with OR
310
- * @returns Logical expression node with OR operator
311
- */
312
- export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
313
- type: 'LogicalExpression',
314
- operator: 'OR',
315
- operands
316
- });
317
-
318
- /**
319
- * Creates an IS NULL expression
320
- * @param left - Operand to check for null
321
- * @returns Null expression node with IS NULL operator
322
- */
323
- export const isNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
324
- type: 'NullExpression',
325
- left: toNode(left),
326
- operator: 'IS NULL'
327
- });
328
-
329
- /**
330
- * Creates an IS NOT NULL expression
331
- * @param left - Operand to check for non-null
332
- * @returns Null expression node with IS NOT NULL operator
333
- */
334
- export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
335
- type: 'NullExpression',
336
- left: toNode(left),
337
- operator: 'IS NOT NULL'
338
- });
339
-
340
- const createInExpression = (
341
- operator: 'IN' | 'NOT IN',
342
- left: OperandNode | ColumnDef,
343
- values: (string | number | LiteralNode)[]
344
- ): InExpressionNode => ({
345
- type: 'InExpression',
346
- left: toNode(left),
347
- operator,
348
- right: values.map(v => toOperand(v))
349
- });
350
-
351
- /**
352
- * Creates an IN expression (value IN list)
353
- * @param left - Operand to check
354
- * @param values - Values to check against
355
- * @returns IN expression node
356
- */
357
- export const inList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
358
- createInExpression('IN', left, values);
359
-
360
- /**
361
- * Creates a NOT IN expression (value NOT IN list)
362
- * @param left - Operand to check
363
- * @param values - Values to check against
364
- * @returns NOT IN expression node
365
- */
366
- export const notInList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
367
- createInExpression('NOT IN', left, values);
368
-
369
- const createBetweenExpression = (
370
- operator: 'BETWEEN' | 'NOT BETWEEN',
371
- left: OperandNode | ColumnDef,
372
- lower: OperandNode | ColumnDef | string | number,
373
- upper: OperandNode | ColumnDef | string | number
374
- ): BetweenExpressionNode => ({
375
- type: 'BetweenExpression',
376
- left: toNode(left),
377
- operator,
378
- lower: toOperand(lower),
379
- upper: toOperand(upper)
380
- });
381
-
382
- /**
383
- * Creates a BETWEEN expression (value BETWEEN lower AND upper)
384
- * @param left - Operand to check
385
- * @param lower - Lower bound
386
- * @param upper - Upper bound
387
- * @returns BETWEEN expression node
388
- */
389
- export const between = (
390
- left: OperandNode | ColumnDef,
391
- lower: OperandNode | ColumnDef | string | number,
392
- upper: OperandNode | ColumnDef | string | number
393
- ): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
394
-
395
- /**
396
- * Creates a NOT BETWEEN expression (value NOT BETWEEN lower AND upper)
397
- * @param left - Operand to check
398
- * @param lower - Lower bound
399
- * @param upper - Upper bound
400
- * @returns NOT BETWEEN expression node
401
- */
402
- export const notBetween = (
403
- left: OperandNode | ColumnDef,
404
- lower: OperandNode | ColumnDef | string | number,
405
- upper: OperandNode | ColumnDef | string | number
406
- ): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
407
-
408
- /**
409
- * Creates a JSON path expression
410
- * @param col - Source column
411
- * @param path - JSON path expression
412
- * @returns JSON path node
413
- */
414
- export const jsonPath = (col: ColumnDef | ColumnNode, path: string): JsonPathNode => ({
415
- type: 'JsonPath',
416
- column: toNode(col) as ColumnNode,
417
- path
418
- });
419
-
420
- /**
421
- * Creates a COUNT function expression
422
- * @param col - Column to count
423
- * @returns Function node with COUNT
424
- */
425
- export const count = (col: ColumnDef | ColumnNode): FunctionNode => ({
426
- type: 'Function',
427
- name: 'COUNT',
428
- args: [toNode(col) as ColumnNode]
429
- });
430
-
431
- /**
432
- * Creates a SUM function expression
433
- * @param col - Column to sum
434
- * @returns Function node with SUM
435
- */
436
- export const sum = (col: ColumnDef | ColumnNode): FunctionNode => ({
437
- type: 'Function',
438
- name: 'SUM',
439
- args: [toNode(col) as ColumnNode]
440
- });
441
-
442
- /**
443
- * Creates an AVG function expression
444
- * @param col - Column to average
445
- * @returns Function node with AVG
446
- */
447
- export const avg = (col: ColumnDef | ColumnNode): FunctionNode => ({
448
- type: 'Function',
449
- name: 'AVG',
450
- args: [toNode(col) as ColumnNode]
451
- });
452
-
453
- /**
454
- * Creates an EXISTS expression
455
- * @param subquery - Subquery to check for existence
456
- * @returns EXISTS expression node
457
- */
458
- export const exists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
459
- type: 'ExistsExpression',
460
- operator: 'EXISTS',
461
- subquery
462
- });
463
-
464
- /**
465
- * Creates a NOT EXISTS expression
466
- * @param subquery - Subquery to check for non-existence
467
- * @returns NOT EXISTS expression node
468
- */
469
- export const notExists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
470
- type: 'ExistsExpression',
471
- operator: 'NOT EXISTS',
472
- subquery
473
- });
474
-
475
- /**
476
- * Creates a CASE expression
477
- * @param conditions - Array of WHEN-THEN conditions
478
- * @param elseValue - Optional ELSE value
479
- * @returns CASE expression node
480
- */
481
- export const caseWhen = (
482
- conditions: { when: ExpressionNode; then: OperandNode | ColumnDef | string | number | boolean | null }[],
483
- elseValue?: OperandNode | ColumnDef | string | number | boolean | null
484
- ): CaseExpressionNode => ({
485
- type: 'CaseExpression',
486
- conditions: conditions.map(c => ({
487
- when: c.when,
488
- then: toOperand(c.then)
489
- })),
490
- else: elseValue !== undefined ? toOperand(elseValue) : undefined
491
- });
492
-
493
- // Window function factories
494
- const buildWindowFunction = (
495
- name: string,
496
- args: (ColumnNode | LiteralNode | JsonPathNode)[] = [],
497
- partitionBy?: ColumnNode[],
498
- orderBy?: OrderByNode[]
499
- ): WindowFunctionNode => {
500
- const node: WindowFunctionNode = {
501
- type: 'WindowFunction',
502
- name,
503
- args
504
- };
505
-
506
- if (partitionBy && partitionBy.length) {
507
- node.partitionBy = partitionBy;
508
- }
509
-
510
- if (orderBy && orderBy.length) {
511
- node.orderBy = orderBy;
512
- }
513
-
514
- return node;
515
- };
516
-
517
- /**
518
- * Creates a ROW_NUMBER window function
519
- * @returns Window function node for ROW_NUMBER
520
- */
521
- export const rowNumber = (): WindowFunctionNode => buildWindowFunction('ROW_NUMBER');
522
-
523
- /**
524
- * Creates a RANK window function
525
- * @returns Window function node for RANK
526
- */
527
- export const rank = (): WindowFunctionNode => buildWindowFunction('RANK');
528
-
529
- /**
530
- * Creates a DENSE_RANK window function
531
- * @returns Window function node for DENSE_RANK
532
- */
533
- export const denseRank = (): WindowFunctionNode => buildWindowFunction('DENSE_RANK');
534
-
535
- /**
536
- * Creates an NTILE window function
537
- * @param n - Number of buckets
538
- * @returns Window function node for NTILE
539
- */
540
- export const ntile = (n: number): WindowFunctionNode => buildWindowFunction('NTILE', [{ type: 'Literal', value: n }]);
541
-
542
- const columnOperand = (col: ColumnDef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
543
-
544
- /**
545
- * Creates a LAG window function
546
- * @param col - Column to lag
547
- * @param offset - Offset (defaults to 1)
548
- * @param defaultValue - Default value if no row exists
549
- * @returns Window function node for LAG
550
- */
551
- export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
552
- const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
553
- if (defaultValue !== undefined) {
554
- args.push({ type: 'Literal', value: defaultValue });
555
- }
556
- return buildWindowFunction('LAG', args);
557
- };
558
-
559
- /**
560
- * Creates a LEAD window function
561
- * @param col - Column to lead
562
- * @param offset - Offset (defaults to 1)
563
- * @param defaultValue - Default value if no row exists
564
- * @returns Window function node for LEAD
565
- */
566
- export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
567
- const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
568
- if (defaultValue !== undefined) {
569
- args.push({ type: 'Literal', value: defaultValue });
570
- }
571
- return buildWindowFunction('LEAD', args);
572
- };
573
-
574
- /**
575
- * Creates a FIRST_VALUE window function
576
- * @param col - Column to get first value from
577
- * @returns Window function node for FIRST_VALUE
578
- */
579
- export const firstValue = (col: ColumnDef | ColumnNode): WindowFunctionNode => buildWindowFunction('FIRST_VALUE', [columnOperand(col)]);
580
-
581
- /**
582
- * Creates a LAST_VALUE window function
583
- * @param col - Column to get last value from
584
- * @returns Window function node for LAST_VALUE
585
- */
586
- export const lastValue = (col: ColumnDef | ColumnNode): WindowFunctionNode => buildWindowFunction('LAST_VALUE', [columnOperand(col)]);
587
-
588
- /**
589
- * Creates a custom window function
590
- * @param name - Window function name
591
- * @param args - Function arguments
592
- * @param partitionBy - Optional PARTITION BY columns
593
- * @param orderBy - Optional ORDER BY clauses
594
- * @returns Window function node
595
- */
596
- export const windowFunction = (
597
- name: string,
598
- args: (ColumnDef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
599
- partitionBy?: (ColumnDef | ColumnNode)[],
600
- orderBy?: { column: ColumnDef | ColumnNode, direction: OrderDirection }[]
601
- ): WindowFunctionNode => {
602
- const nodeArgs = args.map(arg => {
603
- if ((arg as LiteralNode).value !== undefined) {
604
- return arg as LiteralNode;
605
- }
606
- if ((arg as JsonPathNode).path) {
607
- return arg as JsonPathNode;
608
- }
609
- return columnOperand(arg as ColumnDef | ColumnNode);
610
- });
611
-
612
- const partitionNodes = partitionBy?.map(col => columnOperand(col)) ?? undefined;
613
- const orderNodes: OrderByNode[] | undefined = orderBy?.map(o => ({
614
- type: 'OrderBy',
615
- column: columnOperand(o.column),
616
- direction: o.direction
617
- }));
618
-
619
- return buildWindowFunction(name, nodeArgs, partitionNodes, orderNodes);
620
- };
317
+ * @param pattern - Pattern to match
318
+ * @param escape - Optional escape character
319
+ * @returns Binary expression node with LIKE operator
320
+ */
321
+ export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
322
+ createBinaryExpression('LIKE', left, pattern, escape);
323
+
324
+ /**
325
+ * Creates a NOT LIKE pattern matching expression
326
+ * @param left - Left operand
327
+ * @param pattern - Pattern to match
328
+ * @param escape - Optional escape character
329
+ * @returns Binary expression node with NOT LIKE operator
330
+ */
331
+ export const notLike = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
332
+ createBinaryExpression('NOT LIKE', left, pattern, escape);
333
+
334
+ /**
335
+ * Creates a logical AND expression
336
+ * @param operands - Expressions to combine with AND
337
+ * @returns Logical expression node with AND operator
338
+ */
339
+ export const and = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
340
+ type: 'LogicalExpression',
341
+ operator: 'AND',
342
+ operands
343
+ });
344
+
345
+ /**
346
+ * Creates a logical OR expression
347
+ * @param operands - Expressions to combine with OR
348
+ * @returns Logical expression node with OR operator
349
+ */
350
+ export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
351
+ type: 'LogicalExpression',
352
+ operator: 'OR',
353
+ operands
354
+ });
355
+
356
+ /**
357
+ * Creates an IS NULL expression
358
+ * @param left - Operand to check for null
359
+ * @returns Null expression node with IS NULL operator
360
+ */
361
+ export const isNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
362
+ type: 'NullExpression',
363
+ left: toNode(left),
364
+ operator: 'IS NULL'
365
+ });
366
+
367
+ /**
368
+ * Creates an IS NOT NULL expression
369
+ * @param left - Operand to check for non-null
370
+ * @returns Null expression node with IS NOT NULL operator
371
+ */
372
+ export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
373
+ type: 'NullExpression',
374
+ left: toNode(left),
375
+ operator: 'IS NOT NULL'
376
+ });
377
+
378
+ const createInExpression = (
379
+ operator: 'IN' | 'NOT IN',
380
+ left: OperandNode | ColumnDef,
381
+ values: (string | number | LiteralNode)[]
382
+ ): InExpressionNode => ({
383
+ type: 'InExpression',
384
+ left: toNode(left),
385
+ operator,
386
+ right: values.map(v => toOperand(v))
387
+ });
388
+
389
+ /**
390
+ * Creates an IN expression (value IN list)
391
+ * @param left - Operand to check
392
+ * @param values - Values to check against
393
+ * @returns IN expression node
394
+ */
395
+ export const inList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
396
+ createInExpression('IN', left, values);
397
+
398
+ /**
399
+ * Creates a NOT IN expression (value NOT IN list)
400
+ * @param left - Operand to check
401
+ * @param values - Values to check against
402
+ * @returns NOT IN expression node
403
+ */
404
+ export const notInList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
405
+ createInExpression('NOT IN', left, values);
406
+
407
+ const createBetweenExpression = (
408
+ operator: 'BETWEEN' | 'NOT BETWEEN',
409
+ left: OperandNode | ColumnDef,
410
+ lower: OperandNode | ColumnDef | string | number,
411
+ upper: OperandNode | ColumnDef | string | number
412
+ ): BetweenExpressionNode => ({
413
+ type: 'BetweenExpression',
414
+ left: toNode(left),
415
+ operator,
416
+ lower: toOperand(lower),
417
+ upper: toOperand(upper)
418
+ });
419
+
420
+ /**
421
+ * Creates a BETWEEN expression (value BETWEEN lower AND upper)
422
+ * @param left - Operand to check
423
+ * @param lower - Lower bound
424
+ * @param upper - Upper bound
425
+ * @returns BETWEEN expression node
426
+ */
427
+ export const between = (
428
+ left: OperandNode | ColumnDef,
429
+ lower: OperandNode | ColumnDef | string | number,
430
+ upper: OperandNode | ColumnDef | string | number
431
+ ): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
432
+
433
+ /**
434
+ * Creates a NOT BETWEEN expression (value NOT BETWEEN lower AND upper)
435
+ * @param left - Operand to check
436
+ * @param lower - Lower bound
437
+ * @param upper - Upper bound
438
+ * @returns NOT BETWEEN expression node
439
+ */
440
+ export const notBetween = (
441
+ left: OperandNode | ColumnDef,
442
+ lower: OperandNode | ColumnDef | string | number,
443
+ upper: OperandNode | ColumnDef | string | number
444
+ ): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
445
+
446
+ /**
447
+ * Creates a JSON path expression
448
+ * @param col - Source column
449
+ * @param path - JSON path expression
450
+ * @returns JSON path node
451
+ */
452
+ export const jsonPath = (col: ColumnDef | ColumnNode, path: string): JsonPathNode => ({
453
+ type: 'JsonPath',
454
+ column: toNode(col) as ColumnNode,
455
+ path
456
+ });
457
+
458
+ /**
459
+ * Creates a COUNT function expression
460
+ * @param col - Column to count
461
+ * @returns Function node with COUNT
462
+ */
463
+ export const count = (col: ColumnDef | ColumnNode): FunctionNode => ({
464
+ type: 'Function',
465
+ name: 'COUNT',
466
+ args: [toNode(col) as ColumnNode]
467
+ });
468
+
469
+ /**
470
+ * Creates a SUM function expression
471
+ * @param col - Column to sum
472
+ * @returns Function node with SUM
473
+ */
474
+ export const sum = (col: ColumnDef | ColumnNode): FunctionNode => ({
475
+ type: 'Function',
476
+ name: 'SUM',
477
+ args: [toNode(col) as ColumnNode]
478
+ });
479
+
480
+ /**
481
+ * Creates an AVG function expression
482
+ * @param col - Column to average
483
+ * @returns Function node with AVG
484
+ */
485
+ export const avg = (col: ColumnDef | ColumnNode): FunctionNode => ({
486
+ type: 'Function',
487
+ name: 'AVG',
488
+ args: [toNode(col) as ColumnNode]
489
+ });
490
+
491
+ /**
492
+ * Creates an EXISTS expression
493
+ * @param subquery - Subquery to check for existence
494
+ * @returns EXISTS expression node
495
+ */
496
+ export const exists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
497
+ type: 'ExistsExpression',
498
+ operator: 'EXISTS',
499
+ subquery
500
+ });
501
+
502
+ /**
503
+ * Creates a NOT EXISTS expression
504
+ * @param subquery - Subquery to check for non-existence
505
+ * @returns NOT EXISTS expression node
506
+ */
507
+ export const notExists = (subquery: SelectQueryNode): ExistsExpressionNode => ({
508
+ type: 'ExistsExpression',
509
+ operator: 'NOT EXISTS',
510
+ subquery
511
+ });
512
+
513
+ /**
514
+ * Creates a CASE expression
515
+ * @param conditions - Array of WHEN-THEN conditions
516
+ * @param elseValue - Optional ELSE value
517
+ * @returns CASE expression node
518
+ */
519
+ export const caseWhen = (
520
+ conditions: { when: ExpressionNode; then: OperandNode | ColumnDef | string | number | boolean | null }[],
521
+ elseValue?: OperandNode | ColumnDef | string | number | boolean | null
522
+ ): CaseExpressionNode => ({
523
+ type: 'CaseExpression',
524
+ conditions: conditions.map(c => ({
525
+ when: c.when,
526
+ then: toOperand(c.then)
527
+ })),
528
+ else: elseValue !== undefined ? toOperand(elseValue) : undefined
529
+ });
530
+
531
+ // Window function factories
532
+ const buildWindowFunction = (
533
+ name: string,
534
+ args: (ColumnNode | LiteralNode | JsonPathNode)[] = [],
535
+ partitionBy?: ColumnNode[],
536
+ orderBy?: OrderByNode[]
537
+ ): WindowFunctionNode => {
538
+ const node: WindowFunctionNode = {
539
+ type: 'WindowFunction',
540
+ name,
541
+ args
542
+ };
543
+
544
+ if (partitionBy && partitionBy.length) {
545
+ node.partitionBy = partitionBy;
546
+ }
547
+
548
+ if (orderBy && orderBy.length) {
549
+ node.orderBy = orderBy;
550
+ }
551
+
552
+ return node;
553
+ };
554
+
555
+ /**
556
+ * Creates a ROW_NUMBER window function
557
+ * @returns Window function node for ROW_NUMBER
558
+ */
559
+ export const rowNumber = (): WindowFunctionNode => buildWindowFunction('ROW_NUMBER');
560
+
561
+ /**
562
+ * Creates a RANK window function
563
+ * @returns Window function node for RANK
564
+ */
565
+ export const rank = (): WindowFunctionNode => buildWindowFunction('RANK');
566
+
567
+ /**
568
+ * Creates a DENSE_RANK window function
569
+ * @returns Window function node for DENSE_RANK
570
+ */
571
+ export const denseRank = (): WindowFunctionNode => buildWindowFunction('DENSE_RANK');
572
+
573
+ /**
574
+ * Creates an NTILE window function
575
+ * @param n - Number of buckets
576
+ * @returns Window function node for NTILE
577
+ */
578
+ export const ntile = (n: number): WindowFunctionNode => buildWindowFunction('NTILE', [{ type: 'Literal', value: n }]);
579
+
580
+ const columnOperand = (col: ColumnDef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
581
+
582
+ /**
583
+ * Creates a LAG window function
584
+ * @param col - Column to lag
585
+ * @param offset - Offset (defaults to 1)
586
+ * @param defaultValue - Default value if no row exists
587
+ * @returns Window function node for LAG
588
+ */
589
+ export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
590
+ const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
591
+ if (defaultValue !== undefined) {
592
+ args.push({ type: 'Literal', value: defaultValue });
593
+ }
594
+ return buildWindowFunction('LAG', args);
595
+ };
596
+
597
+ /**
598
+ * Creates a LEAD window function
599
+ * @param col - Column to lead
600
+ * @param offset - Offset (defaults to 1)
601
+ * @param defaultValue - Default value if no row exists
602
+ * @returns Window function node for LEAD
603
+ */
604
+ export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
605
+ const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
606
+ if (defaultValue !== undefined) {
607
+ args.push({ type: 'Literal', value: defaultValue });
608
+ }
609
+ return buildWindowFunction('LEAD', args);
610
+ };
611
+
612
+ /**
613
+ * Creates a FIRST_VALUE window function
614
+ * @param col - Column to get first value from
615
+ * @returns Window function node for FIRST_VALUE
616
+ */
617
+ export const firstValue = (col: ColumnDef | ColumnNode): WindowFunctionNode => buildWindowFunction('FIRST_VALUE', [columnOperand(col)]);
618
+
619
+ /**
620
+ * Creates a LAST_VALUE window function
621
+ * @param col - Column to get last value from
622
+ * @returns Window function node for LAST_VALUE
623
+ */
624
+ export const lastValue = (col: ColumnDef | ColumnNode): WindowFunctionNode => buildWindowFunction('LAST_VALUE', [columnOperand(col)]);
625
+
626
+ /**
627
+ * Creates a custom window function
628
+ * @param name - Window function name
629
+ * @param args - Function arguments
630
+ * @param partitionBy - Optional PARTITION BY columns
631
+ * @param orderBy - Optional ORDER BY clauses
632
+ * @returns Window function node
633
+ */
634
+ export const windowFunction = (
635
+ name: string,
636
+ args: (ColumnDef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
637
+ partitionBy?: (ColumnDef | ColumnNode)[],
638
+ orderBy?: { column: ColumnDef | ColumnNode, direction: OrderDirection }[]
639
+ ): WindowFunctionNode => {
640
+ const nodeArgs = args.map(arg => {
641
+ if ((arg as LiteralNode).value !== undefined) {
642
+ return arg as LiteralNode;
643
+ }
644
+ if ((arg as JsonPathNode).path) {
645
+ return arg as JsonPathNode;
646
+ }
647
+ return columnOperand(arg as ColumnDef | ColumnNode);
648
+ });
649
+
650
+ const partitionNodes = partitionBy?.map(col => columnOperand(col)) ?? undefined;
651
+ const orderNodes: OrderByNode[] | undefined = orderBy?.map(o => ({
652
+ type: 'OrderBy',
653
+ column: columnOperand(o.column),
654
+ direction: o.direction
655
+ }));
656
+
657
+ return buildWindowFunction(name, nodeArgs, partitionNodes, orderNodes);
658
+ };