diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java index 3f08b4fae4e..f951bfb7718 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java @@ -14,6 +14,10 @@ package org.apache.jackrabbit.oak.query; import static java.util.Objects.requireNonNull; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME; import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference; import java.math.BigInteger; @@ -32,9 +36,11 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Result.SizePrecision; import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.namepath.JcrPathParser; import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.plugins.index.IndexUtils; @@ -1267,6 +1273,26 @@ public void verifyNotPotentiallySlow() { } String caller = IndexUtils.getCaller(settings.getIgnoredClassNamesInCallTrace()); String message = "Traversal query (query without index): " + statement + "; called by " + caller + "; consider creating an index"; + if (traversal == Traversal.FAIL || traversal == Traversal.WARN && !potentiallySlowTraversalQueryLogged) { + HashSet reindex = new HashSet<>(); + Iterable indexes = context.getRoot().getTree("/" + INDEX_DEFINITIONS_NAME).getChildren(); + for (Tree index : indexes) { + String name = index.getName(); + PropertyState primaryType = index.getProperty(JCR_PRIMARYTYPE); + if (primaryType != null && INDEX_DEFINITIONS_NODE_TYPE.equals(primaryType.getValue(Type.STRING))) { + PropertyState reindexProp = index.getProperty(REINDEX_PROPERTY_NAME); + if (reindexProp != null && reindexProp.getValue(Type.BOOLEAN)) { + reindex.add(name); + } + } + } + message += "\n\nExecution plan:\n" + getPlan(); + if (!reindex.isEmpty()) { + String reindexNames = reindex.stream().map(name -> name + ",").collect(Collectors.joining()); + message += "\n\nNote that the following indexes were unavailable because of re-indexing:\n" + + reindexNames.substring(0, reindexNames.length() - 1); + } + } switch (traversal) { case DEFAULT: // not possible (changed to either FAIL or WARN above) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java index 04b383957c9..e8d7e154e85 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java @@ -18,6 +18,10 @@ */ package org.apache.jackrabbit.oak.jcr.query; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME; +import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition; +import static org.apache.jackrabbit.oak.spi.commit.CommitInfo.EMPTY; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -41,6 +45,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.ValueFactory; +import javax.jcr.Workspace; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.query.InvalidQueryException; import javax.jcr.query.Query; @@ -60,6 +65,8 @@ import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest; +import org.apache.jackrabbit.oak.spi.commit.EmptyHook; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.junit.Ignore; import org.junit.Test; @@ -138,6 +145,37 @@ public void testSettingsOverride() throws Exception { assertEquals(90, count); } + @Test + public void traversalExtendedDiagnosis() throws Exception { + Session session = getAdminSession(); + Workspace workspace = session.getWorkspace(); + NodeBuilder rootBuilder = getNodeStore().getRoot().builder(); + createIndexDefinition( + rootBuilder.child(INDEX_DEFINITIONS_NAME), "foo", true, false, + Set.of("foo"), null); + getNodeStore().merge(rootBuilder, EmptyHook.INSTANCE, EMPTY); + session.refresh(true); + rootBuilder.child("a").setProperty("foo", "abc"); + //set reindex=true without a CommitHook to simulate an ongoing re-indexing process. + rootBuilder.child(INDEX_DEFINITIONS_NAME).child("foo").setProperty(REINDEX_PROPERTY_NAME, true); + getNodeStore().merge(rootBuilder, EmptyHook.INSTANCE, EMPTY); + session.refresh(true); + Node foo = session.getNode("/" + INDEX_DEFINITIONS_NAME).getNode("foo"); + assertTrue(foo.getProperty(REINDEX_PROPERTY_NAME).getBoolean()); + Query query = workspace.getQueryManager().createQuery("select * from [nt:base] where [x] = 1 or [y] = 2 option(traversal fail)", Query.JCR_SQL2); + try { + query.execute(); + fail("traversing query should not succeed"); + } catch (RepositoryException e) { + String message = e.getMessage(); + assertTrue(message.contains("Traversal query (query without index)")); + assertTrue(message.contains("Execution plan")); + assertTrue(message.contains("[nt:base] as [nt:base] /* traverse")); + assertTrue(message.contains("Note that the following indexes were unavailable because of re-indexing")); + assertTrue(message.contains("foo")); + } + } + @Test public void traversalOption() throws Exception { Session session = getAdminSession();