@mikro-orm/knex 7.0.0-dev.2 → 7.0.0-dev.20

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,11 +1,11 @@
1
1
  import { type ControlledTransaction, type Dialect, Kysely } from 'kysely';
2
- import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LoggingOptions, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
2
+ import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LoggingOptions, type MaybePromise, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
3
3
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
4
4
  import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
5
5
  export declare abstract class AbstractSqlConnection extends Connection {
6
6
  protected platform: AbstractSqlPlatform;
7
7
  protected client: Kysely<any>;
8
- abstract createKyselyDialect(overrides: Dictionary): Dialect;
8
+ abstract createKyselyDialect(overrides: Dictionary): MaybePromise<Dialect>;
9
9
  connect(): Promise<void>;
10
10
  /**
11
11
  * @inheritDoc
@@ -19,8 +19,7 @@ export class AbstractSqlConnection extends Connection {
19
19
  }
20
20
  else {
21
21
  this.client = new Kysely({
22
- dialect: this.createKyselyDialect(driverOptions),
23
- // log: m => console.log(m),
22
+ dialect: await this.createKyselyDialect(driverOptions),
24
23
  });
25
24
  }
26
25
  this.connected = true;
@@ -73,14 +72,17 @@ export class AbstractSqlConnection extends Connection {
73
72
  async begin(options = {}) {
74
73
  if (options.ctx) {
75
74
  const ctx = options.ctx;
75
+ await options.eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart, ctx);
76
76
  ctx.index ??= 0;
77
77
  const savepointName = `trx${ctx.index + 1}`;
78
78
  const trx = await options.ctx.savepoint(savepointName).execute();
79
79
  Reflect.defineProperty(trx, 'index', { value: ctx.index + 1 });
80
80
  Reflect.defineProperty(trx, 'savepointName', { value: savepointName });
81
81
  this.logQuery(this.platform.getSavepointSQL(savepointName));
82
+ await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
82
83
  return trx;
83
84
  }
85
+ await this.ensureConnection();
84
86
  await options.eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart);
85
87
  let trxBuilder = this.client.startTransaction();
86
88
  if (options.isolationLevel) {
@@ -100,25 +102,27 @@ export class AbstractSqlConnection extends Connection {
100
102
  if (ctx.isRolledBack) {
101
103
  return;
102
104
  }
105
+ await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
103
106
  if ('savepointName' in ctx) {
104
107
  await ctx.releaseSavepoint(ctx.savepointName).execute();
105
108
  this.logQuery(this.platform.getReleaseSavepointSQL(ctx.savepointName));
106
- return;
107
109
  }
108
- await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
109
- await ctx.commit().execute();
110
- this.logQuery(this.platform.getCommitTransactionSQL());
110
+ else {
111
+ await ctx.commit().execute();
112
+ this.logQuery(this.platform.getCommitTransactionSQL());
113
+ }
111
114
  await eventBroadcaster?.dispatchEvent(EventType.afterTransactionCommit, ctx);
112
115
  }
113
116
  async rollback(ctx, eventBroadcaster) {
117
+ await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
114
118
  if ('savepointName' in ctx) {
115
119
  await ctx.rollbackToSavepoint(ctx.savepointName).execute();
116
120
  this.logQuery(this.platform.getRollbackToSavepointSQL(ctx.savepointName));
117
- return;
118
121
  }
119
- await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
120
- await ctx.rollback().execute();
121
- this.logQuery(this.platform.getRollbackTransactionSQL());
122
+ else {
123
+ await ctx.rollback().execute();
124
+ this.logQuery(this.platform.getRollbackTransactionSQL());
125
+ }
122
126
  await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
123
127
  }
124
128
  async execute(query, params = [], method = 'all', ctx, loggerContext) {
@@ -14,12 +14,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
14
14
  protected constructor(config: Configuration, platform: Platform, connection: Constructor<Connection>, connector: string[]);
15
15
  getPlatform(): Platform;
16
16
  createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
17
- find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
18
- findOne<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
17
+ find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
18
+ findOne<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: string, where: ObjectQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
19
19
  protected hasToManyJoins<T extends object>(hint: PopulateOptions<T>, meta: EntityMetadata<T>): boolean;
20
- findVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
21
- countVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: CountOptions<T, any>): Promise<number>;
22
- protected findFromVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
20
+ findVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
21
+ countVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: CountOptions<T, any>): Promise<number>;
22
+ protected findFromVirtual<T extends object>(entityName: string, where: ObjectQuery<T>, options: FindOptions<T, any> | CountOptions<T, any>, type: QueryType): Promise<EntityData<T>[] | number>;
23
23
  protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
24
24
  mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
25
25
  private mapJoinedProps;
@@ -63,6 +63,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
63
63
  if (options.lockMode) {
64
64
  qb.setLockMode(options.lockMode, options.lockTableAliases);
65
65
  }
66
+ if (options.em) {
67
+ await qb.applyJoinedFilters(options.em, options.filters);
68
+ }
66
69
  const result = await this.rethrow(qb.execute('all'));
67
70
  if (isCursorPagination && !first && !!last) {
68
71
  result.reverse();
@@ -252,6 +255,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
252
255
  : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
253
256
  const tz = this.platform.getTimezone();
254
257
  for (const prop of targetProps) {
258
+ if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
259
+ continue;
260
+ }
255
261
  if (prop.fieldNames.length > 1) { // composite keys
256
262
  const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
257
263
  const pk = Utils.mapFlatCompositePrimaryKey(fk, prop);
@@ -434,7 +440,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
434
440
  else {
435
441
  const field = prop.fieldNames[0];
436
442
  if (!duplicates.includes(field) || !usedDups.includes(field)) {
437
- if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(row[prop.name])) {
443
+ if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
438
444
  keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
439
445
  }
440
446
  else {
@@ -585,7 +591,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
585
591
  if (key in data[idx]) {
586
592
  const pks = Utils.getOrderedPrimaryKeys(cond, meta);
587
593
  sql += ` when (${pkCond}) then `;
588
- if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(data[idx][key])) {
594
+ if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
589
595
  sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
590
596
  }
591
597
  else {
@@ -725,8 +731,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
725
731
  const pivotMeta = this.metadata.find(coll.property.pivotEntity);
726
732
  let schema = pivotMeta.schema;
727
733
  if (schema === '*') {
728
- const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
729
- schema = coll.property.owner ? ownerSchema : this.config.get('schema');
734
+ if (coll.property.owner) {
735
+ schema = wrapped.getSchema() === '*' ? options?.schema ?? this.config.get('schema') : wrapped.getSchema();
736
+ }
737
+ else {
738
+ const targetMeta = coll.property.targetMeta;
739
+ const targetSchema = (coll[0] ?? snap?.[0]) && helper(coll[0] ?? snap?.[0]).getSchema();
740
+ schema = targetMeta.schema === '*' ? options?.schema ?? targetSchema ?? this.config.get('schema') : targetMeta.schema;
741
+ }
730
742
  }
731
743
  else if (schema == null) {
732
744
  schema = this.config.get('schema');
@@ -777,13 +789,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
777
789
  const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
778
790
  qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
779
791
  const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options);
792
+ const additionalFields = [];
780
793
  for (const field of targetFields) {
781
794
  const f = field.toString();
782
- fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
795
+ additionalFields.push(f.includes('.') ? field : `${targetAlias}.${f}`);
783
796
  if (RawQueryFragment.isKnownFragment(field)) {
784
797
  qb.rawFragments.add(f);
785
798
  }
786
799
  }
800
+ fields.unshift(...additionalFields);
787
801
  // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
788
802
  populate.forEach(hint => {
789
803
  const alias = qb.getNextAlias(prop.targetMeta.tableName);
@@ -927,11 +941,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
927
941
  const fields = [];
928
942
  const populate = options.populate ?? [];
929
943
  const joinedProps = this.joinedProps(meta, populate, options);
930
- const shouldHaveColumn = (prop, populate, fields) => {
944
+ const shouldHaveColumn = (meta, prop, populate, fields) => {
931
945
  if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
932
946
  return false;
933
947
  }
934
- if (!fields || fields.includes('*') || prop.primary) {
948
+ if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
935
949
  return true;
936
950
  }
937
951
  return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
@@ -941,7 +955,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
941
955
  if (options.parentJoinPath) {
942
956
  // alias all fields in the primary table
943
957
  meta.props
944
- .filter(prop => shouldHaveColumn(prop, populate, options.explicitFields))
958
+ .filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
945
959
  .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias)));
946
960
  }
947
961
  for (const hint of joinedProps) {
@@ -1289,6 +1303,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1289
1303
  if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
1290
1304
  ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
1291
1305
  }
1306
+ if (meta.root.discriminatorColumn && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1307
+ ret.push(meta.root.discriminatorColumn);
1308
+ }
1292
1309
  }
1293
1310
  else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
1294
1311
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
@@ -65,9 +65,11 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
65
65
  this.params.push(...clause.fields.params);
66
66
  }
67
67
  else if (clause.fields.length > 0) {
68
- // const fields = clause.fields.map(field => this.quote(field));
69
- const conflictColumn = this.quote(clause.fields[0]);
70
- this.parts.push(`on ${this.getTableName()}.${conflictColumn} = tsource.${conflictColumn}`);
68
+ const fields = clause.fields.map(field => {
69
+ const col = this.quote(field);
70
+ return `${this.getTableName()}.${col} = tsource.${col}`;
71
+ });
72
+ this.parts.push(`on ${fields.join(' and ')}`);
71
73
  }
72
74
  const sourceColumns = keys.map(field => `tsource.${this.quote(field)}`).join(', ');
73
75
  const destinationColumns = keys.map(field => this.quote(field)).join(', ');
@@ -80,7 +82,9 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
80
82
  }
81
83
  this.parts.push('then update set');
82
84
  if (!clause.merge || Array.isArray(clause.merge)) {
83
- const parts = keys.map((column) => `${this.quote(column)} = tsource.${this.quote(column)}`);
85
+ const parts = (clause.merge || keys)
86
+ .filter(field => !Array.isArray(clause.fields) || !clause.fields.includes(field))
87
+ .map((column) => `${this.quote(column)} = tsource.${this.quote(column)}`);
84
88
  this.parts.push(parts.join(', '));
85
89
  }
86
90
  else if (typeof clause.merge === 'object') {
@@ -1,5 +1,5 @@
1
1
  import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
2
2
  export declare abstract class BaseSqliteConnection extends AbstractSqlConnection {
3
- connect(): Promise<void>;
3
+ connect(simple?: boolean): Promise<void>;
4
4
  getClientUrl(): string;
5
5
  }
@@ -3,9 +3,15 @@ import { CompiledQuery } from 'kysely';
3
3
  import { Utils } from '@mikro-orm/core';
4
4
  import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
5
5
  export class BaseSqliteConnection extends AbstractSqlConnection {
6
- async connect() {
6
+ async connect(simple = false) {
7
7
  await super.connect();
8
- Utils.ensureDir(dirname(this.config.get('dbName')));
8
+ if (simple) {
9
+ return;
10
+ }
11
+ const dbName = this.config.get('dbName');
12
+ if (dbName && dbName !== ':memory:') {
13
+ Utils.ensureDir(dirname(this.config.get('dbName')));
14
+ }
9
15
  await this.client.executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
10
16
  }
11
17
  getClientUrl() {
@@ -63,7 +63,6 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
63
63
  */
64
64
  processDateProperty(value: unknown): string | number | Date;
65
65
  getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
66
- supportsDownMigrations(): boolean;
67
66
  supportsDeferredUniqueConstraints(): boolean;
68
67
  getFullTextWhereClause(): string;
69
68
  quoteVersionValue(value: Date | number, prop: EntityProperty): Date | string | number;
@@ -84,10 +84,6 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
84
84
  }
85
85
  return super.getIndexName(tableName, columns, type);
86
86
  }
87
- // TODO enable once tests are green
88
- supportsDownMigrations() {
89
- return false;
90
- }
91
87
  supportsDeferredUniqueConstraints() {
92
88
  return false;
93
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/knex",
3
- "version": "7.0.0-dev.2",
3
+ "version": "7.0.0-dev.20",
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
  "type": "module",
6
6
  "exports": {
@@ -50,13 +50,13 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "kysely": "https://pkg.pr.new/kysely-org/kysely/kysely@2b7007e",
53
+ "kysely": "0.28.2",
54
54
  "sqlstring": "2.3.3"
55
55
  },
56
56
  "devDependencies": {
57
- "@mikro-orm/core": "^6.4.5"
57
+ "@mikro-orm/core": "^6.4.15"
58
58
  },
59
59
  "peerDependencies": {
60
- "@mikro-orm/core": "7.0.0-dev.2"
60
+ "@mikro-orm/core": "7.0.0-dev.20"
61
61
  }
62
62
  }
@@ -75,10 +75,6 @@ export class CriteriaNode {
75
75
  if (this.prop.kind === ReferenceKind.MANY_TO_MANY) {
76
76
  return Utils.getPrimaryKeyHash(this.prop.inverseJoinColumns.map(col => `${alias}.${col}`));
77
77
  }
78
- // if we found a matching join, we need to use the target table column names, as we use that alias instead of the root
79
- if (!joinAlias && this.prop.owner && this.prop.joinColumns.length > 1) {
80
- return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
81
- }
82
78
  return Utils.getPrimaryKeyHash(this.prop.referencedColumnNames.map(col => `${alias}.${col}`));
83
79
  }
