@mikro-orm/sql 7.0.0-dev.99 → 7.0.0-rc.1

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 (64) hide show
  1. package/AbstractSqlConnection.d.ts +2 -4
  2. package/AbstractSqlConnection.js +3 -7
  3. package/AbstractSqlDriver.d.ts +89 -23
  4. package/AbstractSqlDriver.js +630 -197
  5. package/AbstractSqlPlatform.d.ts +11 -5
  6. package/AbstractSqlPlatform.js +18 -5
  7. package/PivotCollectionPersister.d.ts +5 -0
  8. package/PivotCollectionPersister.js +30 -12
  9. package/SqlEntityManager.d.ts +2 -2
  10. package/dialects/mysql/{MySqlPlatform.d.ts → BaseMySqlPlatform.d.ts} +4 -3
  11. package/dialects/mysql/{MySqlPlatform.js → BaseMySqlPlatform.js} +9 -4
  12. package/dialects/mysql/MySqlSchemaHelper.d.ts +12 -1
  13. package/dialects/mysql/MySqlSchemaHelper.js +97 -6
  14. package/dialects/mysql/index.d.ts +1 -2
  15. package/dialects/mysql/index.js +1 -2
  16. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +106 -0
  17. package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -0
  18. package/dialects/postgresql/FullTextType.d.ts +14 -0
  19. package/dialects/postgresql/FullTextType.js +59 -0
  20. package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +8 -0
  21. package/dialects/postgresql/PostgreSqlExceptionConverter.js +47 -0
  22. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +90 -0
  23. package/dialects/postgresql/PostgreSqlSchemaHelper.js +732 -0
  24. package/dialects/postgresql/index.d.ts +3 -0
  25. package/dialects/postgresql/index.js +3 -0
  26. package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -0
  27. package/dialects/sqlite/BaseSqliteConnection.js +13 -0
  28. package/dialects/sqlite/BaseSqlitePlatform.d.ts +6 -0
  29. package/dialects/sqlite/BaseSqlitePlatform.js +12 -0
  30. package/dialects/sqlite/SqliteSchemaHelper.d.ts +25 -0
  31. package/dialects/sqlite/SqliteSchemaHelper.js +145 -19
  32. package/dialects/sqlite/index.d.ts +0 -1
  33. package/dialects/sqlite/index.js +0 -1
  34. package/package.json +5 -6
  35. package/plugin/transformer.d.ts +1 -1
  36. package/plugin/transformer.js +1 -1
  37. package/query/CriteriaNode.d.ts +9 -5
  38. package/query/CriteriaNode.js +16 -15
  39. package/query/CriteriaNodeFactory.d.ts +6 -6
  40. package/query/CriteriaNodeFactory.js +33 -31
  41. package/query/NativeQueryBuilder.d.ts +3 -2
  42. package/query/NativeQueryBuilder.js +1 -2
  43. package/query/ObjectCriteriaNode.js +51 -36
  44. package/query/QueryBuilder.d.ts +569 -79
  45. package/query/QueryBuilder.js +614 -171
  46. package/query/QueryBuilderHelper.d.ts +24 -16
  47. package/query/QueryBuilderHelper.js +167 -78
  48. package/query/ScalarCriteriaNode.js +2 -2
  49. package/query/raw.d.ts +11 -3
  50. package/query/raw.js +1 -2
  51. package/schema/DatabaseSchema.d.ts +15 -2
  52. package/schema/DatabaseSchema.js +143 -15
  53. package/schema/DatabaseTable.d.ts +12 -0
  54. package/schema/DatabaseTable.js +91 -31
  55. package/schema/SchemaComparator.d.ts +8 -0
  56. package/schema/SchemaComparator.js +127 -3
  57. package/schema/SchemaHelper.d.ts +26 -3
  58. package/schema/SchemaHelper.js +98 -11
  59. package/schema/SqlSchemaGenerator.d.ts +10 -0
  60. package/schema/SqlSchemaGenerator.js +137 -9
  61. package/tsconfig.build.tsbuildinfo +1 -0
  62. package/typings.d.ts +78 -38
  63. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +0 -1
  64. package/dialects/postgresql/PostgreSqlTableCompiler.js +0 -1
