API: Vault Query

Corda has been architected from the ground up to encourage the use of industry standard, proven query frameworks and libraries for accessing RDBMS backed transactional stores (including the Vault).

Corda provides a number of flexible query mechanisms for accessing the Vault:

The majority of query requirements can be satisfied by using the Vault Query API, which is exposed via the VaultService for use directly by flows:

/**
 * Generic vault query function which takes a [QueryCriteria] object to define filters,
 * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
 * and returns a [Vault.Page] object containing the following:
 *  1. states as a List of <StateAndRef> (page number and size defined by [PageSpecification])
 *  2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table.
 *  3. total number of results available if [PageSpecification] supplied (otherwise returns -1).
 *  4. status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL].
 *  5. other results (aggregate functions with/without using value groups).
 *
 * @throws VaultQueryException if the query cannot be executed for any reason
 *        (missing criteria or parsing error, paging errors, unsupported query, underlying database error).
 *
 * Notes
 *   If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned.
 *   API users must specify a [PageSpecification] if they are expecting more than [DEFAULT_PAGE_SIZE] results,
 *   otherwise a [VaultQueryException] will be thrown alerting to this condition.
 *   It is the responsibility of the API user to request further pages and/or specify a more suitable [PageSpecification].
 */
@Throws(VaultQueryException::class)
fun <T : ContractState> _queryBy(criteria: QueryCriteria,
                                 paging: PageSpecification,
                                 sorting: Sort,
                                 contractStateType: Class<out T>): Vault.Page<T>

/**
 * Generic vault query function which takes a [QueryCriteria] object to define filters,
 * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
 * and returns a [DataFeed] object containing:
 * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]).
 * 2) an [Observable] of [Vault.Update].
 *
 * @throws VaultQueryException if the query cannot be executed for any reason.
 *
 * Notes:
 *    - The snapshot part of the query adheres to the same behaviour as the [queryBy] function.
 *    - The update part of the query currently only supports query criteria filtering by contract
 *      type(s) and state status(es). CID-731 <https://r3-cev.atlassian.net/browse/CID-731> proposes
 *      adding the complete set of [QueryCriteria] filtering.
 */
@Throws(VaultQueryException::class)
fun <T : ContractState> _trackBy(criteria: QueryCriteria,
                                 paging: PageSpecification,
                                 sorting: Sort,
                                 contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>

VaultService.kt

And via CordaRPCOps for use by RPC client applications:

@RPCReturnsObservables
fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria,
                                     paging: PageSpecification,
                                     sorting: Sort,
                                     contractStateType: Class<out T>): Vault.Page<T>

CordaRPCOps.kt

@RPCReturnsObservables
fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria,
                                     paging: PageSpecification,
                                     sorting: Sort,
                                     contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>

CordaRPCOps.kt

Helper methods are also provided with default values for arguments:

fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T>

fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T>

fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T>

fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T>

CordaRPCOps.kt

fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>

fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>>

fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>>

fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>>

CordaRPCOps.kt

The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of filter criteria:

  • Use queryBy to obtain a current snapshot of data (for a given QueryCriteria)
  • Use trackBy to obtain both a current snapshot and a future stream of updates (for a given QueryCriteria)

Simple pagination (page number and size) and sorting (directional ordering using standard or custom property attributes) is also specifiable. Defaults are defined for paging (pageNumber = 1, pageSize = 200) and sorting (direction = ASC).

The QueryCriteria interface provides a flexible mechanism for specifying different filtering criteria, including and/or composition and a rich set of operators to include:

  • Binary logical (AND, OR)
  • Comparison (LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL)
  • Equality (EQUAL, NOT_EQUAL)
  • Likeness (LIKE, NOT_LIKE)
  • Nullability (IS_NULL, NOT_NULL)
  • Collection based (IN, NOT_IN)
  • Standard SQL-92 aggregate functions (SUM, AVG, MIN, MAX, COUNT)