84
80
  getPath(addIndex = false) {
@@ -55,7 +55,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
55
55
  }
56
56
  return { $and };
57
57
  }
58
- alias = this.autoJoin(qb, ownerAlias);
58
+ alias = this.autoJoin(qb, ownerAlias, options);
59
59
  }
60
60
  return keys.reduce((o, field) => {
61
61
  const childNode = this.payload[field];
@@ -193,23 +193,27 @@ export class ObjectCriteriaNode extends CriteriaNode {
193
193
  });
194
194
  return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
195
195
  }
196
- autoJoin(qb, alias) {
196
+ autoJoin(qb, alias, options) {
197
197
  const nestedAlias = qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
198
198
  const customExpression = RawQueryFragment.isKnownFragment(this.key);
199
199
  const scalar = Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || customExpression;
200
200
  const operator = Utils.isPlainObject(this.payload) && Object.keys(this.payload).every(k => Utils.isOperator(k, false));
201
201
  const field = `${alias}.${this.prop.name}`;
202
202
  const method = qb.hasFlag(QueryFlag.INFER_POPULATE) ? 'joinAndSelect' : 'join';
203
+ const path = this.getPath();
203
204
  if (this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator)) {
204
- qb.join(field, nestedAlias, undefined, JoinType.pivotJoin, this.getPath());
205
+ qb.join(field, nestedAlias, undefined, JoinType.pivotJoin, path);
205
206
  }
206
207
  else {
207
208
  const prev = qb._fields?.slice();
208
- qb[method](field, nestedAlias, undefined, JoinType.leftJoin, this.getPath());
209
+ qb[method](field, nestedAlias, undefined, JoinType.leftJoin, path);
209
210
  if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
210
211
  qb._fields = prev;
211
212
  }
212
213
  }
