@mikro-orm/sql 7.1.0-dev.5 → 7.1.0-dev.50

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.
Files changed (56) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +27 -6
  3. package/AbstractSqlDriver.d.ts +26 -1
  4. package/AbstractSqlDriver.js +294 -37
  5. package/AbstractSqlPlatform.d.ts +15 -3
  6. package/AbstractSqlPlatform.js +25 -7
  7. package/PivotCollectionPersister.d.ts +2 -2
  8. package/PivotCollectionPersister.js +19 -3
  9. package/README.md +2 -1
  10. package/SqlEntityManager.d.ts +48 -5
  11. package/SqlEntityManager.js +77 -7
  12. package/SqlMikroORM.d.ts +23 -0
  13. package/SqlMikroORM.js +23 -0
  14. package/dialects/mysql/BaseMySqlPlatform.d.ts +4 -5
  15. package/dialects/mysql/BaseMySqlPlatform.js +9 -10
  16. package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
  17. package/dialects/mysql/MySqlSchemaHelper.js +280 -49
  18. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  19. package/dialects/oracledb/OracleDialect.js +2 -1
  20. package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
  21. package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
  22. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +11 -5
  23. package/dialects/postgresql/BasePostgreSqlPlatform.js +75 -17
  24. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
  25. package/dialects/postgresql/PostgreSqlSchemaHelper.js +362 -28
  26. package/dialects/postgresql/index.d.ts +2 -0
  27. package/dialects/postgresql/index.js +2 -0
  28. package/dialects/postgresql/typeOverrides.d.ts +14 -0
  29. package/dialects/postgresql/typeOverrides.js +12 -0
  30. package/dialects/sqlite/SqlitePlatform.d.ts +2 -1
  31. package/dialects/sqlite/SqlitePlatform.js +4 -0
  32. package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
  33. package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
  34. package/index.d.ts +2 -0
  35. package/index.js +2 -0
  36. package/package.json +4 -4
  37. package/plugin/transformer.d.ts +11 -3
  38. package/plugin/transformer.js +138 -29
  39. package/query/CriteriaNode.d.ts +1 -1
  40. package/query/CriteriaNode.js +2 -2
  41. package/query/ObjectCriteriaNode.js +1 -1
  42. package/query/QueryBuilder.d.ts +42 -1
  43. package/query/QueryBuilder.js +78 -7
  44. package/schema/DatabaseSchema.d.ts +29 -2
  45. package/schema/DatabaseSchema.js +145 -4
  46. package/schema/DatabaseTable.d.ts +20 -1
  47. package/schema/DatabaseTable.js +182 -31
  48. package/schema/SchemaComparator.d.ts +19 -0
  49. package/schema/SchemaComparator.js +250 -1
  50. package/schema/SchemaHelper.d.ts +77 -1
  51. package/schema/SchemaHelper.js +297 -25
  52. package/schema/SqlSchemaGenerator.d.ts +2 -2
  53. package/schema/SqlSchemaGenerator.js +47 -10
  54. package/schema/partitioning.d.ts +13 -0
  55. package/schema/partitioning.js +326 -0
  56. package/typings.d.ts +72 -5
@@ -66,18 +66,23 @@ export class AbstractSqlPlatform extends Platform {
66
66
  }
67
67
  getSearchJsonPropertyKey(path, type, aliased, value) {
68
68
  const [a, ...b] = path;
69
+ const jsonPath = this.quoteValue(`$.${b.map(this.quoteJsonKey).join('.')}`);
69
70
  if (aliased) {
70
- return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
71
+ return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, ${jsonPath})`);
71
72
  }
72
- return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
73
+ return raw(`json_extract(${this.quoteIdentifier(a)}, ${jsonPath})`);
73
74
  }
74
75
  /**
75
76
  * Quotes a key for use inside a JSON path expression (e.g. `$.key`).
76
- * Simple alphanumeric keys are left unquoted; others are wrapped in double quotes.
77
+ * Simple alphanumeric keys are left unquoted; others are wrapped in double quotes
78
+ * with embedded `\` and `"` escaped per the JSON path string syntax.
77
79
  * @internal
78
80
  */
79
81
  quoteJsonKey(key) {
80
- return /^[a-z]\w*$/i.exec(key) ? key : `"${key}"`;
82
+ if (/^[a-z]\w*$/i.test(key)) {
83
+ return key;
84
+ }
85
+ return `"${key.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
81
86
  }
82
87
  getJsonIndexDefinition(index) {
83
88
  return index.columnNames.map(column => {
@@ -123,12 +128,25 @@ export class AbstractSqlPlatform extends Platform {
123
128
  this.validateCollationName(collation);
124
129
  return this.quoteIdentifier(collation);
125
130
  }
126
- /** @internal */
131
+ /**
132
+ * PG ICU locale names include hyphens (`en-US-x-icu`) and libc locales include dots (`en_US.utf8`),
133
+ * so word-chars alone would reject valid real-world collations.
134
+ * @internal
135
+ */
127
136
  validateCollationName(collation) {
128
- if (!/^[\w]+$/.test(collation)) {
129
- throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
137
+ if (!/^[\w\-.]+$/.test(collation)) {
138
+ throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters, hyphens, and dots.`);
130
139
  }
131
140
  }
141
+ /**
142
+ * Whether collation names compare case-insensitively in this dialect. MySQL/MariaDB, MSSQL, and
143
+ * SQLite use case-insensitive collation identifiers; PostgreSQL stores them as case-sensitive
144
+ * names in `pg_collation` (e.g. `en-US-x-icu` is distinct from `EN-US-X-ICU`).
145
+ * @internal
146
+ */
147
+ caseInsensitiveCollationNames() {
148
+ return true;
149
+ }
132
150
  /** @internal */
133
151
  validateJsonPropertyName(name) {
134
152
  if (!AbstractSqlPlatform.#JSON_PROPERTY_NAME_RE.test(name)) {
@@ -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;
@@ -1,3 +1,4 @@
1
+ import { QueryHelper, } from '@mikro-orm/core';
1
2
  class InsertStatement {
2
3
  order;
3
4
  #keys;
@@ -43,12 +44,14 @@ export class PivotCollectionPersister {
43
44
  #ctx;
44
45
  #schema;
45
46
  #loggerContext;
46
- constructor(meta, driver, ctx, schema, loggerContext) {
47
+ #abort;
48
+ constructor(meta, driver, ctx, schema, loggerContext, abort) {
47
49
  this.#meta = meta;
48
50
  this.#driver = driver;
49
51
  this.#ctx = ctx;
50
52
  this.#schema = schema;
51
53
  this.#loggerContext = loggerContext;
54
+ this.#abort = abort;
52
55
  this.#batchSize = this.#driver.config.get('batchSize');
53
56
  }
54
57
  enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
@@ -106,6 +109,16 @@ export class PivotCollectionPersister {
106
109
  buildPivotKeysAndData(prop, fks, pks, deleteAll = false) {
107
110
  let data;
108
111
  let keys;
112
+ // Union-target polymorphic M:N prepends the per-row discriminator to `fks` in syncCollections;
113
+ // Rails-style polymorphic M:N uses a static discriminatorValue on the prop. Normalize to a single
114
+ // "current row's discriminator" value so the prepend block below handles both cases uniformly.
115
+ let rowDiscriminator;
116
+ if (QueryHelper.isUnionTargetPolymorphic(prop) && !deleteAll && fks.length > 0) {
117
+ [rowDiscriminator, ...fks] = fks;
118
+ }
119
+ else if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
120
+ rowDiscriminator = prop.discriminatorValue;
121
+ }
109
122
  if (deleteAll) {
110
123
  data = pks;
111
124
  keys = prop.joinColumns;
@@ -116,8 +129,8 @@ export class PivotCollectionPersister {
116
129
  ? [...prop.inverseJoinColumns, ...prop.joinColumns]
117
130
  : [...prop.joinColumns, ...prop.inverseJoinColumns];
118
131
  }
119
- if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
120
- data = [prop.discriminatorValue, ...data];
132
+ if (rowDiscriminator !== undefined) {
133
+ data = [rowDiscriminator, ...data];
121
134
  keys = [prop.discriminatorColumn, ...keys];
122
135
  }
123
136
  return { data, keys };
@@ -142,6 +155,7 @@ export class PivotCollectionPersister {
142
155
  ctx: this.#ctx,
143
156
  schema: this.#schema,
144
157
  loggerContext: this.#loggerContext,
158
+ ...this.#abort,
145
159
  });
146
160
  }
147
161
  }
@@ -155,6 +169,7 @@ export class PivotCollectionPersister {
155
169
  convertCustomTypes: false,
156
170
  processCollections: false,
157
171
  loggerContext: this.#loggerContext,
172
+ ...this.#abort,
158
173
  });
159
174
  }
160
175
  }
@@ -170,6 +185,7 @@ export class PivotCollectionPersister {
170
185
  upsert: true,
171
186
  onConflictAction: 'ignore',
172
187
  loggerContext: this.#loggerContext,
188
+ ...this.#abort,
173
189
  });