There are four implementations of this interface which can be chained together to define advanced filters:

  • VaultQueryCriteria provides filterable criteria on attributes within the VAULT_STATES table. Filterable attributes include one or more of the following: status (UNCONSUMED, CONSUMED), state reference, contract state type, notary name, soft locked states, timestamps (RECORDED, CONSUMED), state constraints (see Constraint Types), relevancy (ALL, RELEVANT, NON_RELEVANT), and participants (exact or any match).

  • FungibleAssetQueryCriteria provides filterable criteria on attributes defined in the Corda Core FungibleAsset contract state interface, used to represent assets that are fungible, countable and issued by a specific party (for example, Cash.State and CommodityContract.State in the Corda finance module). Filterable attributes include one or more of the following: participants (exact or any match), owner, quantity, issuer name, and issuer reference.

  • LinearStateQueryCriteria provides filterable criteria on attributes defined in the Corda Core LinearState and DealState contract state interfaces, used to represent entities that continuously supersede themselves, all of which share the same linearId (for example, trade entity states such as the IRSState defined in the SIMM valuation demo). Filterable attributes include one or more of the following: participants, linear ID, UUID, and external ID.

  • VaultCustomQueryCriteria provides the means to specify one or many arbitrary expressions on attributes defined by a custom contract state that implements its own schema as described in the API: Persistence documentation and associated examples. Custom criteria expressions are expressed using one of the following type-safe forms of CriteriaExpression: BinaryLogical, Not, ColumnPredicateExpression, and AggregateFunctionExpression. The ColumnPredicateExpression allows for the specification of arbitrary criteria using the previously enumerated operator types. The AggregateFunctionExpression allows for the specification of an aggregate function type (SUM, AVG, MAX, MIN, COUNT) with optional grouping and sorting.

Furthermore, a rich DSL is provided to enable simple construction of custom criteria using any combination of ColumnPredicate. See the Builder object in QueryCriteriaUtils for a complete specification of the DSL.

All QueryCriteria implementations are composable using AND and OR operators.

All QueryCriteria implementations provide an explicitly specifiable set of common attributes:

  • A state status attribute (Vault.StateStatus), which defaults to filtering on UNCONSUMED states. When chaining several criteria using AND or OR, the last value of this attribute will override any previous value.
  • Contract state types (<Set<Class<out ContractState>>), which will contain at minimum one type (by default, this will be ContractState which resolves to all state types). When chaining several criteria using AND and OR operators, all specified contract state types are combined into a single set.

An example of a custom query in Kotlin is provided below:

val generalCriteria = VaultQueryCriteria(Vault.StateStatus.ALL)

val results = builder {
    val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode)
    val quantityIndex = PersistentCashState::pennies.greaterThanOrEqual(10L)

    val customCriteria1 = VaultCustomQueryCriteria(currencyIndex)
    val customCriteria2 = VaultCustomQueryCriteria(quantityIndex)

    val criteria = generalCriteria.and(customCriteria1.and(customCriteria2))
    vaultService.queryBy<Cash.State>(criteria)
}

VaultQueryTests.kt

The Vault Query API leverages the rich semantics of the underlying JPA Hibernate based API: Persistence framework adopted by Corda.

An example of a custom query in Java is provided below:

QueryCriteria generalCriteria = new VaultQueryCriteria(Vault.StateStatus.ALL);

FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
FieldInfo attributeQuantity = getField("pennies", CashSchemaV1.PersistentCashState.class);

CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD");
CriteriaExpression quantityIndex = Builder.greaterThanOrEqual(attributeQuantity, 10L);

QueryCriteria customCriteria2 = new VaultCustomQueryCriteria(quantityIndex);
QueryCriteria customCriteria1 = new VaultCustomQueryCriteria(currencyIndex);


QueryCriteria criteria = generalCriteria.and(customCriteria1).and(customCriteria2);
Vault.Page<ContractState> results = vaultService.queryBy(Cash.State.class, criteria);

