From 6945dd5542f521d8591c2dfe951dc3398a7f010b Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Thu, 25 Apr 2024 14:20:07 +0200 Subject: [PATCH 1/6] EXPOSED-353 dateLiteral does not work on OracleDB 12c or 19c #1338 --- .../exposed/sql/vendors/OracleDialect.kt | 6 - .../sql/javatime/JavaDateColumnType.kt | 33 ++- .../jetbrains/exposed/DateTimeLiteralTest.kt | 208 ++++++++++++++++++ .../exposed/sql/jodatime/DateColumnType.kt | 8 +- .../exposed/JodaDateTimeLiteralTest.kt | 70 ++++++ 5 files changed, 308 insertions(+), 17 deletions(-) create mode 100644 exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt create mode 100644 exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt index c9d9005b88..b3db512d3c 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt @@ -66,12 +66,6 @@ internal object OracleDataTypeProvider : DataTypeProvider() { override fun jsonType(): String = "VARCHAR2(4000)" - override fun processForDefaultValue(e: Expression<*>): String = when { - e is LiteralOp<*> && (e.columnType as? IDateColumnType)?.hasTimePart == false -> "DATE ${super.processForDefaultValue(e)}" - e is LiteralOp<*> && e.columnType is IDateColumnType -> "TIMESTAMP ${super.processForDefaultValue(e)}" - else -> super.processForDefaultValue(e) - } - override fun hexToDb(hexString: String): String = "HEXTORAW('$hexString')" } diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt index 7649c653ca..b2678d3dbb 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt @@ -127,7 +127,14 @@ class JavaLocalDateColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: LocalDate): String { val instant = Instant.from(value.atStartOfDay(ZoneId.systemDefault())) - return "'${DEFAULT_DATE_STRING_FORMATTER.format(instant)}'" + val formatted = DEFAULT_DATE_STRING_FORMATTER.format(instant) + if (currentDialect is OracleDialect) { + // Date literal in Oracle DB must match NLS_DATE_FORMAT parameter. + // That parameter can be changed on DB level. + // But format can be also specified per literal with TO_DATE function + return "TO_DATE('$formatted', 'YYYY-MM-DD')" + } + return "'$formatted'" } override fun valueFromDB(value: Any): LocalDate? = when (value) { @@ -173,7 +180,8 @@ class JavaLocalDateTimeColumnType : ColumnType(), IDateColumnType val dialect = currentDialect return when { dialect is SQLiteDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" - dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" + dialect is OracleDialect -> + "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" dialect is MysqlDialect -> { val formatter = if (dialect.isFractionDateTimeSupported()) MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER else MYSQL_DATE_TIME_STRING_FORMATTER "'${formatter.format(instant)}'" @@ -241,13 +249,14 @@ class JavaLocalTimeColumnType : ColumnType(), IDateColumnType { override fun sqlType(): String = currentDialect.dataTypeProvider.timeType() override fun nonNullValueToString(value: LocalTime): String { - val dialect = currentDialect - val formatter = if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { - ORACLE_TIME_STRING_FORMATTER - } else { - DEFAULT_TIME_STRING_FORMATTER + if (currentDialect is OracleDialect) { + return "TO_TIMESTAMP('${ORACLE_TIME_STRING_FORMATTER.format(value)}', 'YYYY-MM-DD HH24:MI:SS')" + } + if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { + return "'${ORACLE_TIME_STRING_FORMATTER.format(value)}'" } - return "'${formatter.format(value)}'" + + return "'${DEFAULT_TIME_STRING_FORMATTER.format(value)}'" } override fun valueFromDB(value: Any): LocalTime = when (value) { @@ -295,8 +304,12 @@ class JavaInstantColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: Instant): String { val dialect = currentDialect return when { - dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> + dialect is OracleDialect -> + "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + + dialect is SQLiteDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value)}'" + dialect is MysqlDialect -> { val formatter = if (dialect.isFractionDateTimeSupported()) MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER else MYSQL_DATE_TIME_STRING_FORMATTER "'${formatter.format(value)}'" @@ -350,7 +363,7 @@ class JavaOffsetDateTimeColumnType : ColumnType(), IDateColumnTy override fun nonNullValueToString(value: OffsetDateTime): String = when (currentDialect) { is SQLiteDialect -> "'${value.format(SQLITE_OFFSET_DATE_TIME_FORMATTER)}'" is MysqlDialect -> "'${value.format(MYSQL_OFFSET_DATE_TIME_FORMATTER)}'" - is OracleDialect -> "'${value.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}'" + is OracleDialect -> "TO_TIMESTAMP_TZ('${value.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}', 'YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM')" else -> "'${value.format(DEFAULT_OFFSET_DATE_TIME_FORMATTER)}'" } diff --git a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt new file mode 100644 index 0000000000..ff1ce63bc8 --- /dev/null +++ b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt @@ -0,0 +1,208 @@ +package org.jetbrains.exposed + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.javatime.* +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.tests.DatabaseTestsBase +import org.jetbrains.exposed.sql.tests.shared.assertEquals +import org.jetbrains.exposed.sql.update +import org.junit.Ignore +import org.junit.Test +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import kotlin.test.assertNotNull + +class DateTimeLiteralTest : DatabaseTestsBase() { + private val defaultDate = LocalDate.of(2000, 1, 1) + private val futureDate = LocalDate.of(3000, 1, 1) + + object TableWithDate : IntIdTable() { + val date = date("date") + } + + private val defaultDatetime = LocalDateTime.of(2000, 1, 1, 8, 0, 0, 100000000) + private val futureDatetime = LocalDateTime.of(3000, 1, 1, 8, 0, 0, 100000000) + + object TableWithDatetime : IntIdTable() { + val datetime = datetime("datetime") + } + + private val defaultTimestamp = Instant.parse("2000-01-01T01:00:00.00Z") + private val futureTimestamp = Instant.parse("3000-01-01T01:00:00.00Z") + + object TableWithTimestamp : IntIdTable() { + val timestamp = timestamp("timestamp") + } + + private val defaultLocalTime = LocalTime.of(1, 0, 0) + private val futureLocalTime = LocalTime.of(18, 0, 0) + + object TableWithTime : IntIdTable() { + val time = time("time") + } + + @Test + fun testInsertWithDateLiteral() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = dateLiteral(defaultDate) + } + assertEquals(defaultDate, TableWithDate.selectAll().first()[TableWithDate.date]) + } + } + + @Test + fun testUpdateWithDateLiteral() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = defaultDate + } + + TableWithDate.update { it[date] = dateLiteral(futureDate) } + assertEquals(futureDate, TableWithDate.selectAll().first()[TableWithDate.date]) + } + } + + @Test + fun testSelectByDateLiteralEquality() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = defaultDate + } + + val query = TableWithDate.select(TableWithDate.date).where { TableWithDate.date eq dateLiteral(defaultDate) } + assertEquals(defaultDate, query.single()[TableWithDate.date]) + } + } + + @Test + fun testSelectByDateLiteralComparison() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = defaultDate + } + val query = TableWithDate.selectAll().where { TableWithDate.date less dateLiteral(futureDate) } + assertNotNull(query.firstOrNull()) + } + } + + @Test + fun testInsertDatetimeLiteral() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = dateTimeLiteral(defaultDatetime) + } + assertEquals(defaultDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) + } + } + + @Test + fun testUpdateWithDatetimeLiteral() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + + TableWithDatetime.update { it[datetime] = dateTimeLiteral(futureDatetime) } + assertEquals(futureDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) + } + } + + @Test + fun testSelectByDatetimeLiteralEquality() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + + val query = TableWithDatetime.select(TableWithDatetime.datetime).where { TableWithDatetime.datetime eq dateTimeLiteral(defaultDatetime) } + assertEquals(defaultDatetime, query.single()[TableWithDatetime.datetime]) + } + } + + @Test + fun testSelectByDatetimeLiteralComparison() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + val query = TableWithDatetime.selectAll().where { TableWithDatetime.datetime less dateTimeLiteral(futureDatetime) } + assertNotNull(query.firstOrNull()) + } + } + + @Test + fun testInsertWithTimestampLiteral() { + withTables(TableWithTimestamp) { + TableWithTimestamp.insert { + it[timestamp] = timestampLiteral(defaultTimestamp) + } + assertEquals(defaultTimestamp, TableWithTimestamp.selectAll().first()[TableWithTimestamp.timestamp]) + } + } + + @Test + fun testUpdateWithTimestampLiteral() { + withTables(TableWithTimestamp) { + TableWithTimestamp.insert { + it[timestamp] = defaultTimestamp + } + + TableWithTimestamp.update { it[timestamp] = timestampLiteral(futureTimestamp) } + assertEquals(futureTimestamp, TableWithTimestamp.selectAll().first()[TableWithTimestamp.timestamp]) + } + } + + @Test + fun testSelectByTimestampLiteralEquality() { + withTables(TableWithTimestamp) { + TableWithTimestamp.insert { + it[timestamp] = defaultTimestamp + } + + val query = TableWithTimestamp.select(TableWithTimestamp.timestamp).where { TableWithTimestamp.timestamp eq timestampLiteral(defaultTimestamp) } + assertEquals(defaultTimestamp, query.single()[TableWithTimestamp.timestamp]) + } + } + + @Test + fun testInsertTimeLiteral() { + withTables(TableWithTime) { + TableWithTime.insert { + it[time] = timeLiteral(defaultLocalTime) + } + assertEquals(defaultLocalTime, TableWithTime.selectAll().first()[TableWithTime.time]) + } + } + + @Test + fun testUpdateWithTimeLiteral() { + withTables(TableWithTime) { + TableWithTime.insert { + it[time] = defaultLocalTime + } + + TableWithTime.update { it[time] = timeLiteral(futureLocalTime) } + assertEquals(futureLocalTime, TableWithTime.selectAll().first()[TableWithTime.time]) + } + } + + @Test + @Ignore( + "Test fails with 'Collection is empty.' message. It can not find anything in db after insert. " + + "But SQL requests looks correct, and work well manually applied." + ) + fun testSelectByTimeLiteralEquality() { + withTables(TableWithTime) { + TableWithTime.insert { + it[time] = defaultLocalTime + } + + val query = TableWithTime.select(TableWithTime.id, TableWithTime.time).where { TableWithTime.time eq timeLiteral(defaultLocalTime) } + assertEquals(defaultLocalTime, query.single()[TableWithTime.time]) + } + } +} diff --git a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt index b85c1fb84d..564bdcbad1 100644 --- a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt +++ b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt @@ -66,12 +66,18 @@ class DateColumnType(val time: Boolean) : ColumnType(), IDateColumnTyp override fun nonNullValueToString(value: DateTime): String { return if (time) { when { + currentDialect is OracleDialect -> + "TO_TIMESTAMP('${DEFAULT_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}', 'YYYY-MM-DD HH24:MI:SS.FF3')" (currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == false -> "'${MYSQL_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}'" else -> "'${DEFAULT_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}'" } } else { - "'${DEFAULT_DATE_STRING_FORMATTER.print(value)}'" + val formatted = DEFAULT_DATE_STRING_FORMATTER.print(value) + if (currentDialect is OracleDialect) { + return "TO_DATE('$formatted', 'YYYY-MM-DD')" + } + return "'$formatted'" } } diff --git a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt new file mode 100644 index 0000000000..6babc9bf7f --- /dev/null +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt @@ -0,0 +1,70 @@ +package org.jetbrains.exposed + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.jodatime.dateTimeLiteral +import org.jetbrains.exposed.sql.jodatime.datetime +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.tests.DatabaseTestsBase +import org.jetbrains.exposed.sql.tests.shared.assertEquals +import org.jetbrains.exposed.sql.update +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat +import org.junit.Test +import kotlin.test.assertNotNull + +class JodaDateTimeLiteralTest : DatabaseTestsBase() { + private val pattern = "dd-mm-yyyy hh.mm.ss" + + private val defaultDatetime: DateTime = DateTime.parse("01-01-2000 01.00.00", DateTimeFormat.forPattern(pattern)) + private val futureDatetime: DateTime = DateTime.parse("01-01-3000 01.00.00", DateTimeFormat.forPattern(pattern)) + + object TableWithDatetime : IntIdTable() { + val datetime = datetime("datetime") + } + + @Test + fun testInsertWithDateLiteral() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = dateTimeLiteral(defaultDatetime) + } + assertEquals(defaultDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) + } + } + + @Test + fun testUpdateWithDatetimeLiteral() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + + TableWithDatetime.update { it[datetime] = dateTimeLiteral(futureDatetime) } + assertEquals(futureDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) + } + } + + @Test + fun testSelectByDatetimeLiteralEquality() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + + val query = TableWithDatetime.select(TableWithDatetime.datetime).where { TableWithDatetime.datetime eq dateTimeLiteral(defaultDatetime) } + assertEquals(defaultDatetime, query.single()[TableWithDatetime.datetime]) + } + } + + @Test + fun testSelectByDatetimeLiteralComparison() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + val query = TableWithDatetime.selectAll().where { TableWithDatetime.datetime less dateTimeLiteral(futureDatetime) } + assertNotNull(query.firstOrNull()) + } + } +} From 75328e947d080284c2f413fd4696e73476ba5731 Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Fri, 26 Apr 2024 15:41:26 +0200 Subject: [PATCH 2/6] Fix review issues --- .../sql/javatime/JavaDateColumnType.kt | 9 +- .../jetbrains/exposed/DateTimeLiteralTest.kt | 115 ------------------ .../exposed/sql/jodatime/DateColumnType.kt | 3 +- 3 files changed, 5 insertions(+), 122 deletions(-) diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt index b2678d3dbb..6ccae5c88b 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt @@ -249,13 +249,10 @@ class JavaLocalTimeColumnType : ColumnType(), IDateColumnType { override fun sqlType(): String = currentDialect.dataTypeProvider.timeType() override fun nonNullValueToString(value: LocalTime): String { - if (currentDialect is OracleDialect) { - return "TO_TIMESTAMP('${ORACLE_TIME_STRING_FORMATTER.format(value)}', 'YYYY-MM-DD HH24:MI:SS')" - } - if (currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { - return "'${ORACLE_TIME_STRING_FORMATTER.format(value)}'" + val dialect = currentDialect + if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { + return "TIMESTAMP '${ORACLE_TIME_STRING_FORMATTER.format(value)}'" } - return "'${DEFAULT_TIME_STRING_FORMATTER.format(value)}'" } diff --git a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt index ff1ce63bc8..7f8eee94ed 100644 --- a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt +++ b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt @@ -6,13 +6,10 @@ import org.jetbrains.exposed.sql.javatime.* import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.shared.assertEquals -import org.jetbrains.exposed.sql.update -import org.junit.Ignore import org.junit.Test import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime import kotlin.test.assertNotNull class DateTimeLiteralTest : DatabaseTestsBase() { @@ -31,41 +28,11 @@ class DateTimeLiteralTest : DatabaseTestsBase() { } private val defaultTimestamp = Instant.parse("2000-01-01T01:00:00.00Z") - private val futureTimestamp = Instant.parse("3000-01-01T01:00:00.00Z") object TableWithTimestamp : IntIdTable() { val timestamp = timestamp("timestamp") } - private val defaultLocalTime = LocalTime.of(1, 0, 0) - private val futureLocalTime = LocalTime.of(18, 0, 0) - - object TableWithTime : IntIdTable() { - val time = time("time") - } - - @Test - fun testInsertWithDateLiteral() { - withTables(TableWithDate) { - TableWithDate.insert { - it[date] = dateLiteral(defaultDate) - } - assertEquals(defaultDate, TableWithDate.selectAll().first()[TableWithDate.date]) - } - } - - @Test - fun testUpdateWithDateLiteral() { - withTables(TableWithDate) { - TableWithDate.insert { - it[date] = defaultDate - } - - TableWithDate.update { it[date] = dateLiteral(futureDate) } - assertEquals(futureDate, TableWithDate.selectAll().first()[TableWithDate.date]) - } - } - @Test fun testSelectByDateLiteralEquality() { withTables(TableWithDate) { @@ -89,28 +56,6 @@ class DateTimeLiteralTest : DatabaseTestsBase() { } } - @Test - fun testInsertDatetimeLiteral() { - withTables(TableWithDatetime) { - TableWithDatetime.insert { - it[datetime] = dateTimeLiteral(defaultDatetime) - } - assertEquals(defaultDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) - } - } - - @Test - fun testUpdateWithDatetimeLiteral() { - withTables(TableWithDatetime) { - TableWithDatetime.insert { - it[datetime] = defaultDatetime - } - - TableWithDatetime.update { it[datetime] = dateTimeLiteral(futureDatetime) } - assertEquals(futureDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) - } - } - @Test fun testSelectByDatetimeLiteralEquality() { withTables(TableWithDatetime) { @@ -134,28 +79,6 @@ class DateTimeLiteralTest : DatabaseTestsBase() { } } - @Test - fun testInsertWithTimestampLiteral() { - withTables(TableWithTimestamp) { - TableWithTimestamp.insert { - it[timestamp] = timestampLiteral(defaultTimestamp) - } - assertEquals(defaultTimestamp, TableWithTimestamp.selectAll().first()[TableWithTimestamp.timestamp]) - } - } - - @Test - fun testUpdateWithTimestampLiteral() { - withTables(TableWithTimestamp) { - TableWithTimestamp.insert { - it[timestamp] = defaultTimestamp - } - - TableWithTimestamp.update { it[timestamp] = timestampLiteral(futureTimestamp) } - assertEquals(futureTimestamp, TableWithTimestamp.selectAll().first()[TableWithTimestamp.timestamp]) - } - } - @Test fun testSelectByTimestampLiteralEquality() { withTables(TableWithTimestamp) { @@ -167,42 +90,4 @@ class DateTimeLiteralTest : DatabaseTestsBase() { assertEquals(defaultTimestamp, query.single()[TableWithTimestamp.timestamp]) } } - - @Test - fun testInsertTimeLiteral() { - withTables(TableWithTime) { - TableWithTime.insert { - it[time] = timeLiteral(defaultLocalTime) - } - assertEquals(defaultLocalTime, TableWithTime.selectAll().first()[TableWithTime.time]) - } - } - - @Test - fun testUpdateWithTimeLiteral() { - withTables(TableWithTime) { - TableWithTime.insert { - it[time] = defaultLocalTime - } - - TableWithTime.update { it[time] = timeLiteral(futureLocalTime) } - assertEquals(futureLocalTime, TableWithTime.selectAll().first()[TableWithTime.time]) - } - } - - @Test - @Ignore( - "Test fails with 'Collection is empty.' message. It can not find anything in db after insert. " + - "But SQL requests looks correct, and work well manually applied." - ) - fun testSelectByTimeLiteralEquality() { - withTables(TableWithTime) { - TableWithTime.insert { - it[time] = defaultLocalTime - } - - val query = TableWithTime.select(TableWithTime.id, TableWithTime.time).where { TableWithTime.time eq timeLiteral(defaultLocalTime) } - assertEquals(defaultLocalTime, query.single()[TableWithTime.time]) - } - } } diff --git a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt index 564bdcbad1..40a465dbbc 100644 --- a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt +++ b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt @@ -174,7 +174,8 @@ class DateTimeWithTimeZoneColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: DateTime): String = when (currentDialect) { is SQLiteDialect -> "'${SQLITE_AND_MYSQL_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" is MysqlDialect -> "'${SQLITE_AND_MYSQL_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" - is OracleDialect -> "'${ORACLE_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" + is OracleDialect -> + "TO_TIMESTAMP_TZ('${ORACLE_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM')" else -> "'${DEFAULT_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" } From bb7a78cac559764857de688a380edf2ac2dcb7f8 Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Mon, 29 Apr 2024 10:19:28 +0200 Subject: [PATCH 3/6] Fix KotlinDateColumnType, move construction of oracle date literals into separate functions --- .../sql/javatime/JavaDateColumnType.kt | 19 +++++--- .../exposed/sql/jodatime/DateColumnType.kt | 20 ++++++--- .../kotlin/datetime/KotlinDateColumnType.kt | 44 ++++++++++++++----- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt index 6ccae5c88b..47a77314bb 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateColumnType.kt @@ -111,6 +111,15 @@ private fun dateTimeWithFractionFormat(fraction: Int): DateTimeFormatter { return DateTimeFormatter.ofPattern(newFormat).withLocale(Locale.ROOT).withZone(ZoneId.systemDefault()) } +private fun oracleDateTimeLiteral(instant: Instant) = + "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + +private fun oracleDateTimeWithTimezoneLiteral(dateTime: OffsetDateTime) = + "TO_TIMESTAMP_TZ('${dateTime.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}', 'YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM')" + +private fun oracleDateLiteral(instant: Instant) = + "TO_DATE('${DEFAULT_DATE_STRING_FORMATTER.format(instant)}', 'YYYY-MM-DD')" + @Suppress("MagicNumber") private val LocalDate.millis get() = atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000 @@ -132,7 +141,7 @@ class JavaLocalDateColumnType : ColumnType(), IDateColumnType { // Date literal in Oracle DB must match NLS_DATE_FORMAT parameter. // That parameter can be changed on DB level. // But format can be also specified per literal with TO_DATE function - return "TO_DATE('$formatted', 'YYYY-MM-DD')" + return oracleDateLiteral(instant) } return "'$formatted'" } @@ -180,8 +189,7 @@ class JavaLocalDateTimeColumnType : ColumnType(), IDateColumnType val dialect = currentDialect return when { dialect is SQLiteDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" - dialect is OracleDialect -> - "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + dialect is OracleDialect -> oracleDateTimeLiteral(instant) dialect is MysqlDialect -> { val formatter = if (dialect.isFractionDateTimeSupported()) MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER else MYSQL_DATE_TIME_STRING_FORMATTER "'${formatter.format(instant)}'" @@ -301,8 +309,7 @@ class JavaInstantColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: Instant): String { val dialect = currentDialect return when { - dialect is OracleDialect -> - "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + dialect is OracleDialect -> oracleDateTimeLiteral(value) dialect is SQLiteDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value)}'" @@ -360,7 +367,7 @@ class JavaOffsetDateTimeColumnType : ColumnType(), IDateColumnTy override fun nonNullValueToString(value: OffsetDateTime): String = when (currentDialect) { is SQLiteDialect -> "'${value.format(SQLITE_OFFSET_DATE_TIME_FORMATTER)}'" is MysqlDialect -> "'${value.format(MYSQL_OFFSET_DATE_TIME_FORMATTER)}'" - is OracleDialect -> "TO_TIMESTAMP_TZ('${value.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}', 'YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM')" + is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value) else -> "'${value.format(DEFAULT_OFFSET_DATE_TIME_FORMATTER)}'" } diff --git a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt index 40a465dbbc..f15153f07e 100644 --- a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt +++ b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt @@ -50,6 +50,15 @@ private fun dateTimeWithFractionFormat(fraction: Int): DateTimeFormatter { return DateTimeFormat.forPattern(newFormat) } +private fun oracleDateTimeLiteral(dateTime: DateTime) = + "TO_TIMESTAMP('${DEFAULT_DATE_TIME_STRING_FORMATTER.print(dateTime)}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + +private fun oracleDateTimeWithTimezoneLiteral(dateTime: DateTime) = + "TO_TIMESTAMP_TZ('${ORACLE_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(dateTime)}', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM')" + +private fun oracleDateLiteral(dateTime: DateTime) = + "TO_DATE('${DEFAULT_DATE_STRING_FORMATTER.print(dateTime)}', 'YYYY-MM-DD')" + /** * Column for storing dates, as [DateTime]. If [time] is set to `true`, both date and time data is stored. * @@ -66,18 +75,16 @@ class DateColumnType(val time: Boolean) : ColumnType(), IDateColumnTyp override fun nonNullValueToString(value: DateTime): String { return if (time) { when { - currentDialect is OracleDialect -> - "TO_TIMESTAMP('${DEFAULT_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + currentDialect is OracleDialect -> oracleDateTimeLiteral(value.toDateTime(DateTimeZone.getDefault())) (currentDialect as? MysqlDialect)?.isFractionDateTimeSupported() == false -> "'${MYSQL_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}'" else -> "'${DEFAULT_DATE_TIME_STRING_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}'" } } else { - val formatted = DEFAULT_DATE_STRING_FORMATTER.print(value) if (currentDialect is OracleDialect) { - return "TO_DATE('$formatted', 'YYYY-MM-DD')" + return oracleDateLiteral(value) } - return "'$formatted'" + return "'${DEFAULT_DATE_STRING_FORMATTER.print(value)}'" } } @@ -174,8 +181,7 @@ class DateTimeWithTimeZoneColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: DateTime): String = when (currentDialect) { is SQLiteDialect -> "'${SQLITE_AND_MYSQL_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" is MysqlDialect -> "'${SQLITE_AND_MYSQL_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" - is OracleDialect -> - "TO_TIMESTAMP_TZ('${ORACLE_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value.toDateTime(DateTimeZone.getDefault()))}', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM')" + is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value.toDateTime(DateTimeZone.getDefault())) else -> "'${DEFAULT_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" } diff --git a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt index 122050e70e..257ec5e85d 100644 --- a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt +++ b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt @@ -118,6 +118,18 @@ private fun dateTimeWithFractionFormat(fraction: Int): DateTimeFormatter { return DateTimeFormatter.ofPattern(newFormat).withLocale(Locale.ROOT).withZone(ZoneId.systemDefault()) } +private fun oracleDateTimeLiteral(instant: Instant) = + "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant.toJavaInstant())}', 'YYYY-MM-DD HH24:MI:SS.FF3')" + +private fun oracleLocalTimeLiteral(time: LocalTime) = + "TO_TIMESTAMP('${ORACLE_TIME_STRING_FORMATTER.format(time.toJavaLocalTime())}', 'YYYY-MM-DD HH24:MI:SS')" + +private fun oracleDateTimeWithTimezoneLiteral(dateTime: OffsetDateTime) = + "TO_TIMESTAMP_TZ('${dateTime.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}', 'YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM')" + +private fun oracleDateLiteral(instant: Instant) = + "TO_DATE('${DEFAULT_DATE_STRING_FORMATTER.format(instant.toJavaInstant())}', 'YYYY-MM-DD')" + private val LocalDate.millis get() = this.atStartOfDayIn(TimeZone.currentSystemDefault()).epochSeconds * MILLIS_IN_SECOND /** @@ -132,6 +144,9 @@ class KotlinLocalDateColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: LocalDate): String { val instant = Instant.fromEpochMilliseconds(value.atStartOfDayIn(DEFAULT_TIME_ZONE).toEpochMilliseconds()) + if (currentDialect is OracleDialect) { + return oracleDateLiteral(instant) + } return "'${DEFAULT_DATE_STRING_FORMATTER.format(instant.toJavaInstant())}'" } @@ -178,8 +193,8 @@ class KotlinLocalDateTimeColumnType : ColumnType(), IDateColumnTy val dialect = currentDialect return when { dialect is SQLiteDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant.toJavaInstant())}'" - dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> - "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant.toJavaInstant())}'" + dialect is OracleDialect -> oracleDateTimeLiteral(instant) + dialect is MysqlDialect -> { val formatter = if (dialect.isFractionDateTimeSupported()) MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER else MYSQL_DATE_TIME_STRING_FORMATTER "'${formatter.format(instant.toJavaInstant())}'" @@ -249,14 +264,13 @@ class KotlinLocalTimeColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: LocalTime): String { val dialect = currentDialect - val formatter = if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { - ORACLE_TIME_STRING_FORMATTER - } else { - DEFAULT_TIME_STRING_FORMATTER + + if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { + return oracleLocalTimeLiteral(value) } val instant = value.toJavaLocalTime() - return "'${formatter.format(instant)}'" + return "'${DEFAULT_TIME_STRING_FORMATTER.format(instant)}'" } override fun valueFromDB(value: Any): LocalTime = when (value) { @@ -274,6 +288,7 @@ class KotlinLocalTimeColumnType : ColumnType(), IDateColumnType { } java.time.LocalTime.parse(value, formatter).toKotlinLocalTime() } + else -> valueFromDB(value.toString()) } @@ -307,12 +322,13 @@ class KotlinInstantColumnType : ColumnType(), IDateColumnType { val dialect = currentDialect return when { - dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> - "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" dialect is MysqlDialect -> { val formatter = if (dialect.isFractionDateTimeSupported()) MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER else MYSQL_DATE_TIME_STRING_FORMATTER "'${formatter.format(instant)}'" } + + dialect is OracleDialect -> oracleDateTimeLiteral(value) + else -> "'${DEFAULT_DATE_TIME_STRING_FORMATTER.format(instant)}'" } } @@ -330,6 +346,7 @@ class KotlinInstantColumnType : ColumnType(), IDateColumnType { override fun notNullValueToDB(value: Instant): Any = when { currentDialect is SQLiteDialect -> SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value.toJavaInstant()) + else -> java.sql.Timestamp.from(value.toJavaInstant()) } @@ -338,8 +355,10 @@ class KotlinInstantColumnType : ColumnType(), IDateColumnType { return when { dialect is PostgreSQLDialect -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value.toJavaInstant()).trimEnd('0').trimEnd('.')}'::timestamp without time zone" + dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value.toJavaInstant()).trimEnd('0').trimEnd('.')}'" + else -> super.nonNullValueAsDefaultString(value) } } @@ -362,7 +381,7 @@ class KotlinOffsetDateTimeColumnType : ColumnType(), IDateColumn override fun nonNullValueToString(value: OffsetDateTime): String = when (currentDialect) { is SQLiteDialect -> "'${value.format(SQLITE_OFFSET_DATE_TIME_FORMATTER)}'" is MysqlDialect -> "'${value.format(MYSQL_OFFSET_DATE_TIME_FORMATTER)}'" - is OracleDialect -> "'${value.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}'" + is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value) else -> "'${value.format(DEFAULT_OFFSET_DATE_TIME_FORMATTER)}'" } @@ -375,6 +394,7 @@ class KotlinOffsetDateTimeColumnType : ColumnType(), IDateColumn OffsetDateTime.parse(value) } } + else -> error("Unexpected value: $value of ${value::class.qualifiedName}") } @@ -394,9 +414,13 @@ class KotlinOffsetDateTimeColumnType : ColumnType(), IDateColumn return when { dialect is PostgreSQLDialect -> // +00 appended because PostgreSQL stores it in UTC time zone "'${value.format(POSTGRESQL_OFFSET_DATE_TIME_AS_DEFAULT_FORMATTER).trimEnd('0').trimEnd('.')}+00'::timestamp with time zone" + dialect is H2Dialect && dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "'${value.format(POSTGRESQL_OFFSET_DATE_TIME_AS_DEFAULT_FORMATTER).trimEnd('0').trimEnd('.')}'" + dialect is MysqlDialect -> "'${value.format(MYSQL_OFFSET_DATE_TIME_AS_DEFAULT_FORMATTER)}'" + + dialect is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value) else -> super.nonNullValueAsDefaultString(value) } } From 43958f6a6787cd8a92d33c694c0c3806f8e9e35c Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Mon, 29 Apr 2024 11:05:07 +0200 Subject: [PATCH 4/6] Fix time format for H2_ORACLE --- .../exposed/sql/kotlin/datetime/KotlinDateColumnType.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt index 257ec5e85d..244f81157f 100644 --- a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt +++ b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt @@ -265,12 +265,11 @@ class KotlinLocalTimeColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: LocalTime): String { val dialect = currentDialect - if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { - return oracleLocalTimeLiteral(value) + return when { + dialect is OracleDialect -> oracleLocalTimeLiteral(value) + dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "TIMESTAMP '${ORACLE_TIME_STRING_FORMATTER.format(value.toJavaLocalTime())}'" + else -> "'${DEFAULT_TIME_STRING_FORMATTER.format(value.toJavaLocalTime())}'" } - - val instant = value.toJavaLocalTime() - return "'${DEFAULT_TIME_STRING_FORMATTER.format(instant)}'" } override fun valueFromDB(value: Any): LocalTime = when (value) { From 8db64956fe4505900edee84f31f128d4c033fe96 Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Tue, 30 Apr 2024 08:28:42 +0200 Subject: [PATCH 5/6] Review issues: Remove insert/update tests, revert changes in KotlinLocalTimeColumnType --- .../exposed/JodaDateTimeLiteralTest.kt | 23 ------------------- .../kotlin/datetime/KotlinDateColumnType.kt | 12 ++++------ 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt index 6babc9bf7f..1dfb806ad2 100644 --- a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt @@ -7,7 +7,6 @@ import org.jetbrains.exposed.sql.jodatime.datetime import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.shared.assertEquals -import org.jetbrains.exposed.sql.update import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import org.junit.Test @@ -23,28 +22,6 @@ class JodaDateTimeLiteralTest : DatabaseTestsBase() { val datetime = datetime("datetime") } - @Test - fun testInsertWithDateLiteral() { - withTables(TableWithDatetime) { - TableWithDatetime.insert { - it[datetime] = dateTimeLiteral(defaultDatetime) - } - assertEquals(defaultDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) - } - } - - @Test - fun testUpdateWithDatetimeLiteral() { - withTables(TableWithDatetime) { - TableWithDatetime.insert { - it[datetime] = defaultDatetime - } - - TableWithDatetime.update { it[datetime] = dateTimeLiteral(futureDatetime) } - assertEquals(futureDatetime, TableWithDatetime.selectAll().first()[TableWithDatetime.datetime]) - } - } - @Test fun testSelectByDatetimeLiteralEquality() { withTables(TableWithDatetime) { diff --git a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt index 244f81157f..feb0cdeb30 100644 --- a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt +++ b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt @@ -121,9 +121,6 @@ private fun dateTimeWithFractionFormat(fraction: Int): DateTimeFormatter { private fun oracleDateTimeLiteral(instant: Instant) = "TO_TIMESTAMP('${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant.toJavaInstant())}', 'YYYY-MM-DD HH24:MI:SS.FF3')" -private fun oracleLocalTimeLiteral(time: LocalTime) = - "TO_TIMESTAMP('${ORACLE_TIME_STRING_FORMATTER.format(time.toJavaLocalTime())}', 'YYYY-MM-DD HH24:MI:SS')" - private fun oracleDateTimeWithTimezoneLiteral(dateTime: OffsetDateTime) = "TO_TIMESTAMP_TZ('${dateTime.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}', 'YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM')" @@ -264,12 +261,13 @@ class KotlinLocalTimeColumnType : ColumnType(), IDateColumnType { override fun nonNullValueToString(value: LocalTime): String { val dialect = currentDialect + val instant = value.toJavaLocalTime() - return when { - dialect is OracleDialect -> oracleLocalTimeLiteral(value) - dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "TIMESTAMP '${ORACLE_TIME_STRING_FORMATTER.format(value.toJavaLocalTime())}'" - else -> "'${DEFAULT_TIME_STRING_FORMATTER.format(value.toJavaLocalTime())}'" + if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { + return "TIMESTAMP '${ORACLE_TIME_STRING_FORMATTER.format(instant)}'" } + + return "'${DEFAULT_TIME_STRING_FORMATTER.format(instant)}'" } override fun valueFromDB(value: Any): LocalTime = when (value) { From 96b719cecddf415be2ba1c89f87bce91af26d6cc Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Tue, 30 Apr 2024 10:53:37 +0200 Subject: [PATCH 6/6] Add tests for literals in KotlinDateColumnType --- .../kotlin/datetime/KotlinDateColumnType.kt | 3 + .../kotlin/datetime/DateTimeLiteralTest.kt | 95 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/DateTimeLiteralTest.kt diff --git a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt index feb0cdeb30..d5ac61f4f1 100644 --- a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt +++ b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateColumnType.kt @@ -324,6 +324,9 @@ class KotlinInstantColumnType : ColumnType(), IDateColumnType { "'${formatter.format(instant)}'" } + dialect is SQLiteDialect -> + "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" + dialect is OracleDialect -> oracleDateTimeLiteral(value) else -> "'${DEFAULT_DATE_TIME_STRING_FORMATTER.format(instant)}'" diff --git a/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/DateTimeLiteralTest.kt b/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/DateTimeLiteralTest.kt new file mode 100644 index 0000000000..694dc51dc2 --- /dev/null +++ b/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/DateTimeLiteralTest.kt @@ -0,0 +1,95 @@ +package org.jetbrains.exposed.sql.kotlin.datetime + +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.tests.DatabaseTestsBase +import org.jetbrains.exposed.sql.tests.shared.assertEquals +import org.junit.Test +import kotlin.test.assertNotNull + +class DateTimeLiteralTest : DatabaseTestsBase() { + private val defaultDate = LocalDate(2000, 1, 1) + private val futureDate = LocalDate(3000, 1, 1) + + object TableWithDate : IntIdTable() { + val date = date("date") + } + + private val defaultDatetime = LocalDateTime(2000, 1, 1, 8, 0, 0, 100000000) + private val futureDatetime = LocalDateTime(3000, 1, 1, 8, 0, 0, 100000000) + + object TableWithDatetime : IntIdTable() { + val datetime = datetime("datetime") + } + + private val defaultTimestamp = Instant.parse("2000-01-01T01:00:00.00Z") + + object TableWithTimestamp : IntIdTable() { + val timestamp = timestamp("timestamp") + } + + @Test + fun testSelectByDateLiteralEquality() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = defaultDate + } + + val query = TableWithDate.select(TableWithDate.date).where { TableWithDate.date eq dateLiteral(defaultDate) } + assertEquals(defaultDate, query.single()[TableWithDate.date]) + } + } + + @Test + fun testSelectByDateLiteralComparison() { + withTables(TableWithDate) { + TableWithDate.insert { + it[date] = defaultDate + } + val query = TableWithDate.selectAll().where { TableWithDate.date less dateLiteral(futureDate) } + assertNotNull(query.firstOrNull()) + } + } + + @Test + fun testSelectByDatetimeLiteralEquality() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + + val query = TableWithDatetime.select(TableWithDatetime.datetime).where { TableWithDatetime.datetime eq dateTimeLiteral(defaultDatetime) } + assertEquals(defaultDatetime, query.single()[TableWithDatetime.datetime]) + } + } + + @Test + fun testSelectByDatetimeLiteralComparison() { + withTables(TableWithDatetime) { + TableWithDatetime.insert { + it[datetime] = defaultDatetime + } + val query = TableWithDatetime.selectAll().where { TableWithDatetime.datetime less dateTimeLiteral(futureDatetime) } + assertNotNull(query.firstOrNull()) + } + } + + @Test + fun testSelectByTimestampLiteralEquality() { + withTables(TableWithTimestamp) { + addLogger(StdOutSqlLogger) + TableWithTimestamp.insert { + it[timestamp] = defaultTimestamp + } + + val query = TableWithTimestamp.select(TableWithTimestamp.timestamp).where { TableWithTimestamp.timestamp eq timestampLiteral(defaultTimestamp) } + assertEquals(defaultTimestamp, query.single()[TableWithTimestamp.timestamp]) + } + } +}