Skip to content
Open
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
120 changes: 87 additions & 33 deletions yawn-api/src/main/kotlin/com/faire/yawn/project/YawnProjections.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.faire.yawn.project

import com.faire.yawn.YawnDef
import com.faire.yawn.project.YawnProjections.mapping
import com.faire.yawn.query.YawnCompilationContext
import org.hibernate.criterion.Projection
import org.hibernate.criterion.Projections
Expand Down Expand Up @@ -240,20 +241,9 @@
}

internal class PairProjection<SOURCE : Any, A : Any?, B : Any?>(
private val firstProjection: YawnQueryProjection<SOURCE, A>,
private val secondProjection: YawnQueryProjection<SOURCE, B>,
) : YawnQueryProjection<SOURCE, Pair<A, B>> {
override fun compile(context: YawnCompilationContext): Projection {
return Projections.projectionList()
.add(firstProjection.compile(context))
.add(secondProjection.compile(context))
}

override fun project(value: Any?): Pair<A, B> {
val queryResult = value as Array<*>
return Pair(firstProjection.project(queryResult[0]), secondProjection.project(queryResult[1]))
}
}
firstProjection: YawnQueryProjection<SOURCE, A>,
secondProjection: YawnQueryProjection<SOURCE, B>,
) : Mapping2Projection<SOURCE, A, B, Pair<A, B>>(firstProjection, secondProjection, { a, b -> Pair(a, b) })

fun <SOURCE : Any, A : Any?, B : Any?> pair(
firstProjection: YawnQueryProjection<SOURCE, A>,
Expand All @@ -263,33 +253,97 @@
}

internal class TripleProjection<SOURCE : Any, A : Any?, B : Any?, C : Any?>(
private val firstProjection: YawnQueryProjection<SOURCE, A>,
private val secondProjection: YawnQueryProjection<SOURCE, B>,
private val thirdProjection: YawnQueryProjection<SOURCE, C>,
) : YawnQueryProjection<SOURCE, Triple<A, B, C>> {
firstProjection: YawnQueryProjection<SOURCE, A>,
secondProjection: YawnQueryProjection<SOURCE, B>,
thirdProjection: YawnQueryProjection<SOURCE, C>,
) : Mapping3Projection<SOURCE, A, B, C, Triple<A, B, C>>(
firstProjection,
secondProjection,
thirdProjection,
{ a, b, c -> Triple(a, b, c) },
)

fun <SOURCE : Any, A : Any?, B : Any?, C : Any?> triple(
firstProjection: YawnQueryProjection<SOURCE, A>,
secondProjection: YawnQueryProjection<SOURCE, B>,
thirdProjection: YawnQueryProjection<SOURCE, C>,
): YawnQueryProjection<SOURCE, Triple<A, B, C>> {
return TripleProjection(firstProjection, secondProjection, thirdProjection)
}

/**
* Provides an in-memory transformation over a column value to a different type.
* Use this when using more complex data classes as projections to apply minor
* type or value compliance transformations to database column values
* while keeping your projection classes type-safe, without needing to use
* intermediary representations.
* NOTE: this _does not_ change the query and is post-processed in memory.
*/
fun <SOURCE : Any, FROM, TO> mapping(
column: YawnQueryProjection<SOURCE, FROM>,
transform: (FROM) -> TO,
): YawnQueryProjection<SOURCE, TO> {
return Mapping1Projection(column, transform)
}

/**
* A 2-arity version of the [mapping] method.
*/
fun <SOURCE : Any, C1, C2, TO> mapping(
column1: YawnQueryProjection<SOURCE, C1>,
column2: YawnQueryProjection<SOURCE, C2>,
transform: (C1, C2) -> TO,
): YawnQueryProjection<SOURCE, TO> {
return Mapping2Projection(column1, column2, transform)
}

internal open class Mapping1Projection<SOURCE : Any, FROM, TO>(
private val column: YawnQueryProjection<SOURCE, FROM>,
private val transform: (FROM) -> TO,
) : YawnQueryProjection<SOURCE, TO> {
override fun compile(context: YawnCompilationContext): Projection = column.compile(context)

override fun project(value: Any?): TO = transform(column.project(value))
}

