Skip to content
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
4 changes: 4 additions & 0 deletions yawn-api/src/main/kotlin/com/faire/yawn/YawnDef.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ abstract class YawnDef<SOURCE : Any, D : Any> {
abstract inner class YawnColumnDef<F> : YawnQueryProjection<SOURCE, F> {
abstract fun generatePath(context: YawnCompilationContext): String

open fun adaptValue(value: F): Any? {
return value
}

override fun compile(context: YawnCompilationContext): Projection {
return Projections.property(generatePath(context))
}
Expand Down
10 changes: 9 additions & 1 deletion yawn-api/src/main/kotlin/com/faire/yawn/YawnTableDef.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.faire.yawn

import com.faire.yawn.adapter.YawnValueAdapter
import com.faire.yawn.project.YawnQueryProjection
import com.faire.yawn.query.YawnCompilationContext
import org.hibernate.criterion.Projection
Expand Down Expand Up @@ -52,10 +53,17 @@ abstract class YawnTableDef<SOURCE : Any, D : Any>(
*
* @param F the type of the column.
*/
inner class ColumnDef<F>(private vararg val path: String?) : YawnColumnDef<F>() {
inner class ColumnDef<F>(
private vararg val path: String?,
private val adapter: YawnValueAdapter<F>? = null,
) : YawnColumnDef<F>() {
override fun generatePath(context: YawnCompilationContext): String {
return listOfNotNull(context.generateAlias(parent), *path).joinToString(".")
}

override fun adaptValue(value: F): Any? {
return adapter?.adapt(value) ?: super.adaptValue(value)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.faire.yawn.adapter

/**
* An optional adapter to be used when querying with the type of this column.
* This allows Yawn to be smarter about the type-system than the underlying Hibernate is.
*
* For example, if you have a value class wrapping a primitive, the generate metamodel will automatically un-wrap it
* so it works with Hibernate.
*/
fun interface YawnValueAdapter<T> {
fun adapt(value: T): Any?
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.eq(property.generatePath(context), value)
): Criterion = Restrictions.eq(property.generatePath(context), property.adaptValue(value))
}

class EqualsProperty<SOURCE : Any, F>(
Expand All @@ -37,7 +37,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.ne(property.generatePath(context), value)
): Criterion = Restrictions.ne(property.generatePath(context), property.adaptValue(value))
}

class NotEqualsProperty<SOURCE : Any, F>(
Expand All @@ -55,7 +55,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.gt(property.generatePath(context), value)
): Criterion = Restrictions.gt(property.generatePath(context), property.adaptValue(value))
}

class GreaterThanProperty<SOURCE : Any, F>(
Expand All @@ -73,7 +73,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.ge(property.generatePath(context), value)
): Criterion = Restrictions.ge(property.generatePath(context), property.adaptValue(value))
}

class GreaterThanOrEqualToProperty<SOURCE : Any, F>(
Expand All @@ -91,7 +91,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.lt(property.generatePath(context), value)
): Criterion = Restrictions.lt(property.generatePath(context), property.adaptValue(value))
}

class LessThanProperty<SOURCE : Any, F>(
Expand All @@ -109,7 +109,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.le(property.generatePath(context), value)
): Criterion = Restrictions.le(property.generatePath(context), property.adaptValue(value))
}

class LessThanOrEqualToProperty<SOURCE : Any, F>(
Expand All @@ -128,7 +128,11 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.between(property.generatePath(context), lo, hi)
): Criterion = Restrictions.between(
property.generatePath(context),
property.adaptValue(lo),
property.adaptValue(hi),
)
}

class Not<SOURCE : Any>(
Expand All @@ -147,9 +151,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
internal constructor(vararg criteria: YawnQueryCriterion<SOURCE>) : this(criteria.toList())

override fun compile(context: YawnCompilationContext): Criterion = Restrictions.or(
*criteria.map {
it.yawnRestriction.compile(context)
}.toTypedArray(),
*criteria.map { it.yawnRestriction.compile(context) }.toTypedArray(),
)
}

Expand All @@ -159,9 +161,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
constructor(vararg criteria: YawnQueryCriterion<SOURCE>) : this(criteria.toList())

override fun compile(context: YawnCompilationContext): Criterion = Restrictions.and(
*criteria.map {
it.yawnRestriction.compile(context)
}.toTypedArray(),
*criteria.map { it.yawnRestriction.compile(context) }.toTypedArray(),
)
}

