@mikro-orm/sql 7.2.2-dev.6 → 7.2.2-dev.7

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.
@@ -18,6 +18,10 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
18
18
  evaluateFormula(formula: (...args: any[]) => string | Raw, columns: any, table: FormulaTable): string;
19
19
  /** For TPT entities, returns ownProps (columns in this table); otherwise returns all props. */
20
20
  protected getTableProps<T extends object>(meta: EntityMetadata<T>): EntityProperty<T>[];
21
+ /** Columns reloaded by UPDATE: raw values, PKs, generated/version columns and explicit `returning` hints. */
22
+ protected getUpdateReturningProperties<T extends object>(meta: EntityMetadata<T>, data: EntityDictionary<T>[]): EntityProperty<T>[];
23
+ /** Column expression for the `returning` clause of batch updates, applying `convertToJSValueSQL` of custom types. */
24
+ protected getReturningFieldSQL(prop: EntityProperty, fieldName: string, index: number): string;
21
25
  /** Creates a FormulaTable object for use in formula callbacks. */
22
26
  private createFormulaTable;
23
27
  private validateSqlOptions;
@@ -49,6 +49,32 @@ export class AbstractSqlDriver extends DatabaseDriver {
49
49
  getTableProps(meta) {
50
50
  return meta.inheritanceType === 'tpt' && meta.ownProps ? meta.ownProps : meta.props;
51
51
  }
52
+ /** Columns reloaded by UPDATE: raw values, PKs, generated/version columns and explicit `returning` hints. */
53
+ getUpdateReturningProperties(meta, data) {
54
+ const returning = new Set();
55
+ for (const row of data) {
56
+ for (const k of Utils.keys(row)) {
57
+ if (isRaw(row[k])) {
58
+ returning.add(meta.properties[k] ?? meta.root.properties[k]);
59
+ }
60
+ }
61
+ }
62
+ meta.getPrimaryProps().forEach(prop => returning.add(prop));
63
+ // a mixed STI batch shares one table, so the child columns live on the root
64
+ this.getTableProps(meta.root.discriminatorColumn ? meta.root : meta)
65
+ .filter(prop => prop.generated || prop.version || prop.returning)
66
+ .forEach(prop => returning.add(prop));
67
+ return [...returning];
68
+ }
69
+ /** Column expression for the `returning` clause of batch updates, applying `convertToJSValueSQL` of custom types. */
70
+ getReturningFieldSQL(prop, fieldName, index) {
71
+ const quoted = this.platform.quoteIdentifier(fieldName);
72
+ const customType = prop.customTypes?.[index] ?? prop.customType;
73
+ if (prop.hasConvertToJSValueSQL && customType?.convertToJSValueSQL) {
74
+ return `${customType.convertToJSValueSQL(quoted, this.platform)} as ${quoted}`;
75
+ }
76
+ return quoted;
77
+ }
52
78
  /** Creates a FormulaTable object for use in formula callbacks. */
53
79
  createFormulaTable(alias, meta, schema) {
54
80
  const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
@@ -946,7 +972,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
946
972
  /* v8 ignore next */
947
973
  const uniqueFields = options.onConflictFields ??
948
974
  (Utils.isPlainObject(where) ? Utils.keys(where) : meta.primaryKeys);
949
- const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
975
+ const returning = getOnConflictReturningFields(meta, data, uniqueFields, options, this.platform);
950
976
  qb.insert(data)
951
977
  .onConflict(uniqueFields)
952
978
  .returning(returning);
@@ -963,11 +989,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
963
989
  }
964
990
  else {
965
991
  qb.update(data).where(where);
966
- // reload generated columns and version fields
967
992
  const returning = [];
968
993
  this.getTableProps(meta)
969
- .filter(prop => (prop.generated && !prop.primary) || prop.version)
970
- .forEach(prop => returning.push(prop.name));
994
+ .filter(prop => (prop.generated && !prop.primary) || prop.version || prop.returning || isRaw(data[prop.name]))
995
+ .forEach(prop => returning.push(...prop.fieldNames));
971
996
  qb.returning(returning);
972
997
  }
973
998
  res = await this.rethrow(qb.execute('run', false));
@@ -1003,7 +1028,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1003
1028
  : meta.primaryKeys);
1004
1029
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
1005
1030
  qb.setAbortOptions(pickAbortOptions(options));
1006
- let returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
1031
+ let returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options, this.platform);
1007
1032
  if (meta.inheritanceType === 'tpt') {
1008
1033
  // each TPT table only carries its own columns, the entity is reloaded instead of mapping the returned rows
1009
1034
  const own = (key) => meta.primaryKeys.includes(key) ||
@@ -1032,20 +1057,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
1032
1057
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(meta, d)) : [];
1033
1058
  const keys = new Set();
1034
1059
  const fields = new Set();
