@mikro-orm/knex 6.6.5-dev.4 → 6.6.5-dev.6

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.
@@ -61,7 +61,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
61
61
  /**
62
62
  * @internal
63
63
  */
64
- mapPropToFieldNames<T extends object>(qb: QueryBuilder<T, any, any, any>, prop: EntityProperty<T>, tableAlias?: string, explicitFields?: Field<T>[]): Field<T>[];
64
+ mapPropToFieldNames<T extends object>(qb: QueryBuilder<T, any, any, any>, prop: EntityProperty<T>, tableAlias: string | undefined, meta: EntityMetadata<T>, schema?: string, explicitFields?: readonly Field<T>[]): Field<T>[];
65
65
  /** @internal */
66
66
  createQueryBuilder<T extends object>(entityName: EntityName<T> | QueryBuilder<T, any, any, any>, ctx?: Transaction<Knex.Transaction>, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggingOptions, alias?: string, em?: SqlEntityManager): QueryBuilder<T, any, any, any>;
67
67
  protected resolveConnectionType(args: {
@@ -77,5 +77,5 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
77
77
  protected buildJoinedPropsOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options?: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>, parentPath?: string): QueryOrderMap<T>[];
78
78
  protected normalizeFields<T extends object>(fields: Field<T>[], prefix?: string): string[];
79
79
  protected processField<T extends object>(meta: EntityMetadata<T>, prop: EntityProperty<T> | undefined, field: string, ret: Field<T>[]): void;
80
- protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T, any, any, any>, alias: string, options: Pick<FindOptions<T, any, any, any>, 'strategy' | 'fields' | 'exclude'>): Field<T>[];
80
+ protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T, any, any, any>, alias: string, options: Pick<FindOptions<T, any, any, any>, 'strategy' | 'fields' | 'exclude'>, schema?: string): Field<T>[];
81
81
  }
@@ -31,9 +31,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
31
31
  }
32
32
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
33
33
  const joinedProps = this.joinedProps(meta, populate, options);
34
+ const schema = this.getSchemaName(meta, options);
34
35
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
35
- .withSchema(this.getSchemaName(meta, options));
36
- const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
36
+ .withSchema(schema);
37
+ const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
37
38
  const orderBy = this.buildOrderBy(qb, meta, populate, options);
38
39
  const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
39
40
  core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
@@ -250,7 +251,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
250
251
  relationPojo[prop.name] = root[alias];
251
252
  }
252
253
  });
253
- const mapToPk = !!(ref || prop.mapToPk);
254
+ const mapToPk = !hint.dataOnly && !!(ref || prop.mapToPk);
254
255
  const targetProps = mapToPk
255
256
  ? meta2.getPrimaryProps()
256
257
  : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
@@ -320,10 +321,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
320
321
  options = { populate: [], ...options };
321
322
  const populate = options.populate;
322
323
  const joinedProps = this.joinedProps(meta, populate, options);
324
+ const schema = this.getSchemaName(meta, options);
323
325
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
324
326
  const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
325
327
  if (meta && !core_1.Utils.isEmpty(populate)) {
326
- this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
328
+ this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
327
329
  }
328
330
  qb.__populateWhere = options._populateWhere;
329
331
  qb.indexHint(options.indexHint)
@@ -332,7 +334,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
332
334
  .groupBy(options.groupBy)
333
335
  .having(options.having)
334
336
  .populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
335
- .withSchema(this.getSchemaName(meta, options))
337
+ .withSchema(schema)
336
338
  .where(where);
337
339
  if (options.em) {
338
340
  await qb.applyJoinedFilters(options.em, options.filters);
@@ -437,19 +439,21 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
437
439
  }
