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
toCriteria
functions 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
,_notLike
but this may change in a future release.
Functions and Operators¶
Name | Aliases | Examples |
---|---|---|
isNull |
fields.foo.isNull() |
|
notNull |
fields.foo.notNull()
|
|
equal | `==` |
fields.foo `==` bar fields.foo equal bar fields.foo.equal(bar)
|
notEqual | `!=` |
fields.foo `!=` bar fields.foo notEqual bar fields.foo.notEqual(bar)
|
lessThan | lt |
fields.foo lt bar fields.foo lessThan bar fields.foo.lessThan(bar)
|
lessThanOrEqual | lte |
fields.foo lte bar fields.foo lessThanOrEqual bar fields.foo.lessThanOrEqual(bar)
|
greaterThan | gt |
fields.foo gt bar fields.foo greaterThan bar fields.foo.greaterThan(bar)
|
greaterThanOrEqual | gte |
fields.foo gte bar fields.foo greaterThanOrEqual bar fields.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 bar fields.foo.like(bar)
|
|
notLike |
fields.foo notLike bar fields.foo.notLike(bar)
|
|
isIn | `in` |
fields.foo `in` bars fields.foo isIn bars fields.foo.isIn(bars)
|
notIn | `!in` |
fields.foo `!in` bars fields.foo notIn bars fields.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 |