Skip to content

Commit

Permalink
Performance improvement for case insensitive queries (#29597)
Browse files Browse the repository at this point in the history
* Performance update for case insensitive queries. No unit tests needed as functionality is already tested with existing tests.

* Fixing logic error.

* Updating logic to be more accurate, including addinga new CriteriaType function. Also added unit tests.

* Adding performance enhancing logic for IS_EQUAL and unit tests.

* Fixing IS_EQUAL performance logic related to case insensitive search and added unit tests.

* Fixing code style issues.

* Update CriteriaType.java

* Update AbstractQueryGenerator.java

* Update CriteriaType.java

* Update AbstractQueryGenerator.java

* Update AbstractQueryGenerator.java

Co-authored-by: Fabian Meiswinkel <[email protected]>
  • Loading branch information
trande4884 and FabianMeiswinkel authored Jun 24, 2022
1 parent 21cd37d commit 84d8078
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,39 @@ public void testArrayContainsCriteria() {
assertThat(people).containsExactly(TEST_PERSON);
}

@Test
public void testIsNotNullCriteriaCaseSensitive() {
Criteria hasLastName = Criteria.getInstance(CriteriaType.IS_NOT_NULL, "lastName",
Collections.emptyList(),
Part.IgnoreCaseType.ALWAYS);
List<Person> people = TestUtils.toList(cosmosTemplate.find(new CosmosQuery(hasLastName), Person.class,
containerName));

assertThat(people).containsExactly(TEST_PERSON);
}

@Test
public void testStartsWithCriteriaCaseSensitive() {
Criteria nameStartsWith = Criteria.getInstance(CriteriaType.STARTS_WITH, "firstName",
Collections.singletonList(TEST_PERSON.getFirstName().toUpperCase()),
Part.IgnoreCaseType.ALWAYS);
List<Person> people = TestUtils.toList(cosmosTemplate.find(new CosmosQuery(nameStartsWith), Person.class,
containerName));

assertThat(people).containsExactly(TEST_PERSON);
}

@Test
public void testIsEqualCriteriaCaseSensitive() {
Criteria nameStartsWith = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
Collections.singletonList(TEST_PERSON.getFirstName().toUpperCase()),
Part.IgnoreCaseType.ALWAYS);
List<Person> people = TestUtils.toList(cosmosTemplate.find(new CosmosQuery(nameStartsWith), Person.class,
containerName));

assertThat(people).containsExactly(TEST_PERSON);
}

