Skip to content
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

fix: EXPOSED-244 [PostgreSQL] Collate option on column not recognized #1956

Merged
merged 1 commit into from
Jan 3, 2024
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
1 change: 1 addition & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2071,6 +2071,7 @@ public abstract class org/jetbrains/exposed/sql/StringColumnType : org/jetbrains
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
protected final fun escape (Ljava/lang/String;)Ljava/lang/String;
protected final fun escapeAndQuote (Ljava/lang/String;)Ljava/lang/String;
public final fun getCollate ()Ljava/lang/String;
public fun hashCode ()I
public fun nonNullValueToString (Ljava/lang/Object;)Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,12 @@ abstract class StringColumnType(
/** Returns the specified [value] with special characters escaped. */
protected fun escape(value: String): String = value.map { charactersToEscape[it] ?: it }.joinToString("")

/** Returns the specified [value] with special characters escaped and wrapped in quotations, if necessary. */
protected fun escapeAndQuote(value: String): String = when (currentDialect) {
is PostgreSQLDialect -> "\"${escape(value)}\""
else -> escape(value)
}
Comment on lines 547 to +553
Copy link
Member Author

Choose a reason for hiding this comment

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

This logic could not be applied directly to escape() because nonNullValueToString() is dependent on it.

The quotation is only necessary in sqlType() overrides.


override fun valueFromDB(value: Any): Any = when (value) {
is Clob -> value.characterStream.readText()
is ByteArray -> String(value)
Expand Down Expand Up @@ -597,7 +603,7 @@ open class CharColumnType(
override fun sqlType(): String = buildString {
append("CHAR($colLength)")
if (collate != null) {
append(" COLLATE ${escape(collate)}")
append(" COLLATE ${escapeAndQuote(collate)}")
}
}

Expand Down Expand Up @@ -643,7 +649,7 @@ open class VarCharColumnType(
override fun sqlType(): String = buildString {
append(preciseType())
if (collate != null) {
append(" COLLATE ${escape(collate)}")
append(" COLLATE ${escapeAndQuote(collate)}")
}
}

Expand Down Expand Up @@ -689,7 +695,7 @@ open class TextColumnType(
override fun sqlType(): String = buildString {
append(preciseType())
if (collate != null) {
append(" COLLATE ${escape(collate)}")
append(" COLLATE ${escapeAndQuote(collate)}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.jetbrains.exposed.sql.tests.shared.types
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.TestDB
import org.jetbrains.exposed.sql.tests.shared.assertEqualLists
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.junit.Test

Expand All @@ -23,4 +25,39 @@ class CharColumnType : DatabaseTestsBase() {
assertEquals('A', result?.get(CharTable.charColumn))
}
}

@Test
fun testCharColumnWithCollate() {
val collateOption = when (TestDB.enabledDialects().first()) {
TestDB.POSTGRESQL, TestDB.POSTGRESQLNG -> "C"
TestDB.SQLITE -> "binary"
TestDB.SQLSERVER -> "latin1_general_bin"
else -> "utf8mb4_bin"
}

val tester = object : Table("tester") {
val letter = char("letter", 1, collate = collateOption)
}

// H2 only allows collation for the entire database using SET COLLATION
// Oracle only allows collation if MAX_STRING_SIZE=EXTENDED, which can only be set in upgrade mode
// Oracle -> https://docs.oracle.com/en/database/oracle/oracle-database/12.2/refrn/MAX_STRING_SIZE.html#
withDb(excludeSettings = TestDB.allH2TestDB + TestDB.ORACLE) {
try {
SchemaUtils.create(tester)

val letters = listOf("a", "A", "b", "B")
tester.batchInsert(letters) { ch ->
this[tester.letter] = ch
}

// one of the purposes of collation is to determine ordering rules of stored character data types
val expected = letters.sortedBy { it.single().code } // [A, B, a, b]
val actual = tester.selectAll().orderBy(tester.letter).map { it[tester.letter] }
assertEqualLists(expected, actual)
} finally {
SchemaUtils.drop(tester)
}
}
}
}
Loading