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

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
  }
@@ -4,7 +4,7 @@ import type { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
4
4
  import type { QueryBuilder } from './query/QueryBuilder.js';
5
5
  import type { SqlEntityRepository } from './SqlEntityRepository.js';
6
6
  import type { Kysely } from 'kysely';
7
- import type { InferKyselyDB } from './typings.js';
7
+ import type { InferClassEntityDB, InferKyselyDB } from './typings.js';
8
8
  import { type MikroKyselyPluginOptions } from './plugin/index.js';
9
9
  export interface GetKyselyOptions extends MikroKyselyPluginOptions {
10
10
  type?: ConnectionType;
@@ -24,10 +24,11 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
24
24
  /**
25
25
  * Returns configured Kysely instance.
26
26
  */
27
- getKysely<TDB = undefined, TOptions extends GetKyselyOptions = GetKyselyOptions>(options?: TOptions): Kysely<TDB extends undefined ? InferKyselyDB<EntitiesFromManager<this>, TOptions> : TDB>;
27
+ getKysely<TDB = undefined, TOptions extends GetKyselyOptions = GetKyselyOptions>(options?: TOptions): Kysely<TDB extends undefined ? InferKyselyDB<EntitiesFromManager<this>, TOptions> & InferClassEntityDB<AllEntitiesFromManager<this>, TOptions> : TDB>;
28
28
  execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
29
29
  getRepository<T extends object, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U>;
30
30
  protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
31
31
  }
32
32
  type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? (Extract<NonNullable<TEntityManager['~entities']>[number], EntitySchemaWithMeta>) : never;
33
+ type AllEntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? NonNullable<TEntityManager['~entities']>[number] : never;
33
34
  export {};
@@ -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() {
@@ -1,5 +1,8 @@
1
+ import { type Dialect } from 'kysely';
2
+ import type { Dictionary } from '@mikro-orm/core';
1
3
  import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
2
- export declare abstract class BaseSqliteConnection extends AbstractSqlConnection {
4
+ export declare class BaseSqliteConnection extends AbstractSqlConnection {
5
+ createKyselyDialect(options: Dictionary): Dialect;
3
6
  connect(options?: {
4
7
  skipOnConnect?: boolean;
5
8
  }): Promise<void>;
@@ -1,6 +1,10 @@
1
1
  import { CompiledQuery } from 'kysely';
2
2
  import { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
3
3
  export class BaseSqliteConnection extends AbstractSqlConnection {
4
+ createKyselyDialect(options) {
5
+ throw new Error('No SQLite dialect configured. Pass a Kysely dialect via the `driverOptions` config option, '
6
+ + 'e.g. `new NodeSqliteDialect(...)` for node:sqlite or a custom dialect for other libraries.');
7
+ }
4
8
  async connect(options) {
5
9
  await super.connect(options);
6
10
  await this.getClient().executeQuery(CompiledQuery.raw('pragma foreign_keys = on'));
@@ -0,0 +1,21 @@
1
+ import { SqliteDialect } from 'kysely';
2
+ /**
3
+ * Kysely dialect for `node:sqlite` (Node.js 22.5+, Deno 2.2+).
4
+ *
5
+ * Bridges `node:sqlite`'s `DatabaseSync` to the `better-sqlite3` interface
6
+ * that Kysely's `SqliteDialect` expects.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { SqliteDriver, NodeSqliteDialect } from '@mikro-orm/sql';
11
+ *
12
+ * const orm = await MikroORM.init({
13
+ * driver: SqliteDriver,
14
+ * dbName: ':memory:',
15
+ * driverOptions: new NodeSqliteDialect(':memory:'),
16
+ * });
17
+ * ```
18
+ */
19
+ export declare class NodeSqliteDialect extends SqliteDialect {
20
+ constructor(dbName: string);
21
+ }
@@ -0,0 +1,41 @@
1
+ import { SqliteDialect } from 'kysely';
2
+ /**
3
+ * Kysely dialect for `node:sqlite` (Node.js 22.5+, Deno 2.2+).
4
+ *
5
+ * Bridges `node:sqlite`'s `DatabaseSync` to the `better-sqlite3` interface
6
+ * that Kysely's `SqliteDialect` expects.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { SqliteDriver, NodeSqliteDialect } from '@mikro-orm/sql';
11
+ *
12
+ * const orm = await MikroORM.init({
13
+ * driver: SqliteDriver,
14
+ * dbName: ':memory:',
15
+ * driverOptions: new NodeSqliteDialect(':memory:'),
16
+ * });
17
+ * ```
18
+ */
19
+ export class NodeSqliteDialect extends SqliteDialect {
20
+ constructor(dbName) {
21
+ const { DatabaseSync } = globalThis.process.getBuiltinModule('node:sqlite');
22
+ super({
23
+ database: () => {
24
+ const db = new DatabaseSync(dbName);
25
+ return {
26
+ prepare(sql) {
27
+ const stmt = db.prepare(sql);
28
+ return {
29
+ reader: /^\s*(select|pragma|explain|with)/i.test(sql) || /\breturning\b/i.test(sql),
30
+ all: (params) => stmt.all(...params),
31
+ run: (params) => stmt.run(...params),
32
+ /* v8 ignore next */
33
+ get: (params) => stmt.get(...params),
34
+ };
35
+ },
36
+ close() { db.close(); },
37
+ };
38
+ },
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,12 @@
1
+ import type { Configuration } from '@mikro-orm/core';
2
+ import { AbstractSqlDriver } from '../../AbstractSqlDriver.js';
3
+ import { BaseSqliteConnection } from './BaseSqliteConnection.js';
4
+ /**
5
+ * Generic SQLite driver that uses `driverOptions` for the Kysely dialect.
6
+ * Use this with any SQLite library by passing a Kysely dialect via `driverOptions`.
7
+ *
8
+ * For the default better-sqlite3 experience, use `@mikro-orm/sqlite` instead.
9
+ */
10
+ export declare class SqliteDriver extends AbstractSqlDriver<BaseSqliteConnection> {
11
+ constructor(config: Configuration);
12
+ }
@@ -0,0 +1,14 @@
1
+ import { AbstractSqlDriver } from '../../AbstractSqlDriver.js';
2
+ import { BaseSqliteConnection } from './BaseSqliteConnection.js';
3
+ import { SqlitePlatform } from './SqlitePlatform.js';
4
+ /**
5
+ * Generic SQLite driver that uses `driverOptions` for the Kysely dialect.
6
+ * Use this with any SQLite library by passing a Kysely dialect via `driverOptions`.
7
+ *
8
+ * For the default better-sqlite3 experience, use `@mikro-orm/sqlite` instead.
9
+ */
10
+ export class SqliteDriver extends AbstractSqlDriver {
11
+ constructor(config) {
12
+ super(config, new SqlitePlatform(), BaseSqliteConnection, ['kysely']);
13
+ }
14
+ }
@@ -3,7 +3,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
3
3
  import { SqliteNativeQueryBuilder } from './SqliteNativeQueryBuilder.js';
4
4
  import { SqliteSchemaHelper } from './SqliteSchemaHelper.js';
5
5
  import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
6
- export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
6
+ export declare class SqlitePlatform extends AbstractSqlPlatform {
7
7
  protected readonly schemaHelper: SqliteSchemaHelper;
8
8
  protected readonly exceptionConverter: SqliteExceptionConverter;
9
9
  /** @internal */
@@ -71,6 +71,9 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
71
71
  supportsSchemas(): boolean;
72
72
  getDefaultSchemaName(): string | undefined;
73
73
  getFullTextWhereClause(): string;
74
- quoteVersionValue(value: Date | number, prop: EntityProperty): Date | string | number;
74
+ escape(value: any): string;
75
+ convertVersionValue(value: Date | number, prop: EntityProperty): number | {
76
+ $in: (string | number)[];
77
+ };
75
78
  quoteValue(value: any): string;
76
79
  }
@@ -2,7 +2,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
2
2
  import { SqliteNativeQueryBuilder } from './SqliteNativeQueryBuilder.js';
3
3
  import { SqliteSchemaHelper } from './SqliteSchemaHelper.js';
4
4
  import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
5
- export class BaseSqlitePlatform extends AbstractSqlPlatform {
5
+ export class SqlitePlatform extends AbstractSqlPlatform {
6
6
  schemaHelper = new SqliteSchemaHelper(this);
7
7
  exceptionConverter = new SqliteExceptionConverter();
8
8
  /** @internal */
@@ -19,7 +19,7 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
19
19
  return true;
20
20
  }
21
21
  getCurrentTimestampSQL(length) {
22
- return super.getCurrentTimestampSQL(0);
22
+ return `(strftime('%s', 'now') * 1000)`;
23
23
  }
24
24
  getDateTimeTypeDeclarationSQL(column) {
25
25
  return 'datetime';
@@ -101,9 +101,32 @@ export class BaseSqlitePlatform extends AbstractSqlPlatform {
101
101
  getFullTextWhereClause() {
102
102
  return `:column: match :query`;
103
103
  }
104
- quoteVersionValue(value, prop) {
104
+ escape(value) {
105
+ if (value == null) {
106
+ return 'null';
107
+ }
108
+ if (typeof value === 'boolean') {
109
+ return value ? 'true' : 'false';
110
+ }
111
+ if (typeof value === 'number' || typeof value === 'bigint') {
112
+ return '' + value;
113
+ }
114
+ if (value instanceof Date) {
115
+ return '' + +value;
116
+ }
117
+ if (Array.isArray(value)) {
118
+ return value.map(v => this.escape(v)).join(', ');
119
+ }
120
+ if (Buffer.isBuffer(value)) {
121
+ return `X'${value.toString('hex')}'`;
122
+ }
123
+ return `'${String(value).replace(/'/g, "''")}'`;
124
+ }
125
+ convertVersionValue(value, prop) {
105
126
  if (prop.runtimeType === 'Date') {
106
- return this.escape(value).replace(/^'|\.\d{3}'$/g, '');
127
+ const ts = +value;
128
+ const str = new Date(ts).toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
129
+ return { $in: [ts, str] };
107
130
  }
108
131
  return value;
109
132
  }
@@ -39,6 +39,11 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
39
39
  */
40
40
  private extractViewDefinition;
41
41
  private getColumns;
42
+ /**
43
+ * SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
44
+ * We need to add them back so they match what we generate in DDL.
45
+ */
46
+ private wrapExpressionDefault;
42
47
  private getEnumDefinitions;
43
48
  getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string): Promise<string[]>;
44
49
  private getIndexes;
@@ -286,7 +286,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
286
286
  return {
287
287
  name: col.name,
288
288
  type: col.type,
289
- default: col.dflt_value,
289
+ default: this.wrapExpressionDefault(col.dflt_value),
290
290
  nullable: !col.notnull,
291
291
  primary: !!col.pk,
292
292
  mappedType,
@@ -296,6 +296,25 @@ export class SqliteSchemaHelper extends SchemaHelper {
296
296
  };
297
297
  });
298
298
  }
299
+ /**
300
+ * SQLite strips outer parentheses from expression defaults (`DEFAULT (expr)` → `expr` in pragma).
301
+ * We need to add them back so they match what we generate in DDL.
302
+ */
303
+ wrapExpressionDefault(value) {
304
+ if (value == null) {
305
+ return null;
306
+ }
307
+ // simple values that are returned as-is from pragma (no wrapping needed)
308
+ if (/^-?\d/.test(value) || /^[xX]'/.test(value) || value[0] === "'" || value[0] === '"' || value[0] === '(') {
309
+ return value;
310
+ }
311
+ const lower = value.toLowerCase();
312
+ if (['null', 'true', 'false', 'current_timestamp', 'current_date', 'current_time'].includes(lower)) {
313
+ return value;
314
+ }
315
+ // everything else is an expression that had its outer parens stripped
316
+ return `(${value})`;
317
+ }
299
318
  async getEnumDefinitions(connection, tableName, schemaName) {
300
319
  const prefix = this.getSchemaPrefix(schemaName);
301
320
  const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
@@ -1,4 +1,6 @@
1
1
  export * from './BaseSqliteConnection.js';
2
- export * from './BaseSqlitePlatform.js';
2
+ export * from './NodeSqliteDialect.js';
3
+ export * from './SqliteDriver.js';
4
+ export * from './SqlitePlatform.js';
3
5
  export * from './SqliteSchemaHelper.js';
4
6
  export * from './SqliteNativeQueryBuilder.js';
@@ -1,4 +1,6 @@
1
1
  export * from './BaseSqliteConnection.js';
2
- export * from './BaseSqlitePlatform.js';
2
+ export * from './NodeSqliteDialect.js';
3
+ export * from './SqliteDriver.js';
4
+ export * from './SqlitePlatform.js';
3
5
  export * from './SqliteSchemaHelper.js';
4
6
  export * from './SqliteNativeQueryBuilder.js';
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.2",
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.2"
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);