CORDA-3209 Fix vault query for participants specified in common criteria (#5508)

* Generalise participant parsing code & additional test cases.

* Use a common predicate to expand the participants query (when specified more than once - eg. in fungible and linear query criteria).

* Introduce some re-usable functions.

* Additional code clean-up and improvements.

* Fix detekt MaxLineLength errors.
This commit is contained in:
josecoll 2019-09-24 10:32:06 +01:00 committed by GitHub
parent a9d9b668bc
commit f4f46af706
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 59 deletions

View File

@ -27,6 +27,7 @@ import net.corda.node.services.persistence.NodeAttachmentService
import org.hibernate.query.criteria.internal.expression.LiteralExpression import org.hibernate.query.criteria.internal.expression.LiteralExpression
import org.hibernate.query.criteria.internal.path.SingularAttributePath import org.hibernate.query.criteria.internal.path.SingularAttributePath
import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate
import org.hibernate.query.criteria.internal.predicate.CompoundPredicate
import org.hibernate.query.criteria.internal.predicate.InPredicate import org.hibernate.query.criteria.internal.predicate.InPredicate
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
@ -409,63 +410,74 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
} }
} }
private fun getVaultFungibleStateRoot(): Root<out Any> {
val entityStateClass = VaultSchemaV1.VaultFungibleStates::class.java
return rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
}
private fun getVaultLinearStatesRoot(): Root<out Any> {
val entityStateClass = VaultSchemaV1.VaultLinearStates::class.java
return rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
}
private fun getPersistentPartyRoot(): Root<out Any> {
val persistentPartyEntity = VaultSchemaV1.PersistentParty::class.java
return rootEntities.getOrElse(persistentPartyEntity) {
val entityRoot = criteriaQuery.from(persistentPartyEntity)
rootEntities[persistentPartyEntity] = entityRoot
entityRoot
}
}
override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> { override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> {
log.trace { "Parsing FungibleAssetQueryCriteria: $criteria" } log.trace { "Parsing FungibleAssetQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>() val predicateSet = mutableSetOf<Predicate>()
// ensure we re-use any existing instance of the same root entity // ensure we re-use any existing instance of the same root entity
val entityStateClass = VaultSchemaV1.VaultFungibleStates::class.java val vaultFungibleStatesRoot = getVaultFungibleStateRoot()
val vaultFungibleStates = val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"),
rootEntities.getOrElse(entityStateClass) { vaultFungibleStatesRoot.get<PersistentStateRef>("stateRef"))
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultFungibleStates.get<PersistentStateRef>("stateRef"))
predicateSet.add(joinPredicate) predicateSet.add(joinPredicate)
// owner // owner
criteria.owner?.let { criteria.owner?.let {
val owners = criteria.owner as List<AbstractParty> val owners = criteria.owner as List<AbstractParty>
predicateSet.add(criteriaBuilder.and(vaultFungibleStates.get<AbstractParty>("owner").`in`(owners))) predicateSet.add(criteriaBuilder.and(vaultFungibleStatesRoot.get<AbstractParty>("owner").`in`(owners)))
} }
// quantity // quantity
criteria.quantity?.let { criteria.quantity?.let {
predicateSet.add(columnPredicateToPredicate(vaultFungibleStates.get<Long>("quantity"), it)) predicateSet.add(columnPredicateToPredicate(vaultFungibleStatesRoot.get<Long>("quantity"), it))
} }
// issuer party // issuer party
criteria.issuer?.let { criteria.issuer?.let {
val issuerParties = criteria.issuer as List<AbstractParty> val issuerParties = criteria.issuer as List<AbstractParty>
predicateSet.add(criteriaBuilder.and(vaultFungibleStates.get<AbstractParty>("issuer").`in`(issuerParties))) predicateSet.add(criteriaBuilder.and(vaultFungibleStatesRoot.get<AbstractParty>("issuer").`in`(issuerParties)))
} }
// issuer reference // issuer reference
criteria.issuerRef?.let { criteria.issuerRef?.let {
val issuerRefs = (criteria.issuerRef as List<OpaqueBytes>).map { it.bytes } val issuerRefs = (criteria.issuerRef as List<OpaqueBytes>).map { it.bytes }
predicateSet.add(criteriaBuilder.and(vaultFungibleStates.get<ByteArray>("issuerRef").`in`(issuerRefs))) predicateSet.add(criteriaBuilder.and(vaultFungibleStatesRoot.get<ByteArray>("issuerRef").`in`(issuerRefs)))
} }
// Participants. // Participants.
criteria.participants?.let { criteria.participants?.let {
val participants = criteria.participants!! // Join VaultFungibleState and PersistentParty tables (participant values are added to the common query criteria predicate)
val statePartyToFungibleStatesJoin = criteriaBuilder.and(
// Get the persistent party entity. criteriaBuilder.equal(vaultFungibleStatesRoot.get<VaultSchemaV1.VaultFungibleStates>("stateRef"),
val persistentPartyEntity = VaultSchemaV1.PersistentParty::class.java getPersistentPartyRoot().get<VaultSchemaV1.PersistentParty>("compositeKey").get<PersistentStateRef>("stateRef")))
val entityRoot = rootEntities.getOrElse(persistentPartyEntity) { predicateSet.add(statePartyToFungibleStatesJoin)
val entityRoot = criteriaQuery.from(persistentPartyEntity)
rootEntities[persistentPartyEntity] = entityRoot
entityRoot
}
// Add the join and participants predicates.
val statePartyJoin = criteriaBuilder.equal(vaultStates.get<VaultSchemaV1.VaultStates>("stateRef"), entityRoot.get<VaultSchemaV1.PersistentParty>("compositeKey").get<PersistentStateRef>("stateRef"))
val participantsPredicate = criteriaBuilder.and(entityRoot.get<VaultSchemaV1.PersistentParty>("x500Name").`in`(participants))
predicateSet.add(statePartyJoin)
predicateSet.add(participantsPredicate)
} }
return predicateSet return predicateSet
@ -477,47 +489,32 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
val predicateSet = mutableSetOf<Predicate>() val predicateSet = mutableSetOf<Predicate>()
// ensure we re-use any existing instance of the same root entity // ensure we re-use any existing instance of the same root entity
val entityStateClass = VaultSchemaV1.VaultLinearStates::class.java val vaultLinearStatesRoot = getVaultLinearStatesRoot()
val vaultLinearStates =
rootEntities.getOrElse(entityStateClass) {
val entityRoot = criteriaQuery.from(entityStateClass)
rootEntities[entityStateClass] = entityRoot
entityRoot
}
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef")) val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"),
vaultLinearStatesRoot.get<PersistentStateRef>("stateRef"))
predicateSet.add(joinPredicate) predicateSet.add(joinPredicate)
// linear ids UUID // linear ids UUID
criteria.uuid?.let { criteria.uuid?.let {
val uuids = criteria.uuid as List<UUID> val uuids = criteria.uuid as List<UUID>
predicateSet.add(criteriaBuilder.and(vaultLinearStates.get<UUID>("uuid").`in`(uuids))) predicateSet.add(criteriaBuilder.and(vaultLinearStatesRoot.get<UUID>("uuid").`in`(uuids)))
} }
// linear ids externalId // linear ids externalId
criteria.externalId?.let { criteria.externalId?.let {
val externalIds = criteria.externalId as List<String> val externalIds = criteria.externalId as List<String>
if (externalIds.isNotEmpty()) if (externalIds.isNotEmpty())
predicateSet.add(criteriaBuilder.and(vaultLinearStates.get<String>("externalId").`in`(externalIds))) predicateSet.add(criteriaBuilder.and(vaultLinearStatesRoot.get<String>("externalId").`in`(externalIds)))
} }
// Participants. // Participants.
criteria.participants?.let { criteria.participants?.let {
val participants = criteria.participants!! // Join VaultLinearState and PersistentParty tables (participant values are added to the common query criteria predicate)
val statePartyToLinearStatesJoin = criteriaBuilder.and(
// Get the persistent party entity. criteriaBuilder.equal(vaultLinearStatesRoot.get<VaultSchemaV1.VaultLinearStates>("stateRef"),
val persistentPartyEntity = VaultSchemaV1.PersistentParty::class.java getPersistentPartyRoot().get<VaultSchemaV1.PersistentParty>("compositeKey").get<PersistentStateRef>("stateRef")))
val entityRoot = rootEntities.getOrElse(persistentPartyEntity) { predicateSet.add(statePartyToLinearStatesJoin)
val entityRoot = criteriaQuery.from(persistentPartyEntity)
rootEntities[persistentPartyEntity] = entityRoot
entityRoot
}
// Add the join and participants predicates.
val statePartyJoin = criteriaBuilder.equal(vaultStates.get<VaultSchemaV1.VaultStates>("stateRef"), entityRoot.get<VaultSchemaV1.PersistentParty>("compositeKey").get<PersistentStateRef>("stateRef"))
val participantsPredicate = criteriaBuilder.and(entityRoot.get<VaultSchemaV1.PersistentParty>("x500Name").`in`(participants))
predicateSet.add(statePartyJoin)
predicateSet.add(participantsPredicate)
} }
return predicateSet return predicateSet
@ -678,6 +675,33 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
constraintPredicates.add(externalIdPredicate) constraintPredicates.add(externalIdPredicate)
} }
// Participants.
criteria.participants?.let {
val participants = criteria.participants!!
// use a single predicate for querying the persistent party table (incrementally add additional parties)
val predicateID = Pair(VaultSchemaV1.PersistentParty::x500Name.name, EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingParticipants = ((((commonPredicates[predicateID]) as CompoundPredicate).expressions[0]) as InPredicate<*>)
.values.map { participant -> (participant as LiteralExpression<*>).literal }
log.warn("Adding new participants: $participants to existing participants: $existingParticipants")
commonPredicates.replace(predicateID, criteriaBuilder.and(
getPersistentPartyRoot().get<VaultSchemaV1.PersistentParty>("x500Name").`in`(existingParticipants + participants)))
}
else {
// Get the persistent party entity.
commonPredicates[predicateID] = criteriaBuilder.and(
getPersistentPartyRoot().get<VaultSchemaV1.PersistentParty>("x500Name").`in`(participants))
}
// Add the join for vault states to persistent entities (if this is not a Fungible nor Linear criteria query)
if (criteria !is QueryCriteria.FungibleAssetQueryCriteria && criteria !is QueryCriteria.LinearStateQueryCriteria ) {
val statePartyJoin = criteriaBuilder.equal(vaultStates.get<VaultSchemaV1.VaultStates>("stateRef"),
getPersistentPartyRoot().get<VaultSchemaV1.PersistentParty>("compositeKey").get<PersistentStateRef>("stateRef"))
constraintPredicates.add(statePartyJoin)
}
}
return emptySet() return emptySet()
} }