438
440
  if (prop.fieldNames.length > 1) {
439
441
  const newFields = [];
440
- const allParam = [...core_1.Utils.asArray(row[prop.name]) ?? prop.fieldNames.map(() => null)];
442
+ const rawParam = core_1.Utils.asArray(row[prop.name]) ?? prop.fieldNames.map(() => null);
443
+ // Deep flatten nested arrays when needed (for deeply nested composite keys like Tag -> Comment -> Post -> User)
444
+ const needsFlatten = rawParam.length !== prop.fieldNames.length && rawParam.some(v => Array.isArray(v));
445
+ const allParam = needsFlatten ? core_1.Utils.flatten(rawParam, true) : rawParam;
441
446
  // TODO(v7): instead of making this conditional here, the entity snapshot should respect `ownColumns`,
442
447
  // but that means changing the compiled PK getters, which might be seen as breaking
443
448
  const columns = allParam.length > 1 ? prop.fieldNames : prop.ownColumns;
444
- const newParam = [];
449
+ const param = [];
445
450
  columns.forEach((field, idx) => {
446
451
  if (usedDups.includes(field)) {
447
452
  return;
448
453
  }
449
454
  newFields.push(field);
450
- newParam.push(allParam[idx]);
455
+ param.push(allParam[idx]);
451
456
  });
452
- const param = core_1.Utils.flatten(newParam);
453
457
  newFields.forEach((field, idx) => {
454
458
  if (!duplicates.includes(field) || !usedDups.includes(field)) {
455
459
  params.push(param[idx]);
@@ -801,7 +805,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
801
805
  fields,
802
806
  exclude: childExclude,
803
807
  orderBy: this.getPivotOrderBy(prop, pivotProp1, orderBy, options?.orderBy),
804
- populate: [{ field: populateField, strategy: core_1.LoadStrategy.JOINED, joinType: query_1.JoinType.innerJoin, children: populate }],
808
+ populate: [{ field: populateField, strategy: core_1.LoadStrategy.JOINED, joinType: query_1.JoinType.innerJoin, children: populate, dataOnly: pivotProp1.mapToPk && !pivotJoin }],
805
809
  populateWhere: undefined,
806
810
  // @ts-ignore
807
811
  _populateWhere: 'infer',
@@ -950,7 +954,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
950
954
  // alias all fields in the primary table
951
955
  meta.props
952
956
  .filter(prop => this.shouldHaveColumn(meta, prop, populate, explicitFields, exclude))
953
- .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias, explicitFields)));
957
+ .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias, meta, undefined, explicitFields)));
954
958
  }
955
959
  for (const hint of joinedProps) {
956
960
  const [propName, ref] = hint.field.split(':', 2);
@@ -989,10 +993,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
989
993
  }
990
994
  });
991
995
  const childExclude = exclude ? core_1.Utils.extractChildElements(exclude, prop.name) : exclude;
992
- if (!ref && !prop.mapToPk) {
996
+ if (!ref && (!prop.mapToPk || hint.dataOnly)) {
993
997
  fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, childExclude, hint.children, options, tableAlias, path));
994
998
  }
995
- else if (hint.filter || prop.mapToPk || (ref && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
999
+ else if (hint.filter || (prop.mapToPk && !hint.dataOnly) || (ref && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
996
1000
  fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
997
1001
  }
998
1002
  }
@@ -1001,14 +1005,14 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
1001
1005
  /**
1002
1006
  * @internal
1003
1007
  */
1004
- mapPropToFieldNames(qb, prop, tableAlias, explicitFields) {
1008
+ mapPropToFieldNames(qb, prop, tableAlias, meta, schema, explicitFields) {
1005
1009
  if (prop.kind === core_1.ReferenceKind.EMBEDDED && !prop.object) {
1006
1010
  return Object.entries(prop.embeddedProps).flatMap(([name, childProp]) => {
1007
1011
  const childFields = explicitFields ? core_1.Utils.extractChildElements(explicitFields, prop.name) : [];
1008
1012
  if (!this.shouldHaveColumn(prop.targetMeta, { ...childProp, name }, [], childFields.length > 0 ? childFields : undefined)) {
1009
1013
  return [];
1010
1014
  }
1011
- return this.mapPropToFieldNames(qb, childProp, tableAlias, childFields);
1015
+ return this.mapPropToFieldNames(qb, childProp, tableAlias, meta, schema, childFields);
1012
1016
  });
1013
1017
  }
1014
1018
  const aliased = this.platform.quoteIdentifier(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]);
@@ -1028,7 +1032,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
1028
1032
  }
1029
1033
  if (prop.formula) {
1030
1034
  const alias = this.platform.quoteIdentifier(tableAlias ?? qb.alias);
1031
- return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
1035
+ const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
1036
+ const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
1037
+ const table = {
1038
+ alias: alias.toString(),
1039
+ name: meta.tableName,
1040
+ schema: effectiveSchema,
1041
+ qualifiedName,
1042
+ toString: () => alias.toString(),
1043
+ };
1044
+ const columns = meta.createColumnMappingObject();
1045
+ return [(0, core_1.raw)(`${(prop.formula(table, columns))} as ${aliased}`)];
1032
1046
  }
1033
1047
  if (tableAlias) {
1034
1048
  return prop.fieldNames.map(fieldName => {
@@ -1258,7 +1272,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
1258
1272
  }
1259
1273
  ret.push(prop.name);
1260
1274
  }
1261
- buildFields(meta, populate, joinedProps, qb, alias, options) {
1275
+ buildFields(meta, populate, joinedProps, qb, alias, options, schema) {
1262
1276
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
1263
1277
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
1264
1278
  const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
@@ -1303,6 +1317,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
1303
1317
  ret.push('*');
1304
1318
  }
1305
1319
  if (ret.length > 0 && !hasExplicitFields && addFormulas) {
1320
+ const columns = meta.createColumnMappingObject();
1321
+ const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
1306
1322
  for (const prop of meta.props) {
1307
1323
  if (lazyProps.includes(prop)) {
1308
1324
  continue;
@@ -1310,7 +1326,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
1310
1326
  if (prop.formula) {
1311
1327
  const a = this.platform.quoteIdentifier(alias);
1312
1328
  const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1313
- ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
1329
+ const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
1330
+ const table = {
1331
+ alias: a.toString(),
1332
+ name: meta.tableName,
1333
+ schema: effectiveSchema,
1334
+ qualifiedName,
1335
+ toString: () => a.toString(),
1336
+ };
1337
+ ret.push((0, core_1.raw)(`${(prop.formula(table, columns))} as ${aliased}`));
1314
1338
  }
1315
1339
  if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
1316
1340
  ret.push(prop.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/knex",
3
- "version": "6.6.5-dev.4",
3
+ "version": "6.6.5-dev.6",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -66,7 +66,7 @@
66
66
  "@mikro-orm/core": "^6.6.4"
67
67
  },
68
68
  "peerDependencies": {
69
- "@mikro-orm/core": "6.6.5-dev.4",
69
+ "@mikro-orm/core": "6.6.5-dev.6",
70
70
  "better-sqlite3": "*",
71
71
  "libsql": "*",
72
72
  "mariadb": "*"
@@ -222,6 +222,8 @@ class QueryBuilder {
222
222
  const fields = [];
223
223
  const populate = [];
224
224
  const joinKey = Object.keys(this._joins).find(join => join.endsWith(`#${alias}`));
225
+ const targetMeta = prop.targetMeta;
226
+ const schema = this._schema ?? (targetMeta.schema !== '*' ? targetMeta.schema : undefined);
225
227
  if (joinKey) {
226
228
  const path = this._joins[joinKey].path.split('.').slice(1);
227
229
  let children = this._populate;
@@ -234,29 +236,29 @@ class QueryBuilder {
234
236
  }
235
237
  populate.push(...children);
236
238
  }
237
- for (const p of prop.targetMeta.getPrimaryProps()) {
238
- fields.push(...this.driver.mapPropToFieldNames(this, p, alias));
239
+ for (const p of targetMeta.getPrimaryProps()) {
240
+ fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
239
241
  }
240
242
  if (explicitFields) {
241
243
  for (const field of explicitFields) {
242
244
  const [a, f] = this.helper.splitField(field);
243
- const p = prop.targetMeta.properties[f];
245
+ const p = targetMeta.properties[f];
244
246
  if (p) {
245
- fields.push(...this.driver.mapPropToFieldNames(this, p, alias));
247
+ fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
246
248
  }
247
249
  else {
248
250
  fields.push(`${a}.${f} as ${a}__${f}`);
249
251
  }
250
252
  }
251
253
  }
252
- prop.targetMeta.props
254
+ targetMeta.props
253
255
  .filter(prop => {
254
256
  if (!explicitFields) {
255
257
  return this.platform.shouldHaveColumn(prop, populate);
256
258
  }
257
259
  return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
258
260
  })
259
- .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
261
+ .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias, targetMeta, schema)));
260
262
  return fields;
261
263
  }
262
264
  /**
@@ -1164,12 +1166,15 @@ class QueryBuilder {
1164
1166
  this.processPopulateHint();
1165
1167
  this.processNestedJoins();
1166
1168
  if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
1169
+ const schema = this.getSchema(this.mainAlias);
1170
+ const columns = meta.createColumnMappingObject();
1167
1171
  meta.props
1168
1172
  .filter(prop => prop.formula && (!prop.lazy || this.flags.has(core_1.QueryFlag.INCLUDE_LAZY_FORMULAS)))
1169
1173
  .map(prop => {
1170
- const alias = this.knex.ref(this.mainAlias.aliasName).toString();
1171
- const aliased = this.knex.ref(prop.fieldNames[0]).toString();
1172
- return `${prop.formula(alias)} as ${aliased}`;
1174
+ const alias = this.platform.quoteIdentifier(this.mainAlias.aliasName);
1175
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1176
+ const table = this.helper.createFormulaTable(alias.toString(), meta, schema);
1177
+ return `${(prop.formula(table, columns))} as ${aliased}`;
1173
1178
  })
1174
1179
  .filter(field => !this._fields.some(f => {
1175
1180
  if (f instanceof core_1.RawQueryFragment) {
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, LockMode, type QBFilterQuery, RawQueryFragment } from '@mikro-orm/core';
2
+ import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, type FormulaTable, LockMode, type QBFilterQuery, RawQueryFragment } from '@mikro-orm/core';
3
3
  import { JoinType, QueryType } from './enums';
4
4
  import type { Field, JoinOptions } from '../typings';
5
5
  import type { AbstractSqlDriver } from '../AbstractSqlDriver';
@@ -17,7 +17,7 @@ export declare class QueryBuilderHelper {
17
17
  private readonly metadata;
18
18
  constructor(entityName: string, alias: string, aliasMap: Dictionary<Alias<any>>, subQueries: Dictionary<string>, knex: Knex, driver: AbstractSqlDriver);
19
19
  mapper(field: string | Knex.Raw, type?: QueryType): string;
20
- mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null): string;
20
+ mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null, schema?: string): string;
21
21
  processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
22
22
  joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
23
23
  joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
@@ -60,6 +60,7 @@ export declare class QueryBuilderHelper {
60
60
  getProperty(field: string, alias?: string): EntityProperty | undefined;
61
61
  isTableNameAliasRequired(type?: QueryType): boolean;
62
62
  processOnConflictCondition(cond: QBFilterQuery, schema?: string): QBFilterQuery;
63
+ createFormulaTable(alias: string, meta: EntityMetadata, schema?: string): FormulaTable;
63
64
  }
64
65
  export interface Alias<T> {
65
66
  aliasName: string;
@@ -26,7 +26,7 @@ class QueryBuilderHelper {
26
26
  this.platform = this.driver.getPlatform();
27
27
  this.metadata = this.driver.getMetadata();
28
28
  }
29
- mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
29
+ mapper(field, type = enums_1.QueryType.SELECT, value, alias, schema) {
30
30
  if (core_1.Utils.isRawSql(field)) {
31
31
  return this.knex.raw(field.sql, field.params);
32
32
  }
@@ -87,7 +87,10 @@ class QueryBuilderHelper {
87
87
  const alias2 = this.knex.ref(a).toString();
88
88
  const aliased = this.knex.ref(prop.fieldNames[0]).toString();
89
89
  const as = alias === null ? '' : ` as ${aliased}`;
90
- let value = prop.formula(alias2);
90
+ const meta = this.aliasMap[a]?.metadata ?? this.metadata.get(this.entityName);
91
+ const table = this.createFormulaTable(alias2, meta, schema);
92
+ const columns = meta.createColumnMappingObject();
93
+ let value = prop.formula(table, columns);
91
94
  if (!this.isTableNameAliasRequired(type)) {
92
95
  value = value.replaceAll(alias2 + '.', '');
93
96
  }
@@ -208,7 +211,10 @@ class QueryBuilderHelper {
208
211
  const right = `${join.alias}.${join.joinColumns[idx]}`;
209
212
  if (join.prop.formula) {
210
213
  const alias = this.platform.quoteIdentifier(join.ownerAlias);
211
- const left = join.prop.formula(alias);
214
+ const ownerMeta = this.aliasMap[join.ownerAlias]?.metadata ?? this.metadata.get(this.entityName);
215
+ const table = this.createFormulaTable(alias.toString(), ownerMeta, schema);
216
+ const columns = ownerMeta.createColumnMappingObject();
217
+ const left = join.prop.formula(table, columns);
212
218
  conditions.push(`${left} = ${this.knex.ref(right)}`);
213
219
  return;
214
220
  }
@@ -770,5 +776,16 @@ class QueryBuilderHelper {
770
776
  }
771
777
  return cond;
772
778
  }
779
+ createFormulaTable(alias, meta, schema) {
780
+ const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
781
+ const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
782
+ return {
783
+ alias,
784
+ name: meta.tableName,
785
+ schema: effectiveSchema,
786
+ qualifiedName,
787
+ toString: () => alias,
788
+ };
789
+ }
773
790
  }
774
791
  exports.QueryBuilderHelper = QueryBuilderHelper;