@mikro-orm/sql 7.0.0-dev.216 → 7.0.0-dev.218

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.
@@ -98,12 +98,20 @@ export class QueryBuilder {
98
98
  }
99
99
  select(fields, distinct = false) {
100
100
  this.ensureNotFinalized();
101
- this._fields = Utils.asArray(fields);
101
+ this._fields = Utils.asArray(fields).flatMap(f => {
102
+ if (typeof f !== 'string') {
103
+ return f;
104
+ }
105
+ return this.resolveNestedPath(f);
106
+ });
102
107
  if (distinct) {
103
108
  this.flags.add(QueryFlag.DISTINCT);
104
109
  }
105
110
  return this.init(QueryType.SELECT);
106
111
  }
112
+ /**
113
+ * Adds fields to an existing SELECT query.
114
+ */
107
115
  addSelect(fields) {
108
116
  this.ensureNotFinalized();
109
117
  if (this._type && this._type !== QueryType.SELECT) {
@@ -115,24 +123,81 @@ export class QueryBuilder {
115
123
  this.ensureNotFinalized();
116
124
  return this.setFlag(QueryFlag.DISTINCT);
117
125
  }
118
- /** postgres only */
119
126
  distinctOn(fields) {
120
127
  this.ensureNotFinalized();
121
128
  this._distinctOn = Utils.asArray(fields);
122
129
  return this;
123
130
  }
131
+ /**
132
+ * Creates an INSERT query with the given data.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * await em.createQueryBuilder(User)
137
+ * .insert({ name: 'John', email: 'john@example.com' })
138
+ * .execute();
139
+ *
140
+ * // Bulk insert
141
+ * await em.createQueryBuilder(User)
142
+ * .insert([{ name: 'John' }, { name: 'Jane' }])
143
+ * .execute();
144
+ * ```
145
+ */
124
146
  insert(data) {
125
147
  return this.init(QueryType.INSERT, data);
126
148
  }
149
+ /**
150
+ * Creates an UPDATE query with the given data.
151
+ * Use `where()` to specify which rows to update.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * await em.createQueryBuilder(User)
156
+ * .update({ name: 'John Doe' })
157
+ * .where({ id: 1 })
158
+ * .execute();
159
+ * ```
160
+ */
127
161
  update(data) {
128
162
  return this.init(QueryType.UPDATE, data);
129
163
  }
164
+ /**
165
+ * Creates a DELETE query.
166
+ * Use `where()` to specify which rows to delete.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * await em.createQueryBuilder(User)
171
+ * .delete()
172
+ * .where({ id: 1 })
173
+ * .execute();
174
+ *
175
+ * // Or pass the condition directly
176
+ * await em.createQueryBuilder(User)
177
+ * .delete({ isActive: false })
178
+ * .execute();
179
+ * ```
180
+ */
130
181
  delete(cond) {
131
182
  return this.init(QueryType.DELETE, undefined, cond);
132
183
  }
184
+ /**
185
+ * Creates a TRUNCATE query to remove all rows from the table.
186
+ */
133
187
  truncate() {
134
188
  return this.init(QueryType.TRUNCATE);
135
189
  }
190
+ /**
191
+ * Creates a COUNT query to count matching rows.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * const count = await em.createQueryBuilder(User)
196
+ * .count()
197
+ * .where({ isActive: true })
198
+ * .execute('get');
199
+ * ```
200
+ */
136
201
  count(field, distinct = false) {
137
202
  if (field) {
138
203
  this._fields = Utils.asArray(field);
@@ -156,16 +221,27 @@ export class QueryBuilder {
156
221
  this.join(field, alias, cond, JoinType.innerJoin, undefined, schema);
157
222
  return this;
158
223
  }
159
- innerJoinLateral(field, alias, cond, schema) {
160
- this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
161
- return this;
224
+ innerJoinLateral(field, alias, cond = {}, schema) {
225
+ return this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
162
226
  }
163
227
  leftJoin(field, alias, cond = {}, schema) {
164
228
  return this.join(field, alias, cond, JoinType.leftJoin, undefined, schema);
165
229
  }
166
- leftJoinLateral(field, alias, cond, schema) {
230
+ leftJoinLateral(field, alias, cond = {}, schema) {
167
231
  return this.join(field, alias, cond, JoinType.leftJoinLateral, undefined, schema);
168
232
  }
233
+ /**
234
+ * Adds a JOIN clause and automatically selects the joined entity's fields.
235
+ * This is useful for eager loading related entities.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * const qb = em.createQueryBuilder(Book, 'b');
240
+ * qb.select('*')
241
+ * .joinAndSelect('b.author', 'a')
242
+ * .where({ 'a.name': 'John' });
243
+ * ```
244
+ */
169
245
  joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) {
170
246
  if (!this._type) {
171
247
  this.select('*');
@@ -197,13 +273,15 @@ export class QueryBuilder {
197
273
  return this.joinAndSelect(field, alias, cond, JoinType.leftJoin, undefined, fields, schema);
198
274
  }
199
275
  leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
200
- return this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema);
276
+ this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema);
277
+ return this;
201
278
  }
202
279
  innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
203
280
  return this.joinAndSelect(field, alias, cond, JoinType.innerJoin, undefined, fields, schema);
204
281
  }
205
282
  innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
206
- return this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema);
283
+ this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema);
284
+ return this;
207
285
  }
