Vault query criteria default attribute handling. (#1322)

* Performance fix: prevent self joins on VaultStates table (was occurring when Sort specified).

* Enrichment and overriding of Common attributes (eg. Vault.StateStatus and Contract State Types) using composite query criteria.
Remove unnecessary QueryEditor implementation from NodeVaultService.

* Updated documentation and changelog.

* Misc fixes to broken documentation code snippets.

* Incorporating changes from PR review feedback.
This commit is contained in:
josecoll 2017-08-25 08:38:12 +01:00 committed by GitHub
parent 1750ab07af
commit a3dbbc173b
8 changed files with 129 additions and 144 deletions

View File

@ -40,6 +40,7 @@ sealed class QueryCriteria {
abstract class CommonQueryCriteria : QueryCriteria() { abstract class CommonQueryCriteria : QueryCriteria() {
abstract val status: Vault.StateStatus abstract val status: Vault.StateStatus
abstract val contractStateTypes: Set<Class<out ContractState>>?
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this) return parser.parseCriteria(this)
} }
@ -49,13 +50,14 @@ sealed class QueryCriteria {
* VaultQueryCriteria: provides query by attributes defined in [VaultSchema.VaultStates] * VaultQueryCriteria: provides query by attributes defined in [VaultSchema.VaultStates]
*/ */
data class VaultQueryCriteria @JvmOverloads constructor (override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, data class VaultQueryCriteria @JvmOverloads constructor (override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
val contractStateTypes: Set<Class<out ContractState>>? = null, override val contractStateTypes: Set<Class<out ContractState>>? = null,
val stateRefs: List<StateRef>? = null, val stateRefs: List<StateRef>? = null,
val notary: List<AbstractParty>? = null, val notary: List<AbstractParty>? = null,
val softLockingCondition: SoftLockingCondition? = null, val softLockingCondition: SoftLockingCondition? = null,
val timeCondition: TimeCondition? = null) : CommonQueryCriteria() { val timeCondition: TimeCondition? = null) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this)) super.visit(parser)
return parser.parseCriteria(this)
} }
} }
@ -65,9 +67,11 @@ sealed class QueryCriteria {
data class LinearStateQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null, data class LinearStateQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null,
val uuid: List<UUID>? = null, val uuid: List<UUID>? = null,
val externalId: List<String>? = null, val externalId: List<String>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this)) super.visit(parser)
return parser.parseCriteria(this)
} }
} }
@ -83,9 +87,11 @@ sealed class QueryCriteria {
val quantity: ColumnPredicate<Long>? = null, val quantity: ColumnPredicate<Long>? = null,
val issuer: List<AbstractParty>? = null, val issuer: List<AbstractParty>? = null,
val issuerRef: List<OpaqueBytes>? = null, val issuerRef: List<OpaqueBytes>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this)) super.visit(parser)
return parser.parseCriteria(this)
} }
} }
@ -101,9 +107,11 @@ sealed class QueryCriteria {
*/ */
data class VaultCustomQueryCriteria<L : PersistentState> @JvmOverloads constructor data class VaultCustomQueryCriteria<L : PersistentState> @JvmOverloads constructor
(val expression: CriteriaExpression<L, Boolean>, (val expression: CriteriaExpression<L, Boolean>,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this)) super.visit(parser)
return parser.parseCriteria(this)
} }
} }

View File

@ -9,39 +9,36 @@ import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaGetter
@CordaSerializable @CordaSerializable
enum class BinaryLogicalOperator { interface Operator
enum class BinaryLogicalOperator : Operator {
AND, AND,
OR OR
} }
@CordaSerializable enum class EqualityComparisonOperator : Operator {
enum class EqualityComparisonOperator {
EQUAL, EQUAL,
NOT_EQUAL NOT_EQUAL
} }
@CordaSerializable enum class BinaryComparisonOperator : Operator {
enum class BinaryComparisonOperator {
LESS_THAN, LESS_THAN,
LESS_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL,
GREATER_THAN, GREATER_THAN,
GREATER_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL,
} }
@CordaSerializable enum class NullOperator : Operator {
enum class NullOperator {
IS_NULL, IS_NULL,
NOT_NULL NOT_NULL
} }
@CordaSerializable enum class LikenessOperator : Operator {
enum class LikenessOperator {
LIKE, LIKE,
NOT_LIKE NOT_LIKE
} }
@CordaSerializable enum class CollectionOperator : Operator {
enum class CollectionOperator {
IN, IN,
NOT_IN NOT_IN
} }

View File

@ -70,6 +70,15 @@ There are four implementations of this interface which can be chained together t
.. note:: It is a requirement to register any custom contract schemas to be used in Vault Custom queries in the associated `CordaPluginRegistry` configuration for the respective CorDapp using the ``requiredSchemas`` configuration field (which specifies a set of `MappedSchema`) .. note:: It is a requirement to register any custom contract schemas to be used in Vault Custom queries in the associated `CordaPluginRegistry` configuration for the respective CorDapp using the ``requiredSchemas`` configuration field (which specifies a set of `MappedSchema`)
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes:
1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states.
When chaining several criterias using AND / OR, the last value of this attribute will override any previous.
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this will be ``ContractState`` which resolves to all types).
When chaining several criteria using AND / OR, all specified contract state types are combined into a single set.
An example of a custom query is illustrated here: An example of a custom query is illustrated here:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt .. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
@ -77,10 +86,6 @@ An example of a custom query is illustrated here:
:start-after: DOCSTART VaultQueryExample20 :start-after: DOCSTART VaultQueryExample20
:end-before: DOCEND VaultQueryExample20 :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. .. 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.
Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java. Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java.
@ -317,22 +322,22 @@ Query for consumed deal states or linear ids, specify a paging specification and
Aggregations on cash using various functions: Aggregations on cash using various functions:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt .. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: kotlin :language: java
:start-after: DOCSTART VaultJavaQueryExample21 :start-after: DOCSTART VaultJavaQueryExample21
:end-before: DOCEND VaultJavaQueryExample21 :end-before: DOCEND VaultJavaQueryExample21
Aggregations on cash grouped by currency for various functions: Aggregations on cash grouped by currency for various functions:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt .. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: kotlin :language: java
:start-after: DOCSTART VaultJavaQueryExample22 :start-after: DOCSTART VaultJavaQueryExample22
:end-before: DOCEND VaultJavaQueryExample22 :end-before: DOCEND VaultJavaQueryExample22
Sum aggregation on cash grouped by issuer party and currency and sorted by sum: Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt .. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: kotlin :language: java
:start-after: DOCSTART VaultJavaQueryExample23 :start-after: DOCSTART VaultJavaQueryExample23
:end-before: DOCEND VaultJavaQueryExample23 :end-before: DOCEND VaultJavaQueryExample23

View File

@ -6,6 +6,9 @@ from the previous milestone release.
UNRELEASED UNRELEASED
---------- ----------
* Vault query common attributes (state status and contract state types) are now handled correctly when using composite
criteria specifications. State status is overridable. Contract states types are aggregatable.
* Cash selection algorithm is now pluggable (with H2 being the default implementation) * Cash selection algorithm is now pluggable (with H2 being the default implementation)
* Removed usage of Requery ORM library (repalced with JPA/Hibernate) * Removed usage of Requery ORM library (repalced with JPA/Hibernate)

View File

