mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +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:
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user