From 4769b2a3cc196a411e81deed3c24b62ef6604c4e Mon Sep 17 00:00:00 2001 From: Matthias Keck Date: Tue, 9 Apr 2024 08:52:57 +0200 Subject: [PATCH 1/2] Uses lowercase and uppercase field annotations to optimize queries - When all values stored in a field are stored e.g. lowercased, we do not need to query case-insensitively. This can give a small speedup. - When all values in a field are stored e.g. lowercased, we do not need to query with an value containing upper case characters. We apply auto-magical case adaption on some cases where the framework generates queries. So we create the chance of actually finding an result, even if the input did not match the actual db field. This is relevant, e.g. for the Sirius biz virtual filesystem backend ui. There, the normalized filename field of a sql blob containing only lowercase characters might get queried by user input containing the exact filename. By this adaption, we are able to find the blob. - For regular API usage, such magic from framework is not desired. If required, apply it by yourself when creating queries. Fixes: SIRI-951 --- .../db/jdbc/constraints/SQLQueryCompiler.java | 15 ++++++++----- .../db/mixing/annotations/LowerCase.java | 3 ++- .../db/mixing/annotations/UpperCase.java | 3 ++- .../sirius/db/mixing/query/QueryCompiler.java | 21 +++++++++++++++++++ .../mongo/constraints/MongoQueryCompiler.java | 2 +- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main/java/sirius/db/jdbc/constraints/SQLQueryCompiler.java b/src/main/java/sirius/db/jdbc/constraints/SQLQueryCompiler.java index 32a48a641..d845d24e4 100644 --- a/src/main/java/sirius/db/jdbc/constraints/SQLQueryCompiler.java +++ b/src/main/java/sirius/db/jdbc/constraints/SQLQueryCompiler.java @@ -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}. @@ -42,22 +43,26 @@ public SQLQueryCompiler(FilterFactory factory, @Override protected SQLConstraint compileSearchToken(Mapping field, QueryField.Mode mode, String value) { + Optional 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(); } } diff --git a/src/main/java/sirius/db/mixing/annotations/LowerCase.java b/src/main/java/sirius/db/mixing/annotations/LowerCase.java index f7c926159..ffeb35bc8 100644 --- a/src/main/java/sirius/db/mixing/annotations/LowerCase.java +++ b/src/main/java/sirius/db/mixing/annotations/LowerCase.java @@ -17,7 +17,8 @@ /** * Marks a string property as auto lower-cased. *

- * 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) diff --git a/src/main/java/sirius/db/mixing/annotations/UpperCase.java b/src/main/java/sirius/db/mixing/annotations/UpperCase.java index 10e8e305c..77bd4f27f 100644 --- a/src/main/java/sirius/db/mixing/annotations/UpperCase.java +++ b/src/main/java/sirius/db/mixing/annotations/UpperCase.java @@ -17,7 +17,8 @@ /** * Marks a string property as auto upper-cased. *

- * 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) diff --git a/src/main/java/sirius/db/mixing/query/QueryCompiler.java b/src/main/java/sirius/db/mixing/query/QueryCompiler.java index 666ff7a76..d05406ca0 100644 --- a/src/main/java/sirius/db/mixing/query/QueryCompiler.java +++ b/src/main/java/sirius/db/mixing/query/QueryCompiler.java @@ -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; @@ -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; @@ -789,4 +792,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 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(); + } } diff --git a/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java b/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java index c82e1a921..a96bff8fc 100644 --- a/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java +++ b/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java @@ -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 { From 96a909aa6f55645d993aba1ca00e38cb756a9636 Mon Sep 17 00:00:00 2001 From: Matthias Keck Date: Tue, 9 Apr 2024 08:53:28 +0200 Subject: [PATCH 2/2] Some sonarlint and formatting fixes Fixes: SIRI-951 --- src/main/java/sirius/db/mixing/query/QueryCompiler.java | 3 +-- src/main/java/sirius/db/mixing/query/QueryField.java | 4 ++-- .../java/sirius/db/mongo/constraints/MongoQueryCompiler.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/sirius/db/mixing/query/QueryCompiler.java b/src/main/java/sirius/db/mixing/query/QueryCompiler.java index d05406ca0..b08369409 100644 --- a/src/main/java/sirius/db/mixing/query/QueryCompiler.java +++ b/src/main/java/sirius/db/mixing/query/QueryCompiler.java @@ -260,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(); diff --git a/src/main/java/sirius/db/mixing/query/QueryField.java b/src/main/java/sirius/db/mixing/query/QueryField.java index 3fc7a3763..5e0c6e665 100644 --- a/src/main/java/sirius/db/mixing/query/QueryField.java +++ b/src/main/java/sirius/db/mixing/query/QueryField.java @@ -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; diff --git a/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java b/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java index a96bff8fc..d14182039 100644 --- a/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java +++ b/src/main/java/sirius/db/mongo/constraints/MongoQueryCompiler.java @@ -31,7 +31,7 @@ public class MongoQueryCompiler extends QueryCompiler { private static final Mapping FULLTEXT_MAPPING = Mapping.named("$text"); /** - * Represents an artificial field which generates a search using a $text filter. Therefore + * Represents an artificial field which generates a search using a $text filter. Therefore, * an appropriate text index has to be present. */ public static final QueryField FULLTEXT = QueryField.contains(FULLTEXT_MAPPING);