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
This commit is contained in:
hichem-fazai
2019-12-04 20:16:53 +01:00
committed by Andrey.Tarashevskiy
parent 0756360687
commit 677b21783c
9 changed files with 294 additions and 21 deletions

View File

@@ -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}")
}
}

View File

@@ -175,6 +175,42 @@ abstract class FunctionProvider {
expr.toList().appendTo { +it }
append(")")
}
open fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("YEAR(")
append(expr)
append(")")
}
open fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("MONTH(")
append(expr)
append(")")
}
open fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("DAY(")
append(expr)
append(")")
}
open fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("HOUR(")
append(expr)
append(")")
}
open fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("MINUTE(")
append(expr)
append(")")
}
open fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("SECOND(")
append(expr)
append(")")
}
}
/**

View File

@@ -98,6 +98,42 @@ internal object OracleFunctionProvider : FunctionProvider() {
else
expr.toList().appendTo(separator = " || '$separator' || ") { +it }
}
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(YEAR FROM ")
append(expr)
append(")")
}
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(MONTH FROM ")
append(expr)
append(")")
}
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(DAY FROM ")
append(expr)
append(")")
}
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(HOUR FROM ")
append(expr)
append(")")
}
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(MINUTE FROM ")
append(expr)
append(")")
}
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(SECOND FROM ")
append(expr)
append(")")
}
}
open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, OracleFunctionProvider) {

View File

@@ -78,6 +78,42 @@ internal object PostgreSQLFunctionProvider : FunctionProvider() {
append(" ~* ")
append(pattern)
}
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(YEAR FROM ")
append(expr)
append(")")
}
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(MONTH FROM ")
append(expr)
append(")")
}
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(DAY FROM ")
append(expr)
append(")")
}
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(HOUR FROM ")
append(expr)
append(")")
}
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(MINUTE FROM ")
append(expr)
append(")")
}
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("Extract(SECOND FROM ")
append(expr)
append(")")
}
}
open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProvider, PostgreSQLFunctionProvider) {

View File

@@ -53,6 +53,42 @@ internal object SQLiteFunctionProvider : FunctionProvider() {
else
expr.toList().appendTo(this, separator = " || '$separator' || ") { +it }
}
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%Y',")
append(expr)
append(" / 1000, 'unixepoch')")
}
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%m',")
append(expr)
append(" / 1000, 'unixepoch')")
}
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%d',")
append(expr)
append(" / 1000, 'unixepoch')")
}
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%H',")
append(expr)
append(" / 1000, 'unixepoch')")
}
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%M',")
append(expr)
append(" / 1000, 'unixepoch')")
}
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
append("STRFTIME('%S',")
append(expr)
append(" / 1000, 'unixepoch')")
}
}
open class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQLiteFunctionProvider) {

View File

@@ -20,24 +20,50 @@ class CurrentDateTime : Function<LocalDateTime>(JavaLocalDateTimeColumnType.INST
}
}
class Year<T:Temporal?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.year(expr, queryBuilder)
}
}
class Month<T:Temporal?>(val expr: Expression<T>): Function<Int>(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<T:Temporal?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.day(expr, queryBuilder)
}
}
class Hour<T:Temporal?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.hour(expr, queryBuilder)
}
}
class Minute<T:Temporal?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.minute(expr, queryBuilder)
}
}
class Second<T:Temporal?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.second(expr, queryBuilder)
}
}
fun <T: Temporal?> Expression<T>.date() = Date(this)
fun <T: Temporal?> Expression<T>.year() = Year(this)
fun <T: Temporal?> Expression<T>.month() = Month(this)
fun <T: Temporal?> Expression<T>.day() = Day(this)
fun <T: Temporal?> Expression<T>.hour() = Hour(this)
fun <T: Temporal?> Expression<T>.minute() = Minute(this)
fun <T: Temporal?> Expression<T>.second() = Second(this)
fun dateParam(value: LocalDate): Expression<LocalDate> = QueryParameter(value, JavaLocalDateColumnType.INSTANCE)

View File

@@ -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 <T:Temporal> assertEqualDateTime(d1: T?, d2: T?) {
when{
@@ -28,4 +62,9 @@ fun equalDateTime(d1: Temporal?, d2: Temporal?) = try {
false
}
val today: LocalDate = LocalDate.now()
val today: LocalDate = LocalDate.now()
object CitiesTime : IntIdTable("CitiesTime") {
val name = varchar("name", 50) // Column<String>
val local_time = datetime("local_time").nullable() // Column<datetime>
}

View File

@@ -18,23 +18,50 @@ class CurrentDateTime : Function<DateTime>(DateColumnType(false)) {
}
}
class Year<T:DateTime?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.year(expr, queryBuilder)
}
}
class Month<T:DateTime?>(val expr: Expression<T>): Function<Int>(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<T:DateTime?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.day(expr, queryBuilder)
}
}
class Hour<T:DateTime?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.hour(expr, queryBuilder)
}
}
class Minute<T:DateTime?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.minute(expr, queryBuilder)
}
}
class Second<T:DateTime?>(val expr: Expression<T>): Function<Int>(IntegerColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
currentDialect.functionProvider.second(expr, queryBuilder)
}
}
fun <T: DateTime?> Expression<T>.date() = Date(this)
fun <T: DateTime?> Expression<T>.year() = Year(this)
fun <T: DateTime?> Expression<T>.month() = Month(this)
fun <T: DateTime?> Expression<T>.day() = Day(this)
fun <T: DateTime?> Expression<T>.hour() = Hour(this)
fun <T: DateTime?> Expression<T>.minute() = Minute(this)
fun <T: DateTime?> Expression<T>.second() = Second(this)
fun dateParam(value: DateTime): Expression<DateTime> = QueryParameter(value, DateColumnType(false))

View File

@@ -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()
val today: DateTime = DateTime.now().withTimeAtStartOfDay()
object CitiesTime : IntIdTable("CitiesTime") {
val name = varchar("name", 50) // Column<String>
val local_time = datetime("local_time").nullable() // Column<datetime>
}