@mikro-orm/sql 7.0.0-rc.3 → 7.0.0
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/AbstractSqlConnection.d.ts +5 -4
- package/AbstractSqlConnection.js +18 -5
- package/AbstractSqlDriver.d.ts +1 -1
- package/AbstractSqlDriver.js +39 -10
- package/AbstractSqlPlatform.d.ts +34 -0
- package/AbstractSqlPlatform.js +47 -3
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +1 -1
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +17 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +6 -6
- package/dialects/oracledb/OracleDialect.d.ts +78 -0
- package/dialects/oracledb/OracleDialect.js +166 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
- package/dialects/oracledb/index.d.ts +2 -0
- package/dialects/oracledb/index.js +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +12 -8
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +13 -13
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +3 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +12 -8
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +3 -3
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +81 -73
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNodeFactory.js +15 -3
- package/query/NativeQueryBuilder.d.ts +3 -3
- package/query/NativeQueryBuilder.js +4 -2
- package/query/ObjectCriteriaNode.js +4 -4
- package/query/QueryBuilder.d.ts +58 -62
- package/query/QueryBuilder.js +377 -370
- package/query/QueryBuilderHelper.d.ts +14 -11
- package/query/QueryBuilderHelper.js +324 -137
- package/query/ScalarCriteriaNode.js +3 -1
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +50 -33
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +84 -60
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +22 -20
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +2 -1
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +15 -7
- package/typings.d.ts +4 -1
- package/tsconfig.build.tsbuildinfo +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type ControlledTransaction, type Dialect, Kysely } from 'kysely';
|
|
2
|
-
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LogContext, type LoggingOptions, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LogContext, type LoggingOptions, type MaybePromise, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export declare abstract class AbstractSqlConnection extends Connection {
|
|
6
6
|
#private;
|
|
7
7
|
protected platform: AbstractSqlPlatform;
|
|
8
|
-
abstract createKyselyDialect(overrides: Dictionary): Dialect
|
|
8
|
+
abstract createKyselyDialect(overrides: Dictionary): MaybePromise<Dialect>;
|
|
9
9
|
connect(options?: {
|
|
10
10
|
skipOnConnect?: boolean;
|
|
11
11
|
}): Promise<void>;
|
|
12
|
-
createKysely(): void
|
|
12
|
+
createKysely(): MaybePromise<void>;
|
|
13
13
|
/**
|
|
14
14
|
* @inheritDoc
|
|
15
15
|
*/
|
|
@@ -29,6 +29,7 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
29
29
|
error?: Error;
|
|
30
30
|
}>;
|
|
31
31
|
getClient<T = any>(): Kysely<T>;
|
|
32
|
+
initClient(): Promise<void>;
|
|
32
33
|
transactional<T>(cb: (trx: Transaction<ControlledTransaction<any, any>>) => Promise<T>, options?: {
|
|
33
34
|
isolationLevel?: IsolationLevel;
|
|
34
35
|
readOnly?: boolean;
|
|
@@ -50,6 +51,6 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
50
51
|
stream<T extends EntityData<AnyEntity>>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], ctx?: Transaction<Kysely<any>>, loggerContext?: LoggingOptions): AsyncIterableIterator<T>;
|
|
51
52
|
/** @inheritDoc */
|
|
52
53
|
executeDump(dump: string): Promise<void>;
|
|
53
|
-
|
|
54
|
+
protected getSql(query: string, formatted: string, context?: LogContext): string;
|
|
54
55
|
protected transformRawResult<T>(res: any, method?: 'all' | 'get' | 'run'): T;
|
|
55
56
|
}
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -4,7 +4,7 @@ import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
|
4
4
|
export class AbstractSqlConnection extends Connection {
|
|
5
5
|
#client;
|
|
6
6
|
async connect(options) {
|
|
7
|
-
this.
|
|
7
|
+
await this.initClient();
|
|
8
8
|
this.connected = true;
|
|
9
9
|
if (options?.skipOnConnect !== true) {
|
|
10
10
|
await this.onConnect();
|
|
@@ -24,9 +24,13 @@ export class AbstractSqlConnection extends Connection {
|
|
|
24
24
|
this.#client = new Kysely({ dialect: driverOptions });
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const dialect = this.createKyselyDialect(driverOptions);
|
|
28
|
+
if (dialect instanceof Promise) {
|
|
29
|
+
return dialect.then(d => {
|
|
30
|
+
this.#client = new Kysely({ dialect: d });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
this.#client = new Kysely({ dialect });
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
/**
|
|
@@ -62,10 +66,19 @@ export class AbstractSqlConnection extends Connection {
|
|
|
62
66
|
}
|
|
63
67
|
getClient() {
|
|
64
68
|
if (!this.#client) {
|
|
65
|
-
this.createKysely();
|
|
69
|
+
const maybePromise = this.createKysely();
|
|
70
|
+
/* v8 ignore next */
|
|
71
|
+
if (maybePromise instanceof Promise) {
|
|
72
|
+
throw new Error('Current driver requires async initialization, use `MikroORM.init()` instead of the constructor');
|
|
73
|
+
}
|
|
66
74
|
}
|
|
67
75
|
return this.#client;
|
|
68
76
|
}
|
|
77
|
+
async initClient() {
|
|
78
|
+
if (!this.#client) {
|
|
79
|
+
await this.createKysely();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
69
82
|
async transactional(cb, options = {}) {
|
|
70
83
|
const trx = await this.begin(options);
|
|
71
84
|
try {
|
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
|
|
|
53
53
|
nativeInsert<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
54
54
|
nativeInsertMany<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>, transform?: (sql: string) => string): Promise<QueryResult<T>>;
|
|
55
55
|
nativeUpdate<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T> & UpsertOptions<T>): Promise<QueryResult<T>>;
|
|
56
|
-
nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T> & UpsertManyOptions<T
|
|
56
|
+
nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T> & UpsertManyOptions<T>, transform?: (sql: string, params: any[]) => string): Promise<QueryResult<T>>;
|
|
57
57
|
nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T> | string | any, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
|
|
58
58
|
/**
|
|
59
59
|
* Fast comparison for collection snapshots that are represented by PK arrays.
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -60,7 +60,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
60
60
|
this.validateSqlOptions(options);
|
|
61
61
|
const { first, last, before, after } = options;
|
|
62
62
|
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
63
|
-
qb.
|
|
63
|
+
qb.state.resolvedPopulateWhere = options._populateWhere;
|
|
64
64
|
qb.select(fields)
|
|
65
65
|
// only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
|
|
66
66
|
.populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
|
|
@@ -200,7 +200,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
200
200
|
if (type === QueryType.COUNT) {
|
|
201
201
|
native.clear('select').clear('limit').clear('offset').count();
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
204
|
+
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
204
205
|
const query = native.compile();
|
|
205
206
|
const res = await this.execute(query.sql, query.params, 'all', options.ctx);
|
|
206
207
|
if (type === QueryType.COUNT) {
|
|
@@ -215,7 +216,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
215
216
|
const qb = await this.createQueryBuilderFromOptions(meta, where, this.forceBalancedStrategy(options));
|
|
216
217
|
qb.unsetFlag(QueryFlag.DISABLE_PAGINATE);
|
|
217
218
|
const native = qb.getNativeQuery(false);
|
|
218
|
-
|
|
219
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
220
|
+
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
219
221
|
const query = native.compile();
|
|
220
222
|
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
221
223
|
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, options.loggerContext);
|
|
@@ -268,7 +270,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
268
270
|
* and need to be renamed back to `column_name` for the result mapper to work.
|
|
269
271
|
*/
|
|
270
272
|
mapTPTColumns(result, meta, qb) {
|
|
271
|
-
const tptAliases = qb.
|
|
273
|
+
const tptAliases = qb.state.tptAlias;
|
|
272
274
|
// Walk up the TPT hierarchy
|
|
273
275
|
let parentMeta = meta.tptParent;
|
|
274
276
|
while (parentMeta) {
|
|
@@ -526,7 +528,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
526
528
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
527
529
|
}
|
|
528
530
|
this.validateSqlOptions(options);
|
|
529
|
-
qb.
|
|
531
|
+
qb.state.resolvedPopulateWhere = options._populateWhere;
|
|
530
532
|
qb.indexHint(options.indexHint)
|
|
531
533
|
.collation(options.collation)
|
|
532
534
|
.comment(options.comments)
|
|
@@ -556,7 +558,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
556
558
|
else {
|
|
557
559
|
/* v8 ignore next */
|
|
558
560
|
res.insertId = data[meta.primaryKeys[0]] ?? res.insertId ?? res.row[meta.primaryKeys[0]];
|
|
559
|
-
|
|
561
|
+
if (options.convertCustomTypes && meta?.getPrimaryProp().customType) {
|
|
562
|
+
pk = [meta.getPrimaryProp().customType.convertToDatabaseValue(res.insertId, this.platform)];
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
pk = [res.insertId];
|
|
566
|
+
}
|
|
560
567
|
}
|
|
561
568
|
await this.processManyToMany(meta, pk, collections, false, options);
|
|
562
569
|
return res;
|
|
@@ -780,7 +787,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
780
787
|
await this.processManyToMany(meta, pk, collections, true, options);
|
|
781
788
|
return res;
|
|
782
789
|
}
|
|
783
|
-
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
790
|
+
async nativeUpdateMany(entityName, where, data, options = {}, transform) {
|
|
784
791
|
options.processCollections ??= true;
|
|
785
792
|
options.convertCustomTypes ??= true;
|
|
786
793
|
const meta = this.metadata.get(entityName);
|
|
@@ -894,7 +901,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
894
901
|
const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
|
|
895
902
|
sql +=
|
|
896
903
|
pks.length > 1
|
|
897
|
-
? `(${pks.map(pk =>
|
|
904
|
+
? `(${pks.map(pk => this.platform.quoteIdentifier(pk)).join(', ')})`
|
|
898
905
|
: this.platform.quoteIdentifier(pks[0]);
|
|
899
906
|
const conds = where.map(cond => {
|
|
900
907
|
if (Utils.isPlainObject(cond) && Utils.getObjectKeysSize(cond) === 1) {
|
|
@@ -923,6 +930,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
923
930
|
? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}`
|
|
924
931
|
: '';
|
|
925
932
|
}
|
|
933
|
+
if (transform) {
|
|
934
|
+
sql = transform(sql, params);
|
|
935
|
+
}
|
|
926
936
|
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
927
937
|
for (let i = 0; i < collections.length; i++) {
|
|
928
938
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
@@ -1050,8 +1060,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1050
1060
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
1051
1061
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
1052
1062
|
const ownerMeta = pivotProp2.targetMeta;
|
|
1063
|
+
// The pivot query builder doesn't convert custom types, so we need to manually
|
|
1064
|
+
// convert owner PKs to DB format for the query and convert result FKs back to
|
|
1065
|
+
// JS format for consistent key hashing in buildPivotResultMap.
|
|
1066
|
+
const pkProp = ownerMeta.properties[ownerMeta.primaryKeys[0]];
|
|
1067
|
+
const needsConversion = pkProp?.customType?.ensureComparable(ownerMeta, pkProp) && !ownerMeta.compositePK;
|
|
1068
|
+
let ownerPks = ownerMeta.compositePK ? owners : owners.map(o => o[0]);
|
|
1069
|
+
if (needsConversion) {
|
|
1070
|
+
ownerPks = ownerPks.map(v => pkProp.customType.convertToDatabaseValue(v, this.platform, { mode: 'query' }));
|
|
1071
|
+
}
|
|
1053
1072
|
const cond = {
|
|
1054
|
-
[pivotProp2.name]: { $in:
|
|
1073
|
+
[pivotProp2.name]: { $in: ownerPks },
|
|
1055
1074
|
};
|
|
1056
1075
|
if (!Utils.isEmpty(where)) {
|
|
1057
1076
|
cond[pivotProp1.name] = { ...where };
|
|
@@ -1084,6 +1103,16 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1084
1103
|
_populateWhere: 'infer',
|
|
1085
1104
|
populateFilter: this.wrapPopulateFilter(options, pivotProp2.name),
|
|
1086
1105
|
});
|
|
1106
|
+
// Convert result FK values back to JS format so key hashing
|
|
1107
|
+
// in buildPivotResultMap is consistent with the owner keys.
|
|
1108
|
+
if (needsConversion) {
|
|
1109
|
+
for (const item of res) {
|
|
1110
|
+
const fk = item[pivotProp2.name];
|
|
1111
|
+
if (fk != null) {
|
|
1112
|
+
item[pivotProp2.name] = pkProp.customType.convertToJSValue(fk, this.platform);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1087
1116
|
return this.buildPivotResultMap(owners, res, pivotProp2.name, pivotProp1.name);
|
|
1088
1117
|
}
|
|
1089
1118
|
/**
|
|
@@ -1509,7 +1538,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1509
1538
|
* @internal
|
|
1510
1539
|
*/
|
|
1511
1540
|
findTPTChildAlias(qb, childMeta) {
|
|
1512
|
-
const joins = qb.
|
|
1541
|
+
const joins = qb.state.joins;
|
|
1513
1542
|
for (const key of Object.keys(joins)) {
|
|
1514
1543
|
if (joins[key].table === childMeta.tableName && key.includes('[tpt]')) {
|
|
1515
1544
|
return joins[key].alias;
|
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type SchemaHelper } from './schema/SchemaHelper.js';
|
|
|
4
4
|
import type { IndexDef } from './typings.js';
|
|
5
5
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
6
6
|
export declare abstract class AbstractSqlPlatform extends Platform {
|
|
7
|
+
#private;
|
|
7
8
|
protected readonly schemaHelper?: SchemaHelper;
|
|
8
9
|
usesPivotTable(): boolean;
|
|
9
10
|
indexForeignKeys(): boolean;
|
|
@@ -26,6 +27,12 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
26
27
|
quoteValue(value: any): string;
|
|
27
28
|
getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | RawQueryFragment;
|
|
28
29
|
getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | RawQueryFragment;
|
|
30
|
+
/**
|
|
31
|
+
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
32
|
+
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
quoteJsonKey(key: string): string;
|
|
29
36
|
getJsonIndexDefinition(index: IndexDef): string[];
|
|
30
37
|
supportsUnionWhere(): boolean;
|
|
31
38
|
supportsSchemas(): boolean;
|
|
@@ -42,4 +49,31 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
42
49
|
quoteCollation(collation: string): string;
|
|
43
50
|
/** @internal */
|
|
44
51
|
protected validateCollationName(collation: string): void;
|
|
52
|
+
/** @internal */
|
|
53
|
+
validateJsonPropertyName(name: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Returns FROM clause for JSON array iteration.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
getJsonArrayFromSQL(column: string, alias: string, _properties: {
|
|
59
|
+
name: string;
|
|
60
|
+
type: string;
|
|
61
|
+
}[]): string;
|
|
62
|
+
/**
|
|
63
|
+
* Returns SQL expression to access an element's property within a JSON array iteration.
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
getJsonArrayElementPropertySQL(alias: string, property: string, _type: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Wraps JSON array FROM clause and WHERE condition into a full EXISTS condition.
|
|
69
|
+
* MySQL overrides this because `json_table` doesn't support correlated subqueries.
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
getJsonArrayExistsSQL(from: string, where: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Maps a runtime type name (e.g. 'string', 'number') to a driver-specific bind type constant.
|
|
75
|
+
* Used by NativeQueryBuilder for output bindings.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
mapToBindType(type: string): unknown;
|
|
45
79
|
}
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -3,6 +3,7 @@ import { SqlEntityRepository } from './SqlEntityRepository.js';
|
|
|
3
3
|
import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export class AbstractSqlPlatform extends Platform {
|
|
6
|
+
static #JSON_PROPERTY_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6
7
|
schemaHelper;
|
|
7
8
|
usesPivotTable() {
|
|
8
9
|
return true;
|
|
@@ -64,11 +65,18 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
64
65
|
}
|
|
65
66
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
66
67
|
const [a, ...b] = path;
|
|
67
|
-
const quoteKey = (key) => (key.match(/^[a-z]\w*$/i) ? key : `"${key}"`);
|
|
68
68
|
if (aliased) {
|
|
69
|
-
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(
|
|
69
|
+
return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
|
|
70
70
|
}
|
|
71
|
-
return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(
|
|
71
|
+
return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Quotes a key for use inside a JSON path expression (e.g. `$.key`).
|
|
75
|
+
* Simple alphanumeric keys are left unquoted; others are wrapped in double quotes.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
quoteJsonKey(key) {
|
|
79
|
+
return /^[a-z]\w*$/i.exec(key) ? key : `"${key}"`;
|
|
72
80
|
}
|
|
73
81
|
getJsonIndexDefinition(index) {
|
|
74
82
|
return index.columnNames.map(column => {
|
|
@@ -116,4 +124,40 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
116
124
|
throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
|
|
117
125
|
}
|
|
118
126
|
}
|
|
127
|
+
/** @internal */
|
|
128
|
+
validateJsonPropertyName(name) {
|
|
129
|
+
if (!AbstractSqlPlatform.#JSON_PROPERTY_NAME_RE.test(name)) {
|
|
130
|
+
throw new Error(`Invalid JSON property name: '${name}'. JSON property names must contain only alphanumeric characters and underscores.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Returns FROM clause for JSON array iteration.
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
getJsonArrayFromSQL(column, alias, _properties) {
|
|
138
|
+
return `json_each(${column}) as ${this.quoteIdentifier(alias)}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns SQL expression to access an element's property within a JSON array iteration.
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
getJsonArrayElementPropertySQL(alias, property, _type) {
|
|
145
|
+
return `${this.quoteIdentifier(alias)}.${this.quoteIdentifier(property)}`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Wraps JSON array FROM clause and WHERE condition into a full EXISTS condition.
|
|
149
|
+
* MySQL overrides this because `json_table` doesn't support correlated subqueries.
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
getJsonArrayExistsSQL(from, where) {
|
|
153
|
+
return `exists (select 1 from ${from} where ${where})`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Maps a runtime type name (e.g. 'string', 'number') to a driver-specific bind type constant.
|
|
157
|
+
* Used by NativeQueryBuilder for output bindings.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
mapToBindType(type) {
|
|
161
|
+
return type;
|
|
162
|
+
}
|
|
119
163
|
}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { type AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
export declare class PivotCollectionPersister<Entity extends object> {
|
|
4
|
-
private
|
|
5
|
-
|
|
6
|
-
private readonly ctx?;
|
|
7
|
-
private readonly schema?;
|
|
8
|
-
private readonly loggerContext?;
|
|
9
|
-
private readonly inserts;
|
|
10
|
-
private readonly upserts;
|
|
11
|
-
private readonly deletes;
|
|
12
|
-
private readonly batchSize;
|
|
13
|
-
private order;
|
|
14
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined, loggerContext?: Dictionary | undefined);
|
|
4
|
+
#private;
|
|
5
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary);
|
|
15
6
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[], isInitialized?: boolean): void;
|
|
16
7
|
private enqueueInsert;
|
|
17
8
|
private enqueueUpsert;
|
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
class InsertStatement {
|
|
2
|
-
keys;
|
|
3
|
-
data;
|
|
4
2
|
order;
|
|
3
|
+
#keys;
|
|
4
|
+
#data;
|
|
5
5
|
constructor(keys, data, order) {
|
|
6
|
-
this.keys = keys;
|
|
7
|
-
this.data = data;
|
|
8
6
|
this.order = order;
|
|
7
|
+
this.#keys = keys;
|
|
8
|
+
this.#data = data;
|
|
9
9
|
}
|
|
10
10
|
getHash() {
|
|
11
|
-
return JSON.stringify(this
|
|
11
|
+
return JSON.stringify(this.#data);
|
|
12
12
|
}
|
|
13
13
|
getData() {
|
|
14
14
|
const data = {};
|
|
15
|
-
this
|
|
15
|
+
this.#keys.forEach((key, idx) => (data[key] = this.#data[idx]));
|
|
16
16
|
return data;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
class DeleteStatement {
|
|
20
|
-
keys;
|
|
21
|
-
cond;
|
|
20
|
+
#keys;
|
|
21
|
+
#cond;
|
|
22
22
|
constructor(keys, cond) {
|
|
23
|
-
this
|
|
24
|
-
this
|
|
23
|
+
this.#keys = keys;
|
|
24
|
+
this.#cond = cond;
|
|
25
25
|
}
|
|
26
26
|
getHash() {
|
|
27
|
-
return JSON.stringify(this
|
|
27
|
+
return JSON.stringify(this.#cond);
|
|
28
28
|
}
|
|
29
29
|
getCondition() {
|
|
30
30
|
const cond = {};
|
|
31
|
-
this
|
|
31
|
+
this.#keys.forEach((key, idx) => (cond[key] = this.#cond[idx]));
|
|
32
32
|
return cond;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
export class PivotCollectionPersister {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
#inserts = new Map();
|
|
37
|
+
#upserts = new Map();
|
|
38
|
+
#deletes = new Map();
|
|
39
|
+
#batchSize;
|
|
40
|
+
#order = 0;
|
|
41
|
+
#meta;
|
|
42
|
+
#driver;
|
|
43
|
+
#ctx;
|
|
44
|
+
#schema;
|
|
45
|
+
#loggerContext;
|
|
46
46
|
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
47
|
-
this
|
|
48
|
-
this
|
|
49
|
-
this
|
|
50
|
-
this
|
|
51
|
-
this
|
|
52
|
-
this
|
|
47
|
+
this.#meta = meta;
|
|
48
|
+
this.#driver = driver;
|
|
49
|
+
this.#ctx = ctx;
|
|
50
|
+
this.#schema = schema;
|
|
51
|
+
this.#loggerContext = loggerContext;
|
|
52
|
+
this.#batchSize = this.#driver.config.get('batchSize');
|
|
53
53
|
}
|
|
54
54
|
enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
|
|
55
55
|
if (insertDiff.length) {
|
|
@@ -68,8 +68,8 @@ export class PivotCollectionPersister {
|
|
|
68
68
|
for (const fks of insertDiff) {
|
|
69
69
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
70
70
|
const hash = statement.getHash();
|
|
71
|
-
if (prop.owner || !this
|
|
72
|
-
this
|
|
71
|
+
if (prop.owner || !this.#inserts.has(hash)) {
|
|
72
|
+
this.#inserts.set(hash, statement);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -77,26 +77,26 @@ export class PivotCollectionPersister {
|
|
|
77
77
|
for (const fks of insertDiff) {
|
|
78
78
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
79
79
|
const hash = statement.getHash();
|
|
80
|
-
if (prop.owner || !this
|
|
81
|
-
this
|
|
80
|
+
if (prop.owner || !this.#upserts.has(hash)) {
|
|
81
|
+
this.#upserts.set(hash, statement);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
createInsertStatement(prop, fks, pks) {
|
|
86
86
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
87
|
-
return new InsertStatement(keys, data, this
|
|
87
|
+
return new InsertStatement(keys, data, this.#order++);
|
|
88
88
|
}
|
|
89
89
|
enqueueDelete(prop, deleteDiff, pks) {
|
|
90
90
|
if (deleteDiff === true) {
|
|
91
91
|
const { data, keys } = this.buildPivotKeysAndData(prop, [], pks, true);
|
|
92
92
|
const statement = new DeleteStatement(keys, data);
|
|
93
|
-
this
|
|
93
|
+
this.#deletes.set(statement.getHash(), statement);
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
for (const fks of deleteDiff) {
|
|
97
97
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
98
98
|
const statement = new DeleteStatement(keys, data);
|
|
99
|
-
this
|
|
99
|
+
this.#deletes.set(statement.getHash(), statement);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
@@ -130,46 +130,46 @@ export class PivotCollectionPersister {
|
|
|
130
130
|
return items.filter(Boolean);
|
|
131
131
|
}
|
|
132
132
|
async execute() {
|
|
133
|
-
if (this
|
|
134
|
-
const deletes = [...this
|
|
135
|
-
for (let i = 0; i < deletes.length; i += this
|
|
136
|
-
const chunk = deletes.slice(i, i + this
|
|
133
|
+
if (this.#deletes.size > 0) {
|
|
134
|
+
const deletes = [...this.#deletes.values()];
|
|
135
|
+
for (let i = 0; i < deletes.length; i += this.#batchSize) {
|
|
136
|
+
const chunk = deletes.slice(i, i + this.#batchSize);
|
|
137
137
|
const cond = { $or: [] };
|
|
138
138
|
for (const item of chunk) {
|
|
139
139
|
cond.$or.push(item.getCondition());
|
|
140
140
|
}
|
|
141
|
-
await this
|
|
142
|
-
ctx: this
|
|
143
|
-
schema: this
|
|
144
|
-
loggerContext: this
|
|
141
|
+
await this.#driver.nativeDelete(this.#meta.class, cond, {
|
|
142
|
+
ctx: this.#ctx,
|
|
143
|
+
schema: this.#schema,
|
|
144
|
+
loggerContext: this.#loggerContext,
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
if (this
|
|
149
|
-
const filtered = this.collectStatements(this
|
|
150
|
-
for (let i = 0; i < filtered.length; i += this
|
|
151
|
-
const chunk = filtered.slice(i, i + this
|
|
152
|
-
await this
|
|
153
|
-
ctx: this
|
|
154
|
-
schema: this
|
|
148
|
+
if (this.#inserts.size > 0) {
|
|
149
|
+
const filtered = this.collectStatements(this.#inserts);
|
|
150
|
+
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
151
|
+
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
152
|
+
await this.#driver.nativeInsertMany(this.#meta.class, chunk, {
|
|
153
|
+
ctx: this.#ctx,
|
|
154
|
+
schema: this.#schema,
|
|
155
155
|
convertCustomTypes: false,
|
|
156
156
|
processCollections: false,
|
|
157
|
-
loggerContext: this
|
|
157
|
+
loggerContext: this.#loggerContext,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
-
if (this
|
|
162
|
-
const filtered = this.collectStatements(this
|
|
163
|
-
for (let i = 0; i < filtered.length; i += this
|
|
164
|
-
const chunk = filtered.slice(i, i + this
|
|
165
|
-
await this
|
|
166
|
-
ctx: this
|
|
167
|
-
schema: this
|
|
161
|
+
if (this.#upserts.size > 0) {
|
|
162
|
+
const filtered = this.collectStatements(this.#upserts);
|
|
163
|
+
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
164
|
+
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
165
|
+
await this.#driver.nativeUpdateMany(this.#meta.class, [], chunk, {
|
|
166
|
+
ctx: this.#ctx,
|
|
167
|
+
schema: this.#schema,
|
|
168
168
|
convertCustomTypes: false,
|
|
169
169
|
processCollections: false,
|
|
170
170
|
upsert: true,
|
|
171
171
|
onConflictAction: 'ignore',
|
|
172
|
-
loggerContext: this
|
|
172
|
+
loggerContext: this.#loggerContext,
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
}
|
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
9
|
-
[](https://
|
|
10
|
-
[](https://
|
|
9
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
10
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
11
11
|
[](https://discord.gg/w8bjxFHS7X)
|
|
12
|
-
[](https://
|
|
12
|
+
[](https://npmx.dev/package/@mikro-orm/core)
|
|
13
13
|
[](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
|
|
14
14
|
[](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
|
|
15
15
|
|
|
@@ -181,6 +181,7 @@ yarn add @mikro-orm/core @mikro-orm/mysql # for mysql/mariadb
|
|
|
181
181
|
yarn add @mikro-orm/core @mikro-orm/mariadb # for mysql/mariadb
|
|
182
182
|
yarn add @mikro-orm/core @mikro-orm/postgresql # for postgresql
|
|
183
183
|
yarn add @mikro-orm/core @mikro-orm/mssql # for mssql
|
|
184
|
+
yarn add @mikro-orm/core @mikro-orm/oracledb # for oracle
|
|
184
185
|
yarn add @mikro-orm/core @mikro-orm/sqlite # for sqlite
|
|
185
186
|
yarn add @mikro-orm/core @mikro-orm/libsql # for libsql
|
|
186
187
|
```
|
package/SqlEntityManager.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
|
|
|
20
20
|
/**
|
|
21
21
|
* Shortcut for `createQueryBuilder()`
|
|
22
22
|
*/
|
|
23
|
-
qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias
|
|
23
|
+
qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias>;
|
|
24
24
|
/**
|
|
25
25
|
* Returns configured Kysely instance.
|
|
26
26
|
*/
|
package/dialects/index.d.ts
CHANGED
package/dialects/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
|
|
|
5
5
|
import type { IndexDef } from '../../typings.js';
|
|
6
6
|
import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js';
|
|
7
7
|
export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
8
|
+
#private;
|
|
8
9
|
protected readonly schemaHelper: MySqlSchemaHelper;
|
|
9
10
|
protected readonly exceptionConverter: MySqlExceptionConverter;
|
|
10
11
|
protected readonly ORDER_BY_NULLS_TRANSLATE: {
|
|
@@ -42,5 +43,10 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
42
43
|
getFullTextWhereClause(): string;
|
|
43
44
|
getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
|
|
44
45
|
getOrderByExpression(column: string, direction: string, collation?: string): string[];
|
|
46
|
+
getJsonArrayFromSQL(column: string, alias: string, properties: {
|
|
47
|
+
name: string;
|
|
48
|
+
type: string;
|
|
49
|
+
}[]): string;
|
|
50
|
+
getJsonArrayExistsSQL(from: string, where: string): string;
|
|
45
51
|
getDefaultClientUrl(): string;
|
|
46
52
|
}
|