Skip to content

Commit e8c1599

Browse files
authored
refactor: EXPOSED-722 Refactor MariaDB code to reduce checks in MySQL code and make it neater (#2388)
* refactor: Override some functions in `MariaDBDialect` and `MariaDBFunctionProvider` to be able to remove the dialect checks done in `MysqlDialect` and `MysqlFunctionProvider` respectively * refactor: Create new object `MariaDBDataTypeProvider` and move MariaDB-specific checks to it from `MysqlDataTypeProvider` to reduce checks in the latter
1 parent 0330ce6 commit e8c1599

File tree

5 files changed

+55
-21
lines changed

5 files changed

+55
-21
lines changed

exposed-core/api/exposed-core.api

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4200,14 +4200,17 @@ public class org/jetbrains/exposed/sql/vendors/MariaDBDialect : org/jetbrains/ex
42004200
public static final field Companion Lorg/jetbrains/exposed/sql/vendors/MariaDBDialect$Companion;
42014201
public fun <init> ()V
42024202
public fun createIndex (Lorg/jetbrains/exposed/sql/Index;)Ljava/lang/String;
4203+
public fun getDataTypeProvider ()Lorg/jetbrains/exposed/sql/vendors/DataTypeProvider;
42034204
public fun getFunctionProvider ()Lorg/jetbrains/exposed/sql/vendors/FunctionProvider;
42044205
public fun getName ()Ljava/lang/String;
42054206
public fun getSequenceMaxValue ()J
42064207
public fun getSupportsCreateSequence ()Z
42074208
public fun getSupportsOnlyIdentifiersInGeneratedKeys ()Z
42084209
public fun getSupportsSequenceAsGeneratedKeys ()Z
42094210
public fun getSupportsSetDefaultReferenceOption ()Z
4211+
public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z
42104212
public fun isFractionDateTimeSupported ()Z
4213+
public fun isTimeZoneOffsetSupported ()Z
42114214
}
42124215

