[CORDA-2247]: Fixed a bug causing Hibernate to produce cross joins. (#4366)

This commit is contained in:
Michele Sollecito 2018-12-06 10:50:47 +00:00 committed by GitHub
parent 5ad992d01f
commit ad48301149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 2 deletions

View File

@ -24,6 +24,8 @@ Unreleased
The ``confidential-identities`` dependency in your CorDapp must now be ``compile`` and not ``cordaCompile``.
* Fixed a bug resulting in poor vault query performance and incorrect results when sorting.
* Marked the ``Attachment`` interface as ``@DoNotImplement`` because it is not meant to be extended by CorDapp developers. If you have already
done so, please get in contact on the usual communication channels.

View File

@ -254,6 +254,8 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
private val log = contextLogger()
}
// incrementally build list of join predicates
private val joinPredicates = mutableListOf<Predicate>()
// incrementally build list of root entities (for later use in Sort parsing)
private val rootEntities = mutableMapOf<Class<out StatePersistable>, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates))
private val aggregateExpressions = mutableListOf<Expression<*>>()
@ -566,7 +568,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
else
aggregateExpressions
criteriaQuery.multiselect(selections)
val combinedPredicates = commonPredicates.values.plus(predicateSet).plus(constraintPredicates)
val combinedPredicates = commonPredicates.values.plus(predicateSet).plus(constraintPredicates).plus(joinPredicates)
criteriaQuery.where(*combinedPredicates.toTypedArray())
return predicateSet
@ -659,7 +661,12 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
val orderCriteria = mutableListOf<Order>()
sorting.columns.map { (sortAttribute, direction) ->
val actualSorting = if (sorting.columns.none { it.sortAttribute == SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF) }) {
sorting.copy(columns = sorting.columns + Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF), Sort.Direction.ASC))
} else {
sorting
}
actualSorting.columns.map { (sortAttribute, direction) ->
val (entityStateClass, entityStateAttributeParent, entityStateAttributeChild) =
when (sortAttribute) {
is SortAttribute.Standard -> parse(sortAttribute.attribute)
@ -670,6 +677,8 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// scenario where sorting on attributes not parsed as criteria
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
entityRoot
}
when (direction) {
@ -688,6 +697,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
}
if (orderCriteria.isNotEmpty()) {
criteriaQuery.orderBy(orderCriteria)
criteriaQuery.where(*joinPredicates.toTypedArray())
}
}

View File

@ -333,6 +333,88 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
}
}
@Test
fun `query with sort criteria works even when multiple pages have the same value for the sort criteria field`() {
val numberOfStates = 59
val pageSize = 13
database.transaction {
vaultFiller.fillWithSomeTestLinearStates(numberOfStates, linearNumber = 100L)
}
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val sortAttribute = SortAttribute.Custom(DummyLinearStateSchemaV1.PersistentDummyLinearState::class.java, "linearNumber")
Sort.Direction.values().forEach { sortDirection ->
val sorting = Sort(listOf(Sort.SortColumn(sortAttribute, sortDirection)))
val allStates = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, criteria = criteria).states
assertThat(allStates.groupBy(StateAndRef<*>::ref)).hasSameSizeAs(allStates)
when (sortDirection) {
Sort.Direction.ASC -> assertThat(allStates.sortedBy { it.state.data.linearNumber }.sortedBy { it.ref.txhash }.sortedBy { it.ref.index }).isEqualTo(allStates)
Sort.Direction.DESC -> assertThat(allStates.sortedByDescending { it.state.data.linearNumber }.sortedBy { it.ref.txhash }.sortedBy { it.ref.index }).isEqualTo(allStates)
}
(1..3).forEach {
val newAllStates = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, criteria = criteria).states
assertThat(newAllStates.groupBy(StateAndRef<*>::ref)).hasSameSizeAs(allStates)
assertThat(newAllStates).containsExactlyElementsOf(allStates)
}
val queriedStates = mutableListOf<StateAndRef<*>>()
var pageNumber = 0
while (pageNumber * pageSize < numberOfStates) {
val paging = PageSpecification(pageNumber = pageNumber + 1, pageSize = pageSize)
val page = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, paging = paging, criteria = criteria)
queriedStates += page.states
pageNumber++
}
assertThat(queriedStates).containsExactlyElementsOf(allStates)
}
}
@Test
fun `query with sort criteria works with pagination`() {
val numberOfStates = 59
val pageSize = 13
database.transaction {
vaultFiller.fillWithSomeTestLinearStates(numberOfStates, linearNumber = 100L)
}
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val sortAttribute = SortAttribute.Custom(DummyLinearStateSchemaV1.PersistentDummyLinearState::class.java, "stateRef")
Sort.Direction.values().forEach { sortDirection ->
val sorting = Sort(listOf(Sort.SortColumn(sortAttribute, sortDirection)))
val allStates = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, criteria = criteria).states
assertThat(allStates.groupBy(StateAndRef<*>::ref)).hasSameSizeAs(allStates)
when (sortDirection) {
Sort.Direction.ASC -> assertThat(allStates.sortedBy { it.ref.txhash }.sortedBy { it.ref.index }).isEqualTo(allStates)
Sort.Direction.DESC -> assertThat(allStates.sortedByDescending { it.ref.txhash }.sortedByDescending { it.ref.index }).isEqualTo(allStates)
}
(1..3).forEach {
val newAllStates = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, criteria = criteria).states
assertThat(newAllStates.groupBy(StateAndRef<*>::ref)).hasSameSizeAs(allStates)
assertThat(newAllStates).containsExactlyElementsOf(allStates)
}
val queriedStates = mutableListOf<StateAndRef<*>>()
var pageNumber = 0
while (pageNumber * pageSize < numberOfStates) {
val paging = PageSpecification(pageNumber = pageNumber + 1, pageSize = pageSize)
val page = vaultService.queryBy<DummyLinearContract.State>(sorting = sorting, paging = paging, criteria = criteria)
queriedStates += page.states
pageNumber++
}
assertThat(queriedStates).containsExactlyElementsOf(allStates)
}
}
@Test
fun `unconsumed states with count`() {
database.transaction {