@mikro-orm/sql 7.1.0-dev.30 → 7.1.0-dev.32
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.js +26 -5
- package/AbstractSqlDriver.js +42 -6
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +6 -1
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +20 -2
- package/SqlEntityManager.js +16 -4
- package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
- package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +44 -0
- package/dialects/postgresql/index.d.ts +2 -0
- package/dialects/postgresql/index.js +2 -0
- package/dialects/postgresql/typeOverrides.d.ts +14 -0
- package/dialects/postgresql/typeOverrides.js +12 -0
- package/package.json +3 -3
- package/query/QueryBuilder.d.ts +6 -1
- package/query/QueryBuilder.js +11 -2
package/AbstractSqlConnection.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { CompiledQuery, Kysely } from 'kysely';
|
|
2
2
|
import { Connection, EventType, isRaw, Utils, } from '@mikro-orm/core';
|
|
3
3
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
4
|
+
/**
|
|
5
|
+
* Pulls cancellation controls out of a `loggerContext` payload, returning the abort options
|
|
6
|
+
* and a sanitized context that no longer carries them. The QueryBuilder/EM stash
|
|
7
|
+
* `signal`/`inflightQueryAbortStrategy` on `loggerContext` to avoid widening the public
|
|
8
|
+
* connection API; stripping them prevents leakage into user `Logger.logQuery` payloads.
|
|
9
|
+
*/
|
|
10
|
+
function extractAbortOptions(loggerContext) {
|
|
11
|
+
const ctx = loggerContext;
|
|
12
|
+
if (ctx?.signal == null && ctx?.inflightQueryAbortStrategy == null) {
|
|
13
|
+
return { loggerContext };
|
|
14
|
+
}
|
|
15
|
+
const { signal, inflightQueryAbortStrategy, ...rest } = ctx;
|
|
16
|
+
return {
|
|
17
|
+
abort: { signal, inflightQueryAbortStrategy },
|
|
18
|
+
loggerContext: rest,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
4
21
|
/** Base class for SQL database connections, built on top of Kysely. */
|
|
5
22
|
export class AbstractSqlConnection extends Connection {
|
|
6
23
|
#client;
|
|
@@ -183,17 +200,19 @@ export class AbstractSqlConnection extends Connection {
|
|
|
183
200
|
await this.ensureConnection();
|
|
184
201
|
const q = this.prepareQuery(query, params);
|
|
185
202
|
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
203
|
+
const { abort, loggerContext: cleanCtx } = extractAbortOptions(loggerContext);
|
|
186
204
|
return this.executeQuery(sql, async () => {
|
|
187
205
|
const compiled = CompiledQuery.raw(q.formatted);
|
|
188
|
-
const res = await (ctx ?? this.#client).executeQuery(compiled);
|
|
206
|
+
const res = await (ctx ?? this.#client).executeQuery(compiled, abort);
|
|
189
207
|
return this.transformRawResult(res, method);
|
|
190
|
-
}, { ...q, ...
|
|
208
|
+
}, { ...q, ...cleanCtx });
|
|
191
209
|
}
|
|
192
210
|
/** Executes a SQL query and returns an async iterable that yields results row by row. */
|
|
193
211
|
async *stream(query, params = [], ctx, loggerContext, chunkSize) {
|
|
194
212
|
await this.ensureConnection();
|
|
195
213
|
const q = this.prepareQuery(query, params);
|
|
196
214
|
const sql = this.getSql(q.query, q.formatted, loggerContext);
|
|
215
|
+
const { abort, loggerContext: cleanCtx } = extractAbortOptions(loggerContext);
|
|
197
216
|
// construct the compiled query manually with `kind: 'SelectQueryNode'` to avoid sqlite validation for select queries when streaming
|
|
198
217
|
const compiled = {
|
|
199
218
|
query: {
|
|
@@ -203,11 +222,13 @@ export class AbstractSqlConnection extends Connection {
|
|
|
203
222
|
parameters: [],
|
|
204
223
|
};
|
|
205
224
|
try {
|
|
206
|
-
const res = (ctx ?? this.getClient())
|
|
225
|
+
const res = (ctx ?? this.getClient())
|
|
226
|
+
.getExecutor()
|
|
227
|
+
.stream(compiled, chunkSize ?? 100, abort ? { signal: abort.signal } : undefined);
|
|
207
228
|
this.logQuery(sql, {
|
|
208
229
|
sql,
|
|
209
230
|
params,
|
|
210
|
-
...
|
|
231
|
+
...cleanCtx,
|
|
211
232
|
affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
|
|
212
233
|
});
|
|
213
234
|
for await (const items of res) {
|
|
@@ -217,7 +238,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
217
238
|
}
|
|
218
239
|
}
|
|
219
240
|
catch (e) {
|
|
220
|
-
this.logQuery(sql, { sql, params, ...
|
|
241
|
+
this.logQuery(sql, { sql, params, ...cleanCtx, level: 'error' });
|
|
221
242
|
throw e;
|
|
222
243
|
}
|
|
223
244
|
}
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -3,6 +3,28 @@ import { QueryBuilder } from './query/QueryBuilder.js';
|
|
|
3
3
|
import { JoinType, QueryType } from './query/enums.js';
|
|
4
4
|
import { SqlEntityManager } from './SqlEntityManager.js';
|
|
5
5
|
import { PivotCollectionPersister } from './PivotCollectionPersister.js';
|
|
6
|
+
/** Extracts cancellation controls from any options bag that extends `AbortQueryOptions`. */
|
|
7
|
+
function pickAbortOptions(options) {
|
|
8
|
+
if (!options || (options.signal == null && options.inflightQueryAbortStrategy == null)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
signal: options.signal,
|
|
13
|
+
inflightQueryAbortStrategy: options.inflightQueryAbortStrategy,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns a `loggerContext` payload that carries the abort fields alongside any existing
|
|
18
|
+
* context. The connection layer strips them before logging — this avoids widening the public
|
|
19
|
+
* `Connection.execute()` signature.
|
|
20
|
+
*/
|
|
21
|
+
function withAbortContext(loggerContext, options) {
|
|
22
|
+
const abort = pickAbortOptions(options);
|
|
23
|
+
if (!abort) {
|
|
24
|
+
return loggerContext;
|
|
25
|
+
}
|
|
26
|
+
return { ...loggerContext, ...abort };
|
|
27
|
+
}
|
|
6
28
|
/** Base class for SQL database drivers, implementing find/insert/update/delete using QueryBuilder. */
|
|
7
29
|
export class AbstractSqlDriver extends DatabaseDriver {
|
|
8
30
|
[EntityManagerType];
|
|
@@ -60,6 +82,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
60
82
|
const qb = this.createQueryBuilder(meta.class, options.ctx, connectionType, false, options.logging, undefined, options.em)
|
|
61
83
|
.withSchema(schema)
|
|
62
84
|
.cache(false);
|
|
85
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
63
86
|
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
64
87
|
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
65
88
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
@@ -216,7 +239,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
216
239
|
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
217
240
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
218
241
|
const query = native.compile();
|
|
219
|
-
const res = await this.execute(query.sql, query.params, 'all', options.ctx);
|
|
242
|
+
const res = await this.execute(query.sql, query.params, 'all', options.ctx, withAbortContext(options.loggerContext, options));
|
|
220
243
|
if (type === QueryType.COUNT) {
|
|
221
244
|
return res[0].count;
|
|
222
245
|
}
|
|
@@ -233,7 +256,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
233
256
|
native.from(raw(`(${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}`));
|
|
234
257
|
const query = native.compile();
|
|
235
258
|
const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
|
|
236
|
-
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, options.loggerContext, options.chunkSize);
|
|
259
|
+
const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, withAbortContext(options.loggerContext, options), options.chunkSize);
|
|
237
260
|
for await (const row of res) {
|
|
238
261
|
yield this.mapResult(row, meta);
|
|
239
262
|
}
|
|
@@ -541,6 +564,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
541
564
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
542
565
|
const schema = this.getSchemaName(meta, options);
|
|
543
566
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
567
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
544
568
|
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
545
569
|
if (meta && !Utils.isEmpty(populate)) {
|
|
546
570
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, schema);
|
|
@@ -566,6 +590,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
566
590
|
const meta = this.metadata.get(entityName);
|
|
567
591
|
const collections = this.extractManyToMany(meta, data);
|
|
568
592
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
593
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
569
594
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
570
595
|
res.row = res.row || {};
|
|
571
596
|
let pk;
|
|
@@ -598,9 +623,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
598
623
|
const props = this.getCloneableProps(meta);
|
|
599
624
|
const mappedOverrides = this.mapCloneOverrides(overrides, meta, options);
|
|
600
625
|
const { selectFields, insertColumns } = this.buildCloneFields(props, mappedOverrides, meta);
|
|
626
|
+
const abort = pickAbortOptions(options);
|
|
601
627
|
const selectQb = this.createQueryBuilder(meta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
602
628
|
selectQb.select(selectFields).where(where);
|
|
629
|
+
selectQb.setAbortOptions(abort);
|
|
603
630
|
const insertQb = this.createQueryBuilder(meta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
631
|
+
insertQb.setAbortOptions(abort);
|
|
604
632
|
return this.rethrow(insertQb
|
|
605
633
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
606
634
|
.execute('run', false));
|
|
@@ -630,9 +658,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
630
658
|
}
|
|
631
659
|
}
|
|
632
660
|
const sourceWhere = tableMeta === rootMeta ? where : (Utils.extractPK(where, tableMeta) ?? where);
|
|
661
|
+
const abort = pickAbortOptions(options);
|
|
633
662
|
const selectQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'read', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
634
663
|
selectQb.select(selectFields).where(sourceWhere);
|
|
664
|
+
selectQb.setAbortOptions(abort);
|
|
635
665
|
const insertQb = this.createQueryBuilder(tableMeta.class, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(tableMeta, options));
|
|
666
|
+
insertQb.setAbortOptions(abort);
|
|
636
667
|
const res = await this.rethrow(insertQb
|
|
637
668
|
.insertFrom(selectQb, { columns: insertColumns })
|
|
638
669
|
.execute('run', false));
|
|
@@ -840,7 +871,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
840
871
|
if (transform) {
|
|
841
872
|
sql = transform(sql);
|
|
842
873
|
}
|
|
843
|
-
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
874
|
+
const res = await this.execute(sql, params, 'run', options.ctx, withAbortContext(options.loggerContext, options));
|
|
844
875
|
let pk;
|
|
845
876
|
/* v8 ignore next */
|
|
846
877
|
if (pks.length > 1) {
|
|
@@ -873,6 +904,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
873
904
|
}
|
|
874
905
|
if (Utils.hasObjectKeys(data)) {
|
|
875
906
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
907
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
876
908
|
if (options.upsert) {
|
|
877
909
|
/* v8 ignore next */
|
|
878
910
|
const uniqueFields = options.onConflictFields ??
|
|
@@ -918,6 +950,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
918
950
|
? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
|
|
919
951
|
: meta.primaryKeys);
|
|
920
952
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
953
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
921
954
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
922
955
|
qb.insert(data)
|
|
923
956
|
.onConflict(uniqueFields)
|
|
@@ -1063,7 +1096,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1063
1096
|
if (transform) {
|
|
1064
1097
|
sql = transform(sql, params);
|
|
1065
1098
|
}
|
|
1066
|
-
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
1099
|
+
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, withAbortContext(options.loggerContext, options)));
|
|
1067
1100
|
for (let i = 0; i < collections.length; i++) {
|
|
1068
1101
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
1069
1102
|
}
|
|
@@ -1081,6 +1114,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1081
1114
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext)
|
|
1082
1115
|
.delete(where)
|
|
1083
1116
|
.withSchema(this.getSchemaName(meta, options));
|
|
1117
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1084
1118
|
return this.rethrow(qb.execute('run', false));
|
|
1085
1119
|
}
|
|
1086
1120
|
/**
|
|
@@ -1151,6 +1185,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1151
1185
|
if (coll.property.kind === ReferenceKind.ONE_TO_MANY) {
|
|
1152
1186
|
const cols = coll.property.referencedColumnNames;
|
|
1153
1187
|
const qb = this.createQueryBuilder(coll.property.targetMeta.class, options?.ctx, 'write').withSchema(this.getSchemaName(meta, options));
|
|
1188
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
1154
1189
|
if (coll.getSnapshot() === undefined) {
|
|
1155
1190
|
if (coll.property.orphanRemoval) {
|
|
1156
1191
|
const query = qb
|
|
@@ -1192,7 +1227,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1192
1227
|
schema = this.config.get('schema');
|
|
1193
1228
|
}
|
|
1194
1229
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
1195
|
-
const persister = (groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext));
|
|
1230
|
+
const persister = (groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext, pickAbortOptions(options)));
|
|
1196
1231
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks, coll.isInitialized());
|
|
1197
1232
|
}
|
|
1198
1233
|
for (const persister of Utils.values(groups)) {
|
|
@@ -2023,7 +2058,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
2023
2058
|
for (const prop of meta.relations) {
|
|
2024
2059
|
if (collections[prop.name]) {
|
|
2025
2060
|
const pivotMeta = this.metadata.get(prop.pivotEntity);
|
|
2026
|
-
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
2061
|
+
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext, pickAbortOptions(options));
|
|
2027
2062
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
2028
2063
|
await this.rethrow(persister.execute());
|
|
2029
2064
|
}
|
|
@@ -2032,6 +2067,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
2032
2067
|
async lockPessimistic(entity, options) {
|
|
2033
2068
|
const meta = helper(entity).__meta;
|
|
2034
2069
|
const qb = this.createQueryBuilder(meta.class, options.ctx, undefined, undefined, options.logging).withSchema(options.schema ?? meta.schema);
|
|
2070
|
+
qb.setAbortOptions(pickAbortOptions(options));
|
|
2035
2071
|
const cond = Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
2036
2072
|
qb.select(raw('1'))
|
|
2037
2073
|
.where(cond)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, 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
4
|
#private;
|
|
5
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary);
|
|
5
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction, schema?: string, loggerContext?: Dictionary, abort?: AbortQueryOptions);
|
|
6
6
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[], isInitialized?: boolean): void;
|
|
7
7
|
private enqueueInsert;
|
|
8
8
|
private enqueueUpsert;
|
|
@@ -44,12 +44,14 @@ export class PivotCollectionPersister {
|
|
|
44
44
|
#ctx;
|
|
45
45
|
#schema;
|
|
46
46
|
#loggerContext;
|
|
47
|
-
|
|
47
|
+
#abort;
|
|
48
|
+
constructor(meta, driver, ctx, schema, loggerContext, abort) {
|
|
48
49
|
this.#meta = meta;
|
|
49
50
|
this.#driver = driver;
|
|
50
51
|
this.#ctx = ctx;
|
|
51
52
|
this.#schema = schema;
|
|
52
53
|
this.#loggerContext = loggerContext;
|
|
54
|
+
this.#abort = abort;
|
|
53
55
|
this.#batchSize = this.#driver.config.get('batchSize');
|
|
54
56
|
}
|
|
55
57
|
enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
|
|
@@ -153,6 +155,7 @@ export class PivotCollectionPersister {
|
|
|
153
155
|
ctx: this.#ctx,
|
|
154
156
|
schema: this.#schema,
|
|
155
157
|
loggerContext: this.#loggerContext,
|
|
158
|
+
...this.#abort,
|
|
156
159
|
});
|
|
157
160
|
}
|
|
158
161
|
}
|
|
@@ -166,6 +169,7 @@ export class PivotCollectionPersister {
|
|
|
166
169
|
convertCustomTypes: false,
|
|
167
170
|
processCollections: false,
|
|
168
171
|
loggerContext: this.#loggerContext,
|
|
172
|
+
...this.#abort,
|
|
169
173
|
});
|
|
170
174
|
}
|
|
171
175
|
}
|
|
@@ -181,6 +185,7 @@ export class PivotCollectionPersister {
|
|
|
181
185
|
upsert: true,
|
|
182
186
|
onConflictAction: 'ignore',
|
|
183
187
|
loggerContext: this.#loggerContext,
|
|
188
|
+
...this.#abort,
|
|
184
189
|
});
|
|
185
190
|
}
|
|
186
191
|
}
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
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 (including CockroachDB and PGlite), 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
|
|
|
@@ -19,6 +19,7 @@ Install a driver package for your database:
|
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install @mikro-orm/postgresql # PostgreSQL
|
|
22
|
+
npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
|
|
22
23
|
npm install @mikro-orm/mysql # MySQL
|
|
23
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
24
25
|
npm install @mikro-orm/sqlite # SQLite
|
package/SqlEntityManager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntitySchemaWithMeta, EntityManager, type AnyEntity, type ConnectionType, type CountByOptions, type Dictionary, type EntityData, type EntityKey, type EntityName, type EntityRepository, type FilterQuery, type GetRepository, type LoggingOptions, type QueryResult, type RawQueryFragment } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, type EntitySchemaWithMeta, EntityManager, type AnyEntity, type ConnectionType, type CountByOptions, type Dictionary, type EntityData, type EntityKey, type EntityName, type EntityRepository, type FilterQuery, type GetRepository, type LoggingOptions, type QueryResult, type RawQueryFragment } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
import type { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
4
4
|
import type { QueryBuilder } from './query/QueryBuilder.js';
|
|
@@ -6,6 +6,13 @@ import type { SqlEntityRepository } from './SqlEntityRepository.js';
|
|
|
6
6
|
import type { Kysely } from 'kysely';
|
|
7
7
|
import type { InferClassEntityDB, InferKyselyDB } from './typings.js';
|
|
8
8
|
import { type MikroKyselyPluginOptions } from './plugin/index.js';
|
|
9
|
+
/** Options for the modern signature of `SqlEntityManager.execute()`. */
|
|
10
|
+
export interface EmExecuteOptions extends AbortQueryOptions {
|
|
11
|
+
/** Result shape — `'all'` for rows, `'get'` for a single row, `'run'` for affected count. Defaults to `'all'`. */
|
|
12
|
+
method?: 'all' | 'get' | 'run';
|
|
13
|
+
/** Logger context payload forwarded to `Logger.logQuery`. */
|
|
14
|
+
loggerContext?: LoggingOptions;
|
|
15
|
+
}
|
|
9
16
|
/** Options for `SqlEntityManager.getKysely()`. */
|
|
10
17
|
export interface GetKyselyOptions extends MikroKyselyPluginOptions {
|
|
11
18
|
/** Connection type to use (`'read'` or `'write'`). */
|
|
@@ -27,8 +34,19 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
|
|
|
27
34
|
* Returns configured Kysely instance.
|
|
28
35
|
*/
|
|
29
36
|
getKysely<TDB = undefined, TOptions extends GetKyselyOptions = GetKyselyOptions>(options?: TOptions): Kysely<TDB extends undefined ? InferKyselyDB<EntitiesFromManager<this>, TOptions> & InferClassEntityDB<AllEntitiesFromManager<this>, TOptions> : TDB>;
|
|
30
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Executes a raw SQL query, using the current transaction context if available. The fork-level
|
|
39
|
+
* `signal` / `inflightQueryAbortStrategy` (set via `em.fork({ signal })`) is applied automatically.
|
|
40
|
+
* For per-call cancellation use the options-bag overload below.
|
|
41
|
+
*/
|
|
31
42
|
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Executes a raw SQL query with an options bag carrying `method`, `loggerContext`, `signal`
|
|
45
|
+
* and `inflightQueryAbortStrategy`. Per-call `signal` / `inflightQueryAbortStrategy` override
|
|
46
|
+
* the fork-level defaults set via `em.fork({ signal })`. The current transaction context is
|
|
47
|
+
* applied automatically.
|
|
48
|
+
*/
|
|
49
|
+
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params: any[], options: EmExecuteOptions): Promise<T>;
|
|
32
50
|
/**
|
|
33
51
|
* @inheritDoc
|
|
34
52
|
*/
|
package/SqlEntityManager.js
CHANGED
|
@@ -9,7 +9,9 @@ export class SqlEntityManager extends EntityManager {
|
|
|
9
9
|
*/
|
|
10
10
|
createQueryBuilder(entityName, alias, type, loggerContext) {
|
|
11
11
|
const context = this.getContext(false);
|
|
12
|
-
|
|
12
|
+
const qb = this.driver.createQueryBuilder(entityName, context.getTransactionContext(), type, true, loggerContext ?? context.loggerContext, alias, this);
|
|
13
|
+
qb.setAbortOptions(context.getAbortOptions());
|
|
14
|
+
return qb;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Shortcut for `createQueryBuilder()`
|
|
@@ -31,9 +33,19 @@ export class SqlEntityManager extends EntityManager {
|
|
|
31
33
|
}
|
|
32
34
|
return kysely;
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
async execute(query, params = [], methodOrOptions = 'all', loggerContext) {
|
|
37
|
+
const opts = typeof methodOrOptions === 'string' ? { method: methodOrOptions, loggerContext } : methodOrOptions;
|
|
38
|
+
const context = this.getContext(false);
|
|
39
|
+
// Per-field fallback to fork-level abort, matching `EntityManager.prepareOptions` semantics.
|
|
40
|
+
const fork = context.getAbortOptions();
|
|
41
|
+
const hasAbort = opts.signal != null || opts.inflightQueryAbortStrategy != null || fork != null;
|
|
42
|
+
// Connection layer carries abort piggy-backed on `loggerContext`; merge only when needed.
|
|
43
|
+
const merged = hasAbort || opts.loggerContext ? { ...opts.loggerContext } : undefined;
|
|
44
|
+
if (merged && hasAbort) {
|
|
45
|
+
merged.signal = opts.signal ?? fork?.signal;
|
|
46
|
+
merged.inflightQueryAbortStrategy = opts.inflightQueryAbortStrategy ?? fork?.inflightQueryAbortStrategy;
|
|
47
|
+
}
|
|
48
|
+
return this.getDriver().execute(query, params, opts.method ?? 'all', context.getTransactionContext(), merged);
|
|
37
49
|
}
|
|
38
50
|
/**
|
|
39
51
|
* @inheritDoc
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EntityName } from '@mikro-orm/core';
|
|
2
|
+
import { SqlEntityManager } from '../../SqlEntityManager.js';
|
|
3
|
+
import type { AbstractSqlDriver } from '../../AbstractSqlDriver.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared base class for PostgreSQL-flavoured entity managers (`pg`, `pglite`).
|
|
6
|
+
* Adds Postgres-only helpers on top of `SqlEntityManager`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class BasePostgreSqlEntityManager<Driver extends AbstractSqlDriver = AbstractSqlDriver> extends SqlEntityManager<Driver> {
|
|
9
|
+
/**
|
|
10
|
+
* Refreshes a materialized view.
|
|
11
|
+
*
|
|
12
|
+
* @param entityName - The entity name or class of the materialized view
|
|
13
|
+
* @param options - Optional settings
|
|
14
|
+
* @param options.concurrently - If true, refreshes the view concurrently (requires a unique index on the view)
|
|
15
|
+
*/
|
|
16
|
+
refreshMaterializedView<Entity extends object>(entityName: EntityName<Entity>, options?: {
|
|
17
|
+
concurrently?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SqlEntityManager } from '../../SqlEntityManager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared base class for PostgreSQL-flavoured entity managers (`pg`, `pglite`).
|
|
4
|
+
* Adds Postgres-only helpers on top of `SqlEntityManager`.
|
|
5
|
+
*/
|
|
6
|
+
export class BasePostgreSqlEntityManager extends SqlEntityManager {
|
|
7
|
+
/**
|
|
8
|
+
* Refreshes a materialized view.
|
|
9
|
+
*
|
|
10
|
+
* @param entityName - The entity name or class of the materialized view
|
|
11
|
+
* @param options - Optional settings
|
|
12
|
+
* @param options.concurrently - If true, refreshes the view concurrently (requires a unique index on the view)
|
|
13
|
+
*/
|
|
14
|
+
async refreshMaterializedView(entityName, options) {
|
|
15
|
+
const meta = this.getMetadata(entityName);
|
|
16
|
+
if (!meta.view || !meta.materialized) {
|
|
17
|
+
throw new Error(`Entity ${meta.className} is not a materialized view`);
|
|
18
|
+
}
|
|
19
|
+
const helper = this.getDriver().getPlatform().getSchemaHelper();
|
|
20
|
+
const schema = meta.schema ?? this.config.get('schema');
|
|
21
|
+
const sql = helper.refreshMaterializedView(meta.tableName, schema, options?.concurrently);
|
|
22
|
+
await this.execute(sql);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -71,6 +71,12 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
71
71
|
}): string[];
|
|
72
72
|
marshallArray(values: string[]): string;
|
|
73
73
|
unmarshallArray(value: string): string[];
|
|
74
|
+
escape(value: any): string;
|
|
75
|
+
/**
|
|
76
|
+
* Ported from PostgreSQL 9.2.4 source code (`src/interfaces/libpq/fe-exec.c`),
|
|
77
|
+
* matching `pg.Client.prototype.escapeLiteral` so we don't need a `pg` dep here.
|
|
78
|
+
*/
|
|
79
|
+
private escapeLiteral;
|
|
74
80
|
getVarcharTypeDeclarationSQL(column: {
|
|
75
81
|
length?: number;
|
|
76
82
|
}): string;
|
|
@@ -212,6 +212,50 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
212
212
|
return v;
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
|
+
escape(value) {
|
|
216
|
+
if (typeof value === 'bigint') {
|
|
217
|
+
value = value.toString();
|
|
218
|
+
}
|
|
219
|
+
if (typeof value === 'string') {
|
|
220
|
+
return this.escapeLiteral(value);
|
|
221
|
+
}
|
|
222
|
+
if (value instanceof Date) {
|
|
223
|
+
return `'${this.formatDate(value)}'`;
|
|
224
|
+
}
|
|
225
|
+
if (ArrayBuffer.isView(value)) {
|
|
226
|
+
return `E'\\\\x${value.toString('hex')}'`;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(value)) {
|
|
229
|
+
return value.map(v => this.escape(v)).join(', ');
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Ported from PostgreSQL 9.2.4 source code (`src/interfaces/libpq/fe-exec.c`),
|
|
235
|
+
* matching `pg.Client.prototype.escapeLiteral` so we don't need a `pg` dep here.
|
|
236
|
+
*/
|
|
237
|
+
escapeLiteral(str) {
|
|
238
|
+
let hasBackslash = false;
|
|
239
|
+
let escaped = `'`;
|
|
240
|
+
for (let i = 0; i < str.length; i++) {
|
|
241
|
+
const c = str[i];
|
|
242
|
+
if (c === `'`) {
|
|
243
|
+
escaped += c + c;
|
|
244
|
+
}
|
|
245
|
+
else if (c === '\\') {
|
|
246
|
+
escaped += c + c;
|
|
247
|
+
hasBackslash = true;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
escaped += c;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
escaped += `'`;
|
|
254
|
+
if (hasBackslash) {
|
|
255
|
+
escaped = ` E${escaped}`;
|
|
256
|
+
}
|
|
257
|
+
return escaped;
|
|
258
|
+
}
|
|
215
259
|
getVarcharTypeDeclarationSQL(column) {
|
|
216
260
|
if (column.length === -1) {
|
|
217
261
|
return 'varchar';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './PostgreSqlNativeQueryBuilder.js';
|
|
2
2
|
export * from './BasePostgreSqlPlatform.js';
|
|
3
|
+
export * from './BasePostgreSqlEntityManager.js';
|
|
3
4
|
export * from './FullTextType.js';
|
|
4
5
|
export * from './PostgreSqlSchemaHelper.js';
|
|
6
|
+
export * from './typeOverrides.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './PostgreSqlNativeQueryBuilder.js';
|
|
2
2
|
export * from './BasePostgreSqlPlatform.js';
|
|
3
|
+
export * from './BasePostgreSqlEntityManager.js';
|
|
3
4
|
export * from './FullTextType.js';
|
|
4
5
|
export * from './PostgreSqlSchemaHelper.js';
|
|
6
|
+
export * from './typeOverrides.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MikroORM keeps PostgreSQL date/timestamp/interval values as raw strings
|
|
3
|
+
* (and array variants as `string[]`); both `pg` and `pglite` would otherwise
|
|
4
|
+
* eagerly parse them via `pg-types`. Centralizing the OID list here keeps the
|
|
5
|
+
* postgres and pglite drivers in lockstep, while leaving the actual array
|
|
6
|
+
* parsing implementation to the leaf driver (so `@mikro-orm/sql` stays free of
|
|
7
|
+
* postgres-array / postgres-date / postgres-interval dependencies).
|
|
8
|
+
*
|
|
9
|
+
* Use `select typname, oid, typarray from pg_type order by oid` to look up OIDs.
|
|
10
|
+
*/
|
|
11
|
+
type PostgreSqlArrayParser = (value: string) => string[];
|
|
12
|
+
type PostgreSqlValueParser = (value: string) => unknown;
|
|
13
|
+
export declare function createPostgreSqlTypeParsers(arrayParse: PostgreSqlArrayParser): Record<number, PostgreSqlValueParser>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createPostgreSqlTypeParsers(arrayParse) {
|
|
2
|
+
const parsers = {};
|
|
3
|
+
for (const oid of [1082, 1114, 1184, 1186]) {
|
|
4
|
+
// date, timestamp, timestamptz, interval — kept as raw strings
|
|
5
|
+
parsers[oid] = str => str;
|
|
6
|
+
}
|
|
7
|
+
for (const oid of [1182, 1115, 1185, 1187]) {
|
|
8
|
+
// date[], timestamp[], timestamptz[], interval[]
|
|
9
|
+
parsers[oid] = arrayParse;
|
|
10
|
+
}
|
|
11
|
+
return parsers;
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.32",
|
|
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
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"kysely": "0.
|
|
50
|
+
"kysely": "0.29.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@mikro-orm/core": "^7.0.15"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.0-dev.32"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
4
4
|
import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
@@ -915,6 +915,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
915
915
|
* Gets logger context for this query builder.
|
|
916
916
|
*/
|
|
917
917
|
getLoggerContext<T extends Dictionary & LoggingOptions = Dictionary>(): T;
|
|
918
|
+
/**
|
|
919
|
+
* Configures cancellation behavior for this query builder. The signal is forwarded to the
|
|
920
|
+
* underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
|
|
921
|
+
*/
|
|
922
|
+
setAbortOptions(options: AbortQueryOptions | undefined): void;
|
|
918
923
|
private fromVirtual;
|
|
919
924
|
/**
|
|
920
925
|
* Adds a join from a property object. Used internally for TPT joins where the property
|
package/query/QueryBuilder.js
CHANGED
|
@@ -35,6 +35,7 @@ export class QueryBuilder {
|
|
|
35
35
|
#state = _a.createDefaultState();
|
|
36
36
|
#helper;
|
|
37
37
|
#query;
|
|
38
|
+
#abortOptions;
|
|
38
39
|
/** @internal */
|
|
39
40
|
static createDefaultState() {
|
|
40
41
|
return {
|
|
@@ -1010,7 +1011,7 @@ export class QueryBuilder {
|
|
|
1010
1011
|
if (cached?.data !== undefined) {
|
|
1011
1012
|
return cached.data;
|
|
1012
1013
|
}
|
|
1013
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1014
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
1014
1015
|
const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
|
|
1015
1016
|
const meta = this.mainAlias.meta;
|
|
1016
1017
|
if (!options.mapResults || !meta) {
|
|
@@ -1066,7 +1067,7 @@ export class QueryBuilder {
|
|
|
1066
1067
|
options.mapResults ??= true;
|
|
1067
1068
|
const chunkSize = options.chunkSize ?? 100;
|
|
1068
1069
|
const query = this.toQuery();
|
|
1069
|
-
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1070
|
+
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
1070
1071
|
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext, chunkSize);
|
|
1071
1072
|
const meta = this.mainAlias.meta;
|
|
1072
1073
|
if (options.rawResults || !meta) {
|
|
@@ -1290,6 +1291,7 @@ export class QueryBuilder {
|
|
|
1290
1291
|
qb.#state.finalized = false;
|
|
1291
1292
|
qb.#query = undefined;
|
|
1292
1293
|
qb.#helper = qb.createQueryBuilderHelper();
|
|
1294
|
+
qb.#abortOptions = this.#abortOptions;
|
|
1293
1295
|
return qb;
|
|
1294
1296
|
}
|
|
1295
1297
|
/**
|
|
@@ -1305,6 +1307,13 @@ export class QueryBuilder {
|
|
|
1305
1307
|
this.loggerContext ??= {};
|
|
1306
1308
|
return this.loggerContext;
|
|
1307
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Configures cancellation behavior for this query builder. The signal is forwarded to the
|
|
1312
|
+
* underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
|
|
1313
|
+
*/
|
|
1314
|
+
setAbortOptions(options) {
|
|
1315
|
+
this.#abortOptions = options;
|
|
1316
|
+
}
|
|
1308
1317
|
fromVirtual(meta) {
|
|
1309
1318
|
if (typeof meta.expression === 'string') {
|
|
1310
1319
|
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|