diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt index 910bd33b12..d6d09077fc 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt @@ -40,6 +40,7 @@ sealed class QueryCriteria { abstract class CommonQueryCriteria : QueryCriteria() { abstract val status: Vault.StateStatus + abstract val contractStateTypes: Set>? override fun visit(parser: IQueryCriteriaParser): Collection { return parser.parseCriteria(this) } @@ -49,13 +50,14 @@ sealed class QueryCriteria { * 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>? = null, + override val contractStateTypes: Set>? = null, val stateRefs: List? = null, val notary: List? = null, val softLockingCondition: SoftLockingCondition? = null, val timeCondition: TimeCondition? = null) : CommonQueryCriteria() { override fun visit(parser: IQueryCriteriaParser): Collection { - 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? = null, val uuid: List? = null, val externalId: List? = null, - override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { + override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, + override val contractStateTypes: Set>? = null) : CommonQueryCriteria() { override fun visit(parser: IQueryCriteriaParser): Collection { - 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? = null, val issuer: List? = null, val issuerRef: List? = null, - override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { + override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, + override val contractStateTypes: Set>? = null) : CommonQueryCriteria() { override fun visit(parser: IQueryCriteriaParser): Collection { - 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 @JvmOverloads constructor (val expression: CriteriaExpression, - override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) : CommonQueryCriteria() { + override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, + override val contractStateTypes: Set>? = null) : CommonQueryCriteria() { override fun visit(parser: IQueryCriteriaParser): Collection { - return parser.parseCriteria(this as CommonQueryCriteria).plus(parser.parseCriteria(this)) + super.visit(parser) + return parser.parseCriteria(this) } } diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 51cbcde689..e3e58ca933 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -9,39 +9,36 @@ import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @CordaSerializable -enum class BinaryLogicalOperator { +interface Operator + +enum class BinaryLogicalOperator : Operator { AND, OR } -@CordaSerializable -enum class EqualityComparisonOperator { +enum class EqualityComparisonOperator : Operator { EQUAL, NOT_EQUAL } -@CordaSerializable -enum class BinaryComparisonOperator { +enum class BinaryComparisonOperator : Operator { LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, } -@CordaSerializable -enum class NullOperator { +enum class NullOperator : Operator { IS_NULL, NOT_NULL } -@CordaSerializable -enum class LikenessOperator { +enum class LikenessOperator : Operator { LIKE, NOT_LIKE } -@CordaSerializable -enum class CollectionOperator { +enum class CollectionOperator : Operator { IN, NOT_IN } diff --git a/docs/source/api-vault-query.rst b/docs/source/api-vault-query.rst index f924e68134..c3b747c1ec 100644 --- a/docs/source/api-vault-query.rst +++ b/docs/source/api-vault-query.rst @@ -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`) +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 (``>``), 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: .. 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 :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. 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: -.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt - :language: kotlin +.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java + :language: java :start-after: DOCSTART VaultJavaQueryExample21 :end-before: DOCEND VaultJavaQueryExample21 Aggregations on cash grouped by currency for various functions: -.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt - :language: kotlin +.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java + :language: java :start-after: DOCSTART VaultJavaQueryExample22 :end-before: DOCEND VaultJavaQueryExample22 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 - :language: kotlin +.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java + :language: java :start-after: DOCSTART VaultJavaQueryExample23 :end-before: DOCEND VaultJavaQueryExample23 diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 65372ab4f2..cd88037e0a 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,9 @@ from the previous milestone release. 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) * Removed usage of Requery ORM library (repalced with JPA/Hibernate) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index db3cbe47f6..e52df16533 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -13,6 +13,9 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toHexString 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 javax.persistence.Tuple import javax.persistence.criteria.* @@ -30,8 +33,9 @@ class HibernateQueryCriteriaParser(val contractType: Class, // incrementally build list of join predicates private val joinPredicates = mutableListOf() // incrementally build list of root entities (for later use in Sort parsing) - private val rootEntities = mutableMapOf, Root<*>>() + private val rootEntities = mutableMapOf, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates)) private val aggregateExpressions = mutableListOf>() + private val commonPredicates = mutableMapOf, Predicate>() // schema attribute Name, operator -> predicate var stateTypes: Vault.StateStatus = Vault.StateStatus.UNCONSUMED @@ -39,11 +43,6 @@ class HibernateQueryCriteriaParser(val contractType: Class, log.trace { "Parsing VaultQueryCriteria: $criteria" } val predicateSet = mutableSetOf() - // contract State Types - val contractTypes = deriveContractTypes(criteria.contractStateTypes) - if (contractTypes.isNotEmpty()) - predicateSet.add(criteriaBuilder.and(vaultStates.get("contractStateClassName").`in`(contractTypes))) - // soft locking criteria.softLockingCondition?.let { val softLocking = criteria.softLockingCondition @@ -91,12 +90,14 @@ class HibernateQueryCriteriaParser(val contractType: Class, return predicateSet } - private fun deriveContractTypes(contractStateTypes: Set>? = null): List { + private fun deriveContractTypes(contractStateTypes: Set>? = null): Set { + log.trace { "Contract types to be derived: primary ($contractType), additional ($contractStateTypes)" } val combinedContractStateTypes = contractStateTypes?.plus(contractType) ?: setOf(contractType) 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 } - 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, val joinPredicate = criteriaBuilder.equal(vaultStates.get("stateRef"), vaultFungibleStates.get("stateRef")) predicateSet.add(joinPredicate) - // contract State Types - val contractTypes = deriveContractTypes() - if (contractTypes.isNotEmpty()) - predicateSet.add(criteriaBuilder.and(vaultStates.get("contractStateClassName").`in`(contractTypes))) - // owner criteria.owner?.let { val owners = criteria.owner as List @@ -282,11 +278,6 @@ class HibernateQueryCriteriaParser(val contractType: Class, val joinPredicate = criteriaBuilder.equal(vaultStates.get("stateRef"), vaultLinearStates.get("stateRef")) joinPredicates.add(joinPredicate) - // contract State Types - val contractTypes = deriveContractTypes() - if (contractTypes.isNotEmpty()) - predicateSet.add(criteriaBuilder.and(vaultStates.get("contractStateClassName").`in`(contractTypes))) - // linear ids UUID criteria.uuid?.let { val uuids = criteria.uuid as List @@ -322,11 +313,6 @@ class HibernateQueryCriteriaParser(val contractType: Class, val joinPredicate = criteriaBuilder.equal(vaultStates.get("stateRef"), entityRoot.get("stateRef")) joinPredicates.add(joinPredicate) - // contract State Types - val contractTypes = deriveContractTypes() - if (contractTypes.isNotEmpty()) - predicateSet.add(criteriaBuilder.and(vaultStates.get("contractStateClassName").`in`(contractTypes))) - // resolve general criteria expressions parseExpression(entityRoot, criteria.expression, predicateSet) } @@ -379,11 +365,11 @@ class HibernateQueryCriteriaParser(val contractType: Class, val selections = if (aggregateExpressions.isEmpty()) - listOf(vaultStates).plus(rootEntities.map { it.value }) + rootEntities.map { it.value } else aggregateExpressions criteriaQuery.multiselect(selections) - val combinedPredicates = joinPredicates.plus(predicateSet) + val combinedPredicates = joinPredicates.plus(predicateSet).plus(commonPredicates.values) criteriaQuery.where(*combinedPredicates.toTypedArray()) return predicateSet @@ -391,14 +377,39 @@ class HibernateQueryCriteriaParser(val contractType: Class, override fun parseCriteria(criteria: CommonQueryCriteria): Collection { log.trace { "Parsing CommonQueryCriteria: $criteria" } - val predicateSet = mutableSetOf() // state status stateTypes = criteria.status - if (criteria.status != Vault.StateStatus.ALL) - predicateSet.add(criteriaBuilder.equal(vaultStates.get("stateStatus"), criteria.status)) + if (criteria.status != Vault.StateStatus.ALL) { + 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(VaultSchemaV1.VaultStates::stateStatus.name), criteria.status)) + } + } + else { + commonPredicates.put(predicateID, criteriaBuilder.equal(vaultStates.get(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(VaultSchemaV1.VaultStates::contractStateClassName.name).`in`(contractTypes.plus(existingTypes)))) + } + } else { + commonPredicates.put(predicateID, criteriaBuilder.and(vaultStates.get(VaultSchemaV1.VaultStates::contractStateClassName.name).`in`(contractTypes))) + } + } + + return emptySet() } private fun parse(sorting: Sort) { diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 55a68b4fe8..64fb9c81fa 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -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(val services: ServiceHub, - val lockId: UUID, - val contractType: Class) : 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 { - modifiedCriteria = criteria - return emptyList() - } - - override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection { - modifiedCriteria = criteria - return emptyList() - } - - override fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection { - modifiedCriteria = criteria - return emptyList() - } - - override fun parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria): Collection { - modifiedCriteria = criteria - return emptyList() - } - - override fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): Collection { - 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 { - parse(left) - val modifiedLeft = modifiedCriteria - parse(right) - val modifiedRight = modifiedCriteria - modifiedCriteria = modifiedLeft.or(modifiedRight) - return emptyList() - } - - override fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection { - parse(left) - val modifiedLeft = modifiedCriteria - parse(right) - val modifiedRight = modifiedCriteria - modifiedCriteria = modifiedLeft.and(modifiedRight) - return emptyList() - } - - override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection { - val basicQuery = modifiedCriteria - criteria.visit(this) - modifiedCriteria = if (alreadyHasVaultQuery) modifiedCriteria else criteria.and(basicQuery) - return emptyList() - } - - fun queryForEligibleStates(criteria: QueryCriteria): Vault.Page { - 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 @Throws(StatesNotAvailableException::class) override fun , U : Any> tryLockFungibleStatesForSpending(lockId: UUID, @@ -418,9 +346,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT return emptyList() } - // TODO This helper code re-writes the query to alter the defaults on things such as soft locks - // and then runs the query. Ideally we would not need to do this. - val results = QueryEditor(services, lockId, contractType).queryForEligibleStates(eligibleStatesQuery) + // Enrich QueryCriteria with additional default attributes (such as soft locks) + val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF) + 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 val claimedStates = mutableListOf>() diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 31ead52f06..7b4afb99a2 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -192,8 +192,8 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase { QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes); List linearIds = Collections.singletonList(ids.getSecond().getId()); - QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds); - QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds); + QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds, null, status); + QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds, status); QueryCriteria compositeCriteria1 = dealCriteriaAll.or(linearCriteriaAll); QueryCriteria compositeCriteria2 = vaultCriteria.and(compositeCriteria1); diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index b704f1ec9d..2ceab08fce 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -11,7 +11,6 @@ import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.utilities.* import net.corda.finance.* -import net.corda.node.services.schema.NodeSchemaService import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.Commodity 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 results = vaultQuerySvc.queryBy(compositeCriteria, sorting = sorting) - assertThat(results.statesMetadata).hasSize(13) - assertThat(results.states).hasSize(13) + assertThat(results.statesMetadata).hasSize(4) + assertThat(results.states).hasSize(4) } } @@ -1860,13 +1859,13 @@ class VaultQueryTests : TestDependencyInjectionBase() { services.fillWithSomeTestLinearStates(1, "TEST1") val aState = services.fillWithSomeTestLinearStates(1, "TEST2").states 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 { val results = builder { - val externalIdCondition = VaultSchemaV1.VaultLinearStates::externalId.equal("TEST2") + val externalIdCondition = VaultSchemaV1.VaultLinearStates::externalId.equal("TEST1") val externalIdCustomCriteria = VaultCustomQueryCriteria(externalIdCondition) val uuidCondition = VaultSchemaV1.VaultLinearStates::uuid.equal(uuid) @@ -1875,8 +1874,8 @@ class VaultQueryTests : TestDependencyInjectionBase() { val criteria = externalIdCustomCriteria or uuidCustomCriteria vaultQuerySvc.queryBy(criteria) } - assertThat(results.statesMetadata).hasSize(3) - assertThat(results.states).hasSize(3) + assertThat(results.statesMetadata).hasSize(2) + 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>(baseCriteria and enrichedCriteria, sorter).states + assertThat(results).hasSize(4) + } + } + /** * Dynamic trackBy() tests */ @@ -1965,7 +1992,9 @@ class VaultQueryTests : TestDependencyInjectionBase() { fun trackCashStates_unconsumed() { val updates = database.transaction { + // DOCSTART VaultQueryExample15 vaultQuerySvc.trackBy().updates // UNCONSUMED default + // DOCEND VaultQueryExample15 } val (linearStates,dealStates) = database.transaction {