metal-orm 1.0.83 → 1.0.86
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 +299 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -13
- package/dist/index.d.ts +28 -13
- package/dist/index.js +297 -233
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/ast-validation.ts +11 -180
- package/src/core/ast/expression-nodes.ts +35 -25
- package/src/core/ast/expression-visitor.ts +53 -25
- package/src/core/ast/query-visitor.ts +273 -0
- package/src/core/dialect/abstract.ts +11 -7
- package/src/orm/execute.ts +60 -46
- package/src/query-builder/query-ast-service.ts +13 -0
- package/src/query-builder/select/select-operations.ts +32 -24
- package/src/query-builder/select.ts +49 -35
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommonTableExpressionNode,
|
|
3
|
+
DerivedTableNode,
|
|
4
|
+
FunctionTableNode,
|
|
5
|
+
OrderByNode,
|
|
6
|
+
OrderingTerm,
|
|
7
|
+
SelectQueryNode,
|
|
8
|
+
SetOperationNode,
|
|
9
|
+
TableSourceNode
|
|
10
|
+
} from './query.js';
|
|
11
|
+
import type { JoinNode } from './join.js';
|
|
12
|
+
import type {
|
|
13
|
+
ExpressionNode,
|
|
14
|
+
OperandNode,
|
|
15
|
+
ParamNode
|
|
16
|
+
} from './expression-nodes.js';
|
|
17
|
+
import { isOperandNode } from './expression-nodes.js';
|
|
18
|
+
import {
|
|
19
|
+
hasOperandDispatcher,
|
|
20
|
+
type ExpressionVisitor,
|
|
21
|
+
type OperandVisitor,
|
|
22
|
+
visitExpression,
|
|
23
|
+
visitOperand
|
|
24
|
+
} from './expression-visitor.js';
|
|
25
|
+
|
|
26
|
+
export interface SelectQueryVisitor {
|
|
27
|
+
visitSelectQuery?(node: SelectQueryNode): void;
|
|
28
|
+
visitTableSource?(node: TableSourceNode): void;
|
|
29
|
+
visitDerivedTable?(node: DerivedTableNode): void;
|
|
30
|
+
visitFunctionTable?(node: FunctionTableNode): void;
|
|
31
|
+
visitJoin?(node: JoinNode): void;
|
|
32
|
+
visitCte?(node: CommonTableExpressionNode): void;
|
|
33
|
+
visitSetOperation?(node: SetOperationNode): void;
|
|
34
|
+
visitOrderBy?(node: OrderByNode): void;
|
|
35
|
+
visitExpression?(node: ExpressionNode): void;
|
|
36
|
+
visitOperand?(node: OperandNode): void;
|
|
37
|
+
visitParam?(node: ParamNode): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const getNodeType = (value: unknown): string | undefined => {
|
|
41
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
42
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, 'type');
|
|
43
|
+
if (descriptor && typeof descriptor.value === 'string') {
|
|
44
|
+
return descriptor.value;
|
|
45
|
+
}
|
|
46
|
+
if ('type' in value) {
|
|
47
|
+
const type = (value as { type?: unknown }).type;
|
|
48
|
+
return typeof type === 'string' ? type : undefined;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const visitSelectQuery = (ast: SelectQueryNode, visitor: SelectQueryVisitor): void => {
|
|
54
|
+
const visitExpressionNode = (node: ExpressionNode): void => {
|
|
55
|
+
visitExpression(node, expressionVisitor);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const visitOperandNode = (node: OperandNode): void => {
|
|
59
|
+
visitOperand(node, operandVisitor);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const visitOrderingTerm = (term: OrderingTerm): void => {
|
|
63
|
+
if (!term || typeof term !== 'object') return;
|
|
64
|
+
if (isOperandNode(term)) {
|
|
65
|
+
visitOperandNode(term);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const type = getNodeType(term);
|
|
69
|
+
if (type && hasOperandDispatcher(type)) {
|
|
70
|
+
visitOperandNode(term as unknown as OperandNode);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (type) {
|
|
74
|
+
visitExpressionNode(term as ExpressionNode);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const visitOrderByNode = (node: OrderByNode): void => {
|
|
79
|
+
visitor.visitOrderBy?.(node);
|
|
80
|
+
visitOrderingTerm(node.term);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const visitTableSource = (source: TableSourceNode): void => {
|
|
84
|
+
visitor.visitTableSource?.(source);
|
|
85
|
+
if (source.type === 'DerivedTable') {
|
|
86
|
+
visitor.visitDerivedTable?.(source);
|
|
87
|
+
visitSelectQuery(source.query, visitor);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (source.type === 'FunctionTable') {
|
|
91
|
+
visitor.visitFunctionTable?.(source);
|
|
92
|
+
source.args?.forEach(arg => visitOperandNode(arg));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const expressionVisitor: ExpressionVisitor<void> = {
|
|
97
|
+
visitBinaryExpression: (node) => {
|
|
98
|
+
visitor.visitExpression?.(node);
|
|
99
|
+
visitOperandNode(node.left);
|
|
100
|
+
visitOperandNode(node.right);
|
|
101
|
+
if (node.escape) {
|
|
102
|
+
visitOperandNode(node.escape);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
visitLogicalExpression: (node) => {
|
|
106
|
+
visitor.visitExpression?.(node);
|
|
107
|
+
node.operands.forEach(operand => visitExpressionNode(operand));
|
|
108
|
+
},
|
|
109
|
+
visitNullExpression: (node) => {
|
|
110
|
+
visitor.visitExpression?.(node);
|
|
111
|
+
visitOperandNode(node.left);
|
|
112
|
+
},
|
|
113
|
+
visitInExpression: (node) => {
|
|
114
|
+
visitor.visitExpression?.(node);
|
|
115
|
+
visitOperandNode(node.left);
|
|
116
|
+
if (Array.isArray(node.right)) {
|
|
117
|
+
node.right.forEach(operand => visitOperandNode(operand));
|
|
118
|
+
} else {
|
|
119
|
+
visitOperandNode(node.right);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
visitExistsExpression: (node) => {
|
|
123
|
+
visitor.visitExpression?.(node);
|
|
124
|
+
visitSelectQuery(node.subquery, visitor);
|
|
125
|
+
},
|
|
126
|
+
visitBetweenExpression: (node) => {
|
|
127
|
+
visitor.visitExpression?.(node);
|
|
128
|
+
visitOperandNode(node.left);
|
|
129
|
+
visitOperandNode(node.lower);
|
|
130
|
+
visitOperandNode(node.upper);
|
|
131
|
+
},
|
|
132
|
+
visitArithmeticExpression: (node) => {
|
|
133
|
+
visitor.visitExpression?.(node);
|
|
134
|
+
visitOperandNode(node.left);
|
|
135
|
+
visitOperandNode(node.right);
|
|
136
|
+
},
|
|
137
|
+
visitBitwiseExpression: (node) => {
|
|
138
|
+
visitor.visitExpression?.(node);
|
|
139
|
+
visitOperandNode(node.left);
|
|
140
|
+
visitOperandNode(node.right);
|
|
141
|
+
},
|
|
142
|
+
visitOperand: (node) => {
|
|
143
|
+
visitOperandNode(node);
|
|
144
|
+
},
|
|
145
|
+
visitSelectQuery: (node) => {
|
|
146
|
+
visitSelectQuery(node, visitor);
|
|
147
|
+
},
|
|
148
|
+
otherwise: (node) => {
|
|
149
|
+
visitor.visitExpression?.(node);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const operandVisitor: OperandVisitor<void> = {
|
|
154
|
+
visitColumn: (node) => {
|
|
155
|
+
visitor.visitOperand?.(node);
|
|
156
|
+
},
|
|
157
|
+
visitLiteral: (node) => {
|
|
158
|
+
visitor.visitOperand?.(node);
|
|
159
|
+
},
|
|
160
|
+
visitParam: (node) => {
|
|
161
|
+
visitor.visitOperand?.(node);
|
|
162
|
+
visitor.visitParam?.(node);
|
|
163
|
+
},
|
|
164
|
+
visitFunction: (node) => {
|
|
165
|
+
visitor.visitOperand?.(node);
|
|
166
|
+
node.args?.forEach(arg => visitOperandNode(arg));
|
|
167
|
+
node.orderBy?.forEach(order => visitOrderByNode(order));
|
|
168
|
+
if (node.separator) {
|
|
169
|
+
visitOperandNode(node.separator);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
visitJsonPath: (node) => {
|
|
173
|
+
visitor.visitOperand?.(node);
|
|
174
|
+
visitOperandNode(node.column);
|
|
175
|
+
},
|
|
176
|
+
visitScalarSubquery: (node) => {
|
|
177
|
+
visitor.visitOperand?.(node);
|
|
178
|
+
visitSelectQuery(node.query, visitor);
|
|
179
|
+
},
|
|
180
|
+
visitCaseExpression: (node) => {
|
|
181
|
+
visitor.visitOperand?.(node);
|
|
182
|
+
node.conditions.forEach(cond => {
|
|
183
|
+
visitExpressionNode(cond.when);
|
|
184
|
+
visitOperandNode(cond.then);
|
|
185
|
+
});
|
|
186
|
+
if (node.else) {
|
|
187
|
+
visitOperandNode(node.else);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
visitCast: (node) => {
|
|
191
|
+
visitor.visitOperand?.(node);
|
|
192
|
+
visitOperandNode(node.expression);
|
|
193
|
+
},
|
|
194
|
+
visitWindowFunction: (node) => {
|
|
195
|
+
visitor.visitOperand?.(node);
|
|
196
|
+
node.args?.forEach(arg => visitOperandNode(arg));
|
|
197
|
+
node.partitionBy?.forEach(term => visitOperandNode(term));
|
|
198
|
+
node.orderBy?.forEach(order => visitOrderByNode(order));
|
|
199
|
+
},
|
|
200
|
+
visitArithmeticExpression: (node) => {
|
|
201
|
+
visitor.visitOperand?.(node);
|
|
202
|
+
visitOperandNode(node.left);
|
|
203
|
+
visitOperandNode(node.right);
|
|
204
|
+
},
|
|
205
|
+
visitBitwiseExpression: (node) => {
|
|
206
|
+
visitor.visitOperand?.(node);
|
|
207
|
+
visitOperandNode(node.left);
|
|
208
|
+
visitOperandNode(node.right);
|
|
209
|
+
},
|
|
210
|
+
visitExpression: (node) => {
|
|
211
|
+
visitExpressionNode(node);
|
|
212
|
+
},
|
|
213
|
+
visitSelectQuery: (node) => {
|
|
214
|
+
visitSelectQuery(node, visitor);
|
|
215
|
+
},
|
|
216
|
+
visitCollate: (node) => {
|
|
217
|
+
visitor.visitOperand?.(node);
|
|
218
|
+
visitOperandNode(node.expression);
|
|
219
|
+
},
|
|
220
|
+
visitAliasRef: (node) => {
|
|
221
|
+
visitor.visitOperand?.(node);
|
|
222
|
+
},
|
|
223
|
+
otherwise: (node) => {
|
|
224
|
+
visitor.visitOperand?.(node);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
visitor.visitSelectQuery?.(ast);
|
|
229
|
+
|
|
230
|
+
if (ast.ctes) {
|
|
231
|
+
for (const cte of ast.ctes) {
|
|
232
|
+
visitor.visitCte?.(cte);
|
|
233
|
+
visitSelectQuery(cte.query, visitor);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
visitTableSource(ast.from);
|
|
238
|
+
|
|
239
|
+
ast.columns?.forEach(col => {
|
|
240
|
+
visitOperandNode(col as OperandNode);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
ast.joins?.forEach(join => {
|
|
244
|
+
visitor.visitJoin?.(join);
|
|
245
|
+
visitTableSource(join.table);
|
|
246
|
+
visitExpressionNode(join.condition);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (ast.where) {
|
|
250
|
+
visitExpressionNode(ast.where);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
ast.groupBy?.forEach(term => {
|
|
254
|
+
visitOrderingTerm(term);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (ast.having) {
|
|
258
|
+
visitExpressionNode(ast.having);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
ast.orderBy?.forEach(order => {
|
|
262
|
+
visitOrderByNode(order);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
ast.distinct?.forEach(col => {
|
|
266
|
+
visitOperandNode(col);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
ast.setOps?.forEach(op => {
|
|
270
|
+
visitor.visitSetOperation?.(op);
|
|
271
|
+
visitSelectQuery(op.query, visitor);
|
|
272
|
+
});
|
|
273
|
+
};
|
|
@@ -387,13 +387,17 @@ export abstract class Dialect
|
|
|
387
387
|
* @param ctx - Compiler context
|
|
388
388
|
* @returns Compiled SQL operand
|
|
389
389
|
*/
|
|
390
|
-
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
390
|
+
protected compileOperand(node: OperandNode, ctx: CompilerContext): string {
|
|
391
|
+
const descriptor = Object.getOwnPropertyDescriptor(node, 'type');
|
|
392
|
+
const nodeType = typeof descriptor?.value === 'string'
|
|
393
|
+
? descriptor.value
|
|
394
|
+
: (typeof node.type === 'string' ? node.type : undefined);
|
|
395
|
+
const compiler = nodeType ? this.operandCompilers.get(nodeType) : undefined;
|
|
396
|
+
if (!compiler) {
|
|
397
|
+
throw new Error(`Unsupported operand node type "${nodeType ?? 'unknown'}" for ${this.constructor.name}`);
|
|
398
|
+
}
|
|
399
|
+
return compiler(node, ctx);
|
|
400
|
+
}
|
|
397
401
|
|
|
398
402
|
/**
|
|
399
403
|
* Compiles an ordering term (operand, expression, or alias reference).
|
package/src/orm/execute.ts
CHANGED
|
@@ -22,9 +22,13 @@ import {
|
|
|
22
22
|
loadBelongsToManyRelation
|
|
23
23
|
} from './lazy-batch.js';
|
|
24
24
|
|
|
25
|
-
type Row = Record<string, unknown>;
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
type Row = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
type ParamOperandOptions = {
|
|
28
|
+
allowParamOperands?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const flattenResults = (results: { columns: string[]; values: unknown[][] }[]): Row[] => {
|
|
28
32
|
const rows: Row[] = [];
|
|
29
33
|
for (const result of results) {
|
|
30
34
|
const { columns, values } = result;
|
|
@@ -39,13 +43,16 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
|
|
|
39
43
|
return rows;
|
|
40
44
|
};
|
|
41
45
|
|
|
42
|
-
const executeWithContexts = async <TTable extends TableDef>(
|
|
43
|
-
execCtx: ExecutionContext,
|
|
44
|
-
entityCtx: EntityContext,
|
|
45
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
46
|
+
const executeWithContexts = async <TTable extends TableDef>(
|
|
47
|
+
execCtx: ExecutionContext,
|
|
48
|
+
entityCtx: EntityContext,
|
|
49
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
50
|
+
options?: ParamOperandOptions
|
|
51
|
+
): Promise<EntityInstance<TTable>[]> => {
|
|
52
|
+
const ast = qb.getAST();
|
|
53
|
+
const compiled = options?.allowParamOperands
|
|
54
|
+
? execCtx.dialect.compileSelectWithOptions(ast, { allowParams: true })
|
|
55
|
+
: execCtx.dialect.compileSelect(ast);
|
|
49
56
|
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
50
57
|
const rows = flattenResults(executed);
|
|
51
58
|
const lazyRelations = qb.getLazyRelations() as RelationKey<TTable>[];
|
|
@@ -66,13 +73,16 @@ const executeWithContexts = async <TTable extends TableDef>(
|
|
|
66
73
|
return entities;
|
|
67
74
|
};
|
|
68
75
|
|
|
69
|
-
const executePlainWithContexts = async <TTable extends TableDef>(
|
|
70
|
-
execCtx: ExecutionContext,
|
|
71
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
+
const executePlainWithContexts = async <TTable extends TableDef>(
|
|
77
|
+
execCtx: ExecutionContext,
|
|
78
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
79
|
+
options?: ParamOperandOptions
|
|
80
|
+
): Promise<Record<string, unknown>[]> => {
|
|
81
|
+
const ast = qb.getAST();
|
|
82
|
+
const compiled = options?.allowParamOperands
|
|
83
|
+
? execCtx.dialect.compileSelectWithOptions(ast, { allowParams: true })
|
|
84
|
+
: execCtx.dialect.compileSelect(ast);
|
|
85
|
+
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
76
86
|
const rows = flattenResults(executed);
|
|
77
87
|
|
|
78
88
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
@@ -89,12 +99,13 @@ const executePlainWithContexts = async <TTable extends TableDef>(
|
|
|
89
99
|
* @param qb - The select query builder
|
|
90
100
|
* @returns Promise resolving to array of entity instances
|
|
91
101
|
*/
|
|
92
|
-
export async function executeHydrated<TTable extends TableDef>(
|
|
93
|
-
session: OrmSession,
|
|
94
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
export async function executeHydrated<TTable extends TableDef>(
|
|
103
|
+
session: OrmSession,
|
|
104
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
105
|
+
options?: ParamOperandOptions
|
|
106
|
+
): Promise<EntityInstance<TTable>[]> {
|
|
107
|
+
return executeWithContexts(session.getExecutionContext(), session, qb, options);
|
|
108
|
+
}
|
|
98
109
|
|
|
99
110
|
/**
|
|
100
111
|
* Executes a hydrated query and returns plain row objects (no entity proxies).
|
|
@@ -103,12 +114,13 @@ export async function executeHydrated<TTable extends TableDef>(
|
|
|
103
114
|
* @param qb - The select query builder
|
|
104
115
|
* @returns Promise resolving to array of plain row objects
|
|
105
116
|
*/
|
|
106
|
-
export async function executeHydratedPlain<TTable extends TableDef>(
|
|
107
|
-
session: OrmSession,
|
|
108
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
export async function executeHydratedPlain<TTable extends TableDef>(
|
|
118
|
+
session: OrmSession,
|
|
119
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
120
|
+
options?: ParamOperandOptions
|
|
121
|
+
): Promise<Record<string, unknown>[]> {
|
|
122
|
+
return executePlainWithContexts(session.getExecutionContext(), qb, options);
|
|
123
|
+
}
|
|
112
124
|
|
|
113
125
|
/**
|
|
114
126
|
* Executes a hydrated query using execution and hydration contexts.
|
|
@@ -118,17 +130,18 @@ export async function executeHydratedPlain<TTable extends TableDef>(
|
|
|
118
130
|
* @param qb - The select query builder
|
|
119
131
|
* @returns Promise resolving to array of entity instances
|
|
120
132
|
*/
|
|
121
|
-
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
122
|
-
execCtx: ExecutionContext,
|
|
123
|
-
hydCtx: HydrationContext,
|
|
124
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
134
|
+
execCtx: ExecutionContext,
|
|
135
|
+
hydCtx: HydrationContext,
|
|
136
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
137
|
+
options?: ParamOperandOptions
|
|
138
|
+
): Promise<EntityInstance<TTable>[]> {
|
|
139
|
+
const entityCtx = hydCtx.entityContext;
|
|
140
|
+
if (!entityCtx) {
|
|
141
|
+
throw new Error('Hydration context is missing an EntityContext');
|
|
142
|
+
}
|
|
143
|
+
return executeWithContexts(execCtx, entityCtx, qb, options);
|
|
144
|
+
}
|
|
132
145
|
|
|
133
146
|
/**
|
|
134
147
|
* Executes a hydrated query using execution context and returns plain row objects.
|
|
@@ -137,12 +150,13 @@ export async function executeHydratedWithContexts<TTable extends TableDef>(
|
|
|
137
150
|
* @param qb - The select query builder
|
|
138
151
|
* @returns Promise resolving to array of plain row objects
|
|
139
152
|
*/
|
|
140
|
-
export async function executeHydratedPlainWithContexts<TTable extends TableDef>(
|
|
141
|
-
execCtx: ExecutionContext,
|
|
142
|
-
qb: SelectQueryBuilder<unknown, TTable
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
export async function executeHydratedPlainWithContexts<TTable extends TableDef>(
|
|
154
|
+
execCtx: ExecutionContext,
|
|
155
|
+
qb: SelectQueryBuilder<unknown, TTable>,
|
|
156
|
+
options?: ParamOperandOptions
|
|
157
|
+
): Promise<Record<string, unknown>[]> {
|
|
158
|
+
return executePlainWithContexts(execCtx, qb, options);
|
|
159
|
+
}
|
|
146
160
|
|
|
147
161
|
const loadLazyRelationsForTable = async <TTable extends TableDef>(
|
|
148
162
|
ctx: EntityContext,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
CastExpressionNode,
|
|
19
19
|
WindowFunctionNode,
|
|
20
20
|
ScalarSubqueryNode,
|
|
21
|
+
ParamNode,
|
|
21
22
|
and,
|
|
22
23
|
isExpressionSelectionNode,
|
|
23
24
|
isOperandNode
|
|
@@ -258,6 +259,10 @@ export class QueryAstService {
|
|
|
258
259
|
* @returns Normalized ordering term
|
|
259
260
|
*/
|
|
260
261
|
private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
|
|
262
|
+
const paramNode = this.toParamNode(term);
|
|
263
|
+
if (paramNode) {
|
|
264
|
+
return paramNode;
|
|
265
|
+
}
|
|
261
266
|
const from = this.state.ast.from;
|
|
262
267
|
const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
|
|
263
268
|
const termType = (term as { type?: string }).type;
|
|
@@ -284,4 +289,12 @@ export class QueryAstService {
|
|
|
284
289
|
return buildColumnNode(tableRef, term as ColumnDef);
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
private toParamNode(value: unknown): ParamNode | undefined {
|
|
293
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
294
|
+
const type = Object.getOwnPropertyDescriptor(value, 'type')?.value;
|
|
295
|
+
if (type !== 'Param') return undefined;
|
|
296
|
+
const name = Object.getOwnPropertyDescriptor(value, 'name')?.value;
|
|
297
|
+
if (typeof name !== 'string') return undefined;
|
|
298
|
+
return { type: 'Param', name };
|
|
299
|
+
}
|
|
287
300
|
}
|
|
@@ -11,10 +11,14 @@ import { ORDER_DIRECTIONS, OrderDirection } from '../../core/sql/sql.js';
|
|
|
11
11
|
import { OrmSession } from '../../orm/orm-session.js';
|
|
12
12
|
import type { SelectQueryBuilder } from '../select.js';
|
|
13
13
|
|
|
14
|
-
export type WhereHasOptions = {
|
|
15
|
-
correlate?: ExpressionNode;
|
|
16
|
-
};
|
|
17
|
-
|
|
14
|
+
export type WhereHasOptions = {
|
|
15
|
+
correlate?: ExpressionNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ParamOperandOptions = {
|
|
19
|
+
allowParamOperands?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
18
22
|
export type RelationCallback = <TChildTable extends TableDef>(
|
|
19
23
|
qb: SelectQueryBuilder<unknown, TChildTable>
|
|
20
24
|
) => SelectQueryBuilder<unknown, TChildTable>;
|
|
@@ -39,11 +43,12 @@ export function applyOrderBy(
|
|
|
39
43
|
/**
|
|
40
44
|
* Runs the count query for the provided context and session.
|
|
41
45
|
*/
|
|
42
|
-
export async function executeCount(
|
|
43
|
-
context: SelectQueryBuilderContext,
|
|
44
|
-
env: SelectQueryBuilderEnvironment,
|
|
45
|
-
session: OrmSession
|
|
46
|
-
|
|
46
|
+
export async function executeCount(
|
|
47
|
+
context: SelectQueryBuilderContext,
|
|
48
|
+
env: SelectQueryBuilderEnvironment,
|
|
49
|
+
session: OrmSession,
|
|
50
|
+
options?: ParamOperandOptions
|
|
51
|
+
): Promise<number> {
|
|
47
52
|
const unpagedAst: SelectQueryNode = {
|
|
48
53
|
...context.state.ast,
|
|
49
54
|
orderBy: undefined,
|
|
@@ -65,10 +70,12 @@ export async function executeCount(
|
|
|
65
70
|
joins: []
|
|
66
71
|
};
|
|
67
72
|
|
|
68
|
-
const execCtx = session.getExecutionContext();
|
|
69
|
-
const compiled =
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const execCtx = session.getExecutionContext();
|
|
74
|
+
const compiled = options?.allowParamOperands
|
|
75
|
+
? execCtx.dialect.compileSelectWithOptions(countQuery, { allowParams: true })
|
|
76
|
+
: execCtx.dialect.compileSelect(countQuery);
|
|
77
|
+
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
78
|
+
const value = results[0]?.values?.[0]?.[0];
|
|
72
79
|
|
|
73
80
|
if (typeof value === 'number') return value;
|
|
74
81
|
if (typeof value === 'bigint') return Number(value);
|
|
@@ -86,12 +93,13 @@ export interface PaginatedResult<T> {
|
|
|
86
93
|
/**
|
|
87
94
|
* Executes paged queries using the provided builder helpers.
|
|
88
95
|
*/
|
|
89
|
-
export async function executePagedQuery<T, TTable extends TableDef>(
|
|
90
|
-
builder: SelectQueryBuilder<T, TTable>,
|
|
91
|
-
session: OrmSession,
|
|
92
|
-
options: { page: number; pageSize: number },
|
|
93
|
-
countCallback: (session: OrmSession) => Promise<number
|
|
94
|
-
|
|
96
|
+
export async function executePagedQuery<T, TTable extends TableDef>(
|
|
97
|
+
builder: SelectQueryBuilder<T, TTable>,
|
|
98
|
+
session: OrmSession,
|
|
99
|
+
options: { page: number; pageSize: number },
|
|
100
|
+
countCallback: (session: OrmSession) => Promise<number>,
|
|
101
|
+
paramOptions?: ParamOperandOptions
|
|
102
|
+
): Promise<PaginatedResult<T>> {
|
|
95
103
|
const { page, pageSize } = options;
|
|
96
104
|
|
|
97
105
|
if (!Number.isInteger(page) || page < 1) {
|
|
@@ -103,11 +111,11 @@ export async function executePagedQuery<T, TTable extends TableDef>(
|
|
|
103
111
|
|
|
104
112
|
const offset = (page - 1) * pageSize;
|
|
105
113
|
|
|
106
|
-
const totalItems = await countCallback(session);
|
|
107
|
-
const items = await builder.limit(pageSize).offset(offset).execute(session);
|
|
108
|
-
|
|
109
|
-
return { items, totalItems, page, pageSize };
|
|
110
|
-
}
|
|
114
|
+
const totalItems = await countCallback(session);
|
|
115
|
+
const items = await builder.limit(pageSize).offset(offset).execute(session, paramOptions);
|
|
116
|
+
|
|
117
|
+
return { items, totalItems, page, pageSize };
|
|
118
|
+
}
|
|
111
119
|
|
|
112
120
|
/**
|
|
113
121
|
* Builds an EXISTS or NOT EXISTS predicate for a related table.
|