@Test
public void testBetweenCriteria() {
Criteria ageBetween = Criteria.getInstance(CriteriaType.BETWEEN, "age", Arrays.asList(AGE - 1, AGE + 1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ private String generateBinaryQuery(@NonNull Criteria criteria, @NonNull List<Pai
parameters.add(Pair.of(parameter, subjectValue));

if (CriteriaType.isFunction(criteria.getType())) {
return getFunctionCondition(ignoreCase, sqlKeyword, subject, parameter);
return getFunctionCondition(ignoreCase, sqlKeyword, subject, parameter,
CriteriaType.isFunctionWithCaseSensitiveSupport(criteria.getType()));
} else if (criteria.getType() == CriteriaType.IS_EQUAL
&& ignoreCase != Part.IgnoreCaseType.NEVER
&& subjectValue instanceof String) {
return getFunctionCondition(ignoreCase, CriteriaType.STRING_EQUALS.getSqlKeyword(),
subject, parameter, true);
} else {
return getCondition(ignoreCase, sqlKeyword, subject, parameter);
}
Expand Down Expand Up @@ -95,14 +101,19 @@ private String getCondition(final Part.IgnoreCaseType ignoreCase, final String s
* @param sqlKeyword sql key word, operation name
* @param subject sql column name
* @param parameter sql filter value
* @param takesCaseSensitiveParam if the function type can take the third boolean param
* @return condition string
*/
private String getFunctionCondition(final Part.IgnoreCaseType ignoreCase, final String sqlKeyword,
final String subject, final String parameter) {
final String subject, final String parameter, final boolean takesCaseSensitiveParam) {
if (Part.IgnoreCaseType.NEVER == ignoreCase) {
return String.format("%s(r.%s, @%s)", sqlKeyword, subject, parameter);
} else {
return String.format("%s(UPPER(r.%s), UPPER(@%s))", sqlKeyword, subject, parameter);
if (takesCaseSensitiveParam) {
return String.format("%s(r.%s, @%s, true)", sqlKeyword, subject, parameter);
} else {
return String.format("%s(UPPER(r.%s), UPPER(@%s))", sqlKeyword, subject, parameter);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ public enum CriteriaType {
/**
* Array contains
*/
ARRAY_CONTAINS("ARRAY_CONTAINS");
ARRAY_CONTAINS("ARRAY_CONTAINS"),

/**
* String equals
*/
STRING_EQUALS("STRINGEQUALS");

private String sqlKeyword;

Expand Down Expand Up @@ -274,6 +279,24 @@ public static boolean isFunction(CriteriaType type) {
}
}

/**
* Check if CriteriaType operation is a function.
*
* @param type CriteriaType
* @return True if match, or false.
*/
public static boolean isFunctionWithCaseSensitiveSupport(CriteriaType type) {
switch (type) {
case CONTAINING:
case ENDS_WITH:
case STARTS_WITH:
case STRING_EQUALS:
return true;
default:
return false;
}
}

/**
* Check if CriteriaType operation is unary, with format of (ops A -&gt; B).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.util.*;
import org.springframework.lang.*;

import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

public class AbstractQueryGeneratorTest {
Expand Down Expand Up @@ -48,6 +49,54 @@ public void binaryOperatorPriorityPreserved() {
"(", parameterNames.get(1), CriteriaType.OR.getSqlKeyword(), parameterNames.get(2), ")"));
}

@Test
public void generateBinaryQueryWithStartsWithDoesNotUseUpper() {
Criteria nameStartsWith = Criteria.getInstance(CriteriaType.STARTS_WITH, "firstName",
Collections.singletonList("TREVOR"),
Part.IgnoreCaseType.ALWAYS);
CosmosQuery query = new CosmosQuery(nameStartsWith);

SqlQuerySpec result = queryGenerator.generateCosmos(query);

Assert.assertEquals(result.getQueryText(), " WHERE STARTSWITH(r.firstName, @firstName0, true) ");
}

@Test
public void generateBinaryQueryWithArrayContainsUsesUpper() {
Criteria hasLastName = Criteria.getInstance(CriteriaType.ARRAY_CONTAINS, "lastName",
Collections.singletonList("ANDERSON"),
Part.IgnoreCaseType.ALWAYS);
CosmosQuery query = new CosmosQuery(hasLastName);

SqlQuerySpec result = queryGenerator.generateCosmos(query);

Assert.assertEquals(result.getQueryText(), " WHERE ARRAY_CONTAINS(UPPER(r.lastName), UPPER(@lastName0)) ");
}

@Test
public void generateBinaryQueryWithIsEqualIntUsesUpper() {
Criteria isEqualInt = Criteria.getInstance(CriteriaType.IS_EQUAL, "zipcode",
Collections.singletonList(20180),
Part.IgnoreCaseType.ALWAYS);
CosmosQuery query = new CosmosQuery(isEqualInt);

SqlQuerySpec result = queryGenerator.generateCosmos(query);

Assert.assertEquals(result.getQueryText(), " WHERE UPPER(r.zipcode) = UPPER(@zipcode0) ");
}

@Test
public void generateBinaryQueryWithIsEqualStringDoesNotUseUpper() {
Criteria isEqualString = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
Collections.singletonList("TREVOR"),
Part.IgnoreCaseType.ALWAYS);
CosmosQuery query = new CosmosQuery(isEqualString);

SqlQuerySpec result = queryGenerator.generateCosmos(query);

Assert.assertEquals(result.getQueryText(), " WHERE STRINGEQUALS(r.firstName, @firstName0, true) ");
}

private static class EmptyQueryGenerator extends AbstractQueryGenerator implements QuerySpecGenerator {

@Override
Expand Down

0 comments on commit 84d8078

Please sign in to comment.