diff --git a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java index 73c3793ec3..1fb357ffa0 100644 --- a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java +++ b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapper.java @@ -159,9 +159,9 @@ private AttributeFilter mapJsonArgument(String attribute, String key, Object case LIKE: return jsonFilter(like(sb.toString(), value.toString())); case CONTAINS_ALL: - return filterValueList(value, val -> containsAll(sb.toString(), val)); + return jsonFilter(filterValueList(value, val -> containsAll(sb.toString(), val))); case CONTAINS_ANY: - return filterValueList(value, val -> containsAny(sb.toString(), val)); + return jsonFilter(filterValueList(value, val -> containsAny(sb.toString(), val))); case EQUAL: default: return jsonFilter(equalTo(sb.toString(), value)); diff --git a/data-index/data-index-graphql/src/test/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapperTest.java b/data-index/data-index-graphql/src/test/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapperTest.java index 1d9e3fdbee..27f053f9a3 100644 --- a/data-index/data-index-graphql/src/test/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapperTest.java +++ b/data-index/data-index-graphql/src/test/java/org/kie/kogito/index/graphql/query/GraphQLQueryMapperTest.java @@ -86,6 +86,18 @@ void testJsonMapperContains() { jsonFilter(contains("variables.workflowdata.number", 1))); } + @Test + void testJsonMapperContainsAny() { + assertThat(mapper.mapJsonArgument("variables").apply(Map.of("workflowdata", Map.of("number", Map.of("containsAny", List.of(1, 2, 3)))))).containsExactly( + jsonFilter(containsAny("variables.workflowdata.number", List.of(1, 2, 3)))); + } + + @Test + void testJsonMapperContainsAll() { + assertThat(mapper.mapJsonArgument("variables").apply(Map.of("workflowdata", Map.of("number", Map.of("containsAll", List.of(1, 2, 3)))))).containsExactly( + jsonFilter(containsAll("variables.workflowdata.number", List.of(1, 2, 3)))); + } + @Test void testJsonMapperLike() { assertThat(mapper.mapJsonArgument("variables").apply(Map.of("workflowdata", Map.of("number", Map.of("like", "kk"))))).containsExactly( diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/ContainsSQLFunction.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/ContainsSQLFunction.java index 63d37cd4e0..ef0563b936 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/ContainsSQLFunction.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/ContainsSQLFunction.java @@ -18,6 +18,7 @@ */ package org.kie.kogito.index.postgresql; +import java.util.Iterator; import java.util.List; import org.hibernate.dialect.function.StandardSQLFunction; @@ -30,12 +31,21 @@ public class ContainsSQLFunction extends StandardSQLFunction { - static final String NAME = "contains"; + static final String CONTAINS_NAME = "contains"; + static final String CONTAINS_ALL_NAME = "containsAll"; + static final String CONTAINS_ANY_NAME = "containsAny"; + + static final String CONTAINS_SEQ = "??"; + static final String CONTAINS_ALL_SEQ = "??&"; + static final String CONTAINS_ANY_SEQ = "??|"; + + private final String operator; private static final BasicTypeReference RETURN_TYPE = new BasicTypeReference<>("boolean", Boolean.class, SqlTypes.BOOLEAN); - public ContainsSQLFunction() { - super(NAME, RETURN_TYPE); + ContainsSQLFunction(String name, String operator) { + super(name, RETURN_TYPE); + this.operator = operator; } @Override @@ -44,9 +54,23 @@ public void render( List args, ReturnableType returnType, SqlAstTranslator translator) { - args.get(0).accept(translator); - sqlAppender.append(" ?? "); - args.get(1).accept(translator); + int size = args.size(); + if (size < 2) { + throw new IllegalArgumentException("Function " + getName() + " requires at least two arguments"); + } + Iterator iter = args.iterator(); + iter.next().accept(translator); + sqlAppender.append(' '); + sqlAppender.append(operator); + sqlAppender.append(' '); + if (size == 2) { + iter.next().accept(translator); + } else { + sqlAppender.append("array["); + do { + iter.next().accept(translator); + sqlAppender.append(iter.hasNext() ? ',' : ']'); + } while (iter.hasNext()); + } } - } diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/CustomFunctionsContributor.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/CustomFunctionsContributor.java index b233f8c752..cb8a68c9e1 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/CustomFunctionsContributor.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/CustomFunctionsContributor.java @@ -20,12 +20,17 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; + +import static org.kie.kogito.index.postgresql.ContainsSQLFunction.*; public class CustomFunctionsContributor implements FunctionContributor { @Override public void contributeFunctions(FunctionContributions functionContributions) { - functionContributions.getFunctionRegistry() - .register(ContainsSQLFunction.NAME, new ContainsSQLFunction()); + SqmFunctionRegistry registry = functionContributions.getFunctionRegistry(); + registry.register(CONTAINS_NAME, new ContainsSQLFunction(CONTAINS_NAME, CONTAINS_SEQ)); + registry.register(CONTAINS_ANY_NAME, new ContainsSQLFunction(CONTAINS_ANY_NAME, CONTAINS_ANY_SEQ)); + registry.register(CONTAINS_ALL_NAME, new ContainsSQLFunction(CONTAINS_ALL_NAME, CONTAINS_ALL_SEQ)); } -} \ No newline at end of file +} diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java index 4ea3fabfe9..3cb1f347d8 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.kie.kogito.persistence.api.query.AttributeFilter; @@ -74,11 +75,22 @@ public static Predicate buildPredicate(AttributeFilter filter, Root root, return buildPathExpression(builder, root, filter.getAttribute(), isString).in(values.stream().map(o -> buildObjectExpression(builder, o, isString)).collect(Collectors.toList())); case CONTAINS: return builder.isTrue( - builder.function("contains", Boolean.class, buildPathExpression(builder, root, filter.getAttribute(), false), builder.literal(filter.getValue()))); + builder.function(ContainsSQLFunction.CONTAINS_NAME, Boolean.class, buildPathExpression(builder, root, filter.getAttribute(), false), builder.literal(filter.getValue()))); + case CONTAINS_ANY: + return containsPredicate(filter, root, builder, ContainsSQLFunction.CONTAINS_ANY_NAME); + case CONTAINS_ALL: + return containsPredicate(filter, root, builder, ContainsSQLFunction.CONTAINS_ALL_NAME); } throw new UnsupportedOperationException("Filter " + filter + " is not supported"); } + private static Predicate containsPredicate(AttributeFilter filter, Root root, CriteriaBuilder builder, String name) { + return builder.isTrue( + builder.function(name, Boolean.class, + Stream.concat(Stream.of(buildPathExpression(builder, root, filter.getAttribute(), false)), ((List) filter.getValue()).stream().map(o -> builder.literal(o))) + .toArray(Expression[]::new))); + } + private static Expression buildObjectExpression(CriteriaBuilder builder, Object value, boolean isString) { return isString ? builder.literal(value) : builder.function("to_jsonb", Object.class, builder.literal(value)); } diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java index a929f2b689..9eb7611e74 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java @@ -115,5 +115,13 @@ void testProcessInstanceVariables() { processInstanceId); queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(contains("variables.traveller.aliases", "TheDummyThing"))), null, null, null, processInstanceId); + queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(containsAny("variables.traveller.aliases", List.of("TheRealThing", "TheDummyThing")))), null, null, null, + processInstanceId); + queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(containsAny("variables.traveller.aliases", List.of("TheRedPandaThing", "TheDummyThing")))), null, null, null, + processInstanceId); + queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(containsAll("variables.traveller.aliases", List.of("Super", "Astonishing", "TheRealThing")))), null, null, null, + processInstanceId); + queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(containsAll("variables.traveller.aliases", List.of("Super", "TheDummyThing")))), null, null, null, + processInstanceId); } } diff --git a/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/QueryFilterFactory.java b/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/QueryFilterFactory.java index ab02f57810..1017556895 100644 --- a/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/QueryFilterFactory.java +++ b/persistence-commons/persistence-commons-api/src/main/java/org/kie/kogito/persistence/api/query/QueryFilterFactory.java @@ -42,11 +42,11 @@ public static AttributeFilter> in(String attribute, List values) return new AttributeFilter<>(attribute, FilterCondition.IN, values); } - public static AttributeFilter> containsAny(String attribute, List values) { + public static AttributeFilter> containsAny(String attribute, List values) { return new AttributeFilter<>(attribute, FilterCondition.CONTAINS_ANY, values); } - public static AttributeFilter> containsAll(String attribute, List values) { + public static AttributeFilter> containsAll(String attribute, List values) { return new AttributeFilter<>(attribute, FilterCondition.CONTAINS_ALL, values); }