Skip to content

fix: Add support for creating sequence in MariaDB #2324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -662,13 +662,16 @@ 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
public final fun getUrl ()Ljava/lang/String;
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 10 additions & 0 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we implement this by ourselves? I looked how Oracle do this. As I understand Exposed sends sql without returning clause and jdbc driver add it after.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into it and we can deal with it in a separate pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. I wanted to do it by myself earlier but I'm too busy

override val supportsSequenceAsGeneratedKeys: Boolean = false

override fun createIndex(index: Index): String {
if (index.functions != null) {
Expand All @@ -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
}
}
2 changes: 2 additions & 0 deletions exposed-jdbc/api/exposed-jdbc.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long>("test_table") {
override val id: Column<EntityID<Long>> = long("id").autoIncrement().entityId()
override val primaryKey = PrimaryKey(id)
}
} else {
tableWithAutoIncrement
}

SchemaUtils.create(tableWithAutoIncrement)

assertEquals(0, MigrationUtils.statementsRequiredForDatabaseMigration(tableWithAutoIncrement, withLogs = false).size)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down
Loading