Expand All @@ -172,7 +172,9 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.like(column.generatePath(context), value, matchMode)
): Criterion {
return Restrictions.like(column.generatePath(context), column.adaptAsString(value), matchMode)
}
}

class ILike<SOURCE : Any, F : String?>(
Expand All @@ -182,7 +184,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.ilike(column.generatePath(context), value, matchMode)
): Criterion = Restrictions.ilike(column.generatePath(context), column.adaptAsString(value), matchMode)
}

class IsNotNull<SOURCE : Any, F>(
Expand All @@ -207,7 +209,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
) : YawnQueryRestriction<SOURCE> {
override fun compile(
context: YawnCompilationContext,
): Criterion = Restrictions.eqOrIsNull(column.generatePath(context), value)
): Criterion = Restrictions.eqOrIsNull(column.generatePath(context), column.adaptValue(value))
}

class In<SOURCE : Any, F>(
Expand All @@ -218,7 +220,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
return if (values.isEmpty()) {
Restrictions.sqlRestriction("0=1")
} else {
Restrictions.`in`(column.generatePath(context), values)
Restrictions.`in`(column.generatePath(context), values.map { column.adaptValue(it) })
}
}
}
Expand All @@ -231,7 +233,7 @@ interface YawnQueryRestriction<SOURCE : Any> {
return if (values.isEmpty()) {
Restrictions.sqlRestriction("1=1")
} else {
Restrictions.not(Restrictions.`in`(column.generatePath(context), values))
Restrictions.not(Restrictions.`in`(column.generatePath(context), values.map { column.adaptValue(it) }))
}
}
}
Expand All @@ -252,3 +254,18 @@ interface YawnQueryRestriction<SOURCE : Any> {
): Criterion = Restrictions.isNotEmpty(joinColumn.path(context))
}
}

private fun <SOURCE : Any, F : String?> YawnDef<SOURCE, *>.YawnColumnDef<F>.adaptAsString(value: F): String? {
val adaptedValue = adaptValue(value)
if (adaptedValue !is String?) {
error(
"""
Like restriction can only be applied to String values,
but got: ${adaptedValue.javaClass} due to adapter on column $this.
This means a wrong adapter was code-generated into the metamodel.
Please open an issue on GitHub with your schema definition.
""".trimIndent(),
)
}
return adaptedValue
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,27 @@ internal class BookFixtures(
val paul = createPerson {
name = "Paul Duchesne"
email = EmailAddress("paul.duchesne@faire.com")
phone = PhoneNumber("(555) 123-4567")
favoriteBook = lordOfTheRings
favoriteAuthor = andersen
}

val luan = createPerson {
name = "Luan Nico"
email = EmailAddress("luan@faire.com")
phone = PhoneNumber(
areaCode = "555",
centralOfficeCode = "987",
lineNumber = "6543",
)
favoriteBook = hp
favoriteAuthor = tolkien
}
createPerson {
name = "Quinn Budan"
email = EmailAddress("quinn@faire.com")
phone = PhoneNumber("(333) 000-1111")
}
update(rowling) {
favoriteBook = lordOfTheRings
favoriteAuthor = tolkien
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ internal class Person : TimestampedEntity<Person>(), PersonInterface {
@Column
lateinit var email: EmailAddress

/**
* Test for value/inline classes that resolve as primitives for Hibernate
*/
@Column
var phone: PhoneNumber? = null

@ManyToOne(fetch = FetchType.LAZY)
var favoriteBook: Book? = null

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.faire.yawn.setup.entities

@JvmInline
internal value class PhoneNumber(
val value: String,
) {
init {
require(regex.matches(value)) { "Phone number must match pattern (XXX) XXX-XXXX" }
}

constructor(
areaCode: String,
centralOfficeCode: String,
lineNumber: String,
) : this("($areaCode) $centralOfficeCode-$lineNumber")

val areaCode: String
get() = value.substring(1, 4)

val centralOfficeCode: String
get() = value.substring(6, 9)

val lineNumber: String
get() = value.substring(10, 14)

override fun toString(): String = value
}

private val regex = Regex("""^\(\d{3}\) \d{3}-\d{4}$""")
Loading
Loading