Skip to content

Commit

Permalink
feat: remove character replacement for equal statement (#154)
Browse files Browse the repository at this point in the history
* feat: remove character replacement for equal statement

* feat: remove character replacement for equal statement
  • Loading branch information
andrejpetras authored Apr 15, 2024
1 parent 10b8e5f commit 3fd98c0
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 14 deletions.
2 changes: 1 addition & 1 deletion docs/modules/tkit-quarkus/pages/includes/attributes.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:project-version: 2.19.0
:project-version: 2.20.0
:quarkus-version: 3.9.3

:examples-dir: ./../examples/
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class QueryCriteriaUtil {
"*", "%",
"?", "_");

private static final char DEFAULT_ESCAPE_CHAR = '\\';

/**
* The default constructor.
*/
Expand Down Expand Up @@ -261,7 +263,7 @@ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuil
public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuilder, Expression<String> 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);
}

/**
Expand All @@ -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<String> column,
String searchString, final boolean caseInsensitive,
Function<String, String> replaceFunction,
Map<String, String> likeMapping) {
Map<String, String> 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<String> columnDefinition = column;
if (caseInsensitive) {
Expand All @@ -299,17 +297,29 @@ public static Predicate createSearchStringPredicate(CriteriaBuilder criteriaBuil
// check for like characters
boolean like = false;
if (likeMapping != null) {
for (Map.Entry<String, String> 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<String, String> item : likeMapping.entrySet()) {
if (searchString.contains(item.getKey())) {
searchString = searchString.replace(item.getKey(), item.getValue());
}
}
return criteriaBuilder.like(columnDefinition, searchString, escapeChar);
}

// equal predicate
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -47,4 +49,17 @@ public PagedQuery<User> pageUsers(UserSearchCriteria criteria, Page page) {
}
return createPageQuery(cq, page);
}

public PagedQuery<User> pageUsers2(UserSearchCriteria criteria, Page page) {
CriteriaQuery<User> cq = criteriaQuery();
Root<User> root = cq.from(User.class);

List<Predicate> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<String> output) {
UserSearchCriteria criteria = new UserSearchCriteria();
criteria.setName(input);

var pages = userDAO.pageUsers2(criteria, Page.of(0, 10));

Assertions.assertNotNull(pages);
PageResult<User> 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"));
}

}

0 comments on commit 3fd98c0

Please sign in to comment.