From 677b21783ccd306c869bad8c7b391ecc3b7b5f7a Mon Sep 17 00:00:00 2001 From: hichem-fazai Date: Wed, 4 Dec 2019 20:16:53 +0100 Subject: [PATCH] Adding sql datetime functions year, day, hour, minute, second (#707) * Adding sql datetime functions * Adding sql datetime functions (missing space) * Removing jodatime dependency * Adding functions tests to jodatime and javatime modules * Including sqlite in datetime tests and enhacement of code * Value from DB type check --- .../org/jetbrains/exposed/sql/ColumnType.kt | 1 + .../jetbrains/exposed/sql/vendors/Default.kt | 36 +++++++++++++++ .../exposed/sql/vendors/OracleDialect.kt | 36 +++++++++++++++ .../exposed/sql/vendors/PostgreSQL.kt | 36 +++++++++++++++ .../exposed/sql/vendors/SQLiteDialect.kt | 36 +++++++++++++++ .../sql/java-time/JavaDateFunctions.kt | 46 +++++++++++++++---- .../org/jetbrains/exposed/JavaTimeTests.kt | 41 ++++++++++++++++- .../exposed/sql/jodatime/DateFunctions.kt | 45 ++++++++++++++---- .../org/jetbrains/exposed/JodaTimeTests.kt | 38 ++++++++++++++- 9 files changed, 294 insertions(+), 21 deletions(-) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt index f2f7424b..c7228e87 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt @@ -139,6 +139,7 @@ class IntegerColumnType : ColumnType() { override fun valueFromDB(value: Any): Any = when(value) { is Int -> value is Number -> value.toInt() + is String -> value.toInt() else -> error("Unexpected value of type Int: $value of ${value::class.qualifiedName}") } } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index 469adff0..38bd6589 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -175,6 +175,42 @@ abstract class FunctionProvider { expr.toList().appendTo { +it } append(")") } + + open fun year(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("YEAR(") + append(expr) + append(")") + } + + open fun month(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("MONTH(") + append(expr) + append(")") + } + + open fun day(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("DAY(") + append(expr) + append(")") + } + + open fun hour(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("HOUR(") + append(expr) + append(")") + } + + open fun minute(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("MINUTE(") + append(expr) + append(")") + } + + open fun second(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("SECOND(") + append(expr) + append(")") + } } /** 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 addd7713..b78c5c29 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 @@ -98,6 +98,42 @@ internal object OracleFunctionProvider : FunctionProvider() { else expr.toList().appendTo(separator = " || '$separator' || ") { +it } } + + override fun year(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(YEAR FROM ") + append(expr) + append(")") + } + + override fun month(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(MONTH FROM ") + append(expr) + append(")") + } + + override fun day(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(DAY FROM ") + append(expr) + append(")") + } + + override fun hour(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(HOUR FROM ") + append(expr) + append(")") + } + + override fun minute(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(MINUTE FROM ") + append(expr) + append(")") + } + + override fun second(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(SECOND FROM ") + append(expr) + append(")") + } } open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, OracleFunctionProvider) { 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 cc5183e5..e3fd730e 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 @@ -78,6 +78,42 @@ internal object PostgreSQLFunctionProvider : FunctionProvider() { append(" ~* ") append(pattern) } + + override fun year(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(YEAR FROM ") + append(expr) + append(")") + } + + override fun month(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(MONTH FROM ") + append(expr) + append(")") + } + + override fun day(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(DAY FROM ") + append(expr) + append(")") + } + + override fun hour(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(HOUR FROM ") + append(expr) + append(")") + } + + override fun minute(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(MINUTE FROM ") + append(expr) + append(")") + } + + override fun second(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("Extract(SECOND FROM ") + append(expr) + append(")") + } } open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProvider, PostgreSQLFunctionProvider) { diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt index d8e9279f..ca6268e3 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt @@ -53,6 +53,42 @@ internal object SQLiteFunctionProvider : FunctionProvider() { else expr.toList().appendTo(this, separator = " || '$separator' || ") { +it } } + + override fun year(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%Y',") + append(expr) + append(" / 1000, 'unixepoch')") + } + + override fun month(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%m',") + append(expr) + append(" / 1000, 'unixepoch')") + } + + override fun day(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%d',") + append(expr) + append(" / 1000, 'unixepoch')") + } + + override fun hour(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%H',") + append(expr) + append(" / 1000, 'unixepoch')") + } + + override fun minute(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%M',") + append(expr) + append(" / 1000, 'unixepoch')") + } + + override fun second(expr: Expression, queryBuilder: QueryBuilder) = queryBuilder { + append("STRFTIME('%S',") + append(expr) + append(" / 1000, 'unixepoch')") + } } open class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQLiteFunctionProvider) { diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateFunctions.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateFunctions.kt index 7ee6cadf..18fde7e3 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateFunctions.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateFunctions.kt @@ -20,24 +20,50 @@ class CurrentDateTime : Function(JavaLocalDateTimeColumnType.INST } } +class Year(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.year(expr, queryBuilder) + } +} + class Month(val expr: Expression): Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { - when (currentDialect) { - is PostgreSQLDialect -> append("EXTRACT(MONTH FROM ", expr, ")") - is OracleDialect -> append("EXTRACT(MONTH FROM ", expr, ")") - is OracleDialect -> append("EXTRACT(MONTH FROM ", expr, ")") - is SQLServerDialect -> append("MONTH(", expr, ")") - is MariaDBDialect -> append("MONTH(", expr, ")") - is SQLiteDialect -> append("STRFTIME('%m',", expr, ")") - is H2Dialect -> append("MONTH(", expr, ")") - else -> append("MONTH(", expr, ")") - } + currentDialect.functionProvider.month(expr, queryBuilder) + } +} + +class Day(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.day(expr, queryBuilder) + } +} + +class Hour(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.hour(expr, queryBuilder) + } +} + +class Minute(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.minute(expr, queryBuilder) + } +} + +class Second(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.second(expr, queryBuilder) } } fun Expression.date() = Date(this) +fun Expression.year() = Year(this) fun Expression.month() = Month(this) +fun Expression.day() = Day(this) +fun Expression.hour() = Hour(this) +fun Expression.minute() = Minute(this) +fun Expression.second() = Second(this) fun dateParam(value: LocalDate): Expression = QueryParameter(value, JavaLocalDateColumnType.INSTANCE) 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 2a1e3da2..39bf0f48 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 @@ -1,13 +1,47 @@ package org.jetbrains.exposed +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.`java-time`.* +import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.currentDialectTest +import org.jetbrains.exposed.sql.tests.shared.assertEquals import org.jetbrains.exposed.sql.vendors.MysqlDialect +import org.junit.Test import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneOffset import java.time.temporal.Temporal import kotlin.test.assertEquals +open class JavaTimeBaseTest : DatabaseTestsBase() { + + @Test + fun javaTimeFunctions() { + withTables(CitiesTime) { + val now = LocalDateTime.now() + + val cityID = CitiesTime.insertAndGetId { + it[name] = "Tunisia" + it[local_time] = now + } + + val insertedYear = CitiesTime.slice(CitiesTime.local_time.year()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.year()] + val insertedMonth = CitiesTime.slice(CitiesTime.local_time.month()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.month()] + val insertedDay = CitiesTime.slice(CitiesTime.local_time.day()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.day()] + val insertedHour = CitiesTime.slice(CitiesTime.local_time.hour()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.hour()] + val insertedMinute = CitiesTime.slice(CitiesTime.local_time.minute()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.minute()] + val insertedSecond = CitiesTime.slice(CitiesTime.local_time.second()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.second()] + + assertEquals(now.year, insertedYear) + assertEquals(now.month.value, insertedMonth) + assertEquals(now.dayOfMonth, insertedDay) + assertEquals(now.hour, insertedHour) + assertEquals(now.minute, insertedMinute) + assertEquals(now.second, insertedSecond) + } + } +} fun assertEqualDateTime(d1: T?, d2: T?) { when{ @@ -28,4 +62,9 @@ fun equalDateTime(d1: Temporal?, d2: Temporal?) = try { false } -val today: LocalDate = LocalDate.now() \ No newline at end of file +val today: LocalDate = LocalDate.now() + +object CitiesTime : IntIdTable("CitiesTime") { + val name = varchar("name", 50) // Column + val local_time = datetime("local_time").nullable() // Column +} \ No newline at end of file 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 5b3587ba..1e212f08 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 @@ -18,23 +18,50 @@ class CurrentDateTime : Function(DateColumnType(false)) { } } +class Year(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.year(expr, queryBuilder) + } +} + class Month(val expr: Expression): Function(IntegerColumnType()) { override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { - when (currentDialect) { - is PostgreSQLDialect -> append("EXTRACT(MONTH FROM ", expr, ")") - is OracleDialect -> append("EXTRACT(MONTH FROM ", expr, ")") - is SQLServerDialect -> append("MONTH(", expr, ")") - is MariaDBDialect -> append("MONTH(", expr, ")") - is SQLiteDialect -> append("STRFTIME('%m',", expr, ")") - is H2Dialect -> append("MONTH(", expr, ")") - else -> append("MONTH(", expr, ")") - } + currentDialect.functionProvider.month(expr, queryBuilder) + } +} + +class Day(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.day(expr, queryBuilder) + } +} + +class Hour(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.hour(expr, queryBuilder) + } +} + +class Minute(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.minute(expr, queryBuilder) + } +} + +class Second(val expr: Expression): Function(IntegerColumnType()) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + currentDialect.functionProvider.second(expr, queryBuilder) } } fun Expression.date() = Date(this) +fun Expression.year() = Year(this) fun Expression.month() = Month(this) +fun Expression.day() = Day(this) +fun Expression.hour() = Hour(this) +fun Expression.minute() = Minute(this) +fun Expression.second() = Second(this) fun dateParam(value: DateTime): Expression = QueryParameter(value, DateColumnType(false)) 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 92794697..4231e12e 100644 --- a/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt +++ b/exposed-jodatime/src/test/kotlin/org/jetbrains/exposed/JodaTimeTests.kt @@ -1,16 +1,47 @@ package org.jetbrains.exposed +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.jodatime.* import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.currentDialectTest +import org.jetbrains.exposed.sql.tests.shared.assertEquals import org.jetbrains.exposed.sql.vendors.MysqlDialect import org.joda.time.DateTime import org.joda.time.DateTimeZone +import org.junit.Test import kotlin.test.assertEquals open class JodaTimeBaseTest : DatabaseTestsBase() { init { DateTimeZone.setDefault(DateTimeZone.UTC) } + + @Test + fun jodaTimeFunctions() { + withTables(CitiesTime) { + val now = DateTime.now() + + val cityID = CitiesTime.insertAndGetId { + it[name] = "St. Petersburg" + it[local_time] = now.toDateTime() + } + + val insertedYear = CitiesTime.slice(CitiesTime.local_time.year()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.year()] + val insertedMonth = CitiesTime.slice(CitiesTime.local_time.month()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.month()] + val insertedDay = CitiesTime.slice(CitiesTime.local_time.day()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.day()] + val insertedHour = CitiesTime.slice(CitiesTime.local_time.hour()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.hour()] + val insertedMinute = CitiesTime.slice(CitiesTime.local_time.minute()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.minute()] + val insertedSecond = CitiesTime.slice(CitiesTime.local_time.second()).select { CitiesTime.id.eq(cityID) }.single()[CitiesTime.local_time.second()] + + assertEquals(now.year, insertedYear) + assertEquals(now.monthOfYear, insertedMonth) + assertEquals(now.dayOfMonth, insertedDay) + assertEquals(now.hourOfDay, insertedHour) + assertEquals(now.minuteOfHour, insertedMinute) + assertEquals(now.secondOfMinute, insertedSecond) + } + } } fun assertEqualDateTime(d1: DateTime?, d2: DateTime?) { @@ -33,4 +64,9 @@ fun equalDateTime(d1: DateTime?, d2: DateTime?) = try { false } -val today: DateTime = DateTime.now().withTimeAtStartOfDay() \ No newline at end of file +val today: DateTime = DateTime.now().withTimeAtStartOfDay() + +object CitiesTime : IntIdTable("CitiesTime") { + val name = varchar("name", 50) // Column + val local_time = datetime("local_time").nullable() // Column +} \ No newline at end of file