174
190
  }
175
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 EntityData, type EntityName, type EntityRepository, type GetRepository, type QueryResult, type FilterQuery, type LoggingOptions, 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'`). */
@@ -24,14 +31,50 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
24
31
  */
25
32
  qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias>;
26
33
  /**
27
- * Returns configured Kysely instance.
34
+ * Returns a configured Kysely instance bound to this EntityManager.
35
+ *
36
+ * When the EntityManager is inside a transaction (e.g. within `em.transactional(...)`, or after
37
+ * `em.begin()`), the returned Kysely instance automatically uses the transaction context, so any
38
+ * queries executed via Kysely's own `.execute()` / `.executeTakeFirst*()` participate in the
39
+ * current transaction.
40
+ *
41
+ * If you need a Kysely instance that is **not** bound to the current transaction (e.g. to perform
42
+ * a side query against the pool while inside a transactional block), fork the EntityManager first:
43
+ *
44
+ * ```ts
45
+ * await em.transactional(async em => {
46
+ * // bound to the current transaction
47
+ * await em.getKysely().selectFrom('user').selectAll().execute();
48
+ *
49
+ * // bound to the pool, runs outside the transaction
50
+ * await em.fork().getKysely().selectFrom('audit_log').selectAll().execute();
51
+ * });
52
+ * ```
53
+ *
54
+ * The `options.type` (`'read'` / `'write'`) is only honored outside a transaction — inside a
55
+ * transaction the connection is already pinned.
28
56
  */
29
57
  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. */
58
+ /**
59
+ * Executes a raw SQL query, using the current transaction context if available. The fork-level
60
+ * `signal` / `inflightQueryAbortStrategy` (set via `em.fork({ signal })`) is applied automatically.
61
+ * For per-call cancellation use the options-bag overload below.
62
+ */
31
63
  execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
64
+ /**
65
+ * Executes a raw SQL query with an options bag carrying `method`, `loggerContext`, `signal`
66
+ * and `inflightQueryAbortStrategy`. Per-call `signal` / `inflightQueryAbortStrategy` override
67
+ * the fork-level defaults set via `em.fork({ signal })`. The current transaction context is
68
+ * applied automatically.
69
+ */
70
+ execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params: any[], options: EmExecuteOptions): Promise<T>;
71
+ /**
72
+ * @inheritDoc
73
+ */
74
+ countBy<Entity extends object>(entityName: EntityName<Entity>, groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
32
75
  getRepository<T extends object, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U>;
33
76
  protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
34
77
  }
35
- type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? Extract<NonNullable<TEntityManager['~entities']>[number], EntitySchemaWithMeta> : never;
36
- type AllEntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? NonNullable<TEntityManager['~entities']>[number] : never;
78
+ type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends readonly any[] ? Extract<NonNullable<TEntityManager['~entities']>[number], EntitySchemaWithMeta> : never;
79
+ type AllEntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends readonly any[] ? NonNullable<TEntityManager['~entities']>[number] : never;
37
80
  export {};
@@ -1,4 +1,4 @@
1
- import { EntityManager, } from '@mikro-orm/core';
1
+ import { EntityManager, raw, Utils, } from '@mikro-orm/core';
2
2
  import { MikroKyselyPlugin } from './plugin/index.js';
3
3
  /**
4
4
  * @inheritDoc
@@ -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()`
@@ -18,10 +20,33 @@ export class SqlEntityManager extends EntityManager {
18
20
  return this.createQueryBuilder(entityName, alias, type, loggerContext);
19
21
  }
20
22
  /**
21
- * Returns configured Kysely instance.
23
+ * Returns a configured Kysely instance bound to this EntityManager.
24
+ *
25
+ * When the EntityManager is inside a transaction (e.g. within `em.transactional(...)`, or after
26
+ * `em.begin()`), the returned Kysely instance automatically uses the transaction context, so any
27
+ * queries executed via Kysely's own `.execute()` / `.executeTakeFirst*()` participate in the
28
+ * current transaction.
29
+ *
30
+ * If you need a Kysely instance that is **not** bound to the current transaction (e.g. to perform
31
+ * a side query against the pool while inside a transactional block), fork the EntityManager first:
32
+ *
33
+ * ```ts
34
+ * await em.transactional(async em => {
35
+ * // bound to the current transaction
36
+ * await em.getKysely().selectFrom('user').selectAll().execute();
37
+ *
38
+ * // bound to the pool, runs outside the transaction
39
+ * await em.fork().getKysely().selectFrom('audit_log').selectAll().execute();
40
+ * });
41
+ * ```
42
+ *
43
+ * The `options.type` (`'read'` / `'write'`) is only honored outside a transaction — inside a
44
+ * transaction the connection is already pinned.
22
45
  */
23
46
  getKysely(options = {}) {
24
- let kysely = this.getConnection(options.type).getClient();
47
+ const context = this.getContext(false);
48
+ const ctx = context.getTransactionContext();
49
+ let kysely = ctx ?? this.getConnection(options.type).getClient();
25
50
  if (options.columnNamingStrategy != null ||
26
51
  options.tableNamingStrategy != null ||
27
52
  options.processOnCreateHooks != null ||
@@ -31,9 +56,54 @@ export class SqlEntityManager extends EntityManager {
31
56
  }
32
57
  return kysely;
33
58
  }
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);
59
+ async execute(query, params = [], methodOrOptions = 'all', loggerContext) {
60
+ const opts = typeof methodOrOptions === 'string' ? { method: methodOrOptions, loggerContext } : methodOrOptions;
61
+ const context = this.getContext(false);
62
+ // Per-field fallback to fork-level abort, matching `EntityManager.prepareOptions` semantics.
63
+ const fork = context.getAbortOptions();
64
+ const hasAbort = opts.signal != null || opts.inflightQueryAbortStrategy != null || fork != null;
65
+ // Connection layer carries abort piggy-backed on `loggerContext`; merge only when needed.
66
+ const merged = hasAbort || opts.loggerContext ? { ...opts.loggerContext } : undefined;
67
+ if (merged && hasAbort) {
68
+ merged.signal = opts.signal ?? fork?.signal;
69
+ merged.inflightQueryAbortStrategy = opts.inflightQueryAbortStrategy ?? fork?.inflightQueryAbortStrategy;
70
+ }
71
+ return this.getDriver().execute(query, params, opts.method ?? 'all', context.getTransactionContext(), merged);
72
+ }
73
+ /**
74
+ * @inheritDoc
75
+ */
76
+ async countBy(entityName, groupBy, options = {}) {
77
+ const em = this.getContext(false);
78
+ options = { ...options };
79
+ em.prepareOptions(options);
80
+ const meta = em.getMetadata().find(entityName);
81
+ const fields = Utils.asArray(groupBy);
82
+ const { where: rawWhere, ...countOptions } = options;
83
+ await em.tryFlush(entityName, options);
84
+ const where = await em.processWhere(entityName, rawWhere ?? {}, options, 'read');
85
+ const qb = em.createQueryBuilder(meta.class);
86
+ qb
87
+ .select([...fields, raw('count(*) as cnt')])
88
+ .where(where)
89
+ .groupBy(fields);
90
+ if (countOptions.having) {
91
+ qb.having(countOptions.having);
92
+ }
93
+ if (countOptions.schema) {
94
+ qb.withSchema(countOptions.schema);
95
+ }
96
+ const rows = await qb.execute('all', { mapResults: false });
97
+ const results = {};
98
+ for (const row of rows) {
99
+ const keyParts = fields.map(f => {
100
+ const col = meta.properties[f]?.fieldNames?.[0] ?? f;
101
+ return String(row[col]);
102
+ });
103
+ const key = keyParts.join(Utils.PK_SEPARATOR);
104
+ results[key] = +row.cnt;
105
+ }
106
+ return results;
37
107
  }
38
108
  getRepository(entityName) {
39
109
  return super.getRepository(entityName);
@@ -0,0 +1,23 @@
1
+ import { type AnyEntity, type EntityClass, type EntityManager, type EntityManagerType, type EntitySchema, type IDatabaseDriver, MikroORM, type Options } from '@mikro-orm/core';
2
+ import type { AbstractSqlDriver } from './AbstractSqlDriver.js';
3
+ import type { SqlEntityManager } from './SqlEntityManager.js';
4
+ /** Configuration options shared by all SQL drivers. */
5
+ export type SqlOptions<D extends AbstractSqlDriver = AbstractSqlDriver, EM extends SqlEntityManager<D> = SqlEntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<D, EM, Entities>>;
6
+ /**
7
+ * Creates a type-safe configuration object for any SQL driver. The driver class
8
+ * must be passed via `options.driver` (e.g. `SqliteDriver`, `MySqlDriver`, …).
9
+ */
10
+ export declare function defineSqlConfig<D extends AbstractSqlDriver = AbstractSqlDriver, EM extends SqlEntityManager<D> = SqlEntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Partial<Options<D, EM, Entities>>;
11
+ /**
12
+ * Generic entry point for SQL drivers. Use this when consuming `@mikro-orm/sql`
13
+ * directly with a Kysely dialect; for the bundled driver packages prefer
14
+ * `@mikro-orm/sqlite`, `@mikro-orm/postgresql`, etc.
15
+ *
16
+ * @inheritDoc
17
+ */
18
+ export declare class SqlMikroORM<D extends AbstractSqlDriver = AbstractSqlDriver, EM extends SqlEntityManager<D> = SqlEntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends MikroORM<D, EM, Entities> {
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static init<D extends IDatabaseDriver = AbstractSqlDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
23
+ }
package/SqlMikroORM.js ADDED
@@ -0,0 +1,23 @@
1
+ import { defineConfig, MikroORM, } from '@mikro-orm/core';
2
+ /**
3
+ * Creates a type-safe configuration object for any SQL driver. The driver class
4
+ * must be passed via `options.driver` (e.g. `SqliteDriver`, `MySqlDriver`, …).
5
+ */
6
+ export function defineSqlConfig(options) {
7
+ return defineConfig(options);
8
+ }
9
+ /**
10
+ * Generic entry point for SQL drivers. Use this when consuming `@mikro-orm/sql`
11
+ * directly with a Kysely dialect; for the bundled driver packages prefer
12
+ * `@mikro-orm/sqlite`, `@mikro-orm/postgresql`, etc.
13
+ *
14
+ * @inheritDoc
15
+ */
16
+ export class SqlMikroORM extends MikroORM {
17
+ /**
18
+ * @inheritDoc
19
+ */
20
+ static async init(options) {
21
+ return super.init(options);
22
+ }
23
+ }
@@ -17,6 +17,7 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
17
17
  supportsMultiColumnCountDistinct(): boolean;
18
18
  /** @internal */
19
19
  createNativeQueryBuilder(): MySqlNativeQueryBuilder;
20
+ formatIndexHint(indexNames: string[]): string;
20
21
  getDefaultCharset(): string;
21
22
  init(orm: MikroORM): void;
22
23
  getBeginTransactionSQL(options?: {
@@ -34,11 +35,9 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
34
35
  getDefaultMappedType(type: string): Type<unknown>;
35
36
  isNumericColumn(mappedType: Type<unknown>): boolean;
36
37
  supportsUnsigned(): boolean;
37
- /**
38
- * Returns the default name of index for the given columns
39
- * cannot go past 64 character length for identifiers in MySQL
40
- */
41
- getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
38
+ /** MySQL/MariaDB identifier limit. */
39
+ getMaxIdentifierLength(): number;
40
+ getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence' | 'check'): string;
42
41
  getDefaultPrimaryName(tableName: string, columns: string[]): string;
43
42
  supportsCreatingFullTextIndex(): boolean;
44
43
  getFullTextWhereClause(): string;
@@ -1,4 +1,4 @@
1
- import { Utils, QueryOrder, DecimalType, DoubleType, } from '@mikro-orm/core';
1
+ import { QueryOrder, DecimalType, DoubleType, } from '@mikro-orm/core';
2
2
  import { MySqlSchemaHelper } from './MySqlSchemaHelper.js';
3
3
  import { MySqlExceptionConverter } from './MySqlExceptionConverter.js';
4
4
  import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
@@ -25,6 +25,9 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
25
25
  createNativeQueryBuilder() {
26
26
  return new MySqlNativeQueryBuilder(this);
27
27
  }
28
+ formatIndexHint(indexNames) {
29
+ return `use index(${indexNames.join(', ')})`;
30
+ }
28
31
  getDefaultCharset() {
29
32
  return 'utf8mb4';
30
33
  }
@@ -83,19 +86,15 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
83
86
  supportsUnsigned() {
84
87
  return true;
85
88
  }
86
- /**
87
- * Returns the default name of index for the given columns
88
- * cannot go past 64 character length for identifiers in MySQL
89
- */
89
+ /** MySQL/MariaDB identifier limit. */
90
+ getMaxIdentifierLength() {
91
+ return 64;
92
+ }
90
93
  getIndexName(tableName, columns, type) {
91
94
  if (type === 'primary') {
92
95
  return this.getDefaultPrimaryName(tableName, columns);
93
96
  }
94
- const indexName = super.getIndexName(tableName, columns, type);
95
- if (indexName.length > 64) {
96
- return `${indexName.substring(0, 56 - type.length)}_${Utils.hash(indexName, 5)}_${type}`;
97
- }
98
- return indexName;
97
+ return super.getIndexName(tableName, columns, type);
99
98
  }
100
99
  getDefaultPrimaryName(tableName, columns) {
101
100
  return 'PRIMARY'; // https://dev.mysql.com/doc/refman/8.0/en/create-table.html#create-table-indexes-keys
@@ -1,5 +1,5 @@
1
1
  import { type Dictionary, type Transaction, type Type } from '@mikro-orm/core';
2
- import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../../typings.js';
2
+ import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference, SqlTriggerDef, SqlRoutineDef } from '../../typings.js';
3
3
  import type { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
4
4
  import { SchemaHelper } from '../../schema/SchemaHelper.js';
5
5
  import type { DatabaseSchema } from '../../schema/DatabaseSchema.js';
@@ -11,7 +11,12 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
11
11
  'current_timestamp(?)': string[];
12
12
  '0': string[];
13
13
  };
14
+ private static readonly PARTIAL_INDEX_RE;
14
15
  getSchemaBeginning(charset: string, disableForeignKeys?: boolean): string;
16
+ getSetSchemaSQL(schema: string): string;
17
+ getResetSchemaSQL(defaultSchema: string): string;
18
+ supportsMigrationSchema(): boolean;
19
+ tableExists(connection: AbstractSqlConnection, tableName: string, schemaName: string | undefined, ctx?: Transaction): Promise<boolean>;
15
20
  disableForeignKeysSQL(): string;
16
21
  enableForeignKeysSQL(): string;
17
22
  finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
@@ -22,8 +27,10 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
22
27
  getAllIndexes(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<IndexDef[]>>;
23
28
  getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
24
29
  /**
25
- * Build the column list for a MySQL index, with MySQL-specific handling for collation.
26
- * MySQL requires collation to be specified as an expression: (column_name COLLATE collation_name)
30
+ * Build the column list for a MySQL index. MySQL requires collation via an expression:
31
+ * `(column COLLATE collation_name)`. Partial indexes (`where`) are emulated via functional
32
+ * indexes — requires MySQL 8.0.13+. MariaDB does not support inline functional indexes
33
+ * and overrides to throw at a higher level.
27
34
  */
28
35
  protected getIndexColumns(index: IndexDef): string;
29
36
  /**
@@ -32,6 +39,15 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
32
39
  protected appendMySqlIndexSuffix(sql: string, index: IndexDef): string;
33
40
  getAllColumns(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Column[]>>;
34
41
  getAllChecks(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
42
+ /** Generates SQL to create MySQL triggers. MySQL requires one trigger per event. */
43
+ createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
44
+ createRoutine(routine: SqlRoutineDef): string;
45
+ dropRoutine(routine: SqlRoutineDef): string;
46
+ getAllRoutines(connection: AbstractSqlConnection): Promise<SqlRoutineDef[]>;
47
+ private getAllRoutineParams;
48
+ private formatDataAccess;
49
+ private parseDataAccess;
50
+ getAllTriggers(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<SqlTriggerDef[]>>;
35
51
  getAllForeignKeys(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Dictionary<ForeignKey>>>;
36
52
  getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
37
53
  getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column): string;