mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
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:
parent
88a8eabd60
commit
f732d2cefe
@ -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>
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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" }
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user