@ -13,6 +13,9 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import org.hibernate.query.criteria.internal.expression.LiteralExpression
import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate
import org.hibernate.query.criteria.internal.predicate.InPredicate
import java.util.* import java.util.*
import javax.persistence.Tuple import javax.persistence.Tuple
import javax.persistence.criteria.* import javax.persistence.criteria.*
@ -30,8 +33,9 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
// incrementally build list of join predicates // incrementally build list of join predicates
private val joinPredicates = mutableListOf<Predicate>() private val joinPredicates = mutableListOf<Predicate>()
// incrementally build list of root entities (for later use in Sort parsing) // incrementally build list of root entities (for later use in Sort parsing)
private val rootEntities = mutableMapOf<Class<out PersistentState>, Root<*>>() private val rootEntities = mutableMapOf<Class<out PersistentState>, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates))
private val aggregateExpressions = mutableListOf<Expression<*>>() private val aggregateExpressions = mutableListOf<Expression<*>>()
private val commonPredicates = mutableMapOf<Pair<String,Operator>, Predicate>() // schema attribute Name, operator -> predicate
var stateTypes: Vault.StateStatus = Vault.StateStatus.UNCONSUMED var stateTypes: Vault.StateStatus = Vault.StateStatus.UNCONSUMED
@ -39,11 +43,6 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
log.trace { "Parsing VaultQueryCriteria: $criteria" } log.trace { "Parsing VaultQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>() val predicateSet = mutableSetOf<Predicate>()
// contract State Types
val contractTypes = deriveContractTypes(criteria.contractStateTypes)
if (contractTypes.isNotEmpty())
predicateSet.add(criteriaBuilder.and(vaultStates.get<String>("contractStateClassName").`in`(contractTypes)))
// soft locking // soft locking
criteria.softLockingCondition?.let { criteria.softLockingCondition?.let {
val softLocking = criteria.softLockingCondition val softLocking = criteria.softLockingCondition
@ -91,12 +90,14 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
return predicateSet return predicateSet
} }
private fun deriveContractTypes(contractStateTypes: Set<Class<out ContractState>>? = null): List<String> { private fun deriveContractTypes(contractStateTypes: Set<Class<out ContractState>>? = null): Set<String> {
log.trace { "Contract types to be derived: primary ($contractType), additional ($contractStateTypes)" }
val combinedContractStateTypes = contractStateTypes?.plus(contractType) ?: setOf(contractType) val combinedContractStateTypes = contractStateTypes?.plus(contractType) ?: setOf(contractType)
combinedContractStateTypes.filter { it.name != ContractState::class.java.name }.let { combinedContractStateTypes.filter { it.name != ContractState::class.java.name }.let {
val interfaces = it.flatMap { contractTypeMappings[it.name] ?: listOf(it.name) } val interfaces = it.flatMap { contractTypeMappings[it.name] ?: setOf(it.name) }
val concrete = it.filter { !it.isInterface }.map { it.name } val concrete = it.filter { !it.isInterface }.map { it.name }
return interfaces.plus(concrete) log.trace { "Derived contract types: ${interfaces.union(concrete)}" }
return interfaces.union(concrete)
} }
} }
@ -233,11 +234,6 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultFungibleStates.get<PersistentStateRef>("stateRef")) val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultFungibleStates.get<PersistentStateRef>("stateRef"))
predicateSet.add(joinPredicate) predicateSet.add(joinPredicate)
// contract State Types
val contractTypes = deriveContractTypes()
if (contractTypes.isNotEmpty())
predicateSet.add(criteriaBuilder.and(vaultStates.get<String>("contractStateClassName").`in`(contractTypes)))
// owner // owner
criteria.owner?.let { criteria.owner?.let {
val owners = criteria.owner as List<AbstractParty> val owners = criteria.owner as List<AbstractParty>
@ -282,11 +278,6 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef")) val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), vaultLinearStates.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate) joinPredicates.add(joinPredicate)
// contract State Types
val contractTypes = deriveContractTypes()
if (contractTypes.isNotEmpty())
predicateSet.add(criteriaBuilder.and(vaultStates.get<String>("contractStateClassName").`in`(contractTypes)))
// 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>
@ -322,11 +313,6 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef")) val joinPredicate = criteriaBuilder.equal(vaultStates.get<PersistentStateRef>("stateRef"), entityRoot.get<PersistentStateRef>("stateRef"))
joinPredicates.add(joinPredicate) joinPredicates.add(joinPredicate)
// contract State Types
val contractTypes = deriveContractTypes()
if (contractTypes.isNotEmpty())
predicateSet.add(criteriaBuilder.and(vaultStates.get<String>("contractStateClassName").`in`(contractTypes)))
// resolve general criteria expressions // resolve general criteria expressions
parseExpression(entityRoot, criteria.expression, predicateSet) parseExpression(entityRoot, criteria.expression, predicateSet)
} }
@ -379,11 +365,11 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
val selections = val selections =
if (aggregateExpressions.isEmpty()) if (aggregateExpressions.isEmpty())
listOf(vaultStates).plus(rootEntities.map { it.value }) rootEntities.map { it.value }
else else
aggregateExpressions aggregateExpressions
criteriaQuery.multiselect(selections) criteriaQuery.multiselect(selections)
val combinedPredicates = joinPredicates.plus(predicateSet) val combinedPredicates = joinPredicates.plus(predicateSet).plus(commonPredicates.values)
criteriaQuery.where(*combinedPredicates.toTypedArray()) criteriaQuery.where(*combinedPredicates.toTypedArray())
return predicateSet return predicateSet
@ -391,14 +377,39 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
override fun parseCriteria(criteria: CommonQueryCriteria): Collection<Predicate> { override fun parseCriteria(criteria: CommonQueryCriteria): Collection<Predicate> {
log.trace { "Parsing CommonQueryCriteria: $criteria" } log.trace { "Parsing CommonQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>()
// state status // state status
stateTypes = criteria.status stateTypes = criteria.status
if (criteria.status != Vault.StateStatus.ALL) if (criteria.status != Vault.StateStatus.ALL) {
predicateSet.add(criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>("stateStatus"), criteria.status)) val predicateID = Pair(VaultSchemaV1.VaultStates::stateStatus.name, EqualityComparisonOperator.EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.status) {
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::stateStatus.name}] value $existingStatus with ${criteria.status}")
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), criteria.status))
}
}
else {
commonPredicates.put(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), criteria.status))
}
}
return predicateSet // contract state types
val contractTypes = deriveContractTypes(criteria.contractStateTypes)
if (contractTypes.isNotEmpty()) {
val predicateID = Pair(VaultSchemaV1.VaultStates::contractStateClassName.name, CollectionOperator.IN)
if (commonPredicates.containsKey(predicateID)) {
val existingTypes = (commonPredicates[predicateID]!!.expressions[0] as InPredicate<*>).values.map { (it as LiteralExpression).literal }.toSet()
if (existingTypes != contractTypes) {
log.warn("Enriching previous attribute [${VaultSchemaV1.VaultStates::contractStateClassName.name}] values [$existingTypes] with [$contractTypes]")
commonPredicates.replace(predicateID, criteriaBuilder.and(vaultStates.get<String>(VaultSchemaV1.VaultStates::contractStateClassName.name).`in`(contractTypes.plus(existingTypes))))
}
} else {
commonPredicates.put(predicateID, criteriaBuilder.and(vaultStates.get<String>(VaultSchemaV1.VaultStates::contractStateClassName.name).`in`(contractTypes)))
}
}
return emptySet()
} }
private fun parse(sorting: Sort) { private fun parse(sorting: Sort) {

View File

@ -336,78 +336,6 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
} }
} }
// TODO We shouldn't need to rewrite the query if we could modify the defaults.
private class QueryEditor<out T : ContractState>(val services: ServiceHub,
val lockId: UUID,
val contractType: Class<out T>) : IQueryCriteriaParser {
var alreadyHasVaultQuery: Boolean = false
var modifiedCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(contractStateTypes = setOf(contractType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
status = Vault.StateStatus.UNCONSUMED)
override fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria.copy(contractStateTypes = setOf(contractType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
status = Vault.StateStatus.UNCONSUMED)
alreadyHasVaultQuery = true
return emptyList()
}
override fun parseOr(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
parse(left)
val modifiedLeft = modifiedCriteria
parse(right)
val modifiedRight = modifiedCriteria
modifiedCriteria = modifiedLeft.or(modifiedRight)
return emptyList()
}
override fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
parse(left)
val modifiedLeft = modifiedCriteria
parse(right)
val modifiedRight = modifiedCriteria
modifiedCriteria = modifiedLeft.and(modifiedRight)
return emptyList()
}
override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection<Predicate> {
val basicQuery = modifiedCriteria
criteria.visit(this)
modifiedCriteria = if (alreadyHasVaultQuery) modifiedCriteria else criteria.and(basicQuery)
return emptyList()
}
fun queryForEligibleStates(criteria: QueryCriteria): Vault.Page<T> {
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
parse(criteria, sorter)
return services.vaultQueryService.queryBy(contractType, modifiedCriteria, sorter)
}
}
@Suspendable @Suspendable
@Throws(StatesNotAvailableException::class) @Throws(StatesNotAvailableException::class)
override fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID, override fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
@ -418,9 +346,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
return emptyList() return emptyList()
} }
// TODO This helper code re-writes the query to alter the defaults on things such as soft locks // Enrich QueryCriteria with additional default attributes (such as soft locks)
// and then runs the query. Ideally we would not need to do this. val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
val results = QueryEditor(services, lockId, contractType).queryForEligibleStates(eligibleStatesQuery) val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
val enrichedCriteria = QueryCriteria.VaultQueryCriteria(
contractStateTypes = setOf(contractType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)))
val results = services.vaultQueryService.queryBy(contractType, enrichedCriteria.and(eligibleStatesQuery), sorter)
var claimedAmount = 0L var claimedAmount = 0L
val claimedStates = mutableListOf<StateAndRef<T>>() val claimedStates = mutableListOf<StateAndRef<T>>()

