metal-orm 1.1.11 → 1.1.16
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 +758 -769
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +100 -96
- package/scripts/generate-entities/schema.mjs +122 -122
- package/scripts/generate-entities/tree-detection.mjs +92 -92
- package/scripts/inflection/pt-br.mjs +468 -468
- package/scripts/run-eslint.mjs +33 -34
- package/scripts/show-sql-seed.sql +102 -0
- package/scripts/show-sql.mjs +321 -325
- package/src/codegen/naming-strategy.ts +54 -54
- package/src/codegen/typescript.ts +482 -482
- package/src/core/ast/adapters.ts +1 -1
- package/src/core/ast/builders.ts +123 -123
- package/src/core/ast/expression-builders.ts +745 -745
- package/src/core/ast/expression-nodes.ts +304 -304
- package/src/core/ast/expression-visitor.ts +212 -212
- package/src/core/ast/expression.ts +20 -20
- package/src/core/ast/helpers.ts +14 -14
- package/src/core/ast/join.ts +18 -18
- package/src/core/ast/query.ts +200 -200
- package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/render-reference.test.ts +70 -70
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
- package/src/core/ddl/introspect/catalogs/index.ts +5 -5
- package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
- package/src/core/ddl/introspect/context.ts +15 -15
- package/src/core/ddl/introspect/functions/postgres.ts +35 -35
- package/src/core/ddl/introspect/registry.ts +40 -40
- package/src/core/ddl/introspect/run-select.ts +35 -35
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/ddl/naming-strategy.ts +16 -16
- package/src/core/ddl/schema-dialect.ts +56 -56
- package/src/core/ddl/schema-diff.ts +234 -234
- package/src/core/ddl/schema-generator.ts +1 -1
- package/src/core/ddl/schema-introspect.ts +25 -25
- package/src/core/ddl/sql-writing.ts +171 -171
- package/src/core/dialect/abstract.ts +541 -541
- package/src/core/dialect/base/cte-compiler.ts +33 -33
- package/src/core/dialect/base/function-table-formatter.ts +103 -103
- package/src/core/dialect/base/groupby-compiler.ts +21 -21
- package/src/core/dialect/base/join-compiler.ts +25 -25
- package/src/core/dialect/base/orderby-compiler.ts +35 -35
- package/src/core/dialect/base/pagination-strategy.ts +32 -32
- package/src/core/dialect/base/returning-strategy.ts +57 -57
- package/src/core/dialect/base/sql-dialect.ts +294 -294
- package/src/core/dialect/dialect-factory.ts +91 -91
- package/src/core/dialect/mssql/functions.ts +120 -120
- package/src/core/dialect/mssql/index.ts +317 -317
- package/src/core/dialect/mysql/functions.ts +103 -103
- package/src/core/dialect/mysql/index.ts +105 -105
- package/src/core/dialect/postgres/functions.ts +108 -108
- package/src/core/dialect/postgres/index.ts +91 -91
- package/src/core/dialect/sqlite/functions.ts +129 -129
- package/src/core/dialect/sqlite/index.ts +67 -67
- package/src/core/driver/database-driver.ts +19 -19
- package/src/core/driver/mssql-driver.ts +23 -23
- package/src/core/driver/mysql-driver.ts +23 -23
- package/src/core/driver/postgres-driver.ts +23 -23
- package/src/core/driver/sqlite-driver.ts +23 -23
- package/src/core/execution/db-executor.ts +92 -92
- package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
- package/src/core/execution/executors/mssql-executor.ts +184 -184
- package/src/core/execution/executors/mysql-executor.ts +88 -88
- package/src/core/execution/executors/postgres-executor.ts +26 -26
- package/src/core/execution/executors/sqlite-executor.ts +42 -42
- package/src/core/execution/pooling/pool-types.ts +30 -30
- package/src/core/execution/pooling/pool.ts +275 -275
- package/src/core/functions/array.ts +35 -35
- package/src/core/functions/control-flow.ts +79 -79
- package/src/core/functions/datetime.ts +237 -237
- package/src/core/functions/json.ts +70 -70
- package/src/core/functions/numeric.ts +297 -297
- package/src/core/functions/text.ts +334 -334
- package/src/core/functions/types.ts +33 -33
- package/src/core/hydration/types.ts +51 -51
- package/src/core/sql/sql-operator-config.ts +39 -39
- package/src/core/sql/sql.ts +152 -152
- package/src/decorators/column-decorator.ts +87 -87
- package/src/decorators/decorator-metadata.ts +143 -143
- package/src/decorators/entity.ts +93 -93
- package/src/decorators/index.ts +39 -39
- package/src/decorators/relations.ts +231 -231
- package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
- package/src/decorators/transformers/transformer-decorators.ts +134 -134
- package/src/decorators/transformers/transformer-executor.ts +171 -171
- package/src/decorators/transformers/transformer-metadata.ts +73 -73
- package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
- package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
- package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
- package/src/decorators/validators/country-validator-registry.ts +64 -64
- package/src/decorators/validators/country-validators-decorators.ts +203 -203
- package/src/decorators/validators/country-validators.ts +105 -105
- package/src/decorators/validators/validator-metadata.ts +59 -59
- package/src/dto/dto-types.ts +226 -226
- package/src/dto/filter-types.ts +141 -141
- package/src/dto/index.ts +95 -95
- package/src/dto/openapi/generators/base.ts +29 -29
- package/src/dto/openapi/generators/column.ts +37 -37
- package/src/dto/openapi/generators/dto.ts +91 -91
- package/src/dto/openapi/generators/filter.ts +75 -75
- package/src/dto/openapi/generators/nested-dto.ts +618 -618
- package/src/dto/openapi/generators/pagination.ts +111 -111
- package/src/dto/openapi/generators/relation-filter.ts +228 -228
- package/src/dto/openapi/index.ts +12 -12
- package/src/dto/openapi/types.ts +101 -101
- package/src/dto/openapi/utilities.ts +95 -95
- package/src/dto/pagination-utils.ts +150 -150
- package/src/dto/transform.ts +197 -197
- package/src/index.ts +86 -86
- package/src/orm/als.ts +58 -58
- package/src/orm/domain-event-bus.ts +119 -119
- package/src/orm/entity-context.ts +86 -86
- package/src/orm/entity-materializer.ts +159 -159
- package/src/orm/entity-meta.ts +97 -97
- package/src/orm/entity-registry.ts +39 -39
- package/src/orm/entity-relation-cache.ts +39 -39
- package/src/orm/execute.ts +194 -194
- package/src/orm/execution-context.ts +18 -18
- package/src/orm/hydration-context.ts +26 -26
- package/src/orm/hydration.ts +112 -112
- package/src/orm/identity-map.ts +61 -61
- package/src/orm/interceptor-pipeline.ts +16 -16
- package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
- package/src/orm/lazy-batch/belongs-to.ts +108 -108
- package/src/orm/lazy-batch/has-many.ts +69 -69
- package/src/orm/lazy-batch/has-one.ts +68 -68
- package/src/orm/lazy-batch/shared.ts +125 -125
- package/src/orm/orm-session.ts +620 -620
- package/src/orm/orm.ts +34 -34
- package/src/orm/pooled-executor-factory.ts +121 -121
- package/src/orm/query-logger.ts +40 -40
- package/src/orm/relation-change-processor.ts +420 -420
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -156
- package/src/orm/relations/morph-one.ts +151 -151
- package/src/orm/relations/morph-to.ts +162 -162
- package/src/orm/runtime-types.ts +92 -92
- package/src/orm/save-graph.ts +561 -561
- package/src/orm/transaction-runner.ts +24 -24
- package/src/orm/unit-of-work.ts +426 -426
- package/src/query/index.ts +69 -69
- package/src/query/target.ts +46 -46
- package/src/query-builder/column-selector.ts +81 -81
- package/src/query-builder/delete.ts +141 -141
- package/src/query-builder/insert-query-state.ts +149 -149
- package/src/query-builder/insert.ts +117 -117
- package/src/query-builder/query-ast-service.ts +287 -287
- package/src/query-builder/query-resolution.ts +78 -78
- package/src/query-builder/raw-column-parser.ts +38 -38
- package/src/query-builder/relation-filter-utils.ts +153 -153
- package/src/query-builder/relation-join-planner.ts +10 -10
- package/src/query-builder/relation-manager.ts +87 -87
- package/src/query-builder/relation-projection-helper.ts +102 -102
- package/src/query-builder/relation-types.ts +9 -9
- package/src/query-builder/relation-utils.ts +15 -15
- package/src/query-builder/select/cte-facet.ts +40 -40
- package/src/query-builder/select/from-facet.ts +80 -80
- package/src/query-builder/select/join-facet.ts +62 -62
- package/src/query-builder/select/predicate-facet.ts +104 -104
- package/src/query-builder/select/projection-facet.ts +70 -70
- package/src/query-builder/select/relation-facet.ts +81 -81
- package/src/query-builder/select/setop-facet.ts +36 -36
- package/src/query-builder/select-helpers.ts +1 -1
- package/src/query-builder/select-query-builder-deps.ts +130 -130
- package/src/query-builder/select-query-state.ts +207 -207
- package/src/query-builder/update.ts +151 -151
- package/src/schema/column-types.ts +322 -322
- package/src/schema/table-guards.ts +37 -37
- package/src/schema/table.ts +281 -281
- package/src/tree/index.ts +13 -13
- package/src/tree/nested-set-strategy.ts +545 -545
- package/src/tree/tree-decorator.ts +397 -397
- package/src/tree/tree-manager.ts +754 -754
- package/src/tree/tree-query.ts +427 -427
- package/src/tree/tree-types.ts +299 -299
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
import { OrderingTerm, SelectQueryNode } from '../core/ast/query.js';
|
|
2
|
-
import {
|
|
3
|
-
ExpressionNode,
|
|
4
|
-
OperandNode,
|
|
1
|
+
import { OrderingTerm, SelectQueryNode } from '../core/ast/query.js';
|
|
2
|
+
import {
|
|
3
|
+
ExpressionNode,
|
|
4
|
+
OperandNode,
|
|
5
5
|
BinaryExpressionNode,
|
|
6
6
|
LogicalExpressionNode,
|
|
7
7
|
NotExpressionNode,
|
|
8
8
|
InExpressionNode,
|
|
9
|
-
NullExpressionNode,
|
|
10
|
-
JsonPathNode,
|
|
11
|
-
ExistsExpressionNode,
|
|
12
|
-
BetweenExpressionNode,
|
|
13
|
-
ScalarSubqueryNode,
|
|
14
|
-
CaseExpressionNode,
|
|
15
|
-
WindowFunctionNode,
|
|
16
|
-
ArithmeticExpressionNode,
|
|
17
|
-
ColumnNode,
|
|
18
|
-
LiteralNode,
|
|
19
|
-
FunctionNode,
|
|
20
|
-
AliasRefNode,
|
|
21
|
-
CastExpressionNode,
|
|
22
|
-
CollateExpressionNode,
|
|
23
|
-
ExpressionVisitor,
|
|
24
|
-
OperandVisitor,
|
|
25
|
-
visitExpression,
|
|
26
|
-
visitOperand
|
|
27
|
-
} from '../core/ast/expression.js';
|
|
28
|
-
import { SQL_OPERATOR_REGISTRY } from '../core/sql/sql-operator-config.js';
|
|
29
|
-
import { SqlOperator } from '../core/sql/sql.js';
|
|
30
|
-
import { isRelationAlias } from '../query-builder/relation-alias.js';
|
|
31
|
-
import { HydrationMetadata } from '../core/hydration/types.js';
|
|
32
|
-
import { getJoinRelationName } from '../core/ast/join-metadata.js';
|
|
33
|
-
import { NamingStrategy, DefaultNamingStrategy } from './naming-strategy.js';
|
|
34
|
-
|
|
35
|
-
const assertNever = (value: never): never => {
|
|
36
|
-
throw new Error(`Unhandled SQL operator: ${value}`);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Generates TypeScript code from query AST nodes
|
|
41
|
-
*/
|
|
42
|
-
type SelectionColumn =
|
|
43
|
-
| ColumnNode
|
|
44
|
-
| FunctionNode
|
|
45
|
-
| ScalarSubqueryNode
|
|
46
|
-
| CaseExpressionNode
|
|
47
|
-
| WindowFunctionNode
|
|
48
|
-
| CastExpressionNode;
|
|
49
|
-
|
|
50
|
-
export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVisitor<string> {
|
|
51
|
-
constructor(private namingStrategy: NamingStrategy = new DefaultNamingStrategy()) { }
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Generates TypeScript code from a query AST
|
|
55
|
-
* @param ast - Query AST to generate code from
|
|
56
|
-
* @returns Generated TypeScript code
|
|
57
|
-
*/
|
|
58
|
-
generate(ast: SelectQueryNode): string {
|
|
59
|
-
const chainLines = this.buildSelectLines(ast);
|
|
60
|
-
const lines = chainLines.map((line, index) => (index === 0 ? `const query = ${line}` : line));
|
|
61
|
-
lines.push(';', '', 'await query.execute();');
|
|
62
|
-
return lines.join('\n');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Builds TypeScript method chain lines from query AST
|
|
67
|
-
* @param ast - Query AST
|
|
68
|
-
* @returns Array of TypeScript method chain lines
|
|
69
|
-
*/
|
|
70
|
-
private buildSelectLines(ast: SelectQueryNode): string[] {
|
|
71
|
-
const lines: string[] = [];
|
|
72
|
-
const hydration = (ast.meta as HydrationMetadata | undefined)?.hydration;
|
|
73
|
-
const hydratedRelations = new Set(hydration?.relations?.map(r => r.name) ?? []);
|
|
74
|
-
|
|
75
|
-
const selections = ast.columns
|
|
76
|
-
.filter((col): col is SelectionColumn => !(hydration && isRelationAlias(col.alias)))
|
|
77
|
-
.map((col, index) => `${this.getSelectionKey(col, index)}: ${this.printOperand(col)}`);
|
|
78
|
-
|
|
79
|
-
lines.push(`db.select({`);
|
|
80
|
-
selections.forEach((sel, index) => {
|
|
81
|
-
lines.push(` ${sel}${index < selections.length - 1 ? ',' : ''}`);
|
|
82
|
-
});
|
|
83
|
-
lines.push(`})`);
|
|
84
|
-
lines.push(`.from(${this.namingStrategy.tableToSymbol(ast.from)})`);
|
|
85
|
-
|
|
86
|
-
if (ast.distinct && ast.distinct.length) {
|
|
87
|
-
const cols = ast.distinct.map(c => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(', ');
|
|
88
|
-
lines.push(`.distinct(${cols})`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
ast.joins.forEach(join => {
|
|
92
|
-
const relationName = getJoinRelationName(join);
|
|
93
|
-
if (relationName && hydratedRelations.has(relationName)) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (relationName) {
|
|
98
|
-
if (join.kind === 'INNER') {
|
|
99
|
-
lines.push(`.joinRelation('${relationName}')`);
|
|
100
|
-
} else {
|
|
101
|
-
lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
const table = this.namingStrategy.tableToSymbol(join.table);
|
|
105
|
-
const cond = this.printExpression(join.condition);
|
|
106
|
-
let method = 'innerJoin';
|
|
107
|
-
if (join.kind === 'LEFT') method = 'leftJoin';
|
|
108
|
-
if (join.kind === 'RIGHT') method = 'rightJoin';
|
|
109
|
-
lines.push(`.${method}(${table}, ${cond})`);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (hydration?.relations?.length) {
|
|
114
|
-
hydration.relations.forEach(rel => {
|
|
115
|
-
const options: string[] = [];
|
|
116
|
-
if (rel.columns.length) options.push(`columns: [${rel.columns.map(c => `'${c}'`).join(', ')}]`);
|
|
117
|
-
if (rel.aliasPrefix !== rel.name) options.push(`aliasPrefix: '${rel.aliasPrefix}'`);
|
|
118
|
-
const opts = options.length ? `, { ${options.join(', ')} }` : '';
|
|
119
|
-
lines.push(`.include('${rel.name}'${opts})`);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (ast.where) {
|
|
124
|
-
lines.push(`.where(${this.printExpression(ast.where)})`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (ast.groupBy && ast.groupBy.length) {
|
|
128
|
-
const cols = ast.groupBy.map(term => this.printOrderingTerm(term)).join(', ');
|
|
129
|
-
lines.push(`.groupBy(${cols})`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (ast.having) {
|
|
133
|
-
lines.push(`.having(${this.printExpression(ast.having)})`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (ast.orderBy && ast.orderBy.length) {
|
|
137
|
-
ast.orderBy.forEach(o => {
|
|
138
|
-
const term = this.printOrderingTerm(o.term);
|
|
139
|
-
const opts: string[] = [`direction: '${o.direction}'`];
|
|
140
|
-
if (o.nulls) opts.push(`nulls: '${o.nulls}'`);
|
|
141
|
-
if (o.collation) opts.push(`collation: '${o.collation}'`);
|
|
142
|
-
const hasOpts = opts.length > 1;
|
|
143
|
-
if (hasOpts) {
|
|
144
|
-
lines.push(`.orderBy(${term}, { ${opts.join(', ')} })`);
|
|
145
|
-
} else {
|
|
146
|
-
lines.push(`.orderBy(${term}, '${o.direction}')`);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (ast.limit) lines.push(`.limit(${ast.limit})`);
|
|
152
|
-
if (ast.offset) lines.push(`.offset(${ast.offset})`);
|
|
153
|
-
|
|
154
|
-
return lines;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Prints an expression node to TypeScript code
|
|
159
|
-
* @param expr - Expression node to print
|
|
160
|
-
* @returns TypeScript code representation
|
|
161
|
-
*/
|
|
162
|
-
private printExpression(expr: ExpressionNode): string {
|
|
163
|
-
return visitExpression(expr, this);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Prints an operand node to TypeScript code
|
|
168
|
-
* @param node - Operand node to print
|
|
169
|
-
* @returns TypeScript code representation
|
|
170
|
-
*/
|
|
171
|
-
private printOperand(node: OperandNode): string {
|
|
172
|
-
return visitOperand(node, this);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Prints an ordering term (operand/expression/alias) to TypeScript code.
|
|
177
|
-
*/
|
|
178
|
-
private printOrderingTerm(term: OrderingTerm): string {
|
|
179
|
-
if (!term || !('type' in term)) {
|
|
180
|
-
throw new Error('Unsupported ordering term');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
switch (term.type) {
|
|
184
|
-
case 'Column':
|
|
185
|
-
return `${this.namingStrategy.tableToSymbol(term.table)}.${term.name}`;
|
|
186
|
-
case 'AliasRef':
|
|
187
|
-
return this.visitAliasRef(term);
|
|
188
|
-
case 'Literal':
|
|
189
|
-
case 'Function':
|
|
190
|
-
case 'JsonPath':
|
|
191
|
-
case 'ScalarSubquery':
|
|
192
|
-
case 'CaseExpression':
|
|
193
|
-
case 'WindowFunction':
|
|
194
|
-
case 'Cast':
|
|
195
|
-
case 'Collate':
|
|
196
|
-
return this.printOperand(term);
|
|
197
|
-
default:
|
|
198
|
-
return this.printExpression(term);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private getSelectionKey(selection: SelectionColumn, index: number): string {
|
|
203
|
-
if (selection.alias) {
|
|
204
|
-
return selection.alias;
|
|
205
|
-
}
|
|
206
|
-
if (this.isNamedSelection(selection)) {
|
|
207
|
-
return selection.name;
|
|
208
|
-
}
|
|
209
|
-
return `selection${index + 1}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private isNamedSelection(selection: SelectionColumn): selection is ColumnNode | FunctionNode | WindowFunctionNode {
|
|
213
|
-
return 'name' in selection;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
public visitBinaryExpression(binary: BinaryExpressionNode): string {
|
|
217
|
-
return this.printBinaryExpression(binary);
|
|
218
|
-
}
|
|
219
|
-
|
|
9
|
+
NullExpressionNode,
|
|
10
|
+
JsonPathNode,
|
|
11
|
+
ExistsExpressionNode,
|
|
12
|
+
BetweenExpressionNode,
|
|
13
|
+
ScalarSubqueryNode,
|
|
14
|
+
CaseExpressionNode,
|
|
15
|
+
WindowFunctionNode,
|
|
16
|
+
ArithmeticExpressionNode,
|
|
17
|
+
ColumnNode,
|
|
18
|
+
LiteralNode,
|
|
19
|
+
FunctionNode,
|
|
20
|
+
AliasRefNode,
|
|
21
|
+
CastExpressionNode,
|
|
22
|
+
CollateExpressionNode,
|
|
23
|
+
ExpressionVisitor,
|
|
24
|
+
OperandVisitor,
|
|
25
|
+
visitExpression,
|
|
26
|
+
visitOperand
|
|
27
|
+
} from '../core/ast/expression.js';
|
|
28
|
+
import { SQL_OPERATOR_REGISTRY } from '../core/sql/sql-operator-config.js';
|
|
29
|
+
import { SqlOperator } from '../core/sql/sql.js';
|
|
30
|
+
import { isRelationAlias } from '../query-builder/relation-alias.js';
|
|
31
|
+
import { HydrationMetadata } from '../core/hydration/types.js';
|
|
32
|
+
import { getJoinRelationName } from '../core/ast/join-metadata.js';
|
|
33
|
+
import { NamingStrategy, DefaultNamingStrategy } from './naming-strategy.js';
|
|
34
|
+
|
|
35
|
+
const assertNever = (value: never): never => {
|
|
36
|
+
throw new Error(`Unhandled SQL operator: ${value}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generates TypeScript code from query AST nodes
|
|
41
|
+
*/
|
|
42
|
+
type SelectionColumn =
|
|
43
|
+
| ColumnNode
|
|
44
|
+
| FunctionNode
|
|
45
|
+
| ScalarSubqueryNode
|
|
46
|
+
| CaseExpressionNode
|
|
47
|
+
| WindowFunctionNode
|
|
48
|
+
| CastExpressionNode;
|
|
49
|
+
|
|
50
|
+
export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVisitor<string> {
|
|
51
|
+
constructor(private namingStrategy: NamingStrategy = new DefaultNamingStrategy()) { }
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generates TypeScript code from a query AST
|
|
55
|
+
* @param ast - Query AST to generate code from
|
|
56
|
+
* @returns Generated TypeScript code
|
|
57
|
+
*/
|
|
58
|
+
generate(ast: SelectQueryNode): string {
|
|
59
|
+
const chainLines = this.buildSelectLines(ast);
|
|
60
|
+
const lines = chainLines.map((line, index) => (index === 0 ? `const query = ${line}` : line));
|
|
61
|
+
lines.push(';', '', 'await query.execute();');
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Builds TypeScript method chain lines from query AST
|
|
67
|
+
* @param ast - Query AST
|
|
68
|
+
* @returns Array of TypeScript method chain lines
|
|
69
|
+
*/
|
|
70
|
+
private buildSelectLines(ast: SelectQueryNode): string[] {
|
|
71
|
+
const lines: string[] = [];
|
|
72
|
+
const hydration = (ast.meta as HydrationMetadata | undefined)?.hydration;
|
|
73
|
+
const hydratedRelations = new Set(hydration?.relations?.map(r => r.name) ?? []);
|
|
74
|
+
|
|
75
|
+
const selections = ast.columns
|
|
76
|
+
.filter((col): col is SelectionColumn => !(hydration && isRelationAlias(col.alias)))
|
|
77
|
+
.map((col, index) => `${this.getSelectionKey(col, index)}: ${this.printOperand(col)}`);
|
|
78
|
+
|
|
79
|
+
lines.push(`db.select({`);
|
|
80
|
+
selections.forEach((sel, index) => {
|
|
81
|
+
lines.push(` ${sel}${index < selections.length - 1 ? ',' : ''}`);
|
|
82
|
+
});
|
|
83
|
+
lines.push(`})`);
|
|
84
|
+
lines.push(`.from(${this.namingStrategy.tableToSymbol(ast.from)})`);
|
|
85
|
+
|
|
86
|
+
if (ast.distinct && ast.distinct.length) {
|
|
87
|
+
const cols = ast.distinct.map(c => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(', ');
|
|
88
|
+
lines.push(`.distinct(${cols})`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ast.joins.forEach(join => {
|
|
92
|
+
const relationName = getJoinRelationName(join);
|
|
93
|
+
if (relationName && hydratedRelations.has(relationName)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (relationName) {
|
|
98
|
+
if (join.kind === 'INNER') {
|
|
99
|
+
lines.push(`.joinRelation('${relationName}')`);
|
|
100
|
+
} else {
|
|
101
|
+
lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const table = this.namingStrategy.tableToSymbol(join.table);
|
|
105
|
+
const cond = this.printExpression(join.condition);
|
|
106
|
+
let method = 'innerJoin';
|
|
107
|
+
if (join.kind === 'LEFT') method = 'leftJoin';
|
|
108
|
+
if (join.kind === 'RIGHT') method = 'rightJoin';
|
|
109
|
+
lines.push(`.${method}(${table}, ${cond})`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (hydration?.relations?.length) {
|
|
114
|
+
hydration.relations.forEach(rel => {
|
|
115
|
+
const options: string[] = [];
|
|
116
|
+
if (rel.columns.length) options.push(`columns: [${rel.columns.map(c => `'${c}'`).join(', ')}]`);
|
|
117
|
+
if (rel.aliasPrefix !== rel.name) options.push(`aliasPrefix: '${rel.aliasPrefix}'`);
|
|
118
|
+
const opts = options.length ? `, { ${options.join(', ')} }` : '';
|
|
119
|
+
lines.push(`.include('${rel.name}'${opts})`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (ast.where) {
|
|
124
|
+
lines.push(`.where(${this.printExpression(ast.where)})`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (ast.groupBy && ast.groupBy.length) {
|
|
128
|
+
const cols = ast.groupBy.map(term => this.printOrderingTerm(term)).join(', ');
|
|
129
|
+
lines.push(`.groupBy(${cols})`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (ast.having) {
|
|
133
|
+
lines.push(`.having(${this.printExpression(ast.having)})`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (ast.orderBy && ast.orderBy.length) {
|
|
137
|
+
ast.orderBy.forEach(o => {
|
|
138
|
+
const term = this.printOrderingTerm(o.term);
|
|
139
|
+
const opts: string[] = [`direction: '${o.direction}'`];
|
|
140
|
+
if (o.nulls) opts.push(`nulls: '${o.nulls}'`);
|
|
141
|
+
if (o.collation) opts.push(`collation: '${o.collation}'`);
|
|
142
|
+
const hasOpts = opts.length > 1;
|
|
143
|
+
if (hasOpts) {
|
|
144
|
+
lines.push(`.orderBy(${term}, { ${opts.join(', ')} })`);
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(`.orderBy(${term}, '${o.direction}')`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (ast.limit) lines.push(`.limit(${ast.limit})`);
|
|
152
|
+
if (ast.offset) lines.push(`.offset(${ast.offset})`);
|
|
153
|
+
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Prints an expression node to TypeScript code
|
|
159
|
+
* @param expr - Expression node to print
|
|
160
|
+
* @returns TypeScript code representation
|
|
161
|
+
*/
|
|
162
|
+
private printExpression(expr: ExpressionNode): string {
|
|
163
|
+
return visitExpression(expr, this);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Prints an operand node to TypeScript code
|
|
168
|
+
* @param node - Operand node to print
|
|
169
|
+
* @returns TypeScript code representation
|
|
170
|
+
*/
|
|
171
|
+
private printOperand(node: OperandNode): string {
|
|
172
|
+
return visitOperand(node, this);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Prints an ordering term (operand/expression/alias) to TypeScript code.
|
|
177
|
+
*/
|
|
178
|
+
private printOrderingTerm(term: OrderingTerm): string {
|
|
179
|
+
if (!term || !('type' in term)) {
|
|
180
|
+
throw new Error('Unsupported ordering term');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
switch (term.type) {
|
|
184
|
+
case 'Column':
|
|
185
|
+
return `${this.namingStrategy.tableToSymbol(term.table)}.${term.name}`;
|
|
186
|
+
case 'AliasRef':
|
|
187
|
+
return this.visitAliasRef(term);
|
|
188
|
+
case 'Literal':
|
|
189
|
+
case 'Function':
|
|
190
|
+
case 'JsonPath':
|
|
191
|
+
case 'ScalarSubquery':
|
|
192
|
+
case 'CaseExpression':
|
|
193
|
+
case 'WindowFunction':
|
|
194
|
+
case 'Cast':
|
|
195
|
+
case 'Collate':
|
|
196
|
+
return this.printOperand(term);
|
|
197
|
+
default:
|
|
198
|
+
return this.printExpression(term);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private getSelectionKey(selection: SelectionColumn, index: number): string {
|
|
203
|
+
if (selection.alias) {
|
|
204
|
+
return selection.alias;
|
|
205
|
+
}
|
|
206
|
+
if (this.isNamedSelection(selection)) {
|
|
207
|
+
return selection.name;
|
|
208
|
+
}
|
|
209
|
+
return `selection${index + 1}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private isNamedSelection(selection: SelectionColumn): selection is ColumnNode | FunctionNode | WindowFunctionNode {
|
|
213
|
+
return 'name' in selection;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public visitBinaryExpression(binary: BinaryExpressionNode): string {
|
|
217
|
+
return this.printBinaryExpression(binary);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
220
|
public visitLogicalExpression(logical: LogicalExpressionNode): string {
|
|
221
221
|
return this.printLogicalExpression(logical);
|
|
222
222
|
}
|
|
@@ -228,84 +228,84 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
228
228
|
public visitNullExpression(nullExpr: NullExpressionNode): string {
|
|
229
229
|
return this.printNullExpression(nullExpr);
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
public visitInExpression(inExpr: InExpressionNode): string {
|
|
233
|
-
return this.printInExpression(inExpr);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
public visitExistsExpression(existsExpr: ExistsExpressionNode): string {
|
|
237
|
-
return this.printExistsExpression(existsExpr);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
public visitBetweenExpression(betweenExpr: BetweenExpressionNode): string {
|
|
241
|
-
return this.printBetweenExpression(betweenExpr);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
public visitArithmeticExpression(arithExpr: ArithmeticExpressionNode): string {
|
|
245
|
-
return this.printArithmeticExpression(arithExpr);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
public visitColumn(node: ColumnNode): string {
|
|
249
|
-
return this.printColumnOperand(node);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public visitLiteral(node: LiteralNode): string {
|
|
253
|
-
return this.printLiteralOperand(node);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
public visitFunction(node: FunctionNode): string {
|
|
257
|
-
return this.printFunctionOperand(node);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
public visitJsonPath(node: JsonPathNode): string {
|
|
261
|
-
return this.printJsonPathOperand(node);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
public visitScalarSubquery(node: ScalarSubqueryNode): string {
|
|
265
|
-
return this.printScalarSubqueryOperand(node);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
public visitCaseExpression(node: CaseExpressionNode): string {
|
|
269
|
-
return this.printCaseExpressionOperand(node);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
public visitWindowFunction(node: WindowFunctionNode): string {
|
|
273
|
-
return this.printWindowFunctionOperand(node);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
public visitCast(node: CastExpressionNode): string {
|
|
277
|
-
return this.printCastOperand(node);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
public visitCollate(node: CollateExpressionNode): string {
|
|
281
|
-
return this.printCollateOperand(node);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
public visitAliasRef(node: AliasRefNode): string {
|
|
285
|
-
return `aliasRef('${node.name}')`;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Prints a binary expression to TypeScript code
|
|
290
|
-
* @param binary - Binary expression node
|
|
291
|
-
* @returns TypeScript code representation
|
|
292
|
-
*/
|
|
293
|
-
private printBinaryExpression(binary: BinaryExpressionNode): string {
|
|
294
|
-
const left = this.printOperand(binary.left);
|
|
295
|
-
const right = this.printOperand(binary.right);
|
|
296
|
-
const fn = this.mapOp(binary.operator);
|
|
297
|
-
const args = [left, right];
|
|
298
|
-
if (binary.escape) {
|
|
299
|
-
args.push(this.printOperand(binary.escape));
|
|
300
|
-
}
|
|
301
|
-
return `${fn}(${args.join(', ')})`;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Prints a logical expression to TypeScript code
|
|
306
|
-
* @param logical - Logical expression node
|
|
307
|
-
* @returns TypeScript code representation
|
|
308
|
-
*/
|
|
231
|
+
|
|
232
|
+
public visitInExpression(inExpr: InExpressionNode): string {
|
|
233
|
+
return this.printInExpression(inExpr);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public visitExistsExpression(existsExpr: ExistsExpressionNode): string {
|
|
237
|
+
return this.printExistsExpression(existsExpr);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public visitBetweenExpression(betweenExpr: BetweenExpressionNode): string {
|
|
241
|
+
return this.printBetweenExpression(betweenExpr);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public visitArithmeticExpression(arithExpr: ArithmeticExpressionNode): string {
|
|
245
|
+
return this.printArithmeticExpression(arithExpr);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public visitColumn(node: ColumnNode): string {
|
|
249
|
+
return this.printColumnOperand(node);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public visitLiteral(node: LiteralNode): string {
|
|
253
|
+
return this.printLiteralOperand(node);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public visitFunction(node: FunctionNode): string {
|
|
257
|
+
return this.printFunctionOperand(node);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public visitJsonPath(node: JsonPathNode): string {
|
|
261
|
+
return this.printJsonPathOperand(node);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public visitScalarSubquery(node: ScalarSubqueryNode): string {
|
|
265
|
+
return this.printScalarSubqueryOperand(node);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public visitCaseExpression(node: CaseExpressionNode): string {
|
|
269
|
+
return this.printCaseExpressionOperand(node);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public visitWindowFunction(node: WindowFunctionNode): string {
|
|
273
|
+
return this.printWindowFunctionOperand(node);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public visitCast(node: CastExpressionNode): string {
|
|
277
|
+
return this.printCastOperand(node);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public visitCollate(node: CollateExpressionNode): string {
|
|
281
|
+
return this.printCollateOperand(node);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public visitAliasRef(node: AliasRefNode): string {
|
|
285
|
+
return `aliasRef('${node.name}')`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Prints a binary expression to TypeScript code
|
|
290
|
+
* @param binary - Binary expression node
|
|
291
|
+
* @returns TypeScript code representation
|
|
292
|
+
*/
|
|
293
|
+
private printBinaryExpression(binary: BinaryExpressionNode): string {
|
|
294
|
+
const left = this.printOperand(binary.left);
|
|
295
|
+
const right = this.printOperand(binary.right);
|
|
296
|
+
const fn = this.mapOp(binary.operator);
|
|
297
|
+
const args = [left, right];
|
|
298
|
+
if (binary.escape) {
|
|
299
|
+
args.push(this.printOperand(binary.escape));
|
|
300
|
+
}
|
|
301
|
+
return `${fn}(${args.join(', ')})`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Prints a logical expression to TypeScript code
|
|
306
|
+
* @param logical - Logical expression node
|
|
307
|
+
* @returns TypeScript code representation
|
|
308
|
+
*/
|
|
309
309
|
private printLogicalExpression(logical: LogicalExpressionNode): string {
|
|
310
310
|
if (logical.operands.length === 0) return '';
|
|
311
311
|
const parts = logical.operands.map(op => {
|
|
@@ -321,192 +321,192 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
|
|
|
321
321
|
|
|
322
322
|
private printArithmeticExpression(expr: ArithmeticExpressionNode): string {
|
|
323
323
|
const left = this.printOperand(expr.left);
|
|
324
|
-
const right = this.printOperand(expr.right);
|
|
325
|
-
return `${left} ${expr.operator} ${right}`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Prints an IN expression to TypeScript code
|
|
330
|
-
* @param inExpr - IN expression node
|
|
331
|
-
* @returns TypeScript code representation
|
|
332
|
-
*/
|
|
333
|
-
private printInExpression(inExpr: InExpressionNode): string {
|
|
334
|
-
const left = this.printOperand(inExpr.left);
|
|
335
|
-
const fn = this.mapOp(inExpr.operator);
|
|
336
|
-
if (Array.isArray(inExpr.right)) {
|
|
337
|
-
const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
|
|
338
|
-
return `${fn}(${left}, [${values}])`;
|
|
339
|
-
}
|
|
340
|
-
const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
|
|
341
|
-
return `${fn}(${left}, (${subquery}))`;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Prints a null expression to TypeScript code
|
|
346
|
-
* @param nullExpr - Null expression node
|
|
347
|
-
* @returns TypeScript code representation
|
|
348
|
-
*/
|
|
349
|
-
private printNullExpression(nullExpr: NullExpressionNode): string {
|
|
350
|
-
const left = this.printOperand(nullExpr.left);
|
|
351
|
-
const fn = this.mapOp(nullExpr.operator);
|
|
352
|
-
return `${fn}(${left})`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Prints a BETWEEN expression to TypeScript code
|
|
357
|
-
* @param betweenExpr - BETWEEN expression node
|
|
358
|
-
* @returns TypeScript code representation
|
|
359
|
-
*/
|
|
360
|
-
private printBetweenExpression(betweenExpr: BetweenExpressionNode): string {
|
|
361
|
-
const left = this.printOperand(betweenExpr.left);
|
|
362
|
-
const lower = this.printOperand(betweenExpr.lower);
|
|
363
|
-
const upper = this.printOperand(betweenExpr.upper);
|
|
364
|
-
return `${this.mapOp(betweenExpr.operator)}(${left}, ${lower}, ${upper})`;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Prints an EXISTS expression to TypeScript code
|
|
369
|
-
* @param existsExpr - EXISTS expression node
|
|
370
|
-
* @returns TypeScript code representation
|
|
371
|
-
*/
|
|
372
|
-
private printExistsExpression(existsExpr: ExistsExpressionNode): string {
|
|
373
|
-
const subquery = this.inlineChain(this.buildSelectLines(existsExpr.subquery));
|
|
374
|
-
return `${this.mapOp(existsExpr.operator)}(${subquery})`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Prints a column operand to TypeScript code
|
|
379
|
-
* @param column - Column node
|
|
380
|
-
* @returns TypeScript code representation
|
|
381
|
-
*/
|
|
382
|
-
private printColumnOperand(column: ColumnNode): string {
|
|
383
|
-
return `${this.namingStrategy.tableToSymbol(column.table)}.${column.name}`;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Prints a literal operand to TypeScript code
|
|
388
|
-
* @param literal - Literal node
|
|
389
|
-
* @returns TypeScript code representation
|
|
390
|
-
*/
|
|
391
|
-
private printLiteralOperand(literal: LiteralNode): string {
|
|
392
|
-
if (literal.value === null) return 'null';
|
|
393
|
-
return typeof literal.value === 'string' ? `'${literal.value}'` : String(literal.value);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Prints a function operand to TypeScript code
|
|
398
|
-
* @param fn - Function node
|
|
399
|
-
* @returns TypeScript code representation
|
|
400
|
-
*/
|
|
401
|
-
private printFunctionOperand(fn: FunctionNode): string {
|
|
402
|
-
const args = fn.args.map(a => this.printOperand(a)).join(', ');
|
|
403
|
-
return `${fn.name.toLowerCase()}(${args})`;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Prints a JSON path operand to TypeScript code
|
|
408
|
-
* @param json - JSON path node
|
|
409
|
-
* @returns TypeScript code representation
|
|
410
|
-
*/
|
|
411
|
-
private printJsonPathOperand(json: JsonPathNode): string {
|
|
412
|
-
return `jsonPath(${this.namingStrategy.tableToSymbol(json.column.table)}.${json.column.name}, '${json.path}')`;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Prints a scalar subquery operand to TypeScript code
|
|
417
|
-
* @param node - Scalar subquery node
|
|
418
|
-
* @returns TypeScript code representation
|
|
419
|
-
*/
|
|
420
|
-
private printScalarSubqueryOperand(node: ScalarSubqueryNode): string {
|
|
421
|
-
const subquery = this.inlineChain(this.buildSelectLines(node.query));
|
|
422
|
-
return `(${subquery})`;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Prints a CASE expression operand to TypeScript code
|
|
427
|
-
* @param node - CASE expression node
|
|
428
|
-
* @returns TypeScript code representation
|
|
429
|
-
*/
|
|
430
|
-
private printCaseExpressionOperand(node: CaseExpressionNode): string {
|
|
431
|
-
const clauses = node.conditions.map(
|
|
432
|
-
condition =>
|
|
433
|
-
`{ when: ${this.printExpression(condition.when)}, then: ${this.printOperand(condition.then)} }`
|
|
434
|
-
);
|
|
435
|
-
const elseValue = node.else ? `, ${this.printOperand(node.else)}` : '';
|
|
436
|
-
return `caseWhen([${clauses.join(', ')}]${elseValue})`;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Prints a window function operand to TypeScript code
|
|
441
|
-
* @param node - Window function node
|
|
442
|
-
* @returns TypeScript code representation
|
|
443
|
-
*/
|
|
444
|
-
private printWindowFunctionOperand(node: WindowFunctionNode): string {
|
|
445
|
-
let result = `${node.name}(`;
|
|
446
|
-
if (node.args.length > 0) {
|
|
447
|
-
result += node.args.map(arg => this.printOperand(arg)).join(', ');
|
|
448
|
-
}
|
|
449
|
-
result += ') OVER (';
|
|
450
|
-
|
|
451
|
-
const parts: string[] = [];
|
|
452
|
-
|
|
453
|
-
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
454
|
-
const partitionClause =
|
|
455
|
-
'PARTITION BY ' + node.partitionBy.map(col => `${this.namingStrategy.tableToSymbol(col.table)}.${col.name}`).join(', ');
|
|
456
|
-
parts.push(partitionClause);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (node.orderBy && node.orderBy.length > 0) {
|
|
460
|
-
const orderClause =
|
|
461
|
-
'ORDER BY ' +
|
|
462
|
-
node.orderBy
|
|
463
|
-
.map(o => {
|
|
464
|
-
const term = this.printOrderingTerm(o.term);
|
|
465
|
-
const collation = o.collation ? ` COLLATE ${o.collation}` : '';
|
|
466
|
-
const nulls = o.nulls ? ` NULLS ${o.nulls}` : '';
|
|
467
|
-
return `${term} ${o.direction}${collation}${nulls}`;
|
|
468
|
-
})
|
|
469
|
-
.join(', ');
|
|
470
|
-
parts.push(orderClause);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
result += parts.join(' ');
|
|
474
|
-
result += ')';
|
|
475
|
-
|
|
476
|
-
return result;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
private printCastOperand(node: CastExpressionNode): string {
|
|
480
|
-
const typeLiteral = node.castType.replace(/'/g, "\\'");
|
|
481
|
-
return `cast(${this.printOperand(node.expression)}, '${typeLiteral}')`;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
private printCollateOperand(node: CollateExpressionNode): string {
|
|
485
|
-
return `collate(${this.printOperand(node.expression)}, '${node.collation}')`;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Converts method chain lines to inline format
|
|
490
|
-
* @param lines - Method chain lines
|
|
491
|
-
* @returns Inline method chain string
|
|
492
|
-
*/
|
|
493
|
-
private inlineChain(lines: string[]): string {
|
|
494
|
-
return lines
|
|
495
|
-
.map(line => line.trim())
|
|
496
|
-
.filter(line => line.length > 0)
|
|
497
|
-
.join(' ');
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Maps SQL operators to TypeScript function names
|
|
502
|
-
* @param op - SQL operator
|
|
503
|
-
* @returns TypeScript function name
|
|
504
|
-
*/
|
|
505
|
-
private mapOp(op: SqlOperator): string {
|
|
506
|
-
const config = SQL_OPERATOR_REGISTRY[op];
|
|
507
|
-
if (!config) {
|
|
508
|
-
return assertNever(op as never);
|
|
509
|
-
}
|
|
510
|
-
return config.tsName;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
324
|
+
const right = this.printOperand(expr.right);
|
|
325
|
+
return `${left} ${expr.operator} ${right}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Prints an IN expression to TypeScript code
|
|
330
|
+
* @param inExpr - IN expression node
|
|
331
|
+
* @returns TypeScript code representation
|
|
332
|
+
*/
|
|
333
|
+
private printInExpression(inExpr: InExpressionNode): string {
|
|
334
|
+
const left = this.printOperand(inExpr.left);
|
|
335
|
+
const fn = this.mapOp(inExpr.operator);
|
|
336
|
+
if (Array.isArray(inExpr.right)) {
|
|
337
|
+
const values = inExpr.right.map(v => this.printOperand(v)).join(', ');
|
|
338
|
+
return `${fn}(${left}, [${values}])`;
|
|
339
|
+
}
|
|
340
|
+
const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
|
|
341
|
+
return `${fn}(${left}, (${subquery}))`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Prints a null expression to TypeScript code
|
|
346
|
+
* @param nullExpr - Null expression node
|
|
347
|
+
* @returns TypeScript code representation
|
|
348
|
+
*/
|
|
349
|
+
private printNullExpression(nullExpr: NullExpressionNode): string {
|
|
350
|
+
const left = this.printOperand(nullExpr.left);
|
|
351
|
+
const fn = this.mapOp(nullExpr.operator);
|
|
352
|
+
return `${fn}(${left})`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Prints a BETWEEN expression to TypeScript code
|
|
357
|
+
* @param betweenExpr - BETWEEN expression node
|
|
358
|
+
* @returns TypeScript code representation
|
|
359
|
+
*/
|
|
360
|
+
private printBetweenExpression(betweenExpr: BetweenExpressionNode): string {
|
|
361
|
+
const left = this.printOperand(betweenExpr.left);
|
|
362
|
+
const lower = this.printOperand(betweenExpr.lower);
|
|
363
|
+
const upper = this.printOperand(betweenExpr.upper);
|
|
364
|
+
return `${this.mapOp(betweenExpr.operator)}(${left}, ${lower}, ${upper})`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Prints an EXISTS expression to TypeScript code
|
|
369
|
+
* @param existsExpr - EXISTS expression node
|
|
370
|
+
* @returns TypeScript code representation
|
|
371
|
+
*/
|
|
372
|
+
private printExistsExpression(existsExpr: ExistsExpressionNode): string {
|
|
373
|
+
const subquery = this.inlineChain(this.buildSelectLines(existsExpr.subquery));
|
|
374
|
+
return `${this.mapOp(existsExpr.operator)}(${subquery})`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Prints a column operand to TypeScript code
|
|
379
|
+
* @param column - Column node
|
|
380
|
+
* @returns TypeScript code representation
|
|
381
|
+
*/
|
|
382
|
+
private printColumnOperand(column: ColumnNode): string {
|
|
383
|
+
return `${this.namingStrategy.tableToSymbol(column.table)}.${column.name}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Prints a literal operand to TypeScript code
|
|
388
|
+
* @param literal - Literal node
|
|
389
|
+
* @returns TypeScript code representation
|
|
390
|
+
*/
|
|
391
|
+
private printLiteralOperand(literal: LiteralNode): string {
|
|
392
|
+
if (literal.value === null) return 'null';
|
|
393
|
+
return typeof literal.value === 'string' ? `'${literal.value}'` : String(literal.value);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Prints a function operand to TypeScript code
|
|
398
|
+
* @param fn - Function node
|
|
399
|
+
* @returns TypeScript code representation
|
|
400
|
+
*/
|
|
401
|
+
private printFunctionOperand(fn: FunctionNode): string {
|
|
402
|
+
const args = fn.args.map(a => this.printOperand(a)).join(', ');
|
|
403
|
+
return `${fn.name.toLowerCase()}(${args})`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Prints a JSON path operand to TypeScript code
|
|
408
|
+
* @param json - JSON path node
|
|
409
|
+
* @returns TypeScript code representation
|
|
410
|
+
*/
|
|
411
|
+
private printJsonPathOperand(json: JsonPathNode): string {
|
|
412
|
+
return `jsonPath(${this.namingStrategy.tableToSymbol(json.column.table)}.${json.column.name}, '${json.path}')`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Prints a scalar subquery operand to TypeScript code
|
|
417
|
+
* @param node - Scalar subquery node
|
|
418
|
+
* @returns TypeScript code representation
|
|
419
|
+
*/
|
|
420
|
+
private printScalarSubqueryOperand(node: ScalarSubqueryNode): string {
|
|
421
|
+
const subquery = this.inlineChain(this.buildSelectLines(node.query));
|
|
422
|
+
return `(${subquery})`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Prints a CASE expression operand to TypeScript code
|
|
427
|
+
* @param node - CASE expression node
|
|
428
|
+
* @returns TypeScript code representation
|
|
429
|
+
*/
|
|
430
|
+
private printCaseExpressionOperand(node: CaseExpressionNode): string {
|
|
431
|
+
const clauses = node.conditions.map(
|
|
432
|
+
condition =>
|
|
433
|
+
`{ when: ${this.printExpression(condition.when)}, then: ${this.printOperand(condition.then)} }`
|
|
434
|
+
);
|
|
435
|
+
const elseValue = node.else ? `, ${this.printOperand(node.else)}` : '';
|
|
436
|
+
return `caseWhen([${clauses.join(', ')}]${elseValue})`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Prints a window function operand to TypeScript code
|
|
441
|
+
* @param node - Window function node
|
|
442
|
+
* @returns TypeScript code representation
|
|
443
|
+
*/
|
|
444
|
+
private printWindowFunctionOperand(node: WindowFunctionNode): string {
|
|
445
|
+
let result = `${node.name}(`;
|
|
446
|
+
if (node.args.length > 0) {
|
|
447
|
+
result += node.args.map(arg => this.printOperand(arg)).join(', ');
|
|
448
|
+
}
|
|
449
|
+
result += ') OVER (';
|
|
450
|
+
|
|
451
|
+
const parts: string[] = [];
|
|
452
|
+
|
|
453
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
454
|
+
const partitionClause =
|
|
455
|
+
'PARTITION BY ' + node.partitionBy.map(col => `${this.namingStrategy.tableToSymbol(col.table)}.${col.name}`).join(', ');
|
|
456
|
+
parts.push(partitionClause);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
460
|
+
const orderClause =
|
|
461
|
+
'ORDER BY ' +
|
|
462
|
+
node.orderBy
|
|
463
|
+
.map(o => {
|
|
464
|
+
const term = this.printOrderingTerm(o.term);
|
|
465
|
+
const collation = o.collation ? ` COLLATE ${o.collation}` : '';
|
|
466
|
+
const nulls = o.nulls ? ` NULLS ${o.nulls}` : '';
|
|
467
|
+
return `${term} ${o.direction}${collation}${nulls}`;
|
|
468
|
+
})
|
|
469
|
+
.join(', ');
|
|
470
|
+
parts.push(orderClause);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
result += parts.join(' ');
|
|
474
|
+
result += ')';
|
|
475
|
+
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private printCastOperand(node: CastExpressionNode): string {
|
|
480
|
+
const typeLiteral = node.castType.replace(/'/g, "\\'");
|
|
481
|
+
return `cast(${this.printOperand(node.expression)}, '${typeLiteral}')`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private printCollateOperand(node: CollateExpressionNode): string {
|
|
485
|
+
return `collate(${this.printOperand(node.expression)}, '${node.collation}')`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Converts method chain lines to inline format
|
|
490
|
+
* @param lines - Method chain lines
|
|
491
|
+
* @returns Inline method chain string
|
|
492
|
+
*/
|
|
493
|
+
private inlineChain(lines: string[]): string {
|
|
494
|
+
return lines
|
|
495
|
+
.map(line => line.trim())
|
|
496
|
+
.filter(line => line.length > 0)
|
|
497
|
+
.join(' ');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Maps SQL operators to TypeScript function names
|
|
502
|
+
* @param op - SQL operator
|
|
503
|
+
* @returns TypeScript function name
|
|
504
|
+
*/
|
|
505
|
+
private mapOp(op: SqlOperator): string {
|
|
506
|
+
const config = SQL_OPERATOR_REGISTRY[op];
|
|
507
|
+
if (!config) {
|
|
508
|
+
return assertNever(op as never);
|
|
509
|
+
}
|
|
510
|
+
return config.tsName;
|
|
511
|
+
}
|
|
512
|
+
}
|