Support for case insensitive vault queries (#3853)

* Make the criteria builder functions case insensitive

Add IGNORE_CASE versions of the comparison operator enums
Add exactMatch argument to criteria builder functions where strings can be passed in and set its default value to true
Use JvmOverrides to provide the default true version to java without needing to specify a value manually
If exactMatch is true then the original enums will be used, if false the IGNORE_CASE enums will be used instead
HibernateQueryCriteriaParser.columnPredicateToPredicate now takes into account the IGNORE_CASE versions of the enums

* Tidy up QueryCriteriaUtils and HibernateQueryCriteriaParser

Split HibernateQueryCriteriaParser.columnPredicateToPredicate into smaller functions
Reduce duplicated code in QueryCriteriaUtils

* Tidy up QueryCriteriaUtils and HibernateQueryCriteriaParser

Split HibernateQueryCriteriaParser.columnPredicateToPredicate into smaller functions
Reduce duplicated code in QueryCriteriaUtils (missed some code here)

* update changelog and api-vault-query docs with new API functions

* reorder Operator enums so that the ignore case enums are at the end

In case anyone is depending on the order of the enums, to keep compatibility with existing CorDapps the enums should be added at the end to prevent ordinals from breaking.
This commit is contained in:
Dan Newton 2018-09-07 17:20:21 +08:00 committed by josecoll
parent c6400cf344
commit db6c7f38a5
5 changed files with 361 additions and 80 deletions

View File

@ -5,6 +5,10 @@ package net.corda.core.node.services.vault
import net.corda.core.DoNotImplement
import net.corda.core.internal.declaredField
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.services.vault.CollectionOperator.*
import net.corda.core.node.services.vault.ColumnPredicate.*
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
import net.corda.core.node.services.vault.LikenessOperator.*
import net.corda.core.schemas.PersistentState
import net.corda.core.serialization.CordaSerializable
import java.lang.reflect.Field
@ -24,7 +28,9 @@ enum class BinaryLogicalOperator : Operator {
enum class EqualityComparisonOperator : Operator {
EQUAL,
NOT_EQUAL
NOT_EQUAL,
EQUAL_IGNORE_CASE,
NOT_EQUAL_IGNORE_CASE
}
enum class BinaryComparisonOperator : Operator {
@ -41,12 +47,16 @@ enum class NullOperator : Operator {
enum class LikenessOperator : Operator {
LIKE,
NOT_LIKE
NOT_LIKE,
LIKE_IGNORE_CASE,
NOT_LIKE_IGNORE_CASE
}
enum class CollectionOperator : Operator {
IN,
NOT_IN
NOT_IN,
IN_IGNORE_CASE,
NOT_IN_IGNORE_CASE
}
@CordaSerializable
@ -251,27 +261,45 @@ object Builder {
fun <R : Comparable<R>> Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value)
fun <R : Comparable<R>> FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
fun <O, R> KProperty1<O, R?>.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
fun <O, R> KProperty1<O, R?>.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
@JvmOverloads
fun <O, R> KProperty1<O, R?>.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
@JvmOverloads
fun <O, R> KProperty1<O, R?>.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.notEqual(value, exactMatch))
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value)
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value)
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
fun <O, R : Comparable<R>> KProperty1<O, R?>.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
@JvmOverloads
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
@JvmOverloads
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
@JvmStatic
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R> Field.equal(value: R) = info().equal(value)
@JvmStatic
fun <R> FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
fun <R> Field.equal(value: R, exactMatch: Boolean = true) = info().equal(value, exactMatch)
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R> Field.notEqual(value: R) = info().notEqual(value)
@JvmOverloads
fun <R> FieldInfo.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
@JvmStatic
fun <R> FieldInfo.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R> Field.notEqual(value: R, exactMatch: Boolean = true) = info().notEqual(value, exactMatch)
@JvmStatic
@JvmOverloads
fun <R> FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
@ -304,44 +332,77 @@ object Builder {
fun <R : Comparable<R>> FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
@JvmStatic
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>) = info().`in`(collection)
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>, exactMatch: Boolean = true) = info().`in`(collection, exactMatch)
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>) = info().notIn(collection)
@JvmStatic
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
@JvmOverloads
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
@JvmStatic
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>, exactMatch: Boolean = true) = info().notIn(collection, exactMatch)
@JvmStatic
@JvmOverloads
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
@JvmOverloads
fun <R> equal(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) EQUAL else EQUAL_IGNORE_CASE, value)
@JvmOverloads
fun <R> notEqual(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) NOT_EQUAL else NOT_EQUAL_IGNORE_CASE, value)
fun <R> equal(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)
fun <R> notEqual(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)
fun <R : Comparable<R>> lessThan(value: R) = compare(BinaryComparisonOperator.LESS_THAN, value)
fun <R : Comparable<R>> lessThanOrEqual(value: R) = compare(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
fun <R : Comparable<R>> greaterThan(value: R) = compare(BinaryComparisonOperator.GREATER_THAN, value)
fun <R : Comparable<R>> greaterThanOrEqual(value: R) = compare(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
fun <R : Comparable<R>> between(from: R, to: R) = ColumnPredicate.Between(from, to)
fun <R : Comparable<R>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
fun <R : Comparable<R>> notIn(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)
fun like(string: String) = ColumnPredicate.Likeness(LikenessOperator.LIKE, string)
fun notLike(string: String) = ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)
@JvmOverloads
fun <R : Comparable<R>> `in`(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) IN else IN_IGNORE_CASE, collection)
@JvmOverloads
fun <R : Comparable<R>> notIn(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) NOT_IN else NOT_IN_IGNORE_CASE, collection)
@JvmOverloads
fun like(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) LIKE else LIKE_IGNORE_CASE, string)
@JvmOverloads
fun notLike(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) NOT_LIKE else NOT_LIKE_IGNORE_CASE, string)
fun <R> isNull() = ColumnPredicate.NullExpression<R>(NullOperator.IS_NULL)
fun <R> isNotNull() = ColumnPredicate.NullExpression<R>(NullOperator.NOT_NULL)
@JvmOverloads
fun <O> KProperty1<O, String?>.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
fun <O> KProperty1<O, String?>.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
@JvmStatic
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun Field.like(string: String) = info().like(string)
@JvmStatic
fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
fun Field.like(string: String, exactMatch: Boolean = true) = info().like(string, exactMatch)
fun <O> KProperty1<O, String?>.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
@JvmStatic
@JvmOverloads
fun FieldInfo.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
@JvmOverloads
fun <O> KProperty1<O, String?>.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
@JvmStatic
@JvmOverloads
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
fun Field.notLike(string: String) = info().notLike(string)
fun Field.notLike(string: String, exactMatch: Boolean = true) = info().notLike(string, exactMatch)
@JvmStatic
fun FieldInfo.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
@JvmOverloads
fun FieldInfo.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
fun <O, R> KProperty1<O, R?>.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL))
@JvmStatic

