Author: rhauch
Date: 2010-01-08 00:45:17 -0500 (Fri, 08 Jan 2010)
New Revision: 1556
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceAliases.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteAsRangeCriteria.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryQueryManager.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java
Log:
DNA-613 Found several minor issues that all conspired to make this functionality not
work.
1) The XPathToQueryTranslator was incorrectly translating queries that used wildcards in
the path.
2) Queries that used aliases in columns, criteria, or order-by clauses were not always
being resolved correctly, resulting in the inability to locate the correct column (when
constraints or order-by clauses had to be evaluated after the fact.
3) The optimizer was not always pushing SELECT nodes (e.g., criteria) down below ACCESS
nodes. The rule to do this was not included by default and was instead added to the rule
stack only when criteria were encountered. This was changed to simply always add the rule
to the stack.
4) Columns used in constraints or order-by clauses were not always being included in the
PROJECT columns. Thus it was possible that the evaluation of the criteria or order-by
required data that was not in the tuples, causing an error. Note that these PROJECT
columns dictate which values are included in the tuples during processing, and all of
these are not necessarily included in the result columns.
5) The LuceneSearchEngine was incorrectly mapping paths into criteria values that could be
applied to Lucene. If the user specified a criteria such as
PATH(table) LIKE '/Cars/Hybrid/%'
the /Cars/Hybrid/% value was used directly as the LIKE expression. But paths are stored in
the indexes so that the SNS index is always included, and thus the LIKE expressions never
evaluated to return any documents. Since the SNS index in the path might include the
wildcard characters (e.g., '[%]' or '[_]'), we couldn't use the
standard PathFactory to convert the LIKE expression value into a Path. The solution was
to just use String manipulation to insert a '[1]' at the end of each segment that
didn't already include a SNS index.
6) Lucene 'read past EOF' errors; see DNA-630.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2010-01-07 22:24:56 UTC
(rev 1555)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2010-01-08 05:45:17 UTC
(rev 1556)
@@ -2666,7 +2666,8 @@
msg,
accessNode.getString(),
accessRequest.workspace(),
- graphSourceName);
+ graphSourceName,
+
accessRequest.getError().getLocalizedMessage());
return emptyTuples();
}
return accessRequest.getTuples();
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java 2010-01-07
22:24:56 UTC (rev 1555)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -134,6 +134,17 @@
}
/**
+ * Create a copy of this Column except that uses the supplied selector name instead.
+ *
+ * @param newSelectorName the new selector name
+ * @return a new Column with the supplied selector name and the property and column
names from this object; never null
+ * @throws IllegalArgumentException if the supplied selector name is null
+ */
+ public Column with( SelectorName newSelectorName ) {
+ return new Column(newSelectorName, propertyName, columnName);
+ }
+
+ /**
* {@inheritDoc}
*
* @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -89,6 +89,11 @@
}
child.insertAsParent(project);
if (plan == access) return plan;
+
+ // We need to make sure we have all of the columns needed for any
ancestor ...
+ List<Column> requiredColumns =
PlanUtil.findRequiredColumns(context, project);
+ project.setProperty(Property.PROJECT_COLUMNS, requiredColumns);
+ project.addSelectors(getSelectorsFor(requiredColumns));
continue;
}
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceAliases.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceAliases.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceAliases.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -0,0 +1,88 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.optimize;
+
+import java.util.LinkedList;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that changes any nodes that make use of an
alias for a SOURCE, including columns,
+ * including criteria, project nodes, etc. This behavior is similar to what {@link
ReplaceViews} does with views that are given
+ * aliases.
+ */
+@Immutable
+public class ReplaceAliases implements OptimizerRule {
+
+ public static final ReplaceAliases INSTANCE = new ReplaceAliases();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+ */
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+ // For each of the SOURCE nodes ...
+ Schemata schemata = context.getSchemata();
+ for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) {
+
+ // Resolve the node to find the definition in the schemata ...
+ SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME,
SelectorName.class);
+ SelectorName tableAlias = sourceNode.getProperty(Property.SOURCE_ALIAS,
SelectorName.class);
+ if (tableAlias != null) {
+ Table table = schemata.getTable(tableName);
+ // We also need to replace references to the alias for the view ...
+ PlanUtil.ColumnMapping aliasMappings =
PlanUtil.createMappingForAliased(tableAlias, table, sourceNode);
+ // Adjust the plan nodes above the SOURCE node ...
+ PlanUtil.replaceViewReferences(context, sourceNode.getParent(),
aliasMappings);
+ // And adjust the SOURCE node ...
+ sourceNode.removeProperty(Property.SOURCE_ALIAS);
+ sourceNode.getSelectors().remove(tableAlias);
+ sourceNode.addSelector(tableName);
+ }
+ }
+ return plan;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceAliases.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -149,6 +149,7 @@
// is completely done ...
if (!(ruleStack.getFirst() instanceof RaiseSelectCriteria)) {
ruleStack.addFirst(RaiseSelectCriteria.INSTANCE);
+ ruleStack.addFirst(PushSelectCriteria.INSTANCE);
}
// We re-wrote at least one SOURCE, but the resulting plan tree for the view
could actually reference
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteAsRangeCriteria.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteAsRangeCriteria.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteAsRangeCriteria.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -165,6 +165,16 @@
}
/**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ /**
* Rewrite the supplied comparisons, returning the new constraint and nulling in the
supplied list those comparisons that were
* rewritten (and leaving those that were not rewritten)
*
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -69,15 +69,14 @@
*/
protected void populateRuleStack( LinkedList<OptimizerRule> ruleStack,
PlanHints hints ) {
+ ruleStack.addFirst(ReplaceAliases.INSTANCE);
ruleStack.addFirst(RewriteAsRangeCriteria.INSTANCE);
if (hints.hasJoin) {
ruleStack.addFirst(ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM);
ruleStack.addFirst(RewriteIdentityJoins.INSTANCE);
}
ruleStack.addFirst(PushProjects.INSTANCE);
- if (hints.hasCriteria) {
- ruleStack.addFirst(PushSelectCriteria.INSTANCE);
- }
+ ruleStack.addFirst(PushSelectCriteria.INSTANCE);
ruleStack.addFirst(AddAccessNodes.INSTANCE);
ruleStack.addFirst(RightOuterToLeftOuterJoins.INSTANCE);
ruleStack.addFirst(CopyCriteria.INSTANCE);
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2010-01-07
22:24:56 UTC (rev 1555)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -69,6 +69,7 @@
import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
import org.jboss.dna.graph.query.plan.PlanNode.Property;
import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
import org.jboss.dna.graph.query.validate.Schemata.View;
/**
@@ -98,13 +99,11 @@
PlanNode planNode ) {
List<Column> columns = null;
PlanNode node = planNode;
- PlanNode projectNode = null;
// First find the columns from the nearest PROJECT ancestor ...
do {
switch (node.getType()) {
case PROJECT:
columns = node.getPropertyAsList(Property.PROJECT_COLUMNS,
Column.class);
- projectNode = node;
node = null;
break;
default:
@@ -113,14 +112,12 @@
}
} while (node != null);
- // If the supplied node is a PROJECT, just return the PROJECT node's column
list ...
- if (projectNode == planNode) return columns;
-
// Find the names of the selectors ...
Set<SelectorName> names = new HashSet<SelectorName>();
for (PlanNode source : planNode.findAllAtOrBelow(Type.SOURCE)) {
names.add(source.getProperty(Property.SOURCE_NAME, SelectorName.class));
- names.add(source.getProperty(Property.SOURCE_ALIAS, SelectorName.class));
+ SelectorName alias = source.getProperty(Property.SOURCE_ALIAS,
SelectorName.class);
+ if (alias != null) names.add(alias);
}
// Add the PROJECT columns first ...
@@ -131,7 +128,7 @@
}
}
- // Now add the columns from the JOIN and SELECT ancestors ...
+ // Now add the columns from the JOIN, SELECT, PROJECT and SORT ancestors ...
node = planNode;
do {
switch (node.getType()) {
@@ -145,9 +142,23 @@
criteria = node.getProperty(Property.SELECT_CRITERIA,
Constraint.class);
Visitors.visitAll(criteria, collectionVisitor);
break;
+ case SORT:
+ List<Object> orderBys =
node.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
+ if (orderBys != null && !orderBys.isEmpty()) {
+ if (orderBys.get(0) instanceof Ordering) {
+ for (int i = 0; i != orderBys.size(); ++i) {
+ Ordering ordering = (Ordering)orderBys.get(i);
+ Visitors.visitAll(ordering, collectionVisitor);
+ }
+ }
+ }
+ break;
case PROJECT:
- // Already handled above, but we can stop looking for columns ...
- return collectionVisitor.getRequiredColumns();
+ if (node != planNode) {
+ // Already handled above, but we can stop looking for columns
...
+ return collectionVisitor.getRequiredColumns();
+ }
+ break;
default:
break;
}
@@ -203,16 +214,28 @@
*/
@Override
public void visit( Column column ) {
- requireColumn(column.getSelectorName(), column.getPropertyName());
+ requireColumn(column.getSelectorName(), column.getPropertyName(),
column.getColumnName());
}
protected void requireColumn( SelectorName selector,
String propertyName ) {
+ requireColumn(selector, propertyName, null);
+ }
+
+ protected void requireColumn( SelectorName selector,
+ String propertyName,
+ String alias ) {
if (names.contains(selector)) {
// The column is part of the table we're interested in ...
- if (requiredColumnNames.add(propertyName)) {
- String alias = propertyName;
- columns.add(new Column(selector, propertyName, alias));
+ if (alias != null && !alias.equals(propertyName)) {
+ if (requiredColumnNames.add(propertyName) &&
requiredColumnNames.add(alias)) {
+ columns.add(new Column(selector, propertyName, alias));
+ }
+ } else {
+ if (requiredColumnNames.add(propertyName)) {
+ alias = propertyName;
+ columns.add(new Column(selector, propertyName, alias));
+ }
}
}
}
@@ -558,7 +581,10 @@
}
break;
case ACCESS:
- // Nothing to do here, as the selector names are fixed elsewhere
+ // Add all the selectors used by the subnodes ...
+ for (PlanNode child : node) {
+ node.addSelectors(child.getSelectors());
+ }
break;
case SORT:
// The selector names and Ordering objects needs to be changed
...
@@ -886,6 +912,31 @@
return mapping;
}
+ public static ColumnMapping createMappingForAliased( SelectorName tableAlias,
+ Table table,
+ PlanNode tableSourceNode ) {
+ ColumnMapping mapping = new ColumnMapping(tableAlias);
+
+ // Find the projected columns on the nearest PROJECT ...
+ PlanNode project = tableSourceNode.findAncestor(Type.PROJECT);
+ assert project != null;
+
+ // Get the Columns from the PROJECT in the plan node ...
+ List<Column> projectedColumns =
project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+
+ for (int i = 0; i != projectedColumns.size(); ++i) {
+ Column projectedColumn = projectedColumns.get(i);
+ Column projectedColumnInTable =
projectedColumns.get(i).with(table.getName());
+ org.jboss.dna.graph.query.validate.Schemata.Column column =
table.getColumn(projectedColumnInTable.getPropertyName());
+ mapping.map(column.getName(), projectedColumnInTable);
+ if (projectedColumn.getColumnName() != null) {
+ // The projected column has an alias, so add a mapping for it, too
+ mapping.map(projectedColumn.getColumnName(), projectedColumnInTable);
+ }
+ }
+ return mapping;
+ }
+
/**
* Defines how the view columns are mapped (or resolved) into the columns from the
source tables.
*/
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Validator.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -49,6 +49,7 @@
import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.PropertyExistence;
import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.Query;
import org.jboss.dna.graph.query.model.SameNode;
import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.SelectorName;
@@ -66,6 +67,7 @@
private final Problems problems;
private final Map<SelectorName, Table> selectorsByNameOrAlias;
private final Map<SelectorName, Table> selectorsByName;
+ private final Map<String, Schemata.Column> columnsByAlias;
/**
* @param context the query context
@@ -80,6 +82,7 @@
for (Table table : selectorsByName.values()) {
this.selectorsByName.put(table.getName(), table);
}
+ this.columnsByAlias = new HashMap<String, Schemata.Column>();
}
/**
@@ -340,6 +343,28 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.Query)
+ */
+ @Override
+ public void visit( Query obj ) {
+ // Collect the map of columns by alias for this query ...
+ this.columnsByAlias.clear();
+ for (Column column : obj.getColumns()) {
+ // Find the schemata column ...
+ Table table = tableWithNameOrAlias(column.getSelectorName());
+ if (table != null) {
+ Schemata.Column tableColumn = table.getColumn(column.getPropertyName());
+ if (tableColumn != null) {
+ this.columnsByAlias.put(column.getColumnName(), tableColumn);
+ }
+ }
+ }
+ super.visit(obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.SameNode)
*/
@Override
@@ -392,7 +417,11 @@
}
Schemata.Column column = table.getColumn(propertyName);
if (column == null) {
- problems.addError(GraphI18n.columnDoesNotExistOnTable, propertyName,
selectorName.getName());
+ // Maybe the supplied property name is really an alias ...
+ column = this.columnsByAlias.get(propertyName);
+ if (column == null) {
+ problems.addError(GraphI18n.columnDoesNotExistOnTable, propertyName,
selectorName.getName());
+ }
}
return column;
}
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -74,7 +74,7 @@
private List<Integer> ruleExecutionOrder;
private QueryContext context;
private PlanNode node;
- private final boolean print = false;
+ private boolean print;
@Before
public void beforeEach() {
@@ -124,6 +124,7 @@
ruleStack.addAll(rules);
}
};
+ print = false;
}
//
----------------------------------------------------------------------------------------------------------------
@@ -193,12 +194,11 @@
public void shouldOptimizePlanForSimpleQueryWithSelectStarWithAlias() {
node = optimize("SELECT * FROM t1 AS x1");
// Create the expected plan ...
- PlanNode expected = new PlanNode(Type.ACCESS, selector("x1"));
- PlanNode project = new PlanNode(Type.PROJECT, expected,
selector("x1"));
- project.setProperty(Property.PROJECT_COLUMNS, columns(column("x1",
"c11"), column("x1", "c12"), column("x1",
"c13")));
- PlanNode source = new PlanNode(Type.SOURCE, project, selector("x1"));
+ PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, expected,
selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11"), column("t1", "c12"), column("t1",
"c13")));
+ PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
- source.setProperty(Property.SOURCE_ALIAS, selector("x1"));
source.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
// Compare the expected and actual plan ...
assertThat(node.isSameAs(expected), is(true));
@@ -208,15 +208,14 @@
public void
shouldOptimizePlanForSimpleQueryWithSelectStarFromTableWithAliasAndValueCriteria() {
node = optimize("SELECT * FROM t1 AS x1 WHERE c13 < CAST('3' AS
LONG)");
// Create the expected plan ...
- PlanNode expected = new PlanNode(Type.ACCESS, selector("x1"));
- PlanNode project = new PlanNode(Type.PROJECT, expected,
selector("x1"));
- project.setProperty(Property.PROJECT_COLUMNS, columns(column("x1",
"c11"), column("x1", "c12"), column("x1",
"c13")));
- PlanNode select = new PlanNode(Type.SELECT, project, selector("x1"));
- select.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("x1"), "c13"), Operator.LESS_THAN,
+ PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+ PlanNode project = new PlanNode(Type.PROJECT, expected,
selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11"), column("t1", "c12"), column("t1",
"c13")));
+ PlanNode select = new PlanNode(Type.SELECT, project, selector("t1"));
+ select.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c13"), Operator.LESS_THAN,
new Literal(3L)));
- PlanNode source = new PlanNode(Type.SOURCE, select, selector("x1"));
+ PlanNode source = new PlanNode(Type.SOURCE, select, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
- source.setProperty(Property.SOURCE_ALIAS, selector("x1"));
source.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
// Compare the expected and actual plan ...
assertThat(node.isSameAs(expected), is(true));
@@ -348,7 +347,7 @@
PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
- leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11")));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
Operator.EQUAL_TO, new Literal('x')));
@@ -451,7 +450,7 @@
PlanNode leftAccess = new PlanNode(Type.ACCESS, join,
selector("all"));
PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("all"));
- leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("all",
"a1"), column("all", "a2")));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("all",
"a1"), column("all", "a2"), column("all",
"a3")));
PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("all"));
leftSelect1.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new
PropertyValue(selector("all"), "primaryType"),
new
Literal("t1"), new Literal("t0")));
@@ -464,7 +463,7 @@
PlanNode rightAccess = new PlanNode(Type.ACCESS, join,
selector("all"));
PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess,
selector("all"));
- rightProject.setProperty(Property.PROJECT_COLUMNS,
columns(column("all", "a3"), column("all",
"a4")));
+ rightProject.setProperty(Property.PROJECT_COLUMNS,
columns(column("all", "a3"), column("all", "a4"),
column("all", "a2")));
PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject,
selector("all"));
rightSelect1.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new
PropertyValue(selector("all"), "primaryType"),
new
Literal("t2"), new Literal("t0")));
@@ -514,7 +513,83 @@
}
@Test
- public void shouldOptimizePlanForQueryWithOrderByClause() {
+ public void shouldOptimizePlanForQueryUsingTableAndOrderByClause() {
+ print = true;
+ node = optimize("SELECT t1.c11 AS c1 FROM t1 WHERE t1.c11 = 'x' AND
t1.c12 = 'y' ORDER BY t1.c11, t1.c12 DESC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1",
"c11"), descending("t1", "c12")));
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1"), column("t1", "c12")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForQueryUsingTableWithAliasAndOrderByClause() {
+ print = true;
+ node = optimize("SELECT X.c11 AS c1 FROM t1 AS X WHERE X.c11 = 'x'
AND X.c12 = 'y' ORDER BY X.c11, X.c12 DESC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1",
"c11"), descending("t1", "c12")));
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1"), column("t1", "c12")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
+ @Test
+ public void
shouldOptimizePlanForQueryUsingTableWithAliasAndOrderByClauseUsingAliasedColumn() {
+ print = true;
+ node = optimize("SELECT X.c11 AS c1 FROM t1 AS X WHERE X.c11 = 'x'
AND X.c12 = 'y' ORDER BY X.c1, X.c12 DESC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1",
"c11"), descending("t1", "c12")));
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1"), column("t1", "c12")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
+ @Test
+ public void shouldOptimizePlanForQueryUsingViewAndOrderByClause() {
+ print = true;
node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND
v2.c12 = 'y' ORDER BY v2.c11, v2.c12 DESC");
// Create the expected plan ...
@@ -529,7 +604,7 @@
PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
- leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11")));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
Operator.EQUAL_TO, new Literal("x")));
@@ -555,6 +630,48 @@
}
@Test
+ public void shouldOptimizePlanForQueryUsingViewWithAliasAndOrderByClause() {
+ print = true;
+ node = optimize("SELECT Q.c11 AS c1 FROM v2 AS Q WHERE Q.c11 = 'x'
AND Q.c12 = 'y' ORDER BY Q.c11, Q.c12 DESC");
+
+ // Create the expected plan ...
+ PlanNode sort = new PlanNode(Type.SORT, selector("t1"));
+ sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1",
"c11"), descending("t1", "c12")));
+ PlanNode project = new PlanNode(Type.PROJECT, sort, selector("t1"));
+ project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
+ PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"),
selector("t1"));
+ join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ join.setProperty(Property.JOIN_CONDITION, new
EquiJoinCondition(selector("t1"), "c11", selector("t2"),
"c21"));
+
+ PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+ PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
+ PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
+ leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1,
selector("t1"));
+ leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c12"),
+
Operator.EQUAL_TO, new Literal("y")));
+ PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2,
selector("t1"));
+ leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+ leftSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t1")).getColumns());
+
+ PlanNode rightAccess = new PlanNode(Type.ACCESS, join,
selector("t2"));
+ PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess,
selector("t2"));
+ rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2",
"c21")));
+ PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject,
selector("t2"));
+ rightSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t2"), "c21"),
+
Operator.EQUAL_TO, new Literal("x")));
+ PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect1,
selector("t2"));
+ rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+ rightSource.setProperty(Property.SOURCE_COLUMNS,
context.getSchemata().getTable(selector("t2")).getColumns());
+
+ // Compare the expected and actual plan ...
+ assertThat(node.isSameAs(sort), is(true));
+ }
+
+ @Test
public void shouldOptimizePlanForQueryWithOrderByClauseThatUsesScoreFunction() {
node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND
v2.c12 = 'y' ORDER BY SCORE(v2) ASC");
@@ -570,7 +687,7 @@
PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess,
selector("t1"));
- leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11")));
+ leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1",
"c11", "c1")));
PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject,
selector("t1"));
leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new
PropertyValue(selector("t1"), "c11"),
Operator.EQUAL_TO, new Literal("x")));
@@ -656,9 +773,9 @@
PlanNode optimized = new RuleBasedOptimizer().optimize(context, plan);
assertThat("Problems optimizing query: " + sql + "\n" +
problems, problems.hasErrors(), is(false));
if (print) {
- System.out.println();
System.out.println(sql);
System.out.println(optimized);
+ System.out.println();
}
return optimized;
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryQueryManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryQueryManager.java 2010-01-07
22:24:56 UTC (rev 1555)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryQueryManager.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -494,7 +494,8 @@
msg,
accessNode.getString(),
accessRequest.workspace(),
- sourceName);
+ sourceName,
+
accessRequest.getError().getLocalizedMessage());
return emptyTuples();
}
return accessRequest.getTuples();
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java
===================================================================
---
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslator.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -24,9 +24,11 @@
package org.jboss.dna.jcr.xpath;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -157,12 +159,16 @@
AxisStep axis = (AxisStep)step;
NodeTest nodeTest = axis.getNodeTest();
if (nodeTest instanceof NameTest) {
- NameTest nameTest = (NameTest)nodeTest;
- if (!nameTest.isWildcard()) {
+ if (appliesToPathConstraint(axis.getPredicates())) {
+ // Everything in this axis/step applies to the path, so add it
and we'll translate it below ...
path.add(step);
- }
- if (!appliesToPathConstraint(axis.getPredicates())) {
- // The constraints are more complicated, so we need to define a
new source/table ...
+ } else {
+ // The constraints are more complicated than can be applied to
the path ...
+ // if (!nameTest.isWildcard()) {
+ // There is a non-wildcard name test that we still need to add to
the path ...
+ path.add(step);
+ // }
+ // We need to define a new source/table ...
tableName = translateSource(tableName, path, where);
translatePredicates(axis.getPredicates(), tableName, where);
path.clear();
@@ -464,6 +470,9 @@
// This adds the criteria that the attribute exists, and adds it to the
select ...
AttributeNameTest attribute = (AttributeNameTest)predicate;
String propertyName = nameFrom(attribute.getNameTest());
+ // There is nothing in the JCR 1.0 spec that says that a property constrain
implies it should be included in the
+ // result columns
+ // builder.select(tableName + "." + propertyName);
where.hasProperty(tableName, propertyName);
} else if (predicate instanceof NameTest) {
// This adds the criteria that the child node exists ...
@@ -679,17 +688,28 @@
protected boolean translatePathExpressionConstraint( PathExpression pathExrp,
ConstraintBuilder where,
String tableName ) {
- String[] paths = relativePathLikeExpressions(pathExrp);
- if (paths == null || paths.length == 0) return false;
+ RelativePathLikeExpressions expressions = relativePathLikeExpressions(pathExrp);
+ if (expressions.isEmpty()) return false;
where = where.openParen();
boolean first = true;
int number = 0;
- for (String path : paths) {
- if (path == null || path.length() == 0 || path.equals("%/"))
continue;
+ for (String path : expressions) {
+ if (path == null || path.length() == 0 || path.equals("%/") ||
path.equals("%/%") || path.equals("%//%")) continue;
if (first) first = false;
else where.or();
if (path.indexOf('%') != -1) {
where.path(tableName).isLike(path);
+ switch (expressions.depthMode) {
+ case AT_LEAST:
+
where.and().depth(tableName).isGreaterThanOrEqualTo().cast(expressions.depth).asLong();
+ break;
+ case EXACT:
+
where.and().depth(tableName).isEqualTo().cast(expressions.depth).asLong();
+ break;
+ case DEFAULT:
+ // don't have to add the DEPTH criteria ...
+ break;
+ }
} else {
where.path(tableName).isEqualTo(path);
}
@@ -699,19 +719,59 @@
return true;
}
- protected String[] relativePathLikeExpressions( PathExpression pathExpression ) {
+ protected static enum DepthMode {
+ DEFAULT,
+ EXACT,
+ AT_LEAST;
+ }
+
+ protected static class RelativePathLikeExpressions implements Iterable<String>
{
+ protected final List<String> paths;
+ protected final int depth;
+ protected final DepthMode depthMode;
+
+ protected RelativePathLikeExpressions() {
+ this.paths = null;
+ this.depth = 0;
+ this.depthMode = DepthMode.DEFAULT;
+ }
+
+ protected RelativePathLikeExpressions( String[] paths,
+ int depth,
+ DepthMode depthMode ) {
+ this.paths = Arrays.asList(paths);
+ this.depth = depth;
+ this.depthMode = depthMode;
+ }
+
+ protected boolean isEmpty() {
+ return paths == null || paths.isEmpty();
+ }
+
+ public Iterator<String> iterator() {
+ return paths.iterator();
+ }
+ }
+
+ protected RelativePathLikeExpressions relativePathLikeExpressions( PathExpression
pathExpression ) {
List<StepExpression> steps = pathExpression.getSteps();
- if (steps.isEmpty()) return new String[] {};
- if (steps.size() == 1 && steps.get(0) instanceof DescendantOrSelf) return
new String[] {};
+ if (steps.isEmpty()) return new RelativePathLikeExpressions();
+ if (steps.size() == 1 && steps.get(0) instanceof DescendantOrSelf) return
new RelativePathLikeExpressions();
PathLikeBuilder builder = new SinglePathLikeBuilder();
- for (StepExpression step : steps) {
+ int depth = 0;
+ DepthMode depthMode = DepthMode.EXACT;
+ for (Iterator<StepExpression> iterator = steps.iterator();
iterator.hasNext();) {
+ StepExpression step = iterator.next();
if (step instanceof DescendantOrSelf) {
+ ++depth;
+ depthMode = DepthMode.DEFAULT;
if (builder.isEmpty()) {
builder.append("%/");
} else {
builder = new DualPathLikeBuilder(builder.clone(),
builder.append("/%"));
}
} else if (step instanceof AxisStep) {
+ ++depth;
AxisStep axis = (AxisStep)step;
NodeTest nodeTest = axis.getNodeTest();
assert !(nodeTest instanceof ElementTest);
@@ -764,7 +824,7 @@
}
}
}
- return builder.getPaths();
+ return new RelativePathLikeExpressions(builder.getPaths(), depth, depthMode);
}
protected static interface PathLikeBuilder {
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java 2010-01-07
22:24:56 UTC (rev 1555)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrQueryManagerTest.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -42,6 +42,7 @@
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
import org.jboss.dna.graph.property.Name;
@@ -130,9 +131,9 @@
// Create a branch that contains some same-name-siblings ...
Node other = session.getRootNode().addNode("Other",
"nt:unstructured");
- other.addNode("NodeA", "nt:unstructured");
- other.addNode("NodeA", "nt:unstructured");
- other.addNode("NodeA", "nt:unstructured");
+ other.addNode("NodeA",
"nt:unstructured").setProperty("something", "value3");
+ other.addNode("NodeA",
"nt:unstructured").setProperty("something", "value2");
+ other.addNode("NodeA",
"nt:unstructured").setProperty("something", "value1");
session.save();
// Prime creating a first XPath query and SQL query ...
@@ -258,8 +259,12 @@
@Test
public void shouldBeAbleToCreateXPathQuery() throws RepositoryException {
- Query query =
session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)",
Query.XPATH);
+ Query query =
session.getWorkspace().getQueryManager().createQuery("//element(*,car:Car)",
Query.XPATH);
assertThat(query, is(notNullValue()));
+ assertResults(query, query.execute(), 12);
+
+ query =
session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)",
Query.XPATH);
+ assertThat(query, is(notNullValue()));
assertResults(query, query.execute(), 21);
}
@@ -284,18 +289,91 @@
@Test
public void
shouldBeAbleToExecuteXPathQueryToFindAllUnstructuredNodesOrderedByPropertyValue() throws
RepositoryException {
+ QueryManager manager = session.getWorkspace().getQueryManager();
+ Query query = manager.createQuery("//element(*,nt:unstructured) order by
@jcr:primaryType", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ print = true;
+ assertResults(query, result, 21);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+
+ query = manager.createQuery("//element(*,car:Car) order by @car:year",
Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ result = query.execute();
+ print = true;
+ assertResults(query, result, 12);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "car:year", "jcr:path",
"jcr:score");
+ }
+
+ @Test
+ public void shouldBeAbleToExecuteXPathQueryToFindNodesUnderNode() throws
RepositoryException {
+ Query query = session.getWorkspace().getQueryManager().createQuery("
/jcr:root/Cars/Hybrid/*", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ print = true;
+ assertResults(query, result, 3);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+ }
+
+ @Test
+ public void shouldBeAbleToExecuteXPathQueryToFindNodesUnderNodeAndWithProperty()
throws RepositoryException {
+ Query query = session.getWorkspace().getQueryManager().createQuery("
/jcr:root/Cars/Hybrid/*[@car:year]", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertResults(query, result, 3);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+ }
+
+ @Test
+ public void
shouldBeAbleToExecuteXPathQueryToFindNodesUnderNodeAndWithPropertyOrderedByProperty()
throws RepositoryException {
Query query = session.getWorkspace()
.getQueryManager()
- .createQuery("//element(*,nt:unstructured) order by
@jcr:primaryType", Query.XPATH);
+ .createQuery(" /jcr:root/Cars/Hybrid/*[@car:year] order
by @car:year ascending", Query.XPATH);
assertThat(query, is(notNullValue()));
QueryResult result = query.execute();
+ assertResults(query, result, 3);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "car:year", "jcr:path",
"jcr:score");
+ }
+
+ @Test
+ public void shouldBeAbleToExecuteXPathQueryToFindNodesUnderPath() throws
RepositoryException {
+ Query query = session.getWorkspace().getQueryManager().createQuery("
/jcr:root/Cars//*", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
print = true;
- assertResults(query, result, 21);
+ assertResults(query, result, 16);
assertThat(result, is(notNullValue()));
assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
}
@Test
+ public void shouldBeAbleToExecuteXPathQueryToFindNodesUnderPathAndWithProperty()
throws RepositoryException {
+ Query query = session.getWorkspace().getQueryManager().createQuery("
/jcr:root/Cars//*[@car:year]", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertResults(query, result, 16);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "jcr:primaryType",
"jcr:path", "jcr:score");
+ }
+
+ @Test
+ public void
shouldBeAbleToExecuteXPathQueryToFindNodesUnderPathAndWithPropertyOrderedByProperty()
throws RepositoryException {
+ Query query = session.getWorkspace()
+ .getQueryManager()
+ .createQuery(" /jcr:root/Cars//*[@car:year] order by
@car:year ascending", Query.XPATH);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertResults(query, result, 16);
+ assertThat(result, is(notNullValue()));
+ assertResultsHaveColumns(result, "car:year", "jcr:path",
"jcr:score");
+ }
+
+ @Test
public void shouldBeAbleToExecuteXPathQueryToFindAllUnstructuredNodesOrderedByScore()
throws RepositoryException {
Query query =
session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)
order by jcr:score()",
Query.XPATH);
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java 2010-01-07
22:24:56 UTC (rev 1555)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathParserTest.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -189,6 +189,23 @@
literal("3")))));
}
+ @Test
+ public void shouldParsePathExpressionWithNameTestsAndWildcard() {
+ assertThat(parser.parsePathExpr(tokenize("/jcr:root/a/b/*")),
is(pathExpr(axisStep(nameTest("jcr", "root")),
+
axisStep(nameTest("a")),
+
axisStep(nameTest("b")),
+
axisStep(wildcard()))));
+ }
+
+ @Test
+ public void shouldParsePathExpressionWithNameTestsAndWildcardAndPropertyExistence()
{
+ assertThat(parser.parsePathExpr(tokenize("/jcr:root/a/b/*[@prop]")),
+ is(pathExpr(axisStep(nameTest("jcr", "root")),
+ axisStep(nameTest("a")),
+ axisStep(nameTest("b")),
+ axisStep(wildcard(),
attributeNameTest(nameTest("prop"))))));
+ }
+
//
----------------------------------------------------------------------------------------------------------------
// Relative path expression
//
----------------------------------------------------------------------------------------------------------------
Modified:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java
===================================================================
---
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/xpath/XPathToQueryTranslatorTest.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -112,6 +112,18 @@
}
@Test
+ public void shouldTranslateFromXPathUsingNameTestsAndWildcardWithNoPredicates() {
+ assertThat(xpath("/jcr:root/testroot/*"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ as
nodeSet1 WHERE PATH(nodeSet1) LIKE '/testroot/%' AND DEPTH(nodeSet1) = CAST(2 AS
LONG)"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathUsingNameTestsAndWildcardWithPredicates() {
+ assertThat(xpath("/jcr:root/testroot/*[@prop1]"),
+ isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ as
nodeSet1 WHERE (PATH(nodeSet1) LIKE '/testroot/%' AND DEPTH(nodeSet1) = CAST(2 AS
LONG)) AND nodeSet1.prop1 IS NOT NULL"));
+ }
+
+ @Test
public void shouldTranslateFromXPathContainingPathWithDescendantOrSelf() {
assertThat(xpath("/jcr:root/a/b//c"),
isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ AS
nodeSet1 WHERE PATH(nodeSet1) = '/a/b/c' OR PATH(nodeSet1) LIKE
'/a/b/%/c'"));
@@ -272,11 +284,15 @@
}
@Test
- public void shouldTranslateFromXPathUsingOrderBy() {
+ public void shouldTranslateFromXPathUsingElementWildcardAndOrderBy() {
assertThat(xpath("//element(*,*) order by @title"),
isSql("SELECT nodeSet1.title FROM __ALLNODES__ AS nodeSet1 ORDER
BY nodeSet1.title"));
+ }
+
+ @Test
+ public void shouldTranslateFromXPathUsingNameTestsAndWildcardOrderBy() {
assertThat(xpath("/jcr:root/testroot/*[@prop1] order by @prop1
ascending"),
- isSql("SELECT nodeSet1.prop1 FROM __ALLNODES__ as nodeSet1 WHERE
PATH(nodeSet1) = '/testroot' AND nodeSet1.prop1 IS NOT NULL ORDER BY
nodeSet1.prop1"));
+ isSql("SELECT nodeSet1.prop1 FROM __ALLNODES__ as nodeSet1 WHERE
(PATH(nodeSet1) LIKE '/testroot/%' AND DEPTH(nodeSet1) = CAST(2 AS LONG)) AND
nodeSet1.prop1 IS NOT NULL ORDER BY nodeSet1.prop1"));
}
@Test
Modified:
trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java
===================================================================
---
trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java 2010-01-07
22:24:56 UTC (rev 1555)
+++
trunk/extensions/dna-search-lucene/src/main/java/org/jboss/dna/search/lucene/LuceneSearchSession.java 2010-01-08
05:45:17 UTC (rev 1556)
@@ -441,8 +441,8 @@
for (Object value : property) {
if (value == null) continue;
stringValue = processor.stringFactory.create(value);
- // Add a separate field for each property value ...
- doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
rule.getIndexOption()));
+ // Add a separate field for each property value in exact form (not
analyzed) ...
+ doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
Field.Index.NOT_ANALYZED));
if (rule.getIndexOption() != Field.Index.NO) {
// This field is to be full-text searchable ...
@@ -463,6 +463,7 @@
if (fullTextSearchValue != null && fullTextSearchValue.length() != 0) {
doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(),
Field.Store.NO, Field.Index.ANALYZED));
}
+ // System.out.println("Replaced " + doc);
getContentWriter().updateDocument(new Term(ContentIndex.PATH, pathStr), doc);
}
@@ -883,6 +884,22 @@
return new MatchNoneQuery();
}
+ protected String likeExpresionForWildcardPath( String path ) {
+ if (path.equals("/") || path.equals("%")) return path;
+ StringBuilder sb = new StringBuilder();
+ for (String segment : path.split("/")) {
+ if (segment.length() == 0) continue;
+ sb.append("/");
+ sb.append(segment);
+ if (segment.equals("%") || segment.equals("_"))
continue;
+ if (!segment.endsWith("]")) {
+ sb.append("[1]");
+ }
+ }
+ if (path.endsWith("/")) sb.append("/");
+ return sb.toString();
+ }
+
public Query findNodesWith( NodePath nodePath,
Operator operator,
Object value,
@@ -897,6 +914,7 @@
return new NotQuery(findNodeAt(pathValue));
case LIKE:
String likeExpression = processor.stringFactory.create(value);
+ likeExpression = likeExpresionForWildcardPath(likeExpression);
query = findNodesLike(ContentIndex.PATH, likeExpression, caseSensitive);
break;
case GREATER_THAN: