Skip to content

Commit

Permalink
Correct comparison of defaults for String type columns in PostgreSQL (#…
Browse files Browse the repository at this point in the history
…1589) / Test improvement
  • Loading branch information
Tapac committed Nov 14, 2022
1 parent fd1c088 commit 2bea43b
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ open class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypeProvid
return columnDefault !in nonAcceptableDefaults
}

// TODO: Fix changing default value on column as it requires to drop/create constraint
// https://stackoverflow.com/questions/15547210/modify-default-value-in-sql-server
override fun modifyColumn(column: Column<*>, columnDiff: ColumnDiff): List<String> =
super.modifyColumn(column, columnDiff).map { it.replace("MODIFY COLUMN", "ALTER COLUMN") }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData)
return when {
dialect is SQLServerDialect -> defaultValue.trim('(', ')', '\'')
dialect is OracleDialect || h2Mode == H2CompatibilityMode.Oracle -> defaultValue.trim().trim('\'')
dialect is MysqlDialect || h2Mode == H2CompatibilityMode.MySQL || h2Mode == H2CompatibilityMode.MariaDB -> defaultValue.substringAfter("b'").trim('\'').trim()
is PostgreSQLDialect -> defaultValue
else -> defaultValue.trim('\'').trim()
dialect is MysqlDialect || h2Mode == H2CompatibilityMode.MySQL || h2Mode == H2CompatibilityMode.MariaDB ->
defaultValue.substringAfter("b'").trim('\'')
dialect is PostgreSQLDialect || h2Mode == H2CompatibilityMode.PostgreSQL -> defaultValue
else -> defaultValue.trim('\'')
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package org.jetbrains.exposed.sql.tests

import org.h2.engine.Mode
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.StatementInterceptor
import org.jetbrains.exposed.sql.transactions.inTopLevelTransaction
import org.jetbrains.exposed.sql.transactions.nullableTransactionScope
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.transactions.transactionManager
import org.junit.Assume
Expand Down Expand Up @@ -162,12 +164,20 @@ private val mySQLProcess by lazy {
private fun runTestContainersMySQL(): Boolean =
(System.getProperty("exposed.test.mysql.host") ?: System.getProperty("exposed.test.mysql8.host")).isNullOrBlank()

internal var currentTestDB by nullableTransactionScope<TestDB>()

@Suppress("UnnecessaryAbstractClass")
abstract class DatabaseTestsBase {
init {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
}

private object CurrentTestDBInterceptor : StatementInterceptor {
override fun keepUserDataInTransactionStoreOnCommit(userData: Map<Key<*>, Any?>): Map<Key<*>, Any?> {
return userData.filterValues { it is TestDB }
}
}

fun withDb(dbSettings: TestDB, statement: Transaction.(TestDB) -> Unit) {
try {
Assume.assumeTrue(dbSettings in TestDB.enabledInTests())
Expand All @@ -191,6 +201,8 @@ abstract class DatabaseTestsBase {
val database = dbSettings.db!!

transaction(database.transactionManager.defaultIsolationLevel, 1, db = database) {
registerInterceptor(CurrentTestDBInterceptor)
currentTestDB = dbSettings
statement(dbSettings)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.jetbrains.exposed.sql.tests.shared
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.tests.currentDialectIfAvailableTest
import org.jetbrains.exposed.sql.tests.currentDialectTest
import org.jetbrains.exposed.sql.tests.currentTestDB
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertFailsWith
Expand Down Expand Up @@ -45,10 +46,12 @@ fun <T> assertEqualLists(actual: List<T>, vararg expected: T) {
assertEqualLists(actual, expected.toList())
}

fun Transaction.assertTrue(actual: Boolean) = assertTrue(actual, "Failed on ${currentDialectTest.name}")
fun Transaction.assertFalse(actual: Boolean) = assertFalse(actual, "Failed on ${currentDialectTest.name}")
fun <T> Transaction.assertEquals(exp: T, act: T) = assertEquals(exp, act, "Failed on ${currentDialectTest.name}")
fun <T> Transaction.assertEquals(exp: T, act: List<T>) = assertEquals(exp, act.single(), "Failed on ${currentDialectTest.name}")
private val Transaction.failedOn: String get() = currentTestDB?.name ?: currentDialectTest.name

fun Transaction.assertTrue(actual: Boolean) = assertTrue(actual, "Failed on $failedOn")
fun Transaction.assertFalse(actual: Boolean) = assertFalse(actual, "Failed on $failedOn")
fun <T> Transaction.assertEquals(exp: T, act: T) = assertEquals(exp, act, "Failed on $failedOn")
fun <T> Transaction.assertEquals(exp: T, act: List<T>) = assertEquals(exp, act.single(), "Failed on $failedOn")

fun Transaction.assertFailAndRollback(message: String, block: () -> Unit) {
commit()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,28 +274,71 @@ class CreateMissingTablesAndColumnsTests : DatabaseTestsBase() {
}
}

private class StringFieldTable(name: String, isTextColumn: Boolean, default: String) : IntIdTable(name) {
// nullable column is here as Oracle treat '' as NULL
val column: Column<String?> = if (isTextColumn) {
text("test_column").default(default).nullable()
} else {
varchar("test_column", 255).default(default).nullable()
}
}

@Test
fun `columns with default values that are whitespaces shouldn't be treated as empty strings`() {
val tableWhitespaceDefault = object : Table("varchar_test") {
val varchar = varchar("varchar_column", 255).default(" ")
val text = text("text_column").default(" ")
}
val tableWhitespaceDefaultVarchar = StringFieldTable("varchar_whitespace_test", false," ")

val tableEmptyStringDefault = object : Table("varchar_test") {
val varchar = varchar("varchar_column", 255).default("")
val text = text("text_column").default("")
}
val tableWhitespaceDefaultText = StringFieldTable("text_whitespace_test", true, " ")

val tableEmptyStringDefaultVarchar = StringFieldTable("varchar_whitespace_test", false, "")

val tableEmptyStringDefaultText = StringFieldTable("text_whitespace_test", true, "")

// MySQL doesn't support default values on text columns, hence excluded
// SQLite doesn't support alter table with add column, so it doesn't generate the statements, hence excluded
withDb(excludeSettings = listOf(TestDB.MYSQL, TestDB.SQLITE)) {
try {
SchemaUtils.create(tableWhitespaceDefault)
val actual = SchemaUtils.statementsRequiredToActualizeScheme(tableEmptyStringDefault)
// Both columns should be considered as changed, since "" != " "
assertEquals(2, actual.size)
} finally {
SchemaUtils.drop(tableEmptyStringDefault, tableWhitespaceDefault)
withDb(excludeSettings = listOf(TestDB.SQLITE)) { testDb ->
// MySQL doesn't support default values on text columns, hence excluded
val supportsTextDefault = testDb !in listOf(TestDB.MYSQL)
val tablesToTest = listOfNotNull(
tableWhitespaceDefaultVarchar to tableEmptyStringDefaultVarchar,
(tableWhitespaceDefaultText to tableEmptyStringDefaultText).takeIf { supportsTextDefault },
)
tablesToTest.forEach { (whiteSpaceTable, emptyTable) ->
try {
SchemaUtils.create(whiteSpaceTable)

val whiteSpaceId = whiteSpaceTable.insertAndGetId { }

assertEquals(" ", whiteSpaceTable.select { whiteSpaceTable.id eq whiteSpaceId }.single()[whiteSpaceTable.column])

val actual = SchemaUtils.statementsRequiredToActualizeScheme(emptyTable)
// Both columns should be considered as changed, since "" != " "
val expected = when (testDb) {
TestDB.ORACLE, TestDB.H2_ORACLE -> 2
else -> 1
}

assertEquals(expected, actual.size)

// SQL Server requires drop/create constraint to change defaults, unsupported for now
if (testDb != TestDB.SQLSERVER) {
// Apply changes
actual.forEach { exec(it) }
} else {
SchemaUtils.drop(whiteSpaceTable)
SchemaUtils.create(whiteSpaceTable)
}

val emptyId = emptyTable.insertAndGetId { }

// null is here as Oracle treat '' as NULL
val expectedEmptyValue = when (testDb) {
TestDB.ORACLE, TestDB.H2_ORACLE -> null
else -> ""
}

assertEquals(expectedEmptyValue, emptyTable.select { emptyTable.id eq emptyId }.single()[emptyTable.column])
} finally {
SchemaUtils.drop(whiteSpaceTable, emptyTable)
}
}
}
}
Expand Down

0 comments on commit 2bea43b

Please sign in to comment.