mirror of
https://github.com/corda/corda.git
synced 2024-12-23 14:52:29 +00:00
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:
parent
1750ab07af
commit
a3dbbc173b
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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>>()
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user