@mikro-orm/sql 7.0.0-rc.2 → 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 +20 -6
- package/AbstractSqlDriver.d.ts +19 -13
- package/AbstractSqlDriver.js +225 -47
- package/AbstractSqlPlatform.d.ts +35 -0
- package/AbstractSqlPlatform.js +51 -5
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -5
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +18 -2
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +25 -14
- 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 +49 -37
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +75 -59
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +23 -17
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +30 -30
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +91 -82
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +20 -4
- package/query/NativeQueryBuilder.d.ts +28 -3
- package/query/NativeQueryBuilder.js +65 -3
- package/query/ObjectCriteriaNode.js +75 -31
- package/query/QueryBuilder.d.ts +199 -100
- package/query/QueryBuilder.js +544 -358
- package/query/QueryBuilderHelper.d.ts +18 -14
- package/query/QueryBuilderHelper.js +364 -147
- package/query/ScalarCriteriaNode.js +17 -8
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +68 -45
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +191 -107
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +76 -50
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +30 -9
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +26 -12
- package/typings.d.ts +10 -5
- 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,16 +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
|
-
|
|
84
|
-
: node;
|
|
85
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
86
|
-
? this.processInsertValues(nodeWithHooks, entityMeta)
|
|
87
|
-
: nodeWithHooks;
|
|
90
|
+
const nodeWithHooks = this.#options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
|
|
91
|
+
const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
88
92
|
// Handle ON CONFLICT clause
|
|
89
93
|
let finalNode = nodeWithConvertedValues;
|
|
90
94
|
if (node.onConflict?.updates && entityMeta) {
|
|
@@ -95,14 +99,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
95
99
|
table: node.into, // Dummy table
|
|
96
100
|
updates: node.onConflict.updates,
|
|
97
101
|
};
|
|
98
|
-
const updatesWithHooks = this
|
|
102
|
+
const updatesWithHooks = this.#options.processOnUpdateHooks
|
|
99
103
|
? this.processOnUpdateHooks(tempUpdateNode, entityMeta).updates
|
|
100
104
|
: node.onConflict.updates;
|
|
101
105
|
const tempUpdateNodeWithHooks = {
|
|
102
106
|
...tempUpdateNode,
|
|
103
107
|
updates: updatesWithHooks,
|
|
104
108
|
};
|
|
105
|
-
const updatesWithConvertedValues = this
|
|
109
|
+
const updatesWithConvertedValues = this.#options.convertValues
|
|
106
110
|
? this.processUpdateValues(tempUpdateNodeWithHooks, entityMeta).updates
|
|
107
111
|
: updatesWithHooks;
|
|
108
112
|
if (updatesWithConvertedValues && updatesWithConvertedValues !== node.onConflict.updates) {
|
|
@@ -119,12 +123,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
119
123
|
return super.transformInsertQuery(finalNode, queryId);
|
|
120
124
|
}
|
|
121
125
|
finally {
|
|
122
|
-
this
|
|
126
|
+
this.#contextStack.pop();
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
transformUpdateQuery(node, queryId) {
|
|
126
130
|
const currentContext = new Map();
|
|
127
|
-
this
|
|
131
|
+
this.#contextStack.push(currentContext);
|
|
128
132
|
try {
|
|
129
133
|
let entityMeta;
|
|
130
134
|
if (node.table && TableNode.is(node.table)) {
|
|
@@ -134,7 +138,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
134
138
|
if (meta) {
|
|
135
139
|
entityMeta = meta;
|
|
136
140
|
currentContext.set(meta.tableName, meta);
|
|
137
|
-
this
|
|
141
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
}
|
|
@@ -150,21 +154,17 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
150
154
|
this.processJoinNode(join, currentContext);
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
|
-
const nodeWithHooks = this
|
|
154
|
-
|
|
155
|
-
: node;
|
|
156
|
-
const nodeWithConvertedValues = this.options.convertValues && entityMeta
|
|
157
|
-
? this.processUpdateValues(nodeWithHooks, entityMeta)
|
|
158
|
-
: nodeWithHooks;
|
|
157
|
+
const nodeWithHooks = this.#options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
|
|
158
|
+
const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
|
|
159
159
|
return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
|
|
160
160
|
}
|
|
161
161
|
finally {
|
|
162
|
-
this
|
|
162
|
+
this.#contextStack.pop();
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
transformDeleteQuery(node, queryId) {
|
|
166
166
|
const currentContext = new Map();
|
|
167
|
-
this
|
|
167
|
+
this.#contextStack.push(currentContext);
|
|
168
168
|
try {
|
|
169
169
|
const froms = node.from?.froms;
|
|
170
170
|
if (froms && froms.length > 0) {
|
|
@@ -175,7 +175,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
175
175
|
const meta = this.findEntityMetadata(tableName);
|
|
176
176
|
if (meta) {
|
|
177
177
|
currentContext.set(meta.tableName, meta);
|
|
178
|
-
this
|
|
178
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -189,24 +189,24 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
189
189
|
return super.transformDeleteQuery(node, queryId);
|
|
190
190
|
}
|
|
191
191
|
finally {
|
|
192
|
-
this
|
|
192
|
+
this.#contextStack.pop();
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
transformMergeQuery(node, queryId) {
|
|
196
196
|
const currentContext = new Map();
|
|
197
|
-
this
|
|
197
|
+
this.#contextStack.push(currentContext);
|
|
198
198
|
try {
|
|
199
199
|
return super.transformMergeQuery(node, queryId);
|
|
200
200
|
}
|
|
201
201
|
finally {
|
|
202
|
-
this
|
|
202
|
+
this.#contextStack.pop();
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
transformIdentifier(node, queryId) {
|
|
206
206
|
node = super.transformIdentifier(node, queryId);
|
|
207
207
|
const parent = this.nodeStack[this.nodeStack.length - 2];
|
|
208
208
|
// Transform table names when tableNamingStrategy is 'entity'
|
|
209
|
-
if (this
|
|
209
|
+
if (this.#options.tableNamingStrategy === 'entity' && parent && SchemableIdentifierNode.is(parent)) {
|
|
210
210
|
const meta = this.findEntityMetadata(node.name);
|
|
211
211
|
if (meta) {
|
|
212
212
|
return {
|
|
@@ -217,7 +217,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
217
217
|
}
|
|
218
218
|
// Transform column names when columnNamingStrategy is 'property'
|
|
219
219
|
// Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
|
|
220
|
-
if (this
|
|
220
|
+
if (this.#options.columnNamingStrategy === 'property' &&
|
|
221
|
+
parent &&
|
|
222
|
+
(ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
|
|
221
223
|
const ownerMeta = this.findOwnerEntityInContext();
|
|
222
224
|
if (ownerMeta) {
|
|
223
225
|
const prop = ownerMeta.properties[node.name];
|
|
@@ -245,8 +247,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
245
247
|
const tableName = this.getTableName(reference.table);
|
|
246
248
|
if (tableName) {
|
|
247
249
|
// First, check in subquery alias map (for CTE/subquery columns)
|
|
248
|
-
if (this
|
|
249
|
-
return this
|
|
250
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
251
|
+
return this.#subqueryAliasMap.get(tableName);
|
|
250
252
|
}
|
|
251
253
|
// Find entity metadata to get the actual table name
|
|
252
254
|
// Context uses table names (meta.tableName) as keys, not entity names
|
|
@@ -273,16 +275,16 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
273
275
|
}
|
|
274
276
|
}
|
|
275
277
|
// If no explicit table reference, use the first entity in current context
|
|
276
|
-
if (this
|
|
277
|
-
const currentContext = this
|
|
278
|
+
if (this.#contextStack.length > 0) {
|
|
279
|
+
const currentContext = this.#contextStack[this.#contextStack.length - 1];
|
|
278
280
|
for (const [alias, meta] of currentContext.entries()) {
|
|
279
281
|
if (meta) {
|
|
280
282
|
return meta;
|
|
281
283
|
}
|
|
282
284
|
// If the context value is undefined but the alias is in subqueryAliasMap,
|
|
283
285
|
// use the mapped metadata (for CTE/subquery cases)
|
|
284
|
-
if (!meta && this
|
|
285
|
-
const mappedMeta = this
|
|
286
|
+
if (!meta && this.#subqueryAliasMap.has(alias)) {
|
|
287
|
+
const mappedMeta = this.#subqueryAliasMap.get(alias);
|
|
286
288
|
if (mappedMeta) {
|
|
287
289
|
return mappedMeta;
|
|
288
290
|
}
|
|
@@ -312,7 +314,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
312
314
|
}
|
|
313
315
|
const newRows = node.values.values.map(row => {
|
|
314
316
|
const valuesToAdd = missingProps.map(prop => {
|
|
315
|
-
const val = prop.onCreate(undefined, this
|
|
317
|
+
const val = prop.onCreate(undefined, this.#em);
|
|
316
318
|
return val;
|
|
317
319
|
});
|
|
318
320
|
if (ValueListNode.is(row)) {
|
|
@@ -350,7 +352,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
350
352
|
}
|
|
351
353
|
const newUpdates = [...node.updates];
|
|
352
354
|
for (const prop of missingProps) {
|
|
353
|
-
const val = prop.onUpdate(undefined, this
|
|
355
|
+
const val = prop.onUpdate(undefined, this.#em);
|
|
354
356
|
newUpdates.push(ColumnUpdateNode.create(ColumnNode.create(prop.name), ValueNode.create(val)));
|
|
355
357
|
}
|
|
356
358
|
return {
|
|
@@ -465,7 +467,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
465
467
|
return meta.props.find(prop => prop.fieldNames?.includes(columnName));
|
|
466
468
|
}
|
|
467
469
|
shouldConvertValues() {
|
|
468
|
-
return !!this
|
|
470
|
+
return !!this.#options.convertValues;
|
|
469
471
|
}
|
|
470
472
|
prepareInputValue(prop, value, enabled) {
|
|
471
473
|
if (!enabled || !prop || value == null) {
|
|
@@ -480,10 +482,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
480
482
|
}
|
|
481
483
|
}
|
|
482
484
|
if (prop.customType && !isRaw(value)) {
|
|
483
|
-
return prop.customType.convertToDatabaseValue(value, this
|
|
485
|
+
return prop.customType.convertToDatabaseValue(value, this.#platform, {
|
|
486
|
+
fromQuery: true,
|
|
487
|
+
key: prop.name,
|
|
488
|
+
mode: 'query-data',
|
|
489
|
+
});
|
|
484
490
|
}
|
|
485
491
|
if (value instanceof Date) {
|
|
486
|
-
return this
|
|
492
|
+
return this.#platform.processDateProperty(value);
|
|
487
493
|
}
|
|
488
494
|
return value;
|
|
489
495
|
}
|
|
@@ -494,8 +500,8 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
494
500
|
*/
|
|
495
501
|
lookupInContextStack(tableNameOrAlias) {
|
|
496
502
|
// Search from top of stack (current scope) to bottom (parent scopes)
|
|
497
|
-
for (let i = this
|
|
498
|
-
const context = this
|
|
503
|
+
for (let i = this.#contextStack.length - 1; i >= 0; i--) {
|
|
504
|
+
const context = this.#contextStack[i];
|
|
499
505
|
if (context.has(tableNameOrAlias)) {
|
|
500
506
|
return context.get(tableNameOrAlias);
|
|
501
507
|
}
|
|
@@ -517,10 +523,10 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
517
523
|
if (cte.expression?.kind === 'SelectQueryNode') {
|
|
518
524
|
const sourceMeta = this.extractSourceTableFromSelectQuery(cte.expression);
|
|
519
525
|
if (sourceMeta) {
|
|
520
|
-
this
|
|
526
|
+
this.#subqueryAliasMap.set(cteName, sourceMeta);
|
|
521
527
|
// Add CTE to entityMap so it can be used for result transformation if needed
|
|
522
528
|
// (though CTEs usually don't appear in result rows directly, but their columns might)
|
|
523
|
-
this
|
|
529
|
+
this.#entityMap.set(cteName, sourceMeta);
|
|
524
530
|
}
|
|
525
531
|
}
|
|
526
532
|
}
|
|
@@ -550,11 +556,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
550
556
|
if (aliasName) {
|
|
551
557
|
context.set(aliasName, meta);
|
|
552
558
|
if (meta) {
|
|
553
|
-
this
|
|
559
|
+
this.#entityMap.set(aliasName, meta);
|
|
554
560
|
}
|
|
555
561
|
// Also map the alias in subqueryAliasMap if the table name is a CTE
|
|
556
|
-
if (this
|
|
557
|
-
this
|
|
562
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
563
|
+
this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
|
|
558
564
|
}
|
|
559
565
|
}
|
|
560
566
|
}
|
|
@@ -567,7 +573,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
567
573
|
// Try to extract the source table from the subquery
|
|
568
574
|
const sourceMeta = this.extractSourceTableFromSelectQuery(from.node);
|
|
569
575
|
if (sourceMeta) {
|
|
570
|
-
this
|
|
576
|
+
this.#subqueryAliasMap.set(aliasName, sourceMeta);
|
|
571
577
|
}
|
|
572
578
|
}
|
|
573
579
|
}
|
|
@@ -586,7 +592,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
586
592
|
const meta = this.findEntityMetadata(tableName);
|
|
587
593
|
context.set(tableName, meta);
|
|
588
594
|
if (meta) {
|
|
589
|
-
this
|
|
595
|
+
this.#entityMap.set(tableName, meta);
|
|
590
596
|
}
|
|
591
597
|
}
|
|
592
598
|
}
|
|
@@ -606,11 +612,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
606
612
|
if (aliasName) {
|
|
607
613
|
context.set(aliasName, meta);
|
|
608
614
|
if (meta) {
|
|
609
|
-
this
|
|
615
|
+
this.#entityMap.set(aliasName, meta);
|
|
610
616
|
}
|
|
611
617
|
// Also map the alias in subqueryAliasMap if the table name is a CTE
|
|
612
|
-
if (this
|
|
613
|
-
this
|
|
618
|
+
if (this.#subqueryAliasMap.has(tableName)) {
|
|
619
|
+
this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
|
|
614
620
|
}
|
|
615
621
|
}
|
|
616
622
|
}
|
|
@@ -623,7 +629,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
623
629
|
// Try to extract the source table from the subquery
|
|
624
630
|
const sourceMeta = this.extractSourceTableFromSelectQuery(joinTable.node);
|
|
625
631
|
if (sourceMeta) {
|
|
626
|
-
this
|
|
632
|
+
this.#subqueryAliasMap.set(aliasName, sourceMeta);
|
|
627
633
|
}
|
|
628
634
|
}
|
|
629
635
|
}
|
|
@@ -643,7 +649,7 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
643
649
|
// Use table name (meta.tableName) as key to match transformUpdateQuery behavior
|
|
644
650
|
if (meta) {
|
|
645
651
|
context.set(meta.tableName, meta);
|
|
646
|
-
this
|
|
652
|
+
this.#entityMap.set(meta.tableName, meta);
|
|
647
653
|
// Also set with entity name for backward compatibility
|
|
648
654
|
context.set(tableName, meta);
|
|
649
655
|
}
|
|
@@ -706,11 +712,11 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
706
712
|
* Find entity metadata by table name or entity name
|
|
707
713
|
*/
|
|
708
714
|
findEntityMetadata(name) {
|
|
709
|
-
const byEntity = this
|
|
715
|
+
const byEntity = this.#metadata.getByClassName(name, false);
|
|
710
716
|
if (byEntity) {
|
|
711
717
|
return byEntity;
|
|
712
718
|
}
|
|
713
|
-
const allMetadata = Array.from(this
|
|
719
|
+
const allMetadata = Array.from(this.#metadata);
|
|
714
720
|
const byTable = allMetadata.find(m => m.tableName === name);
|
|
715
721
|
if (byTable) {
|
|
716
722
|
return byTable;
|
|
@@ -723,7 +729,9 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
723
729
|
*/
|
|
724
730
|
transformResult(rows, entityMap) {
|
|
725
731
|
// Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
|
|
726
|
-
if ((this
|
|
732
|
+
if ((this.#options.columnNamingStrategy !== 'property' && !this.#options.convertValues) ||
|
|
733
|
+
!rows ||
|
|
734
|
+
rows.length === 0) {
|
|
727
735
|
return rows;
|
|
728
736
|
}
|
|
729
737
|
// If no entities found (e.g. raw query without known tables), return rows as is
|
|
@@ -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,35 +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
|
-
if (typeof value === 'number' ||
|
|
874
|
-
|
|
881
|
+
if (typeof value === 'number' ||
|
|
882
|
+
(typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
|
|
883
|
+
return this.#platform.parseDate(value);
|
|
875
884
|
}
|
|
876
885
|
// Append timezone if not present (only for string values)
|
|
877
|
-
return this
|
|
886
|
+
return this.#platform.parseDate(value + tz);
|
|
878
887
|
}
|
|
879
888
|
// For all other runtimeTypes (number, string, bigint, Buffer, object, any, etc.)
|
|
880
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
|
}
|
package/query/CriteriaNode.js
CHANGED
|
@@ -53,22 +53,34 @@ export class CriteriaNode {
|
|
|
53
53
|
const type = this.prop ? this.prop.kind : null;
|
|
54
54
|
const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
|
|
55
55
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
56
|
-
const scalar = payload === null ||
|
|
56
|
+
const scalar = payload === null ||
|
|
57
|
+
Utils.isPrimaryKey(payload) ||
|
|
58
|
+
payload instanceof RegExp ||
|
|
59
|
+
payload instanceof Date ||
|
|
60
|
+
rawField;
|
|
57
61
|
const operator = Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
|
|
58
62
|
if (composite) {
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
switch (type) {
|
|
62
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
63
|
-
|
|
64
|
-
case ReferenceKind.
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
case ReferenceKind.MANY_TO_ONE:
|
|
67
|
+
return false;
|
|
68
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
69
|
+
return !this.prop.owner;
|
|
70
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
71
|
+
return scalar || operator;
|
|
72
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
73
|
+
return scalar || operator;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
renameFieldToPK(qb, ownerAlias) {
|
|
70
79
|
const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
71
|
-
if (!joinAlias &&
|
|
80
|
+
if (!joinAlias &&
|
|
81
|
+
this.parent &&
|
|
82
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
|
|
83
|
+
this.prop.owner) {
|
|
72
84
|
const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
|
|
73
85
|
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
|
|
74
86
|
}
|
|
@@ -84,7 +96,9 @@ export class CriteriaNode {
|
|
|
84
96
|
const parentPath = opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
|
|
85
97
|
const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
|
|
86
98
|
// ignore group operators to allow easier mapping (e.g. for orderBy)
|
|
87
|
-
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
99
|
+
const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
|
|
100
|
+
? '.' + this.key
|
|
101
|
+
: '';
|
|
88
102
|
const ret = parentPath + index + key;
|
|
89
103
|
if (this.isPivotJoin()) {
|
|
90
104
|
// distinguish pivot table join from target entity join
|
|
@@ -97,7 +111,11 @@ export class CriteriaNode {
|
|
|
97
111
|
return false;
|
|
98
112
|
}
|
|
99
113
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
|
|
100
|
-
const scalar = this.payload === null ||
|
|
114
|
+
const scalar = this.payload === null ||
|
|
115
|
+
Utils.isPrimaryKey(this.payload) ||
|
|
116
|
+
this.payload instanceof RegExp ||
|
|
117
|
+
this.payload instanceof Date ||
|
|
118
|
+
rawField;
|
|
101
119
|
const operator = Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
|
|
102
120
|
return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
|
|
103
121
|
}
|
|
@@ -116,7 +134,7 @@ export class CriteriaNode {
|
|
|
116
134
|
const o = {};
|
|
117
135
|
['entityName', 'key', 'index', 'payload']
|
|
118
136
|
.filter(k => this[k] !== undefined)
|
|
119
|
-
.forEach(k => o[k] = this[k]);
|
|
137
|
+
.forEach(k => (o[k] = this[k]));
|
|
120
138
|
return `${this.constructor.name} ${inspect(o)}`;
|
|
121
139
|
}
|
|
122
140
|
}
|
|
@@ -2,13 +2,18 @@ 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
|
*/
|
|
8
9
|
export class CriteriaNodeFactory {
|
|
9
10
|
static createNode(metadata, entityName, payload, parent, key, validate = true) {
|
|
10
11
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
|
|
11
|
-
const scalar = Utils.isPrimaryKey(payload) ||
|
|
12
|
+
const scalar = Utils.isPrimaryKey(payload) ||
|
|
13
|
+
isRaw(payload) ||
|
|
14
|
+
payload instanceof RegExp ||
|
|
15
|
+
payload instanceof Date ||
|
|
16
|
+
rawField;
|
|
12
17
|
if (Array.isArray(payload) && !scalar) {
|
|
13
18
|
return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
|
|
14
19
|
}
|
|
@@ -65,15 +70,26 @@ export class CriteriaNodeFactory {
|
|
|
65
70
|
}, {});
|
|
66
71
|
return this.createNode(metadata, entityName, map, node, key, validate);
|
|
67
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
|
+
}
|
|
68
85
|
// array operators can be used on embedded properties
|
|
69
|
-
const
|
|
70
|
-
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));
|
|
71
87
|
if (operator) {
|
|
72
88
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
|
|
73
89
|
}
|
|
74
90
|
const map = Object.keys(val).reduce((oo, k) => {
|
|
75
91
|
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
|
|
76
|
-
if (!embeddedProp && !
|
|
92
|
+
if (!embeddedProp && !EMBEDDABLE_ARRAY_OPS.includes(k)) {
|
|
77
93
|
throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
|
|
78
94
|
}
|
|
79
95
|
if (embeddedProp) {
|