@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.
- package/AbstractSqlDriver.d.ts +7 -0
- package/AbstractSqlDriver.js +58 -25
- package/AbstractSqlPlatform.d.ts +8 -1
- package/AbstractSqlPlatform.js +18 -1
- package/SqlEntityManager.d.ts +3 -2
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -1
- package/dialects/mysql/BaseMySqlPlatform.js +4 -3
- package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -1
- package/dialects/sqlite/BaseSqliteConnection.js +4 -0
- package/dialects/sqlite/NodeSqliteDialect.d.ts +21 -0
- package/dialects/sqlite/NodeSqliteDialect.js +41 -0
- package/dialects/sqlite/SqliteDriver.d.ts +12 -0
- package/dialects/sqlite/SqliteDriver.js +14 -0
- package/dialects/sqlite/{BaseSqlitePlatform.d.ts → SqlitePlatform.d.ts} +5 -2
- package/dialects/sqlite/{BaseSqlitePlatform.js → SqlitePlatform.js} +27 -4
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +5 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +20 -1
- package/dialects/sqlite/index.d.ts +3 -1
- package/dialects/sqlite/index.js +3 -1
- package/package.json +2 -2
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +67 -20
- package/query/QueryBuilder.js +80 -15
- package/query/QueryBuilderHelper.d.ts +2 -2
- package/query/QueryBuilderHelper.js +9 -9
- package/schema/SchemaComparator.js +1 -0
- package/schema/SqlSchemaGenerator.d.ts +2 -1
- package/schema/SqlSchemaGenerator.js +2 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +14 -3
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -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>[];
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -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}
|
|
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
|
-
|
|
1648
|
-
|
|
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
|
-
|
|
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) {
|
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -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
|
}
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -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
|
}
|
package/SqlEntityManager.d.ts
CHANGED
|
@@ -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(`${
|
|
113
|
+
ret.push(`${col} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`);
|
|
113
114
|
}
|
|
114
|
-
ret.push(`${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 './
|
|
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/dialects/sqlite/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './BaseSqliteConnection.js';
|
|
2
|
-
export * from './
|
|
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.
|
|
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.
|
|
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);
|