diff --git a/detekt/detekt-config.yml b/detekt/detekt-config.yml index 3379110879..134b846fe9 100644 --- a/detekt/detekt-config.yml +++ b/detekt/detekt-config.yml @@ -58,7 +58,7 @@ complexity: TooManyFunctions: thresholdInClasses: 40 thresholdInFiles: 100 - thresholdInObjects: 26 + thresholdInObjects: 27 thresholdInInterfaces: 12 CyclomaticComplexMethod: threshold: 26 diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index cff127fcc3..b22685d836 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -3738,6 +3738,7 @@ public abstract class org/jetbrains/exposed/sql/vendors/FunctionProvider { public fun cast (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/IColumnType;Lorg/jetbrains/exposed/sql/QueryBuilder;)V public fun charLength (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/QueryBuilder;)V public fun concat (Ljava/lang/String;Lorg/jetbrains/exposed/sql/QueryBuilder;[Lorg/jetbrains/exposed/sql/Expression;)V + public fun date (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/QueryBuilder;)V public fun day (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/QueryBuilder;)V public fun delete (ZLorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/Integer;Lorg/jetbrains/exposed/sql/Transaction;)Ljava/lang/String; public fun explain (ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Transaction;)Ljava/lang/String; diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt index bc1505e8e2..aa618715ef 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/FunctionProvider.kt @@ -170,6 +170,18 @@ abstract class FunctionProvider { // Date/Time functions + /** + * SQL function that extracts the date field from a given temporal expression. + * + * @param expr Expression to extract the year from. + * @param queryBuilder Query builder to append the SQL function to. + */ + open fun date(expr: Expression, queryBuilder: QueryBuilder): Unit = queryBuilder { + append("DATE(") + append(expr) + append(")") + } + /** * SQL function that extracts the year field from a given date. * diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index 92be5c9de5..605faabb20 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -132,6 +132,10 @@ internal object H2FunctionProvider : FunctionProvider() { } return super.explain(analyze, null, internalStatement, transaction) } + + override fun date(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("CAST(", expr, " AS DATE)") + } } /** 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 5982298153..d74bdbf762 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 @@ -131,6 +131,10 @@ internal object OracleFunctionProvider : FunctionProvider() { append("INSTR(", expr, ",\'", substring, "\')") } + override fun date(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("CAST(", expr, " AS DATE)") + } + override fun year(expr: Expression, queryBuilder: QueryBuilder): Unit = queryBuilder { append("Extract(YEAR FROM ") append(expr) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index de55ddbb37..0a8a25a6c4 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -97,6 +97,10 @@ internal object PostgreSQLFunctionProvider : FunctionProvider() { append(pattern) } + override fun date(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("CAST(", expr, " AS DATE)") + } + override fun year(expr: Expression, queryBuilder: QueryBuilder): Unit = queryBuilder { append("Extract(YEAR FROM ") append(expr) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt index 67d0acaa2d..3b86bbb5c7 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt @@ -115,6 +115,10 @@ internal object SQLServerFunctionProvider : FunctionProvider() { queryBuilder: QueryBuilder ): Unit = TransactionManager.current().throwUnsupportedException("SQLServer doesn't provide built in REGEXP expression, use LIKE instead.") + override fun date(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("CAST(", expr, " AS DATE)") + } + override fun year(expr: Expression, queryBuilder: QueryBuilder): Unit = queryBuilder { append("DATEPART(YEAR, ", expr, ")") } 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 47a77314bb..ad78a59d19 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 @@ -114,7 +114,7 @@ private fun dateTimeWithFractionFormat(fraction: Int): DateTimeFormatter { 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) = +private fun oracleTimestampWithTimezoneLiteral(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) = @@ -367,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 -> oracleDateTimeWithTimezoneLiteral(value) + is OracleDialect -> oracleTimestampWithTimezoneLiteral(value) else -> "'${value.format(DEFAULT_OFFSET_DATE_TIME_FORMATTER)}'" } diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateFunctions.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateFunctions.kt index 7bbec27c7a..369e588758 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateFunctions.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/javatime/JavaDateFunctions.kt @@ -17,7 +17,9 @@ import java.time.temporal.Temporal /** Represents an SQL function that extracts the date part from a given temporal [expr]. */ class Date(val expr: Expression) : Function(JavaLocalDateColumnType.INSTANCE) { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("DATE(", expr, ")") } + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.date(expr, queryBuilder) + } } /** Represents an SQL function that extracts the time part from a given temporal [expr]. */ diff --git a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt index 35696bc615..ae60b1d55c 100644 --- a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt +++ b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt @@ -2,7 +2,9 @@ package org.jetbrains.exposed import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.* +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json @@ -27,13 +29,7 @@ import org.junit.Assert.fail import org.junit.Test import java.math.BigDecimal import java.math.RoundingMode -import java.time.Instant -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.ZoneId -import java.time.ZoneOffset +import java.time.* import java.time.temporal.Temporal import kotlin.test.assertEquals @@ -461,6 +457,68 @@ open class JavaTimeBaseTest : DatabaseTestsBase() { } } + @Test + fun testTimestampWithTimeZoneExtensionFunctions() { + val testTable = object : IntIdTable("TestTable") { + val timestampWithTimeZone = timestampWithTimeZone("timestamptz-column") + } + + withDb(excludeSettings = listOf(TestDB.MARIADB)) { + if (!isOldMySql()) { + try { + // UTC time zone + java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone(ZoneOffset.UTC)) + assertEquals("UTC", ZoneId.systemDefault().id) + + SchemaUtils.create(testTable) + + val now = OffsetDateTime.parse("2023-05-04T05:04:01.700+00:00") + val nowId = testTable.insertAndGetId { + it[timestampWithTimeZone] = now + } + + assertEquals( + now.toLocalDate(), + testTable.select(testTable.timestampWithTimeZone.date()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.date()] + ) + + assertEquals( + now.month.value, + testTable.select(testTable.timestampWithTimeZone.month()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.month()] + ) + + assertEquals( + now.dayOfMonth, + testTable.select(testTable.timestampWithTimeZone.day()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.day()] + ) + + assertEquals( + now.hour, + testTable.select(testTable.timestampWithTimeZone.hour()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.hour()] + ) + + assertEquals( + now.minute, + testTable.select(testTable.timestampWithTimeZone.minute()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.minute()] + ) + + assertEquals( + now.second, + testTable.select(testTable.timestampWithTimeZone.second()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.second()] + ) + } finally { + SchemaUtils.drop(testTable) + } + } + } + } + @Test fun testCurrentDateTimeFunction() { val fakeTestTable = object : IntIdTable("fakeTable") {} diff --git a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateFunctions.kt b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateFunctions.kt index ea7ce63bf4..e5c84a524e 100644 --- a/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateFunctions.kt +++ b/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateFunctions.kt @@ -11,7 +11,9 @@ import org.joda.time.DateTime /** Represents an SQL function that extracts the date part from a given datetime [expr]. */ class Date(val expr: Expression) : Function(DateColumnType(false)) { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("DATE(", expr, ")") } + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.date(expr, queryBuilder) + } } /** diff --git a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt index 6c7a81b0d3..2e5a014605 100644 --- a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt @@ -1,7 +1,10 @@ package org.jetbrains.exposed -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json @@ -21,7 +24,7 @@ import org.jetbrains.exposed.sql.tests.shared.assertEqualLists import org.jetbrains.exposed.sql.tests.shared.assertEquals import org.jetbrains.exposed.sql.tests.shared.assertTrue import org.jetbrains.exposed.sql.tests.shared.expectException -import org.jetbrains.exposed.sql.vendors.* +import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect import org.joda.time.DateTime import org.joda.time.DateTimeZone import org.junit.Test @@ -359,6 +362,38 @@ open class JodaTimeBaseTest : DatabaseTestsBase() { } } + @Test + fun testTimestampWithTimeZoneExtensionFunctions() { + val testTable = object : IntIdTable("TestTable") { + val timestampWithTimeZone = timestampWithTimeZone("timestamptz-column") + } + + withDb(excludeSettings = listOf(TestDB.MARIADB)) { + if (!isOldMySql()) { + try { + // UTC time zone + DateTimeZone.setDefault(DateTimeZone.UTC) + assertEquals("UTC", DateTimeZone.getDefault().id) + + SchemaUtils.create(testTable) + + val now = DateTime.now(DateTimeZone.getDefault()) + val nowId = testTable.insertAndGetId { + it[timestampWithTimeZone] = now + } + + assertEquals( + DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0), + testTable.select(testTable.timestampWithTimeZone.date()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.date()] + ) + } finally { + SchemaUtils.drop(testTable) + } + } + } + } + @Test fun testCurrentDateTimeFunction() { val fakeTestTable = object : IntIdTable("fakeTable") {} diff --git a/exposed-kotlin-datetime/api/exposed-kotlin-datetime.api b/exposed-kotlin-datetime/api/exposed-kotlin-datetime.api index 213025baae..d264b64a8e 100644 --- a/exposed-kotlin-datetime/api/exposed-kotlin-datetime.api +++ b/exposed-kotlin-datetime/api/exposed-kotlin-datetime.api @@ -81,6 +81,21 @@ public final class org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateFunctions public static final fun LocalDateTimeYearFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; public static final fun LocalDateYearExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; public static final fun LocalDateYearFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeDateExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeDateFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeDayExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeDayFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeHourExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeHourFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeMinuteExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeMinuteFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeMonthExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeMonthFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeSecondExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeSecondFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeTimeFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeYearExt (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; + public static final fun OffsetDateTimeYearFunction (Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/Function; public static final fun dateLiteral (Lkotlinx/datetime/LocalDate;)Lorg/jetbrains/exposed/sql/LiteralOp; public static final fun dateParam (Lkotlinx/datetime/LocalDate;)Lorg/jetbrains/exposed/sql/Expression; public static final fun dateTimeLiteral (Lkotlinx/datetime/LocalDateTime;)Lorg/jetbrains/exposed/sql/LiteralOp; diff --git a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateFunctions.kt b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateFunctions.kt index 042b3712e7..6c226754e2 100644 --- a/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateFunctions.kt +++ b/exposed-kotlin-datetime/src/main/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinDateFunctions.kt @@ -10,6 +10,7 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.Function import org.jetbrains.exposed.sql.vendors.H2Dialect import org.jetbrains.exposed.sql.vendors.MysqlDialect +import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect import org.jetbrains.exposed.sql.vendors.SQLServerDialect import org.jetbrains.exposed.sql.vendors.currentDialect import org.jetbrains.exposed.sql.vendors.h2Mode @@ -17,7 +18,9 @@ import java.time.OffsetDateTime import kotlin.time.Duration internal class DateInternal(val expr: Expression<*>) : Function(KotlinLocalDateColumnType.INSTANCE) { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("DATE(", expr, ")") } + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.date(expr, queryBuilder) + } } /** Represents an SQL function that extracts the date part from a given [expr]. */ @@ -32,21 +35,34 @@ fun Date(expr: Expression): Function = DateIn @JvmName("InstantDateFunction") fun Date(expr: Expression): Function = DateInternal(expr) -internal class TimeFunction(val expr: Expression<*>) : Function(KotlinLocalTimeColumnType.INSTANCE) { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("Time(", expr, ")") } +/** Represents an SQL function that extracts the date part from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeDateFunction") +fun Date(expr: Expression): Function = DateInternal(expr) + +internal class TimeInternal(val expr: Expression<*>) : Function(KotlinLocalTimeColumnType.INSTANCE) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + when (currentDialect) { + is PostgreSQLDialect -> append(expr, "::time") + else -> append("Time(", expr, ")") + } + } } -/** Represents an SQL function that extracts the time part from a given [expr]. */ +/** Represents an SQL function that extracts the time part from a given date [expr]. */ @JvmName("LocalDateTimeFunction") -fun Time(expr: Expression): Function = TimeFunction(expr) +fun Time(expr: Expression): Function = TimeInternal(expr) /** Represents an SQL function that extracts the time part from a given datetime [expr]. */ @JvmName("LocalDateTimeTimeFunction") -fun Time(expr: Expression): Function = TimeFunction(expr) +fun Time(expr: Expression): Function = TimeInternal(expr) /** Represents an SQL function that extracts the time part from a given timestamp [expr]. */ @JvmName("InstantTimeFunction") -fun Time(expr: Expression): Function = TimeFunction(expr) +fun Time(expr: Expression): Function = TimeInternal(expr) + +/** Represents an SQL function that extracts the time part from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeTimeFunction") +fun Time(expr: Expression): Function = TimeInternal(expr) /** * Represents the base SQL function that returns the current date and time, as determined by the specified [columnType]. @@ -119,6 +135,10 @@ fun Year(expr: Expression): Function = YearInternal @JvmName("InstantYearFunction") fun Year(expr: Expression): Function = YearInternal(expr) +/** Represents an SQL function that extracts the year field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeYearFunction") +fun Year(expr: Expression): Function = YearInternal(expr) + internal class MonthInternal(val expr: Expression<*>) : Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { val dialect = currentDialect @@ -142,6 +162,10 @@ fun Month(expr: Expression): Function = MonthIntern @JvmName("InstantMonthFunction") fun Month(expr: Expression): Function = MonthInternal(expr) +/** Represents an SQL function that extracts the month field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeMonthFunction") +fun Month(expr: Expression): Function = MonthInternal(expr) + internal class DayInternal(val expr: Expression<*>) : Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { val dialect = currentDialect @@ -165,6 +189,10 @@ fun Day(expr: Expression): Function = DayInternal(e @JvmName("InstantDayFunction") fun Day(expr: Expression): Function = DayInternal(expr) +/** Represents an SQL function that extracts the day field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeDayFunction") +fun Day(expr: Expression): Function = DayInternal(expr) + internal class HourInternal(val expr: Expression<*>) : Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { val dialect = currentDialect @@ -176,7 +204,7 @@ internal class HourInternal(val expr: Expression<*>) : Function(IntegerColu } } -/** Represents an SQL function that extracts the hour field from a given [expr]. */ +/** Represents an SQL function that extracts the hour field from a given date [expr]. */ @JvmName("LocalDateHourFunction") fun Hour(expr: Expression): Function = HourInternal(expr) @@ -188,6 +216,10 @@ fun Hour(expr: Expression): Function = HourInternal @JvmName("InstantHourFunction") fun Hour(expr: Expression): Function = HourInternal(expr) +/** Represents an SQL function that extracts the hour field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeHourFunction") +fun Hour(expr: Expression): Function = HourInternal(expr) + internal class MinuteInternal(val expr: Expression<*>) : Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { val dialect = currentDialect @@ -199,7 +231,7 @@ internal class MinuteInternal(val expr: Expression<*>) : Function(IntegerCo } } -/** Represents an SQL function that extracts the minute field from a given [expr]. */ +/** Represents an SQL function that extracts the minute field from a given date [expr]. */ @JvmName("LocalDateMinuteFunction") fun Minute(expr: Expression): Function = MinuteInternal(expr) @@ -211,6 +243,10 @@ fun Minute(expr: Expression): Function = MinuteInte @JvmName("InstantMinuteFunction") fun Minute(expr: Expression): Function = MinuteInternal(expr) +/** Represents an SQL function that extracts the minute field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeMinuteFunction") +fun Minute(expr: Expression): Function = MinuteInternal(expr) + internal class SecondInternal(val expr: Expression<*>) : Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { val dialect = currentDialect @@ -222,7 +258,7 @@ internal class SecondInternal(val expr: Expression<*>) : Function(IntegerCo } } -/** Represents an SQL function that extracts the second field from a given [expr]. */ +/** Represents an SQL function that extracts the second field from a given date [expr]. */ @JvmName("LocalDateSecondFunction") fun Second(expr: Expression): Function = SecondInternal(expr) @@ -234,9 +270,13 @@ fun Second(expr: Expression): Function = SecondInte @JvmName("InstantSecondFunction") fun Second(expr: Expression): Function = SecondInternal(expr) +/** Represents an SQL function that extracts the second field from a given timestampWithTimeZone [expr]. */ +@JvmName("OffsetDateTimeSecondFunction") +fun Second(expr: Expression): Function = SecondInternal(expr) + // Extension functions -/** Returns the date from this expression. */ +/** Returns the date from this date expression. */ @JvmName("LocalDateDateExt") fun Expression.date() = Date(this) @@ -248,6 +288,10 @@ fun Expression.date() = Date(this) @JvmName("InstantDateExt") fun Expression.date() = Date(this) +/** Returns the date from this timestampWithTimeZone expression. */ +@JvmName("OffsetDateTimeDateExt") +fun Expression.date() = Date(this) + /** Returns the year from this date expression, as an integer. */ @JvmName("LocalDateYearExt") fun Expression.year() = Year(this) @@ -260,6 +304,10 @@ fun Expression.year() = Year(this) @JvmName("InstantYearExt") fun Expression.year() = Year(this) +/** Returns the year from this timestampWithTimeZone expression, as an integer. */ +@JvmName("OffsetDateTimeYearExt") +fun Expression.year() = Year(this) + /** Returns the month from this date expression, as an integer between 1 and 12 inclusive. */ @JvmName("LocalDateMonthExt") fun Expression.month() = Month(this) @@ -272,6 +320,10 @@ fun Expression.month() = Month(this) @JvmName("InstantMonthExt") fun Expression.month() = Month(this) +/** Returns the month from this timestampWithTimeZone expression, as an integer between 1 and 12 inclusive. */ +@JvmName("OffsetDateTimeMonthExt") +fun Expression.month() = Month(this) + /** Returns the day from this date expression, as an integer between 1 and 31 inclusive. */ @JvmName("LocalDateDayExt") fun Expression.day() = Day(this) @@ -284,7 +336,11 @@ fun Expression.day() = Day(this) @JvmName("InstantDayExt") fun Expression.day() = Day(this) -/** Returns the hour from this expression, as an integer between 0 and 23 inclusive. */ +/** Returns the day from this timestampWithTimeZone expression, as an integer between 1 and 31 inclusive. */ +@JvmName("OffsetDateTimeDayExt") +fun Expression.day() = Day(this) + +/** Returns the hour from this date expression, as an integer between 0 and 23 inclusive. */ @JvmName("LocalDateHourExt") fun Expression.hour() = Hour(this) @@ -296,7 +352,11 @@ fun Expression.hour() = Hour(this) @JvmName("InstantHourExt") fun Expression.hour() = Hour(this) -/** Returns the minute from this expression, as an integer between 0 and 59 inclusive. */ +/** Returns the hour from this timestampWithTimeZone expression, as an integer between 0 and 23 inclusive. */ +@JvmName("OffsetDateTimeHourExt") +fun Expression.hour() = Hour(this) + +/** Returns the minute from this date expression, as an integer between 0 and 59 inclusive. */ @JvmName("LocalDateMinuteExt") fun Expression.minute() = Minute(this) @@ -308,7 +368,11 @@ fun Expression.minute() = Minute(this) @JvmName("InstantMinuteExt") fun Expression.minute() = Minute(this) -/** Returns the second from this expression, as an integer between 0 and 59 inclusive. */ +/** Returns the minute from this timestampWithTimeZone expression, as an integer between 0 and 59 inclusive. */ +@JvmName("OffsetDateTimeMinuteExt") +fun Expression.minute() = Minute(this) + +/** Returns the second from this date expression, as an integer between 0 and 59 inclusive. */ @JvmName("LocalDateSecondExt") fun Expression.second() = Second(this) @@ -320,6 +384,10 @@ fun Expression.second() = Second(this) @JvmName("InstantSecondExt") fun Expression.second() = Second(this) +/** Returns the second from this timestampWithTimeZone expression, as an integer between 0 and 59 inclusive. */ +@JvmName("OffsetDateTimeSecondExt") +fun Expression.second() = Second(this) + /** Returns the specified [value] as a date query parameter. */ fun dateParam(value: LocalDate): Expression = QueryParameter(value, KotlinLocalDateColumnType.INSTANCE) diff --git a/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinTimeTests.kt b/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinTimeTests.kt index 71844cc13b..06ff2290a3 100644 --- a/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinTimeTests.kt +++ b/exposed-kotlin-datetime/src/test/kotlin/org/jetbrains/exposed/sql/kotlin/datetime/KotlinTimeTests.kt @@ -454,6 +454,74 @@ open class KotlinTimeBaseTest : DatabaseTestsBase() { } } + @Test + fun testTimestampWithTimeZoneExtensionFunctions() { + val testTable = object : IntIdTable("TestTable") { + val timestampWithTimeZone = timestampWithTimeZone("timestamptz-column") + } + + withDb(excludeSettings = listOf(TestDB.MARIADB)) { + if (!isOldMySql()) { + try { + // UTC time zone + java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone(ZoneOffset.UTC)) + assertEquals("UTC", ZoneId.systemDefault().id) + + SchemaUtils.create(testTable) + + val now = OffsetDateTime.now(ZoneId.systemDefault()) + val nowId = testTable.insertAndGetId { + it[timestampWithTimeZone] = now + } + + assertEquals( + now.toLocalDate().toKotlinLocalDate(), + testTable.select(testTable.timestampWithTimeZone.date()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.date()] + ) + + assertEquals( + now.year, + testTable.select(testTable.timestampWithTimeZone.year()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.year()] + ) + + assertEquals( + now.month.value, + testTable.select(testTable.timestampWithTimeZone.month()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.month()] + ) + + assertEquals( + now.dayOfMonth, + testTable.select(testTable.timestampWithTimeZone.day()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.day()] + ) + + assertEquals( + now.hour, + testTable.select(testTable.timestampWithTimeZone.hour()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.hour()] + ) + + assertEquals( + now.minute, + testTable.select(testTable.timestampWithTimeZone.minute()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.minute()] + ) + + assertEquals( + now.second, + testTable.select(testTable.timestampWithTimeZone.second()).where { testTable.id eq nowId } + .single()[testTable.timestampWithTimeZone.second()] + ) + } finally { + SchemaUtils.drop(testTable) + } + } + } + } + @Test fun testCurrentDateTimeFunction() { val fakeTestTable = object : IntIdTable("fakeTable") {}