@mikro-orm/core 7.0.0-dev.113 → 7.0.0-dev.114

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.
package/EntityManager.js CHANGED
@@ -4,7 +4,7 @@ import { Cursor } from './utils/Cursor.js';
4
4
  import { DataloaderUtils } from './utils/DataloaderUtils.js';
5
5
  import { QueryHelper } from './utils/QueryHelper.js';
6
6
  import { TransactionContext } from './utils/TransactionContext.js';
7
- import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
7
+ import { isRaw, Raw } from './utils/RawQueryFragment.js';
8
8
  import { EntityFactory } from './entity/EntityFactory.js';
9
9
  import { EntityAssigner } from './entity/EntityAssigner.js';
10
10
  import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
@@ -457,7 +457,7 @@ export class EntityManager {
457
457
  }
458
458
  ret.push(cond);
459
459
  }
460
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
460
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
461
461
  return conds.length > 1 ? { $and: conds } : conds[0];
462
462
  }
463
463
  /**
@@ -468,12 +468,10 @@ export class EntityManager {
468
468
  const em = this.getContext(false);
469
469
  await em.tryFlush(entityName, options);
470
470
  options.flushMode = 'commit'; // do not try to auto flush again
471
- return RawQueryFragment.run(async () => {
472
- return Promise.all([
473
- em.find(entityName, where, options),
474
- em.count(entityName, where, options),
475
- ]);
476
- });
471
+ return Promise.all([
472
+ em.find(entityName, where, options),
473
+ em.count(entityName, where, options),
474
+ ]);
477
475
  }
478
476
  /**
479
477
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -536,7 +534,7 @@ export class EntityManager {
536
534
  entityName = Utils.className(entityName);
537
535
  options.overfetch ??= true;
538
536
  options.where ??= {};
539
- if (Utils.isEmpty(options.orderBy)) {
537
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
540
538
  throw new Error('Explicit `orderBy` option required');
541
539
  }
542
540
  const [entities, count] = options.includeCount !== false
@@ -152,7 +152,7 @@ export class DatabaseDriver {
152
152
  }
153
153
  const createOrderBy = (prop, direction) => {
154
154
  if (Utils.isPlainObject(direction)) {
155
- const value = Utils.keys(direction).reduce((o, key) => {
155
+ const value = Utils.getObjectQueryKeys(direction).reduce((o, key) => {
156
156
  Object.assign(o, createOrderBy(key, direction[key]));
157
157
  return o;
158
158
  }, {});
@@ -8,7 +8,7 @@ import type { EntityManager } from '../EntityManager.js';
8
8
  import type { DriverException } from '../exceptions.js';
9
9
  import type { Configuration } from '../utils/Configuration.js';
10
10
  import type { LoggingOptions, LogContext } from '../logging/Logger.js';
11
- import type { RawQueryFragment } from '../utils/RawQueryFragment.js';
11
+ import type { Raw } from '../utils/RawQueryFragment.js';
12
12
  export declare const EntityManagerType: unique symbol;
13
13
  export interface IDatabaseDriver<C extends Connection = Connection> {
14
14
  [EntityManagerType]: EntityManager<this>;
@@ -189,7 +189,7 @@ export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOpti
189
189
  processCollections?: boolean;
190
190
  }
191
191
  export interface UpsertOptions<Entity, Fields extends string = never> extends Omit<NativeInsertUpdateOptions<Entity>, 'upsert'> {
192
- onConflictFields?: (keyof Entity)[] | RawQueryFragment;
192
+ onConflictFields?: (keyof Entity)[] | Raw;
193
193
  onConflictAction?: 'ignore' | 'merge';
194
194
  onConflictMergeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
195
195
  onConflictExcludeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
@@ -4,6 +4,7 @@ import { DataloaderType, ReferenceKind } from '../enums.js';
4
4
  import { Reference } from './Reference.js';
5
5
  import { helper, wrap } from './wrap.js';
6
6
  import { QueryHelper } from '../utils/QueryHelper.js';
7
+ import { Raw } from '../utils/RawQueryFragment.js';
7
8
  import { inspect } from '../logging/inspect.js';
8
9
  export class Collection {
9
10
  owner;
@@ -298,7 +299,7 @@ export class Collection {
298
299
  return cond;
299
300
  }
300
301
  createOrderBy(orderBy = []) {
301
- if (Utils.isEmpty(orderBy) && this.property.orderBy) {
302
+ if (Utils.isEmpty(orderBy) && !Raw.hasObjectFragments(orderBy) && this.property.orderBy) {
302
303
  orderBy = this.property.orderBy;
303
304
  }
304
305
  return Utils.asArray(orderBy);
@@ -4,8 +4,8 @@ import { ValidationError } from '../errors.js';
4
4
  import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
6
6
  import { helper } from './wrap.js';
7
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
8
7
  import { expandDotPaths } from './utils.js';
8
+ import { Raw } from '../utils/RawQueryFragment.js';
9
9
  export class EntityLoader {
10
10
  em;
11
11
  metadata;
@@ -237,26 +237,12 @@ export class EntityLoader {
237
237
  if (typeof populateWhere === 'object') {
238
238
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
239
239
  }
240
- if (!Utils.isEmpty(prop.where)) {
240
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
241
241
  where = { $and: [where, prop.where] };
242
242
  }
243
- const propOrderBy = [];
244
- if (prop.orderBy) {
245
- for (const item of Utils.asArray(prop.orderBy)) {
246
- for (const field of Utils.keys(item)) {
247
- const rawField = RawQueryFragment.getKnownFragment(field, false);
248
- if (rawField) {
249
- const raw2 = raw(rawField.sql, rawField.params);
250
- propOrderBy.push({ [raw2.toString()]: item[field] });
251
- continue;
252
- }
253
- propOrderBy.push({ [field]: item[field] });
254
- }
255
- }
256
- }
257
- const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
243
+ const orderBy = [...Utils.asArray(options.orderBy), ...Utils.asArray(prop.orderBy)].filter((order, idx, array) => {
258
244
  // skip consecutive ordering with the same key to get around mongo issues
259
- return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
245
+ return idx === 0 || !Utils.equals(Utils.getObjectQueryKeys(array[idx - 1]), Utils.getObjectQueryKeys(order));
260
246
  });
261
247
  const items = await this.em.find(prop.type, where, {
262
248
  filters, convertCustomTypes, lockMode, populateWhere, logging,
@@ -424,7 +410,7 @@ export class EntityLoader {
424
410
  if (where[op]) {
425
411
  const child = where[op]
426
412
  .map((cond) => cond[prop.name])
427
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
413
+ .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
428
414
  .map((cond) => {
429
415
  if (Utils.isPrimaryKey(cond)) {
430
416
  return { [pk]: cond };
@@ -1,6 +1,6 @@
1
1
  import { Utils } from '../utils/Utils.js';
2
2
  import { ValidationError } from '../errors.js';
3
- import { isRaw } from '../utils/RawQueryFragment.js';
3
+ import { isRaw, Raw } from '../utils/RawQueryFragment.js';
4
4
  import { SCALAR_TYPES } from '../enums.js';
5
5
  /** @internal */