208
286
  getFieldsForJoinedLoad(prop, alias, explicitFields) {
209
287
  const fields = [];
@@ -278,7 +356,13 @@ export class QueryBuilder {
278
356
  filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
279
357
  let cond = await em.applyFilters(join.prop.targetMeta.class, join.cond, filterOptions, 'read');
280
358
  const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, join.prop.targetMeta.class, cond);
281
- cond = criteriaNode.process(this, { matchPopulateJoins: true, filter: true, alias: join.alias, ignoreBranching: true, parentPath: join.path });
359
+ cond = criteriaNode.process(this, {
360
+ matchPopulateJoins: true,
361
+ filter: true,
362
+ alias: join.alias,
363
+ ignoreBranching: true,
364
+ parentPath: join.path,
365
+ });
282
366
  if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
283
367
  // remove nested filters, we only care about scalars here, nesting would require another join branch
284
368
  for (const key of Object.keys(cond)) {
@@ -308,17 +392,18 @@ export class QueryBuilder {
308
392
  }
309
393
  where(cond, params, operator) {
310
394
  this.ensureNotFinalized();
395
+ let processedCond;
311
396
  if (isRaw(cond)) {
312
397
  const sql = this.platform.formatQuery(cond.sql, cond.params);
313
- cond = { [raw(`(${sql})`)]: Utils.asArray(params) };
398
+ processedCond = { [raw(`(${sql})`)]: Utils.asArray(params) };
314
399
  operator ??= '$and';
315
400
  }
316
401
  else if (typeof cond === 'string') {
317
- cond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
402
+ processedCond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
318
403
  operator ??= '$and';
319
404
  }
320
405
  else {
321
- cond = QueryHelper.processWhere({
406
+ processedCond = QueryHelper.processWhere({
322
407
  where: cond,
323
408
  entityName: this.mainAlias.entityName,
324
409
  metadata: this.metadata,
@@ -330,7 +415,7 @@ export class QueryBuilder {
330
415
  }
331
416
  const op = operator || params;
332
417
  const topLevel = !op || !(Utils.hasObjectKeys(this._cond) || RawQueryFragment.hasObjectFragments(this._cond));
333
- const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond);
418
+ const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
334
419
  const ignoreBranching = this.__populateWhere === 'infer';
335
420
  if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
336
421
  // use sub-query to support joining
@@ -382,27 +467,44 @@ export class QueryBuilder {
382
467
  convertCustomTypes: false,
383
468
  type: 'orderBy',
384
469
  });
385
- this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true, type: 'orderBy' }));
386
- // this._orderBy.push(CriteriaNodeFactory.createNode<Entity>(this.metadata, Utils.className(this.mainAlias.entityName), processed).process(this, { matchPopulateJoins: true, type: 'orderBy' }));
470
+ this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
471
+ matchPopulateJoins: true,
472
+ type: 'orderBy',
473
+ }));
387
474
  });
