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..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 @@ -127,7 +136,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 oracleDateLiteral(instant) + } + return "'$formatted'" } override fun valueFromDB(value: Any): LocalDate? = when (value) { @@ -173,7 +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 || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle -> "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(instant)}'" + 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)}'" @@ -242,12 +258,10 @@ class JavaLocalTimeColumnType : 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 "TIMESTAMP '${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 +309,11 @@ 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 -> oracleDateTimeLiteral(value) + + 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 +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 -> "'${value.format(ORACLE_OFFSET_DATE_TIME_FORMATTER)}'" + is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value) 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..7f8eee94ed --- /dev/null +++ b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/DateTimeLiteralTest.kt @@ -0,0 +1,93 @@ +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.junit.Test +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +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") + + 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) { + TableWithTimestamp.insert { + it[timestamp] = defaultTimestamp + } + + val query = TableWithTimestamp.select(TableWithTimestamp.timestamp).where { TableWithTimestamp.timestamp eq timestampLiteral(defaultTimestamp) } + assertEquals(defaultTimestamp, query.single()[TableWithTimestamp.timestamp]) + } + } +} 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..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,12 +75,16 @@ class DateColumnType(val time: Boolean) : ColumnType(), IDateColumnTyp override fun nonNullValueToString(value: DateTime): String { return if (time) { when { + 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 { - "'${DEFAULT_DATE_STRING_FORMATTER.print(value)}'" + if (currentDialect is OracleDialect) { + return oracleDateLiteral(value) + } + return "'${DEFAULT_DATE_STRING_FORMATTER.print(value)}'" } } @@ -168,7 +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 -> "'${ORACLE_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" + is OracleDialect -> oracleDateTimeWithTimezoneLiteral(value.toDateTime(DateTimeZone.getDefault())) else -> "'${DEFAULT_DATE_TIME_WITH_TIME_ZONE_FORMATTER.print(value)}'" } 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..1dfb806ad2 --- /dev/null +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaDateTimeLiteralTest.kt @@ -0,0 +1,47 @@ +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.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 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()) + } + } +} 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..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 @@ -118,6 +118,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.toJavaInstant())}', '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.toJavaInstant())}', 'YYYY-MM-DD')" + private val LocalDate.millis get() = this.atStartOfDayIn(TimeZone.currentSystemDefault()).epochSeconds * MILLIS_IN_SECOND /** @@ -132,6 +141,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 +190,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 +261,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 + val instant = value.toJavaLocalTime() + + if (dialect is OracleDialect || dialect.h2Mode == H2Dialect.H2CompatibilityMode.Oracle) { + return "TIMESTAMP '${ORACLE_TIME_STRING_FORMATTER.format(instant)}'" } - 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 +285,7 @@ class KotlinLocalTimeColumnType : ColumnType(), IDateColumnType { } java.time.LocalTime.parse(value, formatter).toKotlinLocalTime() } + else -> valueFromDB(value.toString()) } @@ -307,12 +319,16 @@ 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 SQLiteDialect -> + "'${SQLITE_AND_ORACLE_DATE_TIME_STRING_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) } } 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]) + } + } +}