metal-orm 1.0.39 → 1.0.41
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/dist/index.cjs +1466 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -51
- package/dist/index.d.ts +723 -51
- package/dist/index.js +1457 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +66 -5
- package/src/core/ast/aggregate-functions.ts +15 -15
- package/src/core/ast/expression-builders.ts +378 -316
- package/src/core/ast/expression-nodes.ts +210 -186
- package/src/core/ast/expression-visitor.ts +40 -30
- package/src/core/ast/query.ts +164 -132
- package/src/core/ast/window-functions.ts +86 -86
- package/src/core/dialect/abstract.ts +509 -479
- package/src/core/dialect/base/groupby-compiler.ts +6 -6
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/orderby-compiler.ts +20 -6
- package/src/core/dialect/base/sql-dialect.ts +237 -138
- package/src/core/dialect/mssql/index.ts +164 -185
- package/src/core/dialect/sqlite/index.ts +39 -34
- package/src/core/execution/db-executor.ts +46 -6
- package/src/core/execution/executors/mssql-executor.ts +39 -22
- package/src/core/execution/executors/mysql-executor.ts +23 -6
- package/src/core/execution/executors/sqlite-executor.ts +29 -3
- package/src/core/execution/pooling/pool-types.ts +30 -0
- package/src/core/execution/pooling/pool.ts +268 -0
- package/src/core/functions/standard-strategy.ts +46 -37
- package/src/decorators/bootstrap.ts +7 -7
- package/src/index.ts +6 -0
- package/src/orm/domain-event-bus.ts +49 -0
- package/src/orm/entity-metadata.ts +9 -9
- package/src/orm/entity.ts +58 -0
- package/src/orm/orm-session.ts +465 -270
- package/src/orm/orm.ts +61 -11
- package/src/orm/pooled-executor-factory.ts +131 -0
- package/src/orm/query-logger.ts +6 -12
- package/src/orm/relation-change-processor.ts +75 -0
- package/src/orm/relations/many-to-many.ts +4 -2
- package/src/orm/save-graph.ts +303 -0
- package/src/orm/transaction-runner.ts +3 -3
- package/src/orm/unit-of-work.ts +128 -0
- package/src/query-builder/delete-query-state.ts +67 -38
- package/src/query-builder/delete.ts +37 -1
- package/src/query-builder/hydration-manager.ts +93 -79
- package/src/query-builder/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/query-ast-service.ts +207 -170
- package/src/query-builder/select-query-state.ts +169 -162
- package/src/query-builder/select.ts +15 -23
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
|
@@ -1,38 +1,67 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
3
|
+
import {
|
|
4
|
+
DeleteQueryNode,
|
|
5
|
+
TableSourceNode
|
|
6
|
+
} from '../core/ast/query.js';
|
|
7
|
+
import { JoinNode } from '../core/ast/join.js';
|
|
8
|
+
import { createTableNode } from '../core/ast/builders.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Maintains immutable state for DELETE queries
|
|
12
|
+
*/
|
|
13
|
+
export class DeleteQueryState {
|
|
14
|
+
public readonly table: TableDef;
|
|
15
|
+
public readonly ast: DeleteQueryNode;
|
|
16
|
+
|
|
17
|
+
constructor(table: TableDef, ast?: DeleteQueryNode) {
|
|
18
|
+
this.table = table;
|
|
19
|
+
this.ast = ast ?? {
|
|
20
|
+
type: 'DeleteQuery',
|
|
21
|
+
from: createTableNode(table),
|
|
22
|
+
joins: []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private clone(nextAst: DeleteQueryNode): DeleteQueryState {
|
|
27
|
+
return new DeleteQueryState(this.table, nextAst);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
withWhere(expr: ExpressionNode): DeleteQueryState {
|
|
31
|
+
return this.clone({
|
|
32
|
+
...this.ast,
|
|
33
|
+
where: expr
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
withReturning(columns: ColumnNode[]): DeleteQueryState {
|
|
38
|
+
return this.clone({
|
|
39
|
+
...this.ast,
|
|
40
|
+
returning: [...columns]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
withUsing(source: TableSourceNode): DeleteQueryState {
|
|
45
|
+
return this.clone({
|
|
46
|
+
...this.ast,
|
|
47
|
+
using: source
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
withJoin(join: JoinNode): DeleteQueryState {
|
|
52
|
+
return this.clone({
|
|
53
|
+
...this.ast,
|
|
54
|
+
joins: [...(this.ast.joins ?? []), join]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
withTableAlias(alias: string): DeleteQueryState {
|
|
59
|
+
return this.clone({
|
|
60
|
+
...this.ast,
|
|
61
|
+
from: {
|
|
62
|
+
...this.ast.from,
|
|
63
|
+
alias
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { ColumnDef } from '../schema/column.js';
|
|
3
3
|
import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
|
|
4
|
+
import { JOIN_KINDS, JoinKind } from '../core/sql/sql.js';
|
|
4
5
|
import { CompiledQuery, DeleteCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
6
|
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
6
|
-
import { DeleteQueryNode } from '../core/ast/query.js';
|
|
7
|
+
import { TableSourceNode, DeleteQueryNode } from '../core/ast/query.js';
|
|
7
8
|
import { DeleteQueryState } from './delete-query-state.js';
|
|
9
|
+
import { createJoinNode } from '../core/ast/join-node.js';
|
|
8
10
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
9
11
|
|
|
10
12
|
type DeleteDialectInput = Dialect | DialectKey;
|
|
@@ -29,12 +31,43 @@ export class DeleteQueryBuilder<T> {
|
|
|
29
31
|
return this.clone(this.state.withWhere(expr));
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
as(alias: string): DeleteQueryBuilder<T> {
|
|
35
|
+
return this.clone(this.state.withTableAlias(alias));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
using(source: TableDef | TableSourceNode): DeleteQueryBuilder<T> {
|
|
39
|
+
return this.clone(this.state.withUsing(this.resolveTableSource(source)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
join(
|
|
43
|
+
table: TableDef | TableSourceNode | string,
|
|
44
|
+
condition: ExpressionNode,
|
|
45
|
+
kind: JoinKind = JOIN_KINDS.INNER,
|
|
46
|
+
relationName?: string
|
|
47
|
+
): DeleteQueryBuilder<T> {
|
|
48
|
+
const target = this.resolveJoinTarget(table);
|
|
49
|
+
const joinNode = createJoinNode(kind, target, condition, relationName);
|
|
50
|
+
return this.clone(this.state.withJoin(joinNode));
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
returning(...columns: (ColumnDef | ColumnNode)[]): DeleteQueryBuilder<T> {
|
|
33
54
|
if (!columns.length) return this;
|
|
34
55
|
const nodes = columns.map(column => buildColumnNode(this.table, column));
|
|
35
56
|
return this.clone(this.state.withReturning(nodes));
|
|
36
57
|
}
|
|
37
58
|
|
|
59
|
+
private resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
|
|
60
|
+
if (isTableSourceNode(source)) {
|
|
61
|
+
return source;
|
|
62
|
+
}
|
|
63
|
+
return { type: 'Table', name: source.name, schema: source.schema };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
|
|
67
|
+
if (typeof table === 'string') return table;
|
|
68
|
+
return this.resolveTableSource(table);
|
|
69
|
+
}
|
|
70
|
+
|
|
38
71
|
// Existing compiler-based compile stays, but we add a new overload.
|
|
39
72
|
|
|
40
73
|
// 1) Keep the old behavior (used internally / tests, if any):
|
|
@@ -61,3 +94,6 @@ export class DeleteQueryBuilder<T> {
|
|
|
61
94
|
return this.state.ast;
|
|
62
95
|
}
|
|
63
96
|
}
|
|
97
|
+
|
|
98
|
+
const isTableSourceNode = (source: TableDef | TableSourceNode): source is TableSourceNode =>
|
|
99
|
+
typeof (source as TableSourceNode).type === 'string';
|
|
@@ -7,63 +7,63 @@ import { ProjectionNode, SelectQueryState } from './select-query-state.js';
|
|
|
7
7
|
import { ColumnNode, eq } from '../core/ast/expression.js';
|
|
8
8
|
import { createJoinNode } from '../core/ast/join-node.js';
|
|
9
9
|
import { JOIN_KINDS } from '../core/sql/sql.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Manages hydration planning for query results
|
|
13
|
-
*/
|
|
14
|
-
export class HydrationManager {
|
|
15
|
-
/**
|
|
16
|
-
* Creates a new HydrationManager instance
|
|
17
|
-
* @param table - Table definition
|
|
18
|
-
* @param planner - Hydration planner
|
|
19
|
-
*/
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly table: TableDef,
|
|
22
|
-
private readonly planner: HydrationPlanner
|
|
23
|
-
) {}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates a new HydrationManager with updated planner
|
|
27
|
-
* @param nextPlanner - Updated hydration planner
|
|
28
|
-
* @returns New HydrationManager instance
|
|
29
|
-
*/
|
|
30
|
-
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
31
|
-
return new HydrationManager(this.table, nextPlanner);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Handles column selection for hydration planning
|
|
36
|
-
* @param state - Current query state
|
|
37
|
-
* @param newColumns - Newly selected columns
|
|
38
|
-
* @returns Updated HydrationManager with captured columns
|
|
39
|
-
*/
|
|
40
|
-
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
41
|
-
const updated = this.planner.captureRootColumns(newColumns);
|
|
42
|
-
return this.clone(updated);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Handles relation inclusion for hydration planning
|
|
47
|
-
* @param state - Current query state
|
|
48
|
-
* @param relation - Relation definition
|
|
49
|
-
* @param relationName - Name of the relation
|
|
50
|
-
* @param aliasPrefix - Alias prefix for the relation
|
|
51
|
-
* @param targetColumns - Target columns to include
|
|
52
|
-
* @returns Updated HydrationManager with included relation
|
|
53
|
-
*/
|
|
54
|
-
onRelationIncluded(
|
|
55
|
-
state: SelectQueryState,
|
|
56
|
-
relation: RelationDef,
|
|
57
|
-
relationName: string,
|
|
58
|
-
aliasPrefix: string,
|
|
59
|
-
targetColumns: string[],
|
|
60
|
-
pivot?: { aliasPrefix: string; columns: string[] }
|
|
61
|
-
): HydrationManager {
|
|
62
|
-
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
63
|
-
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
64
|
-
return this.clone(next);
|
|
65
|
-
}
|
|
66
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages hydration planning for query results
|
|
13
|
+
*/
|
|
14
|
+
export class HydrationManager {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new HydrationManager instance
|
|
17
|
+
* @param table - Table definition
|
|
18
|
+
* @param planner - Hydration planner
|
|
19
|
+
*/
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly table: TableDef,
|
|
22
|
+
private readonly planner: HydrationPlanner
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new HydrationManager with updated planner
|
|
27
|
+
* @param nextPlanner - Updated hydration planner
|
|
28
|
+
* @returns New HydrationManager instance
|
|
29
|
+
*/
|
|
30
|
+
private clone(nextPlanner: HydrationPlanner): HydrationManager {
|
|
31
|
+
return new HydrationManager(this.table, nextPlanner);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handles column selection for hydration planning
|
|
36
|
+
* @param state - Current query state
|
|
37
|
+
* @param newColumns - Newly selected columns
|
|
38
|
+
* @returns Updated HydrationManager with captured columns
|
|
39
|
+
*/
|
|
40
|
+
onColumnsSelected(state: SelectQueryState, newColumns: ProjectionNode[]): HydrationManager {
|
|
41
|
+
const updated = this.planner.captureRootColumns(newColumns);
|
|
42
|
+
return this.clone(updated);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handles relation inclusion for hydration planning
|
|
47
|
+
* @param state - Current query state
|
|
48
|
+
* @param relation - Relation definition
|
|
49
|
+
* @param relationName - Name of the relation
|
|
50
|
+
* @param aliasPrefix - Alias prefix for the relation
|
|
51
|
+
* @param targetColumns - Target columns to include
|
|
52
|
+
* @returns Updated HydrationManager with included relation
|
|
53
|
+
*/
|
|
54
|
+
onRelationIncluded(
|
|
55
|
+
state: SelectQueryState,
|
|
56
|
+
relation: RelationDef,
|
|
57
|
+
relationName: string,
|
|
58
|
+
aliasPrefix: string,
|
|
59
|
+
targetColumns: string[],
|
|
60
|
+
pivot?: { aliasPrefix: string; columns: string[] }
|
|
61
|
+
): HydrationManager {
|
|
62
|
+
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
63
|
+
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
64
|
+
return this.clone(next);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
67
|
/**
|
|
68
68
|
* Applies hydration plan to the AST
|
|
69
69
|
* @param ast - Query AST to modify
|
|
@@ -82,11 +82,11 @@ export class HydrationManager {
|
|
|
82
82
|
const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
|
|
83
83
|
return this.attachHydrationMeta(rewritten, plan);
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Gets the current hydration plan
|
|
88
|
-
* @returns Hydration plan or undefined if none exists
|
|
89
|
-
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets the current hydration plan
|
|
88
|
+
* @returns Hydration plan or undefined if none exists
|
|
89
|
+
*/
|
|
90
90
|
getPlan(): HydrationPlan | undefined {
|
|
91
91
|
return this.planner.getPlan();
|
|
92
92
|
}
|
|
@@ -251,38 +251,52 @@ export class HydrationManager {
|
|
|
251
251
|
const mapped: OrderByNode[] = [];
|
|
252
252
|
|
|
253
253
|
for (const ob of orderBy) {
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
254
|
+
const mappedTerm = this.mapOrderingTerm(ob.term, plan, projectionAliases, baseAlias, availableColumns);
|
|
255
|
+
if (!mappedTerm) return null;
|
|
258
256
|
|
|
259
|
-
|
|
260
|
-
if (!availableColumns.has(alias)) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
mapped.push({
|
|
265
|
-
type: 'OrderBy',
|
|
266
|
-
column: { type: 'Column', table: baseAlias, name: alias },
|
|
267
|
-
direction: ob.direction
|
|
268
|
-
});
|
|
257
|
+
mapped.push({ ...ob, term: mappedTerm });
|
|
269
258
|
}
|
|
270
259
|
|
|
271
260
|
return mapped;
|
|
272
261
|
}
|
|
273
262
|
|
|
263
|
+
private mapOrderingTerm(
|
|
264
|
+
term: OrderByNode['term'],
|
|
265
|
+
plan: HydrationPlan,
|
|
266
|
+
projectionAliases: Map<string, string>,
|
|
267
|
+
baseAlias: string,
|
|
268
|
+
availableColumns: Set<string>
|
|
269
|
+
): OrderByNode['term'] | null {
|
|
270
|
+
if ((term as any).type === 'Column') {
|
|
271
|
+
const col = term as ColumnNode;
|
|
272
|
+
if (col.table !== plan.rootTable) return null;
|
|
273
|
+
const alias = projectionAliases.get(`${col.table}.${col.name}`) ?? col.name;
|
|
274
|
+
if (!availableColumns.has(alias)) return null;
|
|
275
|
+
return { type: 'Column', table: baseAlias, name: alias };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if ((term as any).type === 'AliasRef') {
|
|
279
|
+
const aliasName = (term as any).name;
|
|
280
|
+
if (!availableColumns.has(aliasName)) return null;
|
|
281
|
+
return { type: 'Column', table: baseAlias, name: aliasName };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
274
287
|
private buildPagingColumns(primaryKey: string, orderBy: OrderByNode[] | undefined, tableAlias: string): ColumnNode[] {
|
|
275
288
|
const columns: ColumnNode[] = [{ type: 'Column', table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
276
289
|
|
|
277
290
|
if (!orderBy) return columns;
|
|
278
291
|
|
|
279
292
|
for (const ob of orderBy) {
|
|
280
|
-
|
|
293
|
+
const term = ob.term as ColumnNode;
|
|
294
|
+
if (!columns.some(col => col.name === term.name)) {
|
|
281
295
|
columns.push({
|
|
282
296
|
type: 'Column',
|
|
283
297
|
table: tableAlias,
|
|
284
|
-
name:
|
|
285
|
-
alias:
|
|
298
|
+
name: term.name,
|
|
299
|
+
alias: term.name
|
|
286
300
|
});
|
|
287
301
|
}
|
|
288
302
|
}
|
|
@@ -1,61 +1,131 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import { InsertQueryNode,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import { InsertQueryNode, SelectQueryNode } from '../core/ast/query.js';
|
|
3
|
+
import {
|
|
4
|
+
ColumnNode,
|
|
5
|
+
OperandNode,
|
|
6
|
+
isValueOperandInput,
|
|
7
|
+
valueToOperand
|
|
8
|
+
} from '../core/ast/expression.js';
|
|
9
|
+
import {
|
|
10
|
+
buildColumnNodes,
|
|
11
|
+
createTableNode
|
|
12
|
+
} from '../core/ast/builders.js';
|
|
13
|
+
|
|
14
|
+
type InsertRows = Record<string, unknown>[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Maintains immutable state for building INSERT queries
|
|
18
|
+
*/
|
|
19
|
+
export class InsertQueryState {
|
|
20
|
+
public readonly table: TableDef;
|
|
21
|
+
public readonly ast: InsertQueryNode;
|
|
22
|
+
|
|
23
|
+
constructor(table: TableDef, ast?: InsertQueryNode) {
|
|
24
|
+
this.table = table;
|
|
25
|
+
this.ast = ast ?? {
|
|
26
|
+
type: 'InsertQuery',
|
|
27
|
+
into: createTableNode(table),
|
|
28
|
+
columns: [],
|
|
29
|
+
source: {
|
|
30
|
+
type: 'InsertValues',
|
|
31
|
+
rows: []
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private clone(nextAst: InsertQueryNode): InsertQueryState {
|
|
37
|
+
return new InsertQueryState(this.table, nextAst);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private ensureColumnsFromRow(rows: InsertRows): ColumnNode[] {
|
|
41
|
+
if (this.ast.columns.length) return this.ast.columns;
|
|
42
|
+
return buildColumnNodes(this.table, Object.keys(rows[0]));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private appendValues(rows: OperandNode[][]): OperandNode[][] {
|
|
46
|
+
if (this.ast.source.type === 'InsertValues') {
|
|
47
|
+
return [...this.ast.source.rows, ...rows];
|
|
48
|
+
}
|
|
49
|
+
return rows;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private getTableColumns(): ColumnNode[] {
|
|
53
|
+
const names = Object.keys(this.table.columns);
|
|
54
|
+
if (!names.length) return [];
|
|
55
|
+
return buildColumnNodes(this.table, names);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
withValues(rows: Record<string, unknown>[]): InsertQueryState {
|
|
59
|
+
if (!rows.length) return this;
|
|
60
|
+
|
|
61
|
+
if (this.ast.source.type === 'InsertSelect') {
|
|
62
|
+
throw new Error('Cannot mix INSERT ... VALUES with INSERT ... SELECT source.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const definedColumns = this.ensureColumnsFromRow(rows);
|
|
66
|
+
|
|
67
|
+
const newRows: OperandNode[][] = rows.map((row, rowIndex) =>
|
|
68
|
+
definedColumns.map(column => {
|
|
69
|
+
const rawValue = row[column.name];
|
|
70
|
+
|
|
71
|
+
if (!isValueOperandInput(rawValue)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Invalid insert value for column "${column.name}" in row ${rowIndex}: only primitives, null, or OperandNodes are allowed`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return valueToOperand(rawValue);
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return this.clone({
|
|
82
|
+
...this.ast,
|
|
83
|
+
columns: definedColumns,
|
|
84
|
+
source: {
|
|
85
|
+
type: 'InsertValues',
|
|
86
|
+
rows: this.appendValues(newRows)
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
withColumns(columns: ColumnNode[]): InsertQueryState {
|
|
92
|
+
if (!columns.length) return this;
|
|
93
|
+
return this.clone({
|
|
94
|
+
...this.ast,
|
|
95
|
+
columns: [...columns]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
withSelect(query: SelectQueryNode, columns: ColumnNode[]): InsertQueryState {
|
|
100
|
+
const targetColumns =
|
|
101
|
+
columns.length
|
|
102
|
+
? columns
|
|
103
|
+
: this.ast.columns.length
|
|
104
|
+
? this.ast.columns
|
|
105
|
+
: this.getTableColumns();
|
|
106
|
+
|
|
107
|
+
if (!targetColumns.length) {
|
|
108
|
+
throw new Error('INSERT ... SELECT requires specifying destination columns.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.ast.source.type === 'InsertValues' && this.ast.source.rows.length) {
|
|
112
|
+
throw new Error('Cannot mix INSERT ... SELECT with INSERT ... VALUES source.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this.clone({
|
|
116
|
+
...this.ast,
|
|
117
|
+
columns: [...targetColumns],
|
|
118
|
+
source: {
|
|
119
|
+
type: 'InsertSelect',
|
|
120
|
+
query
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
withReturning(columns: ColumnNode[]): InsertQueryState {
|
|
126
|
+
return this.clone({
|
|
127
|
+
...this.ast,
|
|
128
|
+
returning: [...columns]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { SelectQueryBuilder } from './select.js';
|
|
1
2
|
import { TableDef } from '../schema/table.js';
|
|
2
3
|
import { ColumnDef } from '../schema/column.js';
|
|
3
4
|
import { ColumnNode } from '../core/ast/expression.js';
|
|
4
5
|
import { CompiledQuery, InsertCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
6
|
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
6
|
-
import { InsertQueryNode } from '../core/ast/query.js';
|
|
7
|
+
import { InsertQueryNode, SelectQueryNode } from '../core/ast/query.js';
|
|
7
8
|
import { InsertQueryState } from './insert-query-state.js';
|
|
8
9
|
import { buildColumnNode } from '../core/ast/builders.js';
|
|
9
10
|
|
|
@@ -31,12 +32,37 @@ export class InsertQueryBuilder<T> {
|
|
|
31
32
|
return this.clone(this.state.withValues(rows));
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
columns(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
|
|
36
|
+
if (!columns.length) return this;
|
|
37
|
+
return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fromSelect(
|
|
41
|
+
query: SelectQueryNode | SelectQueryBuilder<any, TableDef<any>>,
|
|
42
|
+
columns: (ColumnDef | ColumnNode)[] = []
|
|
43
|
+
): InsertQueryBuilder<T> {
|
|
44
|
+
const ast = this.resolveSelectQuery(query);
|
|
45
|
+
const nodes = columns.length ? this.resolveColumnNodes(columns) : [];
|
|
46
|
+
return this.clone(this.state.withSelect(ast, nodes));
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
returning(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
|
|
35
50
|
if (!columns.length) return this;
|
|
36
51
|
const nodes = columns.map(column => buildColumnNode(this.table, column));
|
|
37
52
|
return this.clone(this.state.withReturning(nodes));
|
|
38
53
|
}
|
|
39
54
|
|
|
55
|
+
// Helpers for column/AST resolution
|
|
56
|
+
private resolveColumnNodes(columns: (ColumnDef | ColumnNode)[]): ColumnNode[] {
|
|
57
|
+
return columns.map(column => buildColumnNode(this.table, column));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private resolveSelectQuery(query: SelectQueryNode | SelectQueryBuilder<any>): SelectQueryNode {
|
|
61
|
+
return typeof (query as any).getAST === 'function'
|
|
62
|
+
? (query as SelectQueryBuilder<any>).getAST()
|
|
63
|
+
: (query as SelectQueryNode);
|
|
64
|
+
}
|
|
65
|
+
|
|
40
66
|
// Existing compiler-based compile stays, but we add a new overload.
|
|
41
67
|
|
|
42
68
|
// 1) Keep the old behavior (used internally / tests, if any):
|