internal open class Mapping2Projection<SOURCE : Any, C1, C2, TO>(
private val column1: YawnQueryProjection<SOURCE, C1>,
private val column2: YawnQueryProjection<SOURCE, C2>,
private val transform: (C1, C2) -> TO,
) : YawnQueryProjection<SOURCE, TO> {
override fun compile(context: YawnCompilationContext): Projection {
return Projections.projectionList()
.add(firstProjection.compile(context))
.add(secondProjection.compile(context))
.add(thirdProjection.compile(context))
.add(column1.compile(context))
.add(column2.compile(context))
}

override fun project(value: Any?): Triple<A, B, C> {
override fun project(value: Any?): TO {
val queryResult = value as Array<*>

Check failure on line 321 in yawn-api/src/main/kotlin/com/faire/yawn/project/YawnProjections.kt

View workflow job for this annotation

GitHub Actions / Test Results

com.faire.yawn.database.YawnProjectionMappingTest ► yawn query with projection()

Failed test found in: yawn-database-test/build/test-results/test/TEST-com.faire.yawn.database.YawnProjectionMappingTest.xml Error: java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
Raw output
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
	at com.faire.yawn.project.YawnProjections$Mapping2Projection.project(YawnProjections.kt:321)
	at com.faire.yawn.database.YawnProjectionMappingTest_BookNameAndNotesProjection.create$lambda$0(YawnProjectionMappingTest_BookNameAndNotesProjectionDef.kt:23)
	at com.faire.yawn.project.YawnCompositeQueryProjection.project(YawnCompositeQueryProjection.kt:28)
	at com.faire.yawn.criteria.builder.ProjectedTypeSafeCriteriaBuilder.applyFilter$lambda$2$lambda$1(ProjectedTypeSafeCriteriaBuilder.kt:41)
	at com.faire.yawn.criteria.builder.BaseTypeSafeCriteriaBuilder.uniqueResult(BaseTypeSafeCriteriaBuilder.kt:99)
	at com.faire.yawn.database.YawnProjectionMappingTest.yawn_query_with_projection$lambda$4(YawnProjectionMappingTest.kt:24)
	at com.faire.yawn.setup.hibernate.YawnTestTransactor.open(YawnTestTransactor.kt:18)
	at com.faire.yawn.database.YawnProjectionMappingTest.yawn query with projection(YawnProjectionMappingTest.kt:12)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
return Triple(
firstProjection.project(queryResult[0]),
secondProjection.project(queryResult[1]),
thirdProjection.project(queryResult[2]),
)
return transform(column1.project(queryResult[0]), column2.project(queryResult[1]))
}
}

fun <SOURCE : Any, A : Any?, B : Any?, C : Any?> triple(
firstProjection: YawnQueryProjection<SOURCE, A>,
secondProjection: YawnQueryProjection<SOURCE, B>,
thirdProjection: YawnQueryProjection<SOURCE, C>,
): YawnQueryProjection<SOURCE, Triple<A, B, C>> {
return TripleProjection(firstProjection, secondProjection, thirdProjection)
internal open class Mapping3Projection<SOURCE : Any, C1, C2, C3, TO>(
private val column1: YawnQueryProjection<SOURCE, C1>,
private val column2: YawnQueryProjection<SOURCE, C2>,
private val column3: YawnQueryProjection<SOURCE, C3>,
private val transform: (C1, C2, C3) -> TO,
) : YawnQueryProjection<SOURCE, TO> {
override fun compile(context: YawnCompilationContext): Projection {
return Projections.projectionList()
.add(column1.compile(context))
.add(column2.compile(context))
.add(column3.compile(context))
}

override fun project(value: Any?): TO {
val queryResult = value as Array<*>
return transform(
column1.project(queryResult[0]),
column2.project(queryResult[1]),
column3.project(queryResult[2]),
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.faire.yawn.database

import com.faire.yawn.project.YawnProjection
import com.faire.yawn.project.YawnProjections
import com.faire.yawn.setup.entities.BookTable
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

internal class YawnProjectionMappingTest : BaseYawnDatabaseTest() {
@Test
fun `yawn query with projection`() {
transactor.open { session ->
val hobbit = session.project(BookTable) { books ->
addEq(books.name, "The Hobbit")
val authors = join(books.author)
project(
YawnProjectionMappingTest_BookNameAndNotesProjection.create(
uppercaseTitle = YawnProjections.mapping(books.name) { it.uppercase() },
authorNotes = YawnProjections.mapping(authors.name, books.notes) { author, notes ->
"$author says: $notes"
},
),
)
}.uniqueResult()

with(hobbit!!) {
assertThat(uppercaseTitle).isEqualTo("THE HOBBIT")
assertThat(authorNotes).isEqualTo("J.R.R. Tolkien says: J.R.R. Tolkien")
}
}
}

@YawnProjection
internal data class BookNameAndNotes(
val uppercaseTitle: String,
val authorNotes: String,
)
}
Loading