@mikro-orm/sql 7.0.0-rc.3 → 7.0.0
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/AbstractSqlConnection.d.ts +5 -4
- package/AbstractSqlConnection.js +18 -5
- package/AbstractSqlDriver.d.ts +1 -1
- package/AbstractSqlDriver.js +39 -10
- package/AbstractSqlPlatform.d.ts +34 -0
- package/AbstractSqlPlatform.js +47 -3
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +1 -1
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +17 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +6 -6
- package/dialects/oracledb/OracleDialect.d.ts +78 -0
- package/dialects/oracledb/OracleDialect.js +166 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
- package/dialects/oracledb/index.d.ts +2 -0
- package/dialects/oracledb/index.js +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +12 -8
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +13 -13
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +3 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +12 -8
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +3 -3
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +81 -73
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNodeFactory.js +15 -3
- package/query/NativeQueryBuilder.d.ts +3 -3
- package/query/NativeQueryBuilder.js +4 -2
- package/query/ObjectCriteriaNode.js +4 -4
- package/query/QueryBuilder.d.ts +58 -62
- package/query/QueryBuilder.js +377 -370
- package/query/QueryBuilderHelper.d.ts +14 -11
- package/query/QueryBuilderHelper.js +324 -137
- package/query/ScalarCriteriaNode.js +3 -1
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +50 -33
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +84 -60
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +22 -20
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +2 -1
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +15 -7
- package/typings.d.ts +4 -1
- package/tsconfig.build.tsbuildinfo +0 -1
package/plugin/transformer.js
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { AliasNode, ColumnNode, ColumnUpdateNode, OperationNodeTransformer, PrimitiveValueListNode, ReferenceNode, SchemableIdentifierNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
3
3
|
export class MikroTransformer extends OperationNodeTransformer {
|
|
4
|
-
em;
|
|
5
|
-
options;
|
|
6
4
|
/**
|
|
7
5
|
* Context stack to support nested queries (subqueries, CTEs)
|
|
8
6
|
* Each level of query scope has its own Map of table aliases/names to EntityMetadata
|
|
9
7
|
* Top of stack (highest index) is the current scope
|
|
10
8
|
*/
|
|
11
|
-
contextStack = [];
|
|
9
|
+
#contextStack = [];
|
|
12
10
|
/**
|
|
13
11
|
* Subquery alias map: maps subquery/CTE alias to its source table metadata
|
|
14
12
|
* Used to resolve columns from subqueries/CTEs to their original table definitions
|
|
15
13
|
*/
|
|
16
|
-
subqueryAliasMap = new Map();
|
|
17
|
-
metadata;
|
|
18
|
-
platform;
|
|
14
|
+
#subqueryAliasMap = new Map();
|
|
15
|
+
#metadata;
|
|
16
|
+
#platform;
|
|
19
17
|
/**
|
|
20
18
|
* Global map of all entities involved in the query.
|
|
21
19
|
* Populated during AST transformation and used for result transformation.
|
|
22
20
|
*/
|
|
23
|
-
entityMap = new Map();
|
|
21
|
+
#entityMap = new Map();
|
|
22
|
+
#em;
|
|
23
|
+
#options;
|
|
24
24
|
constructor(em, options = {}) {
|
|
25
25
|
super();
|
|
26
|
-
this
|
|
27
|
-
this
|
|
28
|
-
this
|
|
29
|
-
this
|
|
26
|
+
this.#em = em;
|
|
27
|
+
this.#options = options;
|
|
28
|
+
this.#metadata = em.getMetadata();
|
|
29
|
+
this.#platform = em.getDriver().getPlatform();
|
|
30
30
|
}
|
|
31
31
|
reset() {
|
|
32
|
-
this
|
|
33
|
-
this
|
|
32
|
+
this.#subqueryAliasMap.clear();
|
|
33
|
+
this.#entityMap.clear();
|
|
34
34
|
}
|
|
35
35
|
getOutputEntityMap() {
|
|
36
|
-
return this
|
|
36
|
+
return this.#entityMap;
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
getContextStack() {
|
|
40
|
+
return this.#contextStack;
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
getSubqueryAliasMap() {
|
|
44
|
+
return this.#subqueryAliasMap;
|
|
37
45
|
}
|
|
38
46
|
transformSelectQuery(node, queryId) {
|
|
39
47
|
// Push a new context for this query scope (starts with inherited parent context)
|
|
40
48
|
const currentContext = new Map();
|
|
41
|
-
this
|
|
49
|
+
this.#contextStack.push(currentContext);
|
|
42
50
|
try {
|
|
43
51
|
// Process WITH clause (CTEs) first - they define names available in this scope
|
|
44
52
|
if (node.with) {
|
|
@@ -60,12 +68,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
60
68
|
}
|
|
61
69
|
finally {
|
|
62
70
|
// Pop the context when exiting this query scope
|
|
63
|
-
this
|
|
71
|
+
this.#contextStack.pop();
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
74
|
transformInsertQuery(node, queryId) {
|
|
67
75
|
const currentContext = new Map();
|
|
68
|
-
this
|
|
76
|
+
this.#contextStack.push(currentContext);
|
|
69
77
|
try {
|
|
70
78
|
let entityMeta;
|
|
71
79
|
if (node.into) {
|
|
@@ -75,12 +83,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
75
83
|
if (meta) {
|
|
76
84
|
entityMeta = meta;
|
|
77
85
|
currentContext.set(meta.tableName, meta);
|
|
78
|
-
this
|
|
86
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
|
-
const nodeWithHooks = this
|
|
83
|
-
const nodeWithConvertedValues = this
|
|
90
|
+
const nodeWithHooks = this.#options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
|
|
91
|
+
const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
84
92
|
// Handle ON CONFLICT clause
|
|
85
93
|
let finalNode = nodeWithConvertedValues;
|
|
86
94
|
if (node.onConflict?.updates && entityMeta) {
|
|
@@ -91,14 +99,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
91
99
|
table: node.into, // Dummy table
|
|
92
100
|
updates: node.onConflict.updates,
|
|
93
101
|
};
|
|
94
|
-
const updatesWithHooks = this
|
|
102
|
+
const updatesWithHooks = this.#options.processOnUpdateHooks
|
|
95
103
|
? this.processOnUpdateHooks(tempUpdateNode, entityMeta).updates
|
|
96
104
|
: node.onConflict.updates;
|
|
97
105
|
const tempUpdateNodeWithHooks = {
|
|
98
106
|
...tempUpdateNode,
|
|
99
107
|
updates: updatesWithHooks,
|
|
100
108
|
};
|
|
101
|
-
const updatesWithConvertedValues = this
|
|
109
|
+
const updatesWithConvertedValues = this.#options.convertValues
|
|
102
110
|
? this.processUpdateValues(tempUpdateNodeWithHooks, entityMeta).updates
|
|
103
111
|
: updatesWithHooks;
|
|
104
112
|
if (updatesWithConvertedValues && updatesWithConvertedValues !== node.onConflict.updates) {
|
|
@@ -115,12 +123,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
115
123
|
return super.transformInsertQuery(finalNode, queryId);
|
|
116
124
|
}
|
|
117
125
|
finally {
|
|
118
|
-
this
|
|
126
|
+
this.#contextStack.pop();
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
transformUpdateQuery(node, queryId) {
|
|
122
130
|
const currentContext = new Map();
|
|
123
|
-
this
|
|
131
|
+
this.#contextStack.push(currentContext);
|
|
124
132
|
try {
|
|
125
133
|
let entityMeta;
|
|
126
134
|
if (node.table && TableNode.is(node.table)) {
|
|
@@ -130,7 +138,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
130
138
|
if (meta) {
|
|
131
139
|
entityMeta = meta;
|
|
132
140
|
currentContext.set(meta.tableName, meta);
|
|
133
|
-
this
|
|
141
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
144
|
}
|
|
@@ -146,17 +154,17 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
146
154
|
this.processJoinNode(join, currentContext);
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
|
-
const nodeWithHooks = this
|
|
150
|
-
const nodeWithConvertedValues = this
|
|
157
|
+
const nodeWithHooks = this.#options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
|
|
158
|
+
const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
151
159
|
return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
|
|
152
160
|
}
|
|
153
161
|
finally {
|
|
154
|
-
this
|
|
162
|
+
this.#contextStack.pop();
|
|
155
163
|
}
|
|
156
164
|
}
|
|
157
165
|
transformDeleteQuery(node, queryId) {
|
|
158
166
|
const currentContext = new Map();
|
|
159
|
-
this
|
|
167
|
+
this.#contextStack.push(currentContext);
|
|
160
168
|
try {
|
|
161
169
|
const froms = node.from?.froms;
|
|
162
170
|
if (froms && froms.length > 0) {
|
|
@@ -167,7 +175,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
167
175
|
const meta = this.findEntityMetadata(tableName);
|
|
168
176
|
if (meta) {
|
|
169
177
|
currentContext.set(meta.tableName, meta);
|
|
170
|
-
this
|
|
178
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
171
179
|
}
|
|
172
180
|
}
|
|
173
181
|
}
|
|
@@ -181,24 +189,24 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
181
189
|
return super.transformDeleteQuery(node, queryId);
|
|
182
190
|
}
|
|
183
191
|
finally {
|
|
184
|
-
this
|
|
192
|
+
this.#contextStack.pop();
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
transformMergeQuery(node, queryId) {
|
|
188
196
|
const currentContext = new Map();
|
|
189
|
-
this
|
|
197
|
+
this.#contextStack.push(currentContext);
|
|
190
198
|
try {
|
|
191
199
|
return super.transformMergeQuery(node, queryId);
|
|
192
200
|
}
|
|
193
201
|
finally {
|
|
194
|
-
this
|
|
202
|
+
this.#contextStack.pop();
|
|
195
203
|
}
|
|
196
204
|
}
|
|
197
205
|
transformIdentifier(node, queryId) {
|
|
198
206
|
node = super.transformIdentifier(node, queryId);
|
|
199
207
|
const parent = this.nodeStack[this.nodeStack.length - 2];
|
|
200
208
|
// Transform table names when tableNamingStrategy is 'entity'
|
|
201
|
-
if (this
|
|
209
|
+
if (this.#options.tableNamingStrategy === 'entity' && parent && SchemableIdentifierNode.is(parent)) {
|
|
202
210
|
const meta = this.findEntityMetadata(node.name);
|
|
203
211
|
if (meta) {
|
|
204
212
|
return {
|
|
@@ -209,7 +217,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
209
217
|
}
|
|
210
218
|
// Transform column names when columnNamingStrategy is 'property'
|
|
211
219
|
// Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
|
|
212
|
-
if (this
|
|
220
|
+
if (this.#options.columnNamingStrategy === 'property' &&
|
|
213
221
|
parent &&
|
|
214
222
|
(ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
|
|
215
223
|
const ownerMeta = this.findOwnerEntityInContext();
|
|
@@ -239,8 +247,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
239
247
|
const tableName = this.getTableName(reference.table);
|
|
240
248
|
if (tableName) {
|
|
241
249
|
// First, check in subquery alias map (for CTE/subquery columns)
|
|
242
|
-
if (this
|
|
243
|
-
return this
|
|
250
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
251
|
+
return this.#subqueryAliasMap.get(tableName);
|
|
244
252
|
}
|
|
245
253
|
// Find entity metadata to get the actual table name
|
|
246
254
|
// Context uses table names (meta.tableName) as keys, not entity names
|
|
@@ -267,16 +275,16 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
267
275
|
}
|
|
268
276
|
}
|
|
269
277
|
// If no explicit table reference, use the first entity in current context
|
|
270
|
-
if (this
|
|
271
|
-
const currentContext = this
|
|
278
|
+
if (this.#contextStack.length > 0) {
|
|
279
|
+
const currentContext = this.#contextStack[this.#contextStack.length - 1];
|
|
272
280
|
for (const [alias, meta] of currentContext.entries()) {
|
|
273
281
|
if (meta) {
|
|
274
282
|
return meta;
|
|
275
283
|
}
|
|
276
284
|
// If the context value is undefined but the alias is in subqueryAliasMap,
|
|
277
285
|
// use the mapped metadata (for CTE/subquery cases)
|
|
278
|
-
if (!meta && this
|
|
279
|
-
const mappedMeta = this
|
|
286
|
+
if (!meta && this.#subqueryAliasMap.has(alias)) {
|
|
287
|
+
const mappedMeta = this.#subqueryAliasMap.get(alias);
|
|
280
288
|
if (mappedMeta) {
|
|
281
289
|
return mappedMeta;
|
|
282
290
|
}
|
|
@@ -306,7 +314,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
306
314
|
}
|
|
307
315
|
const newRows = node.values.values.map(row => {
|
|
308
316
|
const valuesToAdd = missingProps.map(prop => {
|
|
309
|
-
const val = prop.onCreate(undefined, this
|
|
317
|
+
const val = prop.onCreate(undefined, this.#em);
|
|
310
318
|
return val;
|
|
311
319
|
});
|
|
312
320
|
if (ValueListNode.is(row)) {
|
|
@@ -344,7 +352,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
344
352
|
}
|
|
345
353
|
const newUpdates = [...node.updates];
|
|
346
354
|
for (const prop of missingProps) {
|
|
347
|
-
const val = prop.onUpdate(undefined, this
|
|
355
|
+
const val = prop.onUpdate(undefined, this.#em);
|
|
348
356
|
newUpdates.push(ColumnUpdateNode.create(ColumnNode.create(prop.name), ValueNode.create(val)));
|
|
349
357
|
}
|
|
350
358
|
return {
|
|
@@ -459,7 +467,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
459
467
|
return meta.props.find(prop => prop.fieldNames?.includes(columnName));
|
|
460
468
|
}
|
|
461
469
|
shouldConvertValues() {
|
|
462
|
-
return !!this
|
|
470
|
+
return !!this.#options.convertValues;
|
|
463
471
|
}
|
|
464
472
|
prepareInputValue(prop, value, enabled) {
|
|
465
473
|
if (!enabled || !prop || value == null) {
|
|
@@ -474,14 +482,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
474
482
|
}
|
|
475
483
|
}
|
|
476
484
|
if (prop.customType && !isRaw(value)) {
|
|
477
|
-
return prop.customType.convertToDatabaseValue(value, this
|
|
485
|
+
return prop.customType.convertToDatabaseValue(value, this.#platform, {
|
|
478
486
|
fromQuery: true,
|
|
479
487
|
key: prop.name,
|
|
480
488
|
mode: 'query-data',
|
|
481
489
|
});
|
|
482
490
|
}
|
|
483
491
|
if (value instanceof Date) {
|
|
484
|
-
return this
|
|
492
|
+
return this.#platform.processDateProperty(value);
|
|
485
493
|
}
|
|
486
494
|
return value;
|
|
487
495
|
}
|
|
@@ -492,8 +500,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
492
500
|
*/
|
|
493
501
|
lookupInContextStack(tableNameOrAlias) {
|
|
494
502
|
// Search from top of stack (current scope) to bottom (parent scopes)
|
|
495
|
-
for (let i = this
|
|
496
|
-
const context = this
|
|
503
|
+
for (let i = this.#contextStack.length - 1; i >= 0; i--) {
|
|
504
|
+
const context = this.#contextStack[i];
|
|
497
505
|
if (context.has(tableNameOrAlias)) {
|
|
498
506
|
return context.get(tableNameOrAlias);
|
|
499
507
|
}
|
|
@@ -515,10 +523,10 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
515
523
|
if (cte.expression?.kind === 'SelectQueryNode') {
|
|
516
524
|
const sourceMeta = this.extractSourceTableFromSelectQuery(cte.expression);
|
|
517
525
|
if (sourceMeta) {
|
|
518
|
-
this
|
|
526
|
+
this.#subqueryAliasMap.set(cteName, sourceMeta);
|
|
519
527
|
// Add CTE to entityMap so it can be used for result transformation if needed
|
|
520
528
|
// (though CTEs usually don't appear in result rows directly, but their columns might)
|
|
521
|
-
this
|
|
529
|
+
this.#entityMap.set(cteName, sourceMeta);
|
|
522
530
|
}
|
|
523
531
|
}
|
|
524
532
|
}
|
|
@@ -548,11 +556,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
548
556
|
if (aliasName) {
|
|
549
557
|
context.set(aliasName, meta);
|
|
550
558
|
if (meta) {
|
|
551
|
-
this
|
|
559
|
+
this.#entityMap.set(aliasName, meta);
|
|
552
560
|
}
|
|
553
561
|
// Also map the alias in subqueryAliasMap if the table name is a CTE
|
|
554
|
-
if (this
|
|
555
|
-
this
|
|
562
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
563
|
+
this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
|
|
556
564
|
}
|
|
557
565
|
}
|
|
558
566
|
}
|
|
@@ -565,7 +573,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
565
573
|
// Try to extract the source table from the subquery
|
|
566
574
|
const sourceMeta = this.extractSourceTableFromSelectQuery(from.node);
|
|
567
575
|
if (sourceMeta) {
|
|
568
|
-
this
|
|
576
|
+
this.#subqueryAliasMap.set(aliasName, sourceMeta);
|
|
569
577
|
}
|
|
570
578
|
}
|
|
571
579
|
}
|
|
@@ -584,7 +592,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
584
592
|
const meta = this.findEntityMetadata(tableName);
|
|
585
593
|
context.set(tableName, meta);
|
|
586
594
|
if (meta) {
|
|
587
|
-
this
|
|
595
|
+
this.#entityMap.set(tableName, meta);
|
|
588
596
|
}
|
|
589
597
|
}
|
|
590
598
|
}
|
|
@@ -604,11 +612,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
604
612
|
if (aliasName) {
|
|
605
613
|
context.set(aliasName, meta);
|
|
606
614
|
if (meta) {
|
|
607
|
-
this
|
|
615
|
+
this.#entityMap.set(aliasName, meta);
|
|
608
616
|
}
|
|
609
617
|
// Also map the alias in subqueryAliasMap if the table name is a CTE
|
|
610
|
-
if (this
|
|
611
|
-
this
|
|
618
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
619
|
+
this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
|
|
612
620
|
}
|
|
613
621
|
}
|
|
614
622
|
}
|
|
@@ -621,7 +629,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
621
629
|
// Try to extract the source table from the subquery
|
|
622
630
|
const sourceMeta = this.extractSourceTableFromSelectQuery(joinTable.node);
|
|
623
631
|
if (sourceMeta) {
|
|
624
|
-
this
|
|
632
|
+
this.#subqueryAliasMap.set(aliasName, sourceMeta);
|
|
625
633
|
}
|
|
626
634
|
}
|
|
627
635
|
}
|
|
@@ -641,7 +649,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
641
649
|
// Use table name (meta.tableName) as key to match transformUpdateQuery behavior
|
|
642
650
|
if (meta) {
|
|
643
651
|
context.set(meta.tableName, meta);
|
|
644
|
-
this
|
|
652
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
645
653
|
// Also set with entity name for backward compatibility
|
|
646
654
|
context.set(tableName, meta);
|
|
647
655
|
}
|
|
@@ -704,11 +712,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
704
712
|
* Find entity metadata by table name or entity name
|
|
705
713
|
*/
|
|
706
714
|
findEntityMetadata(name) {
|
|
707
|
-
const byEntity = this
|
|
715
|
+
const byEntity = this.#metadata.getByClassName(name, false);
|
|
708
716
|
if (byEntity) {
|
|
709
717
|
return byEntity;
|
|
710
718
|
}
|
|
711
|
-
const allMetadata = Array.from(this
|
|
719
|
+
const allMetadata = Array.from(this.#metadata);
|
|
712
720
|
const byTable = allMetadata.find(m => m.tableName === name);
|
|
713
721
|
if (byTable) {
|
|
714
722
|
return byTable;
|
|
@@ -721,7 +729,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
721
729
|
*/
|
|
722
730
|
transformResult(rows, entityMap) {
|
|
723
731
|
// Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
|
|
724
|
-
if ((this
|
|
732
|
+
if ((this.#options.columnNamingStrategy !== 'property' && !this.#options.convertValues) ||
|
|
725
733
|
!rows ||
|
|
726
734
|
rows.length === 0) {
|
|
727
735
|
return rows;
|
|
@@ -818,7 +826,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
818
826
|
continue;
|
|
819
827
|
}
|
|
820
828
|
const converted = this.prepareOutputValue(prop, transformed[fieldName]);
|
|
821
|
-
if (this
|
|
829
|
+
if (this.#options.columnNamingStrategy === 'property' && prop.name !== fieldName) {
|
|
822
830
|
if (!(prop.name in transformed)) {
|
|
823
831
|
transformed[prop.name] = converted;
|
|
824
832
|
}
|
|
@@ -828,13 +836,13 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
828
836
|
delete transformed[fieldName];
|
|
829
837
|
continue;
|
|
830
838
|
}
|
|
831
|
-
if (this
|
|
839
|
+
if (this.#options.convertValues) {
|
|
832
840
|
transformed[fieldName] = converted;
|
|
833
841
|
}
|
|
834
842
|
}
|
|
835
843
|
// Second pass: handle relation fields
|
|
836
844
|
// Only run if columnNamingStrategy is 'property', as we don't want to rename FKs otherwise
|
|
837
|
-
if (this
|
|
845
|
+
if (this.#options.columnNamingStrategy === 'property') {
|
|
838
846
|
for (const [fieldName, relationPropertyName] of Object.entries(relationFieldMap)) {
|
|
839
847
|
if (fieldName in transformed && !(relationPropertyName in transformed)) {
|
|
840
848
|
// Move the foreign key value to the relation property name
|
|
@@ -846,36 +854,36 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
846
854
|
return transformed;
|
|
847
855
|
}
|
|
848
856
|
prepareOutputValue(prop, value) {
|
|
849
|
-
if (!this
|
|
857
|
+
if (!this.#options.convertValues || !prop || value == null) {
|
|
850
858
|
return value;
|
|
851
859
|
}
|
|
852
860
|
if (prop.customType) {
|
|
853
|
-
return prop.customType.convertToJSValue(value, this
|
|
861
|
+
return prop.customType.convertToJSValue(value, this.#platform);
|
|
854
862
|
}
|
|
855
863
|
// Aligned with EntityComparator.getResultMapper logic
|
|
856
864
|
if (prop.runtimeType === 'boolean') {
|
|
857
865
|
// Use !! conversion like EntityComparator: value == null ? value : !!value
|
|
858
866
|
return value == null ? value : !!value;
|
|
859
867
|
}
|
|
860
|
-
if (prop.runtimeType === 'Date' && !this
|
|
868
|
+
if (prop.runtimeType === 'Date' && !this.#platform.isNumericProperty(prop)) {
|
|
861
869
|
// Aligned with EntityComparator: exclude numeric timestamp properties
|
|
862
870
|
// If already Date instance or null, return as is
|
|
863
871
|
if (value == null || value instanceof Date) {
|
|
864
872
|
return value;
|
|
865
873
|
}
|
|
866
874
|
// Handle timezone like EntityComparator.parseDate
|
|
867
|
-
const tz = this
|
|
875
|
+
const tz = this.#platform.getTimezone();
|
|
868
876
|
if (!tz || tz === 'local') {
|
|
869
|
-
return this
|
|
877
|
+
return this.#platform.parseDate(value);
|
|
870
878
|
}
|
|
871
879
|
// For non-local timezone, check if value already has timezone info
|
|
872
880
|
// Number (timestamp) doesn't need timezone handling, string needs check
|
|
873
881
|
if (typeof value === 'number' ||
|
|
874
882
|
(typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
|
|
875
|
-
return this
|
|
883
|
+
return this.#platform.parseDate(value);
|
|
876
884
|
}
|
|
877
885
|
// Append timezone if not present (only for string values)
|
|
878
|
-
return this
|
|
886
|
+
return this.#platform.parseDate(value + tz);
|
|
879
887
|
}
|
|
880
888
|
// For all other runtimeTypes (number, string, bigint, Buffer, object, any, etc.)
|
|
881
889
|
// EntityComparator just assigns directly without conversion
|
|
@@ -6,6 +6,6 @@ import type { IQueryBuilder, ICriteriaNodeProcessOptions } from '../typings.js';
|
|
|
6
6
|
export declare class ArrayCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
8
|
unwrap(): any;
|
|
9
|
-
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions):
|
|
9
|
+
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
10
10
|
isStrict(): boolean;
|
|
11
11
|
}
|
|
@@ -2,6 +2,7 @@ import { GroupOperator, isRaw, JsonType, RawQueryFragment, ReferenceKind, Utils,
|
|
|
2
2
|
import { ObjectCriteriaNode } from './ObjectCriteriaNode.js';
|
|
3
3
|
import { ArrayCriteriaNode } from './ArrayCriteriaNode.js';
|
|
4
4
|
import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
|
|
5
|
+
import { EMBEDDABLE_ARRAY_OPS } from './enums.js';
|
|
5
6
|
/**
|
|
6
7
|
* @internal
|
|
7
8
|
*/
|
|
@@ -69,15 +70,26 @@ export class CriteriaNodeFactory {
|
|
|
69
70
|
}, {});
|
|
70
71
|
return this.createNode(metadata, entityName, map, node, key, validate);
|
|
71
72
|
}
|
|
73
|
+
// For array embeddeds stored as real columns, route property-level queries
|
|
74
|
+
// as scalar nodes so QueryBuilderHelper generates EXISTS subqueries with
|
|
75
|
+
// JSON array iteration. Keys containing `~` indicate the property lives
|
|
76
|
+
// inside a parent's object-mode JSON column (MetadataDiscovery uses `~` as
|
|
77
|
+
// the glue for object embeds), where JSON path access is used instead.
|
|
78
|
+
if (prop.array && !String(key).includes('~')) {
|
|
79
|
+
const keys = Object.keys(val);
|
|
80
|
+
const hasOnlyArrayOps = keys.every(k => EMBEDDABLE_ARRAY_OPS.includes(k));
|
|
81
|
+
if (!hasOnlyArrayOps) {
|
|
82
|
+
return this.createScalarNode(metadata, entityName, val, node, key, validate);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
72
85
|
// array operators can be used on embedded properties
|
|
73
|
-
const
|
|
74
|
-
const operator = Object.keys(val).some(f => Utils.isOperator(f) && !allowedOperators.includes(f));
|
|
86
|
+
const operator = Object.keys(val).some(f => Utils.isOperator(f) && !EMBEDDABLE_ARRAY_OPS.includes(f));
|
|
75
87
|
if (operator) {
|
|
76
88
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
|
|
77
89
|
}
|
|
78
90
|
const map = Object.keys(val).reduce((oo, k) => {
|
|
79
91
|
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
|
|
80
|
-
if (!embeddedProp && !
|
|
92
|
+
if (!embeddedProp && !EMBEDDABLE_ARRAY_OPS.includes(k)) {
|
|
81
93
|
throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
|
|
82
94
|
}
|
|
83
95
|
if (embeddedProp) {
|
|
@@ -38,14 +38,14 @@ interface Options {
|
|
|
38
38
|
onConflict?: OnConflictClause;
|
|
39
39
|
lockMode?: LockMode;
|
|
40
40
|
lockTables?: string[];
|
|
41
|
-
returning?: (string | RawQueryFragment)[];
|
|
41
|
+
returning?: (string | RawQueryFragment | [name: string, type: unknown])[];
|
|
42
42
|
comment?: string[];
|
|
43
43
|
hintComment?: string[];
|
|
44
44
|
flags?: Set<QueryFlag>;
|
|
45
45
|
wrap?: [prefix: string, suffix: string];
|
|
46
46
|
ctes?: CteClause[];
|
|
47
47
|
}
|
|
48
|
-
interface TableOptions {
|
|
48
|
+
export interface TableOptions {
|
|
49
49
|
schema?: string;
|
|
50
50
|
indexHint?: string;
|
|
51
51
|
alias?: string;
|
|
@@ -108,7 +108,7 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
108
108
|
distinct(): this;
|
|
109
109
|
distinctOn(fields: string[]): this;
|
|
110
110
|
onConflict(options: OnConflictClause): OnConflictClause;
|
|
111
|
-
returning(fields: (string | RawQueryFragment)[]): this;
|
|
111
|
+
returning(fields: (string | RawQueryFragment | [name: string, type: unknown])[]): this;
|
|
112
112
|
lockMode(lockMode: LockMode, lockTables?: string[]): this;
|
|
113
113
|
comment(comment: string | string[]): this;
|
|
114
114
|
hintComment(comment: string | string[]): this;
|
|
@@ -30,7 +30,8 @@ export class NativeQueryBuilder {
|
|
|
30
30
|
tableName = tableName.toRaw();
|
|
31
31
|
}
|
|
32
32
|
if (typeof tableName === 'string') {
|
|
33
|
-
const
|
|
33
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
34
|
+
const alias = options?.alias ? `${asKeyword}${this.platform.quoteIdentifier(options.alias)}` : '';
|
|
34
35
|
const schema = options?.schema && options.schema !== this.platform.getDefaultSchemaName() ? `${options.schema}.` : '';
|
|
35
36
|
tableName = this.quote(schema + tableName) + alias;
|
|
36
37
|
}
|
|
@@ -474,7 +475,8 @@ export class NativeQueryBuilder {
|
|
|
474
475
|
const parts = id.split(/ as /i);
|
|
475
476
|
const a = this.platform.quoteIdentifier(parts[0]);
|
|
476
477
|
const b = this.platform.quoteIdentifier(parts[1]);
|
|
477
|
-
|
|
478
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
479
|
+
return `${a}${asKeyword}${b}`;
|
|
478
480
|
}
|
|
479
481
|
if (id === '*') {
|
|
480
482
|
return id;
|
|
@@ -39,7 +39,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
39
39
|
throw new Error('Mixing collection operators with other filters is not allowed.');
|
|
40
40
|
}
|
|
41
41
|
const payload = this.payload[key].unwrap();
|
|
42
|
-
const qb2 = qb.clone(true, ['
|
|
42
|
+
const qb2 = qb.clone(true, ['schema']);
|
|
43
43
|
const joinAlias = qb2.getNextAlias(this.prop.targetMeta.class);
|
|
44
44
|
const sub = qb2
|
|
45
45
|
.from(parentMeta.class)
|
|
@@ -289,12 +289,12 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
289
289
|
qb.join(field, nestedAlias, undefined, JoinType.pivotJoin, path);
|
|
290
290
|
}
|
|
291
291
|
else {
|
|
292
|
-
const prev = qb.
|
|
292
|
+
const prev = qb.state.fields?.slice();
|
|
293
293
|
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
294
294
|
const joinType = toOneProperty && !this.prop.nullable ? JoinType.innerJoin : JoinType.leftJoin;
|
|
295
295
|
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
296
296
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
297
|
-
qb.
|
|
297
|
+
qb.state.fields = prev;
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
if (options?.type !== 'orderBy') {
|
|
@@ -303,6 +303,6 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
303
303
|
return nestedAlias;
|
|
304
304
|
}
|
|
305
305
|
isPrefixed(field) {
|
|
306
|
-
return
|
|
306
|
+
return !!/\w+\./.exec(field);
|
|
307
307
|
}
|
|
308
308
|
}
|