@mikro-orm/sql 7.0.0-rc.0 → 7.0.0-rc.1

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.
@@ -19,6 +19,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
19
19
  private getTableProps;
20
20
  /** Creates a FormulaTable object for use in formula callbacks. */
21
21
  private createFormulaTable;
22
+ private validateSqlOptions;
22
23
  createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
23
24
  private createQueryBuilderFromOptions;
24
25
  find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: EntityName<T>, where: ObjectQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
@@ -30,6 +31,11 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
30
31
  protected streamFromVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T, any>): AsyncIterableIterator<EntityData<T>>;
31
32
  protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
32
33
  protected wrapVirtualExpressionInSubqueryStream<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>, type: QueryType.SELECT): AsyncIterableIterator<T>;
34
+ /**
35
+ * Virtual entities have no PKs, so to-many populate joins can't be deduplicated.
36
+ * Force balanced strategy to load to-many relations via separate queries.
37
+ */
38
+ private forceBalancedStrategy;
33
39
  mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
34
40
  /**
35
41
  * Maps aliased columns from TPT parent tables back to their original field names.
@@ -134,6 +140,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
134
140
  protected buildOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>): QueryOrderMap<T>[];
135
141
  protected buildPopulateOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, explicit: boolean, parentAlias?: string): QueryOrderMap<T>[];
136
142
  protected buildJoinedPropsOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options?: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>, parentPath?: string): QueryOrderMap<T>[];
143
+ private buildToManyOrderBy;
137
144
  protected normalizeFields<T extends object>(fields: InternalField<T>[], prefix?: string): string[];
138
145
  protected processField<T extends object>(meta: EntityMetadata<T>, prop: EntityProperty<T> | undefined, field: string, ret: InternalField<T>[]): void;
139
146
  protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T, any, any, any>, alias: string, options: Pick<FindOptions<T, any, any, any>, 'strategy' | 'fields' | 'exclude'>, schema?: string): InternalField<T>[];
@@ -32,6 +32,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
32
32
  const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
33
33
  return { alias, name: meta.tableName, schema: effectiveSchema, qualifiedName, toString: () => alias };
34
34
  }
35
+ validateSqlOptions(options) {
36
+ if (options.collation != null && typeof options.collation !== 'string') {
37
+ throw new Error('Collation option for SQL drivers must be a string (collation name). Use a CollationOptions object only with MongoDB.');
38
+ }
39
+ if (options.indexHint != null && typeof options.indexHint !== 'string') {
40
+ throw new Error('indexHint for SQL drivers must be a string (e.g. \'force index(my_index)\'). Use an object only with MongoDB.');
41
+ }
42
+ }
35
43
  createEntityManager(useContext) {
36
44
  const EntityManagerClass = this.config.get('entityManager', SqlEntityManager);
37
45
  return new EntityManagerClass(this.config, this, this.metadata, useContext);
@@ -49,6 +57,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
49
57
  if (Utils.isPrimaryKey(where, meta.compositePK)) {
50
58
  where = { [Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
51
59
  }
60
+ this.validateSqlOptions(options);
52
61
  const { first, last, before, after } = options;
53
62
  const isCursorPagination = [first, last, before, after].some(v => v != null);
54
63
  qb.__populateWhere = options._populateWhere;
@@ -59,6 +68,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
59
68
  .groupBy(options.groupBy)
60
69
  .having(options.having)
61
70
  .indexHint(options.indexHint)
71
+ .collation(options.collation)
62
72
  .comment(options.comments)
63
73
  .hintComment(options.hintComments);
64
74
  if (isCursorPagination) {
@@ -180,7 +190,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
180
190
  yield* res;
181
191
  }
182
192
  async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
183
- const qb = await this.createQueryBuilderFromOptions(meta, where, options);
193
+ const qb = await this.createQueryBuilderFromOptions(meta, where, this.forceBalancedStrategy(options));
184
194
  qb.setFlag(QueryFlag.DISABLE_PAGINATE);
185
195
  const isCursorPagination = [options.first, options.last, options.before, options.after].some(v => v != null);
186
196
  const native = qb.getNativeQuery(false);
@@ -199,7 +209,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
199
209
  return res.map(row => this.mapResult(row, meta));
200
210
  }
201
211
  async *wrapVirtualExpressionInSubqueryStream(meta, expression, where, options, type) {
202
- const qb = await this.createQueryBuilderFromOptions(meta, where, options);
212
+ const qb = await this.createQueryBuilderFromOptions(meta, where, this.forceBalancedStrategy(options));
203
213
  qb.unsetFlag(QueryFlag.DISABLE_PAGINATE);
204
214
  const native = qb.getNativeQuery(false);
205
215
  native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
@@ -210,6 +220,24 @@ export class AbstractSqlDriver extends DatabaseDriver {
210
220
  yield this.mapResult(row, meta);
211
221
  }
212
222
  }
223
+ /**
224
+ * Virtual entities have no PKs, so to-many populate joins can't be deduplicated.
225
+ * Force balanced strategy to load to-many relations via separate queries.
226
+ */
227
+ forceBalancedStrategy(options) {
228
+ const clearStrategy = (hints) => {
229
+ return hints.map(hint => ({
230
+ ...hint,
231
+ strategy: undefined,
232
+ children: hint.children ? clearStrategy(hint.children) : undefined,
233
+ }));
234
+ };
235
+ const opts = { ...options, strategy: 'balanced' };
236
+ if (Array.isArray(opts.populate)) {
237
+ opts.populate = clearStrategy(opts.populate);
238
+ }
239
+ return opts;
240
+ }
213
241
  mapResult(result, meta, populate = [], qb, map = {}) {
214
242
  // For TPT inheritance, map aliased parent table columns back to their field names
215
243
  if (qb && meta.inheritanceType === 'tpt' && meta.tptParent) {
@@ -474,8 +502,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
474
502
  if (meta && !Utils.isEmpty(populate)) {
475
503
  this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
476
504
  }
505
+ this.validateSqlOptions(options);
477
506
  qb.__populateWhere = options._populateWhere;
478
507
  qb.indexHint(options.indexHint)
508
+ .collation(options.collation)
479
509
  .comment(options.comments)
480
510
  .hintComment(options.hintComments)
481
511
  .groupBy(options.groupBy)
@@ -1486,7 +1516,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1486
1516
  return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
1487
1517
  }
1488
1518
  return prop.fieldNames.map(fieldName => {
1489
- return `${tableAlias}.${fieldName} as ${tableAlias}__${fieldName}`;
1519
+ return raw('?? as ??', [`${tableAlias}.${fieldName}`, `${tableAlias}__${fieldName}`]);
1490
1520
  });
1491
1521
  }
