Skip to content

Commit

Permalink
Try implement domain filter
Browse files Browse the repository at this point in the history
  • Loading branch information
TrulsStenrud committed Aug 28, 2023
1 parent 963053b commit 095f777
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 24 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<artifactId>mockk-jvm</artifactId>
<version>1.13.7</version>
<scope>test</scope>
</dependency>
Expand Down
26 changes: 16 additions & 10 deletions src/main/kotlin/no/liflig/documentstore/dao/Dao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import java.io.InterruptedIOException
import java.sql.SQLTransientException
import java.time.Instant
import java.util.UUID
import kotlin.streams.asSequence

// TODO: update docs
/**
Expand Down Expand Up @@ -254,15 +255,16 @@ abstract class AbstractSearchRepository<I, A, Q>(
offset: Int? = null,
orderBy: String? = null,
orderDesc: Boolean = false,
domainFilter: ((A) -> Boolean) = { true },
bind: Query.() -> Query = { this }
): List<VersionedEntity<A>> = mapExceptions {
val transaction = transactionHandle.get()

if (transaction != null) {
innerGetByPredicate(sqlWhere, transaction, limit, offset, orderBy, orderDesc, bind)
innerGetByPredicate(sqlWhere, transaction, limit, offset, orderBy, orderDesc, domainFilter, bind)
} else {
jdbi.open().use { handle ->
innerGetByPredicate(sqlWhere, handle, limit, offset, orderBy, orderDesc, bind)
innerGetByPredicate(sqlWhere, handle, limit, offset, orderBy, orderDesc, domainFilter, bind)
}
}
}
Expand All @@ -274,10 +276,10 @@ abstract class AbstractSearchRepository<I, A, Q>(
offset: Int? = null,
orderBy: String? = null,
desc: Boolean = false,
domainFilter: ((A) -> Boolean) = { true },
bind: Query.() -> Query = { this }
): MutableList<VersionedEntity<A>> {
val limitString = limit?.let { "LIMIT $it" } ?: ""
val offsetString = offset?.let { "OFFSET $it" } ?: ""
): List<VersionedEntity<A>> {

val orderDirection = if (desc) "DESC" else "ASC"
val orderByString = orderBy ?: "created_at"

Expand All @@ -288,23 +290,26 @@ abstract class AbstractSearchRepository<I, A, Q>(
FROM "$sqlTableName"
WHERE ($sqlWhere)
ORDER BY $orderByString $orderDirection
$limitString
$offsetString
""".trimIndent()
)
.bind()
.map(rowMapper)
.list()
.asSequence()
.filter { domainFilter(it.item) }
.run { offset?.let { drop(it) } ?: this }
.run { limit?.let { take(it) } ?: this }
.toList()
}
}

abstract class QueryObject {
abstract class QueryObject<A> {
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
open val domainFilter: ((A) -> Boolean) = { true }
}

class SearchRepositoryJdbi<I, A, Q>(
Expand All @@ -313,13 +318,14 @@ class SearchRepositoryJdbi<I, A, Q>(
serializationAdapter: SerializationAdapter<A>,
) : AbstractSearchRepository<I, A, Q>(jdbi, sqlTableName, serializationAdapter) where I : EntityId,
A : EntityRoot<I>,
Q : QueryObject {
Q : QueryObject<A> {
override fun search(query: Q): List<VersionedEntity<A>> = getByPredicate(
sqlWhere = query.sqlWhere,
limit = query.limit,
offset = query.offset,
orderBy = query.orderBy,
orderDesc = query.orderDesc,
domainFilter = query.domainFilter,
bind = query.bindSqlParameters,
)
}
Expand Down
89 changes: 89 additions & 0 deletions src/test/kotlin/no/liflig/documentstore/DomainFilterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package no.liflig.documentstore

import io.kotest.matchers.collections.shouldHaveSize
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
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.ExampleQuery
import no.liflig.documentstore.examples.ExampleSerializationAdapter
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DomainFilterTest {
val jdbi = createTestDatabase()

val serializationAdapter = ExampleSerializationAdapter()
val dao = CrudDaoJdbi(jdbi, "example", serializationAdapter)

val searchRepository = SearchRepositoryJdbi(jdbi, "example", serializationAdapter)

val mockAdapter: ExampleSerializationAdapter = mockk {
every { fromJson(any()) } returns ExampleEntity.create("")
}
val searchRepositoryWithMock = SearchRepositoryJdbi(jdbi, "example", mockAdapter)

@BeforeEach
fun clearDatabase() {
searchRepository.search(ExampleQuery()).forEach {
dao.delete(it.item.id, it.version)
}
}
@Test
fun domainFilterWorks() {
runBlocking {
dao.create(ExampleEntity.create("Hello Tes"))
dao.create(ExampleEntity.create("Hello Alfred"))
dao.create(ExampleEntity.create("Bye Ted"))
dao.create(ExampleEntity.create("Bye Alfred"))

searchRepository.search(
ExampleQuery(
domainFilter = { it.text.contains("Hello") },
)
) shouldHaveSize 2
}
}

@Test
fun limitRunsDeserializingCorrectAmountOfTimes() {
runBlocking {
dao.create(ExampleEntity.create("Hello Tes"))
dao.create(ExampleEntity.create("Hello Alfred"))
dao.create(ExampleEntity.create("Bye Ted"))
dao.create(ExampleEntity.create("Bye Alfred"))

val result = searchRepositoryWithMock.search(
ExampleQuery(
limit = 1
)
)

result shouldHaveSize 1
verify(exactly = 1) { mockAdapter.fromJson(any()) }
}
}

@Test
fun offSetWorks() {
runBlocking {
dao.create(ExampleEntity.create("Hello Tes"))
dao.create(ExampleEntity.create("Hello Alfred"))
dao.create(ExampleEntity.create("Bye Ted"))
dao.create(ExampleEntity.create("Bye Alfred"))

val result = searchRepository.search(
ExampleQuery(
offset = 3
)
)

result shouldHaveSize 1
}
}
}
34 changes: 24 additions & 10 deletions src/test/kotlin/no/liflig/documentstore/SearchRepositoryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ 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.ExampleQuery
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
Expand All @@ -20,11 +20,11 @@ class SearchRepositoryTest {
val dao = CrudDaoJdbi(jdbi, "example", serializationAdapter)

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

@BeforeEach
fun clearDatabase() {
searchRepository.search(ExampleTextQuery()).forEach {
searchRepository.search(ExampleQuery()).forEach {
dao.delete(it.item.id, it.version)
}
}
Expand All @@ -35,7 +35,7 @@ class SearchRepositoryTest {
dao.create(ExampleEntity.create("hello world"))
dao.create(ExampleEntity.create("world"))

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

result shouldHaveSize 1
}
Expand All @@ -48,7 +48,7 @@ class SearchRepositoryTest {
dao.create(ExampleEntity.create("2"))
dao.create(ExampleEntity.create("3"))

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

result shouldHaveSize 2
}
Expand All @@ -61,7 +61,7 @@ class SearchRepositoryTest {
dao.create(ExampleEntity.create("world"))
dao.create(ExampleEntity.create("world"))

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

result shouldHaveSize 2
}
Expand All @@ -74,7 +74,7 @@ class SearchRepositoryTest {
dao.create(ExampleEntity.create("B"))
dao.create(ExampleEntity.create("C"))

val result = searchRepository.search(ExampleTextQuery())
val result = searchRepository.search(ExampleQuery())

result shouldHaveSize 3
}
Expand All @@ -87,7 +87,7 @@ class SearchRepositoryTest {
dao.create(ExampleEntity.create("B"))
dao.create(ExampleEntity.create("C"))

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

result shouldBeEqual listOf("A", "B", "C")
}
Expand All @@ -99,10 +99,24 @@ class SearchRepositoryTest {
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 }
val result = searchRepository.search(ExampleQuery(orderBy = "data->>'text'", orderDesc = false)).map { it.item.id }
val resul2 = searchRepository.search(ExampleQuery(orderBy = "data->>'text'", orderDesc = true)).map { it.item.id }

result shouldBeEqual resul2.asReversed()
}
}

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

val result = searchRepository.search(ExampleQuery(domainFilter = { it.text == "B" }))

result shouldHaveSize 1
result.first().item.text shouldBeEqual "B"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package no.liflig.documentstore.examples
import no.liflig.documentstore.dao.QueryObject
import org.jdbi.v3.core.statement.Query

class ExampleTextQuery(
val text: String? = null,
class ExampleQuery(
override val limit: Int? = null,
override val offset: Int? = null,
override val orderBy: String? = null,
override val orderDesc: Boolean = false,
) : QueryObject() {
override val domainFilter: ((ExampleEntity) -> Boolean) = { true },

val text: String? = null,

) : QueryObject<ExampleEntity>() {
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 095f777

Please sign in to comment.