VaultQueryJavaTests.java

Custom queries can be either case sensitive or case insensitive. They are defined via a Boolean as one of the function parameters of each operator function. By default, each operator is case sensitive.

An example of a case-sensitive custom query operator in Kotlin is provided below:

val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, true)

An example of a case-insensitive custom query operator in Kotlin is provided below:

val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, false)

An example of a case-sensitive custom query operator in Java is provided below:

FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", true);

An example of a case-insensitive custom query operator in Java is provided below:

FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", false);

The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200 results). Defining a sensible default page size enables efficient checkpointing within flows, and frees the developer from worrying about pagination where result sets are expected to be constrained to 200 or fewer entries. Where large result sets are expected (such as using the RPC API for reporting and/or UI display), it is strongly recommended to define a PageSpecification to correctly process results with efficient memory utilisation. A fail-fast mode is in place to alert API users to the need for pagination where a single query returns more than 200 results and no PageSpecification has been supplied.

Here’s a query that extracts every unconsumed ContractState from the vault in pages of size 200, starting from the default page number (page one):

val vaultSnapshot = proxy.vaultQueryBy<ContractState>(
    QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
    PageSpecification(DEFAULT_PAGE_NUM, 200))

Some examples of how to write vault queries in Kotlin are provided below.

val result = vaultService.queryBy<ContractState>()

/**
 * Query result returns a [Vault.Page] which contains:
 *  1) actual states as a list of [StateAndRef]
 *  2) state reference and associated vault metadata as a list of [Vault.StateMetadata]
 *  3) [PageSpecification] used to delimit the size of items returned in the result set (defaults to [DEFAULT_PAGE_SIZE])
 *  4) Total number of items available (to aid further pagination if required)
 */
val states = result.states
val metadata = result.statesMetadata

VaultQueryTests.kt

val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
val criteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
val results = vaultService.queryBy<DummyLinearContract.State>(criteria, Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))))

VaultQueryTests.kt

val criteria = VaultQueryCriteria(contractStateTypes = setOf(Cash.State::class.java, DealState::class.java))
val results = vaultService.queryBy<ContractState>(criteria)

VaultQueryTests.kt

val constraintTypeCriteria = VaultQueryCriteria(constraintTypes = setOf(HASH, CZ_WHITELISTED))
val sortAttribute = SortAttribute.Standard(Sort.VaultStateAttribute.CONSTRAINT_TYPE)
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
val constraintResults = vaultService.queryBy<LinearState>(constraintTypeCriteria, sorter)

VaultQueryTests.kt

val constraintCriteria = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(constraintSignature),
        Vault.ConstraintInfo(constraintSignatureCompositeKey), Vault.ConstraintInfo(constraintHash)))
val constraintResults = vaultService.queryBy<LinearState>(constraintCriteria)

VaultQueryTests.kt

val criteria = VaultQueryCriteria(notary = listOf(CASH_NOTARY))
val results = vaultService.queryBy<ContractState>(criteria)

VaultQueryTests.kt

val criteria = LinearStateQueryCriteria(participants = listOf(BIG_CORP, MINI_CORP))
val results = vaultService.queryBy<ContractState>(criteria)

VaultQueryTests.kt

val strictCriteria = LinearStateQueryCriteria(exactParticipants = listOf(MEGA_CORP, BIG_CORP))
val strictResults = vaultService.queryBy<ContractState>(strictCriteria)

VaultQueryTests.kt

val start = TODAY
val end = TODAY.plus(30, ChronoUnit.DAYS)
val recordedBetweenExpression = TimeCondition(
        QueryCriteria.TimeInstantType.RECORDED,
        ColumnPredicate.Between(start, end))
val criteria = VaultQueryCriteria(timeCondition = recordedBetweenExpression)
val results = vaultService.queryBy<ContractState>(criteria)

