mirror of
https://github.com/corda/corda.git
synced 2025-01-23 21:08:48 +00:00
Vault Query Sort by StateRef (or constituents: txId, index) (#990)
* Provide sorting by state reference (and individual constituents of: txId, index) * Fixed formatting. * Updated import following rebase from master. * Updated import following rebase from master.
This commit is contained in:
parent
f718acb939
commit
c81ef7eb93
@ -150,7 +150,13 @@ data class Sort(val columns: Collection<SortColumn>) {
|
||||
@CordaSerializable
|
||||
interface Attribute
|
||||
|
||||
enum class VaultStateAttribute(val columnName: String) : Attribute {
|
||||
enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute {
|
||||
STATE_REF("stateRef", null),
|
||||
STATE_REF_TXN_ID("stateRef", "txId"),
|
||||
STATE_REF_INDEX("stateRef", "index")
|
||||
}
|
||||
|
||||
enum class VaultStateAttribute(val attributeName: String) : Attribute {
|
||||
/** Vault States */
|
||||
NOTARY_NAME("notaryName"),
|
||||
CONTRACT_TYPE("contractStateClassName"),
|
||||
@ -160,14 +166,14 @@ data class Sort(val columns: Collection<SortColumn>) {
|
||||
LOCK_ID("lockId")
|
||||
}
|
||||
|
||||
enum class LinearStateAttribute(val columnName: String) : Attribute {
|
||||
enum class LinearStateAttribute(val attributeName: String) : Attribute {
|
||||
/** Vault Linear States */
|
||||
UUID("uuid"),
|
||||
EXTERNAL_ID("externalId"),
|
||||
DEAL_REFERENCE("dealReference")
|
||||
}
|
||||
|
||||
enum class FungibleStateAttribute(val columnName: String) : Attribute {
|
||||
enum class FungibleStateAttribute(val attributeName: String) : Attribute {
|
||||
/** Vault Fungible States */
|
||||
QUANTITY("quantity"),
|
||||
ISSUER_REF("issuerRef")
|
||||
|
@ -28,6 +28,9 @@ UNRELEASED
|
||||
|
||||
* ``random63BitValue()`` has moved to ``CryptoUtils``
|
||||
|
||||
* Added additional common Sort attributes (see ``Sort.CommandStateAttribute``) for use in Vault Query criteria
|
||||
to include STATE_REF, STATE_REF_TXN_ID, STATE_REF_INDEX
|
||||
|
||||
Milestone 13
|
||||
------------
|
||||
|
||||
|
@ -334,7 +334,7 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
val leftPredicates = parse(left)
|
||||
val rightPredicates = parse(right)
|
||||
|
||||
val andPredicate = criteriaBuilder.and(criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray()))
|
||||
val andPredicate = criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
|
||||
predicateSet.add(andPredicate)
|
||||
|
||||
return predicateSet
|
||||
@ -378,11 +378,11 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
var orderCriteria = mutableListOf<Order>()
|
||||
|
||||
sorting.columns.map { (sortAttribute, direction) ->
|
||||
val (entityStateClass, entityStateColumnName) =
|
||||
when(sortAttribute) {
|
||||
is SortAttribute.Standard -> parse(sortAttribute.attribute)
|
||||
is SortAttribute.Custom -> Pair(sortAttribute.entityStateClass, sortAttribute.entityStateColumnName)
|
||||
}
|
||||
val (entityStateClass, entityStateAttributeParent, entityStateAttributeChild) =
|
||||
when(sortAttribute) {
|
||||
is SortAttribute.Standard -> parse(sortAttribute.attribute)
|
||||
is SortAttribute.Custom -> Triple(sortAttribute.entityStateClass, sortAttribute.entityStateColumnName, null)
|
||||
}
|
||||
val sortEntityRoot =
|
||||
rootEntities.getOrElse(entityStateClass) {
|
||||
// scenario where sorting on attributes not parsed as criteria
|
||||
@ -394,10 +394,16 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
}
|
||||
when (direction) {
|
||||
Sort.Direction.ASC -> {
|
||||
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateColumnName)))
|
||||
if (entityStateAttributeChild != null)
|
||||
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateAttributeParent).get<String>(entityStateAttributeChild)))
|
||||
else
|
||||
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateAttributeParent)))
|
||||
}
|
||||
Sort.Direction.DESC ->
|
||||
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateColumnName)))
|
||||
if (entityStateAttributeChild != null)
|
||||
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateAttributeParent).get<String>(entityStateAttributeChild)))
|
||||
else
|
||||
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateAttributeParent)))
|
||||
}
|
||||
}
|
||||
if (orderCriteria.isNotEmpty()) {
|
||||
@ -406,20 +412,23 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
}
|
||||
}
|
||||
|
||||
private fun parse(sortAttribute: Sort.Attribute): Pair<Class<out PersistentState>, String> {
|
||||
val entityClassAndColumnName : Pair<Class<out PersistentState>, String> =
|
||||
when(sortAttribute) {
|
||||
is Sort.VaultStateAttribute -> {
|
||||
Pair(VaultSchemaV1.VaultStates::class.java, sortAttribute.columnName)
|
||||
private fun parse(sortAttribute: Sort.Attribute): Triple<Class<out PersistentState>, String, String?> {
|
||||
val entityClassAndColumnName : Triple<Class<out PersistentState>, String, String?> =
|
||||
when(sortAttribute) {
|
||||
is Sort.CommonStateAttribute -> {
|
||||
Triple(VaultSchemaV1.VaultStates::class.java, sortAttribute.attributeParent, sortAttribute.attributeChild)
|
||||
}
|
||||
is Sort.VaultStateAttribute -> {
|
||||
Triple(VaultSchemaV1.VaultStates::class.java, sortAttribute.attributeName, null)
|
||||
}
|
||||
is Sort.LinearStateAttribute -> {
|
||||
Triple(VaultSchemaV1.VaultLinearStates::class.java, sortAttribute.attributeName, null)
|
||||
}
|
||||
is Sort.FungibleStateAttribute -> {
|
||||
Triple(VaultSchemaV1.VaultFungibleStates::class.java, sortAttribute.attributeName, null)
|
||||
}
|
||||
else -> throw VaultQueryException("Invalid sort attribute: $sortAttribute")
|
||||
}
|
||||
is Sort.LinearStateAttribute -> {
|
||||
Pair(VaultSchemaV1.VaultLinearStates::class.java, sortAttribute.columnName)
|
||||
}
|
||||
is Sort.FungibleStateAttribute -> {
|
||||
Pair(VaultSchemaV1.VaultFungibleStates::class.java, sortAttribute.columnName)
|
||||
}
|
||||
else -> throw VaultQueryException("Invalid sort attribute: $sortAttribute")
|
||||
}
|
||||
return entityClassAndColumnName
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ import static net.corda.node.utilities.DatabaseSupportKt.transaction;
|
||||
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
|
||||
import static net.corda.testing.CoreTestUtils.getMEGA_CORP_KEY;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
|
||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class VaultQueryJavaTests {
|
||||
@ -134,6 +135,31 @@ public class VaultQueryJavaTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unconsumedStatesForStateRefsSortedByTxnId() {
|
||||
transaction(database, tx -> {
|
||||
|
||||
VaultFiller.fillWithSomeTestLinearStates(services, 8);
|
||||
Vault<LinearState> issuedStates = VaultFiller.fillWithSomeTestLinearStates(services, 2);
|
||||
|
||||
Stream<StateRef> stateRefsStream = StreamSupport.stream(issuedStates.getStates().spliterator(), false).map(StateAndRef::getRef);
|
||||
List<StateRef> stateRefs = stateRefsStream.collect(Collectors.toList());
|
||||
|
||||
SortAttribute.Standard sortAttribute = new SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID);
|
||||
Sort sorting = new Sort(Arrays.asList(new Sort.SortColumn(sortAttribute, Sort.Direction.ASC)));
|
||||
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, null, stateRefs);
|
||||
Vault.Page<DummyLinearContract.State> results = vaultQuerySvc.queryBy(DummyLinearContract.State.class, criteria, sorting);
|
||||
|
||||
assertThat(results.getStates()).hasSize(2);
|
||||
|
||||
stateRefs.sort(Comparator.comparing(stateRef -> toHexString(stateRef.getTxhash().getBytes())));
|
||||
assertThat(results.getStates().get(0).getRef()).isEqualTo(stateRefs.get(0));
|
||||
assertThat(results.getStates().get(1).getRef()).isEqualTo(stateRefs.get(1));
|
||||
|
||||
return tx;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void consumedCashStates() {
|
||||
transaction(database, tx -> {
|
||||
|
@ -202,6 +202,67 @@ class HibernateConfigurationTest {
|
||||
queryResultsAsc.map { println(it.recordedTime) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with sorting by state ref desc and asc`() {
|
||||
// generate additional state ref indexes
|
||||
database.transaction {
|
||||
services.consumeCash(1.DOLLARS)
|
||||
services.consumeCash(2.DOLLARS)
|
||||
services.consumeCash(3.DOLLARS)
|
||||
services.consumeCash(4.DOLLARS)
|
||||
services.consumeCash(5.DOLLARS)
|
||||
}
|
||||
|
||||
// structure query
|
||||
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
|
||||
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
||||
|
||||
val sortByStateRef = vaultStates.get<PersistentStateRef>("stateRef")
|
||||
|
||||
// order by DESC
|
||||
criteriaQuery.orderBy(criteriaBuilder.desc(sortByStateRef))
|
||||
val queryResults = entityManager.createQuery(criteriaQuery).resultList
|
||||
println("DESC by stateRef")
|
||||
queryResults.map { println(it.stateRef) }
|
||||
|
||||
// order by ASC
|
||||
criteriaQuery.orderBy(criteriaBuilder.asc(sortByStateRef))
|
||||
val queryResultsAsc = entityManager.createQuery(criteriaQuery).resultList
|
||||
println("ASC by stateRef")
|
||||
queryResultsAsc.map { println(it.stateRef) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with sorting by state ref index and txId desc and asc`() {
|
||||
// generate additional state ref indexes
|
||||
database.transaction {
|
||||
services.consumeCash(1.DOLLARS)
|
||||
services.consumeCash(2.DOLLARS)
|
||||
services.consumeCash(3.DOLLARS)
|
||||
services.consumeCash(4.DOLLARS)
|
||||
services.consumeCash(5.DOLLARS)
|
||||
}
|
||||
|
||||
// structure query
|
||||
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
|
||||
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
||||
|
||||
val sortByIndex = vaultStates.get<PersistentStateRef>("stateRef").get<String>("index")
|
||||
val sortByTxId = vaultStates.get<PersistentStateRef>("stateRef").get<String>("txId")
|
||||
|
||||
// order by DESC
|
||||
criteriaQuery.orderBy(criteriaBuilder.desc(sortByIndex), criteriaBuilder.desc(sortByTxId))
|
||||
val queryResults = entityManager.createQuery(criteriaQuery).resultList
|
||||
println("DESC by index txId")
|
||||
queryResults.map { println(it.stateRef) }
|
||||
|
||||
// order by ASC
|
||||
criteriaQuery.orderBy(criteriaBuilder.asc(sortByIndex), criteriaBuilder.asc(sortByTxId))
|
||||
val queryResultsAsc = entityManager.createQuery(criteriaQuery).resultList
|
||||
println("ASC by index txId")
|
||||
queryResultsAsc.map { println(it.stateRef) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with pagination`() {
|
||||
// add 100 additional cash entries
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.schemas.testing.DummyLinearStateSchemaV1
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.vault.schemas.jpa.VaultSchemaV1
|
||||
@ -220,21 +221,80 @@ class VaultQueryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unconsumed states for state refs`() {
|
||||
fun `unconsumed cash states sorted by state ref`() {
|
||||
database.transaction {
|
||||
|
||||
var stateRefs : MutableList<StateRef> = mutableListOf()
|
||||
|
||||
val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
val issuedStateRefs = issuedStates.states.map { it.ref }.toList()
|
||||
stateRefs.addAll(issuedStateRefs)
|
||||
|
||||
val spentStates = services.consumeCash(25.DOLLARS)
|
||||
var spentStateRefs = spentStates.states.map { it.ref }.toList()
|
||||
stateRefs.addAll(spentStateRefs)
|
||||
|
||||
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
|
||||
val criteria = VaultQueryCriteria()
|
||||
val results = vaultQuerySvc.queryBy<Cash.State>(criteria, Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))))
|
||||
|
||||
// default StateRef sort is by index then txnId:
|
||||
// order by
|
||||
// vaultschem1_.output_index,
|
||||
// vaultschem1_.transaction_id asc
|
||||
assertThat(results.states).hasSize(8) // -3 CONSUMED + 1 NEW UNCONSUMED (change)
|
||||
|
||||
val sortedStateRefs = stateRefs.sortedBy { it.index }
|
||||
|
||||
assertThat(results.states.first().ref.index).isEqualTo(sortedStateRefs.first().index) // 0
|
||||
assertThat(results.states.last().ref.index).isEqualTo(sortedStateRefs.last().index) // 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unconsumed cash states sorted by state ref txnId and index`() {
|
||||
database.transaction {
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
services.consumeCash(10.DOLLARS)
|
||||
services.consumeCash(10.DOLLARS)
|
||||
|
||||
val sortAttributeTxnId = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
|
||||
val sortAttributeIndex = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_INDEX)
|
||||
val sortBy = Sort(setOf(Sort.SortColumn(sortAttributeTxnId, Sort.Direction.ASC),
|
||||
Sort.SortColumn(sortAttributeIndex, Sort.Direction.ASC)))
|
||||
val criteria = VaultQueryCriteria()
|
||||
val results = vaultQuerySvc.queryBy<Cash.State>(criteria, sortBy)
|
||||
|
||||
results.statesMetadata.forEach {
|
||||
println(" ${it.ref}")
|
||||
}
|
||||
|
||||
// explicit sort order asc by txnId and then index:
|
||||
// order by
|
||||
// vaultschem1_.transaction_id asc,
|
||||
// vaultschem1_.output_index asc
|
||||
assertThat(results.states).hasSize(9) // -2 CONSUMED + 1 NEW UNCONSUMED (change)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unconsumed states for state refs`() {
|
||||
database.transaction {
|
||||
services.fillWithSomeTestLinearStates(8)
|
||||
val issuedStates = services.fillWithSomeTestLinearStates(2)
|
||||
val stateRefs = issuedStates.states.map { it.ref }.toList()
|
||||
|
||||
// DOCSTART VaultQueryExample2
|
||||
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
|
||||
val criteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
|
||||
val results = vaultQuerySvc.queryBy<DummyLinearContract.State>(criteria)
|
||||
val results = vaultQuerySvc.queryBy<DummyLinearContract.State>(criteria, Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))))
|
||||
// DOCEND VaultQueryExample2
|
||||
|
||||
assertThat(results.states).hasSize(2)
|
||||
assertThat(results.states.first().ref).isEqualTo(issuedStates.states.first().ref)
|
||||
assertThat(results.states.last().ref).isEqualTo(issuedStates.states.last().ref)
|
||||
|
||||
val sortedStateRefs = stateRefs.sortedBy { it.txhash.bytes.toHexString() }
|
||||
assertThat(results.states.first().ref).isEqualTo(sortedStateRefs.first())
|
||||
assertThat(results.states.last().ref).isEqualTo(sortedStateRefs.last())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ fun ServiceHub.evolveLinearStates(linearStates: List<StateAndRef<LinearState>>)
|
||||
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>) : StateAndRef<LinearState> = consumeAndProduce(linearState)
|
||||
|
||||
@JvmOverloads
|
||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE) {
|
||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE): Vault<Cash.State> {
|
||||
// A tx that spends our money.
|
||||
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
vaultService.generateSpend(this, amount, to)
|
||||
@ -231,4 +231,10 @@ fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE) {
|
||||
}.toSignedTransaction(checkSufficientSignatures = false)
|
||||
|
||||
recordTransactions(spendTX)
|
||||
|
||||
// Get all the StateRefs of all the generated transactions.
|
||||
val states = spendTX.tx.outputs.indices.map { i -> spendTX.tx.outRef<Cash.State>(i) }
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user