Skip to content

Commit

Permalink
add search repository implementation for queries inheriting SearchRep…
Browse files Browse the repository at this point in the history
…ositoryQuery (#32)
  • Loading branch information
hermannm authored Aug 7, 2023
1 parent 98aa630 commit 1e5add5
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,82 @@ fun <EntityT : EntityRoot<*>> createRowParserWithCount(
}

data object NoCountReceivedFromSearchQueryException : RuntimeException()

/**
* Extend this class with your custom queries to use [SearchRepositoryWithCountJdbi]. Override
* fields to customize the query. Use a sealed class to support multiple different query types.
*
* NB: if using `sqlWhere`, remember to bind your parameters with `bindSqlParameters`!
*
* Example for an entity with a `text` field:
* ```
* data class ExampleSearchQuery(
* val text: String,
* override val limit: Int,
* override val offset: Int,
* ) : SearchRepositoryQuery() {
* override val sqlWhere: String = "(data->>'text' ILIKE '%' || :text || '%')"
*
* override val bindSqlParameters: Query.() -> Query = { bind("text", text) }
* }
*
* fun main() {
* ...
* val searchRepo = SearchRepositoryWithCountJdbi<ExampleId, ExampleEntity, ExampleSearchQuery>(
* jdbi, "example", exampleSerializationAdapter,
* )
*
* val (entities, count) = searchRepo.searchWithCount(
* ExampleTextSearchQuery(text = "Example text", limit = 10, offset = 0)
* )
* }
* ```
*/
open class SearchRepositoryQuery {
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
}

/**
* Generic implementation of [AbstractSearchRepositoryWithCount] for queries that extend
* [SearchRepositoryQuery].
*/
open class SearchRepositoryWithCountJdbi<EntityIdT, EntityT, SearchQueryT>(
jdbi: Jdbi,
sqlTableName: String,
serializationAdapter: SerializationAdapter<EntityT>
) :
AbstractSearchRepositoryWithCount<EntityIdT, EntityT, SearchQueryT>(
jdbi,
sqlTableName,
serializationAdapter,
) where
EntityIdT : EntityId,
EntityT : EntityRoot<EntityIdT>,
SearchQueryT : SearchRepositoryQuery {
override fun search(query: SearchQueryT): List<VersionedEntity<EntityT>> {
return getByPredicate(
sqlWhere = query.sqlWhere,
limit = query.limit,
offset = query.offset,
orderBy = query.orderBy,
orderDesc = query.orderDesc,
bind = query.bindSqlParameters,
)
}

override fun searchWithCount(query: SearchQueryT): EntitiesWithCount<EntityT> {
return getByPredicateWithCount(
sqlWhere = query.sqlWhere,
limit = query.limit,
offset = query.offset,
orderBy = query.orderBy,
orderDesc = query.orderDesc,
bind = query.bindSqlParameters,
)
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/no/liflig/documentstore/ExampleSearchRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import kotlinx.serialization.UseSerializers
import no.liflig.documentstore.dao.AbstractSearchRepository
import no.liflig.documentstore.dao.AbstractSearchRepositoryWithCount
import no.liflig.documentstore.dao.EntitiesWithCount
import no.liflig.documentstore.dao.SearchRepositoryQuery
import no.liflig.documentstore.dao.SerializationAdapter
import no.liflig.documentstore.entity.VersionedEntity
import org.jdbi.v3.core.Jdbi
import org.jdbi.v3.core.statement.Query

data class ExampleQueryObject(
val limit: Int? = null,
Expand Down Expand Up @@ -78,3 +80,13 @@ class ExampleSearchRepositoryWithCount(
)
}
}

data class ExampleTextSearchQuery(
val text: String,
override val limit: Int? = null,
override val offset: Int? = null
) : SearchRepositoryQuery() {
override val sqlWhere: String = "(data->>'text' ILIKE '%' || :text || '%')"

override val bindSqlParameters: Query.() -> Query = { bind("text", text) }
}
28 changes: 28 additions & 0 deletions src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.kotest.matchers.ints.shouldBeLessThan
import kotlinx.coroutines.runBlocking
import no.liflig.documentstore.dao.ConflictDaoException
import no.liflig.documentstore.dao.CrudDaoJdbi
import no.liflig.documentstore.dao.SearchRepositoryWithCountJdbi
import no.liflig.documentstore.dao.coTransactional
import no.liflig.documentstore.dao.transactional
import no.liflig.documentstore.entity.Version
Expand Down Expand Up @@ -34,6 +35,9 @@ class TransactionalTest {
val serializationAdapter = ExampleSerializationAdapter()
val dao = CrudDaoJdbi(jdbi, "example", serializationAdapter)
val searchRepository = ExampleSearchRepository(jdbi, "example", serializationAdapter)
val searchRepositoryWithCountJdbi = SearchRepositoryWithCountJdbi<ExampleId, ExampleEntity, ExampleTextSearchQuery>(
jdbi, "example", serializationAdapter,
)

// Separate DAOs to avoid other tests interfering with the count returned by SearchRepositoryWithCount
val daoWithCount = CrudDaoJdbi(jdbi, "example_with_count", serializationAdapter)
Expand Down Expand Up @@ -317,6 +321,30 @@ class TransactionalTest {
}
}

@Test
fun testSearchRepositoryWithCountJdbi() {
runBlocking {
dao.create(ExampleEntity.create("Very specific name for text search 1"))
dao.create(ExampleEntity.create("Very specific name for text search 2"))
dao.create(ExampleEntity.create("Very specific name for text search 3"))

val result1 = searchRepositoryWithCountJdbi.search(
ExampleTextSearchQuery(text = "Very specific name for text search")
)
assertEquals(result1.size, 3)

val result2 = searchRepositoryWithCountJdbi.searchWithCount(
ExampleTextSearchQuery(
text = "Very specific name for text search",
limit = 2,
offset = 0,
)
)
assertEquals(result2.entities.size, 2)
assertEquals(result2.count, 3)
}
}

@Test
fun verifySnapshot() {
val agg = ExampleEntity.create(
Expand Down

0 comments on commit 1e5add5

Please sign in to comment.