Skip to content

Commit

Permalink
feat(#849): Improved perfomance on or composed filters
Browse files Browse the repository at this point in the history
For filters on the same attribute only one join with the payload attribute table is performed now, instead of one join per possible value.
  • Loading branch information
S-Tim committed Sep 15, 2023
1 parent c259133 commit 5748144
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ internal fun List<Criterion>.toDataEntryPayloadSpecification(): Specification<Da
val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>()
// compose criteria with same name with OR and criteria with different names with AND
val relevantByName = relevant.groupBy { it.name }
val orComposedByName = relevantByName.map { (_, criteria) -> composeOr(criteria.map { it.toDataEntrySpecification() }) }
val orComposedByName = relevantByName.map { (_, criteria) -> criteria.toOrDataEntrySpecification() }

return composeAnd(orComposedByName)
}
Expand All @@ -164,7 +164,7 @@ internal fun List<Criterion>.toTaskPayloadSpecification(): Specification<TaskEnt
val relevant = this.filterIsInstance<Criterion.PayloadEntryCriterion>()
// compose criteria with same name with OR and criteria with different names with AND
val relevantByName = relevant.groupBy { it.name }
val orComposedByName = relevantByName.map { (_, criteria) -> composeOr(criteria.map { it.toTaskSpecification() }) }
val orComposedByName = relevantByName.map { (_, criteria) -> criteria.toOrTaskSpecification() }

return composeAnd(orComposedByName)
}
Expand Down Expand Up @@ -218,13 +218,23 @@ internal fun Criterion.TaskCriterion.toTaskSpecification(): Specification<TaskEn
}

