metal-orm 1.0.1 → 1.0.3
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.
- package/.github/workflows/publish-metal-orm.yml +38 -0
- package/README.md +46 -482
- package/docs/advanced-features.md +85 -0
- package/docs/api-reference.md +22 -0
- package/docs/getting-started.md +104 -0
- package/docs/hydration.md +41 -0
- package/docs/index.md +31 -0
- package/docs/multi-dialect-support.md +34 -0
- package/docs/query-builder.md +75 -0
- package/docs/schema-definition.md +61 -0
- package/package.json +1 -1
- package/src/ast/expression.ts +433 -175
- package/src/ast/join.ts +8 -1
- package/src/ast/query.ts +64 -9
- package/src/builder/hydration-manager.ts +42 -11
- package/src/builder/hydration-planner.ts +80 -31
- package/src/builder/operations/column-selector.ts +37 -1
- package/src/builder/operations/cte-manager.ts +16 -0
- package/src/builder/operations/filter-manager.ts +32 -0
- package/src/builder/operations/join-manager.ts +17 -7
- package/src/builder/operations/pagination-manager.ts +19 -0
- package/src/builder/operations/relation-manager.ts +58 -3
- package/src/builder/query-ast-service.ts +100 -29
- package/src/builder/relation-conditions.ts +30 -1
- package/src/builder/relation-projection-helper.ts +43 -1
- package/src/builder/relation-service.ts +68 -13
- package/src/builder/relation-types.ts +6 -0
- package/src/builder/select-query-builder-deps.ts +64 -3
- package/src/builder/select-query-state.ts +72 -0
- package/src/builder/select.ts +166 -0
- package/src/codegen/typescript.ts +142 -44
- package/src/constants/sql-operator-config.ts +36 -0
- package/src/constants/sql.ts +125 -57
- package/src/dialect/abstract.ts +97 -22
- package/src/dialect/mssql/index.ts +27 -0
- package/src/dialect/mysql/index.ts +22 -0
- package/src/dialect/postgres/index.ts +103 -0
- package/src/dialect/sqlite/index.ts +22 -0
- package/src/runtime/als.ts +15 -1
- package/src/runtime/hydration.ts +20 -15
- package/src/schema/column.ts +45 -5
- package/src/schema/relation.ts +49 -2
- package/src/schema/table.ts +27 -3
- package/src/utils/join-node.ts +20 -0
- package/src/utils/raw-column-parser.ts +32 -0
- package/src/utils/relation-alias.ts +43 -0
- package/tests/postgres.test.ts +30 -0
- package/tests/window-function.test.ts +14 -0
package/src/ast/expression.ts
CHANGED
|
@@ -1,106 +1,188 @@
|
|
|
1
|
-
import { ColumnDef } from '../schema/column';
|
|
2
|
-
import type { SelectQueryNode, OrderByNode } from './query';
|
|
3
|
-
import { OrderDirection } from '../constants/sql';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export interface
|
|
18
|
-
type: '
|
|
19
|
-
name
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
alias?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
export type OperandNode = ColumnNode | LiteralNode | FunctionNode | JsonPathNode | ScalarSubqueryNode | CaseExpressionNode | WindowFunctionNode;
|
|
98
|
+
|
|
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
|
|
177
|
+
*/
|
|
178
|
+
export type ExpressionNode =
|
|
179
|
+
| BinaryExpressionNode
|
|
180
|
+
| LogicalExpressionNode
|
|
181
|
+
| NullExpressionNode
|
|
182
|
+
| InExpressionNode
|
|
183
|
+
| ExistsExpressionNode
|
|
184
|
+
| BetweenExpressionNode;
|
|
185
|
+
|
|
104
186
|
const operandTypes = new Set<OperandNode['type']>([
|
|
105
187
|
'Column',
|
|
106
188
|
'Literal',
|
|
@@ -114,19 +196,27 @@ const operandTypes = new Set<OperandNode['type']>([
|
|
|
114
196
|
const isOperandNode = (node: any): node is OperandNode => {
|
|
115
197
|
return node && operandTypes.has(node.type);
|
|
116
198
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
|
|
130
220
|
const toOperand = (val: OperandNode | ColumnDef | string | number | boolean | null): OperandNode => {
|
|
131
221
|
if (val === null) return { type: 'Literal', value: null };
|
|
132
222
|
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
@@ -134,10 +224,10 @@ const toOperand = (val: OperandNode | ColumnDef | string | number | boolean | nu
|
|
|
134
224
|
}
|
|
135
225
|
return toNode(val as OperandNode | ColumnDef);
|
|
136
226
|
};
|
|
137
|
-
|
|
227
|
+
|
|
138
228
|
// Factories
|
|
139
229
|
const createBinaryExpression = (
|
|
140
|
-
operator:
|
|
230
|
+
operator: SqlOperator,
|
|
141
231
|
left: OperandNode | ColumnDef,
|
|
142
232
|
right: OperandNode | ColumnDef | string | number | boolean | null,
|
|
143
233
|
escape?: string
|
|
@@ -156,39 +246,91 @@ const createBinaryExpression = (
|
|
|
156
246
|
return node;
|
|
157
247
|
};
|
|
158
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
|
+
*/
|
|
159
255
|
export const eq = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
|
|
160
256
|
createBinaryExpression('=', left, right);
|
|
161
257
|
|
|
258
|
+
/**
|
|
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
|
|
263
|
+
*/
|
|
162
264
|
export const gt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
|
|
163
265
|
createBinaryExpression('>', left, right);
|
|
164
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Creates a less-than expression (left < right)
|
|
269
|
+
* @param left - Left operand
|
|
270
|
+
* @param right - Right operand
|
|
271
|
+
* @returns Binary expression node with less-than operator
|
|
272
|
+
*/
|
|
165
273
|
export const lt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
|
|
166
274
|
createBinaryExpression('<', left, right);
|
|
167
275
|
|
|
276
|
+
/**
|
|
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
|
|
282
|
+
*/
|
|
168
283
|
export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
|
|
169
284
|
createBinaryExpression('LIKE', left, pattern, escape);
|
|
170
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Creates a NOT LIKE pattern matching expression
|
|
288
|
+
* @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
|
+
*/
|
|
171
293
|
export const notLike = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
|
|
172
294
|
createBinaryExpression('NOT LIKE', left, pattern, escape);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
*/
|
|
192
334
|
export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
|
|
193
335
|
type: 'NullExpression',
|
|
194
336
|
left: toNode(left),
|
|
@@ -206,9 +348,21 @@ const createInExpression = (
|
|
|
206
348
|
right: values.map(v => toOperand(v))
|
|
207
349
|
});
|
|
208
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
|
+
*/
|
|
209
357
|
export const inList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
210
358
|
createInExpression('IN', left, values);
|
|
211
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
|
+
*/
|
|
212
366
|
export const notInList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
213
367
|
createInExpression('NOT IN', left, values);
|
|
214
368
|
|
|
@@ -225,54 +379,105 @@ const createBetweenExpression = (
|
|
|
225
379
|
upper: toOperand(upper)
|
|
226
380
|
});
|
|
227
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
|
+
*/
|
|
228
389
|
export const between = (
|
|
229
390
|
left: OperandNode | ColumnDef,
|
|
230
391
|
lower: OperandNode | ColumnDef | string | number,
|
|
231
392
|
upper: OperandNode | ColumnDef | string | number
|
|
232
393
|
): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
|
|
233
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
|
+
*/
|
|
234
402
|
export const notBetween = (
|
|
235
403
|
left: OperandNode | ColumnDef,
|
|
236
404
|
lower: OperandNode | ColumnDef | string | number,
|
|
237
405
|
upper: OperandNode | ColumnDef | string | number
|
|
238
406
|
): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
export const
|
|
247
|
-
type: '
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
*/
|
|
276
481
|
export const caseWhen = (
|
|
277
482
|
conditions: { when: ExpressionNode; then: OperandNode | ColumnDef | string | number | boolean | null }[],
|
|
278
483
|
elseValue?: OperandNode | ColumnDef | string | number | boolean | null
|
|
@@ -284,7 +489,7 @@ export const caseWhen = (
|
|
|
284
489
|
})),
|
|
285
490
|
else: elseValue !== undefined ? toOperand(elseValue) : undefined
|
|
286
491
|
});
|
|
287
|
-
|
|
492
|
+
|
|
288
493
|
// Window function factories
|
|
289
494
|
const buildWindowFunction = (
|
|
290
495
|
name: string,
|
|
@@ -309,13 +514,40 @@ const buildWindowFunction = (
|
|
|
309
514
|
return node;
|
|
310
515
|
};
|
|
311
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Creates a ROW_NUMBER window function
|
|
519
|
+
* @returns Window function node for ROW_NUMBER
|
|
520
|
+
*/
|
|
312
521
|
export const rowNumber = (): WindowFunctionNode => buildWindowFunction('ROW_NUMBER');
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Creates a RANK window function
|
|
525
|
+
* @returns Window function node for RANK
|
|
526
|
+
*/
|
|
313
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
|
+
*/
|
|
314
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
|
+
*/
|
|
315
540
|
export const ntile = (n: number): WindowFunctionNode => buildWindowFunction('NTILE', [{ type: 'Literal', value: n }]);
|
|
316
541
|
|
|
317
542
|
const columnOperand = (col: ColumnDef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
|
|
318
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
|
+
*/
|
|
319
551
|
export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
|
|
320
552
|
const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
|
|
321
553
|
if (defaultValue !== undefined) {
|
|
@@ -324,6 +556,13 @@ export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValu
|
|
|
324
556
|
return buildWindowFunction('LAG', args);
|
|
325
557
|
};
|
|
326
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
|
+
*/
|
|
327
566
|
export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
|
|
328
567
|
const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [columnOperand(col), { type: 'Literal', value: offset }];
|
|
329
568
|
if (defaultValue !== undefined) {
|
|
@@ -332,9 +571,28 @@ export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultVal
|
|
|
332
571
|
return buildWindowFunction('LEAD', args);
|
|
333
572
|
};
|
|
334
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
|
+
*/
|
|
335
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
|
+
*/
|
|
336
586
|
export const lastValue = (col: ColumnDef | ColumnNode): WindowFunctionNode => buildWindowFunction('LAST_VALUE', [columnOperand(col)]);
|
|
337
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
|
+
*/
|
|
338
596
|
export const windowFunction = (
|
|
339
597
|
name: string,
|
|
340
598
|
args: (ColumnDef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
|