View File

@ -192,8 +192,8 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes); QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes);
List<UUID> linearIds = Collections.singletonList(ids.getSecond().getId()); List<UUID> linearIds = Collections.singletonList(ids.getSecond().getId());
QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds); QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds, null, status);
QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds); QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds, status);
QueryCriteria compositeCriteria1 = dealCriteriaAll.or(linearCriteriaAll); QueryCriteria compositeCriteria1 = dealCriteriaAll.or(linearCriteriaAll);
QueryCriteria compositeCriteria2 = vaultCriteria.and(compositeCriteria1); QueryCriteria compositeCriteria2 = vaultCriteria.and(compositeCriteria1);

View File

@ -11,7 +11,6 @@ import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.node.services.vault.QueryCriteria.*
import net.corda.core.utilities.* import net.corda.core.utilities.*
import net.corda.finance.* import net.corda.finance.*
import net.corda.node.services.schema.NodeSchemaService
import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.DealState import net.corda.finance.contracts.DealState
@ -1338,8 +1337,8 @@ class VaultQueryTests : TestDependencyInjectionBase() {
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.LinearStateAttribute.EXTERNAL_ID), Sort.Direction.DESC))) val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.LinearStateAttribute.EXTERNAL_ID), Sort.Direction.DESC)))
val results = vaultQuerySvc.queryBy<LinearState>(compositeCriteria, sorting = sorting) val results = vaultQuerySvc.queryBy<LinearState>(compositeCriteria, sorting = sorting)
assertThat(results.statesMetadata).hasSize(13) assertThat(results.statesMetadata).hasSize(4)
assertThat(results.states).hasSize(13) assertThat(results.states).hasSize(4)
} }
} }
@ -1860,13 +1859,13 @@ class VaultQueryTests : TestDependencyInjectionBase() {
services.fillWithSomeTestLinearStates(1, "TEST1") services.fillWithSomeTestLinearStates(1, "TEST1")
val aState = services.fillWithSomeTestLinearStates(1, "TEST2").states val aState = services.fillWithSomeTestLinearStates(1, "TEST2").states
services.consumeLinearStates(aState.toList(), DUMMY_NOTARY) services.consumeLinearStates(aState.toList(), DUMMY_NOTARY)
services.fillWithSomeTestLinearStates(1, "TEST3").states.first().state.data.linearId.id services.fillWithSomeTestLinearStates(1, "TEST1").states.first().state.data.linearId.id
// 2 unconsumed states with same external ID, 1 with different external ID // 2 unconsumed states with same external ID, 1 consumed with different external ID
} }
database.transaction { database.transaction {
val results = builder { val results = builder {
val externalIdCondition = VaultSchemaV1.VaultLinearStates::externalId.equal("TEST2") val externalIdCondition = VaultSchemaV1.VaultLinearStates::externalId.equal("TEST1")
val externalIdCustomCriteria = VaultCustomQueryCriteria(externalIdCondition) val externalIdCustomCriteria = VaultCustomQueryCriteria(externalIdCondition)
val uuidCondition = VaultSchemaV1.VaultLinearStates::uuid.equal(uuid) val uuidCondition = VaultSchemaV1.VaultLinearStates::uuid.equal(uuid)
@ -1875,8 +1874,8 @@ class VaultQueryTests : TestDependencyInjectionBase() {
val criteria = externalIdCustomCriteria or uuidCustomCriteria val criteria = externalIdCustomCriteria or uuidCustomCriteria
vaultQuerySvc.queryBy<LinearState>(criteria) vaultQuerySvc.queryBy<LinearState>(criteria)
} }
assertThat(results.statesMetadata).hasSize(3) assertThat(results.statesMetadata).hasSize(2)
assertThat(results.states).hasSize(3) assertThat(results.states).hasSize(2)
} }
} }
@ -1957,6 +1956,34 @@ class VaultQueryTests : TestDependencyInjectionBase() {
} }
} }
@Test
fun `enriched and overridden composite query handles defaults correctly`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 2, 2, Random(0L))
services.fillWithSomeTestCommodity(Amount(100, Commodity.getInstance("FCOJ")!!), notaryServices)
services.fillWithSomeTestLinearStates(1, "ABC")
services.fillWithSomeTestDeals(listOf("123"))
}
database.transaction {
// Base criteria
val baseCriteria = VaultQueryCriteria(notary = listOf(DUMMY_NOTARY),
status = Vault.StateStatus.CONSUMED)
// Enrich and override QueryCriteria with additional default attributes (such as soft locks)
val enrichedCriteria = VaultQueryCriteria(contractStateTypes = setOf(DealState::class.java), // enrich
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(UUID.randomUUID())),
status = Vault.StateStatus.UNCONSUMED) // override
// Sorting
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
// Execute query
val results = services.vaultQueryService.queryBy<FungibleAsset<*>>(baseCriteria and enrichedCriteria, sorter).states
assertThat(results).hasSize(4)
}
}
/** /**
* Dynamic trackBy() tests * Dynamic trackBy() tests
*/ */
@ -1965,7 +1992,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
fun trackCashStates_unconsumed() { fun trackCashStates_unconsumed() {
val updates = val updates =
database.transaction { database.transaction {
// DOCSTART VaultQueryExample15
vaultQuerySvc.trackBy<Cash.State>().updates // UNCONSUMED default vaultQuerySvc.trackBy<Cash.State>().updates // UNCONSUMED default
// DOCEND VaultQueryExample15
} }
val (linearStates,dealStates) = val (linearStates,dealStates) =
database.transaction { database.transaction {