@mikro-orm/sql 7.0.0-dev.298 → 7.0.0-dev.299

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.
@@ -1,4 +1,4 @@
1
- import { helper, inspect, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core';
1
+ import { EntityMetadata, helper, inspect, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core';
2
2
  import { JoinType, QueryType } from './enums.js';
3
3
  import { QueryBuilderHelper } from './QueryBuilderHelper.js';
4
4
  import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
@@ -82,6 +82,7 @@ export class QueryBuilder {
82
82
  _helper;
83
83
  _query;
84
84
  _unionQuery;
85
+ _ctes = [];
85
86
  platform;
86
87
  tptJoinsApplied = false;
87
88
  autoJoinedPaths = [];
@@ -712,6 +713,9 @@ export class QueryBuilder {
712
713
  if (target instanceof QueryBuilder) {
713
714
  this.fromSubQuery(target, aliasName);
714
715
  }
716
+ else if (typeof target === 'string' && !this.metadata.find(target)) {
717
+ this.fromRawTable(target, aliasName);
718
+ }
715
719
  else {
716
720
  if (aliasName && this._mainAlias && Utils.className(target) !== this._mainAlias.aliasName) {
717
721
  throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this._mainAlias.aliasName}'`);
@@ -737,6 +741,16 @@ export class QueryBuilder {
737
741
  this._query = {};
738
742
  this.finalize();
739
743
  const qb = this.getQueryBase(processVirtualEntity);
744
+ for (const cte of this._ctes) {
745
+ const query = cte.query;
746
+ const opts = { columns: cte.columns, materialized: cte.materialized };
747
+ if (cte.recursive) {
748
+ qb.withRecursive(cte.name, query, opts);
749
+ }
750
+ else {
751
+ qb.with(cte.name, query, opts);
752
+ }
753
+ }
740
754
  const schema = this.getSchema(this.mainAlias);
741
755
  const isNotEmptyObject = (obj) => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj);
742
756
  Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._cond, qb), this._cond && !this._onConflict);
@@ -1121,6 +1135,28 @@ export class QueryBuilder {
1121
1135
  result._unionQuery = { sql: parts.join(` ${separator} `), params };
1122
1136
  return result;
1123
1137
  }
1138
+ with(name, query, options) {
1139
+ return this.addCte(name, query, options);
1140
+ }
1141
+ withRecursive(name, query, options) {
1142
+ return this.addCte(name, query, options, true);
1143
+ }
1144
+ addCte(name, query, options, recursive) {
1145
+ this.ensureNotFinalized();
1146
+ if (this._ctes.some(cte => cte.name === name)) {
1147
+ throw new Error(`CTE with name '${name}' already exists`);
1148
+ }
1149
+ // Eagerly compile QueryBuilder to RawQueryFragment — later mutations to the sub-query won't be reflected
1150
+ const compiled = query instanceof QueryBuilder ? query.toRaw() : query;
1151
+ this._ctes.push({
1152
+ name,
1153
+ query: compiled,
1154
+ recursive,
1155
+ columns: options?.columns,
1156
+ materialized: options?.materialized,
1157
+ });
1158
+ return this;
1159
+ }
1124
1160
  clone(reset, preserve) {
1125
1161
  const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
1126
1162
  reset = reset || [];
@@ -1140,6 +1176,9 @@ export class QueryBuilder {
1140
1176
  if (this._fields && reset !== true && !reset.includes('_fields')) {
1141
1177
  qb._fields = [...this._fields];
1142
1178
  }
1179
+ if (this._ctes.length && reset !== true && !reset.includes('_ctes')) {
1180
+ qb._ctes = this._ctes.map(cte => ({ ...cte }));
1181
+ }
1143
1182
  qb._aliases = { ...this._aliases };
1144
1183
  qb._helper.aliasMap = qb._aliases;
1145
1184
  qb.finalized = false;
@@ -1457,22 +1496,24 @@ export class QueryBuilder {
1457
1496
  }
1458
1497
  getQueryBase(processVirtualEntity) {
1459
1498
  const qb = this.platform.createNativeQueryBuilder().setFlags(this.flags);
1460
- const { subQuery, aliasName, entityName, meta } = this.mainAlias;
1499
+ const { subQuery, aliasName, entityName, meta, rawTableName } = this.mainAlias;
1461
1500
  const requiresAlias = this.finalized && (this._explicitAlias || this.helper.isTableNameAliasRequired(this.type));
1462
1501
  const alias = requiresAlias ? aliasName : undefined;
1463
1502
  const schema = this.getSchema(this.mainAlias);
1464
- const tableName = subQuery instanceof NativeQueryBuilder
1465
- ? subQuery.as(aliasName)
1466
- : subQuery
1467
- ? raw(`(${subQuery.sql}) as ${this.platform.quoteIdentifier(aliasName)}`, subQuery.params)
1468
- : this.helper.getTableName(entityName);
1503
+ const tableName = rawTableName
1504
+ ? rawTableName
1505
+ : subQuery instanceof NativeQueryBuilder
1506
+ ? subQuery.as(aliasName)
1507
+ : subQuery
1508
+ ? raw(`(${subQuery.sql}) as ${this.platform.quoteIdentifier(aliasName)}`, subQuery.params)
1509
+ : this.helper.getTableName(entityName);
1469
1510
  const joinSchema = this._schema ?? this.em?.schema ?? schema;
1470
1511
  if (meta.virtual && processVirtualEntity) {
1471
1512
  qb.from(raw(this.fromVirtual(meta)), { indexHint: this._indexHint });
1472
1513
  }
1473
1514
  else {
1474
1515
  qb.from(tableName, {
1475
- schema,
1516
+ schema: rawTableName ? undefined : schema,
1476
1517
  alias,
1477
1518
  indexHint: this._indexHint,
1478
1519
  });
@@ -2021,6 +2062,22 @@ export class QueryBuilder {
2021
2062
  aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
2022
2063
  this.createMainAlias(entityName, aliasName);
2023
2064
  }
2065
+ fromRawTable(tableName, aliasName) {
2066
+ aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(tableName);
2067
+ const meta = new EntityMetadata({
2068
+ className: tableName,
2069
+ collection: tableName,
2070
+ });
2071
+ meta.root = meta;
2072
+ this._mainAlias = {
2073
+ aliasName,
2074
+ entityName: tableName,
2075
+ meta,
2076
+ rawTableName: tableName,
2077
+ };
2078
+ this._aliases[aliasName] = this._mainAlias;
2079
+ this._helper = this.createQueryBuilderHelper();
2080
+ }
2024
2081
  createQueryBuilderHelper() {
2025
2082
  return new QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this._aliases, this.subQueries, this.driver, this._tptAlias);
2026
2083
  }
@@ -72,6 +72,7 @@ export interface Alias<T> {
72
72
  entityName: EntityName<T>;
73
73
  meta: EntityMetadata<T>;
74
74
  subQuery?: NativeQueryBuilder | RawQueryFragment;
75
+ rawTableName?: string;
75
76
  }
76
77
  export interface OnConflictClause<T> {
77
78
  fields: string[] | Raw;
@@ -803,7 +803,11 @@ export class QueryBuilderHelper {
803
803
  }
804
804
  getProperty(field, alias) {
805
805
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
806
- const meta = this.metadata.get(entityName);
806
+ const meta = this.metadata.find(entityName);
807
+ // raw table name (e.g. CTE) — no metadata available
808
+ if (!meta) {
809
+ return undefined;
810
+ }
807
811
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
808
812
  if (alias) {
809
813
  const prop = meta.properties[alias];