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.
Files changed (52) hide show
  1. package/dist/index.cjs +1466 -189
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +723 -51
  4. package/dist/index.d.ts +723 -51
  5. package/dist/index.js +1457 -189
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/codegen/typescript.ts +66 -5
  9. package/src/core/ast/aggregate-functions.ts +15 -15
  10. package/src/core/ast/expression-builders.ts +378 -316
  11. package/src/core/ast/expression-nodes.ts +210 -186
  12. package/src/core/ast/expression-visitor.ts +40 -30
  13. package/src/core/ast/query.ts +164 -132
  14. package/src/core/ast/window-functions.ts +86 -86
  15. package/src/core/dialect/abstract.ts +509 -479
  16. package/src/core/dialect/base/groupby-compiler.ts +6 -6
  17. package/src/core/dialect/base/join-compiler.ts +9 -12
  18. package/src/core/dialect/base/orderby-compiler.ts +20 -6
  19. package/src/core/dialect/base/sql-dialect.ts +237 -138
  20. package/src/core/dialect/mssql/index.ts +164 -185
  21. package/src/core/dialect/sqlite/index.ts +39 -34
  22. package/src/core/execution/db-executor.ts +46 -6
  23. package/src/core/execution/executors/mssql-executor.ts +39 -22
  24. package/src/core/execution/executors/mysql-executor.ts +23 -6
  25. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  26. package/src/core/execution/pooling/pool-types.ts +30 -0
  27. package/src/core/execution/pooling/pool.ts +268 -0
  28. package/src/core/functions/standard-strategy.ts +46 -37
  29. package/src/decorators/bootstrap.ts +7 -7
  30. package/src/index.ts +6 -0
  31. package/src/orm/domain-event-bus.ts +49 -0
  32. package/src/orm/entity-metadata.ts +9 -9
  33. package/src/orm/entity.ts +58 -0
  34. package/src/orm/orm-session.ts +465 -270
  35. package/src/orm/orm.ts +61 -11
  36. package/src/orm/pooled-executor-factory.ts +131 -0
  37. package/src/orm/query-logger.ts +6 -12
  38. package/src/orm/relation-change-processor.ts +75 -0
  39. package/src/orm/relations/many-to-many.ts +4 -2
  40. package/src/orm/save-graph.ts +303 -0
  41. package/src/orm/transaction-runner.ts +3 -3
  42. package/src/orm/unit-of-work.ts +128 -0
  43. package/src/query-builder/delete-query-state.ts +67 -38
  44. package/src/query-builder/delete.ts +37 -1
  45. package/src/query-builder/hydration-manager.ts +93 -79
  46. package/src/query-builder/insert-query-state.ts +131 -61
  47. package/src/query-builder/insert.ts +27 -1
  48. package/src/query-builder/query-ast-service.ts +207 -170
  49. package/src/query-builder/select-query-state.ts +169 -162
  50. package/src/query-builder/select.ts +15 -23
  51. package/src/query-builder/update-query-state.ts +114 -77
  52. 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 { TableNode, DeleteQueryNode } from '../core/ast/query.js';
4
- import { createTableNode } from '../core/ast/builders.js';
5
-
6
- /**
7
- * Maintains immutable state for DELETE queries
8
- */
9
- export class DeleteQueryState {
10
- public readonly table: TableDef;
11
- public readonly ast: DeleteQueryNode;
12
-
13
- constructor(table: TableDef, ast?: DeleteQueryNode) {
14
- this.table = table;
15
- this.ast = ast ?? {
16
- type: 'DeleteQuery',
17
- from: createTableNode(table)
18
- };
19
- }
20
-
21
- private clone(nextAst: DeleteQueryNode): DeleteQueryState {
22
- return new DeleteQueryState(this.table, nextAst);
23
- }
24
-
25
- withWhere(expr: ExpressionNode): DeleteQueryState {
26
- return this.clone({
27
- ...this.ast,
28
- where: expr
29
- });
30
- }
31
-
32
- withReturning(columns: ColumnNode[]): DeleteQueryState {
33
- return this.clone({
34
- ...this.ast,
35
- returning: [...columns]
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
- // Only rewrite when ordering by root columns; child columns would reintroduce the pagination bug.
255
- if (ob.column.table !== plan.rootTable) {
256
- return null;
257
- }
254
+ const mappedTerm = this.mapOrderingTerm(ob.term, plan, projectionAliases, baseAlias, availableColumns);
255
+ if (!mappedTerm) return null;
258
256
 
259
- const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
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
- if (!columns.some(col => col.name === ob.column.name)) {
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: ob.column.name,
285
- alias: ob.column.name
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, TableNode } from '../core/ast/query.js';
3
- import { ColumnNode, OperandNode, isValueOperandInput, valueToOperand } from '../core/ast/expression.js';
4
- import { buildColumnNodes, createTableNode } from '../core/ast/builders.js';
5
-
6
- /**
7
- * Maintains immutable state for building INSERT queries
8
- */
9
- export class InsertQueryState {
10
- public readonly table: TableDef;
11
- public readonly ast: InsertQueryNode;
12
-
13
- constructor(table: TableDef, ast?: InsertQueryNode) {
14
- this.table = table;
15
- this.ast = ast ?? {
16
- type: 'InsertQuery',
17
- into: createTableNode(table),
18
- columns: [],
19
- values: []
20
- };
21
- }
22
-
23
- private clone(nextAst: InsertQueryNode): InsertQueryState {
24
- return new InsertQueryState(this.table, nextAst);
25
- }
26
-
27
- withValues(rows: Record<string, unknown>[]): InsertQueryState {
28
- if (!rows.length) return this;
29
-
30
- const definedColumns = this.ast.columns.length
31
- ? this.ast.columns
32
- : buildColumnNodes(this.table, Object.keys(rows[0]));
33
-
34
- const newRows: OperandNode[][] = rows.map((row, rowIndex) =>
35
- definedColumns.map(column => {
36
- const rawValue = row[column.name];
37
-
38
- if (!isValueOperandInput(rawValue)) {
39
- throw new Error(
40
- `Invalid insert value for column "${column.name}" in row ${rowIndex}: only primitives, null, or OperandNodes are allowed`
41
- );
42
- }
43
-
44
- return valueToOperand(rawValue);
45
- })
46
- );
47
-
48
- return this.clone({
49
- ...this.ast,
50
- columns: definedColumns,
51
- values: [...this.ast.values, ...newRows]
52
- });
53
- }
54
-
55
- withReturning(columns: ColumnNode[]): InsertQueryState {
56
- return this.clone({
57
- ...this.ast,
58
- returning: [...columns]
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):