metal-orm 1.0.42 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -37
- package/dist/index.cjs +1014 -538
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1267 -371
- package/dist/index.d.ts +1267 -371
- package/dist/index.js +1012 -536
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/scripts/run-eslint.mjs +34 -0
- package/src/codegen/typescript.ts +32 -15
- package/src/core/ast/adapters.ts +8 -2
- package/src/core/ast/builders.ts +105 -76
- package/src/core/ast/expression-builders.ts +430 -392
- package/src/core/ast/expression-nodes.ts +14 -5
- package/src/core/ast/expression-visitor.ts +56 -14
- package/src/core/ast/helpers.ts +23 -0
- package/src/core/ast/join-node.ts +18 -2
- package/src/core/ast/query.ts +6 -6
- package/src/core/ast/window-functions.ts +10 -2
- package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
- package/src/core/ddl/dialects/index.ts +1 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
- package/src/core/ddl/dialects/render-reference.test.ts +69 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
- package/src/core/ddl/introspect/context.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +53 -8
- package/src/core/ddl/introspect/mysql.ts +32 -6
- package/src/core/ddl/introspect/postgres.ts +102 -34
- package/src/core/ddl/introspect/registry.ts +14 -0
- package/src/core/ddl/introspect/run-select.ts +19 -4
- package/src/core/ddl/introspect/sqlite.ts +78 -11
- package/src/core/ddl/introspect/types.ts +0 -1
- package/src/core/ddl/introspect/utils.ts +21 -3
- package/src/core/ddl/naming-strategy.ts +6 -0
- package/src/core/ddl/schema-dialect.ts +20 -6
- package/src/core/ddl/schema-diff.ts +22 -0
- package/src/core/ddl/schema-generator.ts +26 -12
- package/src/core/ddl/schema-plan-executor.ts +6 -0
- package/src/core/ddl/schema-types.ts +6 -0
- package/src/core/ddl/sql-writing.ts +4 -4
- package/src/core/dialect/abstract.ts +19 -7
- package/src/core/dialect/base/function-table-formatter.ts +3 -2
- package/src/core/dialect/base/join-compiler.ts +5 -3
- package/src/core/dialect/base/returning-strategy.ts +1 -0
- package/src/core/dialect/base/sql-dialect.ts +3 -3
- package/src/core/dialect/mssql/functions.ts +24 -25
- package/src/core/dialect/mssql/index.ts +1 -4
- package/src/core/dialect/mysql/functions.ts +0 -1
- package/src/core/dialect/postgres/functions.ts +33 -34
- package/src/core/dialect/postgres/index.ts +1 -0
- package/src/core/dialect/sqlite/functions.ts +18 -19
- package/src/core/dialect/sqlite/index.ts +2 -0
- package/src/core/execution/db-executor.ts +1 -1
- package/src/core/execution/executors/mysql-executor.ts +2 -2
- package/src/core/execution/executors/postgres-executor.ts +1 -1
- package/src/core/execution/pooling/pool.ts +12 -5
- package/src/core/functions/datetime.ts +58 -34
- package/src/core/functions/numeric.ts +96 -31
- package/src/core/functions/standard-strategy.ts +35 -0
- package/src/core/functions/text.ts +84 -23
- package/src/core/functions/types.ts +23 -8
- package/src/decorators/bootstrap.ts +42 -11
- package/src/decorators/column.ts +20 -11
- package/src/decorators/decorator-metadata.ts +30 -9
- package/src/decorators/entity.ts +29 -5
- package/src/decorators/index.ts +3 -0
- package/src/decorators/relations.ts +34 -11
- package/src/orm/als.ts +34 -9
- package/src/orm/entity-context.ts +62 -8
- package/src/orm/entity-meta.ts +8 -8
- package/src/orm/entity-metadata.ts +131 -16
- package/src/orm/entity.ts +28 -29
- package/src/orm/execute.ts +19 -4
- package/src/orm/hydration.ts +42 -39
- package/src/orm/identity-map.ts +1 -1
- package/src/orm/lazy-batch.ts +74 -104
- package/src/orm/orm-session.ts +24 -23
- package/src/orm/orm.ts +2 -5
- package/src/orm/relation-change-processor.ts +12 -11
- package/src/orm/relations/belongs-to.ts +11 -11
- package/src/orm/relations/has-many.ts +54 -10
- package/src/orm/relations/has-one.ts +8 -7
- package/src/orm/relations/many-to-many.ts +13 -13
- package/src/orm/runtime-types.ts +4 -4
- package/src/orm/save-graph.ts +31 -25
- package/src/orm/unit-of-work.ts +17 -17
- package/src/query/index.ts +74 -0
- package/src/query/target.ts +46 -0
- package/src/query-builder/delete-query-state.ts +30 -0
- package/src/query-builder/delete.ts +64 -18
- package/src/query-builder/hydration-manager.ts +52 -5
- package/src/query-builder/insert-query-state.ts +30 -0
- package/src/query-builder/insert.ts +58 -10
- package/src/query-builder/query-ast-service.ts +7 -2
- package/src/query-builder/query-resolution.ts +78 -0
- package/src/query-builder/raw-column-parser.ts +7 -1
- package/src/query-builder/relation-alias.ts +7 -0
- package/src/query-builder/relation-conditions.ts +61 -48
- package/src/query-builder/relation-service.ts +68 -63
- package/src/query-builder/relation-utils.ts +3 -0
- package/src/query-builder/select/cte-facet.ts +40 -0
- package/src/query-builder/select/from-facet.ts +80 -0
- package/src/query-builder/select/join-facet.ts +62 -0
- package/src/query-builder/select/predicate-facet.ts +103 -0
- package/src/query-builder/select/projection-facet.ts +69 -0
- package/src/query-builder/select/relation-facet.ts +81 -0
- package/src/query-builder/select/setop-facet.ts +36 -0
- package/src/query-builder/select-helpers.ts +15 -2
- package/src/query-builder/select-query-builder-deps.ts +19 -1
- package/src/query-builder/select-query-state.ts +2 -1
- package/src/query-builder/select.ts +795 -1163
- package/src/query-builder/update-query-state.ts +52 -0
- package/src/query-builder/update.ts +69 -18
- package/src/schema/column.ts +26 -26
- package/src/schema/table-guards.ts +31 -0
- package/src/schema/table.ts +47 -18
- package/src/schema/types.ts +22 -22
|
@@ -20,7 +20,7 @@ export class HydrationManager {
|
|
|
20
20
|
constructor(
|
|
21
21
|
private readonly table: TableDef,
|
|
22
22
|
private readonly planner: HydrationPlanner
|
|
23
|
-
) {}
|
|
23
|
+
) { }
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Creates a new HydrationManager with updated planner
|
|
@@ -113,6 +113,11 @@ export class HydrationManager {
|
|
|
113
113
|
return hasPagination && this.hasMultiplyingRelations(plan);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Checks if the hydration plan contains relations that multiply rows
|
|
118
|
+
* @param plan - Hydration plan to check
|
|
119
|
+
* @returns True if plan has HasMany or BelongsToMany relations
|
|
120
|
+
*/
|
|
116
121
|
private hasMultiplyingRelations(plan: HydrationPlan): boolean {
|
|
117
122
|
return plan.relations.some(
|
|
118
123
|
rel => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
|
|
@@ -203,6 +208,12 @@ export class HydrationManager {
|
|
|
203
208
|
};
|
|
204
209
|
}
|
|
205
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Generates a unique CTE name by appending a suffix if needed
|
|
213
|
+
* @param existing - Existing CTE nodes
|
|
214
|
+
* @param baseName - Base name for the CTE
|
|
215
|
+
* @returns Unique CTE name
|
|
216
|
+
*/
|
|
206
217
|
private nextCteName(existing: CommonTableExpressionNode[] | undefined, baseName: string): string {
|
|
207
218
|
const names = new Set((existing ?? []).map(cte => cte.name));
|
|
208
219
|
let candidate = baseName;
|
|
@@ -216,16 +227,27 @@ export class HydrationManager {
|
|
|
216
227
|
return candidate;
|
|
217
228
|
}
|
|
218
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Extracts projection names from column nodes
|
|
232
|
+
* @param columns - Projection nodes
|
|
233
|
+
* @returns Array of names or undefined if any column lacks name/alias
|
|
234
|
+
*/
|
|
219
235
|
private getProjectionNames(columns: ProjectionNode[]): string[] | undefined {
|
|
220
236
|
const names: string[] = [];
|
|
221
237
|
for (const col of columns) {
|
|
222
|
-
const
|
|
238
|
+
const node = col as { alias?: string; name?: string };
|
|
239
|
+
const alias = node.alias ?? node.name;
|
|
223
240
|
if (!alias) return undefined;
|
|
224
241
|
names.push(alias);
|
|
225
242
|
}
|
|
226
243
|
return names;
|
|
227
244
|
}
|
|
228
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Builds a map of column keys to their aliases from projection nodes
|
|
248
|
+
* @param columns - Projection nodes
|
|
249
|
+
* @returns Map of 'table.name' to alias
|
|
250
|
+
*/
|
|
229
251
|
private buildProjectionAliasMap(columns: ProjectionNode[]): Map<string, string> {
|
|
230
252
|
const map = new Map<string, string>();
|
|
231
253
|
for (const col of columns) {
|
|
@@ -237,6 +259,15 @@ export class HydrationManager {
|
|
|
237
259
|
return map;
|
|
238
260
|
}
|
|
239
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Maps order by nodes to use base CTE alias
|
|
264
|
+
* @param orderBy - Original order by nodes
|
|
265
|
+
* @param plan - Hydration plan
|
|
266
|
+
* @param projectionAliases - Map of column aliases
|
|
267
|
+
* @param baseAlias - Base CTE alias
|
|
268
|
+
* @param availableColumns - Set of available column names
|
|
269
|
+
* @returns Mapped order by nodes, null if cannot map
|
|
270
|
+
*/
|
|
240
271
|
private mapOrderBy(
|
|
241
272
|
orderBy: OrderByNode[] | undefined,
|
|
242
273
|
plan: HydrationPlan,
|
|
@@ -260,6 +291,15 @@ export class HydrationManager {
|
|
|
260
291
|
return mapped;
|
|
261
292
|
}
|
|
262
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Maps a single ordering term to use base CTE alias
|
|
296
|
+
* @param term - Ordering term to map
|
|
297
|
+
* @param plan - Hydration plan
|
|
298
|
+
* @param projectionAliases - Map of column aliases
|
|
299
|
+
* @param baseAlias - Base CTE alias
|
|
300
|
+
* @param availableColumns - Set of available column names
|
|
301
|
+
* @returns Mapped term or null if cannot map
|
|
302
|
+
*/
|
|
263
303
|
private mapOrderingTerm(
|
|
264
304
|
term: OrderByNode['term'],
|
|
265
305
|
plan: HydrationPlan,
|
|
@@ -267,7 +307,7 @@ export class HydrationManager {
|
|
|
267
307
|
baseAlias: string,
|
|
268
308
|
availableColumns: Set<string>
|
|
269
309
|
): OrderByNode['term'] | null {
|
|
270
|
-
if (
|
|
310
|
+
if (term.type === 'Column') {
|
|
271
311
|
const col = term as ColumnNode;
|
|
272
312
|
if (col.table !== plan.rootTable) return null;
|
|
273
313
|
const alias = projectionAliases.get(`${col.table}.${col.name}`) ?? col.name;
|
|
@@ -275,8 +315,8 @@ export class HydrationManager {
|
|
|
275
315
|
return { type: 'Column', table: baseAlias, name: alias };
|
|
276
316
|
}
|
|
277
317
|
|
|
278
|
-
if (
|
|
279
|
-
const aliasName =
|
|
318
|
+
if (term.type === 'AliasRef') {
|
|
319
|
+
const aliasName = term.name;
|
|
280
320
|
if (!availableColumns.has(aliasName)) return null;
|
|
281
321
|
return { type: 'Column', table: baseAlias, name: aliasName };
|
|
282
322
|
}
|
|
@@ -284,6 +324,13 @@ export class HydrationManager {
|
|
|
284
324
|
return null;
|
|
285
325
|
}
|
|
286
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Builds column nodes for paging CTE
|
|
329
|
+
* @param primaryKey - Primary key name
|
|
330
|
+
* @param orderBy - Order by nodes
|
|
331
|
+
* @param tableAlias - Table alias for columns
|
|
332
|
+
* @returns Array of column nodes for paging
|
|
333
|
+
*/
|
|
287
334
|
private buildPagingColumns(primaryKey: string, orderBy: OrderByNode[] | undefined, tableAlias: string): ColumnNode[] {
|
|
288
335
|
const columns: ColumnNode[] = [{ type: 'Column', table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
289
336
|
|
|
@@ -20,6 +20,11 @@ export class InsertQueryState {
|
|
|
20
20
|
public readonly table: TableDef;
|
|
21
21
|
public readonly ast: InsertQueryNode;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new InsertQueryState instance
|
|
25
|
+
* @param table - The table definition for the INSERT query
|
|
26
|
+
* @param ast - Optional initial AST node, defaults to a basic INSERT query
|
|
27
|
+
*/
|
|
23
28
|
constructor(table: TableDef, ast?: InsertQueryNode) {
|
|
24
29
|
this.table = table;
|
|
25
30
|
this.ast = ast ?? {
|
|
@@ -55,6 +60,13 @@ export class InsertQueryState {
|
|
|
55
60
|
return buildColumnNodes(this.table, names);
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Adds VALUES clause to the INSERT query
|
|
65
|
+
* @param rows - Array of row objects to insert
|
|
66
|
+
* @returns A new InsertQueryState with the VALUES clause added
|
|
67
|
+
* @throws Error if mixing VALUES with SELECT source
|
|
68
|
+
* @throws Error if invalid values are provided
|
|
69
|
+
*/
|
|
58
70
|
withValues(rows: Record<string, unknown>[]): InsertQueryState {
|
|
59
71
|
if (!rows.length) return this;
|
|
60
72
|
|
|
@@ -88,6 +100,11 @@ export class InsertQueryState {
|
|
|
88
100
|
});
|
|
89
101
|
}
|
|
90
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Sets the columns for the INSERT query
|
|
105
|
+
* @param columns - Column nodes to insert into
|
|
106
|
+
* @returns A new InsertQueryState with the specified columns
|
|
107
|
+
*/
|
|
91
108
|
withColumns(columns: ColumnNode[]): InsertQueryState {
|
|
92
109
|
if (!columns.length) return this;
|
|
93
110
|
return this.clone({
|
|
@@ -96,6 +113,14 @@ export class InsertQueryState {
|
|
|
96
113
|
});
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Adds SELECT source to the INSERT query
|
|
118
|
+
* @param query - The SELECT query to use as source
|
|
119
|
+
* @param columns - Target columns for the INSERT
|
|
120
|
+
* @returns A new InsertQueryState with the SELECT source
|
|
121
|
+
* @throws Error if mixing SELECT with VALUES source
|
|
122
|
+
* @throws Error if no destination columns specified
|
|
123
|
+
*/
|
|
99
124
|
withSelect(query: SelectQueryNode, columns: ColumnNode[]): InsertQueryState {
|
|
100
125
|
const targetColumns =
|
|
101
126
|
columns.length
|
|
@@ -122,6 +147,11 @@ export class InsertQueryState {
|
|
|
122
147
|
});
|
|
123
148
|
}
|
|
124
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Adds a RETURNING clause to the INSERT query
|
|
152
|
+
* @param columns - Columns to return after insertion
|
|
153
|
+
* @returns A new InsertQueryState with the RETURNING clause added
|
|
154
|
+
*/
|
|
125
155
|
withReturning(columns: ColumnNode[]): InsertQueryState {
|
|
126
156
|
return this.clone({
|
|
127
157
|
...this.ast,
|
|
@@ -17,6 +17,11 @@ export class InsertQueryBuilder<T> {
|
|
|
17
17
|
private readonly table: TableDef;
|
|
18
18
|
private readonly state: InsertQueryState;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new InsertQueryBuilder instance
|
|
22
|
+
* @param table - The table definition for the INSERT query
|
|
23
|
+
* @param state - Optional initial query state, defaults to a new InsertQueryState
|
|
24
|
+
*/
|
|
20
25
|
constructor(table: TableDef, state?: InsertQueryState) {
|
|
21
26
|
this.table = table;
|
|
22
27
|
this.state = state ?? new InsertQueryState(table);
|
|
@@ -26,19 +31,36 @@ export class InsertQueryBuilder<T> {
|
|
|
26
31
|
return new InsertQueryBuilder(this.table, state);
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Adds VALUES to the INSERT query
|
|
36
|
+
* @param rowOrRows - Single row object or array of row objects to insert
|
|
37
|
+
* @returns A new InsertQueryBuilder with the VALUES clause added
|
|
38
|
+
*/
|
|
29
39
|
values(rowOrRows: Record<string, unknown> | Record<string, unknown>[]): InsertQueryBuilder<T> {
|
|
30
40
|
const rows = Array.isArray(rowOrRows) ? rowOrRows : [rowOrRows];
|
|
31
41
|
if (!rows.length) return this;
|
|
32
42
|
return this.clone(this.state.withValues(rows));
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Specifies the columns for the INSERT query
|
|
47
|
+
* @param columns - Column definitions or nodes to insert into
|
|
48
|
+
* @returns A new InsertQueryBuilder with the specified columns
|
|
49
|
+
*/
|
|
35
50
|
columns(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
|
|
36
51
|
if (!columns.length) return this;
|
|
37
52
|
return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Sets the source of the INSERT query to a SELECT query
|
|
57
|
+
* @template TSource - The source table type
|
|
58
|
+
* @param query - The SELECT query or query builder to use as source
|
|
59
|
+
* @param columns - Optional target columns for the INSERT
|
|
60
|
+
* @returns A new InsertQueryBuilder with the SELECT source
|
|
61
|
+
*/
|
|
62
|
+
fromSelect<TSource extends TableDef>(
|
|
63
|
+
query: SelectQueryNode | SelectQueryBuilder<unknown, TSource>,
|
|
42
64
|
columns: (ColumnDef | ColumnNode)[] = []
|
|
43
65
|
): InsertQueryBuilder<T> {
|
|
44
66
|
const ast = this.resolveSelectQuery(query);
|
|
@@ -46,6 +68,11 @@ export class InsertQueryBuilder<T> {
|
|
|
46
68
|
return this.clone(this.state.withSelect(ast, nodes));
|
|
47
69
|
}
|
|
48
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Adds a RETURNING clause to the INSERT query
|
|
73
|
+
* @param columns - Columns to return after insertion
|
|
74
|
+
* @returns A new InsertQueryBuilder with the RETURNING clause added
|
|
75
|
+
*/
|
|
49
76
|
returning(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
|
|
50
77
|
if (!columns.length) return this;
|
|
51
78
|
const nodes = columns.map(column => buildColumnNode(this.table, column));
|
|
@@ -57,23 +84,35 @@ export class InsertQueryBuilder<T> {
|
|
|
57
84
|
return columns.map(column => buildColumnNode(this.table, column));
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
private resolveSelectQuery
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
private resolveSelectQuery<TSource extends TableDef>(
|
|
88
|
+
query: SelectQueryNode | SelectQueryBuilder<unknown, TSource>
|
|
89
|
+
): SelectQueryNode {
|
|
90
|
+
const candidate = query as { getAST?: () => SelectQueryNode };
|
|
91
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
92
|
+
? candidate.getAST()
|
|
63
93
|
: (query as SelectQueryNode);
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
// Existing compiler-based compile stays, but we add a new overload.
|
|
67
97
|
|
|
68
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Compiles the INSERT query
|
|
100
|
+
* @param compiler - The INSERT compiler to use
|
|
101
|
+
* @returns The compiled query with SQL and parameters
|
|
102
|
+
*/
|
|
69
103
|
compile(compiler: InsertCompiler): CompiledQuery;
|
|
70
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Compiles the INSERT query for the specified dialect
|
|
106
|
+
* @param dialect - The SQL dialect to compile for
|
|
107
|
+
* @returns The compiled query with SQL and parameters
|
|
108
|
+
*/
|
|
71
109
|
compile(dialect: InsertDialectInput): CompiledQuery;
|
|
72
110
|
|
|
73
111
|
compile(arg: InsertCompiler | InsertDialectInput): CompiledQuery {
|
|
74
|
-
|
|
112
|
+
const candidate = arg as { compileInsert?: (ast: InsertQueryNode) => CompiledQuery };
|
|
113
|
+
if (typeof candidate.compileInsert === 'function') {
|
|
75
114
|
// InsertCompiler path – old behavior
|
|
76
|
-
return
|
|
115
|
+
return candidate.compileInsert(this.state.ast);
|
|
77
116
|
}
|
|
78
117
|
|
|
79
118
|
// Dialect | string path – new behavior
|
|
@@ -81,10 +120,19 @@ export class InsertQueryBuilder<T> {
|
|
|
81
120
|
return dialect.compileInsert(this.state.ast);
|
|
82
121
|
}
|
|
83
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Returns the SQL string for the INSERT query
|
|
125
|
+
* @param arg - The compiler or dialect to generate SQL for
|
|
126
|
+
* @returns The SQL string representation of the query
|
|
127
|
+
*/
|
|
84
128
|
toSql(arg: InsertCompiler | InsertDialectInput): string {
|
|
85
|
-
return this.compile(arg as
|
|
129
|
+
return this.compile(arg as InsertCompiler).sql;
|
|
86
130
|
}
|
|
87
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Returns the Abstract Syntax Tree (AST) representation of the query
|
|
134
|
+
* @returns The AST node for the INSERT query
|
|
135
|
+
*/
|
|
88
136
|
getAST(): InsertQueryNode {
|
|
89
137
|
return this.state.ast;
|
|
90
138
|
}
|
|
@@ -49,7 +49,7 @@ export class QueryAstService {
|
|
|
49
49
|
* @param table - Table definition
|
|
50
50
|
* @param state - Current query state
|
|
51
51
|
*/
|
|
52
|
-
constructor(private readonly table: TableDef, private readonly state: SelectQueryState) {}
|
|
52
|
+
constructor(private readonly table: TableDef, private readonly state: SelectQueryState) { }
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Selects columns for the query
|
|
@@ -251,10 +251,15 @@ export class QueryAstService {
|
|
|
251
251
|
return existing ? and(existing, next) : next;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Normalizes an ordering term to a standard OrderingTerm
|
|
256
|
+
* @param term - Column definition or ordering term to normalize
|
|
257
|
+
* @returns Normalized ordering term
|
|
258
|
+
*/
|
|
254
259
|
private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
|
|
255
260
|
const from = this.state.ast.from;
|
|
256
261
|
const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
|
|
257
|
-
const termType = (term as
|
|
262
|
+
const termType = (term as { type?: string }).type;
|
|
258
263
|
if (termType === 'Column') {
|
|
259
264
|
return term as ColumnNode;
|
|
260
265
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SelectQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode } from '../core/ast/query.js';
|
|
2
|
+
import { TableDef } from '../schema/table.js';
|
|
3
|
+
import type { SelectQueryBuilder } from './select.js';
|
|
4
|
+
import type { UpdateQueryBuilder } from './update.js';
|
|
5
|
+
import type { DeleteQueryBuilder } from './delete.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves a SelectQueryBuilder or SelectQueryNode to a SelectQueryNode AST
|
|
9
|
+
* @param query - Query builder or AST node
|
|
10
|
+
* @returns SelectQueryNode AST
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSelectQuery<TSub extends TableDef>(
|
|
13
|
+
query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
|
|
14
|
+
): SelectQueryNode {
|
|
15
|
+
const candidate = query as { getAST?: () => SelectQueryNode };
|
|
16
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
17
|
+
? candidate.getAST()
|
|
18
|
+
: (query as SelectQueryNode);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves a UpdateQueryBuilder or UpdateQueryNode to a UpdateQueryNode AST
|
|
23
|
+
* @param query - Query builder or AST node
|
|
24
|
+
* @returns UpdateQueryNode AST
|
|
25
|
+
*/
|
|
26
|
+
export function resolveUpdateQuery<T>(
|
|
27
|
+
query: UpdateQueryBuilder<T> | UpdateQueryNode
|
|
28
|
+
): UpdateQueryNode {
|
|
29
|
+
const candidate = query as { getAST?: () => UpdateQueryNode };
|
|
30
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
31
|
+
? candidate.getAST()
|
|
32
|
+
: (query as UpdateQueryNode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves a DeleteQueryBuilder or DeleteQueryNode to a DeleteQueryNode AST
|
|
37
|
+
* @param query - Query builder or AST node
|
|
38
|
+
* @returns DeleteQueryNode AST
|
|
39
|
+
*/
|
|
40
|
+
export function resolveDeleteQuery<T>(
|
|
41
|
+
query: DeleteQueryBuilder<T> | DeleteQueryNode
|
|
42
|
+
): DeleteQueryNode {
|
|
43
|
+
const candidate = query as { getAST?: () => DeleteQueryNode };
|
|
44
|
+
return typeof candidate.getAST === 'function' && candidate.getAST
|
|
45
|
+
? candidate.getAST()
|
|
46
|
+
: (query as DeleteQueryNode);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolves a TableDef or TableSourceNode to a TableSourceNode
|
|
51
|
+
* @param source - Table definition or source node
|
|
52
|
+
* @returns TableSourceNode
|
|
53
|
+
*/
|
|
54
|
+
export function resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
|
|
55
|
+
if (isTableSourceNode(source)) {
|
|
56
|
+
return source;
|
|
57
|
+
}
|
|
58
|
+
return { type: 'Table', name: source.name, schema: source.schema };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolves a join target (TableDef, TableSourceNode, or string relation name)
|
|
63
|
+
* @param table - Join target
|
|
64
|
+
* @returns TableSourceNode or string
|
|
65
|
+
*/
|
|
66
|
+
export function resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
|
|
67
|
+
if (typeof table === 'string') return table;
|
|
68
|
+
return resolveTableSource(table);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if a value is a TableSourceNode
|
|
73
|
+
* @param source - Value to check
|
|
74
|
+
* @returns True if value is a TableSourceNode
|
|
75
|
+
*/
|
|
76
|
+
function isTableSourceNode(source: TableDef | TableSourceNode): source is TableSourceNode {
|
|
77
|
+
return typeof (source as TableSourceNode).type === 'string';
|
|
78
|
+
}
|
|
@@ -4,6 +4,11 @@ import { CommonTableExpressionNode } from '../core/ast/query.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Best-effort helper that tries to convert a raw column expression into a `ColumnNode`.
|
|
6
6
|
* This parser is intentionally limited; use it only for simple references or function calls.
|
|
7
|
+
*
|
|
8
|
+
* @param col - Raw column expression string (e.g., "column", "table.column", "COUNT(column)")
|
|
9
|
+
* @param tableName - Default table name to use when no table is specified
|
|
10
|
+
* @param ctes - Optional array of CTEs for context when parsing column references
|
|
11
|
+
* @returns A ColumnNode representing the parsed column expression
|
|
7
12
|
*/
|
|
8
13
|
export const parseRawColumn = (
|
|
9
14
|
col: string,
|
|
@@ -11,7 +16,8 @@ export const parseRawColumn = (
|
|
|
11
16
|
ctes?: CommonTableExpressionNode[]
|
|
12
17
|
): ColumnNode => {
|
|
13
18
|
if (col.includes('(')) {
|
|
14
|
-
const [
|
|
19
|
+
const [_fn, rest] = col.split('(');
|
|
20
|
+
void _fn;
|
|
15
21
|
const colName = rest.replace(')', '');
|
|
16
22
|
const [table, name] = colName.includes('.') ? colName.split('.') : [tableName, colName];
|
|
17
23
|
return { type: 'Column', table, name, alias: col };
|
|
@@ -19,6 +19,9 @@ export interface RelationAliasParts {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Builds a relation alias from the relation name and column name components.
|
|
22
|
+
* @param relationName - The name of the relation
|
|
23
|
+
* @param columnName - The name of the column within the relation
|
|
24
|
+
* @returns A relation alias string in the format "relationName__columnName"
|
|
22
25
|
*/
|
|
23
26
|
export const makeRelationAlias = (relationName: string, columnName: string): string =>
|
|
24
27
|
`${relationName}${RELATION_SEPARATOR}${columnName}`;
|
|
@@ -26,6 +29,8 @@ export const makeRelationAlias = (relationName: string, columnName: string): str
|
|
|
26
29
|
/**
|
|
27
30
|
* Parses a relation alias into its relation/column components.
|
|
28
31
|
* Returns `null` when the alias does not follow the `relation__column` pattern.
|
|
32
|
+
* @param alias - The relation alias string to parse
|
|
33
|
+
* @returns Parsed relation alias parts or null if not a valid relation alias
|
|
29
34
|
*/
|
|
30
35
|
export const parseRelationAlias = (alias: string): RelationAliasParts | null => {
|
|
31
36
|
const idx = alias.indexOf(RELATION_SEPARATOR);
|
|
@@ -38,6 +43,8 @@ export const parseRelationAlias = (alias: string): RelationAliasParts | null =>
|
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Determines whether an alias represents a relation column by checking the `__` convention.
|
|
46
|
+
* @param alias - The alias string to check
|
|
47
|
+
* @returns True if the alias follows the relation alias pattern
|
|
41
48
|
*/
|
|
42
49
|
export const isRelationAlias = (alias?: string): boolean =>
|
|
43
50
|
!!alias && alias.includes(RELATION_SEPARATOR);
|
|
@@ -21,26 +21,26 @@ const assertNever = (value: never): never => {
|
|
|
21
21
|
* @param relation - Relation definition
|
|
22
22
|
* @returns Expression node representing the join condition
|
|
23
23
|
*/
|
|
24
|
-
const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
25
|
-
const rootTable = rootAlias || root.name;
|
|
26
|
-
const defaultLocalKey =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const localKey = relation.localKey || defaultLocalKey;
|
|
24
|
+
const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
25
|
+
const rootTable = rootAlias || root.name;
|
|
26
|
+
const defaultLocalKey =
|
|
27
|
+
relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
|
|
28
|
+
? findPrimaryKey(root)
|
|
29
|
+
: findPrimaryKey(relation.target);
|
|
30
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
31
31
|
|
|
32
32
|
switch (relation.type) {
|
|
33
|
-
case RelationKinds.HasMany:
|
|
34
|
-
case RelationKinds.HasOne:
|
|
35
|
-
return eq(
|
|
36
|
-
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
37
|
-
{ type: 'Column', table: rootTable, name: localKey }
|
|
38
|
-
);
|
|
39
|
-
case RelationKinds.BelongsTo:
|
|
40
|
-
return eq(
|
|
41
|
-
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
42
|
-
{ type: 'Column', table: rootTable, name: relation.foreignKey }
|
|
43
|
-
);
|
|
33
|
+
case RelationKinds.HasMany:
|
|
34
|
+
case RelationKinds.HasOne:
|
|
35
|
+
return eq(
|
|
36
|
+
{ type: 'Column', table: relation.target.name, name: relation.foreignKey },
|
|
37
|
+
{ type: 'Column', table: rootTable, name: localKey }
|
|
38
|
+
);
|
|
39
|
+
case RelationKinds.BelongsTo:
|
|
40
|
+
return eq(
|
|
41
|
+
{ type: 'Column', table: relation.target.name, name: localKey },
|
|
42
|
+
{ type: 'Column', table: rootTable, name: relation.foreignKey }
|
|
43
|
+
);
|
|
44
44
|
case RelationKinds.BelongsToMany:
|
|
45
45
|
throw new Error('BelongsToMany relations do not support the standard join condition builder');
|
|
46
46
|
default:
|
|
@@ -50,25 +50,36 @@ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Builds the join nodes required to include a BelongsToMany relation.
|
|
53
|
+
* @param root - The root table definition
|
|
54
|
+
* @param relationName - Name of the relation being joined
|
|
55
|
+
* @param relation - The BelongsToMany relation definition
|
|
56
|
+
* @param joinKind - The type of join to perform
|
|
57
|
+
* @param extra - Optional additional conditions for the target join
|
|
58
|
+
* @param rootAlias - Optional alias for the root table
|
|
59
|
+
* @returns Array of join nodes for the pivot and target tables
|
|
53
60
|
*/
|
|
54
|
-
export const buildBelongsToManyJoins = (
|
|
55
|
-
root: TableDef,
|
|
56
|
-
relationName: string,
|
|
57
|
-
relation: BelongsToManyRelation,
|
|
58
|
-
joinKind: JoinKind,
|
|
59
|
-
extra?: ExpressionNode,
|
|
60
|
-
rootAlias?: string
|
|
61
|
-
): JoinNode[] => {
|
|
62
|
-
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
63
|
-
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
64
|
-
const rootTable = rootAlias || root.name;
|
|
65
|
-
|
|
66
|
-
const pivotCondition = eq(
|
|
67
|
-
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
68
|
-
{ type: 'Column', table: rootTable, name: rootKey }
|
|
69
|
-
);
|
|
61
|
+
export const buildBelongsToManyJoins = (
|
|
62
|
+
root: TableDef,
|
|
63
|
+
relationName: string,
|
|
64
|
+
relation: BelongsToManyRelation,
|
|
65
|
+
joinKind: JoinKind,
|
|
66
|
+
extra?: ExpressionNode,
|
|
67
|
+
rootAlias?: string
|
|
68
|
+
): JoinNode[] => {
|
|
69
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
70
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
71
|
+
const rootTable = rootAlias || root.name;
|
|
70
72
|
|
|
71
|
-
const
|
|
73
|
+
const pivotCondition = eq(
|
|
74
|
+
{ type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
75
|
+
{ type: 'Column', table: rootTable, name: rootKey }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const pivotJoin = createJoinNode(
|
|
79
|
+
joinKind,
|
|
80
|
+
{ type: 'Table', name: relation.pivotTable.name, schema: relation.pivotTable.schema },
|
|
81
|
+
pivotCondition
|
|
82
|
+
);
|
|
72
83
|
|
|
73
84
|
let targetCondition: ExpressionNode = eq(
|
|
74
85
|
{ type: 'Column', table: relation.target.name, name: targetKey },
|
|
@@ -81,7 +92,7 @@ export const buildBelongsToManyJoins = (
|
|
|
81
92
|
|
|
82
93
|
const targetJoin = createJoinNode(
|
|
83
94
|
joinKind,
|
|
84
|
-
relation.target.name,
|
|
95
|
+
{ type: 'Table', name: relation.target.name, schema: relation.target.schema },
|
|
85
96
|
targetCondition,
|
|
86
97
|
relationName
|
|
87
98
|
);
|
|
@@ -94,24 +105,26 @@ export const buildBelongsToManyJoins = (
|
|
|
94
105
|
* @param root - Root table definition
|
|
95
106
|
* @param relation - Relation definition
|
|
96
107
|
* @param extra - Optional additional expression to combine with AND
|
|
108
|
+
* @param rootAlias - Optional alias for the root table
|
|
97
109
|
* @returns Expression node representing the complete join condition
|
|
98
110
|
*/
|
|
99
|
-
export const buildRelationJoinCondition = (
|
|
100
|
-
root: TableDef,
|
|
101
|
-
relation: RelationDef,
|
|
102
|
-
extra?: ExpressionNode,
|
|
103
|
-
rootAlias?: string
|
|
104
|
-
): ExpressionNode => {
|
|
105
|
-
const base = baseRelationCondition(root, relation, rootAlias);
|
|
106
|
-
return extra ? and(base, extra) : base;
|
|
107
|
-
};
|
|
111
|
+
export const buildRelationJoinCondition = (
|
|
112
|
+
root: TableDef,
|
|
113
|
+
relation: RelationDef,
|
|
114
|
+
extra?: ExpressionNode,
|
|
115
|
+
rootAlias?: string
|
|
116
|
+
): ExpressionNode => {
|
|
117
|
+
const base = baseRelationCondition(root, relation, rootAlias);
|
|
118
|
+
return extra ? and(base, extra) : base;
|
|
119
|
+
};
|
|
108
120
|
|
|
109
121
|
/**
|
|
110
122
|
* Builds a relation correlation condition for subqueries
|
|
111
123
|
* @param root - Root table definition
|
|
112
124
|
* @param relation - Relation definition
|
|
125
|
+
* @param rootAlias - Optional alias for the root table
|
|
113
126
|
* @returns Expression node representing the correlation condition
|
|
114
127
|
*/
|
|
115
|
-
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
116
|
-
return baseRelationCondition(root, relation, rootAlias);
|
|
117
|
-
};
|
|
128
|
+
export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
|
|
129
|
+
return baseRelationCondition(root, relation, rootAlias);
|
|
130
|
+
};
|