Skip to content

Commit f7907ec

Browse files
committed
OAK-12057 - Select same plan with or without LIMIT option
1 parent 52e08fa commit f7907ec

File tree

5 files changed

+73
-25
lines changed

5 files changed

+73
-25
lines changed

oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,8 +1081,6 @@ private SelectorExecutionPlan getBestSelectorExecutionPlan(
10811081
double almostBestCost = Double.POSITIVE_INFINITY;
10821082
IndexPlan almostBestPlan = null;
10831083

1084-
long maxEntryCount = saturatedAdd(offset.orElse(0L), limit.orElse(Long.MAX_VALUE));
1085-
10861084
// Sort the indexes according to their minimum cost to be able to skip the remaining indexes if the cost of the
10871085
// current index is below the minimum cost of the next index.
10881086
List<? extends QueryIndex> queryIndexes = indexProvider.getQueryIndexes(rootState).stream()
@@ -1117,12 +1115,6 @@ private SelectorExecutionPlan getBestSelectorExecutionPlan(
11171115
if (p.getSupportsPathRestriction()) {
11181116
entryCount = scaleEntryCount(rootState, filter, entryCount);
11191117
}
1120-
if (sortOrder == null || p.getSortOrder() != null) {
1121-
// if the query is unordered, or
1122-
// if the query contains "order by" and the index can sort on that,
1123-
// then we don't need to read all entries from the index
1124-
entryCount = Math.min(maxEntryCount, entryCount);
1125-
}
11261118
double c = p.getCostPerExecution() + entryCount * p.getCostPerEntry();
11271119

11281120
if (LOG.isDebugEnabled()) {

oak-core/src/test/java/org/apache/jackrabbit/oak/query/SQL2OptimiseQueryTest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636

3737
import java.text.ParseException;
3838
import java.util.List;
39-
import java.util.regex.Matcher;
40-
import java.util.regex.Pattern;
4139

4240
import javax.jcr.RepositoryException;
4341

@@ -88,7 +86,7 @@ public void limitUnionSize() throws ParseException {
8886
+ " AND ((CONTAINS(*, '10') AND ([jcr:uuid] LIKE '11' OR [jcr:uuid] LIKE '12'))\n"
8987
+ " OR (CONTAINS(*, '13') AND ([jcr:uuid] LIKE '14' OR [jcr:uuid] LIKE '15')))";
9088
SQL2Parser parser = SQL2ParserTest.createTestSQL2Parser(
91-
getMappings(), getNodeTypes(), qeSettings);
89+
getMappings(), getNodeTypes());
9290
Query original;
9391
original = parser.parse(query, false);
9492
assertNotNull(original);
@@ -219,7 +217,7 @@ private static Tree addChildWithProperty(@NotNull Tree father, @NotNull String n
219217
@Test
220218
public void optimise() throws ParseException {
221219
SQL2Parser parser = SQL2ParserTest.createTestSQL2Parser(
222-
getMappings(), getNodeTypes(), qeSettings);
220+
getMappings(), getNodeTypes());
223221
String statement;
224222
Query original, optimised;
225223

@@ -294,7 +292,7 @@ public void optimiseAndOrAnd() throws ParseException {
294292

295293
private void optimiseAndOrAnd(String statement, String expected) throws ParseException {
296294
SQL2Parser parser = SQL2ParserTest.createTestSQL2Parser(
297-
getMappings(), getNodeTypes(), qeSettings);
295+
getMappings(), getNodeTypes());
298296
Query original;
299297
original = parser.parse(statement, false);
300298
assertNotNull(original);
@@ -310,7 +308,7 @@ private void optimiseAndOrAnd(String statement, String expected) throws ParseExc
310308
@Test
311309
public void optimizeKeepsQueryOptions() throws ParseException {
312310
SQL2Parser parser = SQL2ParserTest.createTestSQL2Parser(
313-
getMappings(), getNodeTypes(), qeSettings);
311+
getMappings(), getNodeTypes());
314312
Query original;
315313
String statement = "select * from [nt:unstructured] as [c] " +
316314
"where [a]=1 or [b]=2 option(index tag x)";

oak-core/src/test/java/org/apache/jackrabbit/oak/query/SQL2ParserTest.java

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertFalse;
2222
import static org.junit.Assert.assertTrue;
23+
import static org.mockito.ArgumentMatchers.any;
24+
import static org.mockito.ArgumentMatchers.eq;
25+
import static org.mockito.BDDMockito.given;
26+
import static org.mockito.Mockito.mock;
2327

2428
import java.text.ParseException;
29+
import java.util.List;
2530

2631
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
2732
import org.apache.jackrabbit.oak.query.ast.NodeTypeInfoProvider;
2833
import org.apache.jackrabbit.oak.query.stats.QueryStatsData;
2934
import org.apache.jackrabbit.oak.query.xpath.XPathToSQL2Converter;
35+
import org.apache.jackrabbit.oak.spi.query.QueryIndex;
36+
import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
37+
import org.apache.jackrabbit.oak.spi.state.NodeState;
38+
import org.jetbrains.annotations.NotNull;
3039
import org.junit.Ignore;
3140
import org.junit.Test;
3241

@@ -40,14 +49,12 @@ public class SQL2ParserTest {
4049
private static final SQL2Parser p = createTestSQL2Parser();
4150

4251
public static SQL2Parser createTestSQL2Parser() {
43-
return createTestSQL2Parser(NamePathMapper.DEFAULT, nodeTypes, new QueryEngineSettings());
52+
return createTestSQL2Parser(NamePathMapper.DEFAULT, nodeTypes);
4453
}
4554

46-
public static SQL2Parser createTestSQL2Parser(NamePathMapper mappings, NodeTypeInfoProvider nodeTypes2,
47-
QueryEngineSettings qeSettings) {
55+
public static SQL2Parser createTestSQL2Parser(NamePathMapper mappings, NodeTypeInfoProvider nodeTypes2) {
4856
QueryStatsData data = new QueryStatsData("", "");
49-
return new SQL2Parser(mappings, nodeTypes2, new QueryEngineSettings(),
50-
data.new QueryExecutionStats());
57+
return new SQL2Parser(mappings, nodeTypes2, new QueryEngineSettings(), data.new QueryExecutionStats());
5158
}
5259

5360

@@ -87,6 +94,60 @@ public void testUnwrappedOr() throws ParseException {
8794
assertTrue(q.contains(token));
8895
}
8996

97+
/*
98+
* When a query with LIMIT option, it should still select the index with the least entries
99+
* as those might require being traversed during post-filtering.
100+
*
101+
* See OAK-12057
102+
*/
103+
@Test
104+
public void testPlanningWithLimit() throws ParseException {
105+
// Given
106+
var query = "SELECT * \n" +
107+
"FROM [nt:base] AS s \n" +
108+
"WHERE ISDESCENDANTNODE(s, '/content') AND s.[j:c]='/conf/wknd'\n" +
109+
"OPTION (LIMIT 2)";
110+
111+
// - the first available option
112+
var indexA = mock(QueryIndex.AdvancedQueryIndex.class);
113+
var planA = mock(QueryIndex.IndexPlan.class);
114+
given(indexA.getPlans(any(), any(), any())).willReturn(List.of(planA));
115+
given(planA.getPlanName()).willReturn("planA");
116+
given(planA.getEstimatedEntryCount()).willReturn(10000L); // more entries
117+
given(planA.getCostPerEntry()).willReturn(1.0);
118+
given(planA.getCostPerExecution()).willReturn(100.0);
119+
120+
// - the better option
121+
var indexB = mock(QueryIndex.AdvancedQueryIndex.class);
122+
var planB = mock(QueryIndex.IndexPlan.class);
123+
given(indexB.getPlans(any(), any(), any())).willReturn(List.of(planB));
124+
given(planB.getPlanName()).willReturn("planB");
125+
given(planB.getEstimatedEntryCount()).willReturn(100L); // less entries
126+
given(planB.getCostPerEntry()).willReturn(1.0);
127+
given(planB.getCostPerExecution()).willReturn(100.0);
128+
given(indexB.getPlanDescription(eq(planB), any())).willReturn("planB");
129+
130+
var indexProvider = new QueryIndexProvider() {
131+
@Override
132+
public @NotNull List<? extends QueryIndex> getQueryIndexes(NodeState nodeState) {
133+
return List.of(indexA, indexB);
134+
}
135+
};
136+
137+
var context = mock(ExecutionContext.class);
138+
given(context.getIndexProvider()).willReturn(indexProvider);
139+
140+
// When
141+
var parsedQuery = p.parse(query,false);
142+
parsedQuery.init();
143+
parsedQuery.setExecutionContext(context);
144+
parsedQuery.setTraversalEnabled(false);
145+
parsedQuery.prepare();
146+
147+
// Then
148+
assertEquals("[nt:base] as [s] /* planB */", parsedQuery.getPlan());
149+
}
150+
90151
@Test
91152
public void testCoalesce() throws ParseException {
92153
p.parse("SELECT * FROM [nt:base] WHERE COALESCE([j:c/m/d:t], [j:c/j:t])='a'");
@@ -188,4 +249,5 @@ public void orderingWithUnionOfNodetype() throws Exception {
188249
xpath = "//(element(*, type1) | element(*, type2))[@a='b' or @c='d'] order by @foo";
189250
assertTrue("Converted xpath " + xpath + "doesn't end with 'order by [foo]'", c.convert(xpath).endsWith("order by [foo]"));
190251
}
252+
191253
}

oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.slf4j.Logger;
3535
import org.slf4j.LoggerFactory;
3636

37-
import static java.util.stream.Collectors.toMap;
3837
import static org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction;
3938

4039
/**
@@ -174,7 +173,7 @@ interface AdvanceFulltextQueryIndex extends FulltextQueryIndex, AdvancedQueryInd
174173
* (returning the rows in a specific order), and that can provide detailed
175174
* information about the cost.
176175
*/
177-
interface AdvancedQueryIndex {
176+
interface AdvancedQueryIndex extends QueryIndex {
178177

179178
/**
180179
* Return the possible index plans for the given filter and sort order.
@@ -373,9 +372,6 @@ default boolean logWarningForPathFilterMismatch() {
373372
* A builder for index plans.
374373
*/
375374
class Builder {
376-
377-
private static Logger LOG = LoggerFactory.getLogger(QueryIndex.IndexPlan.Builder.class);
378-
379375
protected double costPerExecution = 1.0;
380376
protected double costPerEntry = 1.0;
381377
protected long estimatedEntryCount = 1000000;

oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/**
1919
* This package contains oak query index related classes.
2020
*/
21-
@Version("3.2.0")
21+
@Version("4.0.0")
2222
package org.apache.jackrabbit.oak.spi.query;
2323

2424
import org.osgi.annotation.versioning.Version;

0 commit comments

Comments
 (0)