FIX Vault Query defaults to UNCONSUMED in all QueryCriteria types (#958)

* Fix https://github.com/corda/corda/issues/949 by providing a default StateStatus argument to all QueryCriteria types.

* Abstracted Common Criteria into its own abstract data class + associated visitor.

* Incorporating feedback from RP PR review.
This commit is contained in:
josecoll 2017-07-05 10:01:35 +01:00 committed by GitHub
parent 88a8eabd60
commit f732d2cefe
5 changed files with 90 additions and 54 deletions

View File

@ -26,32 +26,36 @@ sealed class QueryCriteria {
@CordaSerializable
data class TimeCondition(val type: TimeInstantType, val predicate: ColumnPredicate<Instant>)
/**
* VaultQueryCriteria: provides query by attributes defined in [VaultSchema.VaultStates]
*/
data class VaultQueryCriteria @JvmOverloads constructor (
val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
val contractStateTypes: Set<Class<out ContractState>>? = null,
val stateRefs: List<StateRef>? = null,
val notaryName: List<X500Name>? = null,
val includeSoftlockedStates: Boolean = true,
val timeCondition: TimeCondition? = null) : QueryCriteria() {
abstract class CommonQueryCriteria : QueryCriteria() {
abstract val status: Vault.StateStatus
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
}
}
/**
* VaultQueryCriteria: provides query by attributes defined in [VaultSchema.VaultStates]
*/
data class VaultQueryCriteria @JvmOverloads constructor (override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
val contractStateTypes: Set<Class<out ContractState>>? = null,
val stateRefs: List<StateRef>? = null,
val notaryName: List<X500Name>? = null,
val includeSoftlockedStates: Boolean = true,
val timeCondition: TimeCondition? = null) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this))
}
}
/**
* LinearStateQueryCriteria: provides query by attributes defined in [VaultSchema.VaultLinearState]
*/
data class LinearStateQueryCriteria @JvmOverloads constructor(
val participants: List<AbstractParty>? = null,
val linearId: List<UniqueIdentifier>? = null,
val dealRef: List<String>? = null) : QueryCriteria() {
data class LinearStateQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null,
val linearId: List<UniqueIdentifier>? = null,
val dealRef: List<String>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this))
}
}
@ -62,15 +66,14 @@ sealed class QueryCriteria {
* [Currency] as used in [Cash] contract state
* [Commodity] as used in [CommodityContract] state
*/
data class FungibleAssetQueryCriteria @JvmOverloads constructor(
val participants: List<AbstractParty>? = null,
val owner: List<AbstractParty>? = null,
val quantity: ColumnPredicate<Long>? = null,
val issuerPartyName: List<AbstractParty>? = null,
val issuerRef: List<OpaqueBytes>? = null) : QueryCriteria() {
data class FungibleAssetQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null,
val owner: List<AbstractParty>? = null,
val quantity: ColumnPredicate<Long>? = null,
val issuerPartyName: List<AbstractParty>? = null,
val issuerRef: List<OpaqueBytes>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this))
}
}
@ -84,9 +87,11 @@ sealed class QueryCriteria {
*
* Refer to [CommercialPaper.State] for a concrete example.
*/
data class VaultCustomQueryCriteria<L : PersistentState>(val expression: CriteriaExpression<L, Boolean>) : QueryCriteria() {
data class VaultCustomQueryCriteria<L : PersistentState> @JvmOverloads constructor
(val expression: CriteriaExpression<L, Boolean>,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this))
}
}
@ -112,6 +117,7 @@ sealed class QueryCriteria {
}
interface IQueryCriteriaParser {
fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate>
fun <L: PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate>

View File

@ -78,6 +78,8 @@ There are four implementations of this interface which can be chained together t
:end-before: DOCEND VaultQueryExample20
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
All ``QueryCriteria`` implementations provide an explicitly specifiable ``StateStatus`` attribute which defaults to filtering on UNCONSUMED states.
.. note:: Custom contract states that implement the ``Queryable`` interface may now extend common schemas types ``FungiblePersistentState`` or, ``LinearPersistentState``. Previously, all custom contracts extended the root ``PersistentState`` class and defined repeated mappings of ``FungibleAsset`` and ``LinearState`` attributes. See ``SampleCashSchemaV2`` and ``DummyLinearStateSchemaV2`` as examples.

View File

@ -7,6 +7,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.OpaqueBytes
@ -41,13 +42,6 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
log.trace { "Parsing VaultQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>()
// state status
stateTypes = criteria.status
if (criteria.status == Vault.StateStatus.ALL)
predicateSet.add(vaultStates.get<Vault.StateStatus>("stateStatus").`in`(setOf(Vault.StateStatus.UNCONSUMED, Vault.StateStatus.CONSUMED)))
else
predicateSet.add(criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>("stateStatus"), criteria.status))
// contract State Types
val combinedContractTypeTypes = criteria.contractStateTypes?.plus(contractType) ?: setOf(contractType)
combinedContractTypeTypes.filter { it.name != ContractState::class.java.name }.let {
@ -217,6 +211,7 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
val vaultLinearStates = criteriaQuery.from(VaultSchemaV1.VaultLinearStates::class.java)
rootEntities.putIfAbsent(VaultSchemaV1.VaultLinearStates::class.java, vaultLinearStates)
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
@ -255,6 +250,7 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
try {
val entityRoot = criteriaQuery.from(entityClass)
rootEntities.putIfAbsent(entityClass, entityRoot)
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate)
@ -315,6 +311,18 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
return predicateSet
}
override fun parseCriteria(criteria: CommonQueryCriteria): Collection<Predicate> {
log.trace { "Parsing CommonQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>()
// state status
stateTypes = criteria.status
if (criteria.status != Vault.StateStatus.ALL)
predicateSet.add(criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>("stateStatus"), criteria.status))
return predicateSet
}
private fun parse(sorting: Sort) {
log.trace { "Parsing sorting specification: $sorting" }

View File

@ -279,12 +279,8 @@ public class VaultQueryJavaTests {
List<UniqueIdentifier> linearIds = Arrays.asList(uid);
List<AbstractParty> dealParty = Arrays.asList(getMEGA_CORP());
QueryCriteria dealCriteria = new LinearStateQueryCriteria(dealParty, null, dealIds);
QueryCriteria linearCriteria = new LinearStateQueryCriteria(dealParty, linearIds, null);
QueryCriteria dealOrLinearIdCriteria = or(dealCriteria, linearCriteria);
QueryCriteria compositeCriteria = and(dealOrLinearIdCriteria, vaultCriteria);
PageSpecification pageSpec = new PageSpecification(0, getMAX_PAGE_SIZE());
@ -296,7 +292,7 @@ public class VaultQueryJavaTests {
Observable<Vault.Update> updates = results.getFuture();
// DOCEND VaultJavaQueryExample5
assertThat(snapshot.getStates()).hasSize(4);
assertThat(snapshot.getStates()).hasSize(13);
return tx;
});

View File

@ -753,6 +753,20 @@ class VaultQueryTests {
}
}
@Test
fun `unconsumed cash fungible assets after spending`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.consumeCash(50.DOLLARS)
// should now have x2 CONSUMED + x2 UNCONSUMED (one spent + one change)
val results = vaultQuerySvc.queryBy<Cash.State>(FungibleAssetQueryCriteria())
assertThat(results.statesMetadata).hasSize(2)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `consumed cash fungible assets`() {
database.transaction {
@ -845,7 +859,7 @@ class VaultQueryTests {
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with "TEST"
// DOCSTART VaultQueryExample9
val linearStateCriteria = LinearStateQueryCriteria(linearId = listOf(linearId))
val linearStateCriteria = LinearStateQueryCriteria(linearId = listOf(linearId), status = Vault.StateStatus.ALL)
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultQuerySvc.queryBy<LinearState>(linearStateCriteria.and(vaultCriteria))
// DOCEND VaultQueryExample9
@ -864,7 +878,7 @@ class VaultQueryTests {
services.evolveLinearStates(linearStates) // consume current and produce new state reference
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with "TEST"
val linearStateCriteria = LinearStateQueryCriteria(linearId = linearStates.map { it.state.data.linearId })
val linearStateCriteria = LinearStateQueryCriteria(linearId = linearStates.map { it.state.data.linearId }, status = Vault.StateStatus.ALL)
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.LinearStateAttribute.UUID), Sort.Direction.DESC)))
@ -899,18 +913,15 @@ class VaultQueryTests {
val uid = linearStates.states.first().state.data.linearId
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.UNCONSUMED)
val linearStateCriteria = LinearStateQueryCriteria(linearId = listOf(uid))
val dealStateCriteria = LinearStateQueryCriteria(dealRef = listOf("123", "456", "789"))
val compositeCriteria = vaultCriteria.and(linearStateCriteria).or(dealStateCriteria)
val compositeCriteria = linearStateCriteria or dealStateCriteria
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.LinearStateAttribute.DEAL_REFERENCE), Sort.Direction.DESC)))
val results = vaultQuerySvc.queryBy<LinearState>(compositeCriteria, sorting = sorting)
results.states.forEach {
if (it.state.data is DummyDealContract.State)
println("${(it.state.data as DealState).ref}, ${it.state.data.linearId}") }
assertThat(results.states).hasSize(4)
assertThat(results.statesMetadata).hasSize(13)
assertThat(results.states).hasSize(13)
}
}
@ -942,7 +953,7 @@ class VaultQueryTests {
services.evolveLinearState(linearState3) // consume current and produce new state reference
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with "TEST"
val linearStateCriteria = LinearStateQueryCriteria(linearId = txns.states.map { it.state.data.linearId })
val linearStateCriteria = LinearStateQueryCriteria(linearId = txns.states.map { it.state.data.linearId }, status = Vault.StateStatus.CONSUMED)
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED)
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.LinearStateAttribute.UUID), Sort.Direction.DESC)))
val results = vaultQuerySvc.queryBy<LinearState>(linearStateCriteria.and(vaultCriteria), sorting = sorting)
@ -966,8 +977,12 @@ class VaultQueryTests {
// DOCSTART VaultDeprecatedQueryExample1
val states = vaultSvc.linearHeadsOfType<DummyLinearContract.State>().filter { it.key == linearId }
// DOCEND VaultDeprecatedQueryExample1
assertThat(states).hasSize(1)
// validate against new query api
val results = vaultQuerySvc.queryBy<LinearState>(LinearStateQueryCriteria(linearId = listOf(linearId)))
assertThat(results.statesMetadata).hasSize(1)
assertThat(results.states).hasSize(1)
}
}
@ -987,8 +1002,12 @@ class VaultQueryTests {
// DOCSTART VaultDeprecatedQueryExample2
val states = vaultSvc.consumedStates<DummyLinearContract.State>().filter { it.state.data.linearId == linearId }
// DOCEND VaultDeprecatedQueryExample2
assertThat(states).hasSize(3)
// validate against new query api
val results = vaultQuerySvc.queryBy<LinearState>(LinearStateQueryCriteria(linearId = listOf(linearId), status = Vault.StateStatus.CONSUMED))
assertThat(results.statesMetadata).hasSize(3)
assertThat(results.states).hasSize(3)
}
}
@ -1009,8 +1028,12 @@ class VaultQueryTests {
val states = vaultSvc.states(setOf(DummyLinearContract.State::class.java),
EnumSet.of(Vault.StateStatus.CONSUMED, Vault.StateStatus.UNCONSUMED)).filter { it.state.data.linearId == linearId }
// DOCEND VaultDeprecatedQueryExample3
assertThat(states).hasSize(4)
// validate against new query api
val results = vaultQuerySvc.queryBy<LinearState>(LinearStateQueryCriteria(linearId = listOf(linearId), status = Vault.StateStatus.ALL))
assertThat(results.statesMetadata).hasSize(4)
assertThat(results.states).hasSize(4)
}
}
@ -1420,7 +1443,7 @@ class VaultQueryTests {
services.fillWithSomeTestLinearStates(1, "TEST2")
val uuid = services.fillWithSomeTestLinearStates(1, "TEST3").states.first().state.data.linearId.id
// 2 unconsumed states with same external ID
// 2 unconsumed states with same external ID, 1 with different external ID
val results = builder {
val externalIdCondition = VaultSchemaV1.VaultLinearStates::externalId.equal("TEST2")
@ -1429,10 +1452,11 @@ class VaultQueryTests {
val uuidCondition = VaultSchemaV1.VaultLinearStates::uuid.equal(uuid)
val uuidCustomCriteria = VaultCustomQueryCriteria(uuidCondition)
val criteria = externalIdCustomCriteria.or(uuidCustomCriteria)
val criteria = externalIdCustomCriteria or uuidCustomCriteria
vaultQuerySvc.queryBy<LinearState>(criteria)
}
assertThat(results.states).hasSize(2)
assertThat(results.statesMetadata).hasSize(3)
assertThat(results.states).hasSize(3)
}
}