214
+ if (!options || options.type !== 'orderBy') {
215
+ qb.scheduleFilterCheck(path);
216
+ }
213
217
  return nestedAlias;
214
218
  }
215
219
  isPrefixed(field) {
@@ -1,5 +1,5 @@
1
1
  import { inspect } from 'node:util';
2
- import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
2
+ import { type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityKey, type EntityManager, type EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type ObjectQuery, PopulateHint, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, QueryFlag, type QueryOrderMap, type QueryResult, RawQueryFragment, type RequiredEntityData, type Transaction } from '@mikro-orm/core';
3
3
  import { JoinType, QueryType } from './enums.js';
4
4
  import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
5
5
  import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
@@ -141,6 +141,15 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
141
141
  * Apply filters to the QB where condition.
142
142
  */
143
143
  applyFilters(filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
144
+ private readonly autoJoinedPaths;
145
+ /**
146
+ * @internal
147
+ */
148
+ scheduleFilterCheck(path: string): void;
149
+ /**
150
+ * @internal
151
+ */
152
+ applyJoinedFilters(em: EntityManager, filterOptions?: Dictionary<boolean | Dictionary> | string[] | boolean): Promise<void>;
144
153
  withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
145
154
  where(cond: QBFilterQuery<Entity>, operator?: keyof typeof GroupOperator): this;
146
155
  where(cond: string, params?: any[], operator?: keyof typeof GroupOperator): this;
@@ -256,10 +265,6 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
256
265
  * Executes the query, returning both array of results and total count query (without offset and limit).
257
266
  */
258
267
  getResultAndCount(): Promise<[Entity[], number]>;
259
- /**
260
- * Provides promise-like interface so we can await the QB instance.
261
- */
262
- then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<Loaded<Entity, Hint>[] | number | QueryResult<Entity>>;
263
268
  /**
264
269
  * Returns native query builder instance with sub-query aliased with given alias.
265
270
  * You can provide `EntityName.propName` as alias, then the field name will be used based on the metadata
@@ -303,14 +308,12 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
303
308
  export interface RunQueryBuilder<Entity extends object> extends Omit<QueryBuilder<Entity, any, any>, 'getResult' | 'getSingleResult' | 'getResultList' | 'where'> {
304
309
  where(cond: QBFilterQuery<Entity> | string, params?: keyof typeof GroupOperator | any[], operator?: keyof typeof GroupOperator): this;
305
310
  execute<Result = QueryResult<Entity>>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
306
- then<TResult1 = QueryResult<Entity>, TResult2 = never>(onfulfilled?: ((value: QueryResult<Entity>) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<QueryResult<Entity>>;
307
311
  }
308
312
  export interface SelectQueryBuilder<Entity extends object = AnyEntity, RootAlias extends string = never, Hint extends string = never, Context extends object = never> extends QueryBuilder<Entity, RootAlias, Hint, Context> {
309
313
  execute<Result = Entity[]>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<Result>;
310
314
  execute<Result = Entity[]>(method: 'all', mapResults?: boolean): Promise<Result>;
311
315
  execute<Result = Entity>(method: 'get', mapResults?: boolean): Promise<Result>;
312
316
  execute<Result = QueryResult<Entity>>(method: 'run', mapResults?: boolean): Promise<Result>;
313
- then<TResult1 = Entity[], TResult2 = never>(onfulfilled?: ((value: Loaded<Entity, Hint>[]) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<Loaded<Entity, Hint>[]>;
314
317
  }
315
318
  export interface CountQueryBuilder<Entity extends object> extends QueryBuilder<Entity, any, any> {
316
319
  execute<Result = {
@@ -325,7 +328,6 @@ export interface CountQueryBuilder<Entity extends object> extends QueryBuilder<E
325
328
  execute<Result = QueryResult<{
326
329
  count: number;
327
330
  }>>(method: 'run', mapResults?: boolean): Promise<Result>;
328
- then<TResult1 = number, TResult2 = never>(onfulfilled?: ((value: number) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<number>;
329
331
  }
330
332
  export interface InsertQueryBuilder<T extends object> extends RunQueryBuilder<T> {
331
333
  }
@@ -240,9 +240,12 @@ export class QueryBuilder {
240
240
  }
241
241
  }
242
242
  prop.targetMeta.props
243
- .filter(prop => explicitFields
244
- ? explicitFields.includes(prop.name) || explicitFields.includes(`${alias}.${prop.name}`) || prop.primary
245
- : this.platform.shouldHaveColumn(prop, populate))
243
+ .filter(prop => {
244
+ if (!explicitFields) {
245
+ return this.platform.shouldHaveColumn(prop, populate);
246
+ }
247
+ return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
248
+ })
246
249
  .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
247
250
  return fields;
248
251
  }
@@ -257,6 +260,40 @@ export class QueryBuilder {
257
260
  const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read');
258
261
  this.andWhere(cond);
259
262
  }
263
+ autoJoinedPaths = [];
264
+ /**
265
+ * @internal
266
+ */
267
+ scheduleFilterCheck(path) {
268
+ this.autoJoinedPaths.push(path);
269
+ }
270
+ /**
271
+ * @internal
272
+ */
273
+ async applyJoinedFilters(em, filterOptions = {}) {
274
+ for (const path of this.autoJoinedPaths) {
275
+ const join = this.getJoinForPath(path);
276
+ if (join.type === JoinType.pivotJoin) {
277
+ continue;
278
+ }
279
+ const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
280
+ if (Utils.hasObjectKeys(cond)) {
281
+ // remove nested filters, we only care about scalars here, nesting would require another join branch
282
+ for (const key of Object.keys(cond)) {
283
+ if (Utils.isPlainObject(cond[key]) && Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every'].includes(k)))) {
284
+ delete cond[key];
285
+ }
286
+ }
287
+ if (Utils.hasObjectKeys(join.cond)) {
288
+ /* istanbul ignore next */
289
+ join.cond = { $and: [join.cond, cond] };
290
+ }
291
+ else {
292
+ join.cond = { ...cond };
293
+ }
294
+ }
295
+ }
296
+ }
260
297
  withSubQuery(subQuery, alias) {
261
298
  this.ensureNotFinalized();
262
299
  if (subQuery instanceof RawQueryFragment) {
@@ -335,7 +372,7 @@ export class QueryBuilder {
335
372
  convertCustomTypes: false,
336
373
  type: 'orderBy',
337
374
  });
338
- this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true }));
375
+ this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true, type: 'orderBy' }));
339
376
  });
340
377
  return this;
341
378
  }
@@ -647,7 +684,7 @@ export class QueryBuilder {
647
684
  }
648
685
  const query = this.toQuery();
649
686
  const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ['qb.execute', query.sql, query.params, method]);
650
- if (cached?.data) {
687
+ if (cached?.data !== undefined) {
651
688
  return cached.data;
652
689
  }
653
690
  const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
@@ -753,26 +790,6 @@ export class QueryBuilder {
753
790
  await this.clone().getCount(),
754
791
  ];
755
792
  }
756
- /**
757
- * Provides promise-like interface so we can await the QB instance.
758
- */
759
- then(onfulfilled, onrejected) {
760
- let type = this.type;
761
- if (this.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(QueryFlag.DELETE_SUB_QUERY)) {
762
- type = QueryType.UPDATE;
763
- }
764
- switch (type) {
765
- case QueryType.INSERT:
766
- case QueryType.UPDATE:
767
- case QueryType.DELETE:
768
- case QueryType.UPSERT:
769
- case QueryType.TRUNCATE:
770
- return this.execute('run').then(onfulfilled, onrejected);
771
- case QueryType.COUNT:
772
- return this.getCount().then(onfulfilled, onrejected);
773
- case QueryType.SELECT: return this.getResultList().then(onfulfilled, onrejected);
774
- }
775
- }
776
793
  /**
777
794
  * Returns native query builder instance with sub-query aliased with given alias.
778
795
  * You can provide `EntityName.propName` as alias, then the field name will be used based on the metadata
@@ -1158,6 +1175,7 @@ export class QueryBuilder {
1158
1175
  this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
1159
1176
  this._joins[aliasedName].path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className)}.${prop.name}`;
1160
1177
  this._populateMap[aliasedName] = this._joins[aliasedName].alias;
1178
+ this.createAlias(prop.type, alias);
1161
1179
  }
1162
1180
  });
1163
1181
  this.processPopulateWhere(false);
@@ -1172,7 +1190,7 @@ export class QueryBuilder {
1172
1190
  let joins = Object.values(this._joins);
1173
1191
  for (const join of joins) {
1174
1192
  join.cond_ ??= join.cond;
1175
- join.cond = filter ? { ...join.cond } : {};
1193
+ join.cond = { ...join.cond };
1176
1194
  }
1177
1195
  if (typeof this[key] === 'object') {
1178
1196
  const cond = CriteriaNodeFactory
@@ -1219,9 +1237,7 @@ export class QueryBuilder {
1219
1237
  }
1220
1238
  }
1221
1239
  hasToManyJoins() {
1222
- // console.log(this._joins);
1223
1240
  return Object.values(this._joins).some(join => {
1224
- // console.log(join.prop.name, join.prop.kind, [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind));
1225
1241
  return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
1226
1242
  });
1227
1243
  }
@@ -39,9 +39,6 @@ export class QueryBuilderHelper {
39
39
  const prop = this.getProperty(f, a);
40
40
  const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
41
41
  if (fkIdx2 !== -1) {
42
- if (prop?.ownColumns && !prop.ownColumns.includes(f)) {
43
- continue;
44
- }
45
42
  parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
46
43
  }
47
44
  else if (prop) {
@@ -62,9 +59,6 @@ export class QueryBuilderHelper {
62
59
  }
63
60
  });
64
61
  }
65
- if (parts.length === 1) {
66
- return parts[0];
67
- }
68
62
  return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
69
63
  }
70
64
  const rawField = RawQueryFragment.getKnownFragment(field);
@@ -257,6 +251,105 @@ export class QueryBuilderHelper {
257
251
  }
258
252
  return { sql, params };
259
253
  }
254
+ // <<<<<<< HEAD
255
+ // private processJoinClause(key: string, value: unknown, alias: string, params: Knex.Value[], operator = '$eq'): string {
256
+ // if (Utils.isGroupOperator(key) && Array.isArray(value)) {
257
+ // const parts = value.map(sub => {
258
+ // return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], alias, params)));
259
+ // });
260
+ // return this.wrapQueryGroup(parts, key);
261
+ // }
262
+ //
263
+ // const rawField = RawQueryFragment.getKnownFragment(key);
264
+ //
265
+ // if (!rawField && !Utils.isOperator(key, false) && !this.isPrefixed(key)) {
266
+ // key = `${alias}.${key}`;
267
+ // }
268
+ //
269
+ // if (this.isSimpleRegExp(value)) {
270
+ // params.push(this.getRegExpParam(value));
271
+ // return `${this.knex.ref(this.mapper(key))} like ?`;
272
+ // }
273
+ //
274
+ // if (value instanceof RegExp) {
275
+ // value = this.platform.getRegExpValue(value);
276
+ // }
277
+ //
278
+ // if (Utils.isOperator(key, false) && Utils.isPlainObject(value)) {
279
+ // const parts = Object.keys(value).map(k => this.processJoinClause(k, (value as Dictionary)[k], alias, params, key));
280
+ //
281
+ // return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
282
+ // }
283
+ //
284
+ // if (Utils.isPlainObject(value) && Object.keys(value).every(k => Utils.isOperator(k, false))) {
285
+ // const parts = Object.keys(value).map(op => this.processJoinClause(key, (value as Dictionary)[op], alias, params, op));
286
+ //
287
+ // return this.wrapQueryGroup(parts);
288
+ // }
289
+ //
290
+ // const [fromAlias, fromField] = this.splitField(key as EntityKey);
291
+ // const prop = this.getProperty(fromField, fromAlias);
292
+ // operator = operator === '$not' ? '$eq' : operator;
293
+ // const column = this.mapper(key, undefined, undefined, null);
294
+ //
295
+ // if (value === null) {
296
+ // return `${this.knex.ref(column)} is ${operator === '$ne' ? 'not ' : ''}null`;
297
+ // }
298
+ //
299
+ // if (operator === '$fulltext' && prop) {
300
+ // const query = this.knex.raw(this.platform.getFullTextWhereClause(prop), {
301
+ // column,
302
+ // query: this.knex.raw('?'),
303
+ // }).toSQL().toNative();
304
+ // params.push(value as Knex.Value);
305
+ //
306
+ // return query.sql;
307
+ // }
308
+ //
309
+ // const replacement = this.getOperatorReplacement(operator, { [operator]: value });
310
+ //
311
+ // if (['$in', '$nin'].includes(operator) && Array.isArray(value)) {
312
+ // params.push(...value as Knex.Value[]);
313
+ // return `${this.knex.ref(column)} ${replacement} (${value.map(() => '?').join(', ')})`;
314
+ // }
315
+ //
316
+ // if (operator === '$exists') {
317
+ // value = null;
318
+ // }
319
+ //
320
+ // if (rawField) {
321
+ // let sql = rawField.sql.replaceAll(ALIAS_REPLACEMENT, alias);
322
+ // params.push(...rawField.params as Knex.Value[]);
323
+ // params.push(...Utils.asArray(value) as Knex.Value[]);
324
+ //
325
+ // if ((Utils.asArray(value) as Knex.Value[]).length > 0) {
326
+ // sql += ' = ?';
327
+ // }
328
+ //
329
+ // return sql;
330
+ // }
331
+ //
332
+ // if (value !== null) {
333
+ // if (prop?.customType) {
334
+ // value = prop.customType.convertToDatabaseValue(value, this.platform, { fromQuery: true, key, mode: 'query' });
335
+ // }
336
+ //
337
+ // params.push(value as Knex.Value);
338
+ // }
339
+ //
340
+ // return `${this.knex.ref(column)} ${replacement} ${value === null ? 'null' : '?'}`;
341
+ // }
342
+ //
343
+ // private wrapQueryGroup(parts: string[], operator = '$and') {
344
+ // if (parts.length === 1) {
345
+ // return parts[0];
346
+ // }
347
+ //
348
+ // return `(${parts.join(` ${GroupOperator[operator as keyof typeof GroupOperator]} `)})`;
349
+ // }
350
+ //
351
+ // mapJoinColumns(type: QueryType, join: JoinOptions): (string | Knex.Raw)[] {
352
+ // =======
260
353
  mapJoinColumns(type, join) {
261
354
  if (join.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(join.prop.kind)) {
262
355
  return join.prop.fieldNames.map((_fieldName, idx) => {
@@ -473,8 +566,8 @@ export class QueryBuilderHelper {
473
566
  parts.push(sql);
474
567
  params.push(...params2);
475
568
  }
476
- else if (op === '$in' && Array.isArray(value[op]) && value[op].length === 0) {
477
- parts.push(`1 = 0`);
569
+ else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
570
+ parts.push(`1 = ${op === '$in' ? 0 : 1}`);
478
571
  }
479
572
  else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
480
573
  const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
@@ -97,11 +97,14 @@ export class DatabaseTable {
97
97
  ignoreSchemaChanges: prop.ignoreSchemaChanges,
98
98
  };
99
99
  this.columns[field].unsigned ??= this.columns[field].autoincrement;
100
+ if (this.nativeEnums[type]) {
101
+ this.columns[field].enumItems ??= this.nativeEnums[type].items;
102
+ }
100
103
  const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
101
104
  this.columns[field].default = defaultValue;
102
105
  });
103
106
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
104
- const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
107
+ const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
105
108
  let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
106
109
  if (prop.referencedTableName.includes('.')) {
107
110
  schema = undefined;
@@ -699,16 +702,28 @@ export class DatabaseTable {
699
702
  }
700
703
  addIndex(meta, index, type) {
701
704
  const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
702
- const root = prop.replace(/\..+$/, '');
705
+ const parts = prop.split('.');
706
+ const root = parts[0];
703
707
  if (meta.properties[prop]) {
704
708
  if (meta.properties[prop].embeddedPath) {
705
709
  return [meta.properties[prop].embeddedPath.join('.')];
706
710
  }
707
711
  return meta.properties[prop].fieldNames;
708
712
  }
713
+ const rootProp = meta.properties[root];
714
+ // inline embedded property index, we need to find the field name of the child property
715
+ if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
716
+ const expand = (p, i) => {
717
+ if (parts.length === i) {
718
+ return p.fieldNames[0];
719
+ }
720
+ return expand(p.embeddedProps[parts[i]], i + 1);
721
+ };
722
+ return [expand(rootProp, 1)];
723
+ }
709
724
  // json index, we need to rename the column only
710
- if (meta.properties[root]) {
711
- return [prop.replace(root, meta.properties[root].fieldNames[0])];
725
+ if (rootProp) {
726
+ return [prop.replace(root, rootProp.fieldNames[0])];
712
727
  }
713
728
  /* v8 ignore next */
