From 3fd98c0d2311ce98cb0d97a5038243956fda3e87 Mon Sep 17 00:00:00 2001 From: Andrej Petras Date: Mon, 15 Apr 2024 14:27:50 +0200 Subject: [PATCH] feat: remove character replacement for equal statement (#154) * feat: remove character replacement for equal statement * feat: remove character replacement for equal statement --- .../pages/includes/attributes.adoc | 2 +- .../quarkus/jpa/utils/QueryCriteriaUtil.java | 32 ++++++---- .../jpa/utils/QueryCriteriaUtilTest.java | 3 +- .../org/tkit/quarkus/jpa/test/UserDAO.java | 15 +++++ .../src/main/resources/application.properties | 3 +- .../quarkus/jpa/test/UserDAOLikeTest.java | 64 +++++++++++++++++++ 6 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 extensions/jpa/tests/src/test/java/org/tkit/quarkus/jpa/test/UserDAOLikeTest.java diff --git a/docs/modules/tkit-quarkus/pages/includes/attributes.adoc b/docs/modules/tkit-quarkus/pages/includes/attributes.adoc index 647e91da..083d6f3e 100644 --- a/docs/modules/tkit-quarkus/pages/includes/attributes.adoc +++ b/docs/modules/tkit-quarkus/pages/includes/attributes.adoc @@ -1,4 +1,4 @@ -:project-version: 2.19.0 +:project-version: 2.20.0 :quarkus-version: 3.9.3 :examples-dir: ./../examples/ \ No newline at end of file diff --git a/extensions/jpa/runtime/src/main/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtil.java b/extensions/jpa/runtime/src/main/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtil.java index efa516cf..028c768d 100644 --- a/extensions/jpa/runtime/src/main/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtil.java +++ b/extensions/jpa/runtime/src/main/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtil.java @@ -38,6 +38,8 @@ public class QueryCriteriaUtil { "*", "%", "?", "_"); + private static final char DEFAULT_ESCAPE_CHAR = '\\'; + /** * The default constructor. */ @@ -261,7 +263,7 @@ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuil public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuilder, Expression column, String searchString, final boolean caseInsensitive) { return createSearchStringPredicate(criteriaBuilder, column, searchString, caseInsensitive, - QueryCriteriaUtil::defaultReplaceFunction, DEFAULT_LIKE_MAPPING_CHARACTERS); + QueryCriteriaUtil::defaultReplaceFunction, DEFAULT_LIKE_MAPPING_CHARACTERS, DEFAULT_ESCAPE_CHAR); } /** @@ -273,22 +275,18 @@ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuil * @param caseInsensitive - true in case of insensitive search (db column and search string are given to lower case) * @param replaceFunction - replace special character function for characters in the searchString * @param likeMapping - map of like query mapping characters ['*','%', ...] + * @param escapeChar - escape character for like statement * @return LIKE or EQUAL Predicate according to the search string */ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuilder, Expression column, String searchString, final boolean caseInsensitive, Function replaceFunction, - Map likeMapping) { + Map likeMapping, char escapeChar) { if (searchString == null || searchString.isBlank()) { return null; } - // replace function for special characters - if (replaceFunction != null) { - searchString = replaceFunction.apply(searchString); - } - // case insensitive Expression columnDefinition = column; if (caseInsensitive) { @@ -299,17 +297,29 @@ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuil // check for like characters boolean like = false; if (likeMapping != null) { - for (Map.Entry item : likeMapping.entrySet()) { - if (searchString.contains(item.getKey())) { - searchString = searchString.replace(item.getKey(), item.getValue()); + for (String item : likeMapping.keySet()) { + if (searchString.contains(item)) { like = true; + break; } } } // like predicate if (like) { - return criteriaBuilder.like(columnDefinition, searchString); + + // replace function for special characters + if (replaceFunction != null) { + searchString = replaceFunction.apply(searchString); + } + + // replace for like characters + for (Map.Entry item : likeMapping.entrySet()) { + if (searchString.contains(item.getKey())) { + searchString = searchString.replace(item.getKey(), item.getValue()); + } + } + return criteriaBuilder.like(columnDefinition, searchString, escapeChar); } // equal predicate diff --git a/extensions/jpa/runtime/src/test/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtilTest.java b/extensions/jpa/runtime/src/test/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtilTest.java index 3cb25aa5..f1323deb 100644 --- a/extensions/jpa/runtime/src/test/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtilTest.java +++ b/extensions/jpa/runtime/src/test/java/org/tkit/quarkus/jpa/utils/QueryCriteriaUtilTest.java @@ -1,6 +1,7 @@ package org.tkit.quarkus.jpa.utils; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyChar; import jakarta.persistence.criteria.*; @@ -19,7 +20,7 @@ void createSearchStringPredicateTest() { Predicate predicate = Mockito.mock(Predicate.class); CriteriaBuilder cb = Mockito.mock(CriteriaBuilder.class); Mockito.when(cb.lower(any())).thenReturn(null); - Mockito.when(cb.like(any(), (String) any())).thenAnswer(invocation -> { + Mockito.when(cb.like(any(), (String) any(), anyChar())).thenAnswer(invocation -> { result.like = true; result.searchString = invocation.getArgument(1); return predicate; diff --git a/extensions/jpa/tests/src/main/java/org/tkit/quarkus/jpa/test/UserDAO.java b/extensions/jpa/tests/src/main/java/org/tkit/quarkus/jpa/test/UserDAO.java index 2bafaeb2..5283f9d6 100644 --- a/extensions/jpa/tests/src/main/java/org/tkit/quarkus/jpa/test/UserDAO.java +++ b/extensions/jpa/tests/src/main/java/org/tkit/quarkus/jpa/test/UserDAO.java @@ -1,5 +1,7 @@ package org.tkit.quarkus.jpa.test; +import static org.tkit.quarkus.jpa.utils.QueryCriteriaUtil.addSearchStringPredicate; + import java.util.ArrayList; import java.util.List; @@ -47,4 +49,17 @@ public PagedQuery pageUsers(UserSearchCriteria criteria, Page page) { } return createPageQuery(cq, page); } + + public PagedQuery pageUsers2(UserSearchCriteria criteria, Page page) { + CriteriaQuery cq = criteriaQuery(); + Root root = cq.from(User.class); + + List predicates = new ArrayList<>(); + CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); + addSearchStringPredicate(predicates, cb, root.get(User_.NAME), criteria.getName()); + addSearchStringPredicate(predicates, cb, root.get(User_.EMAIL), criteria.getEmail()); + cq.where(predicates.toArray(new Predicate[0])); + + return createPageQuery(cq, page); + } } diff --git a/extensions/jpa/tests/src/main/resources/application.properties b/extensions/jpa/tests/src/main/resources/application.properties index 008b30d6..24313288 100644 --- a/extensions/jpa/tests/src/main/resources/application.properties +++ b/extensions/jpa/tests/src/main/resources/application.properties @@ -1,5 +1,6 @@ %test.quarkus.datasource.db-kind=postgresql %test.quarkus.hibernate-orm.database.generation=drop-and-create #%test.quarkus.hibernate-orm.log.sql=true - +#%test.quarkus.hibernate-orm.log.format-sql=true +#%test.quarkus.hibernate-orm.log.bind-parameters=true %test.quarkus.datasource.devservices.enabled=true diff --git a/extensions/jpa/tests/src/test/java/org/tkit/quarkus/jpa/test/UserDAOLikeTest.java b/extensions/jpa/tests/src/test/java/org/tkit/quarkus/jpa/test/UserDAOLikeTest.java new file mode 100644 index 00000000..d5c362db --- /dev/null +++ b/extensions/jpa/tests/src/test/java/org/tkit/quarkus/jpa/test/UserDAOLikeTest.java @@ -0,0 +1,64 @@ +package org.tkit.quarkus.jpa.test; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tkit.quarkus.jpa.daos.Page; +import org.tkit.quarkus.jpa.daos.PageResult; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@DisplayName("User DAO tests") +public class UserDAOLikeTest extends AbstractTest { + + private static final Logger log = LoggerFactory.getLogger(UserDAOLikeTest.class); + + @Inject + UserDAO userDAO; + + private void testSearchUser(String input, List output) { + UserSearchCriteria criteria = new UserSearchCriteria(); + criteria.setName(input); + + var pages = userDAO.pageUsers2(criteria, Page.of(0, 10)); + + Assertions.assertNotNull(pages); + PageResult page = pages.getPageResult(); + Assertions.assertNotNull(page); + var names = page.getStream().map(User::getName).toList(); + log.info("Result: {}", names); + Assertions.assertTrue(output.containsAll(names)); + } + + @Test + public void searchUserTest() { + User c = new User(); + c.setEmail("test@test.test"); + c.setName("rest1"); + userDAO.create(c); + + c = new User(); + c.setEmail("test@test.test"); + c.setName("rest\\name"); + userDAO.create(c); + + c = new User(); + c.setEmail("test@test.test"); + c.setName("rest_name"); + userDAO.create(c); + + testSearchUser("rest_name", List.of("rest_name")); + testSearchUser("rest_na*", List.of("rest_name")); + testSearchUser("rest*", List.of("rest1", "rest\\name", "rest_name")); + testSearchUser("rest\\na*", List.of("rest\\name")); + testSearchUser("rest\\name", List.of("rest\\name")); + } + +}