@@ -1,4 +1,4 @@
1
- import { type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type IsolationLevel, type MikroORM, Platform } from '@mikro-orm/core';
1
+ import { type RawQueryFragment, type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type IsolationLevel, type MikroORM, Platform } from '@mikro-orm/core';
2
2
  import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
3
3
  import { type SchemaHelper } from './schema/SchemaHelper.js';
4
4
  import type { IndexDef } from './typings.js';
@@ -24,9 +24,8 @@ export declare abstract class AbstractSqlPlatform extends Platform {
24
24
  getRollbackToSavepointSQL(savepointName: string): string;
25
25
  getReleaseSavepointSQL(savepointName: string): string;
26
26
  quoteValue(value: any): string;
27
- escape(value: any): string;
28
- getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string;
29
- getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string;
27
+ getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | RawQueryFragment;
28
+ getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | RawQueryFragment;
30
29
  getJsonIndexDefinition(index: IndexDef): string[];
31
30
  supportsSchemas(): boolean;
32
31
  /** @inheritDoc */
@@ -34,5 +33,12 @@ export declare abstract class AbstractSqlPlatform extends Platform {
34
33
  /**
35
34
  * @internal
36
35
  */
37
- getOrderByExpression(column: string, direction: string): string[];
36
+ getOrderByExpression(column: string, direction: string, collation?: string): string[];
37
+ /**
38
+ * Quotes a collation name for use in COLLATE clauses.
39
+ * @internal
40
+ */
41
+ quoteCollation(collation: string): string;
42
+ /** @internal */
43
+ protected validateCollationName(collation: string): void;
38
44
  }
@@ -1,4 +1,3 @@
1
- import SqlString from 'sqlstring';
2
1
  import { isRaw, JsonProperty, Platform, raw, Utils, } from '@mikro-orm/core';
3
2
  import { SqlEntityRepository } from './SqlEntityRepository.js';
4
3
  import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
@@ -60,9 +59,6 @@ export class AbstractSqlPlatform extends Platform {
60
59
  }
61
60
  return this.escape(value);
62
61
  }
63
- escape(value) {
64
- return SqlString.escape(value, true, this.timezone);
65
- }
66
62
  getSearchJsonPropertySQL(path, type, aliased) {
67
63
  return this.getSearchJsonPropertyKey(path.split('->'), type, aliased);
68
64
  }
@@ -98,7 +94,24 @@ export class AbstractSqlPlatform extends Platform {
98
94
  /**
99
95
  * @internal
100
96
  */
101
- getOrderByExpression(column, direction) {
97
+ getOrderByExpression(column, direction, collation) {
98
+ if (collation) {
99
+ return [`${column} collate ${this.quoteCollation(collation)} ${direction.toLowerCase()}`];
100
+ }
102
101
  return [`${column} ${direction.toLowerCase()}`];
103
102
  }
103
+ /**
104
+ * Quotes a collation name for use in COLLATE clauses.
105
+ * @internal
106
+ */
107
+ quoteCollation(collation) {
108
+ this.validateCollationName(collation);
109
+ return this.quoteIdentifier(collation);
110
+ }
111
+ /** @internal */
112
+ validateCollationName(collation) {
113
+ if (!/^[\w]+$/.test(collation)) {
114
+ throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
115
+ }
116
+ }
104
117
  }
@@ -17,6 +17,11 @@ export declare class PivotCollectionPersister<Entity extends object> {
17
17
  private enqueueUpsert;
18
18
  private createInsertStatement;
19
19
  private enqueueDelete;
20
+ /**
21
+ * Build the keys and data arrays for pivot table operations.
22
+ * Handles polymorphic M:N by prepending the discriminator column/value.
23
+ */
24
+ private buildPivotKeysAndData;
20
25
  private collectStatements;
21
26
  execute(): Promise<void>;
22
27
  }
@@ -83,27 +83,45 @@ export class PivotCollectionPersister {
83
83
  }
84
84
  }
85
85
  createInsertStatement(prop, fks, pks) {
86
- const data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
87
- const keys = prop.owner
88
- ? [...prop.inverseJoinColumns, ...prop.joinColumns]
89
- : [...prop.joinColumns, ...prop.inverseJoinColumns];
86
+ const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
90
87
  return new InsertStatement(keys, data, this.order++);
91
88
  }
92
89
  enqueueDelete(prop, deleteDiff, pks) {
93
90
  if (deleteDiff === true) {
94
- const statement = new DeleteStatement(prop.joinColumns, pks);
91
+ const { data, keys } = this.buildPivotKeysAndData(prop, [], pks, true);
92
+ const statement = new DeleteStatement(keys, data);
95
93
  this.deletes.set(statement.getHash(), statement);
96
94
  return;
97
95
  }
98
96
  for (const fks of deleteDiff) {
99
- const data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
100
- const keys = prop.owner
101
- ? [...prop.inverseJoinColumns, ...prop.joinColumns]
102
- : [...prop.joinColumns, ...prop.inverseJoinColumns];
97
+ const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
103
98
  const statement = new DeleteStatement(keys, data);
104
99
  this.deletes.set(statement.getHash(), statement);
105
100
  }
106
101
  }
102
+ /**
103
+ * Build the keys and data arrays for pivot table operations.
104
+ * Handles polymorphic M:N by prepending the discriminator column/value.
105
+ */
106
+ buildPivotKeysAndData(prop, fks, pks, deleteAll = false) {
107
+ let data;
108
+ let keys;
109
+ if (deleteAll) {
110
+ data = pks;
111
+ keys = prop.joinColumns;
112
+ }
113
+ else {
114
+ data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
115
+ keys = prop.owner
116
+ ? [...prop.inverseJoinColumns, ...prop.joinColumns]
117
+ : [...prop.joinColumns, ...prop.inverseJoinColumns];
118
+ }
119
+ if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
120
+ data = [prop.discriminatorValue, ...data];
121
+ keys = [prop.discriminatorColumn, ...keys];
122
+ }
123
+ return { data, keys };
124
+ }
107
125
  collectStatements(statements) {
108
126
  const items = [];
109
127
  for (const statement of statements.values()) {
@@ -120,7 +138,7 @@ export class PivotCollectionPersister {
120
138
  for (const item of chunk) {
121
139
  cond.$or.push(item.getCondition());
122
140
  }
123
- await this.driver.nativeDelete(this.meta.className, cond, {
141
+ await this.driver.nativeDelete(this.meta.class, cond, {
124
142
  ctx: this.ctx,
125
143
  schema: this.schema,
126
144
  loggerContext: this.loggerContext,
@@ -131,7 +149,7 @@ export class PivotCollectionPersister {
131
149
  const filtered = this.collectStatements(this.inserts);
132
150
  for (let i = 0; i < filtered.length; i += this.batchSize) {
133
151
  const chunk = filtered.slice(i, i + this.batchSize);
134
- await this.driver.nativeInsertMany(this.meta.className, chunk, {
152
+ await this.driver.nativeInsertMany(this.meta.class, chunk, {
135
153
  ctx: this.ctx,
136
154
  schema: this.schema,
137
155
  convertCustomTypes: false,
@@ -144,7 +162,7 @@ export class PivotCollectionPersister {
144
162
  const filtered = this.collectStatements(this.upserts);
145
163
  for (let i = 0; i < filtered.length; i += this.batchSize) {
146
164
  const chunk = filtered.slice(i, i + this.batchSize);
147
- await this.driver.nativeUpdateMany(this.meta.className, [], chunk, {
165
+ await this.driver.nativeUpdateMany(this.meta.class, [], chunk, {
148
166
  ctx: this.ctx,
149
167
  schema: this.schema,
150
168
  convertCustomTypes: false,
@@ -20,14 +20,14 @@ export declare class SqlEntityManager<Driver extends AbstractSqlDriver = Abstrac
20
20
  /**
21
21
  * Shortcut for `createQueryBuilder()`
22
22
  */
23
- qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias, never, never>;
23
+ qb<Entity extends object, RootAlias extends string = never>(entityName: EntityName<Entity>, alias?: RootAlias, type?: ConnectionType, loggerContext?: LoggingOptions): QueryBuilder<Entity, RootAlias, never, never, never, "*">;
24
24
  /**
25
25
  * Returns configured Kysely instance.
26
26
  */
27
27
  getKysely<TDB = undefined, TOptions extends GetKyselyOptions = GetKyselyOptions>(options?: TOptions): Kysely<TDB extends undefined ? InferKyselyDB<EntitiesFromManager<this>, TOptions> : TDB>;
28
28
  execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: any[], method?: 'all' | 'get' | 'run', loggerContext?: LoggingOptions): Promise<T>;
29
29
  getRepository<T extends object, U extends EntityRepository<T> = SqlEntityRepository<T>>(entityName: EntityName<T>): GetRepository<T, U>;
30
- protected applyDiscriminatorCondition<Entity extends object>(entityName: string, where: FilterQuery<Entity>): FilterQuery<Entity>;
30
+ protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
31
31
  }
32
32
  type EntitiesFromManager<TEntityManager extends EntityManager<any>> = NonNullable<TEntityManager['~entities']> extends any[] ? (Extract<NonNullable<TEntityManager['~entities']>[number], EntitySchemaWithMeta>) : never;
33
33
  export {};
@@ -1,10 +1,10 @@
1
- import { type SimpleColumnMeta, type Type, type TransformContext, type IsolationLevel } from '@mikro-orm/core';
1
+ import { type SimpleColumnMeta, type Type, type TransformContext, type MikroORM, type IsolationLevel } 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';
5
5
  import type { IndexDef } from '../../typings.js';
6
6
  import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js';
7
- export declare class MySqlPlatform extends AbstractSqlPlatform {
7
+ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
8
8
  protected readonly schemaHelper: MySqlSchemaHelper;
9
9
  protected readonly exceptionConverter: MySqlExceptionConverter;
10
10
  protected readonly ORDER_BY_NULLS_TRANSLATE: {
@@ -16,6 +16,7 @@ export declare class MySqlPlatform extends AbstractSqlPlatform {
16
16
  /** @internal */
17
17
  createNativeQueryBuilder(): MySqlNativeQueryBuilder;
18
18
  getDefaultCharset(): string;
19
+ init(orm: MikroORM): void;
19
20
  getBeginTransactionSQL(options?: {
20
21
  isolationLevel?: IsolationLevel;
21
22
  readOnly?: boolean;
@@ -40,6 +41,6 @@ export declare class MySqlPlatform extends AbstractSqlPlatform {
40
41
  supportsCreatingFullTextIndex(): boolean;
41
42
  getFullTextWhereClause(): string;
42
43
  getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
43
- getOrderByExpression(column: string, direction: string): string[];
44
+ getOrderByExpression(column: string, direction: string, collation?: string): string[];
44
45
  getDefaultClientUrl(): string;
45
46
  }
@@ -3,7 +3,7 @@ import { MySqlSchemaHelper } from './MySqlSchemaHelper.js';
3
3
  import { MySqlExceptionConverter } from './MySqlExceptionConverter.js';
4
4
  import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
5
5
  import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js';
6
- export class MySqlPlatform extends AbstractSqlPlatform {
6
+ export class BaseMySqlPlatform extends AbstractSqlPlatform {
7
7
  schemaHelper = new MySqlSchemaHelper(this);
8
8
  exceptionConverter = new MySqlExceptionConverter();
9
9
  ORDER_BY_NULLS_TRANSLATE = {
@@ -19,6 +19,10 @@ export class MySqlPlatform extends AbstractSqlPlatform {
19
19
  getDefaultCharset() {
20
20
  return 'utf8mb4';
21
21
  }
22
+ init(orm) {
23
+ super.init(orm);
24
+ orm.config.get('schemaGenerator').disableForeignKeysForClear ??= true;
25
+ }
22
26
  getBeginTransactionSQL(options) {
23
27
  if (options?.isolationLevel || options?.readOnly) {
24
28
  const parts = [];
@@ -101,13 +105,14 @@ export class MySqlPlatform extends AbstractSqlPlatform {
101
105
  const quotedIndexName = this.quoteIdentifier(indexName);
102
106
  return `alter table ${quotedTableName} add fulltext index ${quotedIndexName}(${quotedColumnNames.join(',')})`;
103
107
  }
104
- getOrderByExpression(column, direction) {
108
+ getOrderByExpression(column, direction, collation) {
105
109
  const ret = [];
106
110
  const dir = direction.toLowerCase();
111
+ const col = collation ? `${column} collate ${this.quoteCollation(collation)}` : column;
107
112
  if (dir in this.ORDER_BY_NULLS_TRANSLATE) {
108
- ret.push(`${column} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`);
113
+ ret.push(`${col} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`);
109
114
  }
110
- ret.push(`${column} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`);
115
+ ret.push(`${col} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`);
111
116
  return ret;
112
117
  }
113
118
  getDefaultClientUrl() {
@@ -1,5 +1,5 @@
1
1
  import { type Dictionary, type Type } from '@mikro-orm/core';
2
- import type { CheckDef, Column, IndexDef, TableDifference, Table, ForeignKey } from '../../typings.js';
2
+ import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } 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';
@@ -16,9 +16,20 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
16
16
  enableForeignKeysSQL(): string;
17
17
  finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
18
18
  getListTablesSQL(): string;
19
+ getListViewsSQL(): string;
20
+ loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string): Promise<void>;
19
21
  loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[]): Promise<void>;
20
22
  getAllIndexes(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<IndexDef[]>>;
21
23
  getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
24
+ /**
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)
27
+ */
28
+ protected getIndexColumns(index: IndexDef): string;
29
+ /**
30
+ * Append MySQL-specific index suffixes like INVISIBLE.
31
+ */
32
+ protected appendMySqlIndexSuffix(sql: string, index: IndexDef): string;
22
33
  getAllColumns(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<Column[]>>;
23
34
  getAllChecks(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<CheckDef[]>>;
24
35
  getAllForeignKeys(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<Dictionary<ForeignKey>>>;
@@ -33,6 +33,28 @@ export class MySqlSchemaHelper extends SchemaHelper {
33
33
  getListTablesSQL() {
34
34
  return `select table_name as table_name, nullif(table_schema, schema()) as schema_name, table_comment as table_comment from information_schema.tables where table_type = 'BASE TABLE' and table_schema = schema()`;
35
35
  }
36
+ getListViewsSQL() {
37
+ return `select table_name as view_name, nullif(table_schema, schema()) as schema_name, view_definition from information_schema.views where table_schema = schema()`;
38
+ }
39
+ async loadViews(schema, connection, schemaName) {
40
+ const views = await connection.execute(this.getListViewsSQL());
41
+ for (const view of views) {
42
+ // MySQL information_schema.views.view_definition requires SHOW VIEW privilege
43
+ // and may return NULL. Use SHOW CREATE VIEW as fallback.
44
+ let definition = view.view_definition?.trim();
45
+ if (!definition) {
46
+ const createView = await connection.execute(`show create view \`${view.view_name}\``);
47
+ if (createView[0]?.['Create View']) {
48
+ // Extract SELECT statement from CREATE VIEW ... AS SELECT ...
49
+ const match = createView[0]['Create View'].match(/\bAS\s+(.+)$/is);
50
+ definition = match?.[1]?.trim();
51
+ }
52
+ }
53
+ if (definition) {
54
+ schema.addView(view.view_name, view.schema_name ?? undefined, definition);
55
+ }
56
+ }
57
+ }
36
58
  async loadInformationSchema(schema, connection, tables) {
37
59
  if (tables.length === 0) {
38
60
  return;
@@ -50,7 +72,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
50
72
  }
51
73
  }
52
74
  async getAllIndexes(connection, tables) {
53
- const sql = `select table_name as table_name, nullif(table_schema, schema()) as schema_name, index_name as index_name, non_unique as non_unique, column_name as column_name /*!80013 , expression as expression */
75
+ const sql = `select table_name as table_name, nullif(table_schema, schema()) as schema_name, index_name as index_name, non_unique as non_unique, column_name as column_name, index_type as index_type, sub_part as sub_part, collation as sort_order /*!80013 , expression as expression, is_visible as is_visible */
54
76
  from information_schema.statistics where table_schema = database()
55
77
  and table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(', ')})
56
78
  order by schema_name, table_name, index_name, seq_in_index`;
@@ -65,6 +87,26 @@ export class MySqlSchemaHelper extends SchemaHelper {
65
87
  primary: index.index_name === 'PRIMARY',
66
88
  constraint: !index.non_unique,
67
89
  };
90
+ // Capture column options (prefix length, sort order)
91
+ if (index.sub_part != null || index.sort_order === 'D') {
92
+ indexDef.columns = [{
93
+ name: index.column_name,
94
+ ...(index.sub_part != null && { length: index.sub_part }),
95
+ ...(index.sort_order === 'D' && { sort: 'DESC' }),
96
+ }];
97
+ }
98
+ // Capture index type for fulltext and spatial indexes
99
+ if (index.index_type === 'FULLTEXT') {
100
+ indexDef.type = 'fulltext';
101
+ }
102
+ else if (index.index_type === 'SPATIAL') {
103
+ /* v8 ignore next */
104
+ indexDef.type = 'spatial';
105
+ }
106
+ // Capture invisible flag (MySQL 8.0.13+)
107
+ if (index.is_visible === 'NO') {
108
+ indexDef.invisible = true;
109
+ }
68
110
  if (!index.column_name || index.expression?.match(/ where /i)) {
69
111
  indexDef.expression = index.expression; // required for the `getCreateIndexSQL()` call
70
112
  indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
@@ -84,17 +126,66 @@ export class MySqlSchemaHelper extends SchemaHelper {
84
126
  }
85
127
  tableName = this.quote(tableName);
86
128
  const keyName = this.quote(index.keyName);
87
- const sql = `alter table ${tableName} add ${index.unique ? 'unique' : 'index'} ${keyName} `;
129
+ let sql = `alter table ${tableName} add ${index.unique ? 'unique' : 'index'} ${keyName} `;
88
130
  if (index.expression && partialExpression) {
89
- return `${sql}(${index.expression})`;
131
+ sql += `(${index.expression})`;
132
+ return this.appendMySqlIndexSuffix(sql, index);
90
133
  }
91
134
  // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
92
135
  if (index.columnNames.some(column => column.includes('.'))) {
93
136
  const columns = this.platform.getJsonIndexDefinition(index);
94
- const sql = `alter table ${tableName} add ${index.unique ? 'unique ' : ''}index ${keyName} `;
95
- return `${sql}(${columns.join(', ')})`;
137
+ sql = `alter table ${tableName} add ${index.unique ? 'unique ' : ''}index ${keyName} `;
138
+ sql += `(${columns.join(', ')})`;
139
+ return this.appendMySqlIndexSuffix(sql, index);
140
+ }
141
+ // Build column list with advanced options
142
+ const columns = this.getIndexColumns(index);
143
+ sql += `(${columns})`;
144
+ return this.appendMySqlIndexSuffix(sql, index);
145
+ }
146
+ /**
147
+ * Build the column list for a MySQL index, with MySQL-specific handling for collation.
148
+ * MySQL requires collation to be specified as an expression: (column_name COLLATE collation_name)
149
+ */
150
+ getIndexColumns(index) {
151
+ if (index.columns?.length) {
152
+ return index.columns.map(col => {
153
+ const quotedName = this.quote(col.name);
154
+ // MySQL supports collation via expression: (column_name COLLATE collation_name)
155
+ // When collation is specified, wrap in parentheses as an expression
156
+ if (col.collation) {
157
+ let expr = col.length ? `${quotedName}(${col.length})` : quotedName;
158
+ expr = `(${expr} collate ${col.collation})`;
159
+ // Sort order comes after the expression
160
+ if (col.sort) {
161
+ expr += ` ${col.sort}`;
162
+ }
163
+ return expr;
164
+ }
165
+ // Standard column definition without collation
166
+ let colDef = quotedName;
167
+ // MySQL supports prefix length
168
+ if (col.length) {
169
+ colDef += `(${col.length})`;
170
+ }
171
+ // MySQL supports sort order
172
+ if (col.sort) {
173
+ colDef += ` ${col.sort}`;
174
+ }
175
+ return colDef;
176
+ }).join(', ');
96
177
  }
97
- return `${sql}(${index.columnNames.map(c => this.quote(c)).join(', ')})`;
178
+ return index.columnNames.map(c => this.quote(c)).join(', ');
179
+ }
180
+ /**
181
+ * Append MySQL-specific index suffixes like INVISIBLE.
182
+ */
183
+ appendMySqlIndexSuffix(sql, index) {
184
+ // MySQL 8.0+ supports INVISIBLE indexes
185
+ if (index.invisible) {
186
+ sql += ' invisible';
187
+ }
188
+ return sql;
98
189
  }
99
190
  async getAllColumns(connection, tables) {
100
191
  const sql = `select table_name as table_name,
@@ -1,4 +1,3 @@
1
- export * from './MySqlExceptionConverter.js';
2
1
  export * from './MySqlSchemaHelper.js';
3
- export * from './MySqlPlatform.js';
2
+ export * from './BaseMySqlPlatform.js';
4
3
  export * from './MySqlNativeQueryBuilder.js';
@@ -1,4 +1,3 @@
1
- export * from './MySqlExceptionConverter.js';
2
1
  export * from './MySqlSchemaHelper.js';
3
- export * from './MySqlPlatform.js';
2
+ export * from './BaseMySqlPlatform.js';
4
3
  export * from './MySqlNativeQueryBuilder.js';
@@ -0,0 +1,106 @@
1
+ import { type EntityProperty, type IsolationLevel, RawQueryFragment, type SimpleColumnMeta, Type } from '@mikro-orm/core';
2
+ import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
3
+ import type { IndexDef } from '../../typings.js';
4
+ import { PostgreSqlNativeQueryBuilder } from './PostgreSqlNativeQueryBuilder.js';
5
+ import { PostgreSqlSchemaHelper } from './PostgreSqlSchemaHelper.js';
6
+ import { PostgreSqlExceptionConverter } from './PostgreSqlExceptionConverter.js';
7
+ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
8
+ protected readonly schemaHelper: PostgreSqlSchemaHelper;
9
+ protected readonly exceptionConverter: PostgreSqlExceptionConverter;
10
+ createNativeQueryBuilder(): PostgreSqlNativeQueryBuilder;
11
+ usesReturningStatement(): boolean;
12
+ usesCascadeStatement(): boolean;
13
+ supportsNativeEnums(): boolean;
14
+ usesEnumCheckConstraints(): boolean;
15
+ supportsMaterializedViews(): boolean;
16
+ supportsCustomPrimaryKeyNames(): boolean;
17
+ getCurrentTimestampSQL(length: number): string;
18
+ getDateTimeTypeDeclarationSQL(column: {
19
+ length?: number;
20
+ }): string;
21
+ getDefaultDateTimeLength(): number;
22
+ getTimeTypeDeclarationSQL(): string;
23
+ getIntegerTypeDeclarationSQL(column: {
24
+ length?: number;
25
+ autoincrement?: boolean;
26
+ generated?: string;
27
+ }): string;
28
+ getBigIntTypeDeclarationSQL(column: {
29
+ autoincrement?: boolean;
30
+ }): string;
31
+ getTinyIntTypeDeclarationSQL(column: {
32
+ length?: number;
33
+ unsigned?: boolean;
34
+ autoincrement?: boolean;
35
+ }): string;
36
+ getUuidTypeDeclarationSQL(column: {
37
+ length?: number;
38
+ }): string;
39
+ getFullTextWhereClause(prop: EntityProperty): string;
40
+ supportsCreatingFullTextIndex(): boolean;
41
+ getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
42
+ normalizeColumnType(type: string, options: {
43
+ length?: number;
44
+ precision?: number;
45
+ scale?: number;
46
+ autoincrement?: boolean;
47
+ }): string;
48
+ getMappedType(type: string): Type<unknown>;
49
+ getRegExpOperator(val?: unknown, flags?: string): string;
50
+ getRegExpValue(val: RegExp): {
51
+ $re: string;
52
+ $flags?: string;
53
+ };
54
+ isBigIntProperty(prop: EntityProperty): boolean;
55
+ getArrayDeclarationSQL(): string;
56
+ getFloatDeclarationSQL(): string;
57
+ getDoubleDeclarationSQL(): string;
58
+ getEnumTypeDeclarationSQL(column: {
59
+ fieldNames: string[];
60
+ items?: unknown[];
61
+ nativeEnumName?: string;
62
+ }): string;
63
+ supportsMultipleStatements(): boolean;
64
+ getBeginTransactionSQL(options?: {
65
+ isolationLevel?: IsolationLevel;
66
+ readOnly?: boolean;
67
+ }): string[];
68
+ marshallArray(values: string[]): string;
69
+ unmarshallArray(value: string): string[];
70
+ getVarcharTypeDeclarationSQL(column: {
71
+ length?: number;
72
+ }): string;
73
+ getCharTypeDeclarationSQL(column: {
74
+ length?: number;
75
+ }): string;
76
+ getIntervalTypeDeclarationSQL(column: {
77
+ length?: number;
78
+ }): string;
79
+ getBlobDeclarationSQL(): string;
80
+ getJsonDeclarationSQL(): string;
81
+ getSearchJsonPropertyKey(path: string[], type: string | undefined | Type, aliased: boolean, value?: unknown): string | RawQueryFragment;
82
+ getJsonIndexDefinition(index: IndexDef): string[];
83
+ quoteIdentifier(id: string | {
84
+ toString: () => string;
85
+ }, quote?: string): string;
86
+ private pad;
87
+ /** @internal */
88
+ formatDate(date: Date): string;
89
+ indexForeignKeys(): boolean;
90
+ getDefaultMappedType(type: string): Type<unknown>;
91
+ supportsSchemas(): boolean;
92
+ getDefaultSchemaName(): string | undefined;
93
+ /**
94
+ * Returns the default name of index for the given columns
95
+ * cannot go past 63 character length for identifiers in MySQL
96
+ */
97
+ getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
98
+ getDefaultPrimaryName(tableName: string, columns: string[]): string;
99
+ /**
100
+ * @inheritDoc
101
+ */
102
+ castColumn(prop?: {
103
+ columnTypes?: string[];
104
+ }): string;
105
+ getDefaultClientUrl(): string;
106
+ }