Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add OffsetDateTime extension functions #2118

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion detekt/detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ complexity:
TooManyFunctions:
thresholdInClasses: 40
thresholdInFiles: 100
thresholdInObjects: 26
thresholdInObjects: 27
thresholdInInterfaces: 12
CyclomaticComplexMethod:
threshold: 26
Expand Down
1 change: 1 addition & 0 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> date(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
append("DATE(")
append(expr)
append(")")
}

/**
* SQL function that extracts the year field from a given date.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ internal object H2FunctionProvider : FunctionProvider() {
}
return super.explain(analyze, null, internalStatement, transaction)
}

override fun <T> date(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("CAST(", expr, " AS DATE)")
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ internal object OracleFunctionProvider : FunctionProvider() {
append("INSTR(", expr, ",\'", substring, "\')")
}

override fun <T> date(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("CAST(", expr, " AS DATE)")
}

override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
append("Extract(YEAR FROM ")
append(expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ internal object PostgreSQLFunctionProvider : FunctionProvider() {
append(pattern)
}

override fun <T> date(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("CAST(", expr, " AS DATE)")
}

override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
append("Extract(YEAR FROM ")
append(expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> date(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("CAST(", expr, " AS DATE)")
}

override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
append("DATEPART(YEAR, ", expr, ")")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down Expand Up @@ -367,7 +367,7 @@ class JavaOffsetDateTimeColumnType : ColumnType<OffsetDateTime>(), 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)}'"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T : Temporal?>(val expr: Expression<T>) : Function<LocalDate>(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]. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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") {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T : DateTime?>(val expr: Expression<T>) : Function<DateTime>(DateColumnType(false)) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append("DATE(", expr, ")") }
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.date(expr, queryBuilder)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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") {}
Expand Down
15 changes: 15 additions & 0 deletions exposed-kotlin-datetime/api/exposed-kotlin-datetime.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading