Skip to content

Commit

Permalink
Create a default searchRepository implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
TrulsStenrud committed Aug 28, 2023
1 parent 19f6c4d commit 963053b
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 4 deletions.
29 changes: 28 additions & 1 deletion src/main/kotlin/no/liflig/documentstore/dao/Dao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ interface Dao
*/
interface CrudDao<I : EntityId, A : EntityRoot<I>> : Dao {

fun create(entity: A,): VersionedEntity<A>
fun create(entity: A): VersionedEntity<A>

fun get(id: I, forUpdate: Boolean = false): VersionedEntity<A>?

Expand Down Expand Up @@ -298,6 +298,32 @@ abstract class AbstractSearchRepository<I, A, Q>(
}
}

abstract class QueryObject {
open val sqlWhere: String = "TRUE"
open val bindSqlParameters: Query.() -> Query = { this } // Default no-op
open val limit: Int? = null
open val offset: Int? = null
open val orderBy: String? = null
open val orderDesc: Boolean = false
}

class SearchRepositoryJdbi<I, A, Q>(
jdbi: Jdbi,
sqlTableName: String,
serializationAdapter: SerializationAdapter<A>,
) : AbstractSearchRepository<I, A, Q>(jdbi, sqlTableName, serializationAdapter) where I : EntityId,
A : EntityRoot<I>,
Q : QueryObject {
override fun search(query: Q): List<VersionedEntity<A>> = getByPredicate(
sqlWhere = query.sqlWhere,
limit = query.limit,
offset = query.offset,
orderBy = query.orderBy,
orderDesc = query.orderDesc,
bind = query.bindSqlParameters,
)
}

/**
* A data class that represents fields for a database row that holds an entity instance.
*
Expand Down Expand Up @@ -360,6 +386,7 @@ inline fun <T> mapExceptions(block: () -> T): T {
is InterruptedIOException,
is ConnectionException,
is CloseException -> throw UnavailableDaoException(e)

is NoCountReceivedFromSearchQueryException -> throw e

else -> throw UnknownDaoException(e)
Expand Down
108 changes: 108 additions & 0 deletions src/test/kotlin/no/liflig/documentstore/SearchRepositoryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package no.liflig.documentstore

import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.runBlocking
import no.liflig.documentstore.dao.CrudDaoJdbi
import no.liflig.documentstore.dao.SearchRepositoryJdbi
import no.liflig.documentstore.examples.ExampleEntity
import no.liflig.documentstore.examples.ExampleId
import no.liflig.documentstore.examples.ExampleSerializationAdapter
import no.liflig.documentstore.examples.ExampleTextQuery
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SearchRepositoryTest {
val jdbi = createTestDatabase()
val serializationAdapter = ExampleSerializationAdapter()
val dao = CrudDaoJdbi(jdbi, "example", serializationAdapter)

val searchRepository =
SearchRepositoryJdbi<ExampleId, ExampleEntity, ExampleTextQuery>(jdbi, "example", serializationAdapter)

@BeforeEach
fun clearDatabase() {
searchRepository.search(ExampleTextQuery()).forEach {
dao.delete(it.item.id, it.version)
}
}

@Test
fun testWhere() {
runBlocking {
dao.create(ExampleEntity.create("hello world"))
dao.create(ExampleEntity.create("world"))

val result = searchRepository.search(ExampleTextQuery(text = "hello world"))

result shouldHaveSize 1
}
}

@Test
fun testLimit() {
runBlocking {
dao.create(ExampleEntity.create("1"))
dao.create(ExampleEntity.create("2"))
dao.create(ExampleEntity.create("3"))

val result = searchRepository.search(ExampleTextQuery(limit = 2))

result shouldHaveSize 2
}
}

@Test
fun testOffsett() {
runBlocking {
dao.create(ExampleEntity.create("hello world"))
dao.create(ExampleEntity.create("world"))
dao.create(ExampleEntity.create("world"))

val result = searchRepository.search(ExampleTextQuery(offset = 1))

result shouldHaveSize 2
}
}

@Test
fun emptySearchReturnsAllElements() {
runBlocking {
dao.create(ExampleEntity.create("A"))
dao.create(ExampleEntity.create("B"))
dao.create(ExampleEntity.create("C"))

val result = searchRepository.search(ExampleTextQuery())

result shouldHaveSize 3
}
}

@Test
fun orderAscWorks() {
runBlocking {
dao.create(ExampleEntity.create("A"))
dao.create(ExampleEntity.create("B"))
dao.create(ExampleEntity.create("C"))

val result = searchRepository.search(ExampleTextQuery(orderBy = "data->>'text'", orderDesc = false)).map { it.item.text }

result shouldBeEqual listOf("A", "B", "C")
}
}
@Test
fun orderDescWorksBothWays() {
runBlocking {
dao.create(ExampleEntity.create("A"))
dao.create(ExampleEntity.create("B"))
dao.create(ExampleEntity.create("C"))

val result = searchRepository.search(ExampleTextQuery(orderBy = "data->>'text'", orderDesc = false)).map { it.item.id }
val resul2 = searchRepository.search(ExampleTextQuery(orderBy = "data->>'text'", orderDesc = true)).map { it.item.id }

result shouldBeEqual resul2.asReversed()
}
}
}
8 changes: 8 additions & 0 deletions src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import no.liflig.documentstore.dao.SearchRepositoryWithCountJdbi
import no.liflig.documentstore.dao.coTransactional
import no.liflig.documentstore.dao.transactional
import no.liflig.documentstore.entity.Version
import no.liflig.documentstore.examples.ExampleEntity
import no.liflig.documentstore.examples.ExampleId
import no.liflig.documentstore.examples.ExampleQueryObject
import no.liflig.documentstore.examples.ExampleSearchRepository
import no.liflig.documentstore.examples.ExampleSearchRepositoryWithCount
import no.liflig.documentstore.examples.ExampleSerializationAdapter
import no.liflig.documentstore.examples.ExampleTextSearchQuery
import no.liflig.documentstore.examples.OrderBy
import no.liflig.snapshot.verifyJsonSnapshot
import org.jdbi.v3.core.Jdbi
import org.junit.jupiter.api.Named
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@file:UseSerializers(InstantSerializer::class)

package no.liflig.documentstore
package no.liflig.documentstore.examples

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
Expand All @@ -10,6 +10,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import no.liflig.documentstore.InstantSerializer
import no.liflig.documentstore.entity.AbstractEntityRoot
import no.liflig.documentstore.entity.EntityTimestamps
import no.liflig.documentstore.entity.UuidEntityId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@file:UseSerializers(InstantSerializer::class)

package no.liflig.documentstore
package no.liflig.documentstore.examples

import kotlinx.serialization.UseSerializers
import no.liflig.documentstore.InstantSerializer
import no.liflig.documentstore.dao.AbstractSearchRepository
import no.liflig.documentstore.dao.AbstractSearchRepositoryWithCount
import no.liflig.documentstore.dao.EntitiesWithCount
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package no.liflig.documentstore
package no.liflig.documentstore.examples

import kotlinx.serialization.json.Json
import no.liflig.documentstore.dao.SerializationAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.liflig.documentstore.examples

import no.liflig.documentstore.dao.QueryObject
import org.jdbi.v3.core.statement.Query

class ExampleTextQuery(
val text: String? = null,
override val limit: Int? = null,
override val offset: Int? = null,
override val orderBy: String? = null,
override val orderDesc: Boolean = false,
) : QueryObject() {
override val sqlWhere: String = ":wildcardQuery IS NULL OR data->>'text' ILIKE :wildcardQuery "
override val bindSqlParameters: Query.() -> Query = { bind("wildcardQuery", text?.let { "%$it%" }) }
}

0 comments on commit 963053b

Please sign in to comment.