VaultQueryTests.kt

val pagingSpec = PageSpecification(DEFAULT_PAGE_NUM, 10)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultService.queryBy<ContractState>(criteria, paging = pagingSpec)

VaultQueryTests.kt

The following code will also detect, using previousPageAnchor, if the vault has concurrently changed whilst querying and will try to get the latest up-to-date version.

retry@
while (true) {
    var pageNumber = DEFAULT_PAGE_NUM
    val states = mutableListOf<StateAndRef<ContractState>>()
    do {
        val pageSpec = PageSpecification(pageNumber = pageNumber, pageSize = pageSize)
        val results = vaultService.queryBy<ContractState>(VaultQueryCriteria(), pageSpec)
        if (results.previousPageAnchor != states.lastOrNull()?.ref) {
            // Start querying from the 1st page again if we find the vault has changed from underneath us.
            continue@retry
        }
        states.addAll(results.states)
        pageNumber++
    } while ((pageSpec.pageSize * (pageNumber - 1)) <= results.totalStatesAvailable)
    return states
}

VaultQueryTests.kt

    val relevancyAllCriteria = VaultQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
    val allDealStateCount = vaultService.queryBy<DummyDealContract.State>(relevancyAllCriteria).states

VaultQueryTests.kt

val linearIds = issuedStates.states.map { it.state.data.linearId }.toList()
val criteria = LinearStateQueryCriteria(linearId = listOf(linearIds.first(), linearIds.last()))
val results = vaultService.queryBy<LinearState>(criteria)

VaultQueryTests.kt

val linearStateCriteria = LinearStateQueryCriteria(linearId = listOf(linearId), status = Vault.StateStatus.ALL)
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultService.queryBy<LinearState>(linearStateCriteria and vaultCriteria)

VaultQueryTests.kt

val criteria = LinearStateQueryCriteria(externalId = listOf("456", "789"))
val results = vaultService.queryBy<DealState>(criteria)

VaultQueryTests.kt

val criteria = LinearStateQueryCriteria(participants = parties)
val results = vaultService.queryBy<DealState>(criteria)

VaultQueryTests.kt

val strictCriteria = LinearStateQueryCriteria().withExactParticipants(parties)
val strictResults = vaultService.queryBy<ContractState>(strictCriteria)

VaultQueryTests.kt

    val allLinearStateCriteria = LinearStateQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
    val allLinearStates = vaultService.queryBy<DummyLinearContract.State>(allLinearStateCriteria).states

VaultQueryTests.kt

val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(USD.currencyCode) }
val criteria = VaultCustomQueryCriteria(ccyIndex)
val results = vaultService.queryBy<FungibleAsset<*>>(criteria)

VaultQueryTests.kt

val fungibleAssetCriteria = FungibleAssetQueryCriteria(quantity = builder { greaterThan(2500L) })
val results = vaultService.queryBy<Cash.State>(fungibleAssetCriteria)

VaultQueryTests.kt

val criteria = FungibleAssetQueryCriteria(issuer = listOf(BOC))
val results = vaultService.queryBy<FungibleAsset<*>>(criteria)

VaultQueryTests.kt

val allCashCriteria = FungibleStateQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
val allCashStates = vaultService.queryBy<Cash.State>(allCashCriteria).states

VaultQueryTests.kt

val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() }
val sumCriteria = VaultCustomQueryCriteria(sum)

val count = builder { CashSchemaV1.PersistentCashState::pennies.count() }
val countCriteria = VaultCustomQueryCriteria(count)

val max = builder { CashSchemaV1.PersistentCashState::pennies.max() }
val maxCriteria = VaultCustomQueryCriteria(max)

val min = builder { CashSchemaV1.PersistentCashState::pennies.min() }
val minCriteria = VaultCustomQueryCriteria(min)

val avg = builder { CashSchemaV1.PersistentCashState::pennies.avg() }
val avgCriteria = VaultCustomQueryCriteria(avg)