/**
* Creates JPA Specification for query of payload attributes based on JSON paths.
* Creates JPA Specification for query of payload attributes based on JSON paths. All criteria must have the same path
* and will be composed by the logical OR operator.
*/
internal fun Criterion.PayloadEntryCriterion.toTaskSpecification(): Specification<TaskEntity> {
return when (this.operator) {
EQUALS -> hasTaskPayloadAttribute(this.name, this.value)
else -> throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
internal fun List<Criterion.PayloadEntryCriterion>.toOrTaskSpecification(): Specification<TaskEntity> {
if (this.isEmpty()) {
throw IllegalArgumentException("List of criteria must not be empty.")
}

if (this.map { it.operator }.any { it != EQUALS }) {
throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
}

if (this.map { it.name }.any { it != this[0].name }) {
throw IllegalArgumentException("All criteria must have the same path.")
}

return hasTaskPayloadAttribute(this[0].name, this.map { it.value })
}

/**
Expand All @@ -242,13 +252,22 @@ internal fun Criterion.DataEntryCriterion.toDataEntrySpecification(): Specificat
}

/**
* Creates JPA Specification for query of payload attributes based on JSON paths.
* Creates JPA Specification for query of payload attributes based on JSON paths. All criteria must have the same path
* and will be composed by the logical OR operator.
*/
internal fun Criterion.PayloadEntryCriterion.toDataEntrySpecification(): Specification<DataEntryEntity> {
return when (this.operator) {
EQUALS -> hasDataEntryPayloadAttribute(this.name, this.value)
else -> throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
internal fun List<Criterion.PayloadEntryCriterion>.toOrDataEntrySpecification(): Specification<DataEntryEntity> {
if (this.isEmpty()) {
throw IllegalArgumentException("List of criteria must not be empty.")
}

if (this.map { it.operator }.any { it != EQUALS }) {
throw IllegalArgumentException("JPA View currently supports only equals as operator for filtering of payload attributes.")
}

if (this.map { it.name }.any { it != this[0].name }) {
throw IllegalArgumentException("All criteria must have the same path.")
}
return hasDataEntryPayloadAttribute(this[0].name, this.map { it.value })
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,23 @@ interface DataEntryRepository : CrudRepository<DataEntryEntity, DataEntryId>, Jp
/**
* Specification for checking the payload attribute.
*/
fun hasDataEntryPayloadAttribute(name: String, value: String): Specification<DataEntryEntity> =
fun hasDataEntryPayloadAttribute(name: String, values: List<String>): Specification<DataEntryEntity> =
Specification { dataEntry, query, builder ->
query.distinct(true)
val join = dataEntry.join<DataEntryEntity, Set<PayloadAttribute>>(DataEntryEntity::payloadAttributes.name)
val pathEquals = builder.equal(
join.get<String>(PayloadAttribute::path.name),
name
)
val valueEquals = builder.equal(
join.get<String>(PayloadAttribute::value.name),
value
)
builder.and(pathEquals, valueEquals)

val valueAnyOf = values.map {
builder.equal(
join.get<String>(PayloadAttribute::value.name),
it
)
}.let { builder.or(*it.toTypedArray()) }

builder.and(pathEquals, valueAnyOf)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,21 +235,25 @@ interface TaskRepository : CrudRepository<TaskEntity, String>, JpaSpecificationE
.or(likeProcessName(pattern))

/**
* Specification for checking the payload attribute of a task.
* Specification for checking the payload attribute of a task. If multiple values are given, one of them must match.
*/
fun hasTaskPayloadAttribute(name: String, value: String): Specification<TaskEntity> =
fun hasTaskPayloadAttribute(name: String, values: List<String>): Specification<TaskEntity> =
Specification { task, query, builder ->
query.distinct(true)
val join = task.join<TaskEntity, Set<PayloadAttribute>>(TaskEntity::payloadAttributes.name)
val pathEquals = builder.equal(
join.get<String>(PayloadAttribute::path.name),
name
)
val valueEquals = builder.equal(
join.get<String>(PayloadAttribute::value.name),
value
)
builder.and(pathEquals, valueEquals)

val valueAnyOf = values.map {
builder.equal(
join.get<String>(PayloadAttribute::value.name),
it
)
}.let { builder.or(*it.toTypedArray()) }

builder.and(pathEquals, valueAnyOf)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ internal class JpaPolyflowViewServiceDataEntryITest {
fun `should find the entry by filter`() {
assertResultIsTestEntry1And2(
jpaPolyflowViewService.query(
DataEntriesQuery(filters = listOf("key=value"))
DataEntriesQuery(filters = listOf("key=value", "key=value2", "key=value3"))
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,13 @@ internal class DataEntryRepositoryITest {
@Test
fun `should find by filter payload attribute`() {

val byPayloadFilterByChildKeyNumberValue = dataEntryRepository.findAll(where(hasDataEntryPayloadAttribute("child.key-number", "42")))
val byPayloadFilterByChildKeyNumberValue = dataEntryRepository.findAll(where(hasDataEntryPayloadAttribute("child.key-number", listOf("42"))))
assertThat(byPayloadFilterByChildKeyNumberValue).containsExactlyInAnyOrderElementsOf(listOf(dataEntry, dataEntry2))

val byPayloadFilterByChildKeyValue =
dataEntryRepository.findAll(
where(hasDataEntryPayloadAttribute("child.key", "value"))
.and(hasDataEntryPayloadAttribute("id", dataEntry.dataEntryId.entryId))
where(hasDataEntryPayloadAttribute("child.key", listOf("value")))
.and(hasDataEntryPayloadAttribute("id", listOf(dataEntry.dataEntryId.entryId)))
)
assertThat(byPayloadFilterByChildKeyValue).containsExactlyInAnyOrderElementsOf(listOf(dataEntry))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,11 @@ class TaskRepositoryITest {

@Test
fun `should find by payload attribute`() {
val result = taskRepository.findAll(hasTaskPayloadAttribute("complex.child2", "small"))
val result = taskRepository.findAll(hasTaskPayloadAttribute("complex.child2", listOf("small")))
assertThat(result).hasSize(1)
assertThat(result).containsExactlyInAnyOrder(task1)

val notfound = taskRepository.findAll(hasTaskPayloadAttribute("complex.child1", "13"))
val notfound = taskRepository.findAll(hasTaskPayloadAttribute("complex.child1", listOf("13")))
assertThat(notfound).isEmpty()
}

Expand Down

0 comments on commit 5748144

Please sign in to comment.