714
729
  return [prop];
@@ -119,9 +119,8 @@ export class SchemaHelper {
119
119
  }
120
120
  }
121
121
  Utils.removeDuplicates(changedNativeEnums).forEach(([enumName, itemsNew, itemsOld]) => {
122
- // postgres allows only adding new items, the values are case insensitive
123
- itemsOld = itemsOld.map(v => v.toLowerCase());
124
- const newItems = itemsNew.filter(val => !itemsOld.includes(val.toLowerCase()));
122
+ // postgres allows only adding new items
123
+ const newItems = itemsNew.filter(val => !itemsOld.includes(val));
125
124
  if (enumName.includes('.')) {
126
125
  const [enumSchemaName, rawEnumName] = enumName.split('.');
127
126
  ret.push(...newItems.map(val => this.getAlterNativeEnumSQL(rawEnumName, enumSchemaName, val, itemsNew, itemsOld)));
@@ -103,24 +103,14 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
103
103
  return super.clearDatabase(options);
104
104
  }
105
105
  await this.execute(this.helper.disableForeignKeysSQL());
106
- for (const meta of this.getOrderedMetadata(options?.schema).reverse()) {
106
+ const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
107
+ for (const meta of this.getOrderedMetadata(schema).reverse()) {
107
108
  await this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
108
- .withSchema(options?.schema)
109
- .truncate();
109
+ .withSchema(schema)
110
+ .truncate()
111
+ .execute();
110
112
  }
111
113
  await this.execute(this.helper.enableForeignKeysSQL());
112
- // const parts: string[] = [this.helper.disableForeignKeysSQL()];
113
- //
114
- // for (const meta of this.getOrderedMetadata(options?.schema).reverse()) {
115
- // const query = this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
116
- // .withSchema(options?.schema)
117
- // .truncate()
118
- // .getFormattedQuery();
119
- // parts.push(query);
120
- // }
121
- //
122
- // parts.push(this.helper.enableForeignKeysSQL());
123
- // await this.execute(parts.join(';\n'));
124
114
  this.clearIdentityMap();
125
115
  }
126
116
  async getDropSchemaSQL(options = {}) {
package/typings.d.ts CHANGED
@@ -163,12 +163,14 @@ export interface IQueryBuilder<T> {
163
163
  setFlag(flag: QueryFlag): this;
164
164
  unsetFlag(flag: QueryFlag): this;
165
165
  hasFlag(flag: QueryFlag): boolean;
166
+ scheduleFilterCheck(path: string): void;
166
167
  }
167
168
  export interface ICriteriaNodeProcessOptions {
168
169
  alias?: string;
169
170
  matchPopulateJoins?: boolean;
170
171
  ignoreBranching?: boolean;
171
172
  preferNoBranch?: boolean;
173
+ type?: 'orderBy';
172
174
  }
173
175
  export interface ICriteriaNode<T extends object> {
174
176
  readonly entityName: string;