Skip to content

Commit

Permalink
fix: EXPOSED-384 CurrentTimestamp cannot be used with OffsetDateTimeC…
Browse files Browse the repository at this point in the history
…olumnType (#2081)

* fix: EXPOSED-384 CurrentTimestamp cannot be used with OffsetDateTimeColumnType

Since the 0.50.0 column type safety changes, CurrentTimestamp is no longer a class,
but an object, and it no longer returns type T, but Instant.

So its previous use as a default expression with a timezone compatible column is
no longer possible, as even changing to a singleton still required OffsetDateTime
as the return type.

This introduces a new object CurrentTimestampWithTimeZone for this specific use
to both modules exposed-java-time and exposed-kotlin-datetime.

Rather than repeating the same query builder logic, the 3 CurrentX objects now
extend from a base sealed class.
  • Loading branch information
bog-walk committed May 14, 2024
1 parent 6261abd commit e5d438a
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 39 deletions.
13 changes: 10 additions & 3 deletions exposed-java-time/api/exposed-java-time.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ public final class org/jetbrains/exposed/sql/javatime/CurrentDate : org/jetbrain
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/javatime/CurrentDateTime : org/jetbrains/exposed/sql/Function {
public final class org/jetbrains/exposed/sql/javatime/CurrentDateTime : org/jetbrains/exposed/sql/javatime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/javatime/CurrentDateTime;
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/javatime/CurrentTimestamp : org/jetbrains/exposed/sql/Function {
public final class org/jetbrains/exposed/sql/javatime/CurrentTimestamp : org/jetbrains/exposed/sql/javatime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/javatime/CurrentTimestamp;
}

public abstract class org/jetbrains/exposed/sql/javatime/CurrentTimestampBase : org/jetbrains/exposed/sql/Function {
public synthetic fun <init> (Lorg/jetbrains/exposed/sql/IColumnType;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/javatime/CurrentTimestampWithTimeZone : org/jetbrains/exposed/sql/javatime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/javatime/CurrentTimestampWithTimeZone;
}

public final class org/jetbrains/exposed/sql/javatime/Date : org/jetbrains/exposed/sql/Function {
public fun <init> (Lorg/jetbrains/exposed/sql/Expression;)V
public final fun getExpr ()Lorg/jetbrains/exposed/sql/Expression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ class Time<T : Temporal?>(val expr: Expression<T>) : Function<LocalTime>(JavaLoc
}

/**
* Represents an SQL function that returns the current date and time, as [LocalDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
* Represents the base SQL function that returns the current date and time, as determined by the specified [columnType].
*/
object CurrentDateTime : Function<LocalDateTime>(JavaLocalDateTimeColumnType.INSTANCE) {
sealed class CurrentTimestampBase<T>(columnType: IColumnType<T & Any>) : Function<T>(columnType) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
+when {
(currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == true -> "CURRENT_TIMESTAMP(6)"
Expand All @@ -54,19 +52,26 @@ object CurrentDate : Function<LocalDate>(JavaLocalDateColumnType.INSTANCE) {
}
}

/**
* Represents an SQL function that returns the current date and time, as [LocalDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
*/
object CurrentDateTime : CurrentTimestampBase<LocalDateTime>(JavaLocalDateTimeColumnType.INSTANCE)

/**
* Represents an SQL function that returns the current date and time, as [Instant].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
*/
object CurrentTimestamp : Function<Instant>(JavaInstantColumnType.INSTANCE) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
+when {
(currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == true -> "CURRENT_TIMESTAMP(6)"
else -> "CURRENT_TIMESTAMP"
}
}
}
object CurrentTimestamp : CurrentTimestampBase<Instant>(JavaInstantColumnType.INSTANCE)

/**
* Represents an SQL function that returns the current date and time with time zone, as [OffsetDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testTimestampWithTimeZoneDefaults
*/
object CurrentTimestampWithTimeZone : CurrentTimestampBase<OffsetDateTime>(JavaOffsetDateTimeColumnType.INSTANCE)

/** Represents an SQL function that extracts the year field from a given temporal [expr]. */
class Year<T : Temporal?>(val expr: Expression<T>) : Function<Int>(IntegerColumnType()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class DefaultsTest : DatabaseTestsBase() {
val testTable = object : IntIdTable("t") {
val t1 = timestampWithTimeZone("t1").default(nowWithTimeZone)
val t2 = timestampWithTimeZone("t2").defaultExpression(timestampWithTimeZoneLiteral)
val t3 = timestampWithTimeZone("t3").defaultExpression(CurrentTimestampWithTimeZone)
}

fun Expression<*>.itOrNull() = when {
Expand All @@ -425,7 +426,8 @@ class DefaultsTest : DatabaseTestsBase() {
"${"t".inProperCase()} (" +
"${"id".inProperCase()} ${currentDialectTest.dataTypeProvider.integerAutoincType()} PRIMARY KEY, " +
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}" +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentTimestampWithTimeZone.itOrNull()}" +
")"

val expected = if (currentDialectTest is OracleDialect ||
Expand All @@ -446,6 +448,9 @@ class DefaultsTest : DatabaseTestsBase() {
val row1 = testTable.selectAll().where { testTable.id eq id1 }.single()
assertEqualDateTime(nowWithTimeZone, row1[testTable.t1])
assertEqualDateTime(nowWithTimeZone, row1[testTable.t2])
val dbDefault = row1[testTable.t3]
assertEquals(dbDefault.offset, nowWithTimeZone.offset)
assertTrue { dbDefault.toLocalDateTime() >= nowWithTimeZone.toLocalDateTime() }

SchemaUtils.drop(testTable)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.joda.time.DateTimeZone
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class JodaTimeDefaultsTest : JodaTimeBaseTest() {
object TableWithDBDefault : IntIdTable() {
Expand Down Expand Up @@ -375,6 +376,7 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() {
val testTable = object : IntIdTable("t") {
val t1 = timestampWithTimeZone("t1").default(nowWithTimeZone)
val t2 = timestampWithTimeZone("t2").defaultExpression(timestampWithTimeZoneLiteral)
val t3 = timestampWithTimeZone("t3").defaultExpression(CurrentDateTime)
}

fun Expression<*>.itOrNull() = when {
Expand All @@ -394,7 +396,8 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() {
"${"t".inProperCase()} (" +
"${"id".inProperCase()} ${currentDialectTest.dataTypeProvider.integerAutoincType()} PRIMARY KEY, " +
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}" +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentDateTime.itOrNull()}" +
")"

val expected = if (currentDialectTest is OracleDialect ||
Expand All @@ -415,6 +418,7 @@ class JodaTimeDefaultsTest : JodaTimeBaseTest() {
val row1 = testTable.selectAll().where { testTable.id eq id1 }.single()
assertEqualDateTime(nowWithTimeZone, row1[testTable.t1])
assertEqualDateTime(nowWithTimeZone, row1[testTable.t2])
assertTrue { row1[testTable.t3].millis >= nowWithTimeZone.millis }

SchemaUtils.drop(testTable)
}
Expand Down
13 changes: 10 additions & 3 deletions exposed-kotlin-datetime/api/exposed-kotlin-datetime.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentDate : org/j
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentDateTime : org/jetbrains/exposed/sql/Function {
public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentDateTime : org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/kotlin/datetime/CurrentDateTime;
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestamp : org/jetbrains/exposed/sql/Function {
public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestamp : org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestamp;
}

public abstract class org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampBase : org/jetbrains/exposed/sql/Function {
public synthetic fun <init> (Lorg/jetbrains/exposed/sql/IColumnType;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V
}

public final class org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampWithTimeZone : org/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampBase {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/kotlin/datetime/CurrentTimestampWithTimeZone;
}

public final class org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnTypeKt {
public static final fun date (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
public static final fun datetime (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,9 @@ fun <T : LocalDateTime?> Time(expr: Expression<T>): Function<LocalTime> = TimeFu
fun <T : Instant?> Time(expr: Expression<T>): Function<LocalTime> = TimeFunction(expr)

/**
* Represents an SQL function that returns the current date and time, as [LocalDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
* Represents the base SQL function that returns the current date and time, as determined by the specified [columnType].
*/
object CurrentDateTime : Function<LocalDateTime>(KotlinLocalDateTimeColumnType.INSTANCE) {
sealed class CurrentTimestampBase<T>(columnType: IColumnType<T & Any>) : Function<T>(columnType) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
+when {
(currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == true -> "CURRENT_TIMESTAMP(6)"
Expand All @@ -62,6 +60,27 @@ object CurrentDateTime : Function<LocalDateTime>(KotlinLocalDateTimeColumnType.I
}
}

/**
* Represents an SQL function that returns the current date and time, as [LocalDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
*/
object CurrentDateTime : CurrentTimestampBase<LocalDateTime>(KotlinLocalDateTimeColumnType.INSTANCE)

/**
* Represents an SQL function that returns the current date and time, as [Instant].
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
*/
object CurrentTimestamp : CurrentTimestampBase<Instant>(KotlinInstantColumnType.INSTANCE)

/**
* Represents an SQL function that returns the current date and time with time zone, as [OffsetDateTime].
*
* @sample org.jetbrains.exposed.DefaultsTest.testTimestampWithTimeZoneDefaults
*/
object CurrentTimestampWithTimeZone : CurrentTimestampBase<OffsetDateTime>(KotlinOffsetDateTimeColumnType.INSTANCE)

/**
* Represents an SQL function that returns the current date, as [LocalDate].
*
Expand All @@ -77,20 +96,6 @@ object CurrentDate : Function<LocalDate>(KotlinLocalDateColumnType.INSTANCE) {
}
}

/**
* Represents an SQL function that returns the current date and time.
*
* @sample org.jetbrains.exposed.DefaultsTest.testConsistentSchemeWithFunctionAsDefaultExpression
*/
object CurrentTimestamp : Function<Instant>(KotlinInstantColumnType.INSTANCE) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
+when {
(currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == true -> "CURRENT_TIMESTAMP(6)"
else -> "CURRENT_TIMESTAMP"
}
}
}

class YearInternal(val expr: Expression<*>) : Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
val dialect = currentDialect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class DefaultsTest : DatabaseTestsBase() {
val testTable = object : IntIdTable("t") {
val t1 = timestampWithTimeZone("t1").default(nowWithTimeZone)
val t2 = timestampWithTimeZone("t2").defaultExpression(timestampWithTimeZoneLiteral)
val t3 = timestampWithTimeZone("t3").defaultExpression(CurrentTimestampWithTimeZone)
}

fun Expression<*>.itOrNull() = when {
Expand All @@ -429,7 +430,8 @@ class DefaultsTest : DatabaseTestsBase() {
"${"t".inProperCase()} (" +
"${"id".inProperCase()} ${currentDialectTest.dataTypeProvider.integerAutoincType()} PRIMARY KEY, " +
"${"t1".inProperCase()} $timestampWithTimeZoneType${testTable.t1.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}" +
"${"t2".inProperCase()} $timestampWithTimeZoneType${testTable.t2.constraintNamePart()} ${timestampWithTimeZoneLiteral.itOrNull()}, " +
"${"t3".inProperCase()} $timestampWithTimeZoneType${testTable.t3.constraintNamePart()} ${CurrentTimestampWithTimeZone.itOrNull()}" +
")"

val expected = if (currentDialectTest is OracleDialect ||
Expand All @@ -450,6 +452,9 @@ class DefaultsTest : DatabaseTestsBase() {
val row1 = testTable.selectAll().where { testTable.id eq id1 }.single()
assertEqualDateTime(nowWithTimeZone, row1[testTable.t1])
assertEqualDateTime(nowWithTimeZone, row1[testTable.t2])
val dbDefault = row1[testTable.t3]
assertEquals(dbDefault.offset, nowWithTimeZone.offset)
assertTrue { dbDefault.toLocalDateTime().toKotlinLocalDateTime() >= nowWithTimeZone.toLocalDateTime().toKotlinLocalDateTime() }

SchemaUtils.drop(testTable)
}
Expand Down

0 comments on commit e5d438a

Please sign in to comment.