val results = vaultService.queryBy<FungibleAsset<*>>(sumCriteria
        .and(countCriteria)
        .and(maxCriteria)
        .and(minCriteria)
        .and(avgCriteria))

VaultQueryTests.kt

val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum(groupByColumns = listOf(CashSchemaV1.PersistentCashState::currency)) }
val sumCriteria = VaultCustomQueryCriteria(sum)

val max = builder { CashSchemaV1.PersistentCashState::pennies.max(groupByColumns = listOf(CashSchemaV1.PersistentCashState::currency)) }
val maxCriteria = VaultCustomQueryCriteria(max)

val min = builder { CashSchemaV1.PersistentCashState::pennies.min(groupByColumns = listOf(CashSchemaV1.PersistentCashState::currency)) }
val minCriteria = VaultCustomQueryCriteria(min)

val avg = builder { CashSchemaV1.PersistentCashState::pennies.avg(groupByColumns = listOf(CashSchemaV1.PersistentCashState::currency)) }
val avgCriteria = VaultCustomQueryCriteria(avg)

val results = vaultService.queryBy<FungibleAsset<*>>(sumCriteria
        .and(maxCriteria)
        .and(minCriteria)
        .and(avgCriteria))

VaultQueryTests.kt

val sum = builder {
    CashSchemaV1.PersistentCashState::pennies.sum(groupByColumns = listOf(CashSchemaV1.PersistentCashState::issuerPartyHash,
            CashSchemaV1.PersistentCashState::currency),
            orderBy = Sort.Direction.DESC)
}

val results = vaultService.queryBy<FungibleAsset<*>>(VaultCustomQueryCriteria(sum))

VaultQueryTests.kt

Dynamic queries (also using VaultQueryCriteria) are an extension to the snapshot queries by returning an additional QueryResults return type in the form of an Observable<Vault.Update>. Refer to ReactiveX Observable for a detailed understanding and usage of this type.

        vaultService.trackBy<Cash.State>().updates     // UNCONSUMED default

VaultQueryTests.kt

val (snapshot, updates) = vaultService.trackBy<LinearState>()

VaultQueryTests.kt

val (snapshot, updates) = vaultService.trackBy<DealState>()

VaultQueryTests.kt

Some examples of how to write vault queries in Java are provided below.

Vault.Page<LinearState> results = vaultService.queryBy(LinearState.class);

VaultQueryJavaTests.java

VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.CONSUMED);
Vault.Page<Cash.State> results = vaultService.queryBy(Cash.State.class, criteria);

VaultQueryJavaTests.java

Vault.StateStatus status = Vault.StateStatus.CONSUMED;
@SuppressWarnings("unchecked")
Set<Class<LinearState>> contractStateTypes = new HashSet(singletonList(LinearState.class));

QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes);

List<UniqueIdentifier> linearIds = singletonList(ids.getSecond());
QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds, Vault.StateStatus.UNCONSUMED, null);
QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds);

QueryCriteria compositeCriteria1 = dealCriteriaAll.or(linearCriteriaAll);
QueryCriteria compositeCriteria2 = compositeCriteria1.and(vaultCriteria);

PageSpecification pageSpec = new PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE);
Sort.SortColumn sortByUid = new Sort.SortColumn(new SortAttribute.Standard(Sort.LinearStateAttribute.UUID), Sort.Direction.DESC);
Sort sorting = new Sort(ImmutableSet.of(sortByUid));
Vault.Page<LinearState> results = vaultService.queryBy(LinearState.class, compositeCriteria2, pageSpec, sorting);

VaultQueryJavaTests.java

The following code will also detect, using previousPageAnchor, if the vault has concurrently changed whilst querying and will try to get the latest up-to-date version.