6
6
  export function validateProperty(prop, givenValue, entity) {
@@ -59,7 +59,7 @@ export function validatePrimaryKey(entity, meta) {
59
59
  }
60
60
  /** @internal */
61
61
  export function validateEmptyWhere(where) {
62
- if (Utils.isEmpty(where)) {
62
+ if (Utils.isEmpty(where) && !Raw.hasObjectFragments(where)) {
63
63
  throw new Error(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`);
64
64
  }
65
65
  }
@@ -3,7 +3,7 @@ import { Collection } from '../entity/Collection.js';
3
3
  import { Reference, ScalarReference } from '../entity/Reference.js';
4
4
  import { parseJsonSafe, Utils } from '../utils/Utils.js';
5
5
  import { ReferenceKind } from '../enums.js';
6
- import { RawQueryFragment } from '../utils/RawQueryFragment.js';
6
+ import { Raw } from '../utils/RawQueryFragment.js';
7
7
  export class ObjectHydrator extends Hydrator {
8
8
  hydrators = {
9
9
  'full~true': new Map(),
@@ -52,7 +52,7 @@ export class ObjectHydrator extends Hydrator {
52
52
  const registerCustomType = (prop, convertorKey, method, context) => {
53
53
  context.set(`${method}_${convertorKey}`, (val) => {
54
54
  /* v8 ignore next */
55
- if (RawQueryFragment.isKnownFragment(val)) {
55
+ if (Raw.isKnownFragment(val)) {
56
56
  return val;
57
57
  }
58
58
  return prop.customType[method](val, this.platform, { mode: 'serialization' });
@@ -7,7 +7,7 @@ import { Cascade, ReferenceKind } from '../enums.js';
7
7
  import { MetadataError } from '../errors.js';
8
8
  import { t, Type } from '../types/index.js';
9
9
  import { colors } from '../logging/colors.js';
10
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
10
+ import { raw, Raw } from '../utils/RawQueryFragment.js';
11
11
  export class MetadataDiscovery {
12
12
  metadata;
13
13
  platform;
@@ -1025,7 +1025,7 @@ export class MetadataDiscovery {
1025
1025
  return;
1026
1026
  }
1027
1027
  let val = prop.default;
1028
- const raw = RawQueryFragment.getKnownFragment(val);
1028
+ const raw = Raw.getKnownFragment(val);
1029
1029
  if (raw) {
1030
1030
  prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
1031
1031
  return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
3
  "type": "module",
4
- "version": "7.0.0-dev.113",
4
+ "version": "7.0.0-dev.114",
5
5
  "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.",
6
6
  "exports": {
7
7
  "./package.json": "./package.json",
@@ -5,7 +5,7 @@ import { ExceptionConverter } from './ExceptionConverter.js';
5
5
  import { ArrayType, BigIntType, BlobType, BooleanType, CharacterType, DateTimeType, DateType, DecimalType, DoubleType, EnumType, FloatType, IntegerType, IntervalType, JsonType, MediumIntType, SmallIntType, StringType, TextType, TimeType, TinyIntType, Type, Uint8ArrayType, UnknownType, UuidType, } from '../types/index.js';
6
6
  import { parseJsonSafe } from '../utils/Utils.js';
7
7
  import { ReferenceKind } from '../enums.js';
8
- import { RawQueryFragment } from '../utils/RawQueryFragment.js';
8
+ import { Raw } from '../utils/RawQueryFragment.js';
9
9
  export const JsonProperty = Symbol('JsonProperty');
10
10
  export class Platform {
11
11
  exceptionConverter = new ExceptionConverter();
@@ -330,7 +330,7 @@ export class Platform {
330
330
  return value;
331
331
  }
332
332
  quoteIdentifier(id, quote = '`') {
333
- const raw = RawQueryFragment.getKnownFragment(id);
333
+ const raw = Raw.getKnownFragment(id);
334
334
  if (raw) {
335
335
  return this.formatQuery(raw.sql, raw.params);
336
336
  }
package/typings.d.ts CHANGED
@@ -14,7 +14,7 @@ import type { EntitySchema } from './metadata/EntitySchema.js';
14
14
  import type { Type, types } from './types/index.js';
15
15
  import type { Platform } from './platforms/Platform.js';
16
16
  import type { Configuration } from './utils/Configuration.js';
17
- import type { RawQueryFragment } from './utils/RawQueryFragment.js';
17
+ import type { Raw } from './utils/RawQueryFragment.js';
18
18
  import type { EntityManager } from './EntityManager.js';
19
19
  import type { EventSubscriber } from './events/EventSubscriber.js';
20
20
  import type { FilterOptions, FindOneOptions, FindOptions, LoadHint } from './drivers/IDatabaseDriver.js';
@@ -314,7 +314,7 @@ type TableName = {
314
314
  schema?: string;
315
315
  toString: () => string;
316
316
  };
317
- export type IndexCallback<T> = (table: TableName, columns: Record<PropertyName<T>, string>, indexName: string) => string | RawQueryFragment;
317
+ export type IndexCallback<T> = (table: TableName, columns: Record<PropertyName<T>, string>, indexName: string) => string | Raw;
318
318
  export type CheckCallback<T> = (columns: Record<PropertyName<T>, string>) => string;
319
319
  export type GeneratedColumnCallback<T> = (columns: Record<keyof T, string>) => string;
320
320
  export interface CheckConstraint<T = any> {
@@ -443,7 +443,7 @@ export interface EntityMetadata<T = any> {
443
443
  schema?: string;
444
444
  pivotTable?: boolean;
445
445
  virtual?: boolean;
446
- expression?: string | ((em: any, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>, stream?: boolean) => MaybePromise<RawQueryFragment | object | string>);
446
+ expression?: string | ((em: any, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>, stream?: boolean) => MaybePromise<Raw | object | string>);
447
447
  discriminatorColumn?: EntityKey<T> | AnyString;
448
448
  discriminatorValue?: number | string;
449
449
  discriminatorMap?: Dictionary<string>;
package/utils/Cursor.js CHANGED
@@ -2,7 +2,7 @@ import { Utils } from './Utils.js';
2
2
  import { ReferenceKind } from '../enums.js';
3
3
  import { Reference } from '../entity/Reference.js';
4
4
  import { helper } from '../entity/wrap.js';
5
- import { RawQueryFragment } from '../utils/RawQueryFragment.js';
5
+ import { Raw } from '../utils/RawQueryFragment.js';
6
6
  import { CursorError } from '../errors.js';
7
7
  import { inspect } from '../logging/inspect.js';
8
8
  /**
@@ -147,8 +147,8 @@ export class Cursor {
147
147
  static getDefinition(meta, orderBy) {
148
148
  return Utils.asArray(orderBy).flatMap(order => {
149
149
  const ret = [];
150
- for (const key of Utils.keys(order)) {
151
- if (RawQueryFragment.isKnownFragment(key)) {
150
+ for (const key of Utils.getObjectQueryKeys(order)) {
151
+ if (Raw.isKnownFragmentSymbol(key)) {
152
152
  ret.push([key, order[key]]);
153
153
  continue;
154
154
  }
@@ -2,7 +2,7 @@ import { clone } from './clone.js';
2
2
  import { ReferenceKind } from '../enums.js';
3
3
  import { compareArrays, compareBooleans, compareBuffers, compareObjects, equals, parseJsonSafe, Utils } from './Utils.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
- import { RawQueryFragment } from './RawQueryFragment.js';
5
+ import { Raw } from './RawQueryFragment.js';
6
6
  export class EntityComparator {
7
7
  metadata;
8
8
  platform;
@@ -482,7 +482,7 @@ export class EntityComparator {
482
482
  const convertorKey = this.safeKey(prop.name);
483
483
  context.set(`convertToDatabaseValue_${convertorKey}`, (val) => {
484
484
  /* v8 ignore next */
485
- if (RawQueryFragment.isKnownFragment(val)) {
485
+ if (Raw.isKnownFragment(val)) {
486
486
  return val;
487
487
  }
488
488
  return prop.customType.convertToDatabaseValue(val, this.platform, { mode: 'serialization' });
@@ -597,7 +597,12 @@ export class EntityComparator {
597
597
  if (prop.customType) {
598
598
  if (prop.customType.compareValues) {
599
599
  const idx = this.tmpIndex++;
600
- context.set(`compareValues_${idx}`, (a, b) => prop.customType.compareValues(a, b));
600
+ context.set(`compareValues_${idx}`, (a, b) => {
601
+ if (Raw.isKnownFragment(a) || Raw.isKnownFragment(b)) {
602
+ return Raw.getKnownFragment(a) === Raw.getKnownFragment(b);
603
+ }
604
+ return prop.customType.compareValues(a, b);
605
+ });
601
606
  return this.getGenericComparator(this.wrap(prop.name), `!compareValues_${idx}(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
602
607
  }
603
608
  type = prop.customType.compareAsType().toLowerCase();
@@ -6,7 +6,7 @@ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
6
6
  export declare class QueryHelper {
7
7
  static readonly SUPPORTED_OPERATORS: string[];
8
8
  static processParams(params: unknown): any;
9
- static processObjectParams<T extends object>(params?: T): T;
9
+ static processObjectParams<T extends Dictionary>(params?: T): T;
10
10
  /**
11
11
  * converts `{ account: { $or: [ [Object], [Object] ] } }`
12
12
  * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
@@ -3,7 +3,7 @@ import { Utils } from './Utils.js';
3
3
  import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
- import { isRaw, RawQueryFragment } from './RawQueryFragment.js';
6
+ import { isRaw, Raw } from './RawQueryFragment.js';
7
7
  /** @internal */
8
8
  export class QueryHelper {
9
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
@@ -29,7 +29,7 @@ export class QueryHelper {
29
29
  return params;
30
30
  }
31
31
  static processObjectParams(params = {}) {
32
- Utils.keys(params).forEach(k => {
32
+ Utils.getObjectQueryKeys(params).forEach(k => {
33
33
  params[k] = QueryHelper.processParams(params[k]);
34
34
  });
35
35
  return params;
@@ -138,12 +138,10 @@ export class QueryHelper {
138
138
  if (!Utils.isPlainObject(where)) {
139
139
  return where;
140
140
  }
141
- return Object.keys(where).reduce((o, key) => {
141
+ return Utils.getObjectQueryKeys(where).reduce((o, key) => {
142
142
  let value = where[key];
143
- const prop = this.findProperty(key, options);
144
- const keys = prop?.joinColumns?.length ?? 0;
145
- const composite = keys > 1;
146
- if (Array.isArray(value) && value.length === 0 && RawQueryFragment.isKnownFragment(key)) {
143
+ const customExpression = Raw.isKnownFragmentSymbol(key);
144
+ if (Array.isArray(value) && value.length === 0 && customExpression) {
147
145
  o[key] = value;
148
146
  return o;
149
147
  }
@@ -157,6 +155,9 @@ export class QueryHelper {
157
155
  o[rootPrimaryKey] = { [key]: QueryHelper.processWhere({ ...options, where: value, root: false }) };
158
156
  return o;
159
157
  }
158
+ const prop = customExpression ? null : this.findProperty(key, options);
159
+ const keys = prop?.joinColumns?.length ?? 0;
160
+ const composite = keys > 1;
160
161
  if (prop?.customType && convertCustomTypes && !isRaw(value)) {
161
162
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
162
163
  }
@@ -164,7 +165,7 @@ export class QueryHelper {
164
165
  if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
165
166
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
166
167
  }
167
- if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
168
+ if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
168
169
  // comparing single composite key - use $eq instead of $in
169
170
  const op = composite && !value.every(v => Array.isArray(v)) ? '$eq' : '$in';
170
171
  o[key] = { [op]: value };
@@ -231,8 +232,8 @@ export class QueryHelper {
231
232
  }
232
233
  static processCustomType(prop, cond, platform, key, fromQuery) {
233
234
  if (Utils.isPlainObject(cond)) {
234
- return Utils.keys(cond).reduce((o, k) => {
235
- if (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k)) {
235
+ return Utils.getObjectQueryKeys(cond).reduce((o, k) => {
236
+ if (!Raw.isKnownFragmentSymbol(k) && (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k))) {
236
237
  o[k] = QueryHelper.processCustomType(prop, cond[k], platform, k, fromQuery);
237
238
  }
238
239
  else {
@@ -1,23 +1,22 @@
1
1
  import type { AnyString, Dictionary, EntityKey } from '../typings.js';
2
+ declare const rawFragmentSymbolBrand: unique symbol;
3
+ export type RawQueryFragmentSymbol = symbol & {
4
+ readonly [rawFragmentSymbolBrand]: true;
5
+ };
2
6
  export declare class RawQueryFragment {
3
7
  #private;
4
8
  readonly sql: string;
5
9
  readonly params: unknown[];
6
- static cloneRegistry?: Set<string>;
7
10
  constructor(sql: string, params?: unknown[]);
11
+ get key(): RawQueryFragmentSymbol;
8
12
  as(alias: string): RawQueryFragment;
9
- valueOf(): string;
13
+ [Symbol.toPrimitive](hint: 'string'): RawQueryFragmentSymbol;
14
+ get [Symbol.toStringTag](): string;
10
15
  toJSON(): string;
11
- toString(): string;
12
- clone(): RawQueryFragment;
13
- static run<T>(cb: (...args: any[]) => Promise<T>): Promise<T>;
14
- /**
15
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
16
- */
17
- static checkCacheSize(): number;
18
- static isKnownFragment(key: string | RawQueryFragment): boolean;
19
- static getKnownFragment(key: string | RawQueryFragment, cleanup?: boolean): RawQueryFragment | undefined;
20
- static remove(key: string): void;
16
+ static isKnownFragmentSymbol(key: unknown): key is RawQueryFragmentSymbol;
17
+ static hasObjectFragments(object: unknown): boolean;
18
+ static isKnownFragment(key: unknown): key is RawQueryFragment | symbol;
19
+ static getKnownFragment(key: unknown): RawQueryFragment | undefined;
21
20
  }
22
21
  export { RawQueryFragment as Raw };
23
22
  export declare function isRaw(value: unknown): value is RawQueryFragment;
@@ -1,81 +1,58 @@
1
1
  import { Utils } from './Utils.js';
2
- import { createAsyncContext } from './AsyncContext.js';
3
2
  export class RawQueryFragment {
4
3
  sql;
5
4
  params;
6
- static #rawQueryCache = new Map();
7
- static #storage = createAsyncContext();
8
- static #index = 0n;
9
- static cloneRegistry;
10
- #used = 0;
5
+ // holds a weak reference to fragments used as object keys
6
+ static #rawQueryReferences = new WeakMap();
11
7
  #key;
12
8
  constructor(sql, params = []) {
13
9
  this.sql = sql;
14
10
  this.params = params;
15
- this.#key = `[raw]: ${this.sql} (#${RawQueryFragment.#index++})`;
11
+ }
12
+ get key() {
13
+ if (!this.#key) {
14
+ this.#key = Symbol(this.toJSON());
15
+ RawQueryFragment.#rawQueryReferences.set(this.#key, this);
16
+ }
17
+ return this.#key;
16
18
  }
17
19
  as(alias) {
18
20
  return new RawQueryFragment(`${this.sql} as ??`, [...this.params, alias]);
19
21
  }
20
- valueOf() {
22
+ [Symbol.toPrimitive](hint) {
23
+ // if a fragment is converted to string (used as an object key), return a unique symbol
24
+ // and save a weak reference to map so we can retrieve it when compiling the query
25
+ if (hint === 'string') {
26
+ return this.key;
27
+ }
21
28
  throw new Error(`Trying to modify raw SQL fragment: '${this.sql}'`);
22
29
  }
23
- toJSON() {
24
- return this.#key;
25
- }
26
- toString() {
27
- RawQueryFragment.#rawQueryCache.set(this.#key, this);
28
- this.#used++;
29
- return this.#key;
30
+ get [Symbol.toStringTag]() {
31
+ return this.toJSON();
30
32
  }
31
- clone() {
32
- RawQueryFragment.cloneRegistry?.add(this.#key);
33
- return new RawQueryFragment(this.sql, this.params);
33
+ toJSON() {
34
+ return `raw('${this.sql}')`;
34
35
  }
35
- static async run(cb) {
36
- const removeStack = new Set();
37
- const res = await this.#storage.run(removeStack, cb);
38
- removeStack.forEach(key => RawQueryFragment.remove(key));
39
- removeStack.clear();
40
- return res;
36
+ static isKnownFragmentSymbol(key) {
37
+ return typeof key === 'symbol' && this.#rawQueryReferences.has(key);
41
38
  }
42
- /**
43
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
44
- */
45
- static checkCacheSize() {
46
- return this.#rawQueryCache.size;
39
+ static hasObjectFragments(object) {
40
+ return Utils.isPlainObject(object) && Object.getOwnPropertySymbols(object).some(symbol => this.isKnownFragmentSymbol(symbol));
47
41
  }
48
42
  static isKnownFragment(key) {
49
43
  if (key instanceof RawQueryFragment) {
50
44
  return true;
51
45
  }
52
- return this.#rawQueryCache.has(key);
46
+ return this.isKnownFragmentSymbol(key);
53
47
  }
54
- static getKnownFragment(key, cleanup = true) {
48
+ static getKnownFragment(key) {
55
49
  if (key instanceof RawQueryFragment) {
56
50
  return key;
57
51
  }
58
- const raw = this.#rawQueryCache.get(key);
59
- if (raw && cleanup) {
60
- this.remove(key);
61
- }
62
- return raw;
63
- }
64
- static remove(key) {
65
- const raw = this.#rawQueryCache.get(key);
66
- if (!raw) {
52
+ if (typeof key !== 'symbol') {
67
53
  return;
68
54
  }
69
- raw.#used--;
70
- if (raw.#used <= 0) {
71
- const removeStack = this.#storage.getStore();
72
- if (removeStack) {
73
- removeStack.add(key);
74
- }
75
- else {
76
- this.#rawQueryCache.delete(key);
77
- }
78
- }
55
+ return this.#rawQueryReferences.get(key);
79
56
  }
80
57
  /** @ignore */
81
58
  /* v8 ignore next */
@@ -193,11 +170,7 @@ export function raw(sql, params) {
193
170
  * ```
194
171
  */
195
172
  export function sql(sql, ...values) {
196
- return raw(sql.reduce((query, queryPart, i) => {
197
- const valueExists = i < values.length;
198
- const text = query + queryPart;
199
- return valueExists ? text + '?' : text;
200
- }, ''), values);
173
+ return raw(sql.join('?'), values);
201
174
  }
202
175
  export function createSqlFunction(func, key) {
203
176
  if (typeof key === 'string') {
package/utils/Utils.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadat
2
2
  import type { Collection } from '../entity/Collection.js';
3
3
  import type { Platform } from '../platforms/Platform.js';
4
4
  import type { ScalarReference } from '../entity/Reference.js';
5
+ import { type RawQueryFragmentSymbol } from './RawQueryFragment.js';
5
6
  export declare function compareObjects(a: any, b: any): boolean;
6
7
  export declare function compareArrays(a: any[] | string, b: any[] | string): boolean;
7
8
  export declare function compareBooleans(a: unknown, b: unknown): boolean;
@@ -155,4 +156,5 @@ export declare class Utils {
155
156
  static values<T extends object>(obj: T): T[keyof T][];
156
157
  static entries<T extends object>(obj: T): [keyof T, T[keyof T]][];
157
158
  static primaryKeyToObject<T>(meta: EntityMetadata<T>, primaryKey: Primary<T> | T, visible?: (keyof T)[]): T;
159
+ static getObjectQueryKeys<T extends Dictionary, K extends string = Extract<keyof T, string>>(obj: T): (K | RawQueryFragmentSymbol)[];
158
160
  }
package/utils/Utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { clone } from './clone.js';
2
2
  import { GroupOperator, PlainObject, QueryOperator, ReferenceKind } from '../enums.js';
3
3
  import { helper } from '../entity/wrap.js';
4
+ import { Raw } from './RawQueryFragment.js';
4
5
  function compareConstructors(a, b) {
5
6
  if (a.constructor === b.constructor) {
6
7
  return true;
@@ -123,7 +124,7 @@ export function parseJsonSafe(value) {
123
124
  }
124
125
  export class Utils {
125
126
  static PK_SEPARATOR = '~~~';
126
- static #ORM_VERSION = '7.0.0-dev.113';
127
+ static #ORM_VERSION = '7.0.0-dev.114';
127
128
  /**
128
129
  * Checks if the argument is instance of `Object`. Returns false for arrays.
129
130
  */
@@ -824,4 +825,12 @@ export class Utils {
824
825
  return o;
825
826
  }, {});
826
827
  }
828
+ static getObjectQueryKeys(obj) {
829
+ return Reflect.ownKeys(obj).filter(key => {
830
+ if (!Object.prototype.propertyIsEnumerable.call(obj, key)) {
831
+ return false;
832
+ }
833
+ return typeof key === 'string' || Raw.isKnownFragmentSymbol(key);
834
+ });
835
+ }
827
836
  }
package/utils/clone.js CHANGED
@@ -3,7 +3,6 @@
3
3
  * clone `EventEmitter`s to get around https://github.com/mikro-orm/mikro-orm/issues/2748
4
4
  * @internal
5
5
  */
6
- import { RawQueryFragment } from './RawQueryFragment.js';
7
6
  /**
8
7
  * Get the property descriptor of a property on an object or its prototype chain.
9
8
  *
@@ -29,10 +28,6 @@ export function clone(parent, respectCustomCloneMethod = true) {
29
28
  if (parent === null) {
30
29
  return null;
31
30
  }
32
- const raw = RawQueryFragment.getKnownFragment(parent, false);
33
- if (raw && respectCustomCloneMethod) {
34
- return raw.clone();
35
- }
36
31
  if (typeof parent !== 'object') {
37
32
  return parent;
38
33
  }
@@ -113,25 +108,16 @@ export function clone(parent, respectCustomCloneMethod = true) {
113
108
  if (attrs && typeof attrs.get === 'function' && attrs.set == null) {
114
109
  continue;
115
110
  }
116
- const raw = RawQueryFragment.getKnownFragment(i, false);
117
- if (raw && respectCustomCloneMethod) {
118
- const i2 = raw.clone().toString();
119
- child[i2] = _clone(parent[i]);
120
- continue;
121
- }
122
111
  child[i] = _clone(parent[i]);
123
112
  }
124
- if (Object.getOwnPropertySymbols) {
125
- const symbols = Object.getOwnPropertySymbols(parent);
126
- for (let i = 0; i < symbols.length; i++) {
127
- const symbol = symbols[i];
128
- const descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
129
- /* v8 ignore next */
130
- if (descriptor && !descriptor.enumerable) {
131
- continue;
132
- }
133
- child[symbol] = _clone(parent[symbol]);
113
+ const symbols = Object.getOwnPropertySymbols(parent);
114
+ for (let i = 0; i < symbols.length; i++) {
115
+ const symbol = symbols[i];
116
+ const descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
117
+ if (descriptor && !descriptor.enumerable) {
118
+ continue;
134
119
  }
120
+ child[symbol] = _clone(parent[symbol]);
135
121
  }
136
122
  return child;
137
123
  }
@@ -1,12 +1,12 @@
1
1
  import type { EntityData, EntityMetadata, FilterQuery } from '../typings.js';
2
2
  import type { UpsertOptions } from '../drivers/IDatabaseDriver.js';
3
- import { type RawQueryFragment } from '../utils/RawQueryFragment.js';
3
+ import { type Raw } from '../utils/RawQueryFragment.js';
4
4
  /** @internal */
5
- export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | RawQueryFragment, options: UpsertOptions<T>): (keyof T)[];
5
+ export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
6
6
  /** @internal */
7
- export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | RawQueryFragment, options: UpsertOptions<T, P>): (keyof T)[] | '*';
7
+ export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>): (keyof T)[] | '*';
8
8
  /** @internal */
9
- export declare function getWhereCondition<T extends object>(meta: EntityMetadata<T>, onConflictFields: (keyof T)[] | RawQueryFragment | undefined, data: EntityData<T>, where: FilterQuery<T>): {
9
+ export declare function getWhereCondition<T extends object>(meta: EntityMetadata<T>, onConflictFields: (keyof T)[] | Raw | undefined, data: EntityData<T>, where: FilterQuery<T>): {
10
10
  where: FilterQuery<T>;
11
11
  propIndex: number | false;
12
12
  };