1492
1522
  /** @internal */
@@ -1639,38 +1669,41 @@ export class AbstractSqlDriver extends DatabaseDriver {
1639
1669
  for (const hint of joinedProps) {
1640
1670
  const [propName, ref] = hint.field.split(':', 2);
1641
1671
  const prop = meta.properties[propName];
1642
- const propOrderBy = prop.orderBy;
1643
1672
  let path = `${parentPath}.${propName}`;
1644
1673
  if (prop.kind === ReferenceKind.MANY_TO_MANY && ref) {
1645
1674
  path += '[pivot]';
1646
1675
  }
1647
- const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1648
- const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
1649
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
1650
- const alias = ref ? propAlias : join.ownerAlias;
1651
- orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: QueryOrder.ASC });
1652
- }
1653
- const effectiveOrderBy = QueryHelper.mergeOrderBy(propOrderBy, prop.targetMeta?.orderBy);
1654
- for (const item of effectiveOrderBy) {
1655
- for (const field of Utils.getObjectQueryKeys(item)) {
1656
- const order = item[field];
1657
- if (RawQueryFragment.isKnownFragmentSymbol(field)) {
1658
- const { sql, params } = RawQueryFragment.getKnownFragment(field);
1659
- const sql2 = propAlias ? sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), propAlias) : sql;
1660
- const key = raw(sql2, params);
1661
- orderBy.push({ [key]: order });
1662
- continue;
1663
- }
1664
- orderBy.push({ [`${propAlias}.${field}`]: order });
1665
- }
1676
+ if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
1677
+ this.buildToManyOrderBy(qb, prop, path, ref, orderBy);
1666
1678
  }
1667
1679
  if (hint.children) {
1668
- const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, prop.targetMeta, hint.children, options, path);
1669
- orderBy.push(...buildJoinedPropsOrderBy);
1680
+ orderBy.push(...this.buildJoinedPropsOrderBy(qb, prop.targetMeta, hint.children, options, path));
1670
1681
  }
1671
1682
  }
1672
1683
  return orderBy;
1673
1684
  }
1685
+ buildToManyOrderBy(qb, prop, path, ref, orderBy) {
1686
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1687
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
1688
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
1689
+ const alias = ref ? propAlias : join.ownerAlias;
1690
+ orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: QueryOrder.ASC });
1691
+ }
1692
+ const effectiveOrderBy = QueryHelper.mergeOrderBy(prop.orderBy, prop.targetMeta?.orderBy);
1693
+ for (const item of effectiveOrderBy) {
1694
+ for (const field of Utils.getObjectQueryKeys(item)) {
1695
+ const order = item[field];
1696
+ if (RawQueryFragment.isKnownFragmentSymbol(field)) {
1697
+ const { sql, params } = RawQueryFragment.getKnownFragment(field);
1698
+ const sql2 = propAlias ? sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), propAlias) : sql;
1699
+ const key = raw(sql2, params);
1700
+ orderBy.push({ [key]: order });
1701
+ continue;
1702
+ }
1703
+ orderBy.push({ [`${propAlias}.${field}`]: order });
1704
+ }
1705
+ }
1706
+ }
1674
1707
  normalizeFields(fields, prefix = '') {
1675
1708
  const ret = [];
1676
1709
  for (const field of fields) {
@@ -33,5 +33,12 @@ export declare abstract class AbstractSqlPlatform extends Platform {
33
33
  /**
34
34
  * @internal
35
35
  */
36
- getOrderByExpression(column: string, direction: string): string[];
36
+ getOrderByExpression(column: string, direction: string, collation?: string): string[];
37
+ /**
38
+ * Quotes a collation name for use in COLLATE clauses.
39
+ * @internal
40
+ */
41
+ quoteCollation(collation: string): string;
42
+ /** @internal */
43
+ protected validateCollationName(collation: string): void;
37
44
  }
@@ -94,7 +94,24 @@ export class AbstractSqlPlatform extends Platform {
94
94
  /**
95
95
  * @internal
96
96
  */
97
- getOrderByExpression(column, direction) {
97
+ getOrderByExpression(column, direction, collation) {
98
+ if (collation) {
99
+ return [`${column} collate ${this.quoteCollation(collation)} ${direction.toLowerCase()}`];
100
+ }
98
101
  return [`${column} ${direction.toLowerCase()}`];
99
102
  }
103
+ /**
104
+ * Quotes a collation name for use in COLLATE clauses.
105
+ * @internal
106
+ */
107
+ quoteCollation(collation) {
108
+ this.validateCollationName(collation);
109
+ return this.quoteIdentifier(collation);
110
+ }
111
+ /** @internal */
112
+ validateCollationName(collation) {
113
+ if (!/^[\w]+$/.test(collation)) {
114
+ throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
115
+ }
116
+ }
100
117
  }
@@ -41,6 +41,6 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
41
41
  supportsCreatingFullTextIndex(): boolean;
42
42
  getFullTextWhereClause(): string;
43
43
  getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
44
- getOrderByExpression(column: string, direction: string): string[];
44
+ getOrderByExpression(column: string, direction: string, collation?: string): string[];
45
45
  getDefaultClientUrl(): string;
46
46
  }
@@ -105,13 +105,14 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
105
105
  const quotedIndexName = this.quoteIdentifier(indexName);
106
106
  return `alter table ${quotedTableName} add fulltext index ${quotedIndexName}(${quotedColumnNames.join(',')})`;
107
107
  }
108
- getOrderByExpression(column, direction) {
108
+ getOrderByExpression(column, direction, collation) {
109
109
  const ret = [];
110
110
  const dir = direction.toLowerCase();
111
+ const col = collation ? `${column} collate ${this.quoteCollation(collation)}` : column;
111
112
  if (dir in this.ORDER_BY_NULLS_TRANSLATE) {
112
- ret.push(`${column} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`);
113
+ ret.push(`${col} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`);
113
114
  }
114
- ret.push(`${column} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`);
115
+ ret.push(`${col} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`);
115
116
  return ret;
116
117
  }
117
118
  getDefaultClientUrl() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.0.0-rc.0",
3
+ "version": "7.0.0-rc.1",
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": {
@@ -56,6 +56,6 @@
56
56
  "@mikro-orm/core": "^6.6.4"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-rc.0"
59
+ "@mikro-orm/core": "7.0.0-rc.1"
60
60
  }
61
61
  }
@@ -90,7 +90,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
90
90
  const operator = Utils.isOperator(field);
91
91
  const isRawField = RawQueryFragment.isKnownFragmentSymbol(field);
92
92
  // we need to keep the prefixing for formulas otherwise we would lose aliasing context when nesting inside group operators
93
- const virtual = childNode.prop?.persist === false && !childNode.prop?.formula;
93
+ const virtual = childNode.prop?.persist === false && !childNode.prop?.formula && !!options?.type;
94
94
  // if key is missing, we are inside group operator and we need to prefix with alias
95
95
  const primaryKey = this.key && this.metadata.find(this.entityName)?.primaryKeys.includes(field);
96
96
  const isToOne = childNode.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(childNode.prop.kind);
@@ -52,14 +52,16 @@ type AddToContext<Type extends object, Context, Field extends string, Alias exte
52
52
  [K in Alias]: [GetPath<Context, Field>, K, ExpandProperty<Type[GetPropName<Field> & keyof Type]>, Select];
53
53
  };
54
54
  type GetPath<Context, Field extends string> = GetAlias<Field> extends infer Alias ? IsNever<Alias> extends true ? GetPropName<Field> : Alias extends keyof Context ? Context[Alias] extends [infer Path, ...any[]] ? AppendToHint<Path & string, GetPropName<Field>> : GetPropName<Field> : GetPropName<Field> : GetPropName<Field>;
55
- type GetType<Type extends object, Context, Field extends string> = GetAlias<Field> extends infer Alias ? IsNever<Alias> extends true ? Type : Alias extends keyof Context ? Context[Alias] extends [string, string, infer PropType, any] ? PropType & object : Type : Type : Type;
55
+ type GetType<Type extends object, Context, Field extends string> = GetAlias<Field> extends infer Alias ? IsNever<Alias> extends true ? Type : [Context] extends [never] ? Type : Alias extends keyof Context ? Context[Alias] extends [string, string, infer PropType, any] ? PropType & object : Type : Type : Type;
56
56
  type AddToHint<RootAlias, Context, Field extends string, Select extends boolean = false> = Select extends true ? GetAlias<Field> extends infer Alias ? IsNever<Alias> extends true ? GetPropName<Field> : Alias extends RootAlias ? GetPropName<Field> : Alias extends keyof Context ? Context[Alias] extends [infer Path, ...any[]] ? AppendToHint<Path & string, GetPropName<Field>> : GetPropName<Field> : GetPropName<Field> : GetPropName<Field> : never;
57
57
  export type ModifyHint<RootAlias, Context, Hint extends string, Field extends string, Select extends boolean = false> = Hint | AddToHint<RootAlias, Context, Field, Select>;
58
58
  export type ModifyContext<Entity extends object, Context, Field extends string, Alias extends string, Select extends boolean = false> = IsNever<Context> extends true ? AddToContext<GetType<Entity, object, Field>, object, Field, Alias, Select> : Context & AddToContext<GetType<Entity, Context, Field>, Context, Field, Alias, Select>;
59
59
  type StripRootAlias<F extends string, RootAlias extends string, Context = never> = F extends `${RootAlias}.${infer Field}` ? Field : F extends `${infer Alias}.${string}` ? Alias extends AliasNames<Context> ? never : F : F;
60
- type ExtractRootFields<Fields, RootAlias extends string, Context = never> = [Fields] extends ['*'] ? '*' : Fields extends `${RootAlias}.*` ? '*' : Fields extends string ? StripRootAlias<Fields, RootAlias, Context> : never;
60
+ type StripFieldAlias<F extends string> = F extends `${infer Path} as ${string}` ? Path : F;
61
+ type ExtractRootFields<Fields, RootAlias extends string, Context = never> = [Fields] extends ['*'] ? '*' : Fields extends `${RootAlias}.*` ? '*' : Fields extends string ? StripRootAlias<StripFieldAlias<Fields>, RootAlias, Context> : never;
61
62
  type PrefixWithPath<Path extends string, Field extends string> = `${Path}.${Field}`;
