@mikro-orm/sql 7.2.5-dev.0 → 7.2.5-dev.10

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.
@@ -127,6 +127,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
127
127
  * @internal
128
128
  */
129
129
  protected addTPTPolymorphicJoinsForRelation<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, baseAlias: string, fields: InternalField<T>[]): void;
130
+ /**
131
+ * Registers the ancestor table aliases of each polymorphically joined TPT descendant,
132
+ * so formulas on descendants resolve inherited columns to the table that owns them.
133
+ * @internal
134
+ */
135
+ registerTPTAncestorAliases(qb: AnyQueryBuilder<any>, meta: EntityMetadata, baseAlias: string, childAliases: Dictionary<string>): void;
130
136
  /**
131
137
  * Find the alias for a TPT child table in the query builder.
132
138
  * @internal
@@ -853,10 +853,16 @@ export class AbstractSqlDriver extends DatabaseDriver {
853
853
  // the map misses subtypes whose props were not initialized yet during discovery (root column owner, grandchildren)
854
854
  const discriminator = row[meta.discriminatorColumn];
855
855
  const childClass = meta.discriminatorMap?.[discriminator];
856
- const activeField = prop.stiFieldNameMap[discriminator] ??
857
- (childClass && this.metadata.find(childClass)?.properties[prop.name]?.fieldNames[0]);
856
+ const childProp = childClass && this.metadata.find(childClass)?.properties[prop.name];
857
+ const activeField = prop.stiFieldNameMap[discriminator] ?? childProp?.fieldNames[0];
858
858
  for (const field of prop.stiFieldNames) {
859
- params.push(field === activeField ? (row[prop.name] ?? null) : null);
859
+ // the subtype's own property, as siblings can declare it with a different type or embeddable
860
+ if (field === activeField) {
861
+ addParams(childProp ?? prop, row);
862
+ }
863
+ else {
864
+ params.push(null);
865
+ }
860
866
  keys.push('?');
861
867
  }
862
868
  return;
@@ -1989,6 +1995,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
1989
1995
  qb.createAlias(childMeta.class, childAlias);
1990
1996
  childAliases[childMeta.className] = childAlias;
1991
1997
  qb.addPropertyJoin(childMeta.tptInverseProp, baseAlias, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
1998
+ }
1999
+ this.registerTPTAncestorAliases(qb, meta, baseAlias, childAliases);
2000
+ for (const childMeta of descendants) {
2001
+ const childAlias = childAliases[childMeta.className];
1992
2002
  // Add fields from this child (only ownProps, skip PKs)
1993
2003
  const schema = childMeta.schema === '*' ? '*' : this.getSchemaName(childMeta);
1994
2004
  childMeta
@@ -2000,6 +2010,21 @@ export class AbstractSqlDriver extends DatabaseDriver {
2000
2010
  fields.push(this.buildTPTDiscriminatorExpression(meta, descendants, childAliases, baseAlias));
2001
2011
  }
2002
2012
  }
2013
+ /**
2014
+ * Registers the ancestor table aliases of each polymorphically joined TPT descendant,
2015
+ * so formulas on descendants resolve inherited columns to the table that owns them.
2016
+ * @internal
2017
+ */
2018
+ registerTPTAncestorAliases(qb, meta, baseAlias, childAliases) {
2019
+ const tptAlias = qb.state.tptAlias;
2020
+ for (const childMeta of meta.allTPTDescendants) {
2021
+ for (let parent = childMeta.tptParent; parent; parent = parent.tptParent) {
2022
+ tptAlias[`${childAliases[childMeta.className]}:${parent.className}`] =
2023
+ childAliases[parent.className] ??
2024
+ (parent === meta ? baseAlias : tptAlias[`${baseAlias}:${parent.className}`]);
2025
+ }
2026
+ }
2027
+ }
2003
2028
  /**
2004
2029
  * Find the alias for a TPT child table in the query builder.
2005
2030
  * @internal
@@ -2087,6 +2112,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
2087
2112
  }
2088
2113
  const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
2089
2114
  const sourceAlias = qb.helper.getTPTAliasForProperty(prop.name, tableAlias);
2115
+ if (prop.formula) {
2116
+ const quotedAlias = this.platform.quoteIdentifier(tableAlias).toString();
2117
+ const table = this.createFormulaTable(quotedAlias, meta, schema);
2118
+ const columns = meta.createColumnMappingObject(p => qb.helper.getTPTAliasForProperty(p.name, tableAlias), tableAlias);
2119
+ return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
2120
+ }
2090
2121
  if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
2091
2122
  return prop.fieldNames.map((col, idx) => {
2092
2123
  if (!prop.customTypes[idx]?.convertToJSValueSQL) {
@@ -2101,12 +2132,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
2101
2132
  const prefixed = this.platform.quoteIdentifier(`${sourceAlias}.${prop.fieldNames[0]}`);
2102
2133
  return [raw(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
2103
2134
  }
2104
- if (prop.formula) {
2105
- const quotedAlias = this.platform.quoteIdentifier(tableAlias).toString();
2106
- const table = this.createFormulaTable(quotedAlias, meta, schema);
2107
- const columns = meta.createColumnMappingObject(tableAlias);
2108
- return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
2109
- }
2110
2135
  return prop.fieldNames.map(fieldName => {
2111
2136
  return raw('?? as ??', [`${sourceAlias}.${fieldName}`, `${tableAlias}__${fieldName}`]);
2112
2137
  });
@@ -2527,7 +2552,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
2527
2552
  const table = this.createFormulaTable(quotedAlias.toString(), meta, effectiveSchema);
2528
2553
  ret.push(raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`));
2529
2554
  }
2530
- if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
2555
+ if (!prop.object && !prop.formula && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
2531
2556
  ret.push(prop.name);
2532
2557
  }
2533
2558
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.2.5-dev.0",
3
+ "version": "7.2.5-dev.10",
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.4"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.2.5-dev.0"
56
+ "@mikro-orm/core": "7.2.5-dev.10"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -1943,7 +1943,8 @@ export class QueryBuilder {
1943
1943
  if (parentAlias) {
1944
1944
  const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta);
1945
1945
  parentMeta
1946
- .ownProps.filter(prop => this.platform.shouldHaveColumn(prop, []))
1946
+ // inherited formulas are selected on the main alias in `finalize()`
1947
+ .ownProps.filter(prop => !prop.formula && this.platform.shouldHaveColumn(prop, []))
1947
1948
  .forEach(prop => this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, parentAlias, parentMeta, schema)));
1948
1949
  }
1949
1950
  parentMeta = parentMeta.tptParent;
@@ -1962,12 +1963,16 @@ export class QueryBuilder {
1962
1963
  if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
1963
1964
  return;
1964
1965
  }
1965
- // LEFT JOIN each descendant table and add their fields
1966
+ // LEFT JOIN each descendant table
1966
1967
  for (const childMeta of descendants) {
1967
1968
  const childAlias = this.getNextAlias(childMeta.className);
1968
1969
  this.createAlias(childMeta.class, childAlias);
1969
1970
  this.#state.tptAlias[childMeta.className] = childAlias;
1970
1971
  this.addPropertyJoin(childMeta.tptInverseProp, this.mainAlias.aliasName, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
1972
+ }
1973
+ this.driver.registerTPTAncestorAliases(this, meta, this.mainAlias.aliasName, this.#state.tptAlias);
1974
+ for (const childMeta of descendants) {
1975
+ const childAlias = this.#state.tptAlias[childMeta.className];
1971
1976
  // Add child fields
1972
1977
  const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta);
1973
1978
  childMeta
@@ -1976,7 +1981,7 @@ export class QueryBuilder {
1976
1981
  }
1977
1982
  // Add computed discriminator (CASE WHEN to determine concrete type)
1978
1983
  // descendants is pre-sorted by depth (deepest first) during discovery
1979
- if (meta.tptDiscriminatorColumn) {
1984
+ if (meta.root.tptDiscriminatorColumn) {
1980
1985
  this.#state.fields.push(this.driver.buildTPTDiscriminatorExpression(meta, descendants, this.#state.tptAlias, this.mainAlias.aliasName));
1981
1986
  }
1982
1987
  }
@@ -166,11 +166,12 @@ export class SchemaComparator {
166
166
  diff.changedTables[viewName] = tableDiff;
167
167
  }
168
168
  }
169
- this.compareRoutines(fromSchema, toSchema, diff);
169
+ this.compareRoutines(fromSchema, toSchema, diff, !!inverseDiff);
170
170
  return diff;
171
171
  }
172
- compareRoutines(fromSchema, toSchema, diff) {
172
+ compareRoutines(fromSchema, toSchema, diff, inverse) {
173
173
  // `ignoreRoutines` makes routines create-only: new ones are still added, but existing routines are never diffed for drop/alter.
174
+ // The down diff (`inverse`) mirrors that: it drops what `up` created and never recreates routines unknown to the metadata.
174
175
  const ignoreRoutines = this.#platform.getConfig().get('schemaGenerator').ignoreRoutines;
175
176
  // Case-fold so user-written `'sql_hash'` matches Oracle's introspected `'SQL_HASH'`.
176
177
  const routineKey = (r) => ((r.schema ? `${r.schema}.` : '') + r.name).toLowerCase();
@@ -179,6 +180,9 @@ export class SchemaComparator {
179
180
  for (const [key, toRoutine] of toByKey) {
180
181
  const fromRoutine = fromByKey.get(key);
181
182
  if (!fromRoutine) {
183
+ if (ignoreRoutines && inverse) {
184
+ continue;
185
+ }
182
186
  diff.newRoutines[key] = toRoutine;
183
187
  this.log(`routine ${key} added`);
184
188
  continue;
@@ -188,7 +192,7 @@ export class SchemaComparator {
188
192
  this.log(`routine ${key} changed`, { fromRoutine, toRoutine });
189
193
  }
190
194
  }
191
- if (!ignoreRoutines) {
195
+ if (!ignoreRoutines || inverse) {
192
196
  for (const [key, fromRoutine] of fromByKey) {
193
197
  if (!toByKey.has(key)) {
194
198
  diff.removedRoutines[key] = fromRoutine;
@@ -493,10 +497,12 @@ export class SchemaComparator {
493
497
  tableDifferences.changedChecks[check.name] = toTableCheck;
494
498
  changes++;
495
499
  }
500
+ const ignoreTriggers = this.#platform.getConfig().get('schemaGenerator').ignoreTriggers;
496
501
  const fromTableTriggers = fromTable.getTriggers();
497
502
  const toTableTriggers = toTable.getTriggers();
498
503
  for (const trigger of toTableTriggers) {
499
- if (fromTable.hasTrigger(trigger.name)) {
504
+ // see `compareRoutines` for how `ignoreTriggers` applies to the down diff
505
+ if (fromTable.hasTrigger(trigger.name) || (ignoreTriggers && inverse)) {
500
506
  continue;
501
507
  }
502
508
  tableDifferences.addedTriggers[trigger.name] = trigger;
@@ -504,7 +510,7 @@ export class SchemaComparator {
504
510
  changes++;
505
511
  }
506
512
  // `ignoreTriggers` makes triggers create-only: declared ones are still added above, but existing triggers are never diffed for drop/alter.
507
- if (!this.#platform.getConfig().get('schemaGenerator').ignoreTriggers) {
513
+ if (!ignoreTriggers || inverse) {
508
514
  for (const trigger of fromTableTriggers) {
509
515
  if (!toTable.hasTrigger(trigger.name)) {
510
516
  tableDifferences.removedTriggers[trigger.name] = trigger;
@@ -513,7 +519,7 @@ export class SchemaComparator {
513
519
  continue;
514
520
  }
515
521
  const toTableTrigger = toTable.getTrigger(trigger.name);
516
- if (this.diffTrigger(trigger, toTableTrigger)) {
522
+ if (!ignoreTriggers && this.diffTrigger(trigger, toTableTrigger)) {
517
523
  this.log(`trigger ${trigger.name} changed in table ${tableDifferences.name}`, {
518
524
  fromTableTrigger: trigger,
519
525
  toTableTrigger,