retry:
while (true) {
    int pageNumber = DEFAULT_PAGE_NUM;
    List<StateAndRef<Cash.State>> states = new ArrayList<>();
    long totalResults;
    do {
        PageSpecification pageSpec = new PageSpecification(pageNumber, pageSize);
        Vault.Page<Cash.State> results = vaultService.queryBy(Cash.State.class, new VaultQueryCriteria(), pageSpec);
        if (!states.isEmpty() && !states.get(states.size() - 1).getRef().equals(results.getPreviousPageAnchor())) {
            // Start querying from the 1st page again if we find the vault has changed from underneath us.
            continue retry;
        }
        totalResults = results.getTotalStatesAvailable();
        states.addAll(results.getStates());
        pageNumber++;
    } while ((pageSize * (pageNumber - 1L) <= totalResults));
    return states;
}

VaultQueryJavaTests.java

FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class);

QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies));
QueryCriteria countCriteria = new VaultCustomQueryCriteria(Builder.count(pennies));
QueryCriteria maxCriteria = new VaultCustomQueryCriteria(Builder.max(pennies));
QueryCriteria minCriteria = new VaultCustomQueryCriteria(Builder.min(pennies));
QueryCriteria avgCriteria = new VaultCustomQueryCriteria(Builder.avg(pennies));

QueryCriteria criteria = sumCriteria.and(countCriteria).and(maxCriteria).and(minCriteria).and(avgCriteria);
Vault.Page<Cash.State> results = vaultService.queryBy(Cash.State.class, criteria);

VaultQueryJavaTests.java

FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class);
FieldInfo currency = getField("currency", CashSchemaV1.PersistentCashState.class);

QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies, singletonList(currency)));
QueryCriteria countCriteria = new VaultCustomQueryCriteria(Builder.count(pennies));
QueryCriteria maxCriteria = new VaultCustomQueryCriteria(Builder.max(pennies, singletonList(currency)));
QueryCriteria minCriteria = new VaultCustomQueryCriteria(Builder.min(pennies, singletonList(currency)));
QueryCriteria avgCriteria = new VaultCustomQueryCriteria(Builder.avg(pennies, singletonList(currency)));

QueryCriteria criteria = sumCriteria.and(countCriteria).and(maxCriteria).and(minCriteria).and(avgCriteria);
Vault.Page<Cash.State> results = vaultService.queryBy(Cash.State.class, criteria);

VaultQueryJavaTests.java

FieldInfo pennies = getField("pennies", CashSchemaV1.PersistentCashState.class);
FieldInfo currency = getField("currency", CashSchemaV1.PersistentCashState.class);
FieldInfo issuerPartyHash = getField("issuerPartyHash", CashSchemaV1.PersistentCashState.class);
QueryCriteria sumCriteria = new VaultCustomQueryCriteria(sum(pennies, asList(issuerPartyHash, currency), Sort.Direction.DESC));
Vault.Page<Cash.State> results = vaultService.queryBy(Cash.State.class, sumCriteria);

VaultQueryJavaTests.java

@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(singletonList(Cash.State.class));

VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, contractStateTypes);
DataFeed<Vault.Page<ContractState>, Vault.Update<ContractState>> results = vaultService.trackBy(ContractState.class, criteria);

Vault.Page<ContractState> snapshot = results.getSnapshot();

VaultQueryJavaTests.java

@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(asList(DealState.class, LinearState.class));
QueryCriteria vaultCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, contractStateTypes);

List<UniqueIdentifier> linearIds = singletonList(uid);
List<AbstractParty> dealParty = singletonList(MEGA_CORP.getParty());
QueryCriteria dealCriteria = new LinearStateQueryCriteria(dealParty, null, dealIds);
QueryCriteria linearCriteria = new LinearStateQueryCriteria(dealParty, linearIds, Vault.StateStatus.UNCONSUMED, null);
QueryCriteria dealOrLinearIdCriteria = dealCriteria.or(linearCriteria);
QueryCriteria compositeCriteria = dealOrLinearIdCriteria.and(vaultCriteria);