388
475
  return this;
389
476
  }
390
477
  groupBy(fields) {
391
478
  this.ensureNotFinalized();
392
- this._groupBy = Utils.asArray(fields);
479
+ this._groupBy = Utils.asArray(fields).flatMap(f => {
480
+ if (typeof f !== 'string') {
481
+ return f;
482
+ }
483
+ return this.resolveNestedPath(f);
484
+ });
393
485
  return this;
394
486
  }
487
+ /**
488
+ * Adds a HAVING clause to the query, typically used with GROUP BY.
489
+ *
490
+ * @example
491
+ * ```ts
492
+ * qb.select([raw('count(*) as count'), 'status'])
493
+ * .groupBy('status')
494
+ * .having({ count: { $gt: 5 } });
495
+ * ```
496
+ */
395
497
  having(cond = {}, params, operator) {
396
498
  this.ensureNotFinalized();
397
499
  if (typeof cond === 'string') {
398
500
  cond = { [raw(`(${cond})`, params)]: [] };
399
501
  }
400
- cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
502
+ const processed = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond, undefined, undefined, false).process(this);
401
503
  if (!this._having || !operator) {
402
- this._having = cond;
504
+ this._having = processed;
403
505
  }
404
506
  else {
405
- const cond1 = [this._having, cond];
507
+ const cond1 = [this._having, processed];
406
508
  this._having = { [operator]: cond1 };
407
509
  }
408
510
  return this;
@@ -459,6 +561,15 @@ export class QueryBuilder {
459
561
  this._populateFilter = populateFilter;
460
562
  return this;
461
563
  }
564
+ /**
565
+ * Sets a LIMIT clause to restrict the number of results.
566
+ *
567
+ * @example
568
+ * ```ts
569
+ * qb.select('*').limit(10); // First 10 results
570
+ * qb.select('*').limit(10, 20); // 10 results starting from offset 20
571
+ * ```
572
+ */
462
573
  limit(limit, offset = 0) {
463
574
  this.ensureNotFinalized();
464
575
  this._limit = limit;
@@ -467,6 +578,14 @@ export class QueryBuilder {
467
578
  }
468
579
  return this;
469
580
  }
