metal-orm 1.0.14 → 1.0.15
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/README.md +40 -45
- package/dist/decorators/index.cjs +1600 -27
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -2
- package/dist/decorators/index.d.ts +6 -2
- package/dist/decorators/index.js +1599 -27
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +4608 -3429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +511 -159
- package/dist/index.d.ts +511 -159
- package/dist/index.js +4526 -3415
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
- package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
- package/package.json +1 -1
- package/src/codegen/typescript.ts +38 -35
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +16 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +144 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +30 -19
- package/src/orm/entity-metadata.ts +7 -0
- package/src/orm/entity.ts +58 -27
- package/src/orm/hydration.ts +25 -17
- package/src/orm/lazy-batch.ts +46 -2
- package/src/orm/orm-context.ts +60 -60
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +43 -2
- package/src/orm/relations/has-one.ts +139 -0
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +60 -60
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +66 -61
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
package/src/core/ast/builders.ts
CHANGED
|
@@ -1,43 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* @param
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
* @param
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
import { ColumnNode } from './expression-nodes.js';
|
|
2
|
+
import { TableNode, FunctionTableNode } from './query.js';
|
|
3
|
+
import { ColumnRef, TableRef } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds or normalizes a column AST node from a column definition or existing node
|
|
7
|
+
* @param table - Table definition providing a default table name
|
|
8
|
+
* @param column - Column definition or existing column node
|
|
9
|
+
*/
|
|
10
|
+
export const buildColumnNode = (table: TableRef, column: ColumnRef | ColumnNode): ColumnNode => {
|
|
11
|
+
if ((column as ColumnNode).type === 'Column') {
|
|
12
|
+
return column as ColumnNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const def = column as ColumnRef;
|
|
16
|
+
return {
|
|
17
|
+
type: 'Column',
|
|
18
|
+
table: def.table || table.name,
|
|
19
|
+
name: def.name
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Builds column AST nodes for a list of column names
|
|
25
|
+
* @param table - Table definition providing the table name
|
|
26
|
+
* @param names - Column names
|
|
27
|
+
*/
|
|
28
|
+
export const buildColumnNodes = (table: TableRef, names: string[]): ColumnNode[] =>
|
|
29
|
+
names.map(name => ({
|
|
30
|
+
type: 'Column',
|
|
31
|
+
table: table.name,
|
|
32
|
+
name
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Builds a table AST node for the provided table definition
|
|
37
|
+
* @param table - Table definition
|
|
38
|
+
*/
|
|
39
|
+
export const createTableNode = (table: TableRef): TableNode => ({
|
|
40
|
+
type: 'Table',
|
|
41
|
+
name: table.name
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a FunctionTable node for expressions like `function_name(args...)` used in FROM
|
|
46
|
+
*/
|
|
47
|
+
export const fnTable = (name: string, args: any[] = [], alias?: string, opts?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }): FunctionTableNode => ({
|
|
48
|
+
type: 'FunctionTable',
|
|
49
|
+
name,
|
|
50
|
+
args,
|
|
51
|
+
alias,
|
|
52
|
+
lateral: opts?.lateral,
|
|
53
|
+
withOrdinality: opts?.withOrdinality,
|
|
54
|
+
columnAliases: opts?.columnAliases,
|
|
55
|
+
schema: opts?.schema
|
|
56
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ColumnDef } from '../../schema/column.js';
|
|
2
1
|
import { SelectQueryNode } from './query.js';
|
|
3
2
|
import { SqlOperator } from '../sql/sql.js';
|
|
3
|
+
import { ColumnRef } from './types.js';
|
|
4
4
|
import {
|
|
5
5
|
ColumnNode,
|
|
6
6
|
FunctionNode,
|
|
@@ -37,9 +37,9 @@ export const valueToOperand = (value: unknown): OperandNode => {
|
|
|
37
37
|
return value as OperandNode;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const toNode = (col:
|
|
40
|
+
const toNode = (col: ColumnRef | OperandNode): OperandNode => {
|
|
41
41
|
if (isOperandNode(col)) return col as OperandNode;
|
|
42
|
-
const def = col as
|
|
42
|
+
const def = col as ColumnRef;
|
|
43
43
|
return { type: 'Column', table: def.table || 'unknown', name: def.name };
|
|
44
44
|
};
|
|
45
45
|
|
|
@@ -48,20 +48,20 @@ const toLiteralNode = (value: string | number | boolean | null): LiteralNode =>
|
|
|
48
48
|
value
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
const toOperand = (val: OperandNode |
|
|
51
|
+
const toOperand = (val: OperandNode | ColumnRef | string | number | boolean | null): OperandNode => {
|
|
52
52
|
if (val === null) return { type: 'Literal', value: null };
|
|
53
53
|
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
54
54
|
return { type: 'Literal', value: val };
|
|
55
55
|
}
|
|
56
|
-
return toNode(val as OperandNode |
|
|
56
|
+
return toNode(val as OperandNode | ColumnRef);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export const columnOperand = (col:
|
|
59
|
+
export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
|
|
60
60
|
|
|
61
61
|
const createBinaryExpression = (
|
|
62
62
|
operator: SqlOperator,
|
|
63
|
-
left: OperandNode |
|
|
64
|
-
right: OperandNode |
|
|
63
|
+
left: OperandNode | ColumnRef,
|
|
64
|
+
right: OperandNode | ColumnRef | string | number | boolean | null,
|
|
65
65
|
escape?: string
|
|
66
66
|
): BinaryExpressionNode => {
|
|
67
67
|
const node: BinaryExpressionNode = {
|
|
@@ -84,15 +84,15 @@ const createBinaryExpression = (
|
|
|
84
84
|
* @param right - Right operand
|
|
85
85
|
* @returns Binary expression node with equality operator
|
|
86
86
|
*/
|
|
87
|
-
export const eq = (left: OperandNode |
|
|
87
|
+
export const eq = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number | boolean): BinaryExpressionNode =>
|
|
88
88
|
createBinaryExpression('=', left, right);
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Creates a not equal expression (left != right)
|
|
92
92
|
*/
|
|
93
93
|
export const neq = (
|
|
94
|
-
left: OperandNode |
|
|
95
|
-
right: OperandNode |
|
|
94
|
+
left: OperandNode | ColumnRef,
|
|
95
|
+
right: OperandNode | ColumnRef | string | number | boolean
|
|
96
96
|
): BinaryExpressionNode => createBinaryExpression('!=', left, right);
|
|
97
97
|
|
|
98
98
|
/**
|
|
@@ -101,13 +101,13 @@ export const neq = (
|
|
|
101
101
|
* @param right - Right operand
|
|
102
102
|
* @returns Binary expression node with greater-than operator
|
|
103
103
|
*/
|
|
104
|
-
export const gt = (left: OperandNode |
|
|
104
|
+
export const gt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
|
|
105
105
|
createBinaryExpression('>', left, right);
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Creates a greater than or equal expression (left >= right)
|
|
109
109
|
*/
|
|
110
|
-
export const gte = (left: OperandNode |
|
|
110
|
+
export const gte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
|
|
111
111
|
createBinaryExpression('>=', left, right);
|
|
112
112
|
|
|
113
113
|
/**
|
|
@@ -116,13 +116,13 @@ export const gte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDe
|
|
|
116
116
|
* @param right - Right operand
|
|
117
117
|
* @returns Binary expression node with less-than operator
|
|
118
118
|
*/
|
|
119
|
-
export const lt = (left: OperandNode |
|
|
119
|
+
export const lt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
|
|
120
120
|
createBinaryExpression('<', left, right);
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Creates a less than or equal expression (left <= right)
|
|
124
124
|
*/
|
|
125
|
-
export const lte = (left: OperandNode |
|
|
125
|
+
export const lte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
|
|
126
126
|
createBinaryExpression('<=', left, right);
|
|
127
127
|
|
|
128
128
|
/**
|
|
@@ -132,7 +132,7 @@ export const lte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDe
|
|
|
132
132
|
* @param escape - Optional escape character
|
|
133
133
|
* @returns Binary expression node with LIKE operator
|
|
134
134
|
*/
|
|
135
|
-
export const like = (left: OperandNode |
|
|
135
|
+
export const like = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
|
|
136
136
|
createBinaryExpression('LIKE', left, pattern, escape);
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -142,7 +142,7 @@ export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: st
|
|
|
142
142
|
* @param escape - Optional escape character
|
|
143
143
|
* @returns Binary expression node with NOT LIKE operator
|
|
144
144
|
*/
|
|
145
|
-
export const notLike = (left: OperandNode |
|
|
145
|
+
export const notLike = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
|
|
146
146
|
createBinaryExpression('NOT LIKE', left, pattern, escape);
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -172,7 +172,7 @@ export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
|
|
|
172
172
|
* @param left - Operand to check for null
|
|
173
173
|
* @returns Null expression node with IS NULL operator
|
|
174
174
|
*/
|
|
175
|
-
export const isNull = (left: OperandNode |
|
|
175
|
+
export const isNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
|
|
176
176
|
type: 'NullExpression',
|
|
177
177
|
left: toNode(left),
|
|
178
178
|
operator: 'IS NULL'
|
|
@@ -183,7 +183,7 @@ export const isNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
|
|
|
183
183
|
* @param left - Operand to check for non-null
|
|
184
184
|
* @returns Null expression node with IS NOT NULL operator
|
|
185
185
|
*/
|
|
186
|
-
export const isNotNull = (left: OperandNode |
|
|
186
|
+
export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
|
|
187
187
|
type: 'NullExpression',
|
|
188
188
|
left: toNode(left),
|
|
189
189
|
operator: 'IS NOT NULL'
|
|
@@ -191,7 +191,7 @@ export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode =>
|
|
|
191
191
|
|
|
192
192
|
const createInExpression = (
|
|
193
193
|
operator: 'IN' | 'NOT IN',
|
|
194
|
-
left: OperandNode |
|
|
194
|
+
left: OperandNode | ColumnRef,
|
|
195
195
|
values: (string | number | LiteralNode)[]
|
|
196
196
|
): InExpressionNode => ({
|
|
197
197
|
type: 'InExpression',
|
|
@@ -206,7 +206,7 @@ const createInExpression = (
|
|
|
206
206
|
* @param values - Values to check against
|
|
207
207
|
* @returns IN expression node
|
|
208
208
|
*/
|
|
209
|
-
export const inList = (left: OperandNode |
|
|
209
|
+
export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
210
210
|
createInExpression('IN', left, values);
|
|
211
211
|
|
|
212
212
|
/**
|
|
@@ -215,14 +215,14 @@ export const inList = (left: OperandNode | ColumnDef, values: (string | number |
|
|
|
215
215
|
* @param values - Values to check against
|
|
216
216
|
* @returns NOT IN expression node
|
|
217
217
|
*/
|
|
218
|
-
export const notInList = (left: OperandNode |
|
|
218
|
+
export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
|
|
219
219
|
createInExpression('NOT IN', left, values);
|
|
220
220
|
|
|
221
221
|
const createBetweenExpression = (
|
|
222
222
|
operator: 'BETWEEN' | 'NOT BETWEEN',
|
|
223
|
-
left: OperandNode |
|
|
224
|
-
lower: OperandNode |
|
|
225
|
-
upper: OperandNode |
|
|
223
|
+
left: OperandNode | ColumnRef,
|
|
224
|
+
lower: OperandNode | ColumnRef | string | number,
|
|
225
|
+
upper: OperandNode | ColumnRef | string | number
|
|
226
226
|
): BetweenExpressionNode => ({
|
|
227
227
|
type: 'BetweenExpression',
|
|
228
228
|
left: toNode(left),
|
|
@@ -239,9 +239,9 @@ const createBetweenExpression = (
|
|
|
239
239
|
* @returns BETWEEN expression node
|
|
240
240
|
*/
|
|
241
241
|
export const between = (
|
|
242
|
-
left: OperandNode |
|
|
243
|
-
lower: OperandNode |
|
|
244
|
-
upper: OperandNode |
|
|
242
|
+
left: OperandNode | ColumnRef,
|
|
243
|
+
lower: OperandNode | ColumnRef | string | number,
|
|
244
|
+
upper: OperandNode | ColumnRef | string | number
|
|
245
245
|
): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
|
|
246
246
|
|
|
247
247
|
/**
|
|
@@ -252,9 +252,9 @@ export const between = (
|
|
|
252
252
|
* @returns NOT BETWEEN expression node
|
|
253
253
|
*/
|
|
254
254
|
export const notBetween = (
|
|
255
|
-
left: OperandNode |
|
|
256
|
-
lower: OperandNode |
|
|
257
|
-
upper: OperandNode |
|
|
255
|
+
left: OperandNode | ColumnRef,
|
|
256
|
+
lower: OperandNode | ColumnRef | string | number,
|
|
257
|
+
upper: OperandNode | ColumnRef | string | number
|
|
258
258
|
): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
|
|
259
259
|
|
|
260
260
|
/**
|
|
@@ -263,7 +263,7 @@ export const notBetween = (
|
|
|
263
263
|
* @param path - JSON path expression
|
|
264
264
|
* @returns JSON path node
|
|
265
265
|
*/
|
|
266
|
-
export const jsonPath = (col:
|
|
266
|
+
export const jsonPath = (col: ColumnRef | ColumnNode, path: string): JsonPathNode => ({
|
|
267
267
|
type: 'JsonPath',
|
|
268
268
|
column: columnOperand(col),
|
|
269
269
|
path
|
|
@@ -276,8 +276,8 @@ export const jsonPath = (col: ColumnDef | ColumnNode, path: string): JsonPathNod
|
|
|
276
276
|
* @returns CASE expression node
|
|
277
277
|
*/
|
|
278
278
|
export const caseWhen = (
|
|
279
|
-
conditions: { when: ExpressionNode; then: OperandNode |
|
|
280
|
-
elseValue?: OperandNode |
|
|
279
|
+
conditions: { when: ExpressionNode; then: OperandNode | ColumnRef | string | number | boolean | null }[],
|
|
280
|
+
elseValue?: OperandNode | ColumnRef | string | number | boolean | null
|
|
281
281
|
): CaseExpressionNode => ({
|
|
282
282
|
type: 'CaseExpression',
|
|
283
283
|
conditions: conditions.map(c => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import type { SelectQueryNode, OrderByNode } from './query.js';
|
|
2
|
+
import { SqlOperator } from '../sql/sql.js';
|
|
3
|
+
import { ColumnRef } from './types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* AST node representing a literal value
|
|
@@ -27,15 +27,17 @@ export interface ColumnNode {
|
|
|
27
27
|
/**
|
|
28
28
|
* AST node representing a function call
|
|
29
29
|
*/
|
|
30
|
-
export interface FunctionNode {
|
|
31
|
-
type: 'Function';
|
|
32
|
-
/** Function name (e.g., COUNT, SUM) */
|
|
33
|
-
name: string;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
export interface FunctionNode {
|
|
31
|
+
type: 'Function';
|
|
32
|
+
/** Function name (e.g., COUNT, SUM) */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Optional canonical function key for dialect-aware rendering */
|
|
35
|
+
fn?: string;
|
|
36
|
+
/** Function arguments */
|
|
37
|
+
args: OperandNode[];
|
|
38
|
+
/** Optional alias for the function result */
|
|
39
|
+
alias?: string;
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* AST node representing a JSON path expression
|
|
@@ -118,10 +120,10 @@ export const isOperandNode = (node: any): node is OperandNode => node && operand
|
|
|
118
120
|
export const isFunctionNode = (node: any): node is FunctionNode => node?.type === 'Function';
|
|
119
121
|
export const isCaseExpressionNode = (node: any): node is CaseExpressionNode => node?.type === 'CaseExpression';
|
|
120
122
|
export const isWindowFunctionNode = (node: any): node is WindowFunctionNode => node?.type === 'WindowFunction';
|
|
121
|
-
export const isExpressionSelectionNode = (
|
|
122
|
-
node:
|
|
123
|
-
): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
|
|
124
|
-
isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
|
|
123
|
+
export const isExpressionSelectionNode = (
|
|
124
|
+
node: ColumnRef | FunctionNode | CaseExpressionNode | WindowFunctionNode
|
|
125
|
+
): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
|
|
126
|
+
isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
|
|
125
127
|
|
|
126
128
|
/**
|
|
127
129
|
* AST node representing a binary expression (e.g., column = value)
|
|
@@ -19,81 +19,134 @@ import {
|
|
|
19
19
|
/**
|
|
20
20
|
* Visitor for expression nodes
|
|
21
21
|
*/
|
|
22
|
-
export interface ExpressionVisitor<R> {
|
|
23
|
-
visitBinaryExpression(node: BinaryExpressionNode): R;
|
|
24
|
-
visitLogicalExpression(node: LogicalExpressionNode): R;
|
|
25
|
-
visitNullExpression(node: NullExpressionNode): R;
|
|
26
|
-
visitInExpression(node: InExpressionNode): R;
|
|
27
|
-
visitExistsExpression(node: ExistsExpressionNode): R;
|
|
28
|
-
visitBetweenExpression(node: BetweenExpressionNode): R;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
22
|
+
export interface ExpressionVisitor<R> {
|
|
23
|
+
visitBinaryExpression?(node: BinaryExpressionNode): R;
|
|
24
|
+
visitLogicalExpression?(node: LogicalExpressionNode): R;
|
|
25
|
+
visitNullExpression?(node: NullExpressionNode): R;
|
|
26
|
+
visitInExpression?(node: InExpressionNode): R;
|
|
27
|
+
visitExistsExpression?(node: ExistsExpressionNode): R;
|
|
28
|
+
visitBetweenExpression?(node: BetweenExpressionNode): R;
|
|
29
|
+
otherwise?(node: ExpressionNode): R;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Visitor for operand nodes
|
|
34
|
+
*/
|
|
35
|
+
export interface OperandVisitor<R> {
|
|
36
|
+
visitColumn?(node: ColumnNode): R;
|
|
37
|
+
visitLiteral?(node: LiteralNode): R;
|
|
38
|
+
visitFunction?(node: FunctionNode): R;
|
|
39
|
+
visitJsonPath?(node: JsonPathNode): R;
|
|
40
|
+
visitScalarSubquery?(node: ScalarSubqueryNode): R;
|
|
41
|
+
visitCaseExpression?(node: CaseExpressionNode): R;
|
|
42
|
+
visitWindowFunction?(node: WindowFunctionNode): R;
|
|
43
|
+
otherwise?(node: OperandNode): R;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type ExpressionDispatch = <R>(node: any, visitor: ExpressionVisitor<R>) => R;
|
|
47
|
+
type OperandDispatch = <R>(node: any, visitor: OperandVisitor<R>) => R;
|
|
48
|
+
|
|
49
|
+
const expressionDispatchers = new Map<string, ExpressionDispatch>();
|
|
50
|
+
const operandDispatchers = new Map<string, OperandDispatch>();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Registers a dispatcher for a custom expression node type.
|
|
54
|
+
* Allows new node kinds without modifying the core switch.
|
|
55
|
+
*/
|
|
56
|
+
export const registerExpressionDispatcher = (type: string, dispatcher: ExpressionDispatch): void => {
|
|
57
|
+
expressionDispatchers.set(type, dispatcher);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Registers a dispatcher for a custom operand node type.
|
|
62
|
+
* Allows new node kinds without modifying the core switch.
|
|
63
|
+
*/
|
|
64
|
+
export const registerOperandDispatcher = (type: string, dispatcher: OperandDispatch): void => {
|
|
65
|
+
operandDispatchers.set(type, dispatcher);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clears all registered dispatchers. Primarily for tests.
|
|
70
|
+
*/
|
|
71
|
+
export const clearExpressionDispatchers = (): void => expressionDispatchers.clear();
|
|
72
|
+
export const clearOperandDispatchers = (): void => operandDispatchers.clear();
|
|
73
|
+
|
|
74
|
+
const unsupportedExpression = (node: ExpressionNode): never => {
|
|
75
|
+
throw new Error(`Unsupported expression type "${(node as any)?.type ?? 'unknown'}"`);
|
|
76
|
+
};
|
|
47
77
|
|
|
48
78
|
const unsupportedOperand = (node: OperandNode): never => {
|
|
49
79
|
throw new Error(`Unsupported operand type "${(node as any)?.type ?? 'unknown'}"`);
|
|
50
80
|
};
|
|
51
81
|
/**
|
|
52
82
|
* Dispatches an expression node to the visitor
|
|
53
|
-
* @param node - Expression node to visit
|
|
54
|
-
* @param visitor - Visitor implementation
|
|
55
|
-
*/
|
|
56
|
-
export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
case '
|
|
65
|
-
return visitor.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
* @param node - Expression node to visit
|
|
84
|
+
* @param visitor - Visitor implementation
|
|
85
|
+
*/
|
|
86
|
+
export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
|
|
87
|
+
const dynamic = expressionDispatchers.get((node as any)?.type);
|
|
88
|
+
if (dynamic) return dynamic(node as any, visitor);
|
|
89
|
+
|
|
90
|
+
switch (node.type) {
|
|
91
|
+
case 'BinaryExpression':
|
|
92
|
+
if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node);
|
|
93
|
+
break;
|
|
94
|
+
case 'LogicalExpression':
|
|
95
|
+
if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node);
|
|
96
|
+
break;
|
|
97
|
+
case 'NullExpression':
|
|
98
|
+
if (visitor.visitNullExpression) return visitor.visitNullExpression(node);
|
|
99
|
+
break;
|
|
100
|
+
case 'InExpression':
|
|
101
|
+
if (visitor.visitInExpression) return visitor.visitInExpression(node);
|
|
102
|
+
break;
|
|
103
|
+
case 'ExistsExpression':
|
|
104
|
+
if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node);
|
|
105
|
+
break;
|
|
106
|
+
case 'BetweenExpression':
|
|
107
|
+
if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node);
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (visitor.otherwise) return visitor.otherwise(node);
|
|
113
|
+
return unsupportedExpression(node);
|
|
114
|
+
};
|
|
74
115
|
|
|
75
116
|
/**
|
|
76
117
|
* Dispatches an operand node to the visitor
|
|
77
|
-
* @param node - Operand node to visit
|
|
78
|
-
* @param visitor - Visitor implementation
|
|
79
|
-
*/
|
|
80
|
-
export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
case '
|
|
89
|
-
return visitor.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
case '
|
|
95
|
-
return visitor.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
* @param node - Operand node to visit
|
|
119
|
+
* @param visitor - Visitor implementation
|
|
120
|
+
*/
|
|
121
|
+
export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
|
|
122
|
+
const dynamic = operandDispatchers.get((node as any)?.type);
|
|
123
|
+
if (dynamic) return dynamic(node as any, visitor);
|
|
124
|
+
|
|
125
|
+
switch (node.type) {
|
|
126
|
+
case 'Column':
|
|
127
|
+
if (visitor.visitColumn) return visitor.visitColumn(node);
|
|
128
|
+
break;
|
|
129
|
+
case 'Literal':
|
|
130
|
+
if (visitor.visitLiteral) return visitor.visitLiteral(node);
|
|
131
|
+
break;
|
|
132
|
+
case 'Function':
|
|
133
|
+
if (visitor.visitFunction) return visitor.visitFunction(node);
|
|
134
|
+
break;
|
|
135
|
+
case 'JsonPath':
|
|
136
|
+
if (visitor.visitJsonPath) return visitor.visitJsonPath(node);
|
|
137
|
+
break;
|
|
138
|
+
case 'ScalarSubquery':
|
|
139
|
+
if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node);
|
|
140
|
+
break;
|
|
141
|
+
case 'CaseExpression':
|
|
142
|
+
if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node);
|
|
143
|
+
break;
|
|
144
|
+
case 'WindowFunction':
|
|
145
|
+
if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node);
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (visitor.otherwise) return visitor.otherwise(node);
|
|
151
|
+
return unsupportedOperand(node);
|
|
152
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './expression-nodes.js';
|
|
2
|
-
export * from './expression-builders.js';
|
|
3
|
-
export * from './window-functions.js';
|
|
4
|
-
export * from './aggregate-functions.js';
|
|
5
|
-
export * from './expression-visitor.js';
|
|
2
|
+
export * from './expression-builders.js';
|
|
3
|
+
export * from './window-functions.js';
|
|
4
|
+
export * from './aggregate-functions.js';
|
|
5
|
+
export * from './expression-visitor.js';
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export * from './adapters.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { JoinNode } from './join.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata stored on JoinNode.meta for higher-level concerns.
|
|
5
|
+
*/
|
|
6
|
+
export interface JoinMetadata {
|
|
7
|
+
relationName?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the relation name from join metadata if present.
|
|
13
|
+
*/
|
|
14
|
+
export const getJoinRelationName = (join: JoinNode): string | undefined =>
|
|
15
|
+
(join.meta as JoinMetadata | undefined)?.relationName;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { JoinNode } from './join.js';
|
|
2
|
-
import { ExpressionNode } from './expression.js';
|
|
3
|
-
import { JoinKind } from '../sql/sql.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import { JoinNode } from './join.js';
|
|
2
|
+
import { ExpressionNode } from './expression.js';
|
|
3
|
+
import { JoinKind } from '../sql/sql.js';
|
|
4
|
+
import { JoinMetadata } from './join-metadata.js';
|
|
5
|
+
import { TableNode, FunctionTableNode } from './query.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a JoinNode ready for AST insertion.
|
|
9
|
+
* Centralizing this avoids copy/pasted object literals when multiple services need to synthesize joins.
|
|
10
|
+
*/
|
|
11
|
+
export const createJoinNode = (
|
|
12
|
+
kind: JoinKind,
|
|
13
|
+
tableName: string | TableNode | FunctionTableNode,
|
|
14
|
+
condition: ExpressionNode,
|
|
15
|
+
relationName?: string
|
|
16
|
+
): JoinNode => ({
|
|
17
|
+
type: 'Join',
|
|
18
|
+
kind,
|
|
19
|
+
table: typeof tableName === 'string' ? { type: 'Table', name: tableName } as TableNode : (tableName as TableNode | FunctionTableNode),
|
|
20
|
+
condition,
|
|
21
|
+
meta: relationName ? ({ relationName } as JoinMetadata) : undefined
|
|
22
|
+
});
|