Skip to content

Commit

Permalink
fix!: EXPOSED-545 Byte column allows out-of-range values
Browse files Browse the repository at this point in the history
  • Loading branch information
joc-a committed Sep 16, 2024
1 parent 96e17d7 commit 4865671
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 7 deletions.
2 changes: 2 additions & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ public final class org/jetbrains/exposed/sql/ByteColumnType : org/jetbrains/expo
public fun sqlType ()Ljava/lang/String;
public fun valueFromDB (Ljava/lang/Object;)Ljava/lang/Byte;
public synthetic fun valueFromDB (Ljava/lang/Object;)Ljava/lang/Object;
public fun valueToDB (Ljava/lang/Byte;)Ljava/lang/Object;
public synthetic fun valueToDB (Ljava/lang/Object;)Ljava/lang/Object;
}

public final class org/jetbrains/exposed/sql/Case {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,15 @@ class ByteColumnType : ColumnType<Byte>() {
is String -> value.toByte()
else -> error("Unexpected value of type Byte: $value of ${value::class.qualifiedName}")
}

override fun valueToDB(value: Byte?): Any? {
return if (currentDialect is SQLServerDialect) {
// Workaround for SQL Server JDBC driver mysterious error for in-range values if there's a CHECK constraint
value?.toShort()
} else {
super.valueToDB(value)
}
}
}

/**
Expand Down
15 changes: 11 additions & 4 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,9 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
// Numeric columns

/** Creates a numeric column, with the specified [name], for storing 1-byte integers. */
fun byte(name: String): Column<Byte> = registerColumn(name, ByteColumnType())
fun byte(name: String): Column<Byte> = registerColumn(name, ByteColumnType()).apply {
check("${generatedSignedCheckPrefix}byte_$name") { it.between(Byte.MIN_VALUE, Byte.MAX_VALUE) }
}

/** Creates a numeric column, with the specified [name], for storing 1-byte unsigned integers.
*
Expand Down Expand Up @@ -1651,14 +1653,19 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
}
is SQLServerDialect -> checkConstraints.filterNot { (name, _) ->
name.startsWith("${generatedUnsignedCheckPrefix}byte_") ||
name.startsWith(generatedSignedCheckPrefix)
name.startsWith("${generatedSignedCheckPrefix}short")
}
is PostgreSQLDialect -> checkConstraints.filterNot { (name, _) ->
name.startsWith(generatedSignedCheckPrefix)
name.startsWith("${generatedSignedCheckPrefix}short")
}
is H2Dialect -> {
when (dialect.h2Mode) {
H2Dialect.H2CompatibilityMode.Oracle -> checkConstraints
H2Dialect.H2CompatibilityMode.Oracle -> checkConstraints.filterNot { (name, _) ->
name.startsWith("${generatedSignedCheckPrefix}byte")
}
H2Dialect.H2CompatibilityMode.PostgreSQL -> checkConstraints.filterNot { (name, _) ->
name.startsWith("${generatedSignedCheckPrefix}short")
}
else -> checkConstraints.filterNot { (name, _) ->
name.startsWith(generatedSignedCheckPrefix)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import java.util.*

@Suppress("TooManyFunctions")
internal object OracleDataTypeProvider : DataTypeProvider() {
override fun byteType(): String = "SMALLINT"
override fun byteType(): String = if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) {
"TINYINT"
} else {
"NUMBER(3)"
}
override fun ubyteType(): String = "NUMBER(4)"
override fun shortType(): String = "NUMBER(5)"
override fun ushortType(): String = "NUMBER(6)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.util.*

internal object SQLServerDataTypeProvider : DataTypeProvider() {
override fun byteType(): String = if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.SQLServer) {
"TINYINT"
} else {
"SMALLINT"
}

override fun ubyteType(): String {
return if ((currentDialect as? H2Dialect)?.h2Mode == H2Dialect.H2CompatibilityMode.SQLServer) {
return if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.SQLServer) {
"SMALLINT"
} else {
"TINYINT"
Expand All @@ -32,7 +38,7 @@ internal object SQLServerDataTypeProvider : DataTypeProvider() {
override fun uuidToDB(value: UUID): Any = value.toString()
override fun dateTimeType(): String = "DATETIME2"
override fun timestampWithTimeZoneType(): String =
if ((currentDialect as? H2Dialect)?.h2Mode == H2Dialect.H2CompatibilityMode.SQLServer) {
if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.SQLServer) {
"TIMESTAMP(9) WITH TIME ZONE"
} else {
"DATETIMEOFFSET"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,39 @@ class NumericColumnTypesTests : DatabaseTestsBase() {
}
}
}

@Test
fun testByteAcceptsOnlyAllowedRange() {
val testTable = object : Table("test_table") {
val byte = byte("byte")
}

withTables(testTable) { testDb ->
val columnName = testTable.byte.nameInDatabaseCase()
val ddlEnding = when (testDb) {
in TestDB.ALL_POSTGRES_LIKE, TestDB.ORACLE, TestDB.SQLITE, TestDB.SQLSERVER ->
"CHECK ($columnName BETWEEN ${Byte.MIN_VALUE} and ${Byte.MAX_VALUE}))"
else -> "($columnName ${testTable.byte.columnType} NOT NULL)"
}
assertTrue(testTable.ddl.single().endsWith(ddlEnding, ignoreCase = true))

testTable.insert { it[byte] = Byte.MIN_VALUE }
testTable.insert { it[byte] = Byte.MAX_VALUE }
assertEquals(2, testTable.select(testTable.byte).count())

val tableName = testTable.nameInDatabaseCase()
assertFailAndRollback(
message = "CHECK constraint violation or out-of-range error for MySQL, MariaDB, and H2 (except for H2_V2_PSQL)"
) {
val outOfRangeValue = Byte.MIN_VALUE - 1
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
}
assertFailAndRollback(
message = "CHECK constraint violation or out-of-range error for MySQL, MariaDB, and H2 (except for H2_V2_PSQL)"
) {
val outOfRangeValue = Byte.MAX_VALUE + 1
exec("INSERT INTO $tableName ($columnName) VALUES ($outOfRangeValue)")
}
}
}
}

0 comments on commit 4865671

Please sign in to comment.