metal-orm 1.1.3 → 1.1.4
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 +715 -703
- package/dist/index.cjs +655 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +170 -8
- package/dist/index.d.ts +170 -8
- package/dist/index.js +649 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/naming-strategy.mjs +16 -1
- package/src/core/ast/procedure.ts +21 -0
- package/src/core/ast/query.ts +47 -19
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/dialect/abstract.ts +560 -547
- package/src/core/dialect/base/sql-dialect.ts +43 -29
- package/src/core/dialect/mssql/index.ts +369 -232
- package/src/core/dialect/mysql/index.ts +99 -7
- package/src/core/dialect/postgres/index.ts +121 -60
- package/src/core/dialect/sqlite/index.ts +97 -64
- package/src/core/execution/db-executor.ts +108 -90
- package/src/core/execution/executors/mssql-executor.ts +28 -24
- package/src/core/execution/executors/mysql-executor.ts +62 -27
- package/src/core/execution/executors/sqlite-executor.ts +10 -9
- package/src/index.ts +9 -6
- package/src/orm/execute-procedure.ts +77 -0
- package/src/orm/execute.ts +74 -73
- package/src/orm/interceptor-pipeline.ts +21 -17
- package/src/orm/pooled-executor-factory.ts +41 -20
- package/src/orm/unit-of-work.ts +6 -4
- package/src/query/index.ts +8 -5
- package/src/query-builder/delete.ts +3 -2
- package/src/query-builder/insert-query-state.ts +47 -19
- package/src/query-builder/insert.ts +142 -28
- package/src/query-builder/procedure-call.ts +122 -0
- package/src/query-builder/select/select-operations.ts +5 -2
- package/src/query-builder/select.ts +1146 -1105
- package/src/query-builder/update.ts +3 -2
- package/src/tree/tree-manager.ts +754 -754
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// src/core/execution/db-executor.ts
|
|
2
|
-
|
|
3
|
-
// low-level canonical shape
|
|
1
|
+
// src/core/execution/db-executor.ts
|
|
2
|
+
|
|
3
|
+
// low-level canonical shape
|
|
4
4
|
export type QueryResult = {
|
|
5
5
|
columns: string[];
|
|
6
6
|
values: unknown[][];
|
|
@@ -10,98 +10,116 @@ export type QueryResult = {
|
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export interface DbExecutor {
|
|
14
|
-
/** Capability flags so the runtime can make correct decisions without relying on optional methods. */
|
|
15
|
-
readonly capabilities: {
|
|
16
|
-
/** True if begin/commit/rollback are real and should be used to provide atomicity. */
|
|
17
|
-
transactions: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
|
|
21
|
-
|
|
22
|
-
beginTransaction(): Promise<void>;
|
|
23
|
-
commitTransaction(): Promise<void>;
|
|
24
|
-
rollbackTransaction(): Promise<void>;
|
|
25
|
-
|
|
26
|
-
/** Release any underlying resources (connections, pool leases, etc). Must be idempotent. */
|
|
27
|
-
dispose(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// --- helpers ---
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Convert an array of row objects into a QueryResult.
|
|
34
|
-
*/
|
|
35
|
-
export function rowsToQueryResult(
|
|
36
|
-
rows: Array<Record<string, unknown>>
|
|
37
|
-
): QueryResult {
|
|
38
|
-
if (rows.length === 0) {
|
|
39
|
-
return { columns: [], values: [] };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const columns = Object.keys(rows[0]);
|
|
43
|
-
const values = rows.map(row => columns.map(c => row[c]));
|
|
44
|
-
return { columns, values };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
13
|
/**
|
|
48
|
-
*
|
|
14
|
+
* Canonical execution payload.
|
|
15
|
+
* It remains array-compatible for a gradual migration but always exposes
|
|
16
|
+
* `resultSets` for explicit multi-result handling.
|
|
49
17
|
*/
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
params?: unknown[]
|
|
54
|
-
): Promise<Array<Record<string, unknown>>>;
|
|
55
|
-
|
|
56
|
-
/** Optional: used to support real transactions. */
|
|
57
|
-
beginTransaction?(): Promise<void>;
|
|
58
|
-
commitTransaction?(): Promise<void>;
|
|
59
|
-
rollbackTransaction?(): Promise<void>;
|
|
60
|
-
|
|
61
|
-
/** Optional: release resources (connection close, pool lease release, etc). */
|
|
62
|
-
dispose?(): Promise<void>;
|
|
63
|
-
}
|
|
18
|
+
export type ExecutionPayload = QueryResult[] & {
|
|
19
|
+
resultSets?: QueryResult[];
|
|
20
|
+
};
|
|
64
21
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
): DbExecutor {
|
|
71
|
-
const supportsTransactions =
|
|
72
|
-
typeof runner.beginTransaction === 'function' &&
|
|
73
|
-
typeof runner.commitTransaction === 'function' &&
|
|
74
|
-
typeof runner.rollbackTransaction === 'function';
|
|
22
|
+
export const toExecutionPayload = (resultSets: QueryResult[]): ExecutionPayload => {
|
|
23
|
+
const payload = resultSets as ExecutionPayload;
|
|
24
|
+
payload.resultSets = resultSets;
|
|
25
|
+
return payload;
|
|
26
|
+
};
|
|
75
27
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
28
|
+
export const payloadResultSets = (payload: ExecutionPayload): QueryResult[] =>
|
|
29
|
+
payload.resultSets ?? payload;
|
|
30
|
+
|
|
31
|
+
export interface DbExecutor {
|
|
32
|
+
/** Capability flags so the runtime can make correct decisions without relying on optional methods. */
|
|
33
|
+
readonly capabilities: {
|
|
34
|
+
/** True if begin/commit/rollback are real and should be used to provide atomicity. */
|
|
35
|
+
transactions: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
executeSql(sql: string, params?: unknown[]): Promise<ExecutionPayload>;
|
|
39
|
+
|
|
40
|
+
beginTransaction(): Promise<void>;
|
|
41
|
+
commitTransaction(): Promise<void>;
|
|
42
|
+
rollbackTransaction(): Promise<void>;
|
|
43
|
+
|
|
44
|
+
/** Release any underlying resources (connections, pool leases, etc). Must be idempotent. */
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- helpers ---
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert an array of row objects into a QueryResult.
|
|
52
|
+
*/
|
|
53
|
+
export function rowsToQueryResult(
|
|
54
|
+
rows: Array<Record<string, unknown>>
|
|
55
|
+
): QueryResult {
|
|
56
|
+
if (rows.length === 0) {
|
|
57
|
+
return { columns: [], values: [] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const columns = Object.keys(rows[0]);
|
|
61
|
+
const values = rows.map(row => columns.map(c => row[c]));
|
|
62
|
+
return { columns, values };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Minimal contract that most SQL clients can implement.
|
|
67
|
+
*/
|
|
68
|
+
export interface SimpleQueryRunner {
|
|
69
|
+
query(
|
|
70
|
+
sql: string,
|
|
71
|
+
params?: unknown[]
|
|
72
|
+
): Promise<Array<Record<string, unknown>>>;
|
|
73
|
+
|
|
74
|
+
/** Optional: used to support real transactions. */
|
|
75
|
+
beginTransaction?(): Promise<void>;
|
|
76
|
+
commitTransaction?(): Promise<void>;
|
|
77
|
+
rollbackTransaction?(): Promise<void>;
|
|
78
|
+
|
|
79
|
+
/** Optional: release resources (connection close, pool lease release, etc). */
|
|
80
|
+
dispose?(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generic factory: turn any SimpleQueryRunner into a DbExecutor.
|
|
85
|
+
*/
|
|
86
|
+
export function createExecutorFromQueryRunner(
|
|
87
|
+
runner: SimpleQueryRunner
|
|
88
|
+
): DbExecutor {
|
|
89
|
+
const supportsTransactions =
|
|
90
|
+
typeof runner.beginTransaction === 'function' &&
|
|
91
|
+
typeof runner.commitTransaction === 'function' &&
|
|
92
|
+
typeof runner.rollbackTransaction === 'function';
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
capabilities: {
|
|
96
|
+
transactions: supportsTransactions,
|
|
97
|
+
},
|
|
80
98
|
async executeSql(sql, params) {
|
|
81
99
|
const rows = await runner.query(sql, params);
|
|
82
100
|
const result = rowsToQueryResult(rows);
|
|
83
|
-
return [result];
|
|
101
|
+
return toExecutionPayload([result]);
|
|
84
102
|
},
|
|
85
|
-
async beginTransaction() {
|
|
86
|
-
if (!supportsTransactions) {
|
|
87
|
-
throw new Error('Transactions are not supported by this executor');
|
|
88
|
-
}
|
|
89
|
-
await runner.beginTransaction!.call(runner);
|
|
90
|
-
},
|
|
91
|
-
async commitTransaction() {
|
|
92
|
-
if (!supportsTransactions) {
|
|
93
|
-
throw new Error('Transactions are not supported by this executor');
|
|
94
|
-
}
|
|
95
|
-
await runner.commitTransaction!.call(runner);
|
|
96
|
-
},
|
|
97
|
-
async rollbackTransaction() {
|
|
98
|
-
if (!supportsTransactions) {
|
|
99
|
-
throw new Error('Transactions are not supported by this executor');
|
|
100
|
-
}
|
|
101
|
-
await runner.rollbackTransaction!.call(runner);
|
|
102
|
-
},
|
|
103
|
-
async dispose() {
|
|
104
|
-
await runner.dispose?.call(runner);
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
103
|
+
async beginTransaction() {
|
|
104
|
+
if (!supportsTransactions) {
|
|
105
|
+
throw new Error('Transactions are not supported by this executor');
|
|
106
|
+
}
|
|
107
|
+
await runner.beginTransaction!.call(runner);
|
|
108
|
+
},
|
|
109
|
+
async commitTransaction() {
|
|
110
|
+
if (!supportsTransactions) {
|
|
111
|
+
throw new Error('Transactions are not supported by this executor');
|
|
112
|
+
}
|
|
113
|
+
await runner.commitTransaction!.call(runner);
|
|
114
|
+
},
|
|
115
|
+
async rollbackTransaction() {
|
|
116
|
+
if (!supportsTransactions) {
|
|
117
|
+
throw new Error('Transactions are not supported by this executor');
|
|
118
|
+
}
|
|
119
|
+
await runner.rollbackTransaction!.call(runner);
|
|
120
|
+
},
|
|
121
|
+
async dispose() {
|
|
122
|
+
await runner.dispose?.call(runner);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
// src/core/execution/executors/mssql-executor.ts
|
|
2
|
-
import {
|
|
3
|
-
DbExecutor,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
toExecutionPayload,
|
|
5
|
+
rowsToQueryResult
|
|
6
|
+
} from '../db-executor.js';
|
|
7
|
+
|
|
8
|
+
export interface MssqlClientLike {
|
|
9
|
+
query(
|
|
10
|
+
sql: string,
|
|
11
|
+
params?: unknown[]
|
|
12
|
+
): Promise<{
|
|
13
|
+
recordset?: Array<Record<string, unknown>>;
|
|
14
|
+
recordsets?: Array<Array<Record<string, unknown>>>;
|
|
15
|
+
}>;
|
|
16
|
+
beginTransaction?(): Promise<void>;
|
|
17
|
+
commit?(): Promise<void>;
|
|
18
|
+
rollback?(): Promise<void>;
|
|
19
|
+
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Creates a database executor for Microsoft SQL Server.
|
|
@@ -28,14 +32,14 @@ export function createMssqlExecutor(
|
|
|
28
32
|
typeof client.rollback === 'function';
|
|
29
33
|
|
|
30
34
|
return {
|
|
31
|
-
capabilities: {
|
|
32
|
-
transactions: supportsTransactions,
|
|
33
|
-
},
|
|
34
|
-
async executeSql(sql, params) {
|
|
35
|
-
const { recordset } = await client.query(sql, params);
|
|
36
|
-
const
|
|
37
|
-
return [
|
|
38
|
-
},
|
|
35
|
+
capabilities: {
|
|
36
|
+
transactions: supportsTransactions,
|
|
37
|
+
},
|
|
38
|
+
async executeSql(sql, params) {
|
|
39
|
+
const { recordset, recordsets } = await client.query(sql, params);
|
|
40
|
+
const sets = Array.isArray(recordsets) ? recordsets : [recordset ?? []];
|
|
41
|
+
return toExecutionPayload(sets.map(set => rowsToQueryResult(set ?? [])));
|
|
42
|
+
},
|
|
39
43
|
async beginTransaction() {
|
|
40
44
|
if (!supportsTransactions) {
|
|
41
45
|
throw new Error('Transactions are not supported by this executor');
|
|
@@ -157,8 +161,8 @@ export function createTediousMssqlClient(
|
|
|
157
161
|
}
|
|
158
162
|
);
|
|
159
163
|
|
|
160
|
-
return { recordset: rows };
|
|
161
|
-
},
|
|
164
|
+
return { recordset: rows, recordsets: [rows] };
|
|
165
|
+
},
|
|
162
166
|
|
|
163
167
|
beginTransaction: connection.beginTransaction
|
|
164
168
|
? () =>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// src/core/execution/executors/mysql-executor.ts
|
|
2
|
-
import {
|
|
3
|
-
DbExecutor,
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
QueryResult,
|
|
5
|
+
toExecutionPayload,
|
|
6
|
+
rowsToQueryResult
|
|
7
|
+
} from '../db-executor.js';
|
|
6
8
|
|
|
7
|
-
export interface MysqlClientLike {
|
|
9
|
+
export interface MysqlClientLike {
|
|
8
10
|
query(
|
|
9
11
|
sql: string,
|
|
10
12
|
params?: unknown[]
|
|
@@ -12,7 +14,56 @@ export interface MysqlClientLike {
|
|
|
12
14
|
beginTransaction?(): Promise<void>;
|
|
13
15
|
commit?(): Promise<void>;
|
|
14
16
|
rollback?(): Promise<void>;
|
|
15
|
-
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type RowObject = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
const isRowObject = (value: unknown): value is RowObject =>
|
|
22
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
|
|
24
|
+
const isRowObjectArray = (value: unknown): value is RowObject[] =>
|
|
25
|
+
Array.isArray(value) && value.every(isRowObject);
|
|
26
|
+
|
|
27
|
+
const isMysqlResultHeader = (value: unknown): value is Record<string, unknown> =>
|
|
28
|
+
isRowObject(value) &&
|
|
29
|
+
('affectedRows' in value ||
|
|
30
|
+
'insertId' in value ||
|
|
31
|
+
'warningStatus' in value ||
|
|
32
|
+
'serverStatus' in value);
|
|
33
|
+
|
|
34
|
+
const headerToQueryResult = (header: Record<string, unknown>): QueryResult => ({
|
|
35
|
+
columns: [],
|
|
36
|
+
values: [],
|
|
37
|
+
meta: {
|
|
38
|
+
insertId: header.insertId as number | string | undefined,
|
|
39
|
+
rowsAffected: header.affectedRows as number | undefined,
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const normalizeMysqlResults = (rows: unknown): QueryResult[] => {
|
|
44
|
+
if (!Array.isArray(rows)) {
|
|
45
|
+
return isMysqlResultHeader(rows)
|
|
46
|
+
? [headerToQueryResult(rows)]
|
|
47
|
+
: [rowsToQueryResult([])];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isRowObjectArray(rows)) {
|
|
51
|
+
return [rowsToQueryResult(rows)];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const normalized: QueryResult[] = [];
|
|
55
|
+
for (const chunk of rows) {
|
|
56
|
+
if (isRowObjectArray(chunk)) {
|
|
57
|
+
normalized.push(rowsToQueryResult(chunk));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (isMysqlResultHeader(chunk)) {
|
|
61
|
+
normalized.push(headerToQueryResult(chunk));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return normalized.length ? normalized : [rowsToQueryResult([])];
|
|
66
|
+
};
|
|
16
67
|
|
|
17
68
|
/**
|
|
18
69
|
* Creates a database executor for MySQL.
|
|
@@ -30,27 +81,11 @@ export function createMysqlExecutor(
|
|
|
30
81
|
return {
|
|
31
82
|
capabilities: {
|
|
32
83
|
transactions: supportsTransactions,
|
|
33
|
-
},
|
|
34
|
-
async executeSql(sql, params) {
|
|
35
|
-
const [rows] = await client.query(sql, params);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const header = rows as Record<string, unknown>;
|
|
39
|
-
return [{
|
|
40
|
-
columns: [],
|
|
41
|
-
values: [],
|
|
42
|
-
meta: {
|
|
43
|
-
insertId: header.insertId as number | undefined,
|
|
44
|
-
rowsAffected: header.affectedRows as number | undefined,
|
|
45
|
-
}
|
|
46
|
-
}];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const result = rowsToQueryResult(
|
|
50
|
-
rows as Array<Record<string, unknown>>
|
|
51
|
-
);
|
|
52
|
-
return [result];
|
|
53
|
-
},
|
|
84
|
+
},
|
|
85
|
+
async executeSql(sql, params) {
|
|
86
|
+
const [rows] = await client.query(sql, params);
|
|
87
|
+
return toExecutionPayload(normalizeMysqlResults(rows));
|
|
88
|
+
},
|
|
54
89
|
async beginTransaction() {
|
|
55
90
|
if (!supportsTransactions) {
|
|
56
91
|
throw new Error('Transactions are not supported by this executor');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/core/execution/executors/sqlite-executor.ts
|
|
2
|
-
import {
|
|
3
|
-
DbExecutor,
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
toExecutionPayload,
|
|
5
|
+
rowsToQueryResult
|
|
6
|
+
} from '../db-executor.js';
|
|
6
7
|
|
|
7
8
|
export interface SqliteClientLike {
|
|
8
9
|
all(
|
|
@@ -32,11 +33,11 @@ export function createSqliteExecutor(
|
|
|
32
33
|
capabilities: {
|
|
33
34
|
transactions: supportsTransactions,
|
|
34
35
|
},
|
|
35
|
-
async executeSql(sql, params) {
|
|
36
|
-
const rows = await client.all(sql, params);
|
|
37
|
-
const result = rowsToQueryResult(rows);
|
|
38
|
-
return [result];
|
|
39
|
-
},
|
|
36
|
+
async executeSql(sql, params) {
|
|
37
|
+
const rows = await client.all(sql, params);
|
|
38
|
+
const result = rowsToQueryResult(rows);
|
|
39
|
+
return toExecutionPayload([result]);
|
|
40
|
+
},
|
|
40
41
|
async beginTransaction() {
|
|
41
42
|
if (!supportsTransactions) {
|
|
42
43
|
throw new Error('Transactions are not supported by this executor');
|
package/src/index.ts
CHANGED
|
@@ -9,11 +9,13 @@ export * from './schema/types.js';
|
|
|
9
9
|
export * from './query-builder/select.js';
|
|
10
10
|
export * from './query-builder/select-helpers.js';
|
|
11
11
|
export * from './query-builder/insert.js';
|
|
12
|
-
export * from './query-builder/update.js';
|
|
13
|
-
export * from './query-builder/delete.js';
|
|
14
|
-
export * from './query/
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './core/ast/
|
|
12
|
+
export * from './query-builder/update.js';
|
|
13
|
+
export * from './query-builder/delete.js';
|
|
14
|
+
export * from './query-builder/procedure-call.js';
|
|
15
|
+
export * from './query/index.js';
|
|
16
|
+
export * from './core/ast/expression.js';
|
|
17
|
+
export * from './core/ast/procedure.js';
|
|
18
|
+
export * from './core/ast/window-functions.js';
|
|
17
19
|
export * from './core/hydration/types.js';
|
|
18
20
|
export * from './core/dialect/mysql/index.js';
|
|
19
21
|
export * from './core/dialect/mssql/index.js';
|
|
@@ -41,7 +43,8 @@ export * from './orm/lazy-batch.js';
|
|
|
41
43
|
export * from './orm/relations/has-many.js';
|
|
42
44
|
export * from './orm/relations/belongs-to.js';
|
|
43
45
|
export * from './orm/relations/many-to-many.js';
|
|
44
|
-
export * from './orm/execute.js';
|
|
46
|
+
export * from './orm/execute.js';
|
|
47
|
+
export * from './orm/execute-procedure.js';
|
|
45
48
|
export type { EntityContext } from './orm/entity-context.js';
|
|
46
49
|
export type { PrimaryKey as EntityPrimaryKey } from './orm/entity-context.js';
|
|
47
50
|
export * from './orm/execution-context.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ProcedureCallNode } from '../core/ast/procedure.js';
|
|
2
|
+
import type { QueryResult } from '../core/execution/db-executor.js';
|
|
3
|
+
import { payloadResultSets } from '../core/execution/db-executor.js';
|
|
4
|
+
import type { CompiledProcedureCall } from '../core/dialect/abstract.js';
|
|
5
|
+
import type { OrmSession } from './orm-session.js';
|
|
6
|
+
|
|
7
|
+
export interface ProcedureExecutionResult {
|
|
8
|
+
resultSets: QueryResult[];
|
|
9
|
+
out: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const resolveColumnIndex = (columns: string[], expectedName: string): number => {
|
|
13
|
+
const exact = columns.findIndex(column => column === expectedName);
|
|
14
|
+
if (exact >= 0) return exact;
|
|
15
|
+
|
|
16
|
+
const lowerExpected = expectedName.toLowerCase();
|
|
17
|
+
return columns.findIndex(column => column.toLowerCase() === lowerExpected);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const extractOutValues = (
|
|
21
|
+
compiled: CompiledProcedureCall,
|
|
22
|
+
resultSets: QueryResult[]
|
|
23
|
+
): Record<string, unknown> => {
|
|
24
|
+
if (!compiled.outParams.names.length || compiled.outParams.source === 'none') {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sourceSet =
|
|
29
|
+
compiled.outParams.source === 'firstResultSet'
|
|
30
|
+
? resultSets[0]
|
|
31
|
+
: resultSets[resultSets.length - 1];
|
|
32
|
+
|
|
33
|
+
if (!sourceSet) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Procedure expected OUT parameters in ${compiled.outParams.source}, but no result set was returned.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!sourceSet.values.length) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Procedure expected OUT parameters in ${compiled.outParams.source}, but the result set has no rows.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const firstRow = sourceSet.values[0];
|
|
46
|
+
const out: Record<string, unknown> = {};
|
|
47
|
+
for (const expectedName of compiled.outParams.names) {
|
|
48
|
+
const columnIndex = resolveColumnIndex(sourceSet.columns, expectedName);
|
|
49
|
+
if (columnIndex < 0) {
|
|
50
|
+
const available = sourceSet.columns.length ? sourceSet.columns.join(', ') : '(none)';
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Procedure OUT parameter "${expectedName}" was not found in ${compiled.outParams.source}. ` +
|
|
53
|
+
`Available columns: ${available}.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
out[expectedName] = firstRow[columnIndex];
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const executeProcedureAst = async (
|
|
62
|
+
session: OrmSession,
|
|
63
|
+
ast: ProcedureCallNode
|
|
64
|
+
): Promise<ProcedureExecutionResult> => {
|
|
65
|
+
const execCtx = session.getExecutionContext();
|
|
66
|
+
const compiled = execCtx.dialect.compileProcedureCall(ast);
|
|
67
|
+
const payload = await execCtx.interceptors.run(
|
|
68
|
+
{ sql: compiled.sql, params: compiled.params },
|
|
69
|
+
execCtx.executor
|
|
70
|
+
);
|
|
71
|
+
const resultSets = payloadResultSets(payload);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
resultSets,
|
|
75
|
+
out: extractOutValues(compiled, resultSets)
|
|
76
|
+
};
|
|
77
|
+
};
|