Query DSL¶
Vaultaire uses an annotation processor at build-time to create a query DSL for your contract/persistent states.
Quick Example¶
Consider the following Contract/Persistent States:
/** Book ContractState */
data class BookState(
val publisher: Party,
val author: Party,
val title: String,
val published: Date = Date()
) : ContractState, QueryableState {
// ...
}
// Use Vaultaire's code generation!
@VaultaireStateUtils(name = "booksQuery", contractStateType = BookState::class)
@Entity
@Table(name = "books")
data class PersistentBookState(
@Column(name = "publisher")
var publisher: String = "",
@Column(name = "author")
var author: String = "",
@Column(name = "title")
var title: String = "",
@Column(name = "published")
var published: Date
) : PersistentState()
Before Vaultaire¶
Before Vaultaire, you probably had to create query criteria with something like:
val query = VaultQueryCriteria(
contractStateTypes = setOf(BookState::class.java),
status = Vault.StateStatus.ALL
)
// Check publisher?
if(checkPublisher) query = query.and(VaultCustomQueryCriteria(PersistentBookState::publisher.equal("Corda Books Ltd.")))
// Add more criteria
query.and(VaultCustomQueryCriteria(PersistentBookState::title.equal("A book on Corda"))
.or(VaultCustomQueryCriteria(PersistentBookState::author.notEqual("John Doe"))))
val sort = Sort(listOf(Sort.SortColumn(
SortAttribute.Custom(PersistentBookState::class.java, "published"), Sort.Direction.DESC)))
// Finally!
queryBy(query, sort)
A bit verbose so get’s difficult to read as the query becomes more complex. Let’s try to simplify things bellow.
With Vaultaire DSL¶
With Vaultaire’s @VaultaireStateUtils and the generated DSL this becomes:
// Use the generated DSL to create query criteria
val query = booksQuery {
status = Vault.StateStatus.ALL
and {
// Check publisher?
if(checkPublisher) fields.publisher `==` "Corda Books Ltd."
or {
fields.title `==` "A book on Corda"
fields.author `!=` "John Doe"
}
}
orderBy {
fields.title sort DESC
}
}
queryBy(query.toCriteria(), query.toSort())
Create a DSL¶
Project Module States¶
To create a query DSL for your state after installing Vaultaire,
annotate the corresponding PersistentState with @VaultaireStateUtils:
// Use Vaultaire's DSL generation!
@VaultaireStateUtils(
// If you omit the name, the DSL function will be named by appending "Query"
// to the decapitalized contract state name, e.g. "bookStateQuery"
name = "booksQuery",
contractStateType = BookState::class)
@Entity
@Table(name = "books")
data class PersistentBookState(
// state properties...
) : PersistentState()
Project Dependency States¶
To create a query DSL for a state from your project dependencies, annotate the
any class in your project using the special @VaultaireStateUtilsFor annotation,
providing the state’s ContractState and PersistentState:
@VaultaireStateUtilsMixin(name = "fungibleTokenConditions",
persistentStateType = PersistentFungibleToken::class,
contractStateType = FungibleToken::class)
class FungibleMixin
Query Settings¶
The generated DSL allows setting QueryCriteria.VaultQueryCriteria members. Here’s an example
using the defaults:
val query = booksQuery {
// settings
status = Vault.StateStatus.UNCONSUMED
stateRefs = null
notary = null
softLockingCondition = null
timeCondition = null
relevancyStatus = Vault.RelevancyStatus.ALL
constraintTypes = emptySet()
constraints = emptySet()
participants = null
// criteria and sorting...
}
Adding Criteria¶
Query riteria are defined within the and / or functions. Both functions can be nested and mixed
with criteria like:
val query = booksQuery {
// settings...
// criteria
or { // Match at least one
fields.foo1 `==` someValue
and { // Match all
fields.foo1.isNull()
fields.foo2 `==` someOtherValue
and {
// ...
}
or {
// ...
}
}
}
// sorting...
}
Adding Aggregates¶
Aggregates can be specified within the and / or functions:
val bookStateQuery = bookStateQuery {
// settings...
// criteria
and {
fields.title `like` "%Corda Foundation%"
fields.genre `==` BookContract.BookGenre.SCIENCE_FICTION
}
// aggregates
aggregate {
// add some aggregates
fields.externalId.count()
fields.id.count()
fields.editions.sum()
fields.price.min()
fields.price.avg()
fields.price.max()
}
}
Note: Corda paged queries can include either query results or “other” results based on the above aggregates. For that purpose, the
toCriteriafunctions accepts an optional boolean to ignore aggregates, thus allowing the reuse of the same query to obtain either paged state or aggregate results.
Accessing Fields¶
Fields can be accessed via the generated DSL’s fields object within and, or, or orderBy
functions using dot notation e.g. fields.foo.
You can also retrieve fields by name with e.g.
fields["foo"]orfields.get("foo")and use non typesafe functions like_equal,_notEqual,_like,_notLikebut this may change in a future release.
Functions and Operators¶
| Name | Aliases | Examples |
|---|---|---|
| isNull |
fields.foo.isNull() |
|
| notNull |
fields.foo.notNull()
|
|
| equal | `==` |
fields.foo `==` barfields.foo equal barfields.foo.equal(bar)
|
| notEqual | `!=` |
fields.foo `!=` barfields.foo notEqual barfields.foo.notEqual(bar)
|
| lessThan | lt |
fields.foo lt barfields.foo lessThan barfields.foo.lessThan(bar)
|
| lessThanOrEqual | lte |
fields.foo lte barfields.foo lessThanOrEqual barfields.foo.lessThanOrEqual(bar)
|
| greaterThan | gt |
fields.foo gt barfields.foo greaterThan barfields.foo.greaterThan(bar)
|
| greaterThanOrEqual | gte |
fields.foo gte barfields.foo greaterThanOrEqual barfields.foo.greaterThanOrEqual(bar)
|
| between | btw |
fields.foo btw Pair(bar1, bar2)fields.foo between Pair(bar1, bar2)fields.foo.between(bar1, bar2)
|
| like |
fields.foo like barfields.foo.like(bar)
|
|
| notLike |
fields.foo notLike barfields.foo.notLike(bar)
|
|
| isIn | `in` |
fields.foo `in` barsfields.foo isIn barsfields.foo.isIn(bars)
|
| notIn | `!in` |
fields.foo `!in` barsfields.foo notIn barsfields.foo.notIn(bars)
|
Aggregate Functions¶
| Name | Examples |
|---|---|
| avg |
fields.foo.avg()fields.foo.avg(groupByColumns)fields.foo.avg(groupByColumns, sortDirection) |
| count |
fields.foo.count() |
| max |
fields.foo.max()fields.foo.max(groupByColumns)fields.foo.max(groupByColumns, sortDirection) |
| min |
fields.foo.min()fields.foo.min(groupByColumns)fields.foo.min(groupByColumns, sortDirection) |
| sum |
fields.foo.sum()fields.foo.sum(groupByColumns)fields.foo.sum(groupByColumns, sortDirection) |
Sorting¶
Sorting is defined using the orderBy function. Both custom fields and
standard attributes are supported, while aliases for standard attributes
are provided for convenience:
val criteria = bookConditions {
// settings and criteria...
// sorting
orderBy {
// Sort by standard attribute alias, same as
// Sort.VaultStateAttribute.RECORDED_TIME sort ASC
recordedTime sort ASC
// Sort by custom field
fields.title sort DESC
}
}
The following standard attribute aliases are provided:
| Alias | Standard Attribute |
|---|---|
| stateRef | Sort.CommonStateAttribute.STATE_REF |
| stateRefTxnId | Sort.CommonStateAttribute.STATE_REF_TXN_ID |
| stateRefIndex | Sort.CommonStateAttribute.STATE_REF_INDEX |
| notaryName | Sort.VaultStateAttribute.NOTARY_NAME |
| contractStateType | Sort.VaultStateAttribute.CONTRACT_STATE_TYPE |
| stateStatus | Sort.VaultStateAttribute.STATE_STATUS |
| recordedTime | Sort.VaultStateAttribute.RECORDED_TIME |
| consumedTime | Sort.VaultStateAttribute.CONSUMED_TIME |
| lockId | Sort.VaultStateAttribute.LOCK_ID |
| constraintType | Sort.VaultStateAttribute.CONSTRAINT_TYPE |
| uuid | Sort.LinearStateAttribute.UUID |
| externalId | Sort.LinearStateAttribute.EXTERNAL_ID |
| quantity | Sort.FungibleStateAttribute.QUANTITY |
| issuerRef | Sort.FungibleStateAttribute.ISSUER_REF |