diff --git a/pom.xml b/pom.xml index 29a2dc5..dace3da 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,7 @@ io.mockk - mockk + mockk-jvm 1.13.7 test diff --git a/src/main/kotlin/no/liflig/documentstore/dao/Dao.kt b/src/main/kotlin/no/liflig/documentstore/dao/Dao.kt index a07ca7e..78cd539 100644 --- a/src/main/kotlin/no/liflig/documentstore/dao/Dao.kt +++ b/src/main/kotlin/no/liflig/documentstore/dao/Dao.kt @@ -45,7 +45,7 @@ interface Dao */ interface CrudDao> : Dao { - fun create(entity: A,): VersionedEntity + fun create(entity: A): VersionedEntity fun get(id: I, forUpdate: Boolean = false): VersionedEntity? @@ -252,6 +252,7 @@ abstract class AbstractSearchRepository( sqlWhere: String = "TRUE", limit: Int? = null, offset: Int? = null, + domainPredicate: ((A) -> Boolean)? = null, orderBy: String? = null, orderDesc: Boolean = false, bind: Query.() -> Query = { this } @@ -259,10 +260,10 @@ abstract class AbstractSearchRepository( val transaction = transactionHandle.get() if (transaction != null) { - innerGetByPredicate(sqlWhere, transaction, limit, offset, orderBy, orderDesc, bind) + innerGetByPredicate(sqlWhere, transaction, limit, offset, domainPredicate, orderBy, orderDesc, bind) } else { jdbi.open().use { handle -> - innerGetByPredicate(sqlWhere, handle, limit, offset, orderBy, orderDesc, bind) + innerGetByPredicate(sqlWhere, handle, limit, offset, domainPredicate, orderBy, orderDesc, bind) } } } @@ -272,12 +273,11 @@ abstract class AbstractSearchRepository( handle: Handle, limit: Int? = null, offset: Int? = null, + domainPredicate: ((A) -> Boolean)? = null, orderBy: String? = null, desc: Boolean = false, bind: Query.() -> Query = { this } ): MutableList> { - val limitString = limit?.let { "LIMIT $it" } ?: "" - val offsetString = offset?.let { "OFFSET $it" } ?: "" val orderDirection = if (desc) "DESC" else "ASC" val orderByString = orderBy ?: "created_at" @@ -288,13 +288,15 @@ abstract class AbstractSearchRepository( FROM "$sqlTableName" WHERE ($sqlWhere) ORDER BY $orderByString $orderDirection - $limitString - $offsetString """.trimIndent() ) .bind() .map(rowMapper) - .list() + .stream() + .run { domainPredicate?.let { filter { agg -> it(agg.item) } } ?: this } + .run { offset?.let { skip(it.toLong()) } ?: this } + .run { limit?.let{ limit(it.toLong()) } ?: this } + .toList() } } @@ -360,6 +362,7 @@ inline fun mapExceptions(block: () -> T): T { is InterruptedIOException, is ConnectionException, is CloseException -> throw UnavailableDaoException(e) + is NoCountReceivedFromSearchQueryException -> throw e else -> throw UnknownDaoException(e) diff --git a/src/test/kotlin/no/liflig/documentstore/DomainfilterTest.kt b/src/test/kotlin/no/liflig/documentstore/DomainfilterTest.kt new file mode 100644 index 0000000..6099f7e --- /dev/null +++ b/src/test/kotlin/no/liflig/documentstore/DomainfilterTest.kt @@ -0,0 +1,101 @@ +package no.liflig.documentstore + +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.ints.exactly +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.SearchRepositoryWithCountJdbi +import no.liflig.documentstore.testexamples.ExampleEntity +import no.liflig.documentstore.testexamples.ExampleId +import no.liflig.documentstore.testexamples.ExampleQueryObject +import no.liflig.documentstore.testexamples.ExampleSearchRepository +import no.liflig.documentstore.testexamples.ExampleSerializationAdapter +import no.liflig.documentstore.testexamples.ExampleTextSearchQuery +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DomainPredicateTest { + val jdbi = createTestDatabase() + + val serializationAdapter = ExampleSerializationAdapter() + + val dao = CrudDaoJdbi(jdbi, "example", serializationAdapter) + + val searchRepository = ExampleSearchRepository(jdbi, "example", serializationAdapter) + + val searchRepositoryWithCountJdbi = SearchRepositoryWithCountJdbi( + jdbi, "example", serializationAdapter, + ) + + @BeforeEach + fun clearDatabase(){ + searchRepository.listAll().forEach { + dao.delete(it.item.id, it.version) + } + } + @Test + fun domainPredicateWorks() { + 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( + ExampleQueryObject( + domainPredicate = { it.text.contains("Hello") }, + ) + ) shouldHaveSize 2 + } + } + + @Test + fun limitWorks() { + runBlocking { + val mockAdapter: ExampleSerializationAdapter = mockk { + every { fromJson(any()) } returns ExampleEntity.create("") + } + val searchRepository = ExampleSearchRepository(jdbi, "example", mockAdapter) + 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( + ExampleQueryObject( + limit = 1 + ) + ) + + result shouldHaveSize 1 + verify(exactly = 1) { mockAdapter.fromJson(any()) } + } + } + + @Test + fun offSetWorks() { + runBlocking { + val mockAdapter: ExampleSerializationAdapter = mockk { + every { fromJson(any()) } returns ExampleEntity.create("") + } + val searchRepository = ExampleSearchRepository(jdbi, "example", mockAdapter) + 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( + ExampleQueryObject( + offset = 3 + ) + ) + + result shouldHaveSize 1 + } + } +} diff --git a/src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt b/src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt index 6bf3719..4b7522b 100644 --- a/src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt +++ b/src/test/kotlin/no/liflig/documentstore/TransactionalTest.kt @@ -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.testexamples.ExampleEntity +import no.liflig.documentstore.testexamples.ExampleId +import no.liflig.documentstore.testexamples.ExampleQueryObject +import no.liflig.documentstore.testexamples.ExampleSearchRepository +import no.liflig.documentstore.testexamples.ExampleSearchRepositoryWithCount +import no.liflig.documentstore.testexamples.ExampleSerializationAdapter +import no.liflig.documentstore.testexamples.ExampleTextSearchQuery +import no.liflig.documentstore.testexamples.OrderBy import no.liflig.snapshot.verifyJsonSnapshot import org.jdbi.v3.core.Jdbi import org.junit.jupiter.api.Named @@ -310,12 +318,12 @@ class TransactionalTest { daoWithCount.create(ExampleEntity.create("B")) daoWithCount.create(ExampleEntity.create("C")) - val queryWithLimitLessThanCount = ExampleQueryObject(limit = 2, offset = 0) + val queryWithLimitLessThanCount = ExampleQueryObject(limit = 2, offset = 0) val result1 = searchRepositoryWithCount.searchWithCount(queryWithLimitLessThanCount) assertEquals(result1.entities.size, queryWithLimitLessThanCount.limit) assertEquals(result1.count, 3) - val queryWithOffsetHigherThanCount = ExampleQueryObject(limit = 2, offset = 1000) + val queryWithOffsetHigherThanCount = ExampleQueryObject(limit = 2, offset = 1000) val result2 = searchRepositoryWithCount.searchWithCount(queryWithOffsetHigherThanCount) assertEquals(result2.count, 3) } diff --git a/src/test/kotlin/no/liflig/documentstore/ExampleEntity.kt b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleEntity.kt similarity index 98% rename from src/test/kotlin/no/liflig/documentstore/ExampleEntity.kt rename to src/test/kotlin/no/liflig/documentstore/testexamples/ExampleEntity.kt index 4e51d8c..00e4d3b 100644 --- a/src/test/kotlin/no/liflig/documentstore/ExampleEntity.kt +++ b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleEntity.kt @@ -1,6 +1,6 @@ @file:UseSerializers(InstantSerializer::class) -package no.liflig.documentstore +package no.liflig.documentstore.testexamples import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/src/test/kotlin/no/liflig/documentstore/ExampleSearchRepository.kt b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSearchRepository.kt similarity index 77% rename from src/test/kotlin/no/liflig/documentstore/ExampleSearchRepository.kt rename to src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSearchRepository.kt index 426332b..821d19c 100644 --- a/src/test/kotlin/no/liflig/documentstore/ExampleSearchRepository.kt +++ b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSearchRepository.kt @@ -1,6 +1,6 @@ @file:UseSerializers(InstantSerializer::class) -package no.liflig.documentstore +package no.liflig.documentstore.testexamples import kotlinx.serialization.UseSerializers import no.liflig.documentstore.dao.AbstractSearchRepository @@ -9,12 +9,15 @@ 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.checkerframework.checker.units.qual.A +import org.checkerframework.checker.units.qual.C import org.jdbi.v3.core.Jdbi import org.jdbi.v3.core.statement.Query -data class ExampleQueryObject( +data class ExampleQueryObject( val limit: Int? = null, val offset: Int? = null, + val domainPredicate: ((A) -> Boolean)? = null, val orderBy: OrderBy? = null, val orderDesc: Boolean = false, ) @@ -29,17 +32,20 @@ class ExampleSearchRepository( sqlTableName: String, serializationAdapter: SerializationAdapter ) : - AbstractSearchRepository( + AbstractSearchRepository>( jdbi, sqlTableName, serializationAdapter ) { override fun listByIds(ids: List): List> { TODO("Not yet implemented") } - override fun search(query: ExampleQueryObject): List> = + fun listAll(): List> = getByPredicate() + + override fun search(query: ExampleQueryObject): List> = getByPredicate( limit = query.limit, offset = query.offset, + domainPredicate = query.domainPredicate, orderDesc = query.orderDesc, orderBy = when (query.orderBy) { @@ -55,17 +61,17 @@ class ExampleSearchRepositoryWithCount( sqlTableName: String, serializationAdapter: SerializationAdapter ) : - AbstractSearchRepositoryWithCount( + AbstractSearchRepositoryWithCount>( jdbi, sqlTableName, serializationAdapter, ) { - override fun search(query: ExampleQueryObject): List> { + override fun search(query: ExampleQueryObject): List> { TODO("Not yet implemented") } override fun searchWithCount( - query: ExampleQueryObject + query: ExampleQueryObject ): EntitiesWithCount { return getByPredicateWithCount( limit = query.limit, diff --git a/src/test/kotlin/no/liflig/documentstore/ExampleSerializationAdapter.kt b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSerializationAdapter.kt similarity index 91% rename from src/test/kotlin/no/liflig/documentstore/ExampleSerializationAdapter.kt rename to src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSerializationAdapter.kt index 340c07b..a8b08e6 100644 --- a/src/test/kotlin/no/liflig/documentstore/ExampleSerializationAdapter.kt +++ b/src/test/kotlin/no/liflig/documentstore/testexamples/ExampleSerializationAdapter.kt @@ -1,4 +1,4 @@ -package no.liflig.documentstore +package no.liflig.documentstore.testexamples import kotlinx.serialization.json.Json import no.liflig.documentstore.dao.SerializationAdapter diff --git a/src/test/kotlin/no/liflig/documentstore/InstantSerializer.kt b/src/test/kotlin/no/liflig/documentstore/testexamples/InstantSerializer.kt similarity index 94% rename from src/test/kotlin/no/liflig/documentstore/InstantSerializer.kt rename to src/test/kotlin/no/liflig/documentstore/testexamples/InstantSerializer.kt index 9bd1139..42aa8fb 100644 --- a/src/test/kotlin/no/liflig/documentstore/InstantSerializer.kt +++ b/src/test/kotlin/no/liflig/documentstore/testexamples/InstantSerializer.kt @@ -1,4 +1,4 @@ -package no.liflig.documentstore +package no.liflig.documentstore.testexamples import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind