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 fe1a809062..542cf710d0 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 @@ -81,6 +81,7 @@ sealed class QueryCriteria : GenericQueryCriteria = emptySet() open val participants: List? = null abstract val contractStateTypes: Set>? + open val externalIds: List = emptyList() override fun visit(parser: IQueryCriteriaParser): Collection { return parser.parseCriteria(this) } @@ -99,8 +100,24 @@ sealed class QueryCriteria : GenericQueryCriteria = emptySet(), override val constraints: Set = emptySet(), - override val participants: List? = null + override val participants: List? = null, + override val externalIds: List = emptyList() ) : CommonQueryCriteria() { + // V4 constructors. + @DeprecatedConstructorForDeserialization(version = 7) + constructor( + status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, + contractStateTypes: Set>? = null, + stateRefs: List? = null, + notary: List? = null, + softLockingCondition: SoftLockingCondition? = null, + timeCondition: TimeCondition? = null, + relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL, + constraintTypes: Set = emptySet(), + constraints: Set = emptySet(), + participants: List? = null + ) : this(status, contractStateTypes, stateRefs, notary, softLockingCondition, timeCondition, relevancyStatus, constraintTypes, constraints, participants, emptyList()) + // V3 c'tors // These have to be manually specified as @JvmOverloads for some reason causes declaration clashes @DeprecatedConstructorForDeserialization(version = 6) @@ -144,6 +161,34 @@ sealed class QueryCriteria : GenericQueryCriteria): VaultQueryCriteria = copy(constraintTypes = constraintTypes) fun withConstraints(constraints: Set): VaultQueryCriteria = copy(constraints = constraints) fun withParticipants(participants: List): VaultQueryCriteria = copy(participants = participants) + fun withExternalIds(externalIds: List): VaultQueryCriteria = copy(externalIds = externalIds) + + fun copy( + status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, + contractStateTypes: Set>? = null, + stateRefs: List? = null, + notary: List? = null, + softLockingCondition: SoftLockingCondition? = null, + timeCondition: TimeCondition? = null, + relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL, + constraintTypes: Set = emptySet(), + constraints: Set = emptySet(), + participants: List? = null + ): VaultQueryCriteria { + return VaultQueryCriteria( + status, + contractStateTypes, + stateRefs, + notary, + softLockingCondition, + timeCondition, + relevancyStatus, + constraintTypes, + constraints, + participants, + externalIds + ) + } fun copy( status: Vault.StateStatus = this.status, @@ -163,7 +208,8 @@ sealed class QueryCriteria : GenericQueryCriteria results = vaultService.queryBy(StateType.class, query); - - .. sourcecode:: kotlin - - val id: UUID = someExternalId - val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.equal(id) } - val queryCriteria = QueryCriteria.VaultCustomQueryCriteria(externalId) - val results = vaultService.queryBy(queryCriteria).states - -The ``VaultCustomQueryCriteria`` can also be combined with other query criteria, like custom schemas, for instance. See the vault query API -examples above for how to combine ``QueryCriteria``. \ No newline at end of file +When performing a vault query, it is now possible to query for states by external ID using the ``externalIds`` parameter in +``VaultQueryCriteria``. \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 94b8717de9..4049ff3cad 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,9 @@ release, see :doc:`app-upgrade-notes`. Unreleased ---------- +* Introduced a new parameter ``externalIds: List`` to ``VaultQueryCriteria`` which allows CorDapp developers to constrain queries + to a specified set of external IDs. + * Introduced a new API on ``KeyManagementService`` which facilitates lookups of ``PublicKey`` s to ``externalId`` s (Account IDs). * Introduced a new low level flow diagnostics tool: checkpoint agent (that can be used standalone or in conjunction with the ``dumpCheckpoints`` shell command). 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 3e0a72fe67..ab59626654 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 @@ -659,6 +659,25 @@ class HibernateQueryCriteriaParser(val contractStateType: Class("stateRef"), entityRoot.get("compositeKey").get("stateRef")) + val externalIdPredicate = criteriaBuilder.and(entityRoot.get("externalId").`in`(ids)) + constraintPredicates.add(externalIdJoin) + constraintPredicates.add(externalIdPredicate) + } + return emptySet() } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/ExternalIdMappingTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/ExternalIdMappingTest.kt index f8fc950025..bd1e8271fc 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/ExternalIdMappingTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/ExternalIdMappingTest.kt @@ -87,6 +87,47 @@ class ExternalIdMappingTest { assertEquals(setOf(dummyStateOne, dummyStateTwo), resultTwo.map { it.state.data }.toSet()) } + @Test + fun `externalIds query criteria test`() { + val vaultService = services.vaultService + + // Create new external ID and two keys mapped to it. + val id = UUID.randomUUID() + val idTwo = UUID.randomUUID() + val keyOne = services.keyManagementService.freshKeyAndCert(myself.identity, false, id) + val keyTwo = services.keyManagementService.freshKeyAndCert(myself.identity, false, id) + val keyThree = services.keyManagementService.freshKeyAndCert(myself.identity, false, idTwo) + + // Create states with a public key assigned to the new external ID. + val dummyStateOne = createDummyState(listOf(AnonymousParty(keyOne.owningKey))) + val dummyStateTwo = createDummyState(listOf(AnonymousParty(keyTwo.owningKey))) + val dummyStateThree = createDummyState(listOf(AnonymousParty(keyThree.owningKey))) + + // This query should return two states! + val result = database.transaction { + vaultService.queryBy(QueryCriteria.VaultQueryCriteria(externalIds = listOf(id))).states + } + assertEquals(setOf(dummyStateOne, dummyStateTwo), result.map { it.state.data }.toSet()) + + // Should return nothing. + val resultTwo = database.transaction { + vaultService.queryBy(QueryCriteria.VaultQueryCriteria(externalIds = listOf(UUID.randomUUID()))).states + } + assertEquals(emptyList(), resultTwo) + + // Should return one state. + val resultThree = database.transaction { + vaultService.queryBy(QueryCriteria.VaultQueryCriteria(externalIds = listOf(idTwo))).states + } + assertEquals(setOf(dummyStateThree), resultThree.map { it.state.data }.toSet()) + + // Should return all states. + val resultFour = database.transaction { + vaultService.queryBy(QueryCriteria.VaultQueryCriteria(externalIds = listOf())).states + } + assertEquals(setOf(dummyStateOne, dummyStateTwo, dummyStateThree), resultFour.map { it.state.data }.toSet()) + } + @Test fun `One state can be mapped to multiple externalIds`() { val vaultService = services.vaultService