Skip to content

Commit

Permalink
feat: support temporal field types
Browse files Browse the repository at this point in the history
  • Loading branch information
yiteam committed Jun 25, 2023
1 parent f1193df commit 7d6f397
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 28 deletions.
37 changes: 28 additions & 9 deletions src/main/kotlin/team/yi/rsql/querydsl/QuerydslRsql.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.querydsl.core.types.*
import com.querydsl.core.types.dsl.*
import com.querydsl.jpa.impl.*
import cz.jirutka.rsql.parser.RSQLParser
import team.yi.rsql.querydsl.exception.*
import team.yi.rsql.querydsl.exception.RsqlException
import team.yi.rsql.querydsl.handler.SortFieldTypeHandler
import team.yi.rsql.querydsl.util.*

Expand Down Expand Up @@ -173,16 +173,30 @@ class QuerydslRsql<E> private constructor(builder: Builder<E>) {
this.orderSpecifiers = builder.orderSpecifiers
}

fun select(select: String?): BuildBuilder<E> = BuildBuilder(this).apply { this.selectString = select }
fun select(vararg expression: Expression<*>?): BuildBuilder<E> = BuildBuilder(this).apply { this.selectExpressions = expression.filterNotNull().distinct() }
fun select(select: String?): BuildBuilder<E> = BuildBuilder(this).apply {
this.selectString = select
}

fun select(vararg expression: Expression<*>?): BuildBuilder<E> = BuildBuilder(this).apply {
this.selectExpressions = expression.filterNotNull().distinct()
}

fun from(entityName: String?): BuildBuilder<E> = BuildBuilder(this).apply { this.entityName = entityName }
fun from(entityClass: Class<E>?): BuildBuilder<E> = BuildBuilder(this).apply { this.entityClass = entityClass }
fun from(entityName: String?): BuildBuilder<E> = BuildBuilder(this).apply {
this.entityName = entityName
}

fun where(where: String?): BuildBuilder<E> = BuildBuilder(this).apply { this.where = where }
fun from(entityClass: Class<E>?): BuildBuilder<E> = BuildBuilder(this).apply {
this.entityClass = entityClass
}

fun where(where: String?): BuildBuilder<E> = BuildBuilder(this).apply {
this.where = where
}

