@mikro-orm/sql 7.0.0-rc.2 → 7.0.0-rc.3

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.
Files changed (38) hide show
  1. package/AbstractSqlConnection.js +2 -1
  2. package/AbstractSqlDriver.d.ts +18 -12
  3. package/AbstractSqlDriver.js +187 -38
  4. package/AbstractSqlPlatform.d.ts +1 -0
  5. package/AbstractSqlPlatform.js +5 -3
  6. package/PivotCollectionPersister.js +2 -2
  7. package/SqlEntityManager.d.ts +2 -2
  8. package/SqlEntityManager.js +5 -5
  9. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  10. package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
  11. package/dialects/mysql/BaseMySqlPlatform.js +1 -2
  12. package/dialects/mysql/MySqlSchemaHelper.js +21 -10
  13. package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
  14. package/dialects/postgresql/PostgreSqlSchemaHelper.js +63 -47
  15. package/dialects/sqlite/BaseSqliteConnection.js +2 -2
  16. package/dialects/sqlite/NodeSqliteDialect.js +3 -1
  17. package/dialects/sqlite/SqlitePlatform.js +4 -1
  18. package/dialects/sqlite/SqliteSchemaHelper.js +11 -9
  19. package/package.json +30 -30
  20. package/plugin/transformer.js +17 -16
  21. package/query/CriteriaNode.js +28 -10
  22. package/query/CriteriaNodeFactory.js +5 -1
  23. package/query/NativeQueryBuilder.d.ts +25 -0
  24. package/query/NativeQueryBuilder.js +61 -1
  25. package/query/ObjectCriteriaNode.js +71 -27
  26. package/query/QueryBuilder.d.ts +151 -48
  27. package/query/QueryBuilder.js +233 -54
  28. package/query/QueryBuilderHelper.d.ts +4 -3
  29. package/query/QueryBuilderHelper.js +47 -17
  30. package/query/ScalarCriteriaNode.js +14 -7
  31. package/query/raw.js +1 -1
  32. package/schema/DatabaseSchema.js +21 -15
  33. package/schema/DatabaseTable.js +114 -54
  34. package/schema/SchemaComparator.js +56 -32
  35. package/schema/SchemaHelper.js +28 -8
  36. package/schema/SqlSchemaGenerator.js +13 -7
  37. package/tsconfig.build.tsbuildinfo +1 -1
  38. package/typings.d.ts +6 -4
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.0.0-rc.2",
3
+ "version": "7.0.0-rc.3",
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
- "type": "module",
6
- "exports": {
7
- "./package.json": "./package.json",
8
- ".": "./index.js"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
13
- },
14
5
  "keywords": [
15
- "orm",
6
+ "data-mapper",
7
+ "ddd",
8
+ "entity",
9
+ "identity-map",
10
+ "javascript",
11
+ "js",
12
+ "mariadb",
13
+ "mikro-orm",
16
14
  "mongo",
17
15
  "mongodb",
18
16
  "mysql",
19
- "mariadb",
17
+ "orm",
20
18
  "postgresql",
21
19
  "sqlite",
22
20
  "sqlite3",
23
21
  "ts",
24
22
  "typescript",
25
- "js",
26
- "javascript",
27
- "entity",
28
- "ddd",
29
- "mikro-orm",
30
- "unit-of-work",
31
- "data-mapper",
32
- "identity-map"
23
+ "unit-of-work"
33
24
  ],
34
- "author": "Martin Adámek",
35
- "license": "MIT",
25
+ "homepage": "https://mikro-orm.io",
36
26
  "bugs": {
37
27
  "url": "https://github.com/mikro-orm/mikro-orm/issues"
38
28
  },
39
- "homepage": "https://mikro-orm.io",
40
- "engines": {
41
- "node": ">= 22.17.0"
29
+ "license": "MIT",
30
+ "author": "Martin Adámek",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git"
34
+ },
35
+ "type": "module",
36
+ "exports": {
37
+ "./package.json": "./package.json",
38
+ ".": "./index.js"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "yarn compile && yarn copy",
@@ -46,16 +46,16 @@
46
46
  "compile": "yarn run -T tsc -p tsconfig.build.json",
47
47
  "copy": "node ../../scripts/copy.mjs"
48
48
  },
49
- "publishConfig": {
50
- "access": "public"
51
- },
52
49
  "dependencies": {
53
50
  "kysely": "0.28.11"
54
51
  },
55
52
  "devDependencies": {
56
- "@mikro-orm/core": "^6.6.4"
53
+ "@mikro-orm/core": "^6.6.8"
57
54
  },
58
55
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-rc.2"
56
+ "@mikro-orm/core": "7.0.0-rc.3"
57
+ },
58
+ "engines": {
59
+ "node": ">= 22.17.0"
60
60
  }
61
61
  }
@@ -79,12 +79,8 @@ export class MikroTransformer extends OperationNodeTransformer {
79
79
  }
80
80
  }
81
81
  }
82
- const nodeWithHooks = this.options.processOnCreateHooks && entityMeta
83
- ? this.processOnCreateHooks(node, entityMeta)
84
- : node;
85
- const nodeWithConvertedValues = this.options.convertValues && entityMeta
86
- ? this.processInsertValues(nodeWithHooks, entityMeta)
87
- : nodeWithHooks;
82
+ const nodeWithHooks = this.options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
83
+ const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
88
84
  // Handle ON CONFLICT clause
89
85
  let finalNode = nodeWithConvertedValues;
90
86
  if (node.onConflict?.updates && entityMeta) {
@@ -150,12 +146,8 @@ export class MikroTransformer extends OperationNodeTransformer {
150
146
  this.processJoinNode(join, currentContext);
151
147
  }
152
148
  }
153
- const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta
154
- ? this.processOnUpdateHooks(node, entityMeta)
155
- : node;
156
- const nodeWithConvertedValues = this.options.convertValues && entityMeta
157
- ? this.processUpdateValues(nodeWithHooks, entityMeta)
158
- : nodeWithHooks;
149
+ const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
150
+ const nodeWithConvertedValues = this.options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
159
151
  return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
160
152
  }
161
153
  finally {
@@ -217,7 +209,9 @@ export class MikroTransformer extends OperationNodeTransformer {
217
209
  }
218
210
  // Transform column names when columnNamingStrategy is 'property'
219
211
  // Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
220
- if (this.options.columnNamingStrategy === 'property' && parent && (ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
212
+ if (this.options.columnNamingStrategy === 'property' &&
213
+ parent &&
214
+ (ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
221
215
  const ownerMeta = this.findOwnerEntityInContext();
222
216
  if (ownerMeta) {
223
217
  const prop = ownerMeta.properties[node.name];
@@ -480,7 +474,11 @@ export class MikroTransformer extends OperationNodeTransformer {
480
474
  }
481
475
  }
482
476
  if (prop.customType && !isRaw(value)) {
483
- return prop.customType.convertToDatabaseValue(value, this.platform, { fromQuery: true, key: prop.name, mode: 'query-data' });
477
+ return prop.customType.convertToDatabaseValue(value, this.platform, {
478
+ fromQuery: true,
479
+ key: prop.name,
480
+ mode: 'query-data',
481
+ });
484
482
  }
485
483
  if (value instanceof Date) {
486
484
  return this.platform.processDateProperty(value);
@@ -723,7 +721,9 @@ export class MikroTransformer extends OperationNodeTransformer {
723
721
  */
724
722
  transformResult(rows, entityMap) {
725
723
  // Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
726
- if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) || !rows || rows.length === 0) {
724
+ if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) ||
725
+ !rows ||
726
+ rows.length === 0) {
727
727
  return rows;
728
728
  }
729
729
  // If no entities found (e.g. raw query without known tables), return rows as is
@@ -870,7 +870,8 @@ export class MikroTransformer extends OperationNodeTransformer {
870
870
  }
871
871
  // For non-local timezone, check if value already has timezone info
872
872
  // Number (timestamp) doesn't need timezone handling, string needs check
873
- if (typeof value === 'number' || (typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
873
+ if (typeof value === 'number' ||
874
+ (typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
874
875
  return this.platform.parseDate(value);
875
876
  }
876
877
  // Append timezone if not present (only for string values)
@@ -53,22 +53,34 @@ export class CriteriaNode {
53
53
  const type = this.prop ? this.prop.kind : null;
54
54
  const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
55
55
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
56
- const scalar = payload === null || Utils.isPrimaryKey(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
56
+ const scalar = payload === null ||
57
+ Utils.isPrimaryKey(payload) ||
58
+ payload instanceof RegExp ||
59
+ payload instanceof Date ||
60
+ rawField;
57
61
  const operator = Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
58
62
  if (composite) {
59
63
  return true;
60
64
  }
61
65
  switch (type) {
62
- case ReferenceKind.MANY_TO_ONE: return false;
63
- case ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
64
- case ReferenceKind.ONE_TO_MANY: return scalar || operator;
65
- case ReferenceKind.MANY_TO_MANY: return scalar || operator;
66
- default: return false;
66
+ case ReferenceKind.MANY_TO_ONE:
67
+ return false;
68
+ case ReferenceKind.ONE_TO_ONE:
69
+ return !this.prop.owner;
70
+ case ReferenceKind.ONE_TO_MANY:
71
+ return scalar || operator;
72
+ case ReferenceKind.MANY_TO_MANY:
73
+ return scalar || operator;
74
+ default:
75
+ return false;
67
76
  }
68
77
  }
69
78
  renameFieldToPK(qb, ownerAlias) {
70
79
  const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
71
- if (!joinAlias && this.parent && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) && this.prop.owner) {
80
+ if (!joinAlias &&
81
+ this.parent &&
82
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
83
+ this.prop.owner) {
72
84
  const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
73
85
  return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
74
86
  }
@@ -84,7 +96,9 @@ export class CriteriaNode {
84
96
  const parentPath = opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
85
97
  const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
86
98
  // ignore group operators to allow easier mapping (e.g. for orderBy)
87
- const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key) ? '.' + this.key : '';
99
+ const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
100
+ ? '.' + this.key
101
+ : '';
88
102
  const ret = parentPath + index + key;
89
103
  if (this.isPivotJoin()) {
90
104
  // distinguish pivot table join from target entity join
@@ -97,7 +111,11 @@ export class CriteriaNode {
97
111
  return false;
98
112
  }
99
113
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
100
- const scalar = this.payload === null || Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || rawField;
114
+ const scalar = this.payload === null ||
115
+ Utils.isPrimaryKey(this.payload) ||
116
+ this.payload instanceof RegExp ||
117
+ this.payload instanceof Date ||
118
+ rawField;
101
119
  const operator = Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
102
120
  return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
103
121
  }
@@ -116,7 +134,7 @@ export class CriteriaNode {
116
134
  const o = {};
117
135
  ['entityName', 'key', 'index', 'payload']
118
136
  .filter(k => this[k] !== undefined)
119
- .forEach(k => o[k] = this[k]);
137
+ .forEach(k => (o[k] = this[k]));
120
138
  return `${this.constructor.name} ${inspect(o)}`;
121
139
  }
122
140
  }
@@ -8,7 +8,11 @@ import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
8
8
  export class CriteriaNodeFactory {
9
9
  static createNode(metadata, entityName, payload, parent, key, validate = true) {
10
10
  const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
11
- const scalar = Utils.isPrimaryKey(payload) || isRaw(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
11
+ const scalar = Utils.isPrimaryKey(payload) ||
12
+ isRaw(payload) ||
13
+ payload instanceof RegExp ||
14
+ payload instanceof Date ||
15
+ rawField;
12
16
  if (Array.isArray(payload) && !scalar) {
13
17
  return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
14
18
  }
@@ -1,6 +1,17 @@
1
1
  import { type Dictionary, LockMode, type QueryFlag, RawQueryFragment, type Subquery } from '@mikro-orm/core';
2
2
  import { QueryType } from './enums.js';
3
3
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
4
+ export interface CteOptions {
5
+ columns?: string[];
6
+ /** PostgreSQL: MATERIALIZED / NOT MATERIALIZED */
7
+ materialized?: boolean;
8
+ }
9
+ interface CteClause extends CteOptions {
10
+ name: string;
11
+ sql: string;
12
+ params: unknown[];
13
+ recursive?: boolean;
14
+ }
4
15
  interface Options {
5
16
  tableName?: string | RawQueryFragment;
6
17
  indexHint?: string;
@@ -32,6 +43,7 @@ interface Options {
32
43
  hintComment?: string[];
33
44
  flags?: Set<QueryFlag>;
34
45
  wrap?: [prefix: string, suffix: string];
46
+ ctes?: CteClause[];
35
47
  }
36
48
  interface TableOptions {
37
49
  schema?: string;
@@ -65,6 +77,17 @@ export declare class NativeQueryBuilder implements Subquery {
65
77
  groupBy(groupBy: (string | RawQueryFragment)[]): this;
66
78
  join(sql: string, params: unknown[]): this;
67
79
  orderBy(orderBy: string): this;
80
+ /**
81
+ * The sub-query is compiled eagerly at call time — later mutations to the
82
+ * sub-query builder will not be reflected in this CTE.
83
+ */
84
+ with(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
85
+ /**
86
+ * Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
87
+ * The sub-query is compiled eagerly — later mutations will not be reflected.
88
+ */
89
+ withRecursive(name: string, query: NativeQueryBuilder | RawQueryFragment, options?: CteOptions): this;
90
+ private addCte;
68
91
  toString(): string;
69
92
  compile(): {
70
93
  sql: string;
@@ -103,6 +126,8 @@ export declare class NativeQueryBuilder implements Subquery {
103
126
  protected compileDelete(): void;
104
127
  protected compileTruncate(): void;
105
128
  protected addHintComment(): void;
129
+ protected compileCtes(): void;
130
+ protected getCteKeyword(hasRecursive: boolean): string;
106
131
  protected getTableName(): string;
107
132
  protected quote(id: string | RawQueryFragment | NativeQueryBuilder): string;
108
133
  }
@@ -1,4 +1,4 @@
1
- import { LockMode, raw, RawQueryFragment, Utils } from '@mikro-orm/core';
1
+ import { LockMode, raw, RawQueryFragment, Utils, } from '@mikro-orm/core';
2
2
  import { QueryType } from './enums.js';
3
3
  /** @internal */
4
4
  export class NativeQueryBuilder {
@@ -59,6 +59,36 @@ export class NativeQueryBuilder {
59
59
  this.options.orderBy = orderBy;
60
60
  return this;
61
61
  }
62
+ /**
63
+ * The sub-query is compiled eagerly at call time — later mutations to the
64
+ * sub-query builder will not be reflected in this CTE.
65
+ */
66
+ with(name, query, options) {
67
+ return this.addCte(name, query, options);
68
+ }
69
+ /**
70
+ * Adds a recursive CTE (`WITH RECURSIVE` on PostgreSQL/MySQL/SQLite, plain `WITH` on MSSQL).
71
+ * The sub-query is compiled eagerly — later mutations will not be reflected.
72
+ */
73
+ withRecursive(name, query, options) {
74
+ return this.addCte(name, query, options, true);
75
+ }
76
+ addCte(name, query, options, recursive) {
77
+ this.options.ctes ??= [];
78
+ if (this.options.ctes.some(cte => cte.name === name)) {
79
+ throw new Error(`CTE with name '${name}' already exists`);
80
+ }
81
+ const { sql, params } = query instanceof NativeQueryBuilder ? query.compile() : { sql: query.sql, params: [...query.params] };
82
+ this.options.ctes.push({
83
+ name,
84
+ sql,
85
+ params,
86
+ recursive,
87
+ columns: options?.columns,
88
+ materialized: options?.materialized,
89
+ });
90
+ return this;
91
+ }
62
92
  toString() {
63
93
  const { sql, params } = this.compile();
64
94
  return this.platform.formatQuery(sql, params);
@@ -72,6 +102,7 @@ export class NativeQueryBuilder {
72
102
  if (this.options.comment) {
73
103
  this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
74
104
  }
105
+ this.compileCtes();
75
106
  switch (this.type) {
76
107
  case QueryType.SELECT:
77
108
  case QueryType.COUNT:
@@ -387,6 +418,35 @@ export class NativeQueryBuilder {
387
418
  this.parts.push(`/*+ ${this.options.hintComment.join(' ')} */`);
388
419
  }
389
420
  }
421
+ compileCtes() {
422
+ const ctes = this.options.ctes;
423
+ if (!ctes || ctes.length === 0) {
424
+ return;
425
+ }
426
+ const hasRecursive = ctes.some(cte => cte.recursive);
427
+ const keyword = this.getCteKeyword(hasRecursive);
428
+ const cteParts = [];
429
+ for (const cte of ctes) {
430
+ let part = this.quote(cte.name);
431
+ if (cte.columns?.length) {
432
+ part += ` (${cte.columns.map(c => this.quote(c)).join(', ')})`;
433
+ }
434
+ part += ' as';
435
+ if (cte.materialized === true) {
436
+ part += ' materialized';
437
+ }
438
+ else if (cte.materialized === false) {
439
+ part += ' not materialized';
440
+ }
441
+ part += ` (${cte.sql})`;
442
+ this.params.push(...cte.params);
443
+ cteParts.push(part);
444
+ }
445
+ this.parts.push(`${keyword} ${cteParts.join(', ')}`);
446
+ }
447
+ getCteKeyword(hasRecursive) {
448
+ return hasRecursive ? 'with recursive' : 'with';
449
+ }
390
450
  getTableName() {
391
451
  if (!this.options.tableName) {
392
452
  throw new Error('No table name provided');
@@ -7,7 +7,8 @@ const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size'];
7
7
  */
8
8
  export class ObjectCriteriaNode extends CriteriaNode {
9
9
  process(qb, options) {
10
- const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
10
+ const matchPopulateJoins = options?.matchPopulateJoins ||
11
+ (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
11
12
  const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
12
13
  const ownerAlias = options?.alias || qb.alias;
13
14
  const keys = Utils.getObjectQueryKeys(this.payload);
@@ -20,13 +21,15 @@ export class ObjectCriteriaNode extends CriteriaNode {
20
21
  if (![ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(this.prop.kind)) {
21
22
  // ignore collection operators when used on a non-relational property - this can happen when they get into
22
23
  // populateWhere via `infer` on m:n properties with select-in strategy
23
- if (this.parent?.parent) { // we validate only usage on top level
24
+ if (this.parent?.parent) {
25
+ // we validate only usage on top level
24
26
  return {};
25
27
  }
26
28
  throw new Error(`Collection operators can be used only inside a collection property context, but it was used for ${this.getPath()}.`);
27
29
  }
28
30
  const $and = [];
29
- const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) || (this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
31
+ const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
32
+ (this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
30
33
  const parentMeta = this.metadata.find(this.parent.entityName);
31
34
  const primaryKeys = parentMeta.primaryKeys.map(pk => {
32
35
  return [QueryType.SELECT, QueryType.COUNT].includes(qb.type) ? `${knownKey ? alias : ownerAlias}.${pk}` : pk;
@@ -48,7 +51,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
48
51
  const pks = this.prop.referencedColumnNames;
49
52
  const countExpr = raw(`count(${pks.map(() => '??').join(', ')})`, pks.map(pk => `${joinAlias}.${pk}`));
50
53
  sub.groupBy(parentMeta.primaryKeys);
51
- sub.having({ $and: Object.keys(sizeCondition).map(op => ({ [countExpr]: { [op]: sizeCondition[op] } })) });
54
+ sub.having({
55
+ $and: Object.keys(sizeCondition).map(op => ({ [countExpr]: { [op]: sizeCondition[op] } })),
56
+ });
52
57
  }
53
58
  else if (key === '$every') {
54
59
  sub.where({ $not: { [this.key]: payload } });
@@ -111,7 +116,11 @@ export class ObjectCriteriaNode extends CriteriaNode {
111
116
  // use '??' placeholder to properly quote the identifier
112
117
  o[raw('??', [field])] = payload;
113
118
  }
114
- else if (primaryKey || virtual || operator || field.includes('.') || ![QueryType.SELECT, QueryType.COUNT].includes(qb.type)) {
119
+ else if (primaryKey ||
120
+ virtual ||
121
+ operator ||
122
+ field.includes('.') ||
123
+ ![QueryType.SELECT, QueryType.COUNT].includes(qb.type)) {
115
124
  this.inlineCondition(field.replaceAll(ALIAS_REPLACEMENT, alias), o, payload);
116
125
  }
117
126
  else {
@@ -121,9 +130,10 @@ export class ObjectCriteriaNode extends CriteriaNode {
121
130
  }, {});
122
131
  }
123
132
  isStrict() {
124
- return this.strict || Utils.getObjectQueryKeys(this.payload).some(key => {
125
- return this.payload[key].isStrict();
126
- });
133
+ return (this.strict ||
134
+ Utils.getObjectQueryKeys(this.payload).some(key => {
135
+ return this.payload[key].isStrict();
136
+ }));
127
137
  }
128
138
  unwrap() {
129
139
  return Utils.getObjectQueryKeys(this.payload).reduce((o, field) => {
@@ -148,8 +158,18 @@ export class ObjectCriteriaNode extends CriteriaNode {
148
158
  }
149
159
  shouldInline(payload) {
150
160
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
151
- const scalar = Utils.isPrimaryKey(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
152
- const operator = Utils.isObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
161
+ const scalar = Utils.isPrimaryKey(payload) ||
162
+ payload instanceof RegExp ||
163
+ payload instanceof Date ||
164
+ rawField;
165
+ const operator = Utils.isObject(payload) &&
166
+ Utils.getObjectQueryKeys(payload).every(k => {
167
+ if (k === '$not' && Utils.isPlainObject(payload[k])) {
168
+ // $not wrapping non-operator conditions (entity props) should be inlined
169
+ return Utils.getObjectQueryKeys(payload[k]).every(ik => Utils.isOperator(ik, false));
170
+ }
171
+ return Utils.isOperator(k, false);
172
+ });
153
173
  return !!this.prop && this.prop.kind !== ReferenceKind.SCALAR && !scalar && !operator;
154
174
  }
155
175
  getChildKey(k, prop, childAlias, alias) {
@@ -161,7 +181,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
161
181
  inlineArrayChildPayload(obj, payload, k, prop, childAlias, alias) {
162
182
  const key = this.getChildKey(k, prop, childAlias);
163
183
  const value = payload.map((child) => Utils.getObjectQueryKeys(child).reduce((inner, childKey) => {
164
- const key = (RawQueryFragment.isKnownFragmentSymbol(childKey) || this.isPrefixed(childKey) || Utils.isOperator(childKey)) ? childKey : this.aliased(childKey, childAlias);
184
+ const key = RawQueryFragment.isKnownFragmentSymbol(childKey) || this.isPrefixed(childKey) || Utils.isOperator(childKey)
185
+ ? childKey
186
+ : this.aliased(childKey, childAlias);
165
187
  inner[key] = child[childKey];
166
188
  return inner;
167
189
  }, {}));
@@ -173,6 +195,12 @@ export class ObjectCriteriaNode extends CriteriaNode {
173
195
  if (RawQueryFragment.isKnownFragmentSymbol(k)) {
174
196
  o[k] = payload[k];
175
197
  }
198
+ else if (k === '$not' &&
199
+ Utils.isPlainObject(payload[k]) &&
200
+ Utils.getObjectQueryKeys(payload[k]).some(ik => !Utils.isOperator(ik, false))) {
201
+ // $not wraps entity conditions (from auto-join), inline at current level
202
+ this.inlineCondition(k, o, payload[k]);
203
+ }
176
204
  else if (Utils.isOperator(k, false)) {
177
205
  const tmp = payload[k];
178
206
  delete payload[k];
@@ -218,24 +246,42 @@ export class ObjectCriteriaNode extends CriteriaNode {
218
246
  }
219
247
  const meta = this.metadata.find(this.entityName);
220
248
  const embeddable = this.prop.kind === ReferenceKind.EMBEDDED;
221
- const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) || (this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
222
- const operatorKeys = knownKey && keys.every(key => Utils.isOperator(key, false));
223
- const primaryKeys = knownKey && keys.every(key => {
224
- if (typeof key !== 'string' || !meta.primaryKeys.includes(key)) {
225
- return false;
226
- }
227
- if (!Utils.isPlainObject(this.payload[key].payload) || ![ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[key].kind)) {
228
- return true;
229
- }
230
- return Utils.getObjectQueryKeys(this.payload[key].payload).every(k => typeof k === 'string' && meta.properties[key].targetMeta.primaryKeys.includes(k));
231
- });
249
+ const knownKey = [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE, ReferenceKind.EMBEDDED].includes(this.prop.kind) ||
250
+ (this.prop.kind === ReferenceKind.ONE_TO_ONE && this.prop.owner);
251
+ const operatorKeys = knownKey &&
252
+ keys.every(key => {
253
+ if (key === '$not') {
254
+ // $not wraps conditions like $and/$or, check if it wraps entity property conditions (needs auto-join)
255
+ // vs simple operator conditions on the FK (doesn't need auto-join)
256
+ const childPayload = this.payload[key].payload;
257
+ if (Utils.isPlainObject(childPayload)) {
258
+ return Utils.getObjectQueryKeys(childPayload).every(k => Utils.isOperator(k, false));
259
+ }
260
+ }
261
+ return Utils.isOperator(key, false);
262
+ });
263
+ const primaryKeys = knownKey &&
264
+ keys.every(key => {
265
+ if (typeof key !== 'string' || !meta.primaryKeys.includes(key)) {
266
+ return false;
267
+ }
268
+ if (!Utils.isPlainObject(this.payload[key].payload) ||
269
+ ![ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[key].kind)) {
270
+ return true;
271
+ }
272
+ return Utils.getObjectQueryKeys(this.payload[key].payload).every(k => typeof k === 'string' && meta.properties[key].targetMeta.primaryKeys.includes(k));
273
+ });
232
274
  return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
233
275
  }
234
276
  autoJoin(qb, alias, options) {
235
277
  const nestedAlias = qb.getNextAlias(this.prop?.pivotEntity ?? this.entityName);
236
278
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
237
- const scalar = Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || rawField;
238
- const operator = Utils.isPlainObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
279
+ const scalar = Utils.isPrimaryKey(this.payload) ||
280
+ this.payload instanceof RegExp ||
281
+ this.payload instanceof Date ||
282
+ rawField;
283
+ const operator = Utils.isPlainObject(this.payload) &&
284
+ Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
239
285
  const field = `${alias}.${this.prop.name}`;
240
286
  const method = qb.hasFlag(QueryFlag.INFER_POPULATE) ? 'joinAndSelect' : 'join';
241
287
  const path = this.getPath();
@@ -245,9 +291,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
245
291
  else {
246
292
  const prev = qb._fields?.slice();
247
293
  const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
248
- const joinType = toOneProperty && !this.prop.nullable
249
- ? JoinType.innerJoin
250
- : JoinType.leftJoin;
294
+ const joinType = toOneProperty && !this.prop.nullable ? JoinType.innerJoin : JoinType.leftJoin;
251
295
  qb[method](field, nestedAlias, undefined, joinType, path);
252
296
  if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
253
297
  qb._fields = prev;