581
+ /**
582
+ * Sets an OFFSET clause to skip a number of results.
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * qb.select('*').limit(10).offset(20); // Results 21-30
587
+ * ```
588
+ */
470
589
  offset(offset) {
471
590
  this.ensureNotFinalized();
472
591
  this._offset = offset;
@@ -837,16 +956,13 @@ export class QueryBuilder {
837
956
  const [res] = await this.getResultList(1);
838
957
  return res || null;
839
958
  }
840
- /**
841
- * Executes count query (without offset and limit), returning total count of results
842
- */
843
959
  async getCount(field, distinct) {
844
960
  let res;
845
961
  if (this.type === QueryType.COUNT) {
846
962
  res = await this.execute('get', false);
847
963
  }
848
964
  else {
849
- const qb = this._type === undefined ? this : this.clone();
965
+ const qb = (this._type === undefined ? this : this.clone());
850
966
  qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
851
967
  qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
852
968
  res = await qb.execute('get', false);
@@ -857,10 +973,7 @@ export class QueryBuilder {
857
973
  * Executes the query, returning both array of results and total count query (without offset and limit).
858
974
  */
859
975
  async getResultAndCount() {
860
- return [
861
- await this.clone().getResultList(),
862
- await this.clone().getCount(),
863
- ];
976
+ return [await this.clone().getResultList(), await this.clone().getCount()];
864
977
  }
865
978
  as(aliasOrTargetEntity, alias) {
866
979
  const qb = this.getNativeQuery();
@@ -1066,7 +1179,9 @@ export class QueryBuilder {
1066
1179
  ret.push(getFieldName(field));
1067
1180
  });
1068
1181
  const requiresSQLConversion = this.mainAlias.meta.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false);
1069
- if (this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
1182
+ if (this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
1183
+ (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) &&
1184
+ requiresSQLConversion.length > 0) {
1070
1185
  for (const p of requiresSQLConversion) {
1071
1186
  ret.push(this.helper.mapper(p.name, this.type));
1072
1187
  }
@@ -1078,6 +1193,84 @@ export class QueryBuilder {
1078
1193
  }
1079
1194
  return Utils.unique(ret);
1080
1195
  }
1196
+ /**
1197
+ * Resolves nested paths like `a.books.title` to their actual field references.
1198
+ * Auto-joins relations as needed and returns `{alias}.{field}`.
1199
+ * For embeddeds: navigates into flattened embeddeds to return the correct field name.
1200
+ */
1201
+ resolveNestedPath(field) {
1202
+ if (typeof field !== 'string' || !field.includes('.')) {
1203
+ return field;
1204
+ }
1205
+ const parts = field.split('.');
1206
+ // Simple alias.property case - let prepareFields handle it
1207
+ if (parts.length === 2 && this._aliases[parts[0]]) {
1208
+ return field;
1209
+ }
1210
+ // Start with root alias
1211
+ let currentAlias = parts[0];
1212
+ let currentMeta = this._aliases[currentAlias] ? this.metadata.get(this._aliases[currentAlias].entityName) : this.mainAlias.meta;
1213
+ // If first part is not an alias, it's a property of the main entity
1214
+ if (!this._aliases[currentAlias]) {
1215
+ currentAlias = this.mainAlias.aliasName;
1216
+ parts.unshift(currentAlias);
1217
+ }
1218
+ // Walk through the path parts (skip the alias)
1219
+ for (let i = 1; i < parts.length; i++) {
1220
+ const propName = parts[i];
1221
+ const prop = currentMeta.properties[propName];
1222
+ if (!prop) {
1223
+ return field; // Unknown property, return as-is for raw SQL support
1224
+ }
1225
+ const isLastPart = i === parts.length - 1;
1226
+ // Handle embedded properties - navigate into flattened embeddeds
1227
+ if (prop.kind === ReferenceKind.EMBEDDED) {
1228
+ if (prop.object) {
1229
+ return `${currentAlias}.${propName}`;
1230
+ }
1231
+ // Navigate through remaining path to find the leaf property
1232
+ const remainingPath = parts.slice(i + 1);
1233
+ let embeddedProp = prop;
1234
+ for (const part of remainingPath) {
1235
+ embeddedProp = embeddedProp?.embeddedProps?.[part];
1236
+ if (embeddedProp?.object && embeddedProp.fieldNames?.[0]) {
1237
+ return `${currentAlias}.${embeddedProp.fieldNames[0]}`;
1238
+ }
1239
+ }
1240
+ return `${currentAlias}.${embeddedProp?.fieldNames?.[0] ?? propName}`;
1241
+ }
1242
+ // Handle relations - auto-join if not the last part
1243
+ if (prop.kind === ReferenceKind.MANY_TO_ONE ||
1244
+ prop.kind === ReferenceKind.ONE_TO_ONE ||
1245
+ prop.kind === ReferenceKind.ONE_TO_MANY ||
1246
+ prop.kind === ReferenceKind.MANY_TO_MANY) {
1247
+ if (isLastPart) {
1248
+ return `${currentAlias}.${propName}`;
1249
+ }
1250
+ // Find existing join or create new one
1251
+ const joinPath = parts.slice(0, i + 1).join('.');
1252
+ const existingJoinKey = Object.keys(this._joins).find(k => {
1253
+ const join = this._joins[k];
1254
+ // Check by path or by key prefix (key format is `alias.field#joinAlias`)
1255
+ return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`);
1256
+ });
1257
+ let joinAlias;
1258
+ if (existingJoinKey) {
1259
+ joinAlias = this._joins[existingJoinKey].alias;
1260
+ }
1261
+ else {
1262
+ joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName);
1263
+ this.join(`${currentAlias}.${propName}`, joinAlias, {}, JoinType.leftJoin);
1264
+ }
1265
+ currentAlias = joinAlias;
1266
+ currentMeta = prop.targetMeta;
1267
+ continue;
1268
+ }
1269
+ // Scalar property - return it (if not last part, it's an invalid path but let SQL handle it)
1270
+ return `${currentAlias}.${propName}`;
1271
+ }
1272
+ return field;
1273
+ }
1081
1274
  init(type, data, cond) {
1082
1275
  this.ensureNotFinalized();
1083
1276
  this._type = type;
@@ -1344,7 +1537,10 @@ export class QueryBuilder {
1344
1537
  wrapPaginateSubQuery(meta) {
1345
1538
  const schema = this.getSchema(this.mainAlias);
1346
1539
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
1347
- const subQuery = this.clone(['_orderBy', '_fields', 'lockMode', 'lockTableAliases']).select(pks).groupBy(pks).limit(this._limit);
1540
+ const subQuery = this.clone(['_orderBy', '_fields', 'lockMode', 'lockTableAliases'])
1541
+ .select(pks)
1542
+ .groupBy(pks)
1543
+ .limit(this._limit);
1348
1544
  // revert the on conditions added via populateWhere, we want to apply those only once
1349
1545
  for (const join of Object.values(subQuery._joins)) {
1350
1546
  if (join.cond_) {
@@ -1,6 +1,6 @@
1
- import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type FlatQueryOrderMap, type FormulaTable, LockMode, type QBFilterQuery, type QBQueryOrderMap, Raw, type RawQueryFragmentSymbol } from '@mikro-orm/core';
1
+ import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FlatQueryOrderMap, type FormulaTable, LockMode, type QueryOrderMap, Raw, type RawQueryFragmentSymbol } from '@mikro-orm/core';
2
2
  import { JoinType, QueryType } from './enums.js';
3
- import type { Field, JoinOptions } from '../typings.js';
3
+ import type { InternalField, JoinOptions } from '../typings.js';
4
4
  import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
5
5
  import { NativeQueryBuilder } from './NativeQueryBuilder.js';
6
6
  /**
@@ -45,10 +45,10 @@ export declare class QueryBuilderHelper {
45
45
  private processObjectSubCondition;
46
46
  private getValueReplacement;
47
47
  private getOperatorReplacement;
48
- validateQueryOrder<T>(orderBy: QBQueryOrderMap<T>): void;
48
+ validateQueryOrder<T>(orderBy: QueryOrderMap<T>): void;
49
49
  getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string[];
50
50
  getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
51
- finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
51
+ finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: InternalField<any>[]): void;
52
52
  splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
53
53
  getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
54
54
  updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
@@ -58,7 +58,7 @@ export declare class QueryBuilderHelper {
58
58
  private fieldName;
59
59
  getProperty(field: string, alias?: string): EntityProperty | undefined;
60
60
  isTableNameAliasRequired(type: QueryType): boolean;
61
- processOnConflictCondition(cond: QBFilterQuery, schema?: string): QBFilterQuery;
61
+ processOnConflictCondition(cond: FilterQuery<any>, schema?: string): FilterQuery<any>;
62
62
  createFormulaTable(alias: string, meta: EntityMetadata, schema?: string): FormulaTable;
63
63
  }
64
64
  export interface Alias<T> {
@@ -70,6 +70,6 @@ export interface Alias<T> {
70
70
  export interface OnConflictClause<T> {
71
71
  fields: string[] | Raw;
72
72
  ignore?: boolean;
73
- merge?: EntityData<T> | Field<T>[];
74
- where?: QBFilterQuery<T>;
73
+ merge?: EntityData<T> | InternalField<T>[];
74
+ where?: FilterQuery<T>;
75
75
  }
@@ -68,10 +68,6 @@ export class QueryBuilderHelper {
68
68
  const prop = this.getProperty(f, a);
69
69
  const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
70
70
  const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
71
- // embeddable nested path instead of a regular property with table alias, reset alias
72
- if (prop?.name === a && prop.embeddedProps[f]) {
73
- return aliasPrefix + prop.fieldNames[fkIdx];
74
- }
75
71
  if (a === prop?.embedded?.[0]) {
76
72
  return aliasPrefix + prop.fieldNames[fkIdx];
77
73
  }
@@ -621,14 +617,15 @@ export class QueryBuilderHelper {
621
617
  if (type === QueryType.UPDATE) {
622
618
  const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
623
619
  if (returningProps.length > 0) {
624
- qb.returning(returningProps.flatMap(prop => {
620
+ const fields = returningProps.flatMap((prop) => {
625
621
  if (prop.hasConvertToJSValueSQL) {
626
622
  const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
627
623
  const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]);
628
624
  return [raw(sql)];
629
625
  }
630
626
  return prop.fieldNames;
631
- }));
627
+ });
628
+ qb.returning(fields);
632
629
  }
633
630
  }
634
631
  }
@@ -753,10 +750,6 @@ export class QueryBuilderHelper {
753
750
  if (alias) {
754
751
  const prop = meta.properties[alias];
755
752
  if (prop?.kind === ReferenceKind.EMBEDDED) {
756
- // we want to select the full object property so hydration works as expected
757
- if (prop.object) {
758
- return prop;
759
- }
760
753
  const parts = field.split('.');
761
754
  const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
762
755
  return nest(prop);
package/query/raw.d.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import { type AnyString, type Dictionary, type EntityKey, type RawQueryFragment } from '@mikro-orm/core';
2
- import type { SelectQueryBuilder } from 'kysely';
3
- import { QueryBuilder } from './QueryBuilder.js';
2
+ import type { SelectQueryBuilder as KyselySelectQueryBuilder } from 'kysely';
3
+ /** @internal Type for QueryBuilder instances passed to raw() - uses toRaw to distinguish from Kysely QueryBuilder */
4
+ type QueryBuilderLike = {
5
+ toQuery(): {
6
+ sql: string;
7
+ params: readonly unknown[];
8
+ };
9
+ toRaw(): RawQueryFragment;
10
+ };
4
11
  /**
5
12
  * Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
6
13
  * by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
@@ -56,4 +63,5 @@ import { QueryBuilder } from './QueryBuilder.js';
56
63
  * export class Author { ... }
57
64
  * ```
58
65
  */
59
- export declare function raw<T extends object = any, R = any>(sql: SelectQueryBuilder<any, any, any> | QueryBuilder<T> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R>;
66
+ export declare function raw<R = RawQueryFragment & symbol, T extends object = any>(sql: QueryBuilderLike | KyselySelectQueryBuilder<any, any, any> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
67
+ export {};
package/query/raw.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { raw as raw_, Utils } from '@mikro-orm/core';
2
- import { QueryBuilder } from './QueryBuilder.js';
3
2
  /**
4
3
  * Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
5
4
  * by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
@@ -60,7 +59,7 @@ export function raw(sql, params) {
60
59
  const query = sql.compile();
61
60
  return raw_(query.sql, query.parameters);
62
61
  }
63
- if (sql instanceof QueryBuilder) {
62
+ if (Utils.isObject(sql) && 'toQuery' in sql) {
64
63
  const query = sql.toQuery();
65
64
  return raw_(query.sql, query.params);
66
65
  }