@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.
@@ -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, ...loggerContext });
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()).getExecutor().stream(compiled, chunkSize ?? 100);
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
- ...loggerContext,
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, ...loggerContext, level: 'error' });
241
+ this.logQuery(sql, { sql, params, ...cleanCtx, level: 'error' });
221
242
  throw e;
222
243
  }
223
244
  }
@@ -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
- constructor(meta, driver, ctx, schema, loggerContext) {
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
@@ -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
- /** Executes a raw SQL query, using the current transaction context if available. */
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
  */
@@ -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
- return this.driver.createQueryBuilder(entityName, context.getTransactionContext(), type, true, loggerContext ?? context.loggerContext, alias, this);
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
- /** Executes a raw SQL query, using the current transaction context if available. */
35
- async execute(query, params = [], method = 'all', loggerContext) {
36
- return this.getDriver().execute(query, params, method, this.getContext(false).getTransactionContext(), loggerContext);
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.30",
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.28.17"
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.30"
56
+ "@mikro-orm/core": "7.1.0-dev.32"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -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
@@ -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)}`;