1035
- const returning = new Set();
1060
+ const returning = this.getUpdateReturningProperties(meta, data);
1036
1061
  for (const row of data) {
1037
1062
  for (const k of Utils.keys(row)) {
1038
1063
  keys.add(k);
1039
- if (isRaw(row[k])) {
1040
- returning.add(k);
1041
- }
1042
1064
  }
1043
1065
  }
1044
- // reload generated columns and version fields
1045
- meta.getPrimaryProps().forEach(prop => returning.add(prop.name));
1046
- this.getTableProps(meta)
1047
- .filter(prop => prop.generated || prop.version)
1048
- .forEach(prop => returning.add(prop.name));
1049
1066
  const pkCond = Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames))
1050
1067
  .map(pk => `${this.platform.quoteIdentifier(pk)} = ?`)
1051
1068
  .join(' and ');
@@ -1115,7 +1132,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
1115
1132
  }
1116
1133
  sql += `, `;
1117
1134
  }
1118
- sql = sql.substring(0, sql.length - 2) + ' where ';
1135
+ sql = sql.substring(0, sql.length - 2);
1136
+ if (this.platform.usesOutputStatement() && returning.length > 0) {
1137
+ const fields = returning.flatMap(prop => prop.fieldNames);
1138
+ sql += ` output ${fields.map(field => 'inserted.' + this.platform.quoteIdentifier(field)).join(', ')}`;
1139
+ }
1140
+ sql += ' where ';
1119
1141
  const pkProps = meta.primaryKeys.concat(...meta.getOwnConcurrencyCheckKeys());
1120
1142
  const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
1121
1143
  const useTupleIn = pks.length <= 1 || this.platform.allowsComparingTuples();
@@ -1151,13 +1173,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1151
1173
  else {
1152
1174
  sql += conds.join(' or ');
1153
1175
  }
1154
- if (this.platform.usesReturningStatement() && returning.size > 0) {
1155
- const returningFields = Utils.flatten([...returning].map(prop => (meta.properties[prop] ?? meta.root.properties[prop]).fieldNames));
1156
- /* v8 ignore next */
1157
- sql +=
1158
- returningFields.length > 0
1159
- ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}`
1160
- : '';
1176
+ if (this.platform.usesReturningStatement() && returning.length > 0) {
1177
+ const returningFields = returning.flatMap(prop => prop.fieldNames.map((field, idx) => this.getReturningFieldSQL(prop, field, idx)));
1178
+ sql += ` returning ${returningFields.join(', ')}`;
1161
1179
  }
1162
1180
  if (transform) {
1163
1181
  sql = transform(sql, params);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.2.2-dev.6",
3
+ "version": "7.2.2-dev.7",
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
  "keywords": [
6
6
  "data-mapper",
@@ -53,7 +53,7 @@
53
53
  "@mikro-orm/core": "^7.2.1"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.2.2-dev.6"
56
+ "@mikro-orm/core": "7.2.2-dev.7"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -916,18 +916,13 @@ export class QueryBuilder {
916
916
  return;
917
917
  }
918
918
  if (this.type === QueryType.UPDATE && data) {
919
- const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
919
+ const returningProps = meta.hydrateProps
920
+ .filter(prop => prop.returning || (prop.fieldNames && isRaw(data[prop.fieldNames[0]])))
921
+ // a TPT table can only return its own columns
922
+ .filter(prop => meta.inheritanceType !== 'tpt' || prop.primary || meta.ownProps.some(p => p.name === prop.name));
920
923
  if (returningProps.length > 0) {
921
- qb.returning(returningProps.flatMap((prop) => {
922
- if (prop.hasConvertToJSValueSQL) {
923
- const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
924
- const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
925
- ' as ' +
926
- this.platform.quoteIdentifier(prop.fieldNames[0]);
927
- return [raw(sql)];
928
- }
929
- return prop.fieldNames;
930
- }));
924
+ const fields = returningProps.flatMap(prop => prop.fieldNames);
925
+ qb.returning(fields.map(field => this.helper.mapper(field, this.type)));
931
926
  }
932
927
  }
933
928
  }
@@ -132,17 +132,10 @@ export class QueryBuilderHelper {
132
132
  }
133
133
  return raw(`${value}${as}`);
134
134
  }
135
- if (prop?.hasConvertToJSValueSQL && type !== QueryType.UPSERT) {
136
- let valueSQL;
137
- if (prop.fieldNames.length > 1 && fkIdx !== -1) {
138
- const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
139
- const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
140
- valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.#platform);
141
- }
142
- else {
143
- const prefixed = this.prefix(field, isTableNameAliasRequired, true);
144
- valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.#platform);
145
- }
135
+ const customType = prop?.customTypes?.[fkIdx] ?? prop?.customType;
136
+ if (prop?.hasConvertToJSValueSQL && customType?.convertToJSValueSQL && type !== QueryType.UPSERT) {
137
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
138
+ const valueSQL = customType.convertToJSValueSQL(prefixed, this.#platform);
146
139
  if (alias === null) {
147
140
  return raw(valueSQL);
148
141
  }