62
63
  type StripJoinAlias<F extends string, Alias extends string> = F extends `${Alias}.${infer Field}` ? Field : F;
64
+ export type JoinSelectField<JoinedEntity, Alias extends string> = (keyof JoinedEntity & string) | `${Alias}.${keyof JoinedEntity & string}`;
63
65
  type AddJoinFields<RootAlias, Context, Field extends string, Alias extends string, JoinFields extends readonly string[]> = JoinFields extends readonly (infer F)[] ? F extends string ? PrefixWithPath<AddToHint<RootAlias, Context, Field, true> & string, StripJoinAlias<F, Alias>> : never : never;
64
66
  export type ModifyFields<CurrentFields extends string, RootAlias, Context, Field extends string, Alias extends string, JoinFields extends readonly string[] | undefined> = JoinFields extends readonly string[] ? CurrentFields | AddJoinFields<RootAlias, Context, Field, Alias, JoinFields> : CurrentFields;
65
67
  type EntityRelations<T> = EntityKey<T, true>;
@@ -68,25 +70,33 @@ type AliasNames<Context> = Context[keyof Context] extends infer Join ? Join exte
68
70
  type ContextRelationKeys<Context> = Context[keyof Context] extends infer Join ? Join extends any ? Join extends [string, infer Alias, infer Type, any] ? `${Alias & string}.${EntityRelations<Type & object>}` : never : never : never;
69
71
  export type QBField<Entity, RootAlias extends string, Context> = EntityRelations<Entity> | `${RootAlias}.${EntityRelations<Entity>}` | ([Context] extends [never] ? never : ContextRelationKeys<Context>);
70
72
  type ContextFieldKeys<Context> = Context[keyof Context] extends infer Join ? Join extends any ? Join extends [string, infer Alias, infer Type, any] ? `${Alias & string}.${keyof Type & string}` : never : never : never;
71
- export type Field<Entity, RootAlias extends string = never, Context = never> = EntityKey<Entity> | (IsNever<RootAlias> extends true ? never : `${RootAlias}.${EntityKey<Entity>}` | `${RootAlias}.*`) | ([Context] extends [never] ? never : ContextFieldKeys<Context> | `${AliasNames<Context>}.*`) | '*' | QueryBuilder<any> | NativeQueryBuilder | RawQueryFragment<any> | (RawQueryFragment & symbol);
73
+ type WithAlias<T extends string> = T | `${T} as ${string}`;
74
+ export type Field<Entity, RootAlias extends string = never, Context = never> = WithAlias<EntityKey<Entity>> | (IsNever<RootAlias> extends true ? never : WithAlias<`${RootAlias}.${EntityKey<Entity>}`> | `${RootAlias}.*`) | ([Context] extends [never] ? never : WithAlias<ContextFieldKeys<Context>> | `${AliasNames<Context>}.*`) | '*' | QueryBuilder<any> | NativeQueryBuilder | RawQueryFragment<any> | (RawQueryFragment & symbol);
72
75
  type RootAliasOrderKeys<RootAlias extends string, Entity> = {
73
76
  [K in `${RootAlias}.${EntityKey<Entity>}`]?: QueryOrderKeysFlat;
74
77
  };
75
78
  type ContextOrderKeys<Context> = {
76
79
  [K in ContextFieldKeys<Context>]?: QueryOrderKeysFlat;
77
80
  };
78
- export type ContextOrderByMap<Entity, RootAlias extends string = never, Context = never> = QueryOrderMap<Entity> | ((IsNever<RootAlias> extends true ? {} : RootAliasOrderKeys<RootAlias, Entity>) & ([Context] extends [never] ? {} : ContextOrderKeys<Context>));
81
+ type RawOrderKeys<RawAliases extends string> = {
82
+ [K in RawAliases]?: QueryOrderKeysFlat;
83
+ };
84
+ export type ContextOrderByMap<Entity, RootAlias extends string = never, Context = never, RawAliases extends string = never> = QueryOrderMap<Entity> | ((IsNever<RootAlias> extends true ? {} : RootAliasOrderKeys<RootAlias, Entity>) & ([Context] extends [never] ? {} : ContextOrderKeys<Context>) & (IsNever<RawAliases> extends true ? {} : string extends RawAliases ? {} : RawOrderKeys<RawAliases>));
79
85
  type AliasedPath<Alias extends string, Type, P extends string> = P extends `${Alias}.*` ? P : P extends `${Alias}.${infer Rest}` ? `${Alias}.${AutoPath<Type & object, Rest, `${PopulatePath.ALL}`>}` : never;
