diff --git a/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/CriterionOperatorRegistryImpl.java b/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/CriterionOperatorRegistryImpl.java index 386ac3bb323..7487f29139c 100644 --- a/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/CriterionOperatorRegistryImpl.java +++ b/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/CriterionOperatorRegistryImpl.java @@ -42,6 +42,7 @@ public static CriterionOperatorRegistry ofDefaults() { registry.registerOperatorPredicate(EQUAL, new EqualOperatorPredicate()); registry.registerOperatorPredicate(IN, new InOperatorPredicate()); registry.registerOperatorPredicate(LIKE, new LikeOperatorPredicate()); + registry.registerOperatorPredicate(ILIKE, new IlikeOperatorPredicate()); registry.registerOperatorPredicate(CONTAINS, new ContainsOperatorPredicate()); return registry; } diff --git a/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/IlikeOperatorPredicate.java b/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/IlikeOperatorPredicate.java new file mode 100644 index 00000000000..0008b1d9734 --- /dev/null +++ b/core/common/lib/query-lib/src/main/java/org/eclipse/edc/query/IlikeOperatorPredicate.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.query; + +import org.eclipse.edc.spi.query.OperatorPredicate; + +import java.util.regex.Pattern; + +public class IlikeOperatorPredicate implements OperatorPredicate { + @Override + public boolean test(Object property, Object operandRight) { + if (operandRight instanceof String stringOperand) { + var regexPattern = Pattern.quote(stringOperand.toLowerCase()) + .replace("%", "\\E.*\\Q") + .replace("_", "\\E.\\Q"); + + return Pattern.compile("^" + regexPattern + "$") + .matcher(property.toString().toLowerCase()) + .matches(); + } + + return false; + } +} diff --git a/core/common/lib/query-lib/src/test/java/org/eclipse/edc/query/IlikeOperatorPredicateTest.java b/core/common/lib/query-lib/src/test/java/org/eclipse/edc/query/IlikeOperatorPredicateTest.java new file mode 100644 index 00000000000..be400b0485c --- /dev/null +++ b/core/common/lib/query-lib/src/test/java/org/eclipse/edc/query/IlikeOperatorPredicateTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.query; + +import org.eclipse.edc.spi.query.OperatorPredicate; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class IlikeOperatorPredicateTest { + + private final OperatorPredicate predicate = new IlikeOperatorPredicate(); + + @Test + void shouldHandlePercentAtTheStartOfTheString() { + assertThat(predicate.test("THIS IS A TEST", "%test")).isTrue(); + assertThat(predicate.test("NOT TESTED", "%test")).isFalse(); + } + + @Test + void shouldHandlePercentAtTheEndOfTheString() { + assertThat(predicate.test("TEST VALID", "test%")).isTrue(); + assertThat(predicate.test(" TEST INVALID", "test%")).isFalse(); + } + + @Test + void shouldHandlePercentAtTheStartAndEndOfTheString() { + assertThat(predicate.test("THIS TEST VALID", "%test%")).isTrue(); + assertThat(predicate.test("TEST ALSO VALID", "%test%")).isTrue(); + assertThat(predicate.test("VALID IS THE TEST", "%test%")).isTrue(); + assertThat(predicate.test("INVALID", "%test%")).isFalse(); + } + +} diff --git a/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslator.java b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslator.java index e1f58401990..6c24d963694 100644 --- a/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslator.java +++ b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslator.java @@ -18,6 +18,7 @@ import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.CONTAINS; import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.EQUAL; +import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.ILIKE; import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.IN; import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.LIKE; @@ -31,6 +32,7 @@ public SqlOperator translate(String operator) { return switch (operator) { case EQUAL -> new SqlOperator("=", Object.class); case LIKE -> new SqlOperator("like", String.class); + case ILIKE -> new SqlOperator("ilike", String.class); case IN -> new SqlOperator("in", Collection.class); case CONTAINS -> new SqlOperator("??", Object.class); default -> null; diff --git a/extensions/common/sql/sql-core/src/test/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslatorTest.java b/extensions/common/sql/sql-core/src/test/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslatorTest.java index 394326ddd9e..510eb13ac15 100644 --- a/extensions/common/sql/sql-core/src/test/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslatorTest.java +++ b/extensions/common/sql/sql-core/src/test/java/org/eclipse/edc/sql/translation/PostgresqlOperatorTranslatorTest.java @@ -40,6 +40,14 @@ void shouldTranslate_like() { assertThat(operator.rightOperandClass()).isEqualTo(String.class); } + @Test + void shouldTranslate_ilike() { + var operator = translator.translate("ilike"); + + assertThat(operator.representation()).isEqualTo("ilike"); + assertThat(operator.rightOperandClass()).isEqualTo(String.class); + } + @Test void shouldTranslate_in() { var operator = translator.translate("in"); diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/query/CriterionOperatorRegistry.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/query/CriterionOperatorRegistry.java index 6a10faaba8a..4fcaa52c575 100644 --- a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/query/CriterionOperatorRegistry.java +++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/query/CriterionOperatorRegistry.java @@ -24,6 +24,7 @@ public interface CriterionOperatorRegistry { String EQUAL = "="; String IN = "in"; String LIKE = "like"; + String ILIKE = "ilike"; String CONTAINS = "contains"; /** diff --git a/spi/control-plane/asset-spi/src/testFixtures/java/org/eclipse/edc/connector/controlplane/asset/spi/testfixtures/AssetIndexTestBase.java b/spi/control-plane/asset-spi/src/testFixtures/java/org/eclipse/edc/connector/controlplane/asset/spi/testfixtures/AssetIndexTestBase.java index 6c09dbf2295..31bd1207a4d 100644 --- a/spi/control-plane/asset-spi/src/testFixtures/java/org/eclipse/edc/connector/controlplane/asset/spi/testfixtures/AssetIndexTestBase.java +++ b/spi/control-plane/asset-spi/src/testFixtures/java/org/eclipse/edc/connector/controlplane/asset/spi/testfixtures/AssetIndexTestBase.java @@ -211,10 +211,7 @@ void shouldReturnAllTheAssets_whenQuerySpecIsEmpty() { @Test @DisplayName("Query assets with query spec") void limit() { - for (var i = 1; i <= 10; i++) { - var asset = getAsset("id" + i); - getAssetIndex().create(asset); - } + range(1, 10).mapToObj(it -> getAsset("id" + it)).forEach(asset -> getAssetIndex().create(asset)); var querySpec = QuerySpec.Builder.newInstance().limit(3).offset(2).build(); var assetsFound = getAssetIndex().queryAssets(querySpec); @@ -405,8 +402,7 @@ void withPrivateSorting() { } @Test - @DisplayName("Query assets using the LIKE operator") - void like() { + void shouldFilter_whenLikeOperator() { var asset1 = getAsset("id1"); getAssetIndex().create(asset1); var asset2 = getAsset("id2"); @@ -418,6 +414,17 @@ void like() { assertThat(assetsFound).isNotNull().hasSize(2); } + @Test + void shouldFilter_whenIlikeOperator() { + getAssetIndex().create(getAsset("ID1")); + getAssetIndex().create(getAsset("ID2")); + var criterion = new Criterion(Asset.PROPERTY_ID, "ilike", "id%"); + + var assetsFound = getAssetIndex().queryAssets(filter(criterion)); + + assertThat(assetsFound).isNotNull().hasSize(2); + } + @Test @DisplayName("Query assets using the LIKE operator on a json value") void likeJson() throws JsonProcessingException {