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 2019/09/13 12:53:26 UTC
svn commit: r1866903 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/query/
oak-core/src/test/java/org/apache/jackrabbit/oak/query/
oak-core/src/test/resources/org/apache/jackrabbit/oak/query/
oak-jcr/src/test/java/org/apac...
Author: thomasm
Date: Fri Sep 13 12:53:26 2019
New Revision: 1866903
URL: http://svn.apache.org/viewvc?rev=1866903&view=rev
Log:
OAK-8245 Add column for explained statement to explain Query result, next to 'plan' column
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ExplainResultTest.java
jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/explain_result.txt
Modified:
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-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/UnionQueryTest.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
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=1866903&r1=1866902&r2=1866903&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 Sep 13 12:53:26 2019
@@ -541,10 +541,18 @@ public class QueryImpl implements Query
if (measure) {
plan += " cost: { " + getIndexCostInfo() + " }";
}
- columns = new ColumnImpl[] { new ColumnImpl("explain", "plan", "plan")};
+ columns = new ColumnImpl[] {
+ new ColumnImpl("explain", "plan", "plan"),
+ new ColumnImpl("explain", "statement", "statement")
+ };
ResultRowImpl r = new ResultRowImpl(this,
Tree.EMPTY_ARRAY,
- new PropertyValue[] { PropertyValues.newString(plan)},
+ new PropertyValue[] {
+ PropertyValues.newString(plan),
+ // remove "explain" keyword from query statement to produce explained statement
+ PropertyValues.newString(getStatement()
+ .replaceFirst("(?i)\\bexplain\\s+", ""))
+ },
null, null);
return Arrays.asList(r).iterator();
}
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=1866903&r1=1866902&r2=1866903&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 Sep 13 12:53:26 2019
@@ -284,10 +284,17 @@ public class UnionQueryImpl implements Q
prepare();
if (explain) {
String plan = getPlan();
- columns = new ColumnImpl[] { new ColumnImpl("explain", "plan", "plan")};
+ columns = new ColumnImpl[] {
+ new ColumnImpl("explain", "plan", "plan"),
+ new ColumnImpl("explain", "statement", "statement")
+ };
ResultRowImpl r = new ResultRowImpl(this,
Tree.EMPTY_ARRAY,
- new PropertyValue[] { PropertyValues.newString(plan)},
+ new PropertyValue[] {
+ PropertyValues.newString(plan),
+ // retrieve the original statement from either of the unioned subqueries, i.e., the left one
+ PropertyValues.newString(left.getStatement().replaceFirst("(?i)\\bexplain\\s+", ""))
+ },
null, null);
return Arrays.asList(r).iterator();
}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java?rev=1866903&r1=1866902&r2=1866903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java Fri Sep 13 12:53:26 2019
@@ -261,12 +261,13 @@ public abstract class AbstractQueryTest
List<String> lines = new ArrayList<String>();
try {
Result result = executeQuery(query, language, NO_BINDINGS);
- for (ResultRow row : result.getRows()) {
- String r = readRow(row, pathsOnly);
- if (query.startsWith("explain ")) {
- r = formatPlan(r);
+ if (query.startsWith("explain ")) {
+ lines.add(formatPlan(readPlan(result.getRows().iterator().next())));
+ } else {
+ for (ResultRow row : result.getRows()) {
+ String r = readRow(row, pathsOnly);
+ lines.add(r);
}
- lines.add(r);
}
if (!query.contains("order by") && !skipSort) {
Collections.sort(lines);
@@ -364,6 +365,10 @@ public abstract class AbstractQueryTest
return buff.toString();
}
+ protected static String readPlan(ResultRow row) {
+ return row.getValue("plan").getValue(Type.STRING);
+ }
+
/**
* Check whether the test is running in debug mode.
*
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ExplainResultTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ExplainResultTest.java?rev=1866903&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ExplainResultTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ExplainResultTest.java Fri Sep 13 12:53:26 2019
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.jackrabbit.oak.InitialContentHelper;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.junit.Test;
+
+public class ExplainResultTest extends AbstractQueryTest {
+
+ @Override
+ protected ContentRepository createRepository() {
+ return new Oak(new MemoryNodeStore(InitialContentHelper.INITIAL_CONTENT))
+ .with(new OpenSecurityProvider())
+ .with(new PropertyIndexProvider())
+ .with(new PropertyIndexEditorProvider()).createContentRepository();
+ }
+
+ @Test
+ public void test_explain_xpath() throws Exception {
+ test("explain_result.txt");
+ final String xpath = "/jcr:root/test//*";
+ Result result = executeQuery(xpath, "xpath", Collections.emptyMap());
+ int count = 0;
+ for (Iterator<? extends ResultRow> rows = result.getRows().iterator(); rows.hasNext(); ) {
+ rows.next();
+ count = count + 1;
+ }
+
+ assertEquals("should exist 2 nodes", 2, count);
+
+ Result explainResult = executeQuery("explain " + xpath,
+ "xpath", Collections.emptyMap());
+ int explainCount = 0;
+ ResultRow explainRow = null;
+ for (ResultRow row : explainResult.getRows()) {
+ if (explainCount == 0) {
+ explainRow = row;
+ }
+ explainCount = explainCount + 1;
+ }
+
+ assertEquals("should exist 1 result", 1, explainCount);
+ assertNotNull("explain row should not be null", explainRow);
+
+ assertTrue("result should have 'plan' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("plan"));
+ assertTrue("result should have 'statement' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("statement"));
+
+ final String explainedStatement = explainRow.getValue("statement").getValue(Type.STRING);
+ assertTrue("'statement' should begin with 'select'", explainedStatement.startsWith("select"));
+ assertTrue("statement should contain original xpath with prefix 'xpath: '",
+ explainedStatement.contains("xpath: " + xpath));
+ }
+
+ @Test
+ public void test_explain_sql1() throws Exception {
+ test("explain_result.txt");
+ final String sql1 = "select [jcr:path] from [nt:base] as a where isdescendantnode(a, '/test')";
+ Result result = executeQuery(sql1, "sql", Collections.emptyMap());
+ int count = 0;
+ for (ResultRow row : result.getRows()) {
+ count = count + 1;
+ }
+
+ assertEquals("should exist 2 nodes", 2, count);
+
+ Result explainResult = executeQuery("explain " + sql1,
+ "sql", Collections.emptyMap());
+ int explainCount = 0;
+ ResultRow explainRow = null;
+ for (ResultRow row : explainResult.getRows()) {
+ if (explainCount == 0) {
+ explainRow = row;
+ }
+ explainCount = explainCount + 1;
+ }
+
+ assertEquals("should exist 1 result", 1, explainCount);
+ assertNotNull("explain row should not be null", explainRow);
+
+ assertTrue("result should have 'plan' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("plan"));
+ assertTrue("result should have 'statement' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("statement"));
+
+ final String explainedStatement = explainRow.getValue("statement").getValue(Type.STRING);
+ assertTrue("'statement' should begin with 'select'", explainedStatement.startsWith("select"));
+ assertEquals("explained statement should be same as original, without 'explain'",
+ sql1, explainedStatement);
+ }
+
+ @Test
+ public void test_explain_sql2() throws Exception {
+ test("explain_result.txt");
+ final String sql2 = "select [jcr:path] from [nt:base] as a where isdescendantnode(a, '/test')";
+ Result result = executeQuery(sql2, "JCR-SQL2", Collections.emptyMap());
+ int count = 0;
+ for (ResultRow row : result.getRows()) {
+ count = count + 1;
+ }
+
+ assertEquals("should exist 2 nodes", 2, count);
+
+ Result explainResult = executeQuery("explain " + sql2,
+ "JCR-SQL2", Collections.emptyMap());
+ int explainCount = 0;
+ ResultRow explainRow = null;
+ for (ResultRow row : explainResult.getRows()) {
+ if (explainCount == 0) {
+ explainRow = row;
+ }
+ explainCount = explainCount + 1;
+ }
+
+ assertEquals("should exist 1 result", 1, explainCount);
+ assertNotNull("explain row should not be null", explainRow);
+
+ assertTrue("result should have 'plan' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("plan"));
+ assertTrue("result should have 'statement' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("statement"));
+
+ final String explainedStatement = explainRow.getValue("statement").getValue(Type.STRING);
+ assertTrue("'statement' should begin with 'select'", explainedStatement.startsWith("select"));
+ assertEquals("explained statement should be same as original, without 'explain'",
+ sql2, explainedStatement);
+ }
+}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/UnionQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/UnionQueryTest.java?rev=1866903&r1=1866902&r2=1866903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/UnionQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/UnionQueryTest.java Fri Sep 13 12:53:26 2019
@@ -17,23 +17,28 @@
package org.apache.jackrabbit.oak.query;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+
import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.InitialContent;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.api.QueryEngine;
import org.apache.jackrabbit.oak.api.Result;
import org.apache.jackrabbit.oak.api.ResultRow;
import org.apache.jackrabbit.oak.api.Tree;
-import org.apache.jackrabbit.oak.InitialContent;
+import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-
public class UnionQueryTest extends AbstractQueryTest {
@Override
@@ -89,6 +94,112 @@ public class UnionQueryTest extends Abst
QueryEngine.NO_BINDINGS, QueryEngine.NO_MAPPINGS);
List<ResultRow> rows = Lists.newArrayList(result.getRows());
+ assertEquals(expected.length, rows.size());
+
+ int i = 0;
+ for (ResultRow rr: result.getRows()) {
+ assertEquals(rr.getPath(), expected[i++]);
+ }
+ }
+
+ @Test
+ public void testExplainStatement() throws Exception {
+ final String left = "SELECT [jcr:path] FROM [nt:base] AS a WHERE ISDESCENDANTNODE(a, '/UnionQueryTest')";
+ final String right = "SELECT [jcr:path] FROM [nt:base] AS a WHERE ISDESCENDANTNODE(a, '/UnionQueryTest')";
+ final String order = "ORDER BY [jcr:path]";
+ final String union = String.format("%s UNION %s %s", left, right, order);
+ final String explainUnion = "explain " + union;
+
+ Result explainResult = executeQuery(explainUnion, QueryEngineImpl.SQL2, QueryEngine.NO_BINDINGS);
+
+ int explainCount = 0;
+ ResultRow explainRow = null;
+ for (ResultRow row : explainResult.getRows()) {
+ if (explainCount == 0) {
+ explainRow = row;
+ }
+ explainCount = explainCount + 1;
+ }
+
+ assertEquals("should exist 1 result", 1, explainCount);
+ assertNotNull("explain row should not be null", explainRow);
+
+ assertTrue("result should have 'plan' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("plan"));
+ assertTrue("result should have 'statement' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("statement"));
+
+ final String explainedStatement = explainRow.getValue("statement").getValue(Type.STRING);
+
+ assertTrue("'statement' should begin with 'select': " + explainedStatement,
+ explainedStatement.startsWith("SELECT"));
+ assertTrue("'statement' should contain ' UNION ': " + explainedStatement,
+ explainedStatement.contains(" UNION "));
+
+ final int limit = 3;
+ final int offset = 2;
+
+ String[] expected = {
+ "/UnionQueryTest/a/b/c",
+ "/UnionQueryTest/a/b/c/d",
+ "/UnionQueryTest/a/b/c/d/e"
+ };
+
+ Result result = qe.executeQuery(explainedStatement, QueryEngineImpl.SQL2, limit, offset,
+ QueryEngine.NO_BINDINGS, QueryEngine.NO_MAPPINGS);
+
+ List<ResultRow> rows = Lists.newArrayList(result.getRows());
+ assertEquals(expected.length, rows.size());
+
+ int i = 0;
+ for (ResultRow rr: result.getRows()) {
+ assertEquals(rr.getPath(), expected[i++]);
+ }
+ }
+
+ @Test
+ public void testExplainStatementXPath() throws Exception {
+ final String xpath = "/jcr:root/UnionQueryTest//(element(*, nt:base) | element(*, nt:folder)) order by jcr:path";
+ final String explainUnion = "explain " + xpath;
+
+ Result explainResult = executeQuery(explainUnion, QueryEngineImpl.XPATH, QueryEngine.NO_BINDINGS);
+
+ int explainCount = 0;
+ ResultRow explainRow = null;
+ for (ResultRow row : explainResult.getRows()) {
+ if (explainCount == 0) {
+ explainRow = row;
+ }
+ explainCount = explainCount + 1;
+ }
+
+ assertEquals("should exist 1 result", 1, explainCount);
+ assertNotNull("explain row should not be null", explainRow);
+
+ assertTrue("result should have 'plan' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("plan"));
+ assertTrue("result should have 'statement' column",
+ Arrays.asList(explainResult.getColumnNames()).contains("statement"));
+
+ final String explainedStatement = explainRow.getValue("statement").getValue(Type.STRING);
+ assertTrue("'statement' should begin with 'select': " + explainedStatement,
+ explainedStatement.startsWith("select"));
+ assertTrue("'statement' should contain ' union ': " + explainedStatement,
+ explainedStatement.contains(" union "));
+
+ final int limit = 3;
+ final int offset = 2;
+
+ String[] expected = {
+ "/UnionQueryTest/a/b/c",
+ "/UnionQueryTest/a/b/c/d",
+ "/UnionQueryTest/a/b/c/d/e"
+ };
+
+ Result result = qe.executeQuery(explainedStatement, QueryEngineImpl.SQL2, limit, offset,
+ QueryEngine.NO_BINDINGS, QueryEngine.NO_MAPPINGS);
+
+ List<ResultRow> rows = Lists.newArrayList(result.getRows());
assertEquals(expected.length, rows.size());
int i = 0;
Added: jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/explain_result.txt
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/explain_result.txt?rev=1866903&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/explain_result.txt (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/explain_result.txt Fri Sep 13 12:53:26 2019
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Syntax:
+# * lines that start with spaces belong to the previous line
+# * lines starting with "#" are remarks.
+# * lines starting with "select" are queries, followed by expected results and an empty line
+# * lines starting with "explain" are followed by expected query plan and an empty line
+# * lines starting with "sql1" are run using the sql1 language
+# * lines starting with "xpath2sql" are just converted
+ from xpath to sql2
+# * all other lines are are committed into the microkernel (line by line)
+# * new tests are typically be added on top, after the syntax docs
+# * use ascii character only
+
+commit / + "test": { "a": { "name": "Hello" }, "b": { "name" : "World" }}
Modified: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java?rev=1866903&r1=1866902&r2=1866903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java Fri Sep 13 12:53:26 2019
@@ -29,6 +29,7 @@ import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
@@ -74,7 +75,8 @@ public class QueryPlanTest extends Abstr
result = q.execute();
it = result.getRows();
assertTrue(it.hasNext());
- String plan = it.nextRow().getValue("plan").getString();
+ Row row = it.nextRow();
+ String plan = row.getValue("plan").getString();
// System.out.println("plan: " + plan);
// should use the node type index
assertEquals("[oak:Unstructured] as [a] " +
@@ -85,6 +87,12 @@ public class QueryPlanTest extends Abstr
", path=//*) where isdescendantnode([a], [/]) */",
plan);
+ String sql2 = row.getValue("statement").getString();
+ assertEquals("select [jcr:path], [jcr:score], * " +
+ "from [oak:Unstructured] as a " +
+ "where isdescendantnode(a, '/') " +
+ "/* xpath: /jcr:root//element(*, oak:Unstructured) */", sql2);
+
String xpath2 = "/jcr:root//element(*, oak:Unstructured)[@jcr:uuid]";
q = qm.createQuery("explain " + xpath2 + "", "xpath");
result = q.execute();