metal-orm 1.0.40 → 1.0.42
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 +53 -14
- package/dist/index.cjs +1298 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +676 -30
- package/dist/index.d.ts +676 -30
- package/dist/index.js +1293 -126
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +6 -2
- package/src/core/ast/expression-builders.ts +25 -4
- package/src/core/ast/expression-nodes.ts +3 -1
- package/src/core/ast/expression.ts +2 -2
- package/src/core/ast/query.ts +24 -2
- package/src/core/dialect/abstract.ts +6 -2
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/sql-dialect.ts +98 -17
- package/src/core/dialect/mssql/index.ts +30 -62
- 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/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/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
- package/src/schema/table.ts +210 -115
|
@@ -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';
|
|
@@ -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):
|
|
@@ -1,77 +1,114 @@
|
|
|
1
|
-
import { TableDef } from '../schema/table.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
1
|
+
import { TableDef } from '../schema/table.js';
|
|
2
|
+
import {
|
|
3
|
+
ColumnNode,
|
|
4
|
+
ExpressionNode,
|
|
5
|
+
OperandNode,
|
|
6
|
+
isOperandNode,
|
|
7
|
+
valueToOperand
|
|
8
|
+
} from '../core/ast/expression.js';
|
|
9
|
+
import {
|
|
10
|
+
TableSourceNode,
|
|
11
|
+
UpdateQueryNode,
|
|
12
|
+
UpdateAssignmentNode
|
|
13
|
+
} from '../core/ast/query.js';
|
|
14
|
+
import { JoinNode } from '../core/ast/join.js';
|
|
15
|
+
import { createTableNode } from '../core/ast/builders.js';
|
|
16
|
+
|
|
17
|
+
type LiteralValue = string | number | boolean | null;
|
|
18
|
+
type UpdateValue = OperandNode | LiteralValue;
|
|
19
|
+
|
|
20
|
+
const isUpdateValue = (value: unknown): value is UpdateValue => {
|
|
21
|
+
if (value === null) return true;
|
|
22
|
+
switch (typeof value) {
|
|
23
|
+
case 'string':
|
|
24
|
+
case 'number':
|
|
25
|
+
case 'boolean':
|
|
26
|
+
return true;
|
|
27
|
+
default:
|
|
28
|
+
return isOperandNode(value);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Immutable state for UPDATE queries
|
|
34
|
+
*/
|
|
35
|
+
export class UpdateQueryState {
|
|
36
|
+
public readonly table: TableDef;
|
|
37
|
+
public readonly ast: UpdateQueryNode;
|
|
38
|
+
|
|
39
|
+
constructor(table: TableDef, ast?: UpdateQueryNode) {
|
|
40
|
+
this.table = table;
|
|
41
|
+
this.ast = ast ?? {
|
|
42
|
+
type: 'UpdateQuery',
|
|
43
|
+
table: createTableNode(table),
|
|
44
|
+
set: [],
|
|
45
|
+
joins: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private clone(nextAst: UpdateQueryNode): UpdateQueryState {
|
|
50
|
+
return new UpdateQueryState(this.table, nextAst);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
withSet(values: Record<string, unknown>): UpdateQueryState {
|
|
54
|
+
const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, rawValue]) => {
|
|
55
|
+
if (!isUpdateValue(rawValue)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Invalid update value for column "${column}": only primitives, null, or OperandNodes are allowed`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
column: {
|
|
63
|
+
type: 'Column',
|
|
64
|
+
table: this.table.name,
|
|
65
|
+
name: column
|
|
66
|
+
},
|
|
67
|
+
value: valueToOperand(rawValue)
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return this.clone({
|
|
72
|
+
...this.ast,
|
|
73
|
+
set: assignments
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
withWhere(expr: ExpressionNode): UpdateQueryState {
|
|
78
|
+
return this.clone({
|
|
79
|
+
...this.ast,
|
|
80
|
+
where: expr
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
withReturning(columns: ColumnNode[]): UpdateQueryState {
|
|
85
|
+
return this.clone({
|
|
86
|
+
...this.ast,
|
|
87
|
+
returning: [...columns]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
withFrom(from: TableSourceNode): UpdateQueryState {
|
|
92
|
+
return this.clone({
|
|
93
|
+
...this.ast,
|
|
94
|
+
from
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
withJoin(join: JoinNode): UpdateQueryState {
|
|
99
|
+
return this.clone({
|
|
100
|
+
...this.ast,
|
|
101
|
+
joins: [...(this.ast.joins ?? []), join]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
withTableAlias(alias: string): UpdateQueryState {
|
|
106
|
+
return this.clone({
|
|
107
|
+
...this.ast,
|
|
108
|
+
table: {
|
|
109
|
+
...this.ast.table,
|
|
110
|
+
alias
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -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, UpdateCompiler, Dialect } from '../core/dialect/abstract.js';
|
|
5
6
|
import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
|
|
6
|
-
import { UpdateQueryNode } from '../core/ast/query.js';
|
|
7
|
+
import { TableSourceNode, UpdateQueryNode } from '../core/ast/query.js';
|
|
7
8
|
import { UpdateQueryState } from './update-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 UpdateDialectInput = Dialect | DialectKey;
|
|
@@ -25,6 +27,26 @@ export class UpdateQueryBuilder<T> {
|
|
|
25
27
|
return new UpdateQueryBuilder(this.table, state);
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
as(alias: string): UpdateQueryBuilder<T> {
|
|
31
|
+
return this.clone(this.state.withTableAlias(alias));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
from(source: TableDef | TableSourceNode): UpdateQueryBuilder<T> {
|
|
35
|
+
const tableSource = this.resolveTableSource(source);
|
|
36
|
+
return this.clone(this.state.withFrom(tableSource));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
join(
|
|
40
|
+
table: TableDef | TableSourceNode | string,
|
|
41
|
+
condition: ExpressionNode,
|
|
42
|
+
kind: JoinKind = JOIN_KINDS.INNER,
|
|
43
|
+
relationName?: string
|
|
44
|
+
): UpdateQueryBuilder<T> {
|
|
45
|
+
const joinTarget = this.resolveJoinTarget(table);
|
|
46
|
+
const joinNode = createJoinNode(kind, joinTarget, condition, relationName);
|
|
47
|
+
return this.clone(this.state.withJoin(joinNode));
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
set(values: Record<string, unknown>): UpdateQueryBuilder<T> {
|
|
29
51
|
return this.clone(this.state.withSet(values));
|
|
30
52
|
}
|
|
@@ -39,6 +61,18 @@ export class UpdateQueryBuilder<T> {
|
|
|
39
61
|
return this.clone(this.state.withReturning(nodes));
|
|
40
62
|
}
|
|
41
63
|
|
|
64
|
+
private resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
|
|
65
|
+
if (isTableSourceNode(source)) {
|
|
66
|
+
return source;
|
|
67
|
+
}
|
|
68
|
+
return { type: 'Table', name: source.name, schema: source.schema };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
|
|
72
|
+
if (typeof table === 'string') return table;
|
|
73
|
+
return this.resolveTableSource(table);
|
|
74
|
+
}
|
|
75
|
+
|
|
42
76
|
// Existing compiler-based compile stays, but we add a new overload.
|
|
43
77
|
|
|
44
78
|
// 1) Keep the old behavior (used internally / tests, if any):
|
|
@@ -65,3 +99,6 @@ export class UpdateQueryBuilder<T> {
|
|
|
65
99
|
return this.state.ast;
|
|
66
100
|
}
|
|
67
101
|
}
|
|
102
|
+
|
|
103
|
+
const isTableSourceNode = (source: TableDef | TableSourceNode): source is TableSourceNode =>
|
|
104
|
+
typeof (source as TableSourceNode).type === 'string';
|