class BuildBuilder<E>(builder: Builder<E>) : Builder<E>(builder) {
fun globalPredicate(globalPredicate: BooleanExpression?): BuildBuilder<E> = this.apply { super.globalPredicate = globalPredicate }
fun globalPredicate(globalPredicate: BooleanExpression?): BuildBuilder<E> = this.apply {
super.globalPredicate = globalPredicate
}

fun build(): QuerydslRsql<E> {
return try {
Expand Down Expand Up @@ -213,8 +227,13 @@ class QuerydslRsql<E> private constructor(builder: Builder<E>) {
}

fun sort(sort: String?): BuildBuilder<E> = this.apply { super.sort = sort }
fun sort(vararg expression: OrderSpecifier<*>?): BuildBuilder<E> = this.apply { super.orderSpecifiers = expression.filterNotNull().distinct() }
fun sort(orderSpecifiers: List<OrderSpecifier<*>>?): BuildBuilder<E> = this.apply { super.orderSpecifiers = orderSpecifiers }
fun sort(vararg expression: OrderSpecifier<*>?): BuildBuilder<E> = this.apply {
super.orderSpecifiers = expression.filterNotNull().distinct()
}

fun sort(orderSpecifiers: List<OrderSpecifier<*>>?): BuildBuilder<E> = this.apply {
super.orderSpecifiers = orderSpecifiers
}
}
}

Expand Down
28 changes: 21 additions & 7 deletions src/main/kotlin/team/yi/rsql/querydsl/RsqlConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,33 @@ class RsqlConfig private constructor(builder: Builder) {
internal val nodeInterceptors = mutableListOf<RsqlNodeInterceptor>()
internal var dateFormat: String? = null

fun operator(vararg operator: RsqlOperator): Builder = this.apply { this.operators += operator }
fun operator(vararg operator: RsqlOperator): Builder = this.apply {
this.operators += operator
}

fun fieldTypeHandler(vararg typeHandler: Class<out FieldTypeHandler>): Builder = this.apply { this.fieldTypeHandlers += typeHandler }
fun fieldTypeHandler(vararg typeHandler: Class<out FieldTypeHandler>): Builder = this.apply {
this.fieldTypeHandlers += typeHandler
}

fun sortFieldTypeHandler(vararg typeHandler: Class<out SortFieldTypeHandler>): Builder = this.apply { this.sortFieldTypeHandlers += typeHandler }
fun sortFieldTypeHandler(vararg typeHandler: Class<out SortFieldTypeHandler>): Builder = this.apply {
this.sortFieldTypeHandlers += typeHandler
}

fun nodeInterceptors(nodeInterceptors: List<RsqlNodeInterceptor>?): Builder = this.apply { nodeInterceptors?.let { this.nodeInterceptors.addAll(nodeInterceptors) } }
fun nodeInterceptors(nodeInterceptors: List<RsqlNodeInterceptor>?): Builder = this.apply {
nodeInterceptors?.let { this.nodeInterceptors.addAll(nodeInterceptors) }
}

fun nodeInterceptor(block: () -> RsqlNodeInterceptor?): Builder = this.apply { block()?.let { this.nodeInterceptor(it) } }
fun nodeInterceptor(block: () -> RsqlNodeInterceptor?): Builder = this.apply {
block()?.let { this.nodeInterceptor(it) }
}

fun nodeInterceptor(nodeInterceptor: RsqlNodeInterceptor?): Builder = this.apply { nodeInterceptor?.let { this.nodeInterceptors.add(nodeInterceptor) } }
fun nodeInterceptor(nodeInterceptor: RsqlNodeInterceptor?): Builder = this.apply {
nodeInterceptor?.let { this.nodeInterceptors.add(nodeInterceptor) }
}

fun dateFormat(dateFormat: String?): Builder = this.apply { this.dateFormat = dateFormat }
fun dateFormat(dateFormat: String?): Builder = this.apply {
this.dateFormat = dateFormat
}

fun build(): RsqlConfig {
return try {
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/team/yi/rsql/querydsl/RsqlConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ object RsqlConstants {
StringFieldTypeHandler::class.java,
CharacterFieldTypeHandler::class.java,
DateTimeFieldTypeHandler::class.java,
TemporalAccessorFieldTypeHandler::class.java,
BooleanFieldTypeHandler::class.java,
ListFieldTypeHandler::class.java,
SetFieldTypeHandler::class.java,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class BooleanFieldTypeHandler(
}
}

override fun toComparable(value: String?): Comparable<Boolean>? {
override fun toComparable(value: String?, fm: FieldMetadata?): Comparable<Boolean>? {
return value?.toBoolean()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ abstract class ComparableFieldTypeHandler<E : Comparable<E>>(
if (values.isEmpty()) return null

return values.map {
val value = toComparable(it) ?: return null
val value = toComparable(it, fm) ?: return null

Expressions.asComparable(value)
}.toList()
}

protected abstract fun toComparable(value: String?): Comparable<E>?
protected abstract fun toComparable(value: String?, fm: FieldMetadata?): Comparable<E>?

override fun getExpression(path: Expression<*>?, values: Collection<Expression<out Any?>?>?, fm: FieldMetadata?): BooleanExpression? {
val left = path as ComparableExpression<E>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ class DateTimeFieldTypeHandler<E : Comparable<E>>(
if (values.isEmpty()) return null

return values.map {
val value = toComparable(it) ?: return null
val value = toComparable(it, fm) ?: return null

Expressions.asDateTime(value)
}.toList()
}

override fun toComparable(value: String?): Comparable<E>? {
override fun toComparable(value: String?, fm: FieldMetadata?): Comparable<E>? {
if (value.isNullOrBlank()) return null

val dateFormat = rsqlConfig.dateFormat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ class EnumFieldTypeHandler<E : Enum<E>>(
if (values.isEmpty()) return null

return values
.map { if (it.isNullOrBlank()) null else Expressions.asEnum(java.lang.Enum.valueOf(fieldMetadata.clazz as Class<E>, it)) }
.map {
if (it.isNullOrBlank()) {
null
} else {
Expressions.asEnum(java.lang.Enum.valueOf(fieldMetadata.clazz as Class<E>, it))
}
}
.toList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package team.yi.rsql.querydsl.handler

import com.querydsl.core.types.*
import com.querydsl.core.types.dsl.Expressions
import cz.jirutka.rsql.parser.ast.ComparisonNode
import team.yi.rsql.querydsl.*
import team.yi.rsql.querydsl.operator.RsqlOperator
import java.time.*
import java.time.temporal.TemporalAccessor

@Suppress("UNCHECKED_CAST")
class TemporalAccessorFieldTypeHandler<E : Comparable<E>>(
override val node: ComparisonNode,
override val operator: RsqlOperator,
override val fieldMetadata: FieldMetadata,
override val rsqlConfig: RsqlConfig,
) : TemporalFieldTypeHandler<E>(node, operator, fieldMetadata, rsqlConfig) {
override fun supports(type: Class<*>?): Boolean {
return if (type == null) {
false
} else {
TemporalAccessor::class.java.isAssignableFrom(type)
}
}

override fun getPath(parentPath: Expression<*>?): Expression<*>? {
return Expressions.dateTimePath(
fieldMetadata.clazz as Class<out Comparable<*>?>,
parentPath as Path<*>?,
fieldMetadata.fieldSelector
)
}

override fun getValue(values: List<String?>, rootPath: Path<*>, fm: FieldMetadata?): Collection<Expression<out Any?>?>? {
if (values.isEmpty()) return null

return values.map {
val value = toComparable(it, fm) ?: return null

Expressions.asDateTime(value)
}.toList()
}

@Suppress("CyclomaticComplexMethod")
override fun toComparable(value: String?, fm: FieldMetadata?): Comparable<E>? {
if (value.isNullOrBlank()) return null

val fieldType = fm?.clazz ?: return null

return runCatching {
when (fieldType) {
LocalDate::class.java -> LocalDate.parse(value)
LocalDateTime::class.java -> LocalDateTime.parse(value)
LocalTime::class.java -> LocalTime.parse(value)
OffsetDateTime::class.java -> OffsetDateTime.parse(value)
OffsetTime::class.java -> OffsetTime.parse(value)
ZonedDateTime::class.java -> ZonedDateTime.parse(value)
Duration::class.java -> Duration.parse(value)
Period::class.java -> Period.parse(value)
Instant::class.java -> Instant.parse(value)
MonthDay::class.java -> MonthDay.parse(value)
Year::class.java -> Year.parse(value)
YearMonth::class.java -> YearMonth.parse(value)
Month::class.java -> Month.valueOf(value)
DayOfWeek::class.java -> DayOfWeek.valueOf(value)
else -> null
} as? Comparable<E>?
}.getOrNull()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import com.querydsl.core.types.dsl.*
import cz.jirutka.rsql.parser.ast.ComparisonNode
import team.yi.rsql.querydsl.*
import team.yi.rsql.querydsl.operator.*
import java.sql.Time
import java.sql.Timestamp
import java.util.*

@Suppress("UNCHECKED_CAST")
Expand All @@ -20,10 +18,7 @@ abstract class TemporalFieldTypeHandler<E : Comparable<E>>(
return if (type == null) {
false
} else {
Date::class.java.isAssignableFrom(type) ||
java.sql.Date::class.java.isAssignableFrom(type) ||
Time::class.java.isAssignableFrom(type) ||
Timestamp::class.java.isAssignableFrom(type)
Date::class.java.isAssignableFrom(type)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/test/kotlin/team/yi/rsql/querydsl/model/Car.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package team.yi.rsql.querydsl.model

import jakarta.persistence.*
import java.time.LocalDateTime
import java.util.*

@Suppress("unused", "SpellCheckingInspection")
Expand All @@ -22,6 +23,9 @@ class Car {
@Column
var mfgdt: Date? = null

@Column(name = "created_at")
var createdAt: LocalDateTime? = null

@OneToOne(fetch = FetchType.LAZY)
var engine: Engine? = null

Expand Down
2 changes: 2 additions & 0 deletions src/test/kotlin/team/yi/rsql/querydsl/test/BaseRsqlTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import team.yi.rsql.querydsl.RsqlConfig
import team.yi.rsql.querydsl.model.*
import team.yi.rsql.querydsl.repository.*
import java.security.SecureRandom
import java.time.LocalDateTime
import java.util.*

@Suppress("UnnecessaryAbstractClass")
Expand Down Expand Up @@ -61,6 +62,7 @@ abstract class BaseRsqlTest {
car.description = "Descreption car $randomNum"
car.active = Math.random() < 0.5
car.mfgdt = Date()
car.createdAt = LocalDateTime.now()
car.screws = screws
car.engine = savedEngine

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ class QuerydslRsqlTest : BaseRsqlTest() {
}
}

@Test
fun shouldHandleLocalDateRange() {
val rsql = QuerydslRsql.Builder<Car>(rsqlConfig)
.from("Car")
.where("createdAt>='2000-01-01T00:01:02' and createdAt<='6666-12-31T23:59:59'")
.build()
val cars = rsql.buildJPAQuery().fetch()

assertNotNull(cars, "result is null")

cars?.let {
assertFalse(cars.isEmpty(), "Can't handle `notnull` operator for Date type")
assertEquals(50, cars.size, "Can't handle `notnull` operator for Date type correctly")
}
}

@Test
fun shouldHandleNumberIn() {
val rsql = QuerydslRsql.Builder<Car>(rsqlConfig)
Expand Down

0 comments on commit 7d6f397

Please sign in to comment.