PageSpecification pageSpec = new PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE);
Sort.SortColumn sortByUid = new Sort.SortColumn(new SortAttribute.Standard(Sort.LinearStateAttribute.UUID), Sort.Direction.DESC);
Sort sorting = new Sort(ImmutableSet.of(sortByUid));
DataFeed<Vault.Page<ContractState>, Vault.Update<ContractState>> results = vaultService.trackBy(ContractState.class, compositeCriteria, pageSpec, sorting);

Vault.Page<ContractState> snapshot = results.getSnapshot();

VaultQueryJavaTests.java

If the results you were expecting do not match actual returned query results, R3 recommends that you add an entry to your log4j2.xml configuration file to enable display of executed SQL statements:

<Logger name="org.hibernate.SQL" level="debug" additivity="false">

    <AppenderRef ref="Console-Appender"/>

</Logger>
  • TrackBy updates do not take into account the full criteria specification due to different and more restrictive syntax in observables filtering (vs full SQL-92 JDBC filtering as used in snapshot views). Specifically, dynamic updates are filtered by contractStateType and stateType (UNCONSUMED, CONSUMED, ALL) only.
  • QueryBy and TrackBy snapshot views using pagination may return different result sets, as each paging request is a separate SQL query on the underlying database, and it is entirely conceivable that state modifications are taking place in between and/or in parallel to paging requests. When using pagination, always check the value of the totalStatesAvailable (from the Vault.Page result) and adjust further paging requests appropriately.

For advanced use cases that require sophisticated pagination, sorting, grouping, and aggregation functions, R3 recommends that the CorDapp developer utilise one of the many proven frameworks that ship with this capability out of the box, namely, implementations of JPQL (JPA Query Language) such as Hibernate for advanced SQL access, and Spring Data for advanced pagination and ordering constructs.

When creating new public keys via the KeyManagementService, it is possible to create an association between the newly created public key and an external ID. This, in effect, allows CorDapp developers to group state ownership/participation keys by an account ID.

The code snippet below show how keys can be associated with an external ID by using the exposed JPA functionality:

public AnonymousParty freshKeyForExternalId(UUID externalId, ServiceHub services) {
    // Create a fresh key pair and return the public key.
    AnonymousParty anonymousParty = freshKey();
    // Associate the fresh key to an external ID.
    services.withEntityManager(entityManager -> {
        PersistentKeyManagementService.PublicKeyHashToExternalId mapping = PersistentKeyManagementService.PublicKeyHashToExternalId(externalId, anonymousParty.owningKey);
        entityManager.persist(mapping);
        return null;
    });
    return anonymousParty;
}
fun freshKeyForExternalId(externalId: UUID, services: ServiceHub): AnonymousParty {
    // Create a fresh key pair and return the public key.
    val anonymousParty = freshKey()
    // Associate the fresh key to an external ID.
    services.withEntityManager {
        val mapping = PersistentKeyManagementService.PublicKeyHashToExternalId(externalId, anonymousParty.owningKey)
        persist(mapping)
    }
    return anonymousParty
}

As can be seen in the code snippet above, the PublicKeyHashToExternalId entity has been added to PersistentKeyManagementService, which allows you to associate your public keys with external IDs.

The logic here is that when these public keys are used to own or participate in a state object, it is trivial to then associate those states with a particular external ID. Behind the scenes, when states are persisted to the vault, the owning keys for each state are persisted to a PersistentParty table. The PersistentParty table can be joined with the PublicKeyHashToExternalId table to create a view which maps each state to one or more external IDs. The entity relationship diagram illustrates how this works.

When performing a vault query, it is now possible to query for states by external ID using the externalIds parameter in VaultQueryCriteria.

Was this page helpful?

Thanks for your feedback!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.

We're sorry this page wasn't helpful. Let us know how we can make it better!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Create an issue

Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.