80
86
  type ContextAliasedPath<Context, P extends string> = Context[keyof Context] extends infer Join ? Join extends any ? Join extends [string, infer Alias, infer Type, any] ? AliasedPath<Alias & string, Type, P> : never : never : never;
81
- type NestedAutoPath<Entity, RootAlias extends string, Context, P extends string> = P extends `${string}:ref` ? never : AliasedPath<RootAlias, Entity, P> | ContextAliasedPath<Context, P> | AutoPath<Entity, P, `${PopulatePath.ALL}`>;
87
+ type NestedAutoPath<Entity, RootAlias extends string, Context, P extends string> = P extends `${string}:ref` ? never : P extends `${infer Path} as ${string}` ? (AliasedPath<RootAlias, Entity, Path> | ContextAliasedPath<Context, Path> | AutoPath<Entity, Path, `${PopulatePath.ALL}`>) extends never ? never : P : AliasedPath<RootAlias, Entity, P> | ContextAliasedPath<Context, P> | AutoPath<Entity, P, `${PopulatePath.ALL}`>;
82
88
  type AliasedObjectQuery<Entity extends object, Alias extends string> = {
83
89
  [K in EntityKey<Entity> as `${Alias}.${K}`]?: ObjectQuery<Entity>[K];
84
90
  };
85
- type JoinCondition<JoinedEntity extends object, Alias extends string> = ObjectQuery<JoinedEntity> | AliasedObjectQuery<JoinedEntity, Alias>;
91
+ type JoinCondition<JoinedEntity extends object, Alias extends string> = (ObjectQuery<JoinedEntity> | AliasedObjectQuery<JoinedEntity, Alias>) & {
92
+ $not?: JoinCondition<JoinedEntity, Alias>;
93
+ $or?: JoinCondition<JoinedEntity, Alias>[];
94
+ $and?: JoinCondition<JoinedEntity, Alias>[];
95
+ };
86
96
  type RawJoinCondition = {
87
97
  [key: string]: FilterValue<Scalar> | RawQueryFragment;
88
98
  };
89
- type ExtractRawAliasFromField<F> = F extends RawQueryFragment<infer A> ? (A extends string ? A : never) : never;
99
+ type ExtractRawAliasFromField<F> = F extends RawQueryFragment<infer A> ? (A extends string ? A : never) : F extends `${string} as ${infer A}` ? A : never;
90
100
  type ExtractRawAliasesFromTuple<T extends readonly unknown[]> = T extends readonly [infer Head, ...infer Tail] ? ExtractRawAliasFromField<Head> | ExtractRawAliasesFromTuple<Tail> : never;
91
101
  type ExtractRawAliases<Fields> = Fields extends readonly unknown[] ? ExtractRawAliasesFromTuple<Fields> : ExtractRawAliasFromField<Fields>;
