From e815785a6a7dfdcf7a18709973c420466b490985 Mon Sep 17 00:00:00 2001 From: devgor88 Date: Mon, 27 Jan 2025 16:53:56 +0300 Subject: [PATCH] fix: Add support for creating sequence in MariaDB (#2324) * fix: Add support for creating sequence in MariaDB * test: EXPOSED-662 add new create table test * fix: add mariadb cases to DatabaseMigrationTests * Revert "test: EXPOSED-662 add new create table test" This reverts commit e6bb1df4f846b9c72c3a3eefb32d53c12e2f664d. * fix: add mariadb one more case to DatabaseMigrationTests * fix: table's name must not be more than 64 characters in MariaDB * fix: supportsSequenceAsGeneratedKeys is false * fix: sequences table implemented in 11.5 version * fix: api update * fix: change version from 11.5 to 10.3 * fix: api fix * Revert "fix: table's name must not be more than 64 characters in MariaDB" This reverts commit cf1c97e3798d8d222b839f296207abef9a42b0ce. * fix: make constant private --- exposed-core/api/exposed-core.api | 7 +++++++ .../org/jetbrains/exposed/sql/Database.kt | 10 ++++++++++ .../statements/api/ExposedDatabaseMetadata.kt | 6 ++++++ .../exposed/sql/vendors/MariaDBDialect.kt | 12 ++++++++++- exposed-jdbc/api/exposed-jdbc.api | 2 ++ .../jdbc/JdbcDatabaseMetadataImpl.kt | 2 ++ .../sql/tests/shared/TransactionExecTests.kt | 1 + .../shared/ddl/DatabaseMigrationTests.kt | 20 +++++++++++++++++++ 8 files changed, 59 insertions(+), 1 deletion(-) diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index 798e1216dc..3e58f5ce87 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -662,6 +662,8 @@ public final class org/jetbrains/exposed/sql/Database { public final fun getDefaultFetchSize ()Ljava/lang/Integer; public final fun getDialect ()Lorg/jetbrains/exposed/sql/vendors/DatabaseDialect; public final fun getIdentifierManager ()Lorg/jetbrains/exposed/sql/statements/api/IdentifierManagerApi; + public final fun getMajorVersion ()I + public final fun getMinorVersion ()I public final fun getSupportsAlterTableWithAddColumn ()Z public final fun getSupportsAlterTableWithDropColumn ()Z public final fun getSupportsMultipleResultSets ()Z @@ -669,6 +671,7 @@ public final class org/jetbrains/exposed/sql/Database { public final fun getUseNestedTransactions ()Z public final fun getVendor ()Ljava/lang/String; public final fun getVersion ()Ljava/math/BigDecimal; + public final fun isVersionCovers (II)Z public final fun isVersionCovers (Ljava/math/BigDecimal;)Z public final fun setUseNestedTransactions (Z)V public fun toString ()Ljava/lang/String; @@ -3571,6 +3574,8 @@ public abstract class org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMe public abstract fun getDatabaseProductVersion ()Ljava/lang/String; public abstract fun getDefaultIsolationLevel ()I public abstract fun getIdentifierManager ()Lorg/jetbrains/exposed/sql/statements/api/IdentifierManagerApi; + public abstract fun getMajorVersion ()I + public abstract fun getMinorVersion ()I public abstract fun getSchemaNames ()Ljava/util/List; public abstract fun getSupportsAlterTableWithAddColumn ()Z public abstract fun getSupportsAlterTableWithDropColumn ()Z @@ -4158,7 +4163,9 @@ public final class org/jetbrains/exposed/sql/vendors/MariaDBDialect : org/jetbra public fun createIndex (Lorg/jetbrains/exposed/sql/Index;)Ljava/lang/String; public fun getFunctionProvider ()Lorg/jetbrains/exposed/sql/vendors/FunctionProvider; public fun getName ()Ljava/lang/String; + public fun getSupportsCreateSequence ()Z public fun getSupportsOnlyIdentifiersInGeneratedKeys ()Z + public fun getSupportsSequenceAsGeneratedKeys ()Z public fun getSupportsSetDefaultReferenceOption ()Z } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt index 20e3400f03..022f6c600c 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt @@ -64,6 +64,16 @@ class Database private constructor( /** Whether the version number of the database is equal to or greater than the provided [version]. */ fun isVersionCovers(version: BigDecimal) = this.version >= version + /** The major version number of the database as a [Int]. */ + val majorVersion by lazy { metadata { majorVersion } } + + /** The minor version number of the database as a [Int]. */ + val minorVersion by lazy { metadata { minorVersion } } + + /** Whether the version number of the database is equal to or greater than the provided [majorVersion] and [minorVersion]. */ + fun isVersionCovers(majorVersion: Int, minorVersion: Int) = + this.majorVersion >= majorVersion && this.minorVersion >= minorVersion + /** Whether the database supports ALTER TABLE with an add column clause. */ val supportsAlterTableWithAddColumn by lazy( LazyThreadSafetyMode.NONE diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt index 7d26995b40..a7c18022e2 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt @@ -19,6 +19,12 @@ abstract class ExposedDatabaseMetadata(val database: String) { /** The version number of the database as a `BigDecimal`. */ abstract val version: BigDecimal + /** The major version number of the database. */ + abstract val majorVersion: Int + + /** The minor version number of the database. */ + abstract val minorVersion: Int + /** The name of the database based on the name of the underlying JDBC driver. */ abstract val databaseDialectName: String diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MariaDBDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MariaDBDialect.kt index 623627f3e1..86093dad04 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MariaDBDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MariaDBDialect.kt @@ -1,6 +1,7 @@ package org.jetbrains.exposed.sql.vendors import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.TransactionManager internal object MariaDBFunctionProvider : MysqlFunctionProvider() { override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder { @@ -70,6 +71,12 @@ class MariaDBDialect : MysqlDialect() { override val functionProvider: FunctionProvider = MariaDBFunctionProvider override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true override val supportsSetDefaultReferenceOption: Boolean = false + override val supportsCreateSequence: Boolean by lazy { + TransactionManager.current().db.isVersionCovers(SEQUENCE_MIN_MAJOR_VERSION, SEQUENCE_MIN_MINOR_VERSION) + } + + // actually MariaDb supports it but jdbc driver prepares statement without RETURNING clause + override val supportsSequenceAsGeneratedKeys: Boolean = false override fun createIndex(index: Index): String { if (index.functions != null) { @@ -81,5 +88,8 @@ class MariaDBDialect : MysqlDialect() { return super.createIndex(index) } - companion object : DialectNameProvider("MariaDB") + companion object : DialectNameProvider("MariaDB") { + private const val SEQUENCE_MIN_MAJOR_VERSION = 10 + private const val SEQUENCE_MIN_MINOR_VERSION = 3 + } } diff --git a/exposed-jdbc/api/exposed-jdbc.api b/exposed-jdbc/api/exposed-jdbc.api index 56aec264f2..f87344a4ad 100644 --- a/exposed-jdbc/api/exposed-jdbc.api +++ b/exposed-jdbc/api/exposed-jdbc.api @@ -43,7 +43,9 @@ public final class org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadat public fun getDatabaseProductVersion ()Ljava/lang/String; public fun getDefaultIsolationLevel ()I public fun getIdentifierManager ()Lorg/jetbrains/exposed/sql/statements/api/IdentifierManagerApi; + public fun getMajorVersion ()I public final fun getMetadata ()Ljava/sql/DatabaseMetaData; + public fun getMinorVersion ()I public fun getSchemaNames ()Ljava/util/List; public fun getSupportsAlterTableWithAddColumn ()Z public fun getSupportsAlterTableWithDropColumn ()Z diff --git a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt index 71ec54b5a5..d1ae431ec5 100644 --- a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt +++ b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt @@ -17,6 +17,8 @@ import java.util.concurrent.ConcurrentHashMap class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData) : ExposedDatabaseMetadata(database) { override val url: String by lazyMetadata { url } override val version: BigDecimal by lazyMetadata { BigDecimal("$databaseMajorVersion.$databaseMinorVersion") } + override val majorVersion: Int by lazyMetadata { databaseMajorVersion } + override val minorVersion: Int by lazyMetadata { databaseMinorVersion } override val databaseDialectName: String by lazyMetadata { when (driverName) { diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt index c5f65b9141..dda528a679 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt @@ -96,6 +96,7 @@ class TransactionExecTests : DatabaseTestsBase() { TestDB.SQLSERVER -> "SELECT current_value AS $columnAlias FROM sys.sequences" TestDB.ORACLE -> "SELECT ${ExecTable.id.autoIncColumnType?.autoincSeq}.CURRVAL AS $columnAlias FROM DUAL" TestDB.POSTGRESQL -> "SELECT lastval() AS $columnAlias" + TestDB.MARIADB -> "SELECT LASTVAL(${ExecTable.id.autoIncColumnType?.autoincSeq}) AS $columnAlias" else -> "SELECT LAST_INSERT_ID() AS $columnAlias" } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/DatabaseMigrationTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/DatabaseMigrationTests.kt index 8b9e9df6ed..75304b67b4 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/DatabaseMigrationTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/DatabaseMigrationTests.kt @@ -433,6 +433,16 @@ class DatabaseMigrationTests : DatabaseTestsBase() { withDb(excludeSettings = listOf(TestDB.SQLITE)) { testDb -> if (currentDialectTest.supportsCreateSequence) { try { + // MariaDB does not allow to create auto column without defining it as a key + val tableWithAutoIncrement = if (testDb == TestDB.MARIADB) { + object : IdTable("test_table") { + override val id: Column> = long("id").autoIncrement().entityId() + override val primaryKey = PrimaryKey(id) + } + } else { + tableWithAutoIncrement + } + SchemaUtils.create(tableWithAutoIncrement) assertEquals(0, MigrationUtils.statementsRequiredForDatabaseMigration(tableWithAutoIncrement, withLogs = false).size) @@ -526,6 +536,11 @@ class DatabaseMigrationTests : DatabaseTestsBase() { assertEquals(1, statements.size) assertEquals("ALTER TABLE TEST_TABLE ALTER COLUMN ID BIGINT AUTO_INCREMENT NOT NULL", statements[0]) } + TestDB.MARIADB -> { + assertEquals(2, statements.size) + assertEquals("ALTER TABLE test_table MODIFY COLUMN id BIGINT AUTO_INCREMENT NOT NULL", statements[0]) + assertEquals(expectedDropSequenceStatement(sequenceName), statements[1]) + } else -> { assertEquals(2, statements.size) assertTrue(statements[0].startsWith("ALTER TABLE TEST_TABLE ALTER COLUMN ID", ignoreCase = true)) @@ -633,6 +648,11 @@ class DatabaseMigrationTests : DatabaseTestsBase() { assertEquals(1, statements.size) assertEquals("ALTER TABLE TEST_TABLE ALTER COLUMN ID BIGINT AUTO_INCREMENT NOT NULL", statements[0]) } + TestDB.MARIADB -> { + assertEquals(2, statements.size) + assertEquals("ALTER TABLE test_table MODIFY COLUMN id BIGINT AUTO_INCREMENT NOT NULL", statements[0]) + assertEquals(expectedDropSequenceStatement(sequence.name), statements[1]) + } else -> { assertEquals(2, statements.size) assertTrue(statements[0].startsWith("ALTER TABLE TEST_TABLE ALTER COLUMN ID", ignoreCase = true))