You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2017/02/10 10:41:58 UTC
svn commit: r1782447 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/query/
oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/
Author: thomasm
Date: Fri Feb 10 10:41:57 2017
New Revision: 1782447
URL: http://svn.apache.org/viewvc?rev=1782447&view=rev
Log:
OAK-5621 Warn traversal queries: false positives for joins and SQL-2 queries using "or"
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java?rev=1782447&r1=1782446&r2=1782447&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java Fri Feb 10 10:41:57 2017
@@ -190,4 +190,21 @@ public interface Query {
*/
void setQueryOptions(QueryOptions options);
+ /**
+ * Whether the query is potentially slow.
+ * Only supported for prepared queries.
+ *
+ * @return true if traversal is the only option
+ */
+ boolean isPotentiallySlow();
+
+ /**
+ * Verify the query is not potentially slow. Only supported for prepared
+ * queries.
+ *
+ * @throws IllegalArgumentException if potentially slow, and configured to
+ * fail in this case
+ */
+ void verifyNotPotentiallySlow();
+
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java?rev=1782447&r1=1782446&r2=1782447&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java Fri Feb 10 10:41:57 2017
@@ -292,6 +292,7 @@ public abstract class QueryEngineImpl im
// we only have the original query so we prepare and return it.
result = queries.iterator().next();
result.prepare();
+ result.verifyNotPotentiallySlow();
LOG.debug("No alternatives found. Query: {}", result);
} else {
double bestCost = Double.POSITIVE_INFINITY;
@@ -300,8 +301,12 @@ public abstract class QueryEngineImpl im
// it's the default behaviour. That way, we always log the cost and
// can more easily analyze problems. The querySelectionMode flag can
// be used to override the cheapest.
+ boolean isPotentiallySlow = true;
for (Query q : checkNotNull(queries)) {
q.prepare();
+ if (!q.isPotentiallySlow()) {
+ isPotentiallySlow = false;
+ }
double cost = q.getEstimatedCost();
LOG.debug("cost: {} for query {}", cost, q);
if (q.containsUnfilteredFullTextCondition()) {
@@ -329,6 +334,9 @@ public abstract class QueryEngineImpl im
case CHEAPEST:
default:
}
+ if (isPotentiallySlow) {
+ result.verifyNotPotentiallySlow();
+ }
}
return result;
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1782447&r1=1782446&r2=1782447&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java Fri Feb 10 10:41:57 2017
@@ -203,6 +203,8 @@ public class QueryImpl implements Query
private boolean isInternal;
+ private boolean potentiallySlowTraversalQuery;
+
QueryImpl(String statement, SourceImpl source, ConstraintImpl constraint,
ColumnImpl[] columns, NamePathMapper mapper, QueryEngineSettings settings) {
this.statement = statement;
@@ -644,6 +646,11 @@ public class QueryImpl implements Query
// use a greedy algorithm
SourceImpl result = null;
Set<SourceImpl> available = new HashSet<SourceImpl>();
+ // the query is only slow if all possible join orders are slow
+ // (in theory, due to using the greedy algorithm, a query might be considered
+ // slow even thought there is a plan that doesn't need to use traversal, but
+ // only for 3-way and higher joins, and only if traversal is considered very fast)
+ boolean isPotentiallySlowJoin = true;
while (sources.size() > 0) {
int bestIndex = 0;
double bestCost = Double.POSITIVE_INFINITY;
@@ -663,12 +670,16 @@ public class QueryImpl implements Query
bestIndex = i;
best = test;
}
+ if (!potentiallySlowTraversalQuery) {
+ isPotentiallySlowJoin = false;
+ }
test.unprepare();
}
available.add(sources.remove(bestIndex));
result = best;
best.prepare(bestPlan);
}
+ potentiallySlowTraversalQuery = isPotentiallySlowJoin;
estimatedCost = result.prepare().getEstimatedCost();
source = result;
isSortedByIndex = canSortByIndex();
@@ -1035,7 +1046,7 @@ public class QueryImpl implements Query
bestPlan = indexPlan;
}
}
- boolean potentiallySlowTraversalQuery = bestIndex == null;
+ potentiallySlowTraversalQuery = bestIndex == null;
if (traversalEnabled) {
TraversingIndex traversal = new TraversingIndex();
double cost = traversal.getCost(filter, rootState);
@@ -1051,6 +1062,17 @@ public class QueryImpl implements Query
}
}
}
+ return new SelectorExecutionPlan(filter.getSelector(), bestIndex,
+ bestPlan, bestCost);
+ }
+
+ @Override
+ public boolean isPotentiallySlow() {
+ return potentiallySlowTraversalQuery;
+ }
+
+ @Override
+ public void verifyNotPotentiallySlow() {
if (potentiallySlowTraversalQuery) {
QueryOptions.Traversal traversal = queryOptions.traversal;
if (traversal == Traversal.DEFAULT) {
@@ -1078,7 +1100,6 @@ public class QueryImpl implements Query
throw new IllegalArgumentException(message);
}
}
- return new SelectorExecutionPlan(filter.getSelector(), bestIndex, bestPlan, bestCost);
}
private List<OrderEntry> getSortOrder(FilterImpl filter) {
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java?rev=1782447&r1=1782446&r2=1782447&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java Fri Feb 10 10:41:57 2017
@@ -396,4 +396,16 @@ public class UnionQueryImpl implements Q
right.containsUnfilteredFullTextCondition();
}
+ @Override
+ public boolean isPotentiallySlow() {
+ return left.isPotentiallySlow() ||
+ right.isPotentiallySlow();
+ }
+
+ @Override
+ public void verifyNotPotentiallySlow() {
+ left.verifyNotPotentiallySlow();
+ right.verifyNotPotentiallySlow();
+ }
+
}
Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java?rev=1782447&r1=1782446&r2=1782447&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java Fri Feb 10 10:41:57 2017
@@ -72,36 +72,69 @@ public class QueryTest extends AbstractR
public void traversalOption() throws Exception {
Session session = getAdminSession();
QueryManager qm = session.getWorkspace().getQueryManager();
- try {
- qm.createQuery("//*[@test] option(traversal fail)",
- "xpath").execute();
- fail();
- } catch (InvalidQueryException e) {
- // expected
- }
- try {
- qm.createQuery("select * from [nt:base] option(traversal fail)",
- Query.JCR_SQL2).execute();
- fail();
- } catch (InvalidQueryException e) {
- // expected
- }
- qm.createQuery("//*[@test] option(traversal ok)",
- "xpath").execute();
- qm.createQuery("//*[@test] option(traversal warn)",
- "xpath").execute();
- qm.createQuery("select * from [nt:base] option(traversal ok)",
- Query.JCR_SQL2).execute();
- qm.createQuery("select * from [nt:base] option(traversal warn)",
- Query.JCR_SQL2).execute();
+
+ // for union queries:
+ // both subqueries use an index
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] where ischildnode('/') or [jcr:uuid] = 1 option(traversal fail)"));
+ // no subquery uses an index
+ assertFalse(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] where [x] = 1 or [y] = 2 option(traversal fail)"));
+ // first one does not, second one does
+ assertFalse(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] where [jcr:uuid] = 1 or [x] = 2 option(traversal fail)"));
+ // first one does, second one does not
+ assertFalse(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] where [x] = 2 or [jcr:uuid] = 1 option(traversal fail)"));
+
+ // queries that possibly use traversal (depending on the join order)
+ assertTrue(isValidQuery(qm, "xpath",
+ "/jcr:root/content//*/jcr:content[@jcr:uuid='1'] option(traversal fail)"));
+ assertTrue(isValidQuery(qm, "xpath",
+ "/jcr:root/content/*/jcr:content[@jcr:uuid='1'] option(traversal fail)"));
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] as [a] inner join [nt:base] as [b] on ischildnode(b, a) " +
+ "where [a].[jcr:uuid] = 1 option(traversal fail)"));
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] as [a] inner join [nt:base] as [b] on ischildnode(a, b) " +
+ "where [a].[jcr:uuid] = 1 option(traversal fail)"));
+
+ // union with joins
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] as [a] inner join [nt:base] as [b] on ischildnode(a, b) " +
+ "where ischildnode([a], '/') or [a].[jcr:uuid] = 1 option(traversal fail)"));
+
+ assertFalse(isValidQuery(qm, "xpath",
+ "//*[@test] option(traversal fail)"));
+ assertFalse(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] option(traversal fail)"));
+ assertTrue(isValidQuery(qm, "xpath",
+ "//*[@test] option(traversal ok)"));
+ assertTrue(isValidQuery(qm, "xpath",
+ "//*[@test] option(traversal warn)"));
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] option(traversal ok)"));
+ assertTrue(isValidQuery(qm, Query.JCR_SQL2,
+ "select * from [nt:base] option(traversal warn)"));
+
// the following is not really traversal, it is just listing child nodes:
- qm.createQuery("/jcr:root/*[@test] option(traversal fail)",
- "xpath").execute();
+ assertTrue(isValidQuery(qm, "xpath",
+ "/jcr:root/*[@test] option(traversal fail)"));
// the following is not really traversal; it is just one node:
- qm.createQuery("/jcr:root/oak:index[@test] option(traversal fail)",
- "xpath").execute();
+ assertTrue(isValidQuery(qm, "xpath",
+ "/jcr:root/oak:index[@test] option(traversal fail)"));
- }
+ }
+
+ private static boolean isValidQuery(QueryManager qm, String language, String query) throws RepositoryException {
+ try {
+ qm.createQuery(query, language).execute();
+ return true;
+ } catch (InvalidQueryException e) {
+ assertTrue(e.toString(), e.toString().indexOf("query without index") >= 0);
+ return false;
+ }
+ }
@Test
public void firstSelector() throws Exception {