92
102
  type FlatOperatorMap = {
@@ -194,6 +204,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
194
204
  protected _joinedProps: Map<string, PopulateOptions<any>>;
195
205
  protected _cache?: boolean | number | [string, number];
196
206
  protected _indexHint?: string;
207
+ protected _collation?: string;
197
208
  protected _comments: string[];
198
209
  protected _hintComments: string[];
199
210
  protected flushMode?: FlushMode;
@@ -228,6 +239,10 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
228
239
  * // Select with raw expressions
229
240
  * qb.select([raw('count(*) as total')]);
230
241
  *
242
+ * // Select with aliases (works for regular and formula properties)
243
+ * qb.select(['id', 'fullName as displayName']);
244
+ * qb.select(['id', sql.ref('fullName').as('displayName')]);
245
+ *
231
246
  * // Select with distinct
232
247
  * qb.select('*', true);
233
248
  * ```
@@ -396,11 +411,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
396
411
  * .where({ 'a.name': 'John' });
397
412
  * ```
398
413
  */
399
- joinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly string[] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, type?: JoinType, path?: string, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
400
- leftJoinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly string[] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
401
- leftJoinLateralAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly string[] | undefined = undefined>(field: [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
402
- innerJoinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly string[] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
403
- innerJoinLateralAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly string[] | undefined = undefined>(field: [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
414
+ joinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly [JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>, ...JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>[]] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, type?: JoinType, path?: string, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
415
+ leftJoinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly [JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>, ...JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>[]] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
416
+ leftJoinLateralAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly [JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>, ...JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>[]] | undefined = undefined>(field: [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
417
+ innerJoinAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly [JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>, ...JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>[]] | undefined = undefined>(field: Field | [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
418
+ innerJoinLateralAndSelect<Field extends QBField<Entity, RootAlias, Context>, Alias extends string, const JoinFields extends readonly [JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>, ...JoinSelectField<JoinedEntityType<Entity, Context, Field & string>, Alias>[]] | undefined = undefined>(field: [Field, RawQueryFragment | QueryBuilder<any>], alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, fields?: JoinFields, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field, true> & {}, ModifyContext<Entity, Context, Field, Alias, true>, RawAliases, ModifyFields<Fields, RootAlias, Context, Field, Alias, JoinFields>>;
404
419
  protected getFieldsForJoinedLoad(prop: EntityProperty<Entity>, alias: string, explicitFields?: readonly string[]): InternalField<Entity>[];
405
420
  /**
406
421
  * Apply filters to the QB where condition.
@@ -503,7 +518,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
503
518
  * qb.orderBy({ 'profile.bio': 'asc' }); // nested via dot notation
504
519
  * ```
505
520
  */
506
- orderBy(orderBy: ContextOrderByMap<Entity, RootAlias, Context> | ContextOrderByMap<Entity, RootAlias, Context>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
521
+ orderBy(orderBy: ContextOrderByMap<Entity, RootAlias, Context, RawAliases> | ContextOrderByMap<Entity, RootAlias, Context, RawAliases>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
507
522
  /**
508
523
  * Adds an ORDER BY clause to the query, replacing any existing order.
509
524
  *
@@ -516,19 +531,21 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
516
531
  * ```
517
532
  */
518
533
  orderBy<const T extends Record<string, QueryOrderKeysFlat>>(orderBy: T & {
519
- [K in keyof T]: K extends NestedAutoPath<Entity, RootAlias, Context, K & string> ? T[K] : never;
534
+ [K in keyof T]: K extends NestedAutoPath<Entity, RootAlias, Context, K & string> ? T[K] : (K extends RawAliases ? T[K] : never);
520
535
  }): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
521
536
  /**
522
537
  * Adds additional ORDER BY clause without replacing existing order.
523
538
  */
524
- andOrderBy(orderBy: ContextOrderByMap<Entity, RootAlias, Context> | ContextOrderByMap<Entity, RootAlias, Context>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
539
+ andOrderBy(orderBy: ContextOrderByMap<Entity, RootAlias, Context, RawAliases> | ContextOrderByMap<Entity, RootAlias, Context, RawAliases>[]): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
525
540
  /**
526
541
  * Adds additional ORDER BY clause without replacing existing order.
527
542
  */
528
543
  andOrderBy<const T extends Record<string, QueryOrderKeysFlat>>(orderBy: T & {
529
- [K in keyof T]: K extends NestedAutoPath<Entity, RootAlias, Context, K & string> ? T[K] : never;
544
+ [K in keyof T]: K extends NestedAutoPath<Entity, RootAlias, Context, K & string> ? T[K] : (K extends RawAliases ? T[K] : never);
530
545
  }): SelectQueryBuilder<Entity, RootAlias, Hint, Context, RawAliases, Fields>;
531
546
  private processOrderBy;
547
+ /** Collect custom aliases from select fields (stored as 'resolved as alias' strings by select()). */
548
+ private getSelectAliases;
532
549
  /**
533
550
  * Adds a GROUP BY clause to the query.
534
551
  *
@@ -611,6 +628,10 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
611
628
  * Adds index hint to the FROM clause.
612
629
  */
613
630
  indexHint(sql: string | undefined): this;
631
+ /**
632
+ * Adds COLLATE clause to ORDER BY expressions.
633
+ */
634
+ collation(collation: string | undefined): this;
614
635
  /**
615
636
  * Prepend comment to the sql query using the syntax `/* ... *&#8205;/`. Some characters are forbidden such as `/*, *&#8205;/` and `?`.
616
637
  */