Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimizes some system generates queries based on casing annotations #648

Merged
merged 2 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/main/java/sirius/db/jdbc/constraints/SQLQueryCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import sirius.kernel.commons.Tuple;

import java.util.List;
import java.util.Optional;

/**
* Provides a query compiler for {@link sirius.db.jdbc.SmartQuery} and {@link SQLFilterFactory}.
Expand All @@ -42,22 +43,26 @@ public SQLQueryCompiler(FilterFactory<SQLConstraint> factory,

@Override
protected SQLConstraint compileSearchToken(Mapping field, QueryField.Mode mode, String value) {
Optional<String> caseOptimizedValue = getCaseOptimizedValue(field, value);
switch (mode) {
case EQUAL:
return factory.eq(field, value);
return factory.eq(field, caseOptimizedValue.orElse(value));
case LIKE:
if (value.contains("*")) {
if (caseOptimizedValue.isEmpty() && value.contains("*")) {
return OMA.FILTERS.like(field).matches(value).ignoreCase().build();
} else {
return factory.eq(field, value);
return factory.eq(field, caseOptimizedValue.orElse(value));
}
case PREFIX:
if (value.contains("*")) {
if (caseOptimizedValue.isEmpty() && value.contains("*")) {
return OMA.FILTERS.like(field).startsWith(value).ignoreCase().build();
} else {
return OMA.FILTERS.like(field).startsWith(value).build();
return OMA.FILTERS.like(field).startsWith(caseOptimizedValue.orElse(value)).build();
}
default:
if (caseOptimizedValue.isPresent()) {
return OMA.FILTERS.like(field).contains(caseOptimizedValue.get()).build();
}
return OMA.FILTERS.like(field).contains(value).ignoreCase().build();
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/sirius/db/mixing/annotations/LowerCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
/**
* Marks a string property as auto lower-cased.
* <p>
* The value of this property will be lower-cased before it is written to the database.
* The value of this property will be lower-cased before it is written to the database. Also, it is used to optimize
* some system generated queries.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/sirius/db/mixing/annotations/UpperCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
/**
* Marks a string property as auto upper-cased.
* <p>
* The value of this property will be upper-cased before it is written to the database.
* The value of this property will be upper-cased before it is written to the database. Also, it is used to optimize
* some system generated queries.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/sirius/db/mixing/query/QueryCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import sirius.db.mixing.EntityDescriptor;
import sirius.db.mixing.Mapping;
import sirius.db.mixing.Property;
import sirius.db.mixing.annotations.LowerCase;
import sirius.db.mixing.annotations.UpperCase;
import sirius.db.mixing.properties.BaseEntityRefListProperty;
import sirius.db.mixing.properties.BaseEntityRefProperty;
import sirius.db.mixing.properties.LocalDateProperty;
Expand All @@ -35,6 +37,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -257,8 +260,7 @@ private C parseExpression() {
skipWhitespace();

if ((reader.current().is('!') || reader.current().is('-'))) {
if (reader.next().isWhitespace() ||reader.next()
.isEndOfInput()) {
if (reader.next().isWhitespace() || reader.next().isEndOfInput()) {
// If there is a single "-" or "!" in a string like "foo - bar", we simly skip the dash
// as it is ignored by the indexing tokenizer anyway...
reader.consume();
Expand Down Expand Up @@ -789,4 +791,22 @@ private C parseTag() {

return null;
}

/**
* Returns the optimized value for the given field respecting the Sirius DB casing annotations.
*
* @param field the field to query for
* @param value the value to query
* @return the optimized value to use in a constraint if any, or an Optional.empty() if no optimization is possible
*/
protected Optional<String> getCaseOptimizedValue(Mapping field, String value) {
Property property = resolveProperty(field.toString()).getSecond();
if (property.isAnnotationPresent(LowerCase.class)) {
return Optional.of(value.toLowerCase());
}
if (property.isAnnotationPresent(UpperCase.class)) {
return Optional.of(value.toUpperCase());
}
return Optional.empty();
sabieber marked this conversation as resolved.
Show resolved Hide resolved
}
}
4 changes: 2 additions & 2 deletions src/main/java/sirius/db/mixing/query/QueryField.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public enum Mode {
EQUAL, LIKE, PREFIX, CONTAINS
}

private Mapping field;
private Mode mode;
private final Mapping field;
private final Mode mode;

private QueryField(Mapping field, Mode mode) {
this.field = field;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class MongoQueryCompiler extends QueryCompiler<MongoConstraint> {
private static final Mapping FULLTEXT_MAPPING = Mapping.named("$text");

/**
* Represents an artificial field which generates a search using a <tt>$text</tt> filter. Therefore
* Represents an artificial field which generates a search using a <tt>$text</tt> filter. Therefore,
* an appropriate <b>text</b> index has to be present.
*/
public static final QueryField FULLTEXT = QueryField.contains(FULLTEXT_MAPPING);
Expand All @@ -58,7 +58,7 @@ protected MongoConstraint compileSearchToken(Mapping field, QueryField.Mode mode
}

if (mode == QueryField.Mode.EQUAL) {
return factory.eq(field, value);
return factory.eq(field, getCaseOptimizedValue(field, value).orElse(value));
} else if (mode == QueryField.Mode.PREFIX) {
return QueryBuilder.FILTERS.prefix(field, value);
} else {
Expand Down