diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java index 6ceebaf14e1..f594df65846 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/Oak.java @@ -588,6 +588,10 @@ public Oak with(@NotNull Whiteboard whiteboard) { LOG.info("Registered improved cost feature: " + QueryEngineSettings.FT_NAME_IMPROVED_IS_NULL_COST); closer.register(improvedIsNullCostFeature); queryEngineSettings.setImprovedIsNullCostFeature(improvedIsNullCostFeature); + Feature optimizeInRestrictionsForFunctions = newFeature(QueryEngineSettings.FT_OPTIMIZE_IN_RESTRICTIONS_FOR_FUNCTIONS, whiteboard); + LOG.info("Registered optimize in restrictions for functions feature: " + QueryEngineSettings.FT_OPTIMIZE_IN_RESTRICTIONS_FOR_FUNCTIONS); + closer.register(optimizeInRestrictionsForFunctions); + queryEngineSettings.setOptimizeInRestrictionsForFunctions(optimizeInRestrictionsForFunctions); } return this; @@ -987,6 +991,10 @@ public void setImprovedIsNullCostFeature(@Nullable Feature feature) { settings.setImprovedIsNullCostFeature(feature); } + public void setOptimizeInRestrictionsForFunctions(@Nullable Feature feature) { + settings.setOptimizeInRestrictionsForFunctions(feature); + } + @Override public void setQueryValidatorPattern(String key, String pattern, String comment, boolean failQuery) { settings.getQueryValidator().setPattern(key, pattern, comment, failQuery); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java index a6f68ea9cae..bb90eb2ad9e 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java @@ -65,6 +65,8 @@ public class QueryEngineSettings implements QueryEngineSettingsMBean, QueryLimit public static final String FT_NAME_IMPROVED_IS_NULL_COST = "FT_OAK-10532"; + public static final String FT_OPTIMIZE_IN_RESTRICTIONS_FOR_FUNCTIONS = "FT_OAK-11214"; + public static final int DEFAULT_PREFETCH_COUNT = Integer.getInteger(OAK_QUERY_PREFETCH_COUNT, -1); public static final String OAK_QUERY_FAIL_TRAVERSAL = "oak.queryFailTraversal"; @@ -114,6 +116,7 @@ public class QueryEngineSettings implements QueryEngineSettingsMBean, QueryLimit private Feature prefetchFeature; private Feature improvedIsNullCostFeature; + private Feature optimizeInRestrictionsForFunctions; private String autoOptionsMappingJson = "{}"; private QueryOptions.AutomaticQueryOptionsMapping autoOptionsMapping = new QueryOptions.AutomaticQueryOptionsMapping(autoOptionsMappingJson); @@ -218,6 +221,16 @@ public boolean getImprovedIsNullCost() { return improvedIsNullCostFeature == null || improvedIsNullCostFeature.isEnabled(); } + public void setOptimizeInRestrictionsForFunctions(@Nullable Feature feature) { + this.optimizeInRestrictionsForFunctions = feature; + } + + @Override + public boolean getOptimizeInRestrictionsForFunctions() { + // enabled if the feature toggle is not used + return optimizeInRestrictionsForFunctions == null || optimizeInRestrictionsForFunctions.isEnabled(); + } + public String getStrictPathRestriction() { return strictPathRestriction.name(); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java index df46dd82735..08036577ce7 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/LowerCaseImpl.java @@ -105,6 +105,13 @@ public void restrict(FilterImpl f, Operator operator, PropertyValue v) { public void restrictList(FilterImpl f, List list) { // "LOWER(x) IN (A, B)" implies x is not null operand.restrict(f, Operator.NOT_EQUAL, null); + if (!f.getQueryLimits().getOptimizeInRestrictionsForFunctions()) { + return; + } + String fn = getFunction(f.getSelector()); + if (fn != null) { + f.restrictPropertyAsList(QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn, list); + } } @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java index 577ed96aee4..49d6d69b465 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeLocalNameImpl.java @@ -109,7 +109,13 @@ public void restrict(FilterImpl f, Operator operator, PropertyValue v) { @Override public void restrictList(FilterImpl f, List list) { - // optimizations of type "LOCALNAME(..) IN(A, B)" are not supported + if (!f.getQueryLimits().getOptimizeInRestrictionsForFunctions()) { + return; + } + String fn = getFunction(f.getSelector()); + if (fn != null) { + f.restrictPropertyAsList(QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn, list); + } } @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java index 00484b254f1..7c799153f74 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NodeNameImpl.java @@ -111,7 +111,13 @@ public void restrict(FilterImpl f, Operator operator, PropertyValue v) { @Override public void restrictList(FilterImpl f, List list) { - // optimizations of type "NAME(..) IN(A, B)" are not supported + if (!f.getQueryLimits().getOptimizeInRestrictionsForFunctions()) { + return; + } + String fn = getFunction(f.getSelector()); + if (fn != null) { + f.restrictPropertyAsList(QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn, list); + } } @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotFullTextSearchImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotFullTextSearchImpl.java index 82f59afc777..6506fc5eefa 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotFullTextSearchImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotFullTextSearchImpl.java @@ -25,7 +25,6 @@ import org.jetbrains.annotations.NotNull; import org.apache.jackrabbit.guava.common.base.Splitter; -import org.apache.jackrabbit.guava.common.collect.ImmutableSet; public class NotFullTextSearchImpl extends FullTextSearchImpl { private static final Set KEYWORDS = Set.of("or"); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PathImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PathImpl.java index 7f6c1cc7966..131437fee3d 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PathImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PathImpl.java @@ -95,7 +95,13 @@ public void restrict(FilterImpl f, Operator operator, PropertyValue v) { @Override public void restrictList(FilterImpl f, List list) { - // optimizations of type "NAME(..) IN(A, B)" are not supported + if (!f.getQueryLimits().getOptimizeInRestrictionsForFunctions()) { + return; + } + String fn = getFunction(f.getSelector()); + if (fn != null) { + f.restrictPropertyAsList(QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn, list); + } } @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java index 400b7bbc233..af670098164 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/UpperCaseImpl.java @@ -98,6 +98,13 @@ public void restrict(FilterImpl f, Operator operator, PropertyValue v) { public void restrictList(FilterImpl f, List list) { // "UPPER(x) IN (A, B)" implies x is not null operand.restrict(f, Operator.NOT_EQUAL, null); + if (!f.getQueryLimits().getOptimizeInRestrictionsForFunctions()) { + return; + } + String fn = getFunction(f.getSelector()); + if (fn != null) { + f.restrictPropertyAsList(QueryConstants.FUNCTION_RESTRICTION_PREFIX + fn, list); + } } @Override diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java index e56cd34f275..7dc4d86e7c9 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java @@ -46,6 +46,39 @@ private Filter createFilterSQL(String sql) throws ParseException { return q.createFilter(true); } + @Test + public void functionBasedIndexOr() throws Exception { + String sql2 = "select [jcr:path] from [nt:base] where lower([test]) in('hello', 'world')"; + assertEquals("Filter(query=select [jcr:path] from [nt:base] " + + "where lower([test]) in('hello', 'world'), " + + "path=*, property=[function*lower*@test=[in(hello, world)], " + + "test=[is not null]])", createFilterSQL(sql2).toString()); + + sql2 = "select [jcr:path] from [nt:base] where upper([test]) in('hello', 'world')"; + assertEquals("Filter(query=select [jcr:path] from [nt:base] " + + "where upper([test]) in('hello', 'world'), " + + "path=*, property=[function*upper*@test=[in(hello, world)], " + + "test=[is not null]])", createFilterSQL(sql2).toString()); + + sql2 = "select [jcr:path] from [nt:base] where name() in('hello', 'world')"; + assertEquals("Filter(query=select [jcr:path] from [nt:base] " + + "where name() in('hello', 'world'), " + + "path=*, property=[function*@:name=[in(hello, world)]])", + createFilterSQL(sql2).toString()); + + sql2 = "select [jcr:path] from [nt:base] where localname() in('hello', 'world')"; + assertEquals("Filter(query=select [jcr:path] from [nt:base] " + + "where localname() in('hello', 'world'), " + + "path=*, property=[function*@:localname=[in(hello, world)]])", + createFilterSQL(sql2).toString()); + + sql2 = "select [jcr:path] from [nt:base] where path() in('/hello', '/world')"; + assertEquals("Filter(query=select [jcr:path] from [nt:base] " + + "where path() in('/hello', '/world'), " + + "path=*, property=[function*@:path=[in(/hello, /world)]])", + createFilterSQL(sql2).toString()); + } + @Test public void functionBasedIndex() throws Exception { String sql2 = "select [jcr:path] from [nt:base] where lower([test]) = 'hello'"; diff --git a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java index 366e2a0872d..d06edaf5201 100644 --- a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java +++ b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java @@ -39,12 +39,22 @@ default boolean getImprovedIsNullCost() { return true; } + /** + * See OAK-11214. This method is used for backward compatibility + * (bug compatibility) only. + * + * @return true, except when backward compatibility for OAK-11214 is enabled + */ + default public boolean getOptimizeInRestrictionsForFunctions() { + return true; + } + boolean getFailTraversal(); default String getStrictPathRestriction() { return StrictPathRestriction.DISABLE.name(); } - + /** * Retrieve the java package names / full qualified class names which should be * ignored when finding the class starting a query diff --git a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java index 24d875f1de3..2cdf053fccc 100644 --- a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java +++ b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java @@ -18,7 +18,7 @@ /** * This package contains oak query index related classes. */ -@Version("3.0.0") +@Version("3.1.0") package org.apache.jackrabbit.oak.spi.query; import org.osgi.annotation.versioning.Version;