View File

@ -166,6 +166,44 @@ An example of a custom query in Java is illustrated here:
where an anonymous party does not resolve to an X500 name via the ``IdentityService``, no query results will ever be
produced. For performance reasons, queries do not use ``PublicKey`` as search criteria.
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 is illustrated here:
.. container:: codeset
.. sourcecode:: kotlin
val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, true)
.. note:: The ``Boolean`` input of ``true`` in this example could be removed since the function will default to ``true`` when not provided.
An example of a case insensitive custom query operator is illustrated here:
.. container:: codeset
.. sourcecode:: kotlin
val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, false)
An example of a case sensitive custom query operator in Java is illustrated here:
.. container:: codeset
.. sourcecode:: java
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 illustrated here:
.. container:: codeset
.. sourcecode:: java
FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class);
CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", false);
Pagination
----------
The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200

View File

@ -6,6 +6,9 @@ release, see :doc:`upgrade-notes`.
Unreleased
----------
* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default queries will be case sensitive.
* Getter added to ``CordaRPCOps`` for the node's network parameters.
* The RPC client library now checks at startup whether the server is of the client libraries major version or higher.

View File

@ -7,6 +7,13 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.BinaryComparisonOperator.*
import net.corda.core.node.services.vault.CollectionOperator.*
import net.corda.core.node.services.vault.ColumnPredicate.*
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
import net.corda.core.node.services.vault.LikenessOperator.*
import net.corda.core.node.services.vault.NullOperator.IS_NULL
import net.corda.core.node.services.vault.NullOperator.NOT_NULL
import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.PersistentStateRef
@ -54,54 +61,93 @@ abstract class AbstractQueryCriteriaParser<Q : GenericQueryCriteria<Q,P>, in P:
protected fun columnPredicateToPredicate(column: Path<out Any?>, columnPredicate: ColumnPredicate<*>): Predicate {
return when (columnPredicate) {
is ColumnPredicate.EqualityComparison -> {
val literal = columnPredicate.rightLiteral
when (columnPredicate.operator) {
EqualityComparisonOperator.EQUAL -> criteriaBuilder.equal(column, literal)
EqualityComparisonOperator.NOT_EQUAL -> criteriaBuilder.notEqual(column, literal)
}
}
is ColumnPredicate.BinaryComparison -> {
val literal: Comparable<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
when (columnPredicate.operator) {
BinaryComparisonOperator.GREATER_THAN -> criteriaBuilder.greaterThan(column, literal)
BinaryComparisonOperator.GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal)
BinaryComparisonOperator.LESS_THAN -> criteriaBuilder.lessThan(column, literal)
BinaryComparisonOperator.LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal)
}
}
is ColumnPredicate.Likeness -> {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
when (columnPredicate.operator) {
LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral)
LikenessOperator.NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral)
}
}
is ColumnPredicate.CollectionExpression -> {
when (columnPredicate.operator) {
CollectionOperator.IN -> column.`in`(columnPredicate.rightLiteral)
CollectionOperator.NOT_IN -> criteriaBuilder.not(column.`in`(columnPredicate.rightLiteral))
}
}
is ColumnPredicate.Between -> {
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightToLiteral)
criteriaBuilder.between(column, fromLiteral, toLiteral)
}
is ColumnPredicate.NullExpression -> {
when (columnPredicate.operator) {
NullOperator.IS_NULL -> criteriaBuilder.isNull(column)
NullOperator.NOT_NULL -> criteriaBuilder.isNotNull(column)
}
}
is EqualityComparison -> equalityComparisonToPredicate(column, columnPredicate)
is BinaryComparison -> binaryComparisonToPredicate(column, columnPredicate)
is Likeness -> likeComparisonToPredicate(column, columnPredicate)
is CollectionExpression -> collectionComparisonToPredicate(column, columnPredicate)
is Between -> betweenComparisonToPredicate(column, columnPredicate)
is NullExpression -> nullComparisonToPredicate(column, columnPredicate)
else -> throw VaultQueryException("Not expecting $columnPredicate")
}
}
private fun equalityComparisonToPredicate(column: Path<out Any?>, columnPredicate: EqualityComparison<*>): Predicate {
val literal = columnPredicate.rightLiteral
return if (literal is String) {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
when (columnPredicate.operator) {
EQUAL -> criteriaBuilder.equal(column, literal)
EQUAL_IGNORE_CASE -> criteriaBuilder.equal(criteriaBuilder.upper(column), literal.toUpperCase())
NOT_EQUAL -> criteriaBuilder.notEqual(column, literal)
NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(criteriaBuilder.upper(column), literal.toUpperCase())
}
} else {
when (columnPredicate.operator) {
EQUAL, EQUAL_IGNORE_CASE -> criteriaBuilder.equal(column, literal)
NOT_EQUAL, NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(column, literal)
}
}
}
private fun binaryComparisonToPredicate(column: Path<out Any?>, columnPredicate: BinaryComparison<*>): Predicate {
val literal: Comparable<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
return when (columnPredicate.operator) {
GREATER_THAN -> criteriaBuilder.greaterThan(column, literal)
GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal)
LESS_THAN -> criteriaBuilder.lessThan(column, literal)
LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal)
}
}
private fun likeComparisonToPredicate(column: Path<out Any?>, columnPredicate: Likeness): Predicate {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
return when (columnPredicate.operator) {
LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral)
LIKE_IGNORE_CASE -> criteriaBuilder.like(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase())
NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral)
NOT_LIKE_IGNORE_CASE -> criteriaBuilder.notLike(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase())
}
}
private fun collectionComparisonToPredicate(column: Path<out Any?>, columnPredicate: CollectionExpression<*>): Predicate {
val literal = columnPredicate.rightLiteral
return if (literal.any { it is String }) {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
@Suppress("UNCHECKED_CAST")
literal as Collection<String>
when (columnPredicate.operator) {
IN -> column.`in`(literal)
IN_IGNORE_CASE -> criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() })
NOT_IN -> criteriaBuilder.not(column.`in`(literal))
NOT_IN_IGNORE_CASE -> criteriaBuilder.not(criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() }))
}
} else {
when (columnPredicate.operator) {
IN, IN_IGNORE_CASE -> column.`in`(literal)
NOT_IN, NOT_IN_IGNORE_CASE -> criteriaBuilder.not(column.`in`(literal))
}
}
}
private fun betweenComparisonToPredicate(column: Path<out Any?>, columnPredicate: Between<*>): Predicate {
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightToLiteral)
return criteriaBuilder.between(column, fromLiteral, toLiteral)
}
private fun nullComparisonToPredicate(column: Path<out Any?>, columnPredicate: NullExpression<*>): Predicate {
return when (columnPredicate.operator) {
IS_NULL -> criteriaBuilder.isNull(column)
NOT_NULL -> criteriaBuilder.isNotNull(column)
}
}
}
class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: CriteriaBuilder,
@ -474,7 +520,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// state status
stateTypes = criteria.status
if (criteria.status != Vault.StateStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::stateStatus.name, EqualityComparisonOperator.EQUAL)
val predicateID = Pair(VaultSchemaV1.VaultStates::stateStatus.name, EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.status) {
@ -488,7 +534,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// state relevance.
if (criteria.isRelevant != Vault.RelevancyStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::isRelevant.name, EqualityComparisonOperator.EQUAL)
val predicateID = Pair(VaultSchemaV1.VaultStates::isRelevant.name, EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.isRelevant) {
@ -503,7 +549,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// contract state types
val contractStateTypes = deriveContractStateTypes(criteria.contractStateTypes)
if (contractStateTypes.isNotEmpty()) {
val predicateID = Pair(VaultSchemaV1.VaultStates::contractStateClassName.name, CollectionOperator.IN)
val predicateID = Pair(VaultSchemaV1.VaultStates::contractStateClassName.name, IN)
if (commonPredicates.containsKey(predicateID)) {
val existingTypes = (commonPredicates[predicateID]!!.expressions[0] as InPredicate<*>).values.map { (it as LiteralExpression).literal }.toSet()
if (existingTypes != contractStateTypes) {

View File

@ -800,6 +800,139 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
}
}
@Test
fun `logical operator case insensitive EQUAL`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal("gBp", false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `logical operator case insensitive EQUAL does not affect numbers`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.equal(10000, false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(3)
}
}
@Test
fun `logical operator case insensitive NOT_EQUAL does not return results containing the same characters as the case insensitive string`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notEqual("gBp", false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `logical operator case insensitive NOT_EQUAL does not affect numbers`() {
database.transaction {
listOf(USD, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER)
val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.notEqual(10000, false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `logical operator case insensitive IN`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val currencies = listOf("cHf", "gBp")
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.`in`(currencies, false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `logical operator case insensitive IN does not affect numbers`() {
database.transaction {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, USD), notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCash(AMOUNT(200, CHF), notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER)
val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.`in`(listOf(10000L, 20000L), false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `logical operator case insensitive NOT IN does not return results containing the same characters as the case insensitive strings`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val currencies = listOf("cHf", "gBp")
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notIn(currencies, false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `logical operator case insensitive NOT_IN does not affect numbers`() {
database.transaction {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, USD), notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCash(AMOUNT(200, CHF), notaryServices, 1, DUMMY_CASH_ISSUER)
vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER)
val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.notIn(listOf(10000L, 20000L), false) }
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `logical operator case insensitive LIKE`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.like("%bP", false) } // GPB
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `logical operator NOT LIKE does not return results containing the same characters as the case insensitive string`() {
database.transaction {
listOf(USD, GBP, CHF).forEach {
vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER)
}
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notLike("%bP", false) } // GPB
val criteria = VaultCustomQueryCriteria(logicalExpression)
val results = vaultService.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `aggregate functions without group clause`() {
database.transaction {