42134216
public final class org/jetbrains/exposed/sql/vendors/MariaDBDialect$Companion : org/jetbrains/exposed/sql/vendors/VendorDialect$DialectNameProvider {
@@ -4221,14 +4224,15 @@ public class org/jetbrains/exposed/sql/vendors/MysqlDialect : org/jetbrains/expo
42214224
public fun dropIndex (Ljava/lang/String;Ljava/lang/String;ZZ)Ljava/lang/String;
42224225
public fun dropSchema (Lorg/jetbrains/exposed/sql/Schema;Z)Ljava/lang/String;
42234226
protected fun fillConstraintCacheForTables (Ljava/util/List;)V
4227+
protected final fun getNotAcceptableDefaults ()Ljava/util/List;
42244228
public fun getSupportsCreateSequence ()Z
42254229
public fun getSupportsOrderByNullsFirstLast ()Z
42264230
public fun getSupportsSetDefaultReferenceOption ()Z
42274231
public fun getSupportsSubqueryUnions ()Z
42284232
public fun getSupportsTernaryAffectedRowValues ()Z
42294233
public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z
42304234
public fun isFractionDateTimeSupported ()Z
4231-
public final fun isTimeZoneOffsetSupported ()Z
4235+
public fun isTimeZoneOffsetSupported ()Z
42324236
protected fun metadataMatchesTable (Ljava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Table;)Z
42334237
public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
42344238
}

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchReplaceStatement.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ open class BatchReplaceStatement(
2323
val valuesSql = values.toSqlString(prepared)
2424
val dialect = transaction.db.dialect
2525
val functionProvider = when (dialect.h2Mode) {
26-
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider()
26+
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider.INSTANCE
2727
else -> dialect.functionProvider
2828
}
2929
return functionProvider.replace(table, values.unzip().first, valuesSql, transaction, prepared)

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/ReplaceStatement.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ open class ReplaceStatement<Key : Any>(table: Table) : InsertStatement<Key>(tabl
2020
val valuesSql = values.toSqlString(prepared)
2121
val dialect = transaction.db.dialect
2222
val functionProvider = when (dialect.h2Mode) {
23-
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider()
23+
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider.INSTANCE
2424
else -> dialect.functionProvider
2525
}
2626
return functionProvider.replace(table, values.unzip().first, valuesSql, transaction, prepared)
@@ -42,7 +42,7 @@ open class ReplaceSelectStatement(
4242
val querySql = selectQuery.prepareSQL(transaction, prepared)
4343
val dialect = transaction.db.dialect
4444
val functionProvider = when (dialect.h2Mode) {
45-
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider()
45+
H2Dialect.H2CompatibilityMode.MySQL, H2Dialect.H2CompatibilityMode.MariaDB -> MysqlFunctionProvider.INSTANCE
4646
else -> dialect.functionProvider
4747
}
4848
return functionProvider.replace(targets.single(), columns, querySql, transaction, prepared)

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MariaDBDialect.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
package org.jetbrains.exposed.sql.vendors
22

3+
import org.jetbrains.exposed.exceptions.UnsupportedByDialectException
34
import org.jetbrains.exposed.sql.*
5+
import org.jetbrains.exposed.sql.Function
46
import org.jetbrains.exposed.sql.transactions.TransactionManager
57

8+
internal object MariaDBDataTypeProvider : MysqlDataTypeProvider() {
9+
override fun timestampWithTimeZoneType(): String {
10+
throw UnsupportedByDialectException("This vendor does not support timestamp with time zone data type", currentDialect)
11+
}
12+
13+
override fun processForDefaultValue(e: Expression<*>): String = when {
14+
e is LiteralOp<*> -> (e.columnType as IColumnType<Any?>).valueAsDefaultString(e.value)
15+
e is Function<*> || currentDialect is MariaDBDialect -> "$e"
16+
else -> "($e)"
17+
}
18+
}
19+
620
internal object MariaDBFunctionProvider : MysqlFunctionProvider() {
721
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
822
append("NEXTVAL(", seq.identifier, ")")
@@ -61,13 +75,16 @@ internal object MariaDBFunctionProvider : MysqlFunctionProvider() {
6175
toString()
6276
}
6377
}
78+
79+
override fun isUpsertAliasSupported(dialect: DatabaseDialect): Boolean = false
6480
}
6581

6682
/**
6783
* MariaDB dialect implementation.
6884
*/
6985
open class MariaDBDialect : MysqlDialect() {
7086
override val name: String = dialectName
87+
override val dataTypeProvider: DataTypeProvider = MariaDBDataTypeProvider
7188
override val functionProvider: FunctionProvider = MariaDBFunctionProvider
7289
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
7390
override val supportsSetDefaultReferenceOption: Boolean = false
@@ -91,6 +108,20 @@ open class MariaDBDialect : MysqlDialect() {
91108
@Suppress("MagicNumber")
92109
override fun isFractionDateTimeSupported(): Boolean = TransactionManager.current().db.isVersionCovers(5, 3)
93110

111+
override fun isTimeZoneOffsetSupported(): Boolean = false
112+
113+
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean {
114+
if (e is LiteralOp<*>) return true
115+
if (fullVersion >= "10.2.1") {
116+
return true
117+
}
118+
119+
// This check is quite optimistic, it will not allow to create a varchar columns with "CURRENT_DATE" default value for example
120+
// Comparing to the previous variant with white list of functions the new variant does not reject valid values,
121+
// it could be checked on the test UpsertTests::testUpsertWithColumnExpressions()
122+
return e.toString().trim() !in notAcceptableDefaults
123+
}
124+
94125
override fun createIndex(index: Index): String {
95126
if (index.functions != null) {
96127
exposedLogger.warn(

exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/MysqlDialect.kt

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import org.jetbrains.exposed.sql.*
66
import org.jetbrains.exposed.sql.transactions.TransactionManager
77
import java.math.BigDecimal
88

9-
internal object MysqlDataTypeProvider : DataTypeProvider() {
10-
9+
internal open class MysqlDataTypeProvider : DataTypeProvider() {
1110
override fun binaryType(): String {
1211
exposedLogger.error("The length of the Binary column is missing.")
1312
error("The length of the Binary column is missing.")
@@ -19,11 +18,7 @@ internal object MysqlDataTypeProvider : DataTypeProvider() {
1918
if ((currentDialect as? MysqlDialect)?.isTimeZoneOffsetSupported() == true) {
2019
"TIMESTAMP(6)"
2120
} else {
22-
throw UnsupportedByDialectException(
23-
"This vendor does not support timestamp with time zone data type" +
24-
((currentDialect as? MariaDBDialect)?.let { "" } ?: " for this version"),
25-
currentDialect
26-
)
21+
throw UnsupportedByDialectException("This vendor does not support timestamp with time zone data type for this version", currentDialect)
2722
}
2823

2924
override fun ubyteType(): String = "TINYINT UNSIGNED"
@@ -56,14 +51,12 @@ internal object MysqlDataTypeProvider : DataTypeProvider() {
5651

5752
override fun processForDefaultValue(e: Expression<*>): String = when {
5853
e is LiteralOp<*> && e.columnType is JsonColumnMarker -> when {
59-
currentDialect is MariaDBDialect -> super.processForDefaultValue(e)
6054
((currentDialect as? MysqlDialect)?.fullVersion ?: "0") >= "8.0.13" -> "(${super.processForDefaultValue(e)})"
6155
else -> throw UnsupportedByDialectException(
6256
"MySQL versions prior to 8.0.13 do not accept default values on JSON columns",
6357
currentDialect
6458
)
6559
}
66-
currentDialect is MariaDBDialect -> super.processForDefaultValue(e)
6760
// The default value specified in a DEFAULT clause can be a literal constant or an expression. With one
6861
// exception, enclose expression default values within parentheses to distinguish them from literal constant
6962
// default values. The exception is that, for TIMESTAMP and DATETIME columns, you can specify the
@@ -91,11 +84,13 @@ internal object MysqlDataTypeProvider : DataTypeProvider() {
9184
}
9285

9386
override fun hexToDb(hexString: String): String = "0x$hexString"
87+
88+
companion object {
89+
internal val INSTANCE = MysqlDataTypeProvider()
90+
}
9491
}
9592

9693
internal open class MysqlFunctionProvider : FunctionProvider() {
97-
internal object INSTANCE : MysqlFunctionProvider()
98-
9994
override fun random(seed: Int?): String = "RAND(${seed?.toString().orEmpty()})"
10095

10196
private class MATCH(val expr: Expression<*>, val pattern: String, val mode: MatchMode) : Op<Boolean>() {
@@ -314,8 +309,8 @@ internal open class MysqlFunctionProvider : FunctionProvider() {
314309
}
315310
}
316311

317-
private fun isUpsertAliasSupported(dialect: DatabaseDialect): Boolean = when (dialect) {
318-
is MysqlDialect -> dialect !is MariaDBDialect && dialect.fullVersion >= "8.0.19"
312+
open fun isUpsertAliasSupported(dialect: DatabaseDialect): Boolean = when (dialect) {
313+
is MysqlDialect -> dialect.fullVersion >= "8.0.19"
319314
else -> false // H2_MySQL mode also uses this function provider & requires older unsupported version
320315
}
321316

@@ -331,12 +326,16 @@ internal open class MysqlFunctionProvider : FunctionProvider() {
331326
}
332327
return super.queryLimitAndOffset(size, offset, alreadyOrdered)
333328
}
329+
330+
companion object {
331+
internal val INSTANCE = MysqlFunctionProvider()
332+
}
334333
}
335334

336335
/**
337336
* MySQL dialect implementation.
338337
*/
339-
open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, MysqlFunctionProvider.INSTANCE) {
338+
open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider.INSTANCE, MysqlFunctionProvider.INSTANCE) {
340339

341340
internal val isMysql8: Boolean by lazy {
342341
TransactionManager.current().db.isVersionCovers(BigDecimal("8.0"))
@@ -361,13 +360,13 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
361360
open fun isFractionDateTimeSupported(): Boolean = TransactionManager.current().db.isVersionCovers(5, 6)
362361

363362
/** Returns `true` if a MySQL database is being used and its version is greater than or equal to 8.0. */
364-
fun isTimeZoneOffsetSupported(): Boolean = (currentDialect !is MariaDBDialect) && isMysql8
363+
open fun isTimeZoneOffsetSupported(): Boolean = isMysql8
365364

366-
private val notAcceptableDefaults = mutableListOf("CURRENT_DATE()", "CURRENT_DATE")
365+
protected val notAcceptableDefaults = mutableListOf("CURRENT_DATE()", "CURRENT_DATE")
367366

368367
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean {
369368
if (super.isAllowedAsColumnDefault(e)) return true
370-
if ((currentDialect is MariaDBDialect && fullVersion >= "10.2.1") || (currentDialect !is MariaDBDialect && fullVersion >= "8.0.13")) {
369+
if (fullVersion >= "8.0.13") {
371370
return true
372371
}
373372

0 commit comments

Comments
 (0)