View File

@ -229,6 +229,34 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
* Query API tests * Query API tests
*/ */
/** Generic Query tests: using CommonQueryCriteria */
@Test
fun `unconsumed base contract states for single participant`() {
database.transaction {
identitySvc.verifyAndRegisterIdentity(BIG_CORP_IDENTITY)
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP, MINI_CORP))
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP))
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP, BIG_CORP)) // true
val criteria = VaultQueryCriteria(participants = listOf(BIG_CORP))
val results = vaultService.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed base contract states for two participants`() {
database.transaction {
identitySvc.verifyAndRegisterIdentity(BIG_CORP_IDENTITY)
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP, MINI_CORP)) // true
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP, BIG_CORP)) // true
vaultFiller.fillWithDummyState(participants = listOf(MEGA_CORP))
val criteria = VaultQueryCriteria(participants = listOf(MINI_CORP, BIG_CORP))
val results = vaultService.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(2)
}
}
/** Generic Query tests /** Generic Query tests
(combining both FungibleState and LinearState contract types) */ (combining both FungibleState and LinearState contract types) */
@ -2379,8 +2407,8 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
identitySvc.verifyAndRegisterIdentity(BOB_IDENTITY) identitySvc.verifyAndRegisterIdentity(BOB_IDENTITY)
identitySvc.verifyAndRegisterIdentity(CHARLIE_IDENTITY) identitySvc.verifyAndRegisterIdentity(CHARLIE_IDENTITY)
vaultFiller.fillWithSomeTestLinearStates(1, "TEST1", listOf(ALICE)) vaultFiller.fillWithSomeTestLinearStates(1, "TEST1", listOf(ALICE))
vaultFiller.fillWithSomeTestLinearStates(1, "TEST2", listOf(BOB)) vaultFiller.fillWithSomeTestLinearStates(1, "TEST2", listOf(BOB))
vaultFiller.fillWithSomeTestLinearStates(1, "TEST3", listOf(CHARLIE)) vaultFiller.fillWithSomeTestLinearStates(1, "TEST3", listOf(CHARLIE))
vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 1, DUMMY_CASH_ISSUER) vaultFiller.fillWithSomeTestCash(100.DOLLARS, notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCommodity(Amount(100, Commodity.getInstance("FCOJ")!!), notaryServices, DUMMY_OBLIGATION_ISSUER.ref(1)) vaultFiller.fillWithSomeTestCommodity(Amount(100, Commodity.getInstance("FCOJ")!!), notaryServices, DUMMY_OBLIGATION_ISSUER.ref(1))
vaultFiller.fillWithDummyState() vaultFiller.fillWithDummyState()
@ -2397,7 +2425,11 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
assertThat(resultsFASC.states).hasSize(2) assertThat(resultsFASC.states).hasSize(2)
// composite query for both linear and fungible asset states by participants // composite query for both linear and fungible asset states by participants
val resultsComposite = vaultService.queryBy<ContractState>(linearStateCriteria.or(fungibleAssetStateCriteria)) val resultsComposite = vaultService.queryBy<ContractState>(linearStateCriteria.or(fungibleAssetStateCriteria))
assertThat(resultsComposite.states).hasSize(4) assertThat(resultsComposite.states).hasSize(5)
// composite query both linear and fungible and dummy asset states by participants
val commonQueryCriteria = VaultQueryCriteria(participants = listOf(MEGA_CORP))
val resultsAll = vaultService.queryBy<ContractState>(linearStateCriteria.or(fungibleAssetStateCriteria).or(commonQueryCriteria))
assertThat(resultsAll.states).hasSize(6)
} }
} }

View File

@ -224,15 +224,16 @@ class VaultFiller @JvmOverloads constructor(
/** /**
* Records a dummy state in the Vault (useful for creating random states when testing vault queries) * Records a dummy state in the Vault (useful for creating random states when testing vault queries)
*/ */
fun fillWithDummyState() : Vault<DummyState> { fun fillWithDummyState(participants: List<AbstractParty> = listOf(services.myInfo.singleIdentity())) : Vault<DummyState> {
val outputState = TransactionState( val outputState = TransactionState(
data = DummyState(Random().nextInt(), participants = listOf(services.myInfo.singleIdentity())), data = DummyState(Random().nextInt(), participants = participants),
contract = DummyContract.PROGRAM_ID, contract = DummyContract.PROGRAM_ID,
notary = defaultNotary.party notary = defaultNotary.party
) )
val participantKeys : List<PublicKey> = participants.map { it.owningKey }
val builder = TransactionBuilder() val builder = TransactionBuilder()
.addOutputState(outputState) .addOutputState(outputState)
.addCommand(DummyCommandData, defaultNotary.party.owningKey) .addCommand(DummyCommandData, participantKeys)
val stxn = services.signInitialTransaction(builder) val stxn = services.signInitialTransaction(builder)
services.recordTransactions(stxn) services.recordTransactions(stxn)
return Vault(setOf(stxn.tx.outRef(0))) return Vault(setOf(stxn.tx.outRef(0)))