import { Utils, QueryOrder, DecimalType, DoubleType } from '@mikro-orm/core'; import { MySqlSchemaHelper } from './MySqlSchemaHelper.js'; import { MySqlExceptionConverter } from './MySqlExceptionConverter.js'; import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js'; import { MySqlNativeQueryBuilder } from './MySqlNativeQueryBuilder.js'; export class BaseMySqlPlatform extends AbstractSqlPlatform { schemaHelper = new MySqlSchemaHelper(this); exceptionConverter = new MySqlExceptionConverter(); #jsonTypeCasts = { string: 'text', number: 'double', bigint: 'bigint', boolean: 'unsigned', }; ORDER_BY_NULLS_TRANSLATE = { [QueryOrder.asc_nulls_first]: 'is not null', [QueryOrder.asc_nulls_last]: 'is null', [QueryOrder.desc_nulls_first]: 'is not null', [QueryOrder.desc_nulls_last]: 'is null', }; /** @internal */ createNativeQueryBuilder() { return new MySqlNativeQueryBuilder(this); } getDefaultCharset() { return 'utf8mb4'; } init(orm) { super.init(orm); orm.config.get('schemaGenerator').disableForeignKeysForClear ??= true; } getBeginTransactionSQL(options) { if (options?.isolationLevel || options?.readOnly) { const parts = []; if (options.isolationLevel) { parts.push(`isolation level ${options.isolationLevel}`); } if (options.readOnly) { parts.push('read only'); } const sql = `set transaction ${parts.join(', ')}`; return [sql, 'begin']; } return ['begin']; } convertJsonToDatabaseValue(value, context) { if (context?.mode === 'query') { return value; } return JSON.stringify(value); } getJsonIndexDefinition(index) { return index.columnNames.map(column => { if (!column.includes('.')) { return column; } const [root, ...path] = column.split('.'); return `(json_value(${this.quoteIdentifier(root)}, '$.${path.join('.')}' returning ${index.options?.returning ?? 'char(255)'}))`; }); } getBooleanTypeDeclarationSQL() { return 'tinyint(1)'; } normalizeColumnType(type, options) { const simpleType = this.extractSimpleType(type); if (['decimal', 'numeric'].includes(simpleType)) { return this.getDecimalTypeDeclarationSQL(options); } return type; } getDefaultMappedType(type) { if (type === 'tinyint(1)') { return super.getDefaultMappedType('boolean'); } return super.getDefaultMappedType(type); } isNumericColumn(mappedType) { return super.isNumericColumn(mappedType) || [DecimalType, DoubleType].some(t => mappedType instanceof t); } supportsUnsigned() { return true; } /** * Returns the default name of index for the given columns * cannot go past 64 character length for identifiers in MySQL */ getIndexName(tableName, columns, type) { if (type === 'primary') { return this.getDefaultPrimaryName(tableName, columns); } const indexName = super.getIndexName(tableName, columns, type); if (indexName.length > 64) { return `${indexName.substring(0, 56 - type.length)}_${Utils.hash(indexName, 5)}_${type}`; } return indexName; } getDefaultPrimaryName(tableName, columns) { return 'PRIMARY'; // https://dev.mysql.com/doc/refman/8.0/en/create-table.html#create-table-indexes-keys } supportsCreatingFullTextIndex() { return true; } getFullTextWhereClause() { return `match(:column:) against (:query in boolean mode)`; } getFullTextIndexExpression(indexName, schemaName, tableName, columns) { /* v8 ignore next */ const quotedTableName = this.quoteIdentifier(schemaName ? `${schemaName}.${tableName}` : tableName); const quotedColumnNames = columns.map(c => this.quoteIdentifier(c.name)); const quotedIndexName = this.quoteIdentifier(indexName); return `alter table ${quotedTableName} add fulltext index ${quotedIndexName}(${quotedColumnNames.join(',')})`; } getOrderByExpression(column, direction, collation) { const ret = []; const dir = direction.toLowerCase(); const col = collation ? `${column} collate ${this.quoteCollation(collation)}` : column; if (dir in this.ORDER_BY_NULLS_TRANSLATE) { ret.push(`${col} ${this.ORDER_BY_NULLS_TRANSLATE[dir]}`); } ret.push(`${col} ${dir.replace(/(\s|nulls|first|last)*/gi, '')}`); return ret; } getJsonArrayFromSQL(column, alias, properties) { const columns = properties .map( p => `${this.quoteIdentifier(p.name)} ${this.#jsonTypeCasts[p.type] ?? 'text'} path '$.${this.quoteJsonKey(p.name)}'`, ) .join(', '); return `json_table(${column}, '$[*]' columns (${columns})) as ${this.quoteIdentifier(alias)}`; } // MySQL does not support correlated json_table inside EXISTS subqueries, // so we use a semi-join via the comma-join pattern instead. getJsonArrayExistsSQL(from, where) { return `(select 1 from ${from} where ${where} limit 1) is not null`; } getDefaultClientUrl() { return 'mysql://root@127.0.0.1:3306'; } }