Author: rhauch
Date: 2009-09-21 16:03:40 -0400 (Mon, 21 Sep 2009)
New Revision: 1234
Added:
trunk/dna-common/src/main/java/org/jboss/dna/common/text/SecureHashTextEncoder.java
trunk/dna-common/src/main/java/org/jboss/dna/common/util/ObjectUtil.java
trunk/dna-common/src/test/java/org/jboss/dna/common/text/SecureHashTextEncoderTest.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryContext.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/And.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/BindVariableName.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNode.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNodeJoinCondition.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/model/Command.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Comparison.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Constraint.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNode.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNodeJoinCondition.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/EquiJoinCondition.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearch.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Join.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinCondition.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinType.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LanguageObject.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Limit.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Not.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Or.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Order.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Ordering.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyExistence.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Query.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/QueryCommand.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Readable.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNode.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNodeJoinCondition.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Selector.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SelectorName.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetQuery.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Source.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/StaticOperand.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitable.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithm.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/Optimizer.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/OptimizerRule.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoins.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/parse/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/ColumnExpression.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/FullTextSearchParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/JoinAlgorithm.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/Planner.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DelegatingComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctOfSortedComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ExceptComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/IntersectComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/JoinComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/LimitComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/MergeJoinComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NestedLoopJoinComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NoResultsComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/Processor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProjectComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResultColumns.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SelectComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SetOperationComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortLocationsComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/UnionComponent.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ValueCache.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableColumn.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableKey.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/AbstractQueryObjectTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/QueryTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/SetQueryTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/AddAccessNodesTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithmTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/PushSelectCriteriaTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoinsTest.java
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/parse/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/FullTextSearchParserTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlTokenizerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanHintsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanNodeTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/DistinctComponentTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultsTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortLocationsComponentTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
trunk/dna-search/
trunk/dna-search/.classpath
trunk/dna-search/.project
trunk/dna-search/pom.xml
trunk/dna-search/src/
trunk/dna-search/src/main/
trunk/dna-search/src/main/java/
trunk/dna-search/src/main/java/org/
trunk/dna-search/src/main/java/org/jboss/
trunk/dna-search/src/main/java/org/jboss/dna/
trunk/dna-search/src/main/java/org/jboss/dna/search/
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
trunk/dna-search/src/main/java/org/jboss/dna/search/EncodingNamespaceRegistry.java
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexContext.java
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingRules.java
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingStrategy.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
trunk/dna-search/src/main/java/org/jboss/dna/search/StoreLittleIndexingStrategy.java
trunk/dna-search/src/main/java/org/jboss/dna/search/WorkspaceSearchEngine.java
trunk/dna-search/src/main/resources/
trunk/dna-search/src/main/resources/org/
trunk/dna-search/src/main/resources/org/jboss/
trunk/dna-search/src/main/resources/org/jboss/dna/
trunk/dna-search/src/main/resources/org/jboss/dna/search/
trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties
trunk/dna-search/src/test/
trunk/dna-search/src/test/java/
trunk/dna-search/src/test/java/org/
trunk/dna-search/src/test/java/org/jboss/
trunk/dna-search/src/test/java/org/jboss/dna/
trunk/dna-search/src/test/java/org/jboss/dna/search/
trunk/dna-search/src/test/java/org/jboss/dna/search/EncodingNamespaceRegistryTest.java
trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchI18nTest.java
trunk/dna-search/src/test/java/org/jboss/dna/search/WorkspaceSearchEngineTest.java
trunk/dna-search/src/test/resources/
trunk/dna-search/src/test/resources/aircraft.xml
trunk/dna-search/src/test/resources/cars.xml
trunk/dna-search/src/test/resources/log4j.properties
Modified:
trunk/.gitignore
trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndTokenizerTest.java
trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java
trunk/dna-common/src/main/java/org/jboss/dna/common/text/Position.java
trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java
trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties
trunk/dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Location.java
trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
trunk/pom.xml
Log:
DNA-467 Add search/query support to the graph API
Added an initial version of the query engine functionality. This design provides a way to
define and supply queries (in a various languages) and have the engine parse them into a
single Abstract Query Model (equivalent to the abstract syntax tree for a query), plan,
validate, optimize and then process the portions atomic portions of the query plan. This
whole system was designed to be easily reused as-is or extended and customized to provide
the desired behavior. But because this is a generalized query engine capable of query
over a 'graph', the actual processing of the atomic portions of the queries must
be provided when the engine is used. Part of this commit includes a new
'dna-search' project containing a specialization of the query engine with a
processor capable of using a set of Lucene search indexes, along with utility and
management methods to populate and update the indexes (by indexing the entire content
and/or by updating the content based upon events).
A number of packages were added to 'dna-graph', including: an abstract query model
(AQM) based upon the JSR-283 specification in 'o.j.dna.graph.query.model'; a query
engine component in 'o.j.dna.graph.query', a query planning module in
'o.j.dna.graph.query.plan'; an extensible rule-based optimization module in
'o.j.dna.graph.query.optimize'; a simple way to define the schemata that is being
queried in 'o.j.dna.query.validate'; a flexible processing plan model and
execution framework in 'o.j.dna.graph.query.process'; and a framework for
different query language parsers (including a JCR-SQL2 parser) in
'o.j.dna.query.parse'. This entire query engine framework was designed to be
reused and/or extended in multiple places, and so includes a way to accept and execute
queries from a number of different an abstraction of the actual processing of the
low-level atomic queries. Numerous unit tests were added to test each of the components,
including a large number of tests for the SQ!
L parser.
A new 'dna-search' project was created and the initial Lucene-based query engine
functionality was added. Quite a few tests were added to verify the desired behavior.
At this point, the general query engine and the Lucene-based specialization are for the
most part complete and thoroughly tested, but these components need to be integrated into
the larger connector framework and JCR implementation. All of the Lucene index generation
and management needs to be coordinated and integrated with the administration and
lifecycle of the DNA connectors and JCR engine. Additionally, while there are methods to
create/update the indexes, the ability to extract text from binary property values still
needs to be added. In short, there still is a lot of outstanding work.
Modified: trunk/.gitignore
===================================================================
--- trunk/.gitignore 2009-09-21 18:44:47 UTC (rev 1233)
+++ trunk/.gitignore 2009-09-21 20:03:40 UTC (rev 1234)
@@ -15,6 +15,9 @@
/dna-integration-tests/.settings
/dna-integration-tests/target
+/dna-search/.settings
+/dna-search/target
+
/dna-jcr/.settings
/dna-jcr/target
Modified: trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndTokenizerTest.java
===================================================================
--- trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndTokenizerTest.java 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndTokenizerTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -112,7 +112,6 @@
public void shouldNotIncludeColonInListOfSymbolsSinceTheyCanAppearInNames() {
tokenizer = new CndTokenizer(true, true);
String content = "dna:someName";
- int numSymbols = content.length();
tokenize(content);
assertNextTokenIs(0, content.length(), CndTokenizer.WORD);
assertNoMoreTokens();
@@ -122,7 +121,6 @@
public void shouldCreateVendorExtensionToken() {
tokenizer = new CndTokenizer(true, true);
String content = "{vendor extension}";
- int numSymbols = content.length();
tokenize(content);
assertNextTokenIs(0, content.length(), CndTokenizer.VENDOR_EXTENSION);
assertNoMoreTokens();
@@ -132,7 +130,6 @@
public void shouldNotCreateVendorExtensionTokenIfTokenizerIsNotUsingThem() {
tokenizer = new CndTokenizer(true, false);
String content = "{vendor extension}";
- int numSymbols = content.length();
tokenize(content);
assertNoMoreTokens();
}
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/CommonI18n.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -101,6 +101,9 @@
public static I18n startMethodMustBeCalledBeforeConsumingOrMatching;
public static I18n noMatchingDoubleQuoteFound;
public static I18n noMatchingSingleQuoteFound;
+ public static I18n expectingValidIntegerAtLineAndColumn;
+ public static I18n expectingValidLongAtLineAndColumn;
+ public static I18n expectingValidBooleanAtLineAndColumn;
static {
try {
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/text/Position.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/Position.java 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/text/Position.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -10,8 +10,8 @@
private final int line;
private final int column;
- protected Position( int line,
- int column ) {
+ public Position( int line,
+ int column ) {
this.line = line;
this.column = column;
}
Added:
trunk/dna-common/src/main/java/org/jboss/dna/common/text/SecureHashTextEncoder.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/SecureHashTextEncoder.java
(rev 0)
+++
trunk/dna-common/src/main/java/org/jboss/dna/common/text/SecureHashTextEncoder.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,98 @@
+/*
+ * 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.common.text;
+
+import java.security.NoSuchAlgorithmException;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.SecureHash;
+import org.jboss.dna.common.util.SecureHash.Algorithm;
+import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
+
+/**
+ * A text encoder that performs a secure hash of the input text and returns that hash as
the encoded text. This encoder can be
+ * configured to use different secure hash algorithsm and to return a fixed set of
characters of the hash.
+ */
+public class SecureHashTextEncoder implements TextEncoder {
+
+ private final Algorithm algorithm;
+ private final int maxLength;
+
+ /**
+ * Create an encoder that uses the supplied algorithm and returns only the supplied
number of characters in the hash.
+ *
+ * @param algorithm the algorithm that should be used
+ * @throws IllegalArgumentException if the algorithm is null
+ */
+ public SecureHashTextEncoder( Algorithm algorithm ) {
+ this(algorithm, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Create an encoder that uses the supplied algorithm and returns only the supplied
number of characters in the hash.
+ *
+ * @param algorithm the algorithm that should be used
+ * @param maxLength the maximumLength, or a non-positive number (or {@link
Integer#MAX_VALUE}) if the full hash should be used
+ * @throws IllegalArgumentException if the algorithm is null
+ */
+ public SecureHashTextEncoder( Algorithm algorithm,
+ int maxLength ) {
+ CheckArg.isNotNull(algorithm, "algorithm");
+ this.algorithm = algorithm;
+ this.maxLength = maxLength < 1 ? Integer.MAX_VALUE : maxLength;
+ }
+
+ /**
+ * Get the maximum length of the encoded string, or {@link Integer#MAX_VALUE} if
there is no maximum.
+ *
+ * @return the maximum encoded string length; always positive
+ */
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ /**
+ * Return the secure hash algorithm used by this encoder.
+ *
+ * @return the algorithm; never null
+ */
+ public Algorithm getAlgorithm() {
+ return algorithm;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.common.text.TextEncoder#encode(java.lang.String)
+ */
+ public String encode( String text ) {
+ try {
+ byte[] hash = SecureHash.getHash(algorithm, text.getBytes());
+ String result = Base64.encode(hash);
+ return result.length() < maxLength ? result : result.substring(0,
maxLength);
+ } catch (NoSuchAlgorithmException e) {
+ return text;
+ }
+ }
+
+}
Property changes on:
trunk/dna-common/src/main/java/org/jboss/dna/common/text/SecureHashTextEncoder.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/text/TokenStream.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -444,6 +444,72 @@
}
/**
+ * Convert the value of this token to an integer, return it, and move to the next
token.
+ *
+ * @return the current token's value, converted to an integer
+ * @throws ParsingException if there is no such token to consume, or if the token
cannot be converted to an integer
+ * @throws IllegalStateException if this method was called before the stream was
{@link #start() started}
+ */
+ public int consumeInteger() throws ParsingException, IllegalStateException {
+ if (completed) throwNoMoreContent();
+ // Get the value from the current token ...
+ String value = currentToken().value();
+ try {
+ int result = Integer.parseInt(value);
+ moveToNextToken();
+ return result;
+ } catch (NumberFormatException e) {
+ Position position = currentToken().position();
+ String msg = CommonI18n.expectingValidIntegerAtLineAndColumn.text(value,
position.getLine(), position.getColumn());
+ throw new ParsingException(position, msg);
+ }
+ }
+
+ /**
+ * Convert the value of this token to a long, return it, and move to the next token.
+ *
+ * @return the current token's value, converted to an integer
+ * @throws ParsingException if there is no such token to consume, or if the token
cannot be converted to a long
+ * @throws IllegalStateException if this method was called before the stream was
{@link #start() started}
+ */
+ public long consumeLong() throws ParsingException, IllegalStateException {
+ if (completed) throwNoMoreContent();
+ // Get the value from the current token ...
+ String value = currentToken().value();
+ try {
+ long result = Long.parseLong(value);
+ moveToNextToken();
+ return result;
+ } catch (NumberFormatException e) {
+ Position position = currentToken().position();
+ String msg = CommonI18n.expectingValidLongAtLineAndColumn.text(value,
position.getLine(), position.getColumn());
+ throw new ParsingException(position, msg);
+ }
+ }
+
+ /**
+ * Convert the value of this token to an integer, return it, and move to the next
token.
+ *
+ * @return the current token's value, converted to an integer
+ * @throws ParsingException if there is no such token to consume, or if the token
cannot be converted to an integer
+ * @throws IllegalStateException if this method was called before the stream was
{@link #start() started}
+ */
+ public boolean consumeBoolean() throws ParsingException, IllegalStateException {
+ if (completed) throwNoMoreContent();
+ // Get the value from the current token ...
+ String value = currentToken().value();
+ try {
+ boolean result = Boolean.parseBoolean(value);
+ moveToNextToken();
+ return result;
+ } catch (NumberFormatException e) {
+ Position position = currentToken().position();
+ String msg = CommonI18n.expectingValidBooleanAtLineAndColumn.text(value,
position.getLine(), position.getColumn());
+ throw new ParsingException(position, msg);
+ }
+ }
+
+ /**
* Return the value of this token and move to the next token.
*
* @return the value of the current token
@@ -451,16 +517,19 @@
* @throws IllegalStateException if this method was called before the stream was
{@link #start() started}
*/
public String consume() throws ParsingException, IllegalStateException {
- if (completed) {
- String msg = CommonI18n.noMoreContent.text();
- throw new ParsingException(tokens.get(tokens.size() - 1).position(), msg);
- }
+ if (completed) throwNoMoreContent();
// Get the value from the current token ...
String result = currentToken().value();
moveToNextToken();
return result;
}
+ protected void throwNoMoreContent() throws ParsingException {
+ String msg = CommonI18n.noMoreContent.text();
+ Position pos = tokens.isEmpty() ? new Position(1, 0) : tokens.get(tokens.size() -
1).position();
+ throw new ParsingException(pos, msg);
+ }
+
/**
* Attempt to consume this current token as long as it matches the expected value, or
throw an exception if the token does not
* match.
@@ -1278,6 +1347,14 @@
boolean isNextWhitespace();
/**
+ * Determine if the next character on the sream is a {@link
Character#isLetterOrDigit(char) letter or digit}. This method
+ * does <i>not</i> advance the stream.
+ *
+ * @return true if there is a {@link #next() next} character and it is a letter
or digit, or false otherwise
+ */
+ boolean isNextLetterOrDigit();
+
+ /**
* Determine if the next character on the sream is a {@link
XmlCharacters#isValid(int) valid XML character}. This method
* does <i>not</i> advance the stream.
*
@@ -1647,7 +1724,7 @@
private int lastIndex = -1;
private final int maxIndex;
private int lineNumber = 1;
- private int columnNumber = 1;
+ private int columnNumber = 0;
private boolean nextCharMayBeLineFeed;
public CharacterArrayStream( char[] content ) {
@@ -1692,6 +1769,7 @@
throw new NoSuchElementException();
}
char result = content[++lastIndex];
+ ++columnNumber;
if (result == '\r') {
nextCharMayBeLineFeed = true;
++lineNumber;
@@ -1785,6 +1863,16 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.common.text.TokenStream.CharacterStream#isNextLetterOrDigit()
+ */
+ public boolean isNextLetterOrDigit() {
+ int nextIndex = lastIndex + 1;
+ return nextIndex <= maxIndex &&
Character.isLetterOrDigit(content[nextIndex]);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.common.text.TokenStream.CharacterStream#isNextValidXmlCharacter()
*/
public boolean isNextValidXmlCharacter() {
Added: trunk/dna-common/src/main/java/org/jboss/dna/common/util/ObjectUtil.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/util/ObjectUtil.java
(rev 0)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/util/ObjectUtil.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,40 @@
+/*
+ * 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.common.util;
+
+/**
+ * Common utility methods for general objects.
+ */
+public class ObjectUtil {
+
+ public static <Type> boolean isEqualNoNulls( Type reference1,
+ Type reference2 ) {
+ return reference1.equals(reference2);
+ }
+
+ public static <Type> boolean isEqualWithNulls( Type reference1,
+ Type reference2 ) {
+ return reference1 == null ? reference2 == null : reference1.equals(reference2);
+ }
+}
Property changes on:
trunk/dna-common/src/main/java/org/jboss/dna/common/util/ObjectUtil.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties
===================================================================
---
trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties 2009-09-21
18:44:47 UTC (rev 1233)
+++
trunk/dna-common/src/main/resources/org/jboss/dna/common/CommonI18n.properties 2009-09-21
20:03:40 UTC (rev 1234)
@@ -90,3 +90,6 @@
startMethodMustBeCalledBeforeConsumingOrMatching = The 'start()' method must be
called before tokens can be consumed or matched
noMatchingDoubleQuoteFound = No matching closing double quote found for the one at line
{0}, column {1}
noMatchingSingleQuoteFound = No matching closing single quote found for the one at line
{0}, column {1}
+expectingValidIntegerAtLineAndColumn = Expecting a valid integer value but found
'{0}' at line {1}, column {2}
+expectingValidLongAtLineAndColumn = Expecting a valid long value but found '{0}'
at line {1}, column {2}
+expectingValidBooleanAtLineAndColumn = Expecting a valid boolean value but found
'{0}' at line {1}, column {2}
Added:
trunk/dna-common/src/test/java/org/jboss/dna/common/text/SecureHashTextEncoderTest.java
===================================================================
---
trunk/dna-common/src/test/java/org/jboss/dna/common/text/SecureHashTextEncoderTest.java
(rev 0)
+++
trunk/dna-common/src/test/java/org/jboss/dna/common/text/SecureHashTextEncoderTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,99 @@
+/*
+ * 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.common.text;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import java.util.HashSet;
+import java.util.Set;
+import org.jboss.dna.common.util.SecureHash.Algorithm;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SecureHashTextEncoderTest {
+
+ private SecureHashTextEncoder encoder = new SecureHashTextEncoder(Algorithm.SHA_1);
+ private SecureHashTextEncoder shortEncoder = new
SecureHashTextEncoder(Algorithm.SHA_1, 4);
+ private SecureHashTextEncoder md5Encoder = new SecureHashTextEncoder(Algorithm.MD5);
+ private Set<String> alreadyEncoded = new HashSet<String>();
+ private Set<String> alreadyEncodedShort = new HashSet<String>();
+ private Set<String> alreadyEncodedMd5 = new HashSet<String>();
+
+ @Before
+ public void beforeEach() {
+
+ }
+
+ protected void checkEncoding( String input ) {
+ assertThat(alreadyEncoded.add(checkEncoding(encoder, input)), is(true));
+ assertThat(alreadyEncodedShort.add(checkEncoding(shortEncoder, input)),
is(true));
+ assertThat(alreadyEncodedMd5.add(checkEncoding(md5Encoder, input)), is(true));
+ }
+
+ protected String checkEncoding( SecureHashTextEncoder encoder,
+ String input ) {
+ String output = encoder.encode(input);
+ assertThat(output, is(notNullValue()));
+ assertThat(output.length() <= encoder.getMaxLength(), is(true));
+ return output;
+ }
+
+ @Test
+ public void shouldEncodeAlphabeticCharacters() {
+ checkEncoding("abcdefghijklmnopqrstuvwxyz");
+ checkEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ }
+
+ @Test
+ public void shouldEncodeNumericCharacters() {
+ checkEncoding("0123456789");
+ }
+
+ @Test
+ public void shouldEncodePunctuationCharacters() {
+ checkEncoding("~`!(a)#$%^&()-_+={}\\;\"'<,>.?");
+ }
+
+ @Test
+ public void shouldEncodeUrlsAndHaveNoDuplicates() {
+ checkEncoding("http://www.jboss.org");
+
checkEncoding("http://www.jboss.org/");
+
checkEncoding("http://www.jboss.org/dna");
+
checkEncoding("http://www.jboss.org/dna/1.0");
+
checkEncoding("http://www.jboss.org/dna/internal/1.0");
+
checkEncoding("http://www.jcp.org/jcr/1.0");
+
checkEncoding("http://www.jcp.org/jcr/nt/1.0");
+
checkEncoding("http://www.jcp.org/jcr/mix/1.0");
+
checkEncoding("http://www.jcp.org/jcr/sv/1.0");
+
checkEncoding("http://www.acme.com/this/is/a/really/long/url/this/is...;
+
checkEncoding("http://www.acme.com/this/is/a/really/long/url/this/is...;
+ // System.out.println(alreadyEncoded);
+ // System.out.println(alreadyEncodedShort);
+ // System.out.println(alreadyEncodedMd5);
+ }
+}
Property changes on:
trunk/dna-common/src/test/java/org/jboss/dna/common/text/SecureHashTextEncoderTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/ExecutionContext.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -57,6 +57,8 @@
@Immutable
public class ExecutionContext implements ClassLoaderFactory, Cloneable {
+ public static final ExecutionContext DEFAULT_CONTEXT = new ExecutionContext();
+
private final ClassLoaderFactory classLoaderFactory;
private final PropertyFactory propertyFactory;
private final ValueFactories valueFactories;
@@ -210,7 +212,7 @@
*
* @return the property factory; never <code>null</code>
*/
- public PropertyFactory getPropertyFactory() {
+ public final PropertyFactory getPropertyFactory() {
return this.propertyFactory;
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-09-21 18:44:47
UTC (rev 1233)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-09-21 20:03:40
UTC (rev 1234)
@@ -127,6 +127,35 @@
public static I18n unableToMoveNodeToBeChildOfDecendent;
public static I18n childNotFound;
+ /* Query */
+ public static I18n tableDoesNotExist;
+ public static I18n columnDoesNotExistOnTable;
+ public static I18n columnDoesNotExistInQuery;
+ public static I18n selectorDoesNotExistInQuery;
+ public static I18n propertyOnSelectorIsNotUsedInQuery;
+ public static I18n errorResolvingNodesFromLocationsUsingSourceAndWorkspace;
+ public static I18n queryHasNoResults;
+ public static I18n schemataKeyReferencesNonExistingColumn;
+ public static I18n nextMethodMustBeCalledBeforeGettingValue;
+ public static I18n expectingValidName;
+ public static I18n expectingValidPath;
+ public static I18n columnMustBeScoped;
+ public static I18n expectingValidNameAtLineAndColumn;
+ public static I18n expectingValidPathAtLineAndColumn;
+ public static I18n mustBeScopedAtLineAndColumn;
+ public static I18n unexpectedToken;
+ public static I18n secondValueInLimitRangeCannotBeLessThanFirst;
+ public static I18n expectingComparisonOperator;
+ public static I18n expectingConstraintCondition;
+ public static I18n functionIsAmbiguous;
+ public static I18n bindVariableMustConformToNcName;
+ public static I18n invalidPropertyType;
+ public static I18n valueCannotBeCastToSpecifiedType;
+ public static I18n noMatchingBracketFound;
+ public static I18n expectingLiteralAndUnableToParseAsLong;
+ public static I18n expectingLiteralAndUnableToParseAsDouble;
+ public static I18n expectingLiteralAndUnableToParseAsDate;
+
static {
try {
I18n.initialize(GraphI18n.class);
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Location.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Location.java 2009-09-21 18:44:47
UTC (rev 1233)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Location.java 2009-09-21 20:03:40
UTC (rev 1234)
@@ -24,6 +24,7 @@
package org.jboss.dna.graph;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -45,7 +46,32 @@
@Immutable
public abstract class Location implements Iterable<Property>,
Comparable<Location> {
+ private static final Comparator<Location> COMPARATOR = new
Comparator<Location>() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+ */
+ public int compare( Location o1,
+ Location o2 ) {
+ if (o1 == o2) return 0;
+ if (o1 == null) return -1;
+ if (o2 == null) return 1;
+ return o1.compareTo(o2);
+ }
+ };
+
/**
+ * Get a {@link Comparator} that can be used to compare two Location objects. Note
that Location implements {@link Comparable}
+ * .
+ *
+ * @return the comparator; never null
+ */
+ public static final Comparator<Location> comparator() {
+ return COMPARATOR;
+ }
+
+ /**
* Simple shared iterator instance that is used when there are no properties.
*/
protected static final Iterator<Property> NO_ID_PROPERTIES_ITERATOR = new
Iterator<Property>() {
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ *
+ */
+public class InvalidQueryException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public InvalidQueryException() {
+ }
+
+ /**
+ * @param message
+ */
+ public InvalidQueryException( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * @param cause
+ */
+ public InvalidQueryException( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public InvalidQueryException( String message,
+ Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/InvalidQueryException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,1361 @@
+/*
+ * 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;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.ValueFormatException;
+import org.jboss.dna.graph.query.model.AllNodes;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Join;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.Limit;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Ordering;
+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.QueryCommand;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Selector;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetQuery;
+import org.jboss.dna.graph.query.model.Source;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.SetQuery.Operation;
+
+/**
+ * A component that can be used to programmatically create {@link QueryCommand} objects.
Simply call methods to build the selector
+ * clause, from clause, join criteria, where criteria, limits, and ordering, and then
{@link #query() obtain the query}. This
+ * builder should be adequate for most queries; however, any query that cannot be
expressed by this builder can always be
+ * constructed by directly creating the Abstract Query Model classes.
+ * <p>
+ * This builder is stateful and therefore should only be used by one thread at a time.
However, once a query has been built, the
+ * builder can be {@link #clear() cleared} and used to create another query.
+ * </p>
+ * <p>
+ * The order in which the methods are called are (for the most part) important. Simply
call the methods in the same order that
+ * would be most natural in a normal SQL query. For example, the following code creates a
Query object that is equivalent to "
+ * <code>SELECT * FROM table</code>":
+ *
+ * <pre>
+ * QueryCommand query = builder.selectStar().from("table").query();
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * Here are a few other examples:
+ * <table border="1" cellspacing="0" cellpadding="3"
summary="">
+ * <tr>
+ * <th>SQL Statement</th>
+ * <th>QueryBuilder code</th>
+ * </tr>
+ * <tr>
+ * <td>
+ *
+ * <pre>
+ * SELECT * FROM table1
+ * INNER JOIN table2
+ * ON table2.c0 = table1.c0
+ * </pre>
+ *
+ * </td>
+ * <td>
+ *
+ * <pre>
+ * query =
builder.selectStar().from("table1").join("table2").on("table2.c0=table1.c0").query();
+ * </pre>
+ *
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>
+ *
+ * <pre>
+ * SELECT * FROM table1 AS t1
+ * INNER JOIN table2 AS t2
+ * ON t1.c0 = t2.c0
+ * </pre>
+ *
+ * </td>
+ * <td>
+ *
+ * <pre>
+ * query = builder.selectStar().from("table1 AS
t1").join("table2 AS
t2").on("t1.c0=t2.c0").query();
+ * </pre>
+ *
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>
+ *
+ * <pre>
+ * SELECT * FROM table1 AS t1
+ * INNER JOIN table2 AS t2
+ * ON t1.c0 = t2.c0
+ * INNER JOIN table3 AS t3
+ * ON t1.c1 = t3.c1
+ * </pre>
+ *
+ * </td>
+ * <td>
+ *
+ * <pre>
+ * query = builder.selectStar()
+ * .from("table1 AS t1")
+ * .innerJoin("table2 AS t2")
+ * .on("t1.c0=t2.c0")
+ * .innerJoin("table3 AS t3")
+ * .on("t1.c1=t3.c1")
+ * .query();
+ * </pre>
+ *
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>
+ *
+ * <pre>
+ * SELECT * FROM table1
+ * UNION
+ * SELECT * FROM table2
+ * </pre>
+ *
+ * </td>
+ * <td>
+ *
+ * <pre>
+ * query =
builder.selectStar().from("table1").union().selectStar().from("table2").query();
+ * </pre>
+ *
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>
+ *
+ * <pre>
+ * SELECT t1.c1,t1.c2,t2.c3 FROM table1 AS t1
+ * INNER JOIN table2 AS t2
+ * ON t1.c0 = t2.c0
+ * UNION ALL
+ * SELECT t3.c1,t3.c2,t4.c3 FROM table3 AS t3
+ * INNER JOIN table4 AS t4
+ * ON t3.c0 = t4.c0
+ * </pre>
+ *
+ * </td>
+ * <td>
+ *
+ * <pre>
+ * query =
builder.select("t1.c1","t1.c2","t2.c3",)
+ * .from("table1 AS t1")
+ * .innerJoin("table2 AS t2")
+ * .on("t1.c0=t2.c0")
+ * .union()
+ *
.select("t3.c1","t3.c2","t4.c3",)
+ * .from("table3 AS t3")
+ * .innerJoin("table4 AS t4")
+ * .on("t3.c0=t4.c0")
+ * .query();
+ * </pre>
+ *
+ * </td>
+ * </tr>
+ * </table>
+ * </pre>
+ */
+@NotThreadSafe
+public class QueryBuilder {
+
+ protected final ExecutionContext context;
+ protected Source source = new AllNodes();
+ protected Constraint constraint;
+ protected List<Column> columns = new LinkedList<Column>();
+ protected List<Ordering> orderings = new LinkedList<Ordering>();
+ protected Limit limit = Limit.NONE;
+ protected boolean distinct;
+ protected QueryCommand firstQuery;
+ protected Operation firstQuerySetOperation;
+ protected boolean firstQueryAll;
+
+ /**
+ * Create a new builder that uses the supplied execution context.
+ *
+ * @param context the execution context
+ * @throws IllegalArgumentException if the context is null
+ */
+ public QueryBuilder( ExecutionContext context ) {
+ CheckArg.isNotNull(context, "context");
+ this.context = context;
+ }
+
+ /**
+ * Clear this builder completely to start building a new query.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder clear() {
+ return clear(true);
+ }
+
+ /**
+ * Utility method that does all the work of the clear, but with a flag that defines
whether to clear the first query. This
+ * method is used by {@link #clear()} as well as the {@link #union() many} {@link
#intersect() set} {@link #except()
+ * operations}.
+ *
+ * @param clearFirstQuery true if the first query should be cleared, or false if the
first query should be retained
+ * @return this builder object, for convenience in method chaining
+ */
+ protected QueryBuilder clear( boolean clearFirstQuery ) {
+ source = new AllNodes();
+ constraint = null;
+ columns = new LinkedList<Column>();
+ orderings = new LinkedList<Ordering>();
+ limit = Limit.NONE;
+ distinct = false;
+ if (clearFirstQuery) {
+ this.firstQuery = null;
+ this.firstQuerySetOperation = null;
+ }
+ return this;
+ }
+
+ /**
+ * Convenience method that creates a selector name object using the supplied string.
+ *
+ * @param name the name of the selector; may not be null
+ * @return the selector name; never null
+ */
+ protected SelectorName selector( String name ) {
+ return new SelectorName(name.trim());
+ }
+
+ /**
+ * Convenience method that creates a {@link NamedSelector} object given a string that
contains the selector name and
+ * optionally an alias. The format of the string parameter is <code>name [AS
alias]</code>. Leading and trailing whitespace
+ * are trimmed.
+ *
+ * @param nameWithOptionalAlias the name and optional alias; may not be null
+ * @return the named selector object; never null
+ */
+ protected NamedSelector namedSelector( String nameWithOptionalAlias ) {
+ String[] parts = nameWithOptionalAlias.split("\\sAS\\s");
+ if (parts.length == 2) {
+ return new NamedSelector(selector(parts[0]), selector(parts[1]));
+ }
+ return new NamedSelector(selector(parts[0]));
+ }
+
+ /**
+ * Convenience method that creates a {@link Name} object given the supplied string.
Leading and trailing whitespace are
+ * trimmed.
+ *
+ * @param name the name; may not be null
+ * @return the name; never null
+ * @throws InvalidQueryException if the supplied name is not a valid {@link Name}
object
+ */
+ protected Name name( String name ) {
+ try {
+ return context.getValueFactories().getNameFactory().create(name.trim());
+ } catch (ValueFormatException e) {
+ throw new InvalidQueryException(GraphI18n.expectingValidName.text(name));
+ }
+ }
+
+ /**
+ * Convenience method that creates a {@link Path} object given the supplied string.
Leading and trailing whitespace are
+ * trimmed.
+ *
+ * @param path the path; may not be null
+ * @return the path; never null
+ * @throws InvalidQueryException if the supplied string is not a valid {@link Path}
object
+ */
+ protected Path path( String path ) {
+ try {
+ return context.getValueFactories().getPathFactory().create(path.trim());
+ } catch (ValueFormatException e) {
+ throw new InvalidQueryException(GraphI18n.expectingValidPath.text(path));
+ }
+ }
+
+ /**
+ * Create a {@link Column} given the supplied expression. The expression has the form
"<code>[tableName.]columnName</code>",
+ * where "<code>tableName</code>" must be a valid table name or
alias. If the table name/alias is not specified, then there is
+ * expected to be a single FROM clause with a single named selector.
+ *
+ * @param nameExpression the expression specifying the columm name and (optionally)
the table's name or alias; may not be null
+ * @return the column; never null
+ * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ */
+ protected Column column( String nameExpression ) {
+ String[] parts = nameExpression.split("(?<!\\\\)\\."); // a . not
preceded by an escaping slash
+ for (int i = 0; i != parts.length; ++i) {
+ parts[i] = parts[i].trim();
+ }
+ SelectorName name = null;
+ Name propertyName = null;
+ String columnName = null;
+ if (parts.length == 2) {
+ name = selector(parts[0]);
+ propertyName = name(parts[1]);
+ columnName = parts[1];
+ } else {
+ if (source instanceof Selector) {
+ Selector selector = (Selector)source;
+ name = selector.hasAlias() ? selector.getAlias() : selector.getName();
+ propertyName = name(parts[0]);
+ columnName = parts[0];
+ } else {
+ throw new
InvalidQueryException(GraphI18n.columnMustBeScoped.text(parts[0]));
+ }
+ }
+ return new Column(name, propertyName, columnName);
+ }
+
+ /**
+ * Select all of the single-valued columns.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder selectStar() {
+ columns.clear();
+ return this;
+ }
+
+ /**
+ * Select the columns with the supplied names. Each column name has the form
"<code>[tableName.]columnName</code>", where "
+ * <code>tableName</code>" must be a valid table name or alias. If
the table name/alias is not specified, then there is
+ * expected to be a single FROM clause with a single named selector.
+ *
+ * @param columnNames the column expressions; may not be null
+ * @return this builder object, for convenience in method chaining
+ * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ */
+ public QueryBuilder select( String... columnNames ) {
+ columns.clear();
+ for (String expression : columnNames) {
+ columns.add(column(expression));
+ }
+ return this;
+ }
+
+ /**
+ * Select all of the distinct values from the single-valued columns.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder selectDistinctStar() {
+ distinct = true;
+ return selectStar();
+ }
+
+ /**
+ * Select the distinct values from the columns with the supplied names. Each column
name has the form "
+ * <code>[tableName.]columnName</code>", where "
<code>tableName</code>" must be a valid table name or alias. If the
table
+ * name/alias is not specified, then there is expected to be a single FROM clause
with a single named selector.
+ *
+ * @param columnNames the column expressions; may not be null
+ * @return this builder object, for convenience in method chaining
+ * @throws InvalidQueryException if the table's name/alias is not specified, but
the query has more than one named source
+ */
+ public QueryBuilder selectDistinct( String... columnNames ) {
+ distinct = true;
+ return select(columnNames);
+ }
+
+ /**
+ * Specify that the query should select from the "__ALLNODES__" built-in
table.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder fromAllNodes() {
+ this.source = new AllNodes();
+ return this;
+ }
+
+ /**
+ * Specify that the query should select from the "__ALLNODES__" built-in
table using the supplied alias.
+ *
+ * @param alias the alias for the "__ALL_NODES" table; may not be null
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder fromAllNodesAs( String alias ) {
+ AllNodes allNodes = new AllNodes(selector(alias));
+ SelectorName oldName = this.source instanceof Selector ?
((Selector)source).getName() : null;
+ // Go through the columns and change the selector name to use the new alias ...
+ for (int i = 0; i != columns.size(); ++i) {
+ Column old = columns.get(i);
+ if (old.getSelectorName().equals(oldName)) {
+ columns.set(i, new Column(allNodes.getAliasOrName(),
old.getPropertyName(), old.getColumnName()));
+ }
+ }
+ this.source = allNodes;
+ return this;
+ }
+
+ /**
+ * Specify the name of the table from which tuples should be selected. The supplied
string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableNameWithOptionalAlias the name of the table, optionally including the
alias
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder from( String tableNameWithOptionalAlias ) {
+ Selector selector = namedSelector(tableNameWithOptionalAlias);
+ SelectorName oldName = this.source instanceof Selector ?
((Selector)source).getName() : null;
+ // Go through the columns and change the selector name to use the new alias ...
+ for (int i = 0; i != columns.size(); ++i) {
+ Column old = columns.get(i);
+ if (old.getSelectorName().equals(oldName)) {
+ columns.set(i, new Column(selector.getAliasOrName(),
old.getPropertyName(), old.getColumnName()));
+ }
+ }
+ this.source = selector;
+ return this;
+ }
+
+ /**
+ * Begin the WHERE clause for this query by obtaining the constraint builder. When
completed, be sure to call
+ * {@link ConstraintBuilder#end() end()} on the resulting constraint builder, or else
the constraint will not be applied to
+ * the current query.
+ *
+ * @return the constraint builder that can be used to specify the criteria; never
null
+ */
+ public ConstraintBuilder where() {
+ return new ConstraintBuilder(null);
+ }
+
+ /**
+ * Perform an inner join between the already defined source with the supplied table.
The supplied string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause join( String tableName ) {
+ return innerJoin(tableName);
+ }
+
+ /**
+ * Perform an inner join between the already defined source with the supplied table.
The supplied string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause innerJoin( String tableName ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(tableName), JoinType.INNER);
+ }
+
+ /**
+ * Perform a cross join between the already defined source with the supplied table.
The supplied string is of the form "
+ * <code>tableName [AS alias]</code>". Cross joins have a higher
precedent than other join types, so if this is called after
+ * another join was defined, the resulting cross join will be between the previous
join's right-hand side and the supplied
+ * table.
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause crossJoin( String tableName ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(tableName), JoinType.CROSS);
+ }
+
+ /**
+ * Perform a full outer join between the already defined source with the supplied
table. The supplied string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause fullOuterJoin( String tableName ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(tableName), JoinType.FULL_OUTER);
+ }
+
+ /**
+ * Perform a left outer join between the already defined source with the supplied
table. The supplied string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause leftOuterJoin( String tableName ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(tableName), JoinType.LEFT_OUTER);
+ }
+
+ /**
+ * Perform a right outer join between the already defined source with the supplied
table. The supplied string is of the form "
+ * <code>tableName [AS alias]</code>".
+ *
+ * @param tableName the name of the table, optionally including the alias
+ * @return the component that must be used to complete the join specification; never
null
+ */
+ public JoinClause rightOuterJoin( String tableName ) {
+ // Expect there to be a source already ...
+ return new JoinClause(namedSelector(tableName), JoinType.RIGHT_OUTER);
+ }
+
+ /**
+ * Specify the maximum number of rows that are to be returned in the results. By
default there is no limit.
+ *
+ * @param rowLimit the maximum number of rows
+ * @return this builder object, for convenience in method chaining
+ * @throws IllegalArgumentException if the row limit is not a positive integer
+ */
+ public QueryBuilder limit( int rowLimit ) {
+ this.limit.withRowLimit(rowLimit);
+ return this;
+ }
+
+ /**
+ * Specify the number of rows that results are to skip. The default offset is
'0'.
+ *
+ * @param offset the number of rows before the results are to begin
+ * @return this builder object, for convenience in method chaining
+ * @throws IllegalArgumentException if the row limit is a negative integer
+ */
+ public QueryBuilder offset( int offset ) {
+ this.limit.withOffset(offset);
+ return this;
+ }
+
+ /**
+ * Perform a UNION between the query as defined prior to this method and the query
that will be defined following this method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder union() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.UNION;
+ this.firstQueryAll = false;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Perform a UNION ALL between the query as defined prior to this method and the
query that will be defined following this
+ * method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder unionAll() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.UNION;
+ this.firstQueryAll = true;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Perform an INTERSECT between the query as defined prior to this method and the
query that will be defined following this
+ * method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder intersect() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.INTERSECT;
+ this.firstQueryAll = false;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Perform an INTERSECT ALL between the query as defined prior to this method and the
query that will be defined following
+ * this method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder intersectAll() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.INTERSECT;
+ this.firstQueryAll = true;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Perform an EXCEPT between the query as defined prior to this method and the query
that will be defined following this
+ * method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder except() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.EXCEPT;
+ this.firstQueryAll = false;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Perform an EXCEPT ALL between the query as defined prior to this method and the
query that will be defined following this
+ * method.
+ *
+ * @return this builder object, for convenience in method chaining
+ */
+ public QueryBuilder exceptAll() {
+ this.firstQuery = query();
+ this.firstQuerySetOperation = Operation.EXCEPT;
+ this.firstQueryAll = true;
+ clear(false);
+ return this;
+ }
+
+ /**
+ * Return a {@link QueryCommand} representing the currently-built query.
+ *
+ * @return the resulting query command; never null
+ * @see #clear()
+ */
+ public QueryCommand query() {
+ QueryCommand result = new Query(source, constraint, orderings, columns, limit,
distinct);
+ if (this.firstQuery != null) {
+ // EXCEPT has a higher precedence than INTERSECT or UNION, so if the first
query is
+ // an INTERSECT or UNION SetQuery, the result should be applied to the RHS of
the previous set ...
+ if (firstQuery instanceof SetQuery && firstQuerySetOperation ==
Operation.EXCEPT) {
+ SetQuery setQuery = (SetQuery)firstQuery;
+ QueryCommand left = setQuery.getLeft();
+ QueryCommand right = setQuery.getRight();
+ SetQuery exceptQuery = new SetQuery(right, Operation.EXCEPT, result,
firstQueryAll);
+ result = new SetQuery(left, setQuery.getOperation(), exceptQuery,
setQuery.isAll());
+ } else {
+ result = new SetQuery(this.firstQuery, this.firstQuerySetOperation,
result, this.firstQueryAll);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Class used to specify a join clause of a query.
+ *
+ * @see QueryBuilder#join(String)
+ * @see QueryBuilder#innerJoin(String)
+ * @see QueryBuilder#leftOuterJoin(String)
+ * @see QueryBuilder#rightOuterJoin(String)
+ * @see QueryBuilder#fullOuterJoin(String)
+ */
+ public class JoinClause {
+ private final NamedSelector rightSource;
+ private final JoinType type;
+
+ protected JoinClause( NamedSelector rightTable,
+ JoinType type ) {
+ this.rightSource = rightTable;
+ this.type = type;
+ }
+
+ /**
+ * Walk the current source or the 'rightSource' to find the named
selector with the supplied name or alias
+ *
+ * @param tableName the table name
+ * @return the selector name matching the supplied table name; never null
+ * @throws InvalidQueryException if the table name could not be resolved
+ */
+ protected SelectorName nameOf( String tableName ) {
+ final SelectorName name = new SelectorName(tableName);
+ // Look at the right source ...
+ if (rightSource.getAliasOrName().equals(name)) return name;
+ // Look through the left source ...
+ final AtomicBoolean notFound = new AtomicBoolean(true);
+ Visitors.visitAll(source, new Visitors.AbstractVisitor() {
+ @Override
+ public void visit( AllNodes selector ) {
+ if (notFound.get() && selector.getAliasOrName().equals(name))
notFound.set(false);
+ }
+
+ @Override
+ public void visit( NamedSelector selector ) {
+ if (notFound.get() && selector.getAliasOrName().equals(name))
notFound.set(false);
+ }
+ });
+ if (notFound.get()) {
+ throw new InvalidQueryException("Expected \"" + tableName
+ "\" to be a valid table name or alias");
+ }
+ return name;
+ }
+
+ /**
+ * Define the join as using an equi-join criteria by specifying the expression
equating two columns. Each column reference
+ * must be qualified with the appropriate table name or alias.
+ *
+ * @param columnEqualExpression the equality expression between the two tables;
may not be null
+ * @return the query builder instance, for method chaining purposes
+ */
+ public QueryBuilder on( String columnEqualExpression ) {
+ String[] parts = columnEqualExpression.split("=");
+ if (parts.length != 2) {
+ throw new InvalidQueryException("Expected equality expression for
columns, but found \"" + columnEqualExpression
+ + "\"");
+ }
+ return createJoin(new EquiJoinCondition(column(parts[0]),
column(parts[1])));
+ }
+
+ /**
+ * Define the join criteria to require the two tables represent the same node.
The supplied tables must be a valid name or
+ * alias.
+ *
+ * @param table1 the name or alias of the first table
+ * @param table2 the name or alias of the second table
+ * @return the query builder instance, for method chaining purposes
+ */
+ public QueryBuilder onSameNode( String table1,
+ String table2 ) {
+ return createJoin(new SameNodeJoinCondition(nameOf(table1),
nameOf(table2)));
+ }
+
+ /**
+ * Define the join criteria to require the node in one table is a descendant of
the node in another table. The supplied
+ * tables must be a valid name or alias.
+ *
+ * @param ancestorTable the name or alias of the table containing the ancestor
node
+ * @param descendantTable the name or alias of the table containing the
descendant node
+ * @return the query builder instance, for method chaining purposes
+ */
+ public QueryBuilder onDescendant( String ancestorTable,
+ String descendantTable ) {
+ return createJoin(new DescendantNodeJoinCondition(nameOf(ancestorTable),
nameOf(descendantTable)));
+ }
+
+ /**
+ * Define the join criteria to require the node in one table is a child of the
node in another table. The supplied tables
+ * must be a valid name or alias.
+ *
+ * @param parentTable the name or alias of the table containing the parent node
+ * @param childTable the name or alias of the table containing the child node
+ * @return the query builder instance, for method chaining purposes
+ */
+ public QueryBuilder onChildNode( String parentTable,
+ String childTable ) {
+ return createJoin(new DescendantNodeJoinCondition(nameOf(parentTable),
nameOf(childTable)));
+ }
+
+ protected QueryBuilder createJoin( JoinCondition condition ) {
+ // CROSS joins have a higher precedence, so we may need to adjust the
existing left side in this case...
+ if (type == JoinType.CROSS && source instanceof Join &&
((Join)source).getType() != JoinType.CROSS) {
+ // A CROSS join follows a non-CROSS join, so the CROSS join becomes
precendent ...
+ Join left = (Join)source;
+ Join cross = new Join(left.getRight(), type, rightSource, condition);
+ source = new Join(left.getLeft(), left.getType(), cross,
left.getJoinCondition());
+ } else {
+ // Otherwise, just create using usual precedence ...
+ source = new Join(source, type, rightSource, condition);
+ }
+ return QueryBuilder.this;
+ }
+ }
+
+ /**
+ * Interface that defines a dynamic operand portion of a criteria.
+ */
+ public interface DynamicOperandBuilder {
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
property value whose length matches the
+ * criteria.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder length( String table,
+ String property );
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
matching value for the named property.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param property the name of the property; may not be null and must refer to a
valid property name
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder propertyValue( String table,
+ String property );
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must satisfy the
supplied full-text search on the nodes'
+ * property values.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder fullTextSearchScore( String table );
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
matching node local name.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder nodeLocalName( String table );
+
+ /**
+ * Constrains the nodes in the the supplied table such that they must have a
matching node name.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @return the interface for completing the value portion of the criteria
specification; never null
+ */
+ public ComparisonBuilder nodeName( String table );
+
+ /**
+ * Begin a constraint against the uppercase form of a dynamic operand.
+ *
+ * @return the interface for completing the criteria specification; never null
+ */
+ public DynamicOperandBuilder upperCaseOf();
+
+ /**
+ * Begin a constraint against the lowercase form of a dynamic operand.
+ *
+ * @return the interface for completing the criteria specification; never null
+ */
+ public DynamicOperandBuilder lowerCaseOf();
+
+ }
+
+ public class ConstraintBuilder implements DynamicOperandBuilder {
+ private final ConstraintBuilder parent;
+ /** Used for the current operations */
+ private Constraint constraint;
+ /** Set when a logical criteria is started */
+ private Constraint left;
+ private boolean and;
+ private boolean negateConstraint;
+
+ protected ConstraintBuilder( ConstraintBuilder parent ) {
+ this.parent = parent;
+ }
+
+ /**
+ * Complete this constraint specification.
+ *
+ * @return the query builder, for method chaining purposes
+ */
+ public QueryBuilder end() {
+ buildLogicalConstraint();
+ QueryBuilder.this.constraint = constraint;
+ return QueryBuilder.this;
+ }
+
+ /**
+ * Simulate the use of an open parenthesis in the constraint. The resulting
builder should be used to define the
+ * constraint within the parenthesis, and should always be terminated with a
{@link #closeParen()}.
+ *
+ * @return the constraint builder that should be used to define the portion of
the constraint within the parenthesis;
+ * never null
+ * @see #closeParen()
+ */
+ public ConstraintBuilder openParen() {
+ return new ConstraintBuilder(this);
+ }
+
+ /**
+ * Complete the specification of a constraint clause, and return the builder for
the parent constraint clause.
+ *
+ * @return the constraint builder that was used to create this parenthetical
constraint clause builder; never null
+ */
+ public ConstraintBuilder closeParen() {
+ assert parent != null;
+ buildLogicalConstraint();
+ return parent.setConstraint(constraint);
+ }
+
+ /**
+ * Signal that the previous constraint clause be AND-ed together with another
constraint clause that will be defined
+ * immediately after this method call.
+ *
+ * @return the constraint builder for the remaining constraint clause; never
null
+ */
+ public ConstraintBuilder and() {
+ buildLogicalConstraint();
+ left = constraint;
+ constraint = null;
+ and = true;
+ return this;
+ }
+
+ /**
+ * Signal that the previous constraint clause be OR-ed together with another
constraint clause that will be defined
+ * immediately after this method call.
+ *
+ * @return the constraint builder for the remaining constraint clause; never
null
+ */
+ public ConstraintBuilder or() {
+ buildLogicalConstraint();
+ left = constraint;
+ constraint = null;
+ and = false;
+ return this;
+ }
+
+ /**
+ * Signal that the next constraint clause (defined immediately after this method)
should be negated.
+ *
+ * @return the constraint builder for the constraint clause that is to be
negated; never null
+ */
+ public ConstraintBuilder not() {
+ negateConstraint = true;
+ return this;
+ }
+
+ protected ConstraintBuilder buildLogicalConstraint() {
+ if (negateConstraint && constraint != null) {
+ constraint = new Not(constraint);
+ negateConstraint = false;
+ }
+ if (left != null && constraint != null) {
+ if (and) {
+ // If the left constraint is an OR, we need to rearrange things since
AND is higher precedence ...
+ if (left instanceof Or) {
+ Or previous = (Or)left;
+ constraint = new Or(previous.getLeft(), new
And(previous.getRight(), constraint));
+ } else {
+ constraint = new And(left, constraint);
+ }
+ } else {
+ constraint = new Or(left, constraint);
+ }
+ left = null;
+ }
+ return this;
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table is the same
node as that appearing at the supplied
+ * path.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param asNodeAtPath the path to the node
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder isSameNode( String table,
+ String asNodeAtPath ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new SameNode(selector(table), path(asNodeAtPath)));
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table is the child
of the node at the supplied path.
+ *
+ * @param childTable the name of the table; may not be null and must refer to a
valid name or alias of a table appearing
+ * in the FROM clause
+ * @param parentPath the path to the parent node
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder isChild( String childTable,
+ String parentPath ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new ChildNode(selector(childTable), path(parentPath)));
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table is a
descendant of the node at the supplied path.
+ *
+ * @param descendantTable the name of the table; may not be null and must refer
to a valid name or alias of a table
+ * appearing in the FROM clause
+ * @param ancestorPath the path to the ancestor node
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder isBelowPath( String descendantTable,
+ String ancestorPath ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new DescendantNode(selector(descendantTable),
path(ancestorPath)));
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table has at least
one value for the named property. path.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param propertyName the name of the property
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder hasProperty( String table,
+ String propertyName ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new PropertyExistence(selector(table),
name(propertyName)));
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table have at least
one property that satisfies the full-text
+ * search expression.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param searchExpression the full-text search expression
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder search( String table,
+ String searchExpression ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new FullTextSearch(selector(table), searchExpression));
+ }
+
+ /**
+ * Define a constraint clause that the node within the named table have a value
for the named property that satisfies the
+ * full-text search expression.
+ *
+ * @param table the name of the table; may not be null and must refer to a valid
name or alias of a table appearing in the
+ * FROM clause
+ * @param propertyName the name of the property to be searched
+ * @param searchExpression the full-text search expression
+ * @return the constraint builder that was used to create this clause; never
null
+ */
+ public ConstraintBuilder search( String table,
+ String propertyName,
+ String searchExpression ) {
+ if (constraint != null) throw new InvalidQueryException("Existing
constraint; call in proper order");
+ return setConstraint(new FullTextSearch(selector(table), name(propertyName),
searchExpression));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#length(java.lang.String,
java.lang.String)
+ */
+ public ComparisonBuilder length( String table,
+ String property ) {
+ return new ComparisonBuilder(this, new Length(new
PropertyValue(selector(table), name(property))));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#propertyValue(String,
String)
+ */
+ public ComparisonBuilder propertyValue( String table,
+ String property ) {
+ return new ComparisonBuilder(this, new PropertyValue(selector(table),
name(property)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#fullTextSearchScore(String)
+ */
+ public ComparisonBuilder fullTextSearchScore( String table ) {
+ return new ComparisonBuilder(this, new
FullTextSearchScore(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#nodeLocalName(String)
+ */
+ public ComparisonBuilder nodeLocalName( String table ) {
+ return new ComparisonBuilder(this, new NodeLocalName(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#nodeName(String)
+ */
+ public ComparisonBuilder nodeName( String table ) {
+ return new ComparisonBuilder(this, new NodeName(selector(table)));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#upperCaseOf()
+ */
+ public DynamicOperandBuilder upperCaseOf() {
+ return new UpperCaser(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryBuilder.DynamicOperandBuilder#lowerCaseOf()
+ */
+ public DynamicOperandBuilder lowerCaseOf() {
+ return new LowerCaser(this);
+ }
+
+ protected ConstraintBuilder setConstraint( Constraint constraint ) {
+ if (this.constraint != null && this.left == null) {
+ and();
+ }
+ this.constraint = constraint;
+ return buildLogicalConstraint();
+ }
+ }
+
+ /**
+ * A specialized form of the {@link ConstraintBuilder} that always wraps the
generated constraint in a {@link UpperCase}
+ * instance.
+ */
+ protected class UpperCaser extends ConstraintBuilder {
+ private final ConstraintBuilder delegate;
+
+ protected UpperCaser( ConstraintBuilder delegate ) {
+ super(null);
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected ConstraintBuilder setConstraint( Constraint constraint ) {
+ Comparison comparison = (Comparison)constraint;
+ return delegate.setConstraint(new Comparison(new
UpperCase(comparison.getOperand1()), comparison.getOperator(),
+ comparison.getOperand2()));
+ }
+ }
+
+ /**
+ * A specialized form of the {@link ConstraintBuilder} that always wraps the
generated constraint in a {@link LowerCase}
+ * instance.
+ */
+ protected class LowerCaser extends ConstraintBuilder {
+ private final ConstraintBuilder delegate;
+
+ protected LowerCaser( ConstraintBuilder delegate ) {
+ super(null);
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected ConstraintBuilder setConstraint( Constraint constraint ) {
+ Comparison comparison = (Comparison)constraint;
+ return delegate.setConstraint(new Comparison(new
LowerCase(comparison.getOperand1()), comparison.getOperator(),
+ comparison.getOperand2()));
+ }
+ }
+
+ /**
+ * An interface used to set the right-hand side of a constraint.
+ */
+ public class ComparisonBuilder {
+ private final DynamicOperand left;
+ private final ConstraintBuilder constraintBuilder;
+
+ protected ComparisonBuilder( ConstraintBuilder constraintBuilder,
+ DynamicOperand left ) {
+ this.left = left;
+ this.constraintBuilder = constraintBuilder;
+ }
+
+ protected ConstraintBuilder isVariable( Operator operator,
+ String variableName ) {
+ assert operator != null;
+ return this.constraintBuilder.setConstraint(new Comparison(left, operator,
new BindVariableName(variableName)));
+ }
+
+ protected ConstraintBuilder is( Operator operator,
+ Object literal ) {
+ assert operator != null;
+ return this.constraintBuilder.setConstraint(new Comparison(left, operator,
new Literal(literal)));
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be equivalent to the value of
the supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isEqualToVariable( String variableName ) {
+ return isVariable(Operator.EQUAL_TO, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be greater than the value of
the supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isGreaterThanVariable( String variableName ) {
+ return isVariable(Operator.GREATER_THAN, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be greater than or equal to
the value of the supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isGreaterThanOrEqualToVariable( String variableName ) {
+ return isVariable(Operator.GREATER_THAN_OR_EQUAL_TO, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be less than the value of the
supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLessThanVariable( String variableName ) {
+ return isVariable(Operator.LESS_THAN, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be less than or equal to the
value of the supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLessThanOrEqualToVariable( String variableName ) {
+ return isVariable(Operator.LESS_THAN_OR_EQUAL_TO, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be LIKE the value of the
supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLikeVariable( String variableName ) {
+ return isVariable(Operator.LIKE, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be not equal to the value of
the supplied variable.
+ *
+ * @param variableName the name of the variable
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isNotEqualToVariable( String variableName ) {
+ return isVariable(Operator.NOT_EQUAL_TO, variableName);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be equivalent to the supplied
literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isEqualTo( Object literal ) {
+ return is(Operator.EQUAL_TO, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be greater than the supplied
literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isGreaterThan( Object literal ) {
+ return is(Operator.GREATER_THAN, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be greater than or equal to
the supplied literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isGreaterThanOrEqualTo( Object literal ) {
+ return is(Operator.GREATER_THAN_OR_EQUAL_TO, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be less than the supplied
literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLessThan( Object literal ) {
+ return is(Operator.LESS_THAN, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be less than or equal to the
supplied literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLessThanOrEqualTo( Object literal ) {
+ return is(Operator.LESS_THAN_OR_EQUAL_TO, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be LIKE the supplied literal
value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isLike( Object literal ) {
+ return is(Operator.LIKE, literal);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint to be not equal to the supplied
literal value.
+ *
+ * @param literal the literal value
+ * @return the builder used to create the constraint clause, ready to be used to
create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder isNotEqualTo( Object literal ) {
+ return is(Operator.NOT_EQUAL_TO, literal);
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryBuilder.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryContext.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryContext.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryContext.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.SimpleProblems;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ *
+ */
+public final class QueryContext {
+ private final ExecutionContext context;
+ private final PlanHints hints;
+ private final Schemata schemata;
+ private final Problems problems;
+ private final Map<String, Object> variables;
+
+ public QueryContext( ExecutionContext context,
+ PlanHints hints,
+ Schemata schemata,
+ Problems problems,
+ Map<String, Object> variables ) {
+ CheckArg.isNotNull(context, "context");
+ this.context = context;
+ this.hints = hints != null ? hints : new PlanHints();
+ this.schemata = schemata;
+ this.problems = problems != null ? problems : new SimpleProblems();
+ this.variables = variables != null ? Collections.<String,
Object>unmodifiableMap(new HashMap<String, Object>(variables)) :
Collections.<String, Object>emptyMap();
+ }
+
+ public QueryContext( ExecutionContext context,
+ PlanHints hints,
+ Schemata schemata,
+ Problems problems ) {
+ this(context, hints, schemata, problems, null);
+ }
+
+ public QueryContext( ExecutionContext context,
+ PlanHints hints,
+ Schemata schemata ) {
+ this(context, hints, schemata, null, null);
+ }
+
+ /**
+ * @return context
+ */
+ public final ExecutionContext getExecutionContext() {
+ return context;
+ }
+
+ /**
+ * @return hints
+ */
+ public final PlanHints getHints() {
+ return hints;
+ }
+
+ /**
+ * @return problems
+ */
+ public final Problems getProblems() {
+ return problems;
+ }
+
+ /**
+ * @return schemata
+ */
+ public Schemata getSchemata() {
+ return schemata;
+ }
+
+ /**
+ * Get the variables that are to be substituted into the {@link BindVariableName}
used in the query.
+ *
+ * @return immutable map of variable values keyed by their name; never null
+ */
+ public Map<String, Object> getVariables() {
+ return variables;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryContext.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,165 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.QueryResults.Statistics;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.optimize.Optimizer;
+import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.Planner;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Traversal;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.process.Processor;
+import org.jboss.dna.graph.query.process.QueryResultColumns;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ * A query engine that is able to execute formal queries expressed in the Graph API's
{@link QueryCommand Abstract Query Model}.
+ */
+@ThreadSafe
+public class QueryEngine {
+
+ private final Planner planner;
+ private final Optimizer optimizer;
+ private final Processor processor;
+ private final Schemata schemata;
+
+ public QueryEngine( Planner planner,
+ Optimizer optimizer,
+ Processor processor,
+ Schemata schemata ) {
+ CheckArg.isNotNull(processor, "processor");
+ this.planner = planner != null ? planner : new CanonicalPlanner();
+ this.optimizer = optimizer != null ? optimizer : new RuleBasedOptimizer();
+ this.processor = processor;
+ this.schemata = schemata != null ? schemata : new Schemata() {
+ public Table getTable( SelectorName name ) {
+ // This won't allow the query engine to do anything (or much of
anything),
+ // but it is legal and will result in meaningful problems
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param query the query that is to be executed
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ public QueryResults execute( ExecutionContext context,
+ QueryCommand query ) {
+ return execute(context, query, new PlanHints());
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param context the context in which the query should be executed
+ * @param query the query that is to be executed
+ * @param hints the hints for the execution; may be null if there are no hints
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ public QueryResults execute( ExecutionContext context,
+ QueryCommand query,
+ PlanHints hints ) {
+ QueryContext queryContext = new QueryContext(context, hints, schemata);
+
+ // Create the canonical plan ...
+ long start = System.nanoTime();
+ PlanNode plan = planner.createPlan(queryContext, query);
+ long duration = System.nanoTime() - start;
+ Statistics stats = new Statistics(duration);
+
+ QueryResultColumns resultColumns = QueryResultColumns.empty();
+ if (!queryContext.getProblems().hasErrors()) {
+ // Optimize the plan ...
+ start = System.nanoTime();
+ PlanNode optimizedPlan = optimizer.optimize(queryContext, plan);
+ duration = System.nanoTime() - start;
+ stats = stats.withOptimizationTime(duration);
+
+ // Find the query result columns ...
+ start = System.nanoTime();
+ resultColumns = determineQueryResultColumns(optimizedPlan);
+ duration = System.nanoTime() - start;
+ stats = stats.withOptimizationTime(duration);
+
+ if (!queryContext.getProblems().hasErrors()) {
+ // Execute the plan ...
+ try {
+ start = System.nanoTime();
+ return processor.execute(queryContext, query, stats, optimizedPlan);
+ } finally {
+ duration = System.nanoTime() - start;
+ stats = stats.withOptimizationTime(duration);
+ }
+ }
+ }
+ // There were problems somewhere ...
+ return new org.jboss.dna.graph.query.process.QueryResults(queryContext, query,
resultColumns, stats);
+ }
+
+ protected QueryResultColumns determineQueryResultColumns( PlanNode optimizedPlan ) {
+ // Look for which columns to include in the results; this will be defined by the
highest PROJECT node ...
+ PlanNode project = optimizedPlan.findAtOrBelow(Traversal.LEVEL_ORDER,
Type.PROJECT);
+ if (project != null) {
+ List<Column> columns =
project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ // Determine whether to include the full-text search scores in the results
...
+ final AtomicBoolean includeFullTextSearchScores = new AtomicBoolean(false);
+ for (PlanNode select : optimizedPlan.findAllAtOrBelow(Type.SELECT)) {
+ Constraint constraint = select.getProperty(Property.SELECT_CRITERIA,
Constraint.class);
+ if (constraint != null) {
+ Visitors.visitAll(constraint, new Visitors.AbstractVisitor() {
+ @Override
+ public void visit( FullTextSearch obj ) {
+ includeFullTextSearchScores.set(true);
+ }
+ });
+ }
+ if (includeFullTextSearchScores.get()) break;
+ }
+ return new QueryResultColumns(columns, includeFullTextSearchScores.get());
+ }
+ return QueryResultColumns.empty();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryEngine.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,651 @@
+/*
+ * 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;
+
+import java.text.DecimalFormat;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.QueryCommand;
+
+/**
+ * The resulting output of a query.
+ */
+@Immutable
+public interface QueryResults {
+
+ /**
+ * Get the execution context in which the query was evaluated.
+ *
+ * @return the execution context; never null
+ */
+ public ExecutionContext getContext();
+
+ /**
+ * Get the original query command.
+ *
+ * @return the query; never null
+ */
+ public QueryCommand getCommand();
+
+ /**
+ * Get the description of the columns contained in these results. These columns can
be used to discover the indexes of the
+ * corresponding values from the arrays representing the {@link #getTuples()
tuples}.
+ *
+ * @return the column descriptions; never null
+ */
+ public Columns getColumns();
+
+ /**
+ * Get a cursor that can be used to walk through the results.
+ *
+ * @return the cursor; never null, though possibly empty (meaning {@link
Cursor#hasNext()} may return true)
+ */
+ public Cursor getCursor();
+
+ /**
+ * Get the actual tuples that contain the results. Each element in the list
represents a tuple, and each tuple corresponds to
+ * the column definitions.
+ *
+ * @return the list of tuples; never null but possibly empty
+ */
+ public List<Object[]> getTuples();
+
+ /**
+ * Get the problems encountered during execution.
+ *
+ * @return the problems; never null but possibly empty
+ */
+ public Problems getProblems();
+
+ /**
+ * Return true if there is at least one error recorded in the {@link #getProblems()
problems}.
+ *
+ * @return true if there is one or more errors associated with the query, or false
otherwise
+ */
+ public boolean hasErrors();
+
+ /**
+ * Return true if there is at least one warning recorded in the {@link #getProblems()
problems}.
+ *
+ * @return true if there is one or more warnings associated with the query, or false
otherwise
+ */
+ public boolean hasWarnings();
+
+ /**
+ * Get the statistics that describe the time metrics for this query.
+ *
+ * @return the statistics; never null
+ */
+ public Statistics getStatistics();
+
+ /**
+ * An interface used to walk through the results.
+ */
+ public interface Cursor {
+ /**
+ * Determine whether this cursor can be moved from its current position to the
next row.
+ *
+ * @return true if there is another row, or false otherwise
+ */
+ boolean hasNext();
+
+ /**
+ * Move this cursor position to the next row. obtained for
+ *
+ * @throws NoSuchElementException if there is no next row that the cursor can
point to.
+ */
+ void next();
+
+ /**
+ * Get the 0-based index of the current row.
+ *
+ * @return the index of the current row; never negative
+ * @exception IllegalStateException if the <tt>next()</tt> method has
not yet been called
+ */
+ int getRowIndex();
+
+ /**
+ * Get from the current row the location of the node from which the value in the
given column was taken.
+ *
+ * @param columnNumber the column number (0-based)
+ * @return the location of the node; never null
+ * @throws IndexOutOfBoundsException if the column number is negative or larger
than the number of columns
+ * @throws IllegalStateException if the <tt>next()</tt> method has
not yet been called, or if there are no results
+ */
+ Location getLocation( int columnNumber );
+
+ /**
+ * Get from the current row the location of the node that was produced by the
named selector.
+ *
+ * @param selectorName the name of the selector that resulted in a node appearing
in the current row
+ * @return the location of the node; or null if there is no node corresponding to
the named selector for the current row
+ * @throws NoSuchElementException if the selector name does not correspond to an
available selector
+ * @throws IllegalStateException if the <tt>next()</tt> method has
not yet been called, or if there are no results
+ */
+ Location getLocation( String selectorName );
+
+ /**
+ * Get from the current row the value in the given column.
+ *
+ * @param columnNumber the column number (0-based)
+ * @return the value; possibly null
+ * @throws IndexOutOfBoundsException if the column number is negative or larger
than the number of columns
+ * @throws IllegalStateException if the <tt>next()</tt> method has
not yet been called, or if there are no results
+ */
+ Object getValue( int columnNumber );
+
+ /**
+ * Get the value in the named column.
+ *
+ * @param columnName the name of the column
+ * @return the value; possibly null
+ * @throws NoSuchElementException if the column name does not correspond to an
available column
+ * @throws IllegalStateException if the <tt>next()</tt> method has
not yet been called, or if there are no results
+ */
+ Object getValue( String columnName );
+ }
+
+ /**
+ * Definition of the columns that are available in the results, which outline the
structure of the
+ * {@link QueryResults#getTuples() tuples} in the results, and which can be used to
access the individual values in each of
+ * the tuples.
+ */
+ public interface Columns {
+ /**
+ * Get the columns.
+ *
+ * @return the immutable list of columns, with size equal to {@link
#getColumnCount()}; never null
+ */
+ public List<Column> getColumns();
+
+ /**
+ * Get the names of the columns.
+ *
+ * @return the immutable list of column names, with size equal to {@link
#getColumnCount()}; never null
+ */
+ public List<String> getColumnNames();
+
+ /**
+ * Get the number of columns in each tuple.
+ *
+ * @return the number of columns; always positive
+ */
+ public int getColumnCount();
+
+ /**
+ * Get the number of {@link Location} objects in each tuple.
+ *
+ * @return the number of Location objects; always positive
+ */
+ public int getLocationCount();
+
+ /**
+ * Get the names of the selectors that are associated with these results. These
results contain a single {@link Location}
+ * object for each of the selectors.
+ *
+ * @return the immutable list of selector names, with size equal to {@link
#getLocationCount()}; never null
+ */
+ public List<String> getSelectorNames();
+
+ /**
+ * Get the size of the tuple arrays.
+ *
+ * @return the length of each tuple array
+ */
+ public int getTupleSize();
+
+ /**
+ * Get the names of the all of the tuple values.
+ *
+ * @return the immutable list of names
+ */
+ public List<String> getTupleValueNames();
+
+ /**
+ * Get the index of a tuple's correct Location object given the column
index.
+ *
+ * @param columnIndex the column index
+ * @return the Location index that corresponds to the supplied column; never
negative
+ * @throws IndexOutOfBoundsException if the column index is invalid
+ */
+ public int getLocationIndexForColumn( int columnIndex );
+
+ /**
+ * Get the index of a tuple's correct Location object given the column
index.
+ *
+ * @param columnName the column name
+ * @return the Location index that corresponds to the supplied column; never
negative
+ * @throws NoSuchElementException if the column name is invalid
+ */
+ public int getLocationIndexForColumn( String columnName );
+
+ /**
+ * Get the index of a tuple's correct Location object given the name of the
selector used in the query.
+ *
+ * @param selectorName the selector name
+ * @return the Location index that corresponds to the supplied column; never
negative
+ * @throws NoSuchElementException if the selector name is invalid
+ */
+ public int getLocationIndex( String selectorName );
+
+ /**
+ * Determine if these results contain values from the selector with the supplied
name.
+ *
+ * @param selectorName the selector name
+ * @return true if the results have values from the supplied selector, or false
otherwise
+ */
+ public boolean hasSelector( String selectorName );
+
+ /**
+ * Get the name of the property that corresponds to the supplied column in each
tuple.
+ *
+ * @param columnIndex the column index
+ * @return the property name; never null
+ * @throws IndexOutOfBoundsException if the column index is invalid
+ */
+ public Name getPropertyNameForColumn( int columnIndex );
+
+ /**
+ * Get the name of the property that corresponds to the supplied column in each
tuple.
+ *
+ * @param columnName the column name
+ * @return the property name; never null
+ * @throws NoSuchElementException if the column name is invalid or doesn't
match an existing column
+ */
+ public Name getPropertyNameForColumn( String columnName );
+
+ /**
+ * Get the index of the column given the column name.
+ *
+ * @param columnName the column name
+ * @return the column index
+ * @throws NoSuchElementException if the column name is invalid or doesn't
match an existing column
+ */
+ public int getColumnIndexForName( String columnName );
+
+ /**
+ * Get the index of the column given the name of the selector and the property
name from where the column should be
+ * obtained.
+ *
+ * @param selectorName the selector name
+ * @param propertyName the name of the property
+ * @return the column index that corresponds to the supplied column; never
negative
+ * @throws NoSuchElementException if the selector name or the property name are
invalid
+ */
+ public int getColumnIndexForProperty( String selectorName,
+ Name propertyName );
+
+ /**
+ * Get the index of the tuple value containing the full-text search score for the
node taken from the named selector.
+ *
+ * @param selectorName the selector name
+ * @return the index that corresponds to the {@link Double} full-text search
score, or -1 if there is no full-text search
+ * score for the named selector
+ * @throws NoSuchElementException if the selector name is invalid
+ */
+ public int getFullTextSearchScoreIndexFor( String selectorName );
+
+ /**
+ * Determine whether these results include full-text search scores.
+ *
+ * @return true if the full-text search scores are included in the results, or
false otherwise
+ */
+ public boolean hasFullTextSearchScores();
+
+ /**
+ * Determine whether this mapping includes all of the columns (and locations) in
the supplied mapping.
+ *
+ * @param other the other mapping; may not be null
+ * @return true if all of the other mapping's columns and locations are
included in this mapping, or false otherwise
+ */
+ public boolean includes( Columns other );
+
+ /**
+ * Determine whether this column and the other are
<i>union-compatible</i> (that is, having the same columns).
+ *
+ * @param other the other mapping; may not be null
+ * @return true if this and the supplied columns definition are union-compatible,
or false if they are not
+ */
+ public boolean isUnionCompatible( Columns other );
+
+ /**
+ * Obtain a new definition for the query results that can be used to reference
the same tuples that use this columns
+ * definition, but that defines a subset of the columns in this definition. This
is useful in a PROJECT operation, since
+ * that reduces the number of columns.
+ *
+ * @param columns the new columns, which must be a subset of the columns in this
definition; may not be null
+ * @return the new columns definition; never null
+ */
+ public Columns subSelect( List<Column> columns );
+
+ /**
+ * Obtain a new definition for the query results that can be used to reference
the same tuples that use this columns
+ * definition, but that defines a subset of the columns in this definition. This
is useful in a PROJECT operation, since
+ * that reduces the number of columns.
+ *
+ * @param columns the new columns, which must be a subset of the columns in this
definition; may not be null
+ * @return the new columns definition; never null
+ */
+ public Columns subSelect( Column... columns );
+ }
+
+ public static class Statistics implements Comparable<Statistics> {
+ protected static final Statistics EMPTY_STATISTICS = new Statistics();
+
+ private final long planningNanos;
+ private final long optimizationNanos;
+ private final long resultFormulationNanos;
+ private final long executionNanos;
+
+ private Statistics() {
+ this(0L, 0L, 0L, 0L);
+ }
+
+ protected Statistics( long planningNanos ) {
+ this(planningNanos, 0L, 0L, 0L);
+ }
+
+ protected Statistics( long planningNanos,
+ long optimizationNanos,
+ long resultFormulationNanos,
+ long executionNanos ) {
+ this.planningNanos = planningNanos;
+ this.optimizationNanos = optimizationNanos;
+ this.resultFormulationNanos = resultFormulationNanos;
+ this.executionNanos = executionNanos;
+ }
+
+ /**
+ * Get the time required to come up with the canonical plan.
+ *
+ * @param unit the time unit that should be used
+ * @return the time to plan, in the desired units
+ * @throws IllegalArgumentException if the unit is null
+ */
+ public long getPlanningTime( TimeUnit unit ) {
+ CheckArg.isNotNull(unit, "unit");
+ return unit.convert(planningNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Get the time required to determine or select a (more) optimal plan.
+ *
+ * @param unit the time unit that should be used
+ * @return the time to determine an optimal plan, in the desired units
+ * @throws IllegalArgumentException if the unit is null
+ */
+ public long getOptimizationTime( TimeUnit unit ) {
+ CheckArg.isNotNull(unit, "unit");
+ return unit.convert(optimizationNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Get the time required to formulate the structure of the results.
+ *
+ * @param unit the time unit that should be used
+ * @return the time to formulate the results, in the desired units
+ * @throws IllegalArgumentException if the unit is null
+ */
+ public long getResultFormulationTime( TimeUnit unit ) {
+ CheckArg.isNotNull(unit, "unit");
+ return unit.convert(resultFormulationNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Get the time required to execute the query.
+ *
+ * @param unit the time unit that should be used
+ * @return the time to execute the query, in the desired units
+ * @throws IllegalArgumentException if the unit is null
+ */
+ public long getExecutionTime( TimeUnit unit ) {
+ return unit.convert(executionNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Get the time required to execute the query.
+ *
+ * @param unit the time unit that should be used
+ * @return the time to execute the query, in the desired units
+ * @throws IllegalArgumentException if the unit is null
+ */
+ public long getTotalTime( TimeUnit unit ) {
+ return unit.convert(totalTime(), TimeUnit.NANOSECONDS);
+ }
+
+ protected long totalTime() {
+ return planningNanos + optimizationNanos + resultFormulationNanos +
executionNanos;
+ }
+
+ /**
+ * Create a new statistics object that has the supplied planning time.
+ *
+ * @param planningNanos the number of nanoseconds required by planning
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the time value is negative
+ */
+ public Statistics withPlanningTime( long planningNanos ) {
+ CheckArg.isNonNegative(planningNanos, "planningNanos");
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied optimization time.
+ *
+ * @param optimizationNanos the number of nanoseconds required by optimization
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the time value is negative
+ */
+ public Statistics withOptimizationTime( long optimizationNanos ) {
+ CheckArg.isNonNegative(optimizationNanos, "optimizationNanos");
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied result formulation time.
+ *
+ * @param resultFormulationNanos the number of nanoseconds required by result
formulation
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the time value is negative
+ */
+ public Statistics withResultsFormulationTime( long resultFormulationNanos ) {
+ CheckArg.isNonNegative(resultFormulationNanos,
"resultFormulationNanos");
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied execution time.
+ *
+ * @param executionNanos the number of nanoseconds required to execute the query
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the time value is negative
+ */
+ public Statistics withExecutionTime( long executionNanos ) {
+ CheckArg.isNonNegative(executionNanos, "executionNanos");
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied planning time.
+ *
+ * @param planning the time required to plan the query
+ * @param unit the time unit
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the unit is null or if the time value is
negative
+ */
+ public Statistics withPlanningTime( long planning,
+ TimeUnit unit ) {
+ CheckArg.isNonNegative(planning, "planning");
+ CheckArg.isNotNull(unit, "unit");
+ long planningNanos = TimeUnit.NANOSECONDS.convert(planning, unit);
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied optimization time.
+ *
+ * @param optimization the time required by optimization
+ * @param unit the time unit
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the unit is null or if the time value is
negative
+ */
+ public Statistics withOptimizationTime( long optimization,
+ TimeUnit unit ) {
+ CheckArg.isNonNegative(optimization, "optimization");
+ CheckArg.isNotNull(unit, "unit");
+ long optimizationNanos = TimeUnit.NANOSECONDS.convert(optimization, unit);
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied result formulation time.
+ *
+ * @param resultFormulation the time required to formulate the results
+ * @param unit the time unit
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the unit is null or if the time value is
negative
+ */
+ public Statistics withResultsFormulationTime( long resultFormulation,
+ TimeUnit unit ) {
+ CheckArg.isNonNegative(resultFormulation, "resultFormulation");
+ CheckArg.isNotNull(unit, "unit");
+ long resultFormulationNanos = TimeUnit.NANOSECONDS.convert(resultFormulation,
unit);
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * Create a new statistics object that has the supplied execution time.
+ *
+ * @param execution the time required to execute the query
+ * @param unit the time unit
+ * @return the new statistics object; never null
+ * @throws IllegalArgumentException if the unit is null or if the time value is
negative
+ */
+ public Statistics withExecutionTime( long execution,
+ TimeUnit unit ) {
+ CheckArg.isNonNegative(execution, "execution");
+ CheckArg.isNotNull(unit, "unit");
+ long executionNanos = TimeUnit.NANOSECONDS.convert(execution, unit);
+ return new Statistics(planningNanos, optimizationNanos,
resultFormulationNanos, executionNanos);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo( Statistics that ) {
+ if (that == this) return 0;
+ long diff = this.totalTime() - that.totalTime();
+ if (diff < 0) return -1;
+ if (diff > 0) return 1;
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ readable(totalTime(), sb);
+ boolean first = false;
+ if (planningNanos != 0L) {
+ sb.append(" (plan=");
+ readable(planningNanos, sb);
+ first = false;
+ }
+ if (optimizationNanos != 0L) {
+ if (first) {
+ first = false;
+ sb.append(" (");
+ } else {
+ sb.append(" ,");
+ }
+ sb.append("opt=");
+ readable(optimizationNanos, sb);
+ }
+ if (resultFormulationNanos != 0L) {
+ if (first) {
+ first = false;
+ sb.append(" (");
+ } else {
+ sb.append(" ,");
+ }
+ sb.append("res=");
+ readable(resultFormulationNanos, sb);
+ }
+ if (executionNanos != 0L) {
+ if (first) {
+ first = false;
+ sb.append(" (");
+ } else {
+ sb.append(" ,");
+ }
+ sb.append("exec=");
+ readable(executionNanos, sb);
+ }
+ if (!first) sb.append(')');
+ return sb.toString();
+ }
+
+ protected void readable( long nanos,
+ StringBuilder sb ) {
+ // 3210987654321
+ // XXXXXXXXXXXXX nanos
+ // XXXXXXXXXX micros
+ // XXXXXXX millis
+ // XXXX seconds
+ if (nanos < 1000) {
+ sb.append(nanos).append(" ns");
+ } else if (nanos < 1000000) {
+ double value = nanos / 1000d;
+ sb.append(FORMATTER.get().format(value)).append(" usec");
+ } else if (nanos < 1000000000) {
+ double value = nanos / 1000000d;
+ sb.append(FORMATTER.get().format(value)).append(" ms");
+ } else {
+ double value = nanos / 1000000000d;
+ sb.append(FORMATTER.get().format(value)).append(" sec");
+ }
+ }
+ }
+
+ static ThreadLocal<DecimalFormat> FORMATTER = new
ThreadLocal<DecimalFormat>() {
+ @Override
+ protected synchronized DecimalFormat initialValue() {
+ return new DecimalFormat("###,###,##0.0##");
+ }
+ };
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/QueryResults.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,83 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.ObjectUtil;
+
+/**
+ * A selector that represents a source that returns all nodes.
+ */
+@Immutable
+public class AllNodes extends Selector {
+
+ public static final SelectorName ALL_NODES_NAME = new
SelectorName("__ALLNODES__");
+
+ public AllNodes() {
+ super(ALL_NODES_NAME);
+ }
+
+ /**
+ * Create a selector with the supplied alias.
+ *
+ * @param alias the alias for this selector; may be null
+ */
+ public AllNodes( SelectorName alias ) {
+ super(ALL_NODES_NAME, alias);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof AllNodes) {
+ AllNodes that = (AllNodes)obj;
+ return ObjectUtil.isEqualWithNulls(this.getAlias(), that.getAlias());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/AllNodes.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/And.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/And.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/And.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,93 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A constraint that evaluates to true when <i>both</i> of the other
constraints evaluate to true.
+ */
+@Immutable
+public class And extends Constraint {
+
+ private final Constraint left;
+ private final Constraint right;
+
+ public And( Constraint left,
+ Constraint right ) {
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(right, "right");
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * @return left
+ */
+ public final Constraint getLeft() {
+ return left;
+ }
+
+ /**
+ * @return right
+ */
+ public final Constraint getRight() {
+ return right;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof And) {
+ And that = (And)obj;
+ return left.equals(that.left) && right.equals(that.right);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/And.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/BindVariableName.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/BindVariableName.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/BindVariableName.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,82 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A value bound to a variable name used in a {@link Comparison} constraint.
+ */
+@Immutable
+public class BindVariableName extends StaticOperand {
+
+ private final String variableName;
+
+ public BindVariableName( String variableName ) {
+ CheckArg.isNotNull(variableName, "variableName");
+ this.variableName = variableName;
+ }
+
+ /**
+ * @return variableName
+ */
+ public final String getVariableName() {
+ return variableName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof BindVariableName) {
+ BindVariableName that = (BindVariableName)obj;
+ return this.variableName.equals(that.variableName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/BindVariableName.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNode.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNode.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,102 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * A constraint requiring that the selected node is a child of the node reachable by the
supplied absolute path
+ */
+@Immutable
+public class ChildNode extends Constraint {
+ private final SelectorName selectorName;
+ private final Path parentPath;
+
+ /**
+ * Create a constraint requiring that the node identified by the selector is a child
of the node reachable by the supplied
+ * absolute path.
+ *
+ * @param selectorName the name of the selector
+ * @param parentPath the absolute path to the parent
+ */
+ public ChildNode( SelectorName selectorName,
+ Path parentPath ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(parentPath, "parentPath");
+ this.selectorName = selectorName;
+ this.parentPath = parentPath;
+ }
+
+ /**
+ * @return selectorName
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * @return parentPath
+ */
+ public final Path getParentPath() {
+ return parentPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof ChildNode) {
+ ChildNode that = (ChildNode)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!this.parentPath.equals(that.parentPath)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNode.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNodeJoinCondition.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNodeJoinCondition.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNodeJoinCondition.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,101 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A join condition that evaluates to true only when the named child node is indeed a
child of the named parent node.
+ */
+@Immutable
+public class ChildNodeJoinCondition extends JoinCondition {
+ private final SelectorName childSelectorName;
+ private final SelectorName parentSelectorName;
+
+ /**
+ * Create a join condition that determines whether the node identified by the child
selector is a child of the node identified
+ * by the parent selector.
+ *
+ * @param parentSelectorName the first selector
+ * @param childSelectorName the second selector
+ */
+ public ChildNodeJoinCondition( SelectorName parentSelectorName,
+ SelectorName childSelectorName ) {
+ CheckArg.isNotNull(childSelectorName, "childSelectorName");
+ CheckArg.isNotNull(parentSelectorName, "parentSelectorName");
+ this.childSelectorName = childSelectorName;
+ this.parentSelectorName = parentSelectorName;
+ }
+
+ /**
+ * @return childSelectorName
+ */
+ public final SelectorName getChildSelectorName() {
+ return childSelectorName;
+ }
+
+ /**
+ * @return parentSelectorName
+ */
+ public final SelectorName getParentSelectorName() {
+ return parentSelectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition that = (ChildNodeJoinCondition)obj;
+ if (!this.childSelectorName.equals(that.childSelectorName)) return false;
+ if (!this.parentSelectorName.equals(that.parentSelectorName)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/ChildNodeJoinCondition.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: 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
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,128 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.property.Name;
+
+/**
+ *
+ */
+@Immutable
+public class Column implements LanguageObject {
+
+ private final SelectorName selectorName;
+ private final Name propertyName;
+ private final String columnName;
+
+ /**
+ * Include a column for each of the single-valued, accessible properties on the node
identified by the selector.
+ *
+ * @param selectorName the selector name
+ */
+ public Column( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ this.propertyName = null;
+ this.columnName = null;
+ }
+
+ /**
+ * A column with the given name representing the named property on the node
identified by the selector.
+ *
+ * @param selectorName the selector name
+ * @param propertyName the name of the property
+ * @param columnName the name of the column
+ */
+ public Column( SelectorName selectorName,
+ Name propertyName,
+ String columnName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(propertyName, "propertyName");
+ CheckArg.isNotNull(columnName, "columnName");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.columnName = columnName;
+ }
+
+ /**
+ * @return selectorName
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * @return propertyName
+ */
+ public final Name getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * @return columnName
+ */
+ public final String getColumnName() {
+ return columnName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Column) {
+ Column that = (Column)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.propertyName, that.propertyName))
return false;
+ if (!ObjectUtil.isEqualWithNulls(this.columnName, that.columnName)) return
false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Column.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Command.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Command.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Command.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,31 @@
+/*
+ * 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.model;
+
+/**
+ * Represents the abstract base class for all top-level language objects that are the
root of a language object tree.
+ */
+public abstract class Command implements LanguageObject {
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Command.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Comparison.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Comparison.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Comparison.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,107 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A constraint that evaluates to true when the defined operation evaluates to true.
+ */
+@Immutable
+public class Comparison extends Constraint {
+
+ private final DynamicOperand operand1;
+ private final StaticOperand operand2;
+ private final Operator operator;
+
+ public Comparison( DynamicOperand operand1,
+ Operator operator,
+ StaticOperand operand2 ) {
+ CheckArg.isNotNull(operand1, "operand1");
+ CheckArg.isNotNull(operator, "operator");
+ CheckArg.isNotNull(operand2, "operand2");
+ this.operand1 = operand1;
+ this.operand2 = operand2;
+ this.operator = operator;
+ }
+
+ /**
+ * @return operand1
+ */
+ public final DynamicOperand getOperand1() {
+ return operand1;
+ }
+
+ /**
+ * @return operand2
+ */
+ public final StaticOperand getOperand2() {
+ return operand2;
+ }
+
+ /**
+ * @return operator
+ */
+ public final Operator getOperator() {
+ return operator;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Comparison) {
+ Comparison that = (Comparison)obj;
+ if (!this.operator.equals(that.operator)) return false;
+ if (!this.operand1.equals(that.operand1)) return false;
+ if (!this.operand2.equals(that.operand2)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Comparison.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Constraint.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Constraint.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Constraint.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ *
+ */
+@Immutable
+public abstract class Constraint implements LanguageObject {
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Constraint.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNode.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNode.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,102 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * A constraint requiring that the selected node is a descendant of the node reachable by
the supplied absolute path
+ */
+@Immutable
+public class DescendantNode extends Constraint {
+ private final SelectorName selectorName;
+ private final Path ancestorPath;
+
+ /**
+ * Create a constraint requiring that the node identified by the selector is a
descendant of the node reachable by the
+ * supplied absolute path.
+ *
+ * @param selectorName the name of the selector
+ * @param ancestorPath the absolute path to the ancestor
+ */
+ public DescendantNode( SelectorName selectorName,
+ Path ancestorPath ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(ancestorPath, "ancestorPath");
+ this.selectorName = selectorName;
+ this.ancestorPath = ancestorPath;
+ }
+
+ /**
+ * @return selectorName
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * @return ancestorPath
+ */
+ public final Path getAncestorPath() {
+ return ancestorPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof DescendantNode) {
+ DescendantNode that = (DescendantNode)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!this.ancestorPath.equals(that.ancestorPath)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNode.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNodeJoinCondition.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNodeJoinCondition.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNodeJoinCondition.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,101 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A join condition that evaluates to true only when the named node is a descendant of
another named node.
+ */
+@Immutable
+public class DescendantNodeJoinCondition extends JoinCondition {
+ private final SelectorName descendantSelectorName;
+ private final SelectorName ancestorSelectorName;
+
+ /**
+ * Create a join condition that determines whether the node identified by the
descendant selector is indeed a descendant of
+ * the node identified by the ancestor selector.
+ *
+ * @param ancestorSelectorName the name of the ancestor selector
+ * @param descendantSelectorName the name of the descendant selector
+ */
+ public DescendantNodeJoinCondition( SelectorName ancestorSelectorName,
+ SelectorName descendantSelectorName ) {
+ CheckArg.isNotNull(descendantSelectorName, "descendantSelectorName");
+ CheckArg.isNotNull(ancestorSelectorName, "ancestorSelectorName");
+ this.descendantSelectorName = descendantSelectorName;
+ this.ancestorSelectorName = ancestorSelectorName;
+ }
+
+ /**
+ * @return descendantSelectorName
+ */
+ public final SelectorName getDescendantSelectorName() {
+ return descendantSelectorName;
+ }
+
+ /**
+ * @return ancestorSelectorName
+ */
+ public final SelectorName getAncestorSelectorName() {
+ return ancestorSelectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition that = (DescendantNodeJoinCondition)obj;
+ if (!this.descendantSelectorName.equals(that.descendantSelectorName)) return
false;
+ if (!this.ancestorSelectorName.equals(that.ancestorSelectorName)) return
false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DescendantNodeJoinCondition.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,40 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ * A dynamic operand used in a {@link Comparison} constraint.
+ */
+@Immutable
+public abstract class DynamicOperand implements LanguageObject {
+
+ /**
+ * Get the selector symbol to which this operand applies.
+ *
+ * @return the selector name; never null
+ */
+ public abstract SelectorName getSelectorName();
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/DynamicOperand.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/EquiJoinCondition.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/EquiJoinCondition.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/EquiJoinCondition.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,130 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Name;
+
+/**
+ * A join condition that tests whether a property on a node is equal to a property on
another node. A node-tuple satisfies the
+ * constraint only if:
+ * <ul>
+ * <li>the {@code selector1Name} node has a property named {@code property1Name},
and</li>
+ * <li>the {@code selector2Name} node has a property named {@code property2Name},
and</li>
+ * <li>the value of property {@code property1Name} is equal to the value of
property {@code property2Name}</li>
+ * </ul>
+ */
+@Immutable
+public class EquiJoinCondition extends JoinCondition {
+ private final SelectorName selector1Name;
+ private final Name property1Name;
+ private final SelectorName selector2Name;
+ private final Name property2Name;
+
+ public EquiJoinCondition( SelectorName selector1Name,
+ Name property1Name,
+ SelectorName selector2Name,
+ Name property2Name ) {
+ CheckArg.isNotNull(selector1Name, "selector1Name");
+ CheckArg.isNotNull(property1Name, "property1Name");
+ CheckArg.isNotNull(selector2Name, "selector2Name");
+ CheckArg.isNotNull(property2Name, "property2Name");
+ this.selector1Name = selector1Name;
+ this.property1Name = property1Name;
+ this.selector2Name = selector2Name;
+ this.property2Name = property2Name;
+ }
+
+ public EquiJoinCondition( Column column1,
+ Column column2 ) {
+ this(column1.getSelectorName(), column1.getPropertyName(),
column2.getSelectorName(), column2.getPropertyName());
+ }
+
+ /**
+ * @return selector1Name
+ */
+ public final SelectorName getSelector1Name() {
+ return selector1Name;
+ }
+
+ /**
+ * @return property1Name
+ */
+ public final Name getProperty1Name() {
+ return property1Name;
+ }
+
+ /**
+ * @return selector2Name
+ */
+ public final SelectorName getSelector2Name() {
+ return selector2Name;
+ }
+
+ /**
+ * @return property2Name
+ */
+ public final Name getProperty2Name() {
+ return property2Name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof EquiJoinCondition) {
+ EquiJoinCondition that = (EquiJoinCondition)obj;
+ if (!this.selector1Name.equals(that.selector1Name)) return false;
+ if (!this.selector2Name.equals(that.selector2Name)) return false;
+ if (!this.property1Name.equals(that.property1Name)) return false;
+ if (!this.property2Name.equals(that.property2Name)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/EquiJoinCondition.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearch.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearch.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearch.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,388 @@
+/*
+ * 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.model;
+
+import java.util.Iterator;
+import java.util.List;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.parse.FullTextSearchParser;
+
+/**
+ * A constraint that evaluates to true only when a full-text search applied to the search
scope results in positive findings. If a
+ * property name is supplied, then the search is limited to the value(s) of the named
property on the node(s) in the search scope.
+ */
+@Immutable
+public class FullTextSearch extends Constraint {
+ private final SelectorName selectorName;
+ private final Name propertyName;
+ private final String fullTextSearchExpression;
+ private Term term;
+
+ /**
+ * Create a constraint defining a full-text search against the property values on
node within the search scope.
+ *
+ * @param selectorName the name of the node selector defining the search scope
+ * @param propertyName the name of the property to be searched; may be null if all
property values are to be searched
+ * @param fullTextSearchExpression the search expression
+ * @param term the term representation, if it is known; may be null
+ */
+ public FullTextSearch( SelectorName selectorName,
+ Name propertyName,
+ String fullTextSearchExpression,
+ Term term ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotEmpty(fullTextSearchExpression,
"fullTextSearchExpression");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.fullTextSearchExpression = fullTextSearchExpression;
+ }
+
+ /**
+ * Create a constraint defining a full-text search against the property values on
node within the search scope.
+ *
+ * @param selectorName the name of the node selector defining the search scope
+ * @param propertyName the name of the property to be searched; may be null if all
property values are to be searched
+ * @param fullTextSearchExpression the search expression
+ */
+ public FullTextSearch( SelectorName selectorName,
+ Name propertyName,
+ String fullTextSearchExpression ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotEmpty(fullTextSearchExpression,
"fullTextSearchExpression");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.fullTextSearchExpression = fullTextSearchExpression;
+ this.term = null;
+ }
+
+ /**
+ * Create a constraint defining a full-text search against the node within the search
scope.
+ *
+ * @param selectorName the name of the node selector defining the search scope
+ * @param fullTextSearchExpression the search expression
+ */
+ public FullTextSearch( SelectorName selectorName,
+ String fullTextSearchExpression ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotEmpty(fullTextSearchExpression,
"fullTextSearchExpression");
+ this.selectorName = selectorName;
+ this.propertyName = null;
+ this.fullTextSearchExpression = fullTextSearchExpression;
+ }
+
+ /**
+ * @return selectorName
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * @return propertyName
+ */
+ public final Name getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * @return fullTextSearchExpression
+ */
+ public final String getFullTextSearchExpression() {
+ return fullTextSearchExpression;
+ }
+
+ /**
+ * Get the formal {@link Term} representation of the expression.
+ *
+ * @return the term representing this search; never null
+ * @throws ParsingException if there is an error producing the term representation
+ */
+ public Term getTerm() {
+ // Idempotent, so okay to not lock/synchronize ...
+ if (term == null) {
+ term = new FullTextSearchParser().parse(fullTextSearchExpression);
+ }
+ return term;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof FullTextSearch) {
+ FullTextSearch that = (FullTextSearch)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.propertyName, that.propertyName))
return false;
+ if (!this.fullTextSearchExpression.equals(that.fullTextSearchExpression))
return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+
+ /**
+ * The general notion of a term that makes up a full-text search.
+ */
+ public static interface Term {
+ }
+
+ /**
+ * A {@link Term} that represents a single search term. The term may be comprised of
multiple words.
+ */
+ public static class SimpleTerm implements Term {
+ private final String value;
+ private final boolean excluded;
+ private final boolean quoted;
+
+ /**
+ * Create a simple term with the value and whether the term is excluded or
included.
+ *
+ * @param value the value that makes up the term
+ * @param excluded true if the term should not appear, or false if the term is
required
+ */
+ public SimpleTerm( String value,
+ boolean excluded ) {
+ assert value != null;
+ assert value.trim().length() > 0;
+ this.value = value;
+ this.excluded = excluded;
+ this.quoted = this.value.indexOf(' ') != -1;
+ }
+
+ /**
+ * Get the value of this term. Note that this is the actual value that is to be
searched for, and will not include the
+ * {@link #isQuotingRequired() quotes}.
+ *
+ * @return the value; never null
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Get whether or not this term is expected to appear in the results.
+ *
+ * @return true if the term is expected to not appear, or false if the term is
expected to appear
+ */
+ public boolean isExcluded() {
+ return excluded;
+ }
+
+ /**
+ * Get whether this term needs to be quoted because it consists of multiple
words.
+ *
+ * @return true if the term needs to be quoted, or false otherwise
+ */
+ public boolean isQuotingRequired() {
+ return quoted;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SimpleTerm) {
+ SimpleTerm that = (SimpleTerm)obj;
+ if (this.isExcluded() != that.isExcluded()) return false;
+ return this.getValue().equals(that.getValue());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ String value = quoted ? "\"" + this.value +
"\"" : this.value;
+ return excluded ? "-" + value : value;
+ }
+ }
+
+ /**
+ * A list of {@link Term}s.
+ */
+ public static abstract class CompoundTerm implements Term, Iterable<Term> {
+ private final List<Term> terms;
+
+ /**
+ * Create a compound term of the supplied terms.
+ *
+ * @param terms the terms; may not be null or empty
+ */
+ protected CompoundTerm( List<Term> terms ) {
+ this.terms = terms;
+ }
+
+ /**
+ * Get the terms that make up this compound term.
+ *
+ * @return the terms in the disjunction; never null and never empty
+ */
+ public List<Term> getTerms() {
+ return terms;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator<Term> iterator() {
+ return terms.iterator();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return terms.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (this.getClass().isInstance(obj)) {
+ CompoundTerm that = (CompoundTerm)obj;
+ return this.getTerms().equals(that.getTerms());
+ }
+ return false;
+ }
+
+ protected String toString( String delimiter ) {
+ if (terms.size() == 1) return terms.iterator().next().toString();
+ StringBuilder sb = new StringBuilder();
+ sb.append("( ");
+ boolean first = true;
+ for (Term term : terms) {
+ if (first) first = false;
+ else sb.append(' ').append(delimiter).append(' ');
+ sb.append(term);
+ }
+ sb.append(" )");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * A set of {@link Term}s that are ANDed together.
+ */
+ public static class Disjunction extends CompoundTerm {
+
+ /**
+ * Create a disjunction of the supplied terms.
+ *
+ * @param terms the terms to be ORed together; may not be null or empty
+ */
+ public Disjunction( List<Term> terms ) {
+ super(terms);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return toString("OR");
+ }
+ }
+
+ /**
+ * A set of {@link Term}s that are ANDed together.
+ */
+ public static class Conjunction extends CompoundTerm {
+
+ /**
+ * Create a conjunction of the supplied terms.
+ *
+ * @param terms the terms to be ANDed together; may not be null or empty
+ */
+ public Conjunction( List<Term> terms ) {
+ super(terms);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return toString("AND");
+ }
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearch.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,91 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the full-text search score of a node given by a
selector, used in a {@link Comparison}
+ * constraint.
+ */
+@Immutable
+public class FullTextSearchScore extends DynamicOperand {
+ private final SelectorName selectorName;
+
+ /**
+ * Create a dynamic operand that evaluates to the full-text search score of the node
identified by the selector.
+ *
+ * @param selectorName the name of the selector
+ */
+ public FullTextSearchScore( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof FullTextSearchScore) {
+ FullTextSearchScore that = (FullTextSearchScore)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/FullTextSearchScore.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Join.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Join.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Join.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,128 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ *
+ */
+@Immutable
+public class Join extends Source {
+
+ private final Source left;
+ private final Source right;
+ private final JoinType type;
+ private final JoinCondition joinCondition;
+
+ /**
+ * Create a join of the left and right sources, using the supplied join condition.
The outputs of the left and right sources
+ * are expected to be equivalent.
+ *
+ * @param left the left source being joined
+ * @param type the type of join
+ * @param right the right source being joined
+ * @param joinCondition the join condition
+ */
+ public Join( Source left,
+ JoinType type,
+ Source right,
+ JoinCondition joinCondition ) {
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(right, "right");
+ CheckArg.isNotNull(type, "type");
+ CheckArg.isNotNull(joinCondition, "joinCondition");
+ this.left = left;
+ this.right = right;
+ this.type = type;
+ this.joinCondition = joinCondition;
+ }
+
+ /**
+ * @return left
+ */
+ public final Source getLeft() {
+ return left;
+ }
+
+ /**
+ * @return right
+ */
+ public final Source getRight() {
+ return right;
+ }
+
+ /**
+ * @return type
+ */
+ public final JoinType getType() {
+ return type;
+ }
+
+ /**
+ * @return joinCondition
+ */
+ public final JoinCondition getJoinCondition() {
+ return joinCondition;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Join) {
+ Join that = (Join)obj;
+ if (!this.type.equals(that.type)) return false;
+ if (!this.left.equals(that.left)) return false;
+ if (!this.right.equals(that.right)) return false;
+ if (!this.joinCondition.equals(that.joinCondition)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Join.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinCondition.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinCondition.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinCondition.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ * The condition used for a join between two sources.
+ */
+@Immutable
+public abstract class JoinCondition implements LanguageObject {
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinCondition.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinType.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinType.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinType.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,100 @@
+/*
+ * 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.model;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+
+public enum JoinType implements Readable {
+ INNER("INNER JOIN"),
+ LEFT_OUTER("LEFT OUTER JOIN"),
+ RIGHT_OUTER("RIGHT OUTER JOIN"),
+ FULL_OUTER("FULL OUTER JOIN"),
+ CROSS("CROSS JOIN");
+
+ private static final Map<String, JoinType> TYPE_BY_SYMBOL;
+ static {
+ Map<String, JoinType> typesBySymbol = new HashMap<String,
JoinType>();
+ for (JoinType type : JoinType.values()) {
+ typesBySymbol.put(type.getSymbol().toUpperCase(), type);
+ }
+ TYPE_BY_SYMBOL = Collections.unmodifiableMap(typesBySymbol);
+ }
+
+ private final String symbol;
+
+ private JoinType( String symbol ) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * @return symbol
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * Check if this join type is an outer join.
+ *
+ * @return true if left/right/full outer, or false otherwise
+ */
+ public boolean isOuter() {
+ return this.equals(LEFT_OUTER) || this.equals(FULL_OUTER) ||
this.equals(RIGHT_OUTER);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ /**
+ * Attempt to find the JoinType given a symbol. The matching is done independent of
case.
+ *
+ * @param symbol the symbol
+ * @return the JoinType having the supplied symbol, or null if there is no JoinType
with the supplied symbol
+ * @throws IllegalArgumentException if the symbol is null
+ */
+ public static JoinType forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ return TYPE_BY_SYMBOL.get(symbol.toUpperCase());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Readable#getString(org.jboss.dna.graph.ExecutionContext)
+ */
+ public String getString( ExecutionContext context ) {
+ return getSymbol();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/JoinType.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LanguageObject.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LanguageObject.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LanguageObject.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,30 @@
+/*
+ * 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.model;
+
+/**
+ * A common interface for all query language objects.
+ */
+public interface LanguageObject extends Visitable {
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LanguageObject.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,96 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the length of the supplied propety values, used in
a {@link Comparison} constraint.
+ */
+@Immutable
+public class Length extends DynamicOperand {
+ private final PropertyValue propertyValue;
+
+ /**
+ * Create a dynamic operand that evaluates to the length of the supplied property
values.
+ *
+ * @param propertyValue the property value operand
+ */
+ public Length( PropertyValue propertyValue ) {
+ CheckArg.isNotNull(propertyValue, "propertyValue");
+ this.propertyValue = propertyValue;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public SelectorName getSelectorName() {
+ return propertyValue.getSelectorName();
+ }
+
+ /**
+ * @return propertyValue
+ */
+ public final PropertyValue getPropertyValue() {
+ return propertyValue;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Length) {
+ Length that = (Length)obj;
+ return this.propertyValue.equals(that.propertyValue);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Length.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Limit.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Limit.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Limit.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,139 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ *
+ */
+@Immutable
+public class Limit implements LanguageObject {
+
+ public static final Limit NONE = new Limit(Integer.MAX_VALUE, 0);
+
+ private final int offset;
+ private final int rowLimit;
+
+ public Limit( int rowLimit ) {
+ CheckArg.isPositive(rowLimit, "rowLimit");
+ this.rowLimit = rowLimit;
+ this.offset = 0;
+ }
+
+ public Limit( int rowLimit,
+ int offset ) {
+ CheckArg.isPositive(rowLimit, "rowLimit");
+ CheckArg.isNonNegative(offset, "offset");
+ this.rowLimit = rowLimit;
+ this.offset = offset;
+ }
+
+ /**
+ * @return offset
+ */
+ public final int getOffset() {
+ return offset;
+ }
+
+ /**
+ * Get the maximum number of rows that are to be returned.
+ *
+ * @return the maximum number of rows; always positive, or equal to {@link
Integer#MAX_VALUE} if there is no limit
+ */
+ public final int getRowLimit() {
+ return rowLimit;
+ }
+
+ /**
+ * Determine whether this limit clause is necessary.
+ *
+ * @return true if the number of rows is not limited and there is no offset, or false
otherwise
+ */
+ public final boolean isUnlimited() {
+ return rowLimit == Integer.MAX_VALUE && offset == 0;
+ }
+
+ /**
+ * Determine whether this limit clause defines a maximum limit
+ *
+ * @return true if the number of rows are limited, or false if there is no limit to
the number of rows
+ */
+ public final boolean hasRowLimited() {
+ return rowLimit != Integer.MAX_VALUE;
+ }
+
+ /**
+ * Determine whether this limit clause defines an offset.
+ *
+ * @return true if there is an offset, or false if there is no offset
+ */
+ public final boolean isOffset() {
+ return offset > 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Limit) {
+ Limit that = (Limit)obj;
+ return this.offset == that.offset && this.rowLimit == that.rowLimit;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+
+ public Limit withRowLimit( int rowLimit ) {
+ return new Limit(rowLimit, offset);
+ }
+
+ public Limit withOffset( int offset ) {
+ return new Limit(rowLimit, offset);
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Limit.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,82 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A literal value used in a {@link Comparison} constraint.
+ */
+@Immutable
+public class Literal extends StaticOperand {
+
+ private final Object value;
+
+ public Literal( Object value ) {
+ CheckArg.isNotNull(value, "value");
+ this.value = value;
+ }
+
+ /**
+ * @return value
+ */
+ public final Object getValue() {
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Literal) {
+ Literal that = (Literal)obj;
+ return this.value.equals(that.value);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Literal.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,97 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the lower-case representation of the supplied
operand, used in a {@link Comparison}
+ * constraint.
+ */
+@Immutable
+public class LowerCase extends DynamicOperand {
+ private final DynamicOperand operand;
+
+ /**
+ * Create a dynamic operand that evaluates to the lower-case representation of the
supplied operand.
+ *
+ * @param operand the operand that is to be lower-cased
+ */
+ public LowerCase( DynamicOperand operand ) {
+ CheckArg.isNotNull(operand, "operand");
+ this.operand = operand;
+ }
+
+ /**
+ * @return operand
+ */
+ public final DynamicOperand getOperand() {
+ return operand;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public SelectorName getSelectorName() {
+ return operand.getSelectorName();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof LowerCase) {
+ LowerCase that = (LowerCase)obj;
+ return this.operand.equals(that.operand);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/LowerCase.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,92 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.ObjectUtil;
+
+/**
+ *
+ */
+@Immutable
+public class NamedSelector extends Selector {
+
+ /**
+ * Create a selector with a name.
+ *
+ * @param name the name for this selector
+ * @throws IllegalArgumentException if the selector name is null
+ */
+ public NamedSelector( SelectorName name ) {
+ super(name);
+ }
+
+ /**
+ * Create a selector with the supplied name and alias.
+ *
+ * @param name the name for this selector
+ * @param alias the alias for this selector; may be null
+ * @throws IllegalArgumentException if the selector name is null
+ */
+ public NamedSelector( SelectorName name,
+ SelectorName alias ) {
+ super(name, alias);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NamedSelector) {
+ NamedSelector that = (NamedSelector)obj;
+ if (!this.getName().equals(that.getName())) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.getAlias(), that.getAlias())) return
false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NamedSelector.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,89 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the local name of a node given by a selector, used
in a {@link Comparison} constraint.
+ */
+@Immutable
+public class NodeLocalName extends DynamicOperand {
+ private final SelectorName selectorName;
+
+ /**
+ * Create a dynamic operand that evaluates to the local name of the node identified
by the selector.
+ *
+ * @param selectorName the name of the selector
+ */
+ public NodeLocalName( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NodeLocalName) {
+ NodeLocalName that = (NodeLocalName)obj;
+ return this.selectorName.equals(that.selectorName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeLocalName.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,89 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the qualified name of a node given by a selector,
used in a {@link Comparison} constraint.
+ */
+@Immutable
+public class NodeName extends DynamicOperand {
+ private final SelectorName selectorName;
+
+ /**
+ * Create a dynamic operand that evaluates to the qualified name of the node
identified by the selector.
+ *
+ * @param selectorName the name of the selector
+ */
+ public NodeName( SelectorName selectorName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NodeName) {
+ NodeName that = (NodeName)obj;
+ return this.selectorName.equals(that.selectorName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/NodeName.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Not.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Not.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Not.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,90 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A constraint that negates another constraint.
+ */
+@Immutable
+public class Not extends Constraint {
+
+ private final Constraint constraint;
+
+ /**
+ * Create a constraint that negates another constraint.
+ *
+ * @param constraint the constraint that is being negated
+ * @throws IllegalArgumentException if the supplied constraint is null
+ */
+ public Not( Constraint constraint ) {
+ CheckArg.isNotNull(constraint, "constraint");
+ this.constraint = constraint;
+ }
+
+ /**
+ * The constraint being negated.
+ *
+ * @return the constraint; never null
+ */
+ public final Constraint getConstraint() {
+ return constraint;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Not) {
+ Not that = (Not)obj;
+ return this.constraint.equals(that.constraint);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Not.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,87 @@
+/*
+ * 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.model;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ *
+ */
+public enum Operator {
+ EQUAL_TO("="),
+ NOT_EQUAL_TO("!="),
+ LESS_THAN("<"),
+ LESS_THAN_OR_EQUAL_TO("<="),
+ GREATER_THAN(">"),
+ GREATER_THAN_OR_EQUAL_TO(">="),
+ LIKE("LIKE");
+
+ private static final Map<String, Operator> OPERATORS_BY_SYMBOL;
+ static {
+ Map<String, Operator> opsBySymbol = new HashMap<String, Operator>();
+ for (Operator operator : Operator.values()) {
+ opsBySymbol.put(operator.getSymbol().toUpperCase(), operator);
+ }
+ opsBySymbol.put("<>", NOT_EQUAL_TO);
+ OPERATORS_BY_SYMBOL = Collections.unmodifiableMap(opsBySymbol);
+ }
+
+ private final String symbol;
+
+ private Operator( String symbol ) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * @return symbol
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ /**
+ * Attempt to find the Operator given a symbol. The matching is done independent of
case.
+ *
+ * @param symbol the symbol
+ * @return the Operator having the supplied symbol, or null if there is no Operator
with the supplied symbol
+ * @throws IllegalArgumentException if the symbol is null
+ */
+ public static Operator forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ return OPERATORS_BY_SYMBOL.get(symbol.toUpperCase());
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Operator.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Or.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Or.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Or.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,104 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A constraint that evaluates to true when either of the other constraints evaluates to
true.
+ */
+@Immutable
+public class Or extends Constraint {
+
+ private final Constraint left;
+ private final Constraint right;
+
+ /**
+ * Create a constraint that evaluates to true if either of the two supplied
constraints evaluates to true.
+ *
+ * @param left the left constraint
+ * @param right the right constraint
+ * @throws IllegalArgumentException if the left or right constraints are null
+ */
+ public Or( Constraint left,
+ Constraint right ) {
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(right, "right");
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * Get the left-hand constraint.
+ *
+ * @return the left-hand constraint; never null
+ */
+ public final Constraint getLeft() {
+ return left;
+ }
+
+ /**
+ * Get the right-hand constraint.
+ *
+ * @return the right-hand constraint; never null
+ */
+ public final Constraint getRight() {
+ return right;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Or) {
+ Or that = (Or)obj;
+ return this.left.equals(that.left) && this.right.equals(that.right);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Or.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Order.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Order.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Order.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,81 @@
+/*
+ * 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.model;
+
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+
+/**
+ *
+ */
+public enum Order implements Readable {
+ ASCENDING("ASC"),
+ DESCENDING("DESC");
+
+ private final String symbol;
+
+ private Order( String symbol ) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * @return symbol
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ /**
+ * Attempt to find the Order given a symbol. The matching is done independent of
case.
+ *
+ * @param symbol the symbol
+ * @return the Order having the supplied symbol, or null if there is no Order with
the supplied symbol
+ * @throws IllegalArgumentException if the symbol is null
+ */
+ public static Order forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ if (ASCENDING.getSymbol().equalsIgnoreCase(symbol)) return ASCENDING;
+ if (DESCENDING.getSymbol().equalsIgnoreCase(symbol)) return DESCENDING;
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Readable#getString(org.jboss.dna.graph.ExecutionContext)
+ */
+ public String getString( ExecutionContext context ) {
+ return getSymbol();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Order.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Ordering.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Ordering.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Ordering.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,105 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A specification of the ordering for the results.
+ */
+@Immutable
+public class Ordering implements LanguageObject {
+
+ private final DynamicOperand operand;
+ private final Order order;
+
+ /**
+ * Create a new ordering specification, given the supplied operand and order.
+ *
+ * @param operand the operand being ordered
+ * @param order the order type
+ * @throws IllegalArgumentException if the operand or order type is null
+ */
+ public Ordering( DynamicOperand operand,
+ Order order ) {
+ CheckArg.isNotNull(operand, "operand");
+ CheckArg.isNotNull(order, "order");
+ this.operand = operand;
+ this.order = order;
+ }
+
+ /**
+ * Get the operand being ordered.
+ *
+ * @return the operand; never null
+ */
+ public final DynamicOperand getOperand() {
+ return operand;
+ }
+
+ /**
+ * The order type.
+ *
+ * @return the type; never null
+ */
+ public final Order getOrder() {
+ return order;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Ordering) {
+ Ordering that = (Ordering)obj;
+ if (this.order != that.order) return false;
+ return this.operand.equals(that.operand);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Ordering.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyExistence.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyExistence.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyExistence.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,103 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Name;
+
+/**
+ * A constraint that evaluates to true only when a named property exists on a node.
+ */
+@Immutable
+public class PropertyExistence extends Constraint {
+ private final SelectorName selectorName;
+ private final Name propertyName;
+
+ /**
+ * Create a constraint requiring that a property exist on a node.
+ *
+ * @param selectorName the name of the node selector
+ * @param propertyName the name of the property that must exist
+ */
+ public PropertyExistence( SelectorName selectorName,
+ Name propertyName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(propertyName, "propertyName");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * Get the name of the selector.
+ *
+ * @return the selector name; never null
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * Get the name of the property.
+ *
+ * @return the property name; never null
+ */
+ public final Name getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof PropertyExistence) {
+ PropertyExistence that = (PropertyExistence)obj;
+ return this.selectorName.equals(that.selectorName) &&
this.propertyName.equals(that.propertyName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyExistence.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,105 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Name;
+
+/**
+ * A dynamic operand that evaluates to the value(s) of a property on a selector, used in
a {@link Comparison} constraint.
+ */
+@Immutable
+public class PropertyValue extends DynamicOperand {
+ private final SelectorName selectorName;
+ private final Name propertyName;
+
+ /**
+ * Create a dynamic operand that evaluates to the property values of the node
identified by the selector.
+ *
+ * @param selectorName the name of the selector
+ * @param propertyName the name of the property
+ * @throws IllegalArgumentException if the selector name or property name are null
+ */
+ public PropertyValue( SelectorName selectorName,
+ Name propertyName ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(propertyName, "propertyName");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * Get the name of the property.
+ *
+ * @return the property name; never null
+ */
+ public final Name getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof PropertyValue) {
+ PropertyValue that = (PropertyValue)obj;
+ return this.selectorName.equals(that.selectorName) &&
this.propertyName.equals(that.propertyName);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/PropertyValue.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Query.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Query.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Query.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,218 @@
+/*
+ * 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.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+
+/**
+ *
+ */
+@Immutable
+public class Query extends QueryCommand {
+
+ public static final boolean IS_DISTINCT_DEFAULT = false;
+
+ private final Source source;
+ private final Constraint constraint;
+ private final List<Column> columns;
+ private final boolean distinct;
+
+ /**
+ * Create a new query that uses the supplied source.
+ *
+ * @param source the source
+ * @throws IllegalArgumentException if the source is null
+ */
+ public Query( Source source ) {
+ super();
+ CheckArg.isNotNull(source, "source");
+ this.source = source;
+ this.constraint = null;
+ this.columns = Collections.<Column>emptyList();
+ this.distinct = IS_DISTINCT_DEFAULT;
+ }
+
+ /**
+ * Create a new query that uses the supplied source, constraint, orderings, columns
and limits.
+ *
+ * @param source the source
+ * @param constraint the constraint (or composite constraint), or null or empty if
there are no constraints
+ * @param orderings the specifications of how the results are to be ordered, or null
if the order is to be implementation
+ * determined
+ * @param columns the columns to be included in the results, or null or empty if
there are no explicit columns and the actual
+ * result columns are to be implementation determiend
+ * @param limit the limit for the results, or null if all of the results are to be
included
+ * @param isDistinct true if duplicates are to be removed from the results
+ * @throws IllegalArgumentException if the source is null
+ */
+ public Query( Source source,
+ Constraint constraint,
+ List<Ordering> orderings,
+ List<Column> columns,
+ Limit limit,
+ boolean isDistinct ) {
+ super(orderings, limit);
+ CheckArg.isNotNull(source, "source");
+ this.source = source;
+ this.constraint = constraint;
+ this.columns = columns != null ? columns :
Collections.<Column>emptyList();
+ this.distinct = isDistinct;
+ }
+
+ /**
+ * Get the source for the results.
+ *
+ * @return the query source; never null
+ */
+ public final Source getSource() {
+ return source;
+ }
+
+ /**
+ * Get the constraints, if there are any.
+ *
+ * @return the constraint; may be null
+ */
+ public final Constraint getConstraint() {
+ return constraint;
+ }
+
+ /**
+ * Return the columns defining the query results. If there are no columns, then the
columns are implementation determined.
+ *
+ * @return the list of columns; never null
+ */
+ public final List<Column> getColumns() {
+ return columns;
+ }
+
+ /**
+ * Determine whether this query is to return only distinct values.
+ *
+ * @return true if the query is to remove duplicate tuples, or false otherwise
+ */
+ public boolean isDistinct() {
+ return distinct;
+ }
+
+ public Query distinct() {
+ return new Query(source, constraint, getOrderings(), columns, getLimits(),
true);
+ }
+
+ public Query noDistinct() {
+ return new Query(source, constraint, getOrderings(), columns, getLimits(),
false);
+ }
+
+ public Query constrainedBy( Constraint constraint ) {
+ return new Query(source, constraint, getOrderings(), columns, getLimits(),
distinct);
+ }
+
+ public Query orderedBy( List<Ordering> orderings ) {
+ return new Query(source, constraint, orderings, columns, getLimits(), distinct);
+ }
+
+ public Query withLimit( int rowLimit ) {
+ return new Query(source, constraint, getOrderings(), columns,
getLimits().withRowLimit(rowLimit), distinct);
+ }
+
+ public Query withOffset( int offset ) {
+ return new Query(source, constraint, getOrderings(), columns,
getLimits().withOffset(offset), distinct);
+ }
+
+ public Query returning( List<Column> columns ) {
+ return new Query(source, constraint, getOrderings(), columns, getLimits(),
distinct);
+ }
+
+ public Query adding( Ordering... orderings ) {
+ List<Ordering> newOrderings = null;
+ if (this.getOrderings() != null) {
+ newOrderings = new ArrayList<Ordering>(getOrderings());
+ for (Ordering ordering : orderings) {
+ newOrderings.add(ordering);
+ }
+ } else {
+ newOrderings = Arrays.asList(orderings);
+ }
+ return new Query(source, constraint, newOrderings, columns, getLimits(),
distinct);
+ }
+
+ public Query adding( Column... columns ) {
+ List<Column> newColumns = null;
+ if (this.columns != null) {
+ newColumns = new ArrayList<Column>(this.columns);
+ for (Column column : columns) {
+ newColumns.add(column);
+ }
+ } else {
+ newColumns = Arrays.asList(columns);
+ }
+ return new Query(source, constraint, getOrderings(), newColumns, getLimits(),
distinct);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Query) {
+ Query that = (Query)obj;
+ if (this.distinct != that.distinct) return false;
+ if (!this.source.equals(that.source)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.getLimits(), that.getLimits())) return
false;
+ if (!ObjectUtil.isEqualWithNulls(this.constraint, that.constraint)) return
false;
+ if (!ObjectUtil.isEqualWithNulls(this.columns, that.columns)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.getOrderings(), that.getOrderings()))
return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Query.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/QueryCommand.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/QueryCommand.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/QueryCommand.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,74 @@
+/*
+ * 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.model;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents the abstract base class for all query commands. Subclasses include {@link
Query} and {@link SetQuery}.
+ */
+public abstract class QueryCommand extends Command {
+
+ private final List<Ordering> orderings;
+ private final Limit limits;
+
+ /**
+ * Create a new query command.
+ */
+ protected QueryCommand() {
+ this(null, null);
+ }
+
+ /**
+ * Create a new query command that uses the supplied orderings and limits.
+ *
+ * @param orderings the specifications of how the results are to be ordered, or null
if the order is to be implementation
+ * determined result columns are to be implementation determiend
+ * @param limit the limit for the results, or null if all of the results are to be
included
+ */
+ protected QueryCommand( List<Ordering> orderings,
+ Limit limit ) {
+ this.orderings = orderings != null ? orderings :
Collections.<Ordering>emptyList();
+ this.limits = limit != null ? limit : Limit.NONE;
+ }
+
+ /**
+ * Return the orderings for this query.
+ *
+ * @return the list of orderings; never null
+ */
+ public final List<Ordering> getOrderings() {
+ return orderings;
+ }
+
+ /**
+ * Get the limits associated with this query.
+ *
+ * @return the limits; never null but possibly {@link Limit#isUnlimited() unlimited}
+ */
+ public final Limit getLimits() {
+ return limits;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/QueryCommand.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Readable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Readable.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Readable.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,40 @@
+/*
+ * 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.model;
+
+import org.jboss.dna.graph.ExecutionContext;
+
+/**
+ *
+ */
+public interface Readable {
+ /**
+ * Get the string representation of this query object.
+ *
+ * @param context the execution context in which the conversion is to take place
+ * @return the string representation; never null
+ */
+ String getString( ExecutionContext context );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Readable.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNode.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNode.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,106 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * A constraint requiring that the selected node is reachable by the supplied absolute
path
+ */
+@Immutable
+public class SameNode extends Constraint {
+ private final SelectorName selectorName;
+ private final Path path;
+
+ /**
+ * Create a constraint requiring that the node identified by the selector is
reachable by the supplied absolute path.
+ *
+ * @param selectorName the name of the selector
+ * @param path the absolute path
+ * @throws IllegalArgumentException if the selector name or path are null
+ */
+ public SameNode( SelectorName selectorName,
+ Path path ) {
+ CheckArg.isNotNull(selectorName, "selectorName");
+ CheckArg.isNotNull(path, "path");
+ this.selectorName = selectorName;
+ this.path = path;
+ }
+
+ /**
+ * Get the name of the selector.
+ *
+ * @return the selector name; never null
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * Get the absolute path for the node
+ *
+ * @return the absolute path; never null
+ */
+ public final Path getPath() {
+ return path;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SameNode) {
+ SameNode that = (SameNode)obj;
+ if (!this.selectorName.equals(that.selectorName)) return false;
+ if (!this.path.equals(that.path)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNode.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNodeJoinCondition.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNodeJoinCondition.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNodeJoinCondition.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,141 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * A join condition that tests whether two nodes are the same nodes (that is, have the
same identifier or have the same relative
+ * path from the nearest ancestor with an identifiers).
+ */
+@Immutable
+public class SameNodeJoinCondition extends JoinCondition {
+ private final SelectorName selector1Name;
+ private final SelectorName selector2Name;
+ private final Path selector2Path;
+
+ /**
+ * Create a join condition that determines whether the node identified by the first
selector is the same as the node at the
+ * given path relative to the node identified by the second selector.
+ *
+ * @param selector1Name the name of the first selector
+ * @param selector2Name the name of the second selector
+ * @param selector2Path the relative path from the second selector locating the node
being compared with the first selector
+ * @throws IllegalArgumentException if the path or either selector name is null
+ */
+ public SameNodeJoinCondition( SelectorName selector1Name,
+ SelectorName selector2Name,
+ Path selector2Path ) {
+ CheckArg.isNotNull(selector1Name, "selector1Name");
+ CheckArg.isNotNull(selector2Name, "selector2Name");
+ CheckArg.isNotNull(selector2Path, "selector2Path");
+ this.selector1Name = selector1Name;
+ this.selector2Name = selector2Name;
+ this.selector2Path = selector2Path;
+ }
+
+ /**
+ * Create a join condition that determines whether the node identified by the first
selector is the same as the node
+ * identified by the second selector.
+ *
+ * @param selector1Name the name of the first selector
+ * @param selector2Name the name of the second selector
+ * @throws IllegalArgumentException if either selector name is null
+ */
+ public SameNodeJoinCondition( SelectorName selector1Name,
+ SelectorName selector2Name ) {
+ CheckArg.isNotNull(selector1Name, "selector1Name");
+ CheckArg.isNotNull(selector2Name, "selector2Name");
+ this.selector1Name = selector1Name;
+ this.selector2Name = selector2Name;
+ this.selector2Path = null;
+ }
+
+ /**
+ * Get the selector name for the first side of the join condition.
+ *
+ * @return the name of the first selector; never null
+ */
+ public final SelectorName getSelector1Name() {
+ return selector1Name;
+ }
+
+ /**
+ * Get the selector name for the second side of the join condition.
+ *
+ * @return the name of the second selector; never null
+ */
+ public final SelectorName getSelector2Name() {
+ return selector2Name;
+ }
+
+ /**
+ * Get the path for the node being used, relative to the second selector.
+ *
+ * @return the relative path to the node; may be null if the second selector is the
node being used
+ */
+ public final Path getSelector2Path() {
+ return selector2Path;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition that = (SameNodeJoinCondition)obj;
+ if (!this.selector1Name.equals(that.selector1Name)) return false;
+ if (!this.selector2Name.equals(that.selector2Name)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.selector2Path, that.selector2Path))
return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SameNodeJoinCondition.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Selector.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Selector.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Selector.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,100 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ *
+ */
+@Immutable
+public abstract class Selector extends Source {
+
+ private final SelectorName name;
+ private final SelectorName alias;
+
+ /**
+ * Create a selector with a name.
+ *
+ * @param name the name for this selector
+ * @throws IllegalArgumentException if the selector name is null
+ */
+ protected Selector( SelectorName name ) {
+ CheckArg.isNotNull(name, "name");
+ this.name = name;
+ this.alias = null;
+ }
+
+ /**
+ * Create a selector with the supplied name and alias.
+ *
+ * @param name the name for this selector
+ * @param alias the alias for this selector; may be null
+ * @throws IllegalArgumentException if the selector name is null
+ */
+ protected Selector( SelectorName name,
+ SelectorName alias ) {
+ CheckArg.isNotNull(name, "name");
+ this.name = name;
+ this.alias = alias;
+ }
+
+ /**
+ * Get the name for this selector.
+ *
+ * @return the selector name; never null
+ */
+ public SelectorName getName() {
+ return name;
+ }
+
+ /**
+ * Get the alias name for this source, if there is one.
+ *
+ * @return the alias name, or null if there is none.
+ */
+ public SelectorName getAlias() {
+ return alias;
+ }
+
+ /**
+ * Get the alias if this selector has one, or the name.
+ *
+ * @return the alias or name; never null
+ */
+ public SelectorName getAliasOrName() {
+ return alias != null ? alias : name;
+ }
+
+ /**
+ * Determine if this selector has an alias.
+ *
+ * @return true if this selector has an alias, or false otherwise.
+ */
+ public boolean hasAlias() {
+ return alias != null;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Selector.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SelectorName.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SelectorName.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SelectorName.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,95 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+
+/**
+ * A representation of a qualified or expanded name.
+ */
+@Immutable
+public class SelectorName implements Readable {
+
+ private final String name;
+
+ public SelectorName( String name ) {
+ CheckArg.isNotEmpty(name, "name");
+ this.name = name;
+ }
+
+ /**
+ * The raw name of the selector.
+ *
+ * @return the raw name; never null and never empty
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.Readable#getString(ExecutionContext)
+ */
+ public String getString( ExecutionContext context ) {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SelectorName) {
+ SelectorName that = (SelectorName)obj;
+ return this.name.equals(that.getName());
+ }
+ return false;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SelectorName.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetQuery.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetQuery.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetQuery.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,230 @@
+/*
+ * 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.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.ExecutionContext;
+
+/**
+ * This object acts as a Set operator on multiple {@link QueryCommand queries}, such as
performing UNION, INTERSECT, and EXCEPT
+ * operations.
+ * <p>
+ * The two {@link QueryCommand queries} are expected to have the same number and order of
columns, and the corresponding columns
+ * types must be compatible.
+ * </p>
+ */
+@Immutable
+public class SetQuery extends QueryCommand {
+
+ public enum Operation implements Readable {
+ UNION("UNION"),
+ INTERSECT("INTERSECT"),
+ EXCEPT("EXCEPT");
+
+ private static final Map<String, Operation> OPERATIONS_BY_SYMBOL;
+ static {
+ Map<String, Operation> opsBySymbol = new HashMap<String,
Operation>();
+ for (Operation op : Operation.values()) {
+ opsBySymbol.put(op.getSymbol(), op);
+ }
+ OPERATIONS_BY_SYMBOL = Collections.unmodifiableMap(opsBySymbol);
+ }
+
+ private final String symbol;
+
+ private Operation( String symbol ) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * @return symbol
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ public static Operation forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ return OPERATIONS_BY_SYMBOL.get(symbol.toUpperCase());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Readable#getString(org.jboss.dna.graph.ExecutionContext)
+ */
+ public String getString( ExecutionContext context ) {
+ return getSymbol();
+ }
+ }
+
+ private final QueryCommand left;
+ private final QueryCommand right;
+ private final Operation operation;
+ private final boolean all;
+
+ public SetQuery( QueryCommand left,
+ Operation operation,
+ QueryCommand right,
+ boolean all ) {
+ super();
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(right, "right");
+ CheckArg.isNotNull(operation, "operation");
+ this.left = left;
+ this.right = right;
+ this.operation = operation;
+ this.all = all;
+ }
+
+ public SetQuery( QueryCommand left,
+ Operation operation,
+ QueryCommand right,
+ boolean all,
+ List<Ordering> orderings,
+ Limit limit ) {
+ super(orderings, limit);
+ CheckArg.isNotNull(left, "left");
+ CheckArg.isNotNull(right, "right");
+ CheckArg.isNotNull(operation, "operation");
+ this.left = left;
+ this.right = right;
+ this.operation = operation;
+ this.all = all;
+ }
+
+ /**
+ * Get the left-hand query.
+ *
+ * @return the left-hand query; never null
+ */
+ public final QueryCommand getLeft() {
+ return left;
+ }
+
+ /**
+ * Get the right-hand query.
+ *
+ * @return the right-hand query; never null
+ */
+ public final QueryCommand getRight() {
+ return right;
+ }
+
+ /**
+ * Get the set operation for this query.
+ *
+ * @return the operation; never null
+ */
+ public final Operation getOperation() {
+ return operation;
+ }
+
+ /**
+ * Return whether this set query is a 'UNION ALL' or 'INTERSECT ALL'
or 'EXCEPT ALL' query.
+ *
+ * @return true if this is an 'ALL' query, or false otherwise
+ */
+ public final boolean isAll() {
+ return all;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SetQuery) {
+ SetQuery that = (SetQuery)obj;
+ if (this.operation != that.operation) return false;
+ if (!this.left.equals(that.left)) return false;
+ if (!this.right.equals(that.right)) return false;
+ if (!ObjectUtil.isEqualWithNulls(this.getLimits(), that.getLimits())) return
false;
+ if (!ObjectUtil.isEqualWithNulls(this.getOrderings(), that.getOrderings()))
return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+
+ public SetQuery withLimit( int rowLimit ) {
+ return new SetQuery(left, operation, right, all, getOrderings(),
getLimits().withRowLimit(rowLimit));
+ }
+
+ public SetQuery withOffset( int offset ) {
+ return new SetQuery(left, operation, right, all, getOrderings(),
getLimits().withOffset(offset));
+ }
+
+ public SetQuery adding( Ordering... orderings ) {
+ List<Ordering> newOrderings = null;
+ if (this.getOrderings() != null) {
+ newOrderings = new ArrayList<Ordering>(getOrderings());
+ for (Ordering ordering : orderings) {
+ newOrderings.add(ordering);
+ }
+ } else {
+ newOrderings = Arrays.asList(orderings);
+ }
+ return new SetQuery(left, operation, right, all, newOrderings, getLimits());
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/SetQuery.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Source.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Source.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Source.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ * The source that represents a set of node tuples. The {@link Selector} and {@link Join}
are the two concrete types of sources.
+ */
+@Immutable
+public abstract class Source implements LanguageObject {
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Source.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/StaticOperand.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/StaticOperand.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/StaticOperand.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ * A static operand used in a {@link Comparison} constraint.
+ */
+@Immutable
+public abstract class StaticOperand implements LanguageObject {
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/StaticOperand.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,99 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.CheckArg;
+
+/**
+ * A dynamic operand that evaluates to the upper-case representation of the supplied
operand, used in a {@link Comparison}
+ * constraint.
+ */
+@Immutable
+public class UpperCase extends DynamicOperand {
+ private final DynamicOperand operand;
+
+ /**
+ * Create a dynamic operand that evaluates to the upper-case representation of the
supplied operand.
+ *
+ * @param operand the operand that is to be lower-cased
+ */
+ public UpperCase( DynamicOperand operand ) {
+ CheckArg.isNotNull(operand, "operand");
+ this.operand = operand;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.model.DynamicOperand#getSelectorName()
+ */
+ @Override
+ public SelectorName getSelectorName() {
+ return operand.getSelectorName();
+ }
+
+ /**
+ * Get the operand that is being uppercased.
+ *
+ * @return the operand being uppercased; never null
+ */
+ public final DynamicOperand getOperand() {
+ return operand;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof UpperCase) {
+ UpperCase that = (UpperCase)obj;
+ return this.operand.equals(that.operand);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitable#accept(org.jboss.dna.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/UpperCase.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitable.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitable.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,34 @@
+/*
+ * 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.model;
+
+import net.jcip.annotations.Immutable;
+
+/**
+ *
+ */
+@Immutable
+public interface Visitable {
+ void accept( Visitor visitor );
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitable.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,91 @@
+/*
+ * 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.model;
+
+/**
+ * The basic interface for all query visitor implementations.
+ */
+public interface Visitor {
+
+ void visit( AllNodes obj );
+
+ void visit( And obj );
+
+ void visit( BindVariableName obj );
+
+ void visit( ChildNode obj );
+
+ void visit( ChildNodeJoinCondition obj );
+
+ void visit( Column obj );
+
+ void visit( Comparison obj );
+
+ void visit( DescendantNode obj );
+
+ void visit( DescendantNodeJoinCondition obj );
+
+ void visit( EquiJoinCondition obj );
+
+ void visit( FullTextSearch obj );
+
+ void visit( FullTextSearchScore obj );
+
+ void visit( Join obj );
+
+ void visit( Length obj );
+
+ void visit( Limit limit );
+
+ void visit( Literal obj );
+
+ void visit( LowerCase obj );
+
+ void visit( NodeLocalName obj );
+
+ void visit( NodeName obj );
+
+ void visit( NamedSelector obj );
+
+ void visit( Not obj );
+
+ void visit( Or obj );
+
+ void visit( Ordering obj );
+
+ void visit( PropertyExistence obj );
+
+ void visit( PropertyValue obj );
+
+ void visit( Query obj );
+
+ void visit( SameNode obj );
+
+ void visit( SameNodeJoinCondition obj );
+
+ void visit( SetQuery obj );
+
+ void visit( UpperCase obj );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitor.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,1284 @@
+/*
+ * 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.model;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * A set of common visitors that can be reused or extended, and methods that provide easy
construction and calling of visitors.
+ */
+public class Visitors {
+
+ /**
+ * Visit all objects in the supplied {@link Visitable object} using a {@link
NavigationVisitor} (specifically a
+ * {@link WalkAllVisitor}), and with each of these visited objects calling the
appropriate {@code visit(...)} method on the
+ * supplied {@link Visitor}.
+ *
+ * @param <StrategyVisitor> the type of strategy visitor
+ * @param visitable the top-level object to be visited
+ * @param strategyVisitor the visitor that is to be called for each visited objects,
but that does <i>not</i> call
+ * {@link Visitable#accept(Visitor)}
+ * @return the strategy visitor, allowing the caller to easily invoke operations on
the visitor after visitation has completed
+ */
+ public static <StrategyVisitor extends Visitor> StrategyVisitor visitAll(
Visitable visitable,
+
StrategyVisitor strategyVisitor ) {
+ visitable.accept(new WalkAllVisitor(strategyVisitor));
+ return strategyVisitor;
+ }
+
+ /**
+ * Visit the supplied {@link Visitable object} using the supplied {@link Visitor},
which must be responsible for navigation as
+ * well as any business logic.
+ *
+ * @param <GeneralVisitor> the type of visitor
+ * @param visitable the top-level object to be visited
+ * @param visitor the visitor that is to be used
+ * @return the visitor, allowing the caller to easily invoke operations on the
visitor after visitation has completed
+ */
+ public static <GeneralVisitor extends Visitor> GeneralVisitor visit( Visitable
visitable,
+ GeneralVisitor
visitor ) {
+ visitable.accept(visitor);
+ return visitor;
+ }
+
+ /**
+ * Using a visitor, obtain the readable string representation of the supplied {@link
Visitable object}
+ *
+ * @param visitable the visitable
+ * @param context the execution context in which the representation should be
produced, or null if there is none
+ * @return the string representation
+ */
+ public static String readable( Visitable visitable,
+ ExecutionContext context ) {
+ return visit(visitable, new ReadableVisitor(context)).getString();
+ }
+
+ /**
+ * Using a visitor, obtain the readable string representation of the supplied {@link
Visitable object}
+ *
+ * @param visitable the visitable
+ * @return the string representation
+ */
+ public static String readable( Visitable visitable ) {
+ return visit(visitable, new ReadableVisitor()).getString();
+ }
+
+ /**
+ * Get the names of the selectors referenced by the visitable object.
+ *
+ * @param visitable the object to be visited
+ * @return the set of selector names referenced in some way by the visitable; never
null
+ */
+ public static Set<SelectorName> getSelectorsReferencedBy( Visitable visitable )
{
+ final Set<SelectorName> symbols = new HashSet<SelectorName>();
+ // Walk the entire structure, so only supply a StrategyVisitor (that does no
navigation) ...
+ visitAll(visitable, new AbstractVisitor() {
+ @Override
+ public void visit( AllNodes allNodes ) {
+ if (allNodes.hasAlias()) {
+ symbols.add(allNodes.getAlias());
+ } else {
+ symbols.add(allNodes.getName());
+ }
+ }
+
+ @Override
+ public void visit( ChildNode childNode ) {
+ symbols.add(childNode.getSelectorName());
+ }
+
+ @Override
+ public void visit( ChildNodeJoinCondition joinCondition ) {
+ symbols.add(joinCondition.getChildSelectorName());
+ symbols.add(joinCondition.getParentSelectorName());
+ }
+
+ @Override
+ public void visit( Column column ) {
+ symbols.add(column.getSelectorName());
+ }
+
+ @Override
+ public void visit( DescendantNode descendant ) {
+ symbols.add(descendant.getSelectorName());
+ }
+
+ @Override
+ public void visit( DescendantNodeJoinCondition joinCondition ) {
+ symbols.add(joinCondition.getAncestorSelectorName());
+ symbols.add(joinCondition.getDescendantSelectorName());
+ }
+
+ @Override
+ public void visit( EquiJoinCondition joinCondition ) {
+ symbols.add(joinCondition.getSelector1Name());
+ symbols.add(joinCondition.getSelector2Name());
+ }
+
+ @Override
+ public void visit( FullTextSearch fullTextSearch ) {
+ symbols.add(fullTextSearch.getSelectorName());
+ }
+
+ @Override
+ public void visit( Length length ) {
+ symbols.add(length.getSelectorName());
+ }
+
+ @Override
+ public void visit( NodeLocalName node ) {
+ symbols.add(node.getSelectorName());
+ }
+
+ @Override
+ public void visit( NodeName node ) {
+ symbols.add(node.getSelectorName());
+ }
+
+ @Override
+ public void visit( NamedSelector node ) {
+ if (node.hasAlias()) {
+ symbols.add(node.getAlias());
+ } else {
+ symbols.add(node.getName());
+ }
+ }
+
+ @Override
+ public void visit( PropertyExistence prop ) {
+ symbols.add(prop.getSelectorName());
+ }
+
+ @Override
+ public void visit( PropertyValue prop ) {
+ symbols.add(prop.getSelectorName());
+ }
+
+ @Override
+ public void visit( SameNode node ) {
+ symbols.add(node.getSelectorName());
+ }
+
+ @Override
+ public void visit( SameNodeJoinCondition joinCondition ) {
+ symbols.add(joinCondition.getSelector1Name());
+ symbols.add(joinCondition.getSelector2Name());
+ }
+ });
+ return symbols;
+ }
+
+ /**
+ * A common base class for all visitors, which provides no-op implementations for all
{@code visit(...)} methods. Visitor
+ * implementations can subclass and implement only those methods that they need to
implement.
+ * <p>
+ * This is often an excellent base class for <i>strategy visitors</i>,
which simply are {@link Visitor} implementations that
+ * are responsible only for visiting the supplied object but that never call {@link
Visitable#accept(Visitor)}. Such strategy
+ * visitors can be used in conjunction with separate <i>{@link
NavigationVisitor navigation visitors}</i> that do the job of
+ * navigating the Visitable objects and, for each, delegating to the strategy
visitor. See
+ * {@link Visitors#visitAll(Visitable, Visitor)} for an example.
+ * </p>
+ */
+ public static class AbstractVisitor implements Visitor {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.AllNodes)
+ */
+ public void visit( AllNodes obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.And)
+ */
+ public void visit( And obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
+ */
+ public void visit( BindVariableName obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNode)
+ */
+ public void visit( ChildNode obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNodeJoinCondition)
+ */
+ public void visit( ChildNodeJoinCondition obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Column)
+ */
+ public void visit( Column obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Comparison)
+ */
+ public void visit( Comparison obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNode)
+ */
+ public void visit( DescendantNode obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNodeJoinCondition)
+ */
+ public void visit( DescendantNodeJoinCondition obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
+ */
+ public void visit( EquiJoinCondition obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearch)
+ */
+ public void visit( FullTextSearch obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearchScore)
+ */
+ public void visit( FullTextSearchScore obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Join)
+ */
+ public void visit( Join obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Length)
+ */
+ public void visit( Length obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Limit)
+ */
+ public void visit( Limit limit ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Literal)
+ */
+ public void visit( Literal obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.LowerCase)
+ */
+ public void visit( LowerCase obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeName)
+ */
+ public void visit( NodeName obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeLocalName)
+ */
+ public void visit( NodeLocalName obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NamedSelector)
+ */
+ public void visit( NamedSelector obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Not)
+ */
+ public void visit( Not obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Or)
+ */
+ public void visit( Or obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Ordering)
+ */
+ public void visit( Ordering obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
+ */
+ public void visit( PropertyExistence obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
+ */
+ public void visit( PropertyValue obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Query)
+ */
+ public void visit( Query obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNode)
+ */
+ public void visit( SameNode obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNodeJoinCondition)
+ */
+ public void visit( SameNodeJoinCondition obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
+ */
+ public void visit( SetQuery obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.UpperCase)
+ */
+ public void visit( UpperCase obj ) {
+ }
+ }
+
+ /**
+ * An abstract visitor implementation that performs navigation of the query object.
+ * <p>
+ * Subclasses should always implement the {@code visit(T object)} methods by
performing the following actions:
+ * <ol>
+ * <li>Call <code>strategy.visit(object);</code></li>
+ * <li>Add any children of {@code object} that are to be visited using {@link
#enqueue(Visitable)}</li>
+ * <li>Call {@link #visitNext()}</code></li>
+ * </ol>
+ * </p>
+ */
+ public static abstract class NavigationVisitor implements Visitor {
+ protected final Visitor strategy;
+ private final LinkedList<? super Visitable> itemQueue = new
LinkedList<Visitable>();
+
+ /**
+ * Create a visitor that walks all query objects.
+ *
+ * @param strategy the visitor that should be called at every node.
+ */
+ protected NavigationVisitor( Visitor strategy ) {
+ assert strategy != null;
+ this.strategy = strategy;
+ }
+
+ protected final void enqueue( Visitable objectToBeVisited ) {
+ itemQueue.add(objectToBeVisited);
+ }
+
+ protected final void enqueue( Iterable<? extends Visitable>
objectsToBeVisited ) {
+ for (Visitable objectToBeVisited : objectsToBeVisited) {
+ itemQueue.add(objectToBeVisited);
+ }
+ }
+
+ protected final void visitNext() {
+ if (!itemQueue.isEmpty()) {
+ Visitable first = (Visitable)itemQueue.removeFirst();
+ first.accept(this);
+ }
+ }
+ }
+
+ /**
+ * A visitor implementation that walks the entire query object tree and delegates to
another supplied visitor to do the actual
+ * work.
+ */
+ public static class WalkAllVisitor extends NavigationVisitor {
+
+ /**
+ * Create a visitor that walks all query objects.
+ *
+ * @param strategy the visitor that should be called at every node.
+ */
+ protected WalkAllVisitor( Visitor strategy ) {
+ super(strategy);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.AllNodes)
+ */
+ public void visit( AllNodes allNodes ) {
+ strategy.visit(allNodes);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.And)
+ */
+ public void visit( And and ) {
+ strategy.visit(and);
+ enqueue(and.getLeft());
+ enqueue(and.getRight());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
+ */
+ public void visit( BindVariableName variableName ) {
+ strategy.visit(variableName);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNode)
+ */
+ public void visit( ChildNode child ) {
+ strategy.visit(child);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNodeJoinCondition)
+ */
+ public void visit( ChildNodeJoinCondition joinCondition ) {
+ strategy.visit(joinCondition);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Column)
+ */
+ public void visit( Column column ) {
+ strategy.visit(column);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Comparison)
+ */
+ public void visit( Comparison comparison ) {
+ strategy.visit(comparison);
+ enqueue(comparison.getOperand1());
+ enqueue(comparison.getOperand2());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNode)
+ */
+ public void visit( DescendantNode descendant ) {
+ strategy.visit(descendant);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNodeJoinCondition)
+ */
+ public void visit( DescendantNodeJoinCondition condition ) {
+ strategy.visit(condition);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
+ */
+ public void visit( EquiJoinCondition condition ) {
+ strategy.visit(condition);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearch)
+ */
+ public void visit( FullTextSearch fullTextSearch ) {
+ strategy.visit(fullTextSearch);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearchScore)
+ */
+ public void visit( FullTextSearchScore score ) {
+ strategy.visit(score);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Join)
+ */
+ public void visit( Join join ) {
+ strategy.visit(join);
+ enqueue(join.getLeft());
+ enqueue(join.getJoinCondition());
+ enqueue(join.getRight());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Length)
+ */
+ public void visit( Length length ) {
+ strategy.visit(length);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Limit)
+ */
+ public void visit( Limit limit ) {
+ strategy.visit(limit);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Literal)
+ */
+ public void visit( Literal literal ) {
+ strategy.visit(literal);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.LowerCase)
+ */
+ public void visit( LowerCase lowerCase ) {
+ strategy.visit(lowerCase);
+ enqueue(lowerCase.getOperand());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeName)
+ */
+ public void visit( NodeName nodeName ) {
+ strategy.visit(nodeName);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeLocalName)
+ */
+ public void visit( NodeLocalName nodeLocalName ) {
+ strategy.visit(nodeLocalName);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NamedSelector)
+ */
+ public void visit( NamedSelector selector ) {
+ strategy.visit(selector);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Not)
+ */
+ public void visit( Not not ) {
+ strategy.visit(not);
+ enqueue(not.getConstraint());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Or)
+ */
+ public void visit( Or or ) {
+ strategy.visit(or);
+ enqueue(or.getLeft());
+ enqueue(or.getRight());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Ordering)
+ */
+ public void visit( Ordering ordering ) {
+ strategy.visit(ordering);
+ enqueue(ordering.getOperand());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
+ */
+ public void visit( PropertyExistence existence ) {
+ strategy.visit(existence);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
+ */
+ public void visit( PropertyValue propertyValue ) {
+ strategy.visit(propertyValue);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Query)
+ */
+ public void visit( Query query ) {
+ strategy.visit(query);
+ enqueue(query.getSource());
+ enqueue(query.getColumns());
+ enqueue(query.getConstraint());
+ enqueue(query.getOrderings());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNode)
+ */
+ public void visit( SameNode sameNode ) {
+ strategy.visit(sameNode);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNodeJoinCondition)
+ */
+ public void visit( SameNodeJoinCondition condition ) {
+ strategy.visit(condition);
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
+ */
+ public void visit( SetQuery setQuery ) {
+ strategy.visit(setQuery);
+ enqueue(setQuery.getLeft());
+ enqueue(setQuery.getRight());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.UpperCase)
+ */
+ public void visit( UpperCase upperCase ) {
+ strategy.visit(upperCase);
+ enqueue(upperCase.getOperand());
+ visitNext();
+ }
+ }
+
+ public static class ReadableVisitor implements Visitor {
+ private final StringBuilder sb = new StringBuilder();
+ private final ExecutionContext context;
+ private final NamespaceRegistry registry;
+
+ public ReadableVisitor( ExecutionContext context ) {
+ CheckArg.isNotNull(context, "context");
+ this.context = context;
+ this.registry = context == null ? null : context.getNamespaceRegistry();
+ }
+
+ public ReadableVisitor() {
+ this.context = null;
+ this.registry = null;
+ }
+
+ protected final ReadableVisitor append( String string ) {
+ sb.append(string);
+ return this;
+ }
+
+ protected final ReadableVisitor append( char character ) {
+ sb.append(character);
+ return this;
+ }
+
+ protected final ReadableVisitor append( int value ) {
+ sb.append(value);
+ return this;
+ }
+
+ protected final ReadableVisitor append( SelectorName name ) {
+ sb.append(name.getString(context));
+ return this;
+ }
+
+ protected final ReadableVisitor append( Name name ) {
+ sb.append(name.getString(registry, null, null));
+ return this;
+ }
+
+ protected final ReadableVisitor append( Path path ) {
+ sb.append('\'');
+ sb.append(path.getString(registry));
+ sb.append('\'');
+ return this;
+ }
+
+ /**
+ * @return context
+ */
+ public final ExecutionContext getContext() {
+ return context;
+ }
+
+ /**
+ * Get the string representation of the visited objects.
+ *
+ * @return the string representation
+ */
+ public final String getString() {
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.AllNodes)
+ */
+ public void visit( AllNodes allNodes ) {
+ append(allNodes.getName());
+ if (allNodes.hasAlias()) {
+ append(" AS ").append(allNodes.getAlias());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.And)
+ */
+ public void visit( And and ) {
+ append('(');
+ and.getLeft().accept(this);
+ append(" AND ");
+ and.getRight().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.BindVariableName)
+ */
+ public void visit( BindVariableName variable ) {
+ append('$').append(variable.getVariableName());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNode)
+ */
+ public void visit( ChildNode child ) {
+ append("ISCHILDNODE(");
+ append(child.getSelectorName());
+ append(',');
+ append(child.getParentPath());
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.ChildNodeJoinCondition)
+ */
+ public void visit( ChildNodeJoinCondition condition ) {
+ append("ISCHILDNODE(");
+ append(condition.getChildSelectorName());
+ append(',');
+ append(condition.getParentSelectorName());
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Column)
+ */
+ public void visit( Column column ) {
+ append(column.getSelectorName());
+ if (column.getPropertyName() == null) {
+ append(".*");
+ } else {
+ Name propertyName = column.getPropertyName();
+ String propName = propertyName.getString(registry, null, null);
+ append('.').append(propName);
+ if (!propName.equals(column.getColumnName()) &&
!propertyName.getLocalName().equals(column.getColumnName())) {
+ append(" AS ").append(column.getColumnName());
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Comparison)
+ */
+ public void visit( Comparison comparison ) {
+ comparison.getOperand1().accept(this);
+ append(' ').append(comparison.getOperator().getSymbol()).append('
');
+ comparison.getOperand2().accept(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNode)
+ */
+ public void visit( DescendantNode descendant ) {
+ append("ISDESCENDANTNODE(");
+ append(descendant.getSelectorName());
+ append(',');
+ append(descendant.getAncestorPath());
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.DescendantNodeJoinCondition)
+ */
+ public void visit( DescendantNodeJoinCondition condition ) {
+ append("ISDESCENDANTNODE(");
+ append(condition.getDescendantSelectorName());
+ append(',');
+ append(condition.getAncestorSelectorName());
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
+ */
+ public void visit( EquiJoinCondition condition ) {
+
append(condition.getSelector1Name()).append('.').append(condition.getProperty1Name());
+ append(" = ");
+
append(condition.getSelector2Name()).append('.').append(condition.getProperty2Name());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearch)
+ */
+ public void visit( FullTextSearch fullText ) {
+ append("CONTAINS(").append(fullText.getSelectorName());
+ if (fullText.getPropertyName() != null) {
+ append('.').append(fullText.getPropertyName());
+ }
+
sb.append(",'").append(fullText.getFullTextSearchExpression()).append("')");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.FullTextSearchScore)
+ */
+ public void visit( FullTextSearchScore score ) {
+
append("SCORE(").append(score.getSelectorName()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Join)
+ */
+ public void visit( Join join ) {
+ join.getLeft().accept(this);
+ // if (join.getType() != JoinType.INNER) {
+ sb.append(' ').append(join.getType().getSymbol());
+ // } else {
+ // sb.append(',');
+ // }
+ append(' ');
+ join.getRight().accept(this);
+ append(" ON ");
+ join.getJoinCondition().accept(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Length)
+ */
+ public void visit( Length length ) {
+ append("LENGTH(");
+ length.getPropertyValue().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Limit)
+ */
+ public void visit( Limit limit ) {
+ append("LIMIT ").append(limit.getRowLimit());
+ if (limit.getOffset() != 0) {
+ append(" OFFSET ").append(limit.getOffset());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Literal)
+ */
+ public void visit( Literal literal ) {
+ if (context == null) append(literal.getValue().toString());
+ else
append(context.getValueFactories().getStringFactory().create(literal.getValue()));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.LowerCase)
+ */
+ public void visit( LowerCase lowerCase ) {
+ append("LOWER(");
+ lowerCase.getOperand().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeLocalName)
+ */
+ public void visit( NodeLocalName name ) {
+
append("LOCALNAME(").append(name.getSelectorName()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NodeName)
+ */
+ public void visit( NodeName name ) {
+
append("NAME(").append(name.getSelectorName()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.NamedSelector)
+ */
+ public void visit( NamedSelector selector ) {
+ append(selector.getName());
+ if (selector.hasAlias()) {
+ append(" AS ").append(selector.getAlias());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Not)
+ */
+ public void visit( Not not ) {
+ append('(');
+ append("NOT ");
+ not.getConstraint().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Or)
+ */
+ public void visit( Or or ) {
+ append('(');
+ or.getLeft().accept(this);
+ append(" OR ");
+ or.getRight().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Ordering)
+ */
+ public void visit( Ordering ordering ) {
+ ordering.getOperand().accept(this);
+ append(' ').append(ordering.getOrder().getSymbol());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
+ */
+ public void visit( PropertyExistence existence ) {
+
append(existence.getSelectorName()).append('.').append(existence.getPropertyName()).append("
IS NOT NULL");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
+ */
+ public void visit( PropertyValue value ) {
+
append(value.getSelectorName()).append('.').append(value.getPropertyName());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.Query)
+ */
+ public void visit( Query query ) {
+ append("SELECT ");
+ if (query.isDistinct()) append("DISTINCT ");
+ if (query.getColumns().isEmpty()) {
+ append('*');
+ } else {
+ boolean isFirst = true;
+ for (Column column : query.getColumns()) {
+ if (isFirst) isFirst = false;
+ else append(',');
+ column.accept(this);
+ }
+ }
+ append(" FROM ");
+ query.getSource().accept(this);
+ if (query.getConstraint() != null) {
+ append(" WHERE ");
+ query.getConstraint().accept(this);
+ }
+ if (!query.getOrderings().isEmpty()) {
+ append(" ORDER BY ");
+ boolean isFirst = true;
+ for (Ordering ordering : query.getOrderings()) {
+ if (isFirst) isFirst = false;
+ else append(',');
+ ordering.accept(this);
+ }
+ }
+ if (!query.getLimits().isUnlimited()) {
+ append(' ');
+ query.getLimits().accept(this);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNode)
+ */
+ public void visit( SameNode sameNode ) {
+
append("ISSAMENODE(").append(sameNode.getSelectorName()).append(',').append(sameNode.getPath()).append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SameNodeJoinCondition)
+ */
+ public void visit( SameNodeJoinCondition condition ) {
+
append("ISSAMENODE(").append(condition.getSelector1Name()).append(',').append(condition.getSelector2Name());
+ if (condition.getSelector2Path() != null) {
+ append(',').append(condition.getSelector2Path());
+ }
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.SetQuery)
+ */
+ public void visit( SetQuery query ) {
+ query.getLeft().accept(this);
+ append(' ').append(query.getOperation().getSymbol()).append('
');
+ if (query.isAll()) append("ALL ");
+ query.getRight().accept(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Visitor#visit(org.jboss.dna.graph.query.model.UpperCase)
+ */
+ public void visit( UpperCase upperCase ) {
+ append("UPPER(");
+ upperCase.getOperand().accept(this);
+ append(')');
+ }
+
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,96 @@
+/*
+ * 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.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that inserts an ACCESS above each SOURCE node
in a query plan. This rule is often the
+ * first rule to run against a {@link CanonicalPlanner canonical plan} (see
+ * {@link RuleBasedOptimizer#populateRuleStack(LinkedList, PlanHints)}.
+ * <p>
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * After:
+ *
+ * <pre>
+ * ...
+ * |
+ * ACCESS
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * </p>
+ */
+@Immutable
+public class AddAccessNodes implements OptimizerRule {
+
+ public static final AddAccessNodes INSTANCE = new AddAccessNodes();
+
+ /**
+ * {@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 ) {
+ // On each of the source nodes ...
+ for (PlanNode source : plan.findAllAtOrBelow(Type.SOURCE)) {
+ // The source node should have no children ...
+ if (source.getChildCount() != 0) continue;
+
+ // Create the ACCESS node, set the selectors, and insert above the source
node ...
+ PlanNode access = new PlanNode(Type.ACCESS);
+ access.addSelectors(source.getSelectors());
+ source.insertAsParent(access);
+ }
+ 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/AddAccessNodes.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithm.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithm.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithm.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,242 @@
+/*
+ * 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 java.util.List;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.Order;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.JoinAlgorithm;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that choose the appropriate join algorithm and
sets up any prerequisites, based upon
+ * the {@link JoinCondition}.
+ * <p>
+ * There are two static instances that can be used (or the equivalent can be instantiated
or subclassed using the constructor):
+ * one that only uses {@link JoinAlgorithm#NESTED_LOOP nested-loop}, and another that
will attempt to use
+ * {@link JoinAlgorithm#MERGE merge} where possible. Both instances ignore any existing
{@link Property#JOIN_ALGORITHM} property
+ * value set on the JOIN node.
+ * </p>
+ * <p>
+ * For example, the {@link #USE_ONLY_NESTED_JOIN_ALGORITHM} instance will convert this
simple tree:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN
+ * / \
+ * ... ...
+ * </pre>
+ *
+ * into this:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN ({@link Property#JOIN_ALGORITHM JOIN_ALGORITHM}={@link
JoinAlgorithm#NESTED_LOOP NESTED_LOOP})
+ * / \
+ * ... ...
+ * </pre>
+ *
+ * On the other hand, the {@link #USE_BEST_JOIN_ALGORITHM} instance will do a couple of
different things, depending upon the input
+ * plan.
+ * <ol>
+ * <li>If the condition is a {@link DescendantNodeJoinCondition}, then the join
algorithm will always be
+ * {@link JoinAlgorithm#NESTED_LOOP}.</li>
+ * <li>Otherwise, the rule will use the {@link JoinAlgorithm#MERGE} algorithm and
will change this structure:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN
+ * / \
+ * ... ...
+ * </pre>
+ *
+ * into this:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN ({@link Property#JOIN_ALGORITHM JOIN_ALGORITHM}={@link
JoinAlgorithm#MERGE MERGE})
+ * / \
+ * / \
+ * DUP_REMOVE DUP_REMOVE
+ * | |
+ * SORT SORT
+ * | |
+ * ... ...
+ * </pre>
+ *
+ * </li>
+ * </ol>
+ * </p>
+ */
+@Immutable
+public class ChooseJoinAlgorithm implements OptimizerRule {
+
+ public static final ChooseJoinAlgorithm USE_ONLY_NESTED_JOIN_ALGORITHM = new
ChooseJoinAlgorithm(true);
+ public static final ChooseJoinAlgorithm USE_BEST_JOIN_ALGORITHM = new
ChooseJoinAlgorithm(false);
+
+ private final boolean useOnlyNested;
+
+ protected ChooseJoinAlgorithm( boolean useOnlyNested ) {
+ this.useOnlyNested = useOnlyNested;
+ }
+
+ /**
+ * {@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 JOIN nodes ...
+ for (PlanNode joinNode : plan.findAllAtOrBelow(Type.JOIN)) {
+ JoinCondition condition = joinNode.getProperty(Property.JOIN_CONDITION,
JoinCondition.class);
+ if (useOnlyNested) {
+ joinNode.setProperty(Property.JOIN_ALGORITHM,
JoinAlgorithm.NESTED_LOOP);
+ break;
+ }
+
+ if (condition instanceof DescendantNodeJoinCondition) {
+ // It has to be a nest-loop join ...
+ joinNode.setProperty(Property.JOIN_ALGORITHM,
JoinAlgorithm.NESTED_LOOP);
+ } else {
+ joinNode.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.MERGE);
+ assert joinNode.getChildCount() == 2;
+
+ // We can try to use the merge join, but we need to sort and remove
remove duplicates ...
+ // on the left and right children of the join ...
+ Set<SelectorName> leftSelectors =
joinNode.getFirstChild().getSelectors();
+ Set<SelectorName> rightSelectors =
joinNode.getLastChild().getSelectors();
+ List<Object> leftSortBy = new LinkedList<Object>();
+ List<Object> rightSortBy = new LinkedList<Object>();
+ createOrderBysForJoinCondition(condition, leftSelectors, leftSortBy,
rightSelectors, rightSortBy);
+
+ PlanNode leftSort = new PlanNode(Type.SORT, leftSelectors);
+ leftSort.setProperty(Property.SORT_ORDER_BY, leftSortBy);
+ joinNode.getFirstChild().insertAsParent(leftSort);
+ if (joinNode.getFirstChild().findAllAtOrBelow(Type.DUP_REMOVE).isEmpty())
{
+ // There is no duplicate removal below the left-hand side of the
join, so insert it ...
+ PlanNode leftDupRemoval = new PlanNode(Type.DUP_REMOVE,
leftSelectors);
+ joinNode.getFirstChild().insertAsParent(leftDupRemoval);
+ }
+
+ // There is no sort below the right-hand side of the join, so insert it
...
+ PlanNode rightSort = new PlanNode(Type.SORT, rightSelectors);
+ rightSort.setProperty(Property.SORT_ORDER_BY, rightSortBy);
+ joinNode.getLastChild().insertAsParent(rightSort);
+ if (joinNode.getLastChild().findAllAtOrBelow(Type.DUP_REMOVE).isEmpty())
{
+ // There is no duplicate removal below the right-hand side of the
join, so insert it ...
+ PlanNode rightDupRemoval = new PlanNode(Type.DUP_REMOVE,
rightSelectors);
+ joinNode.getLastChild().insertAsParent(rightDupRemoval);
+ }
+ }
+ }
+ return plan;
+ }
+
+ protected void createOrderBysForJoinCondition( JoinCondition condition,
+ Set<SelectorName>
leftSelectors,
+ List<Object> leftSortBy,
+ Set<SelectorName>
rightSelectors,
+ List<Object> rightSortBy ) {
+ if (condition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition joinCondition = (SameNodeJoinCondition)condition;
+ SelectorName name1 = joinCondition.getSelector1Name();
+ SelectorName name2 = joinCondition.getSelector2Name();
+ if (leftSelectors.contains(name1)) {
+ leftSortBy.add(name1);
+ rightSortBy.add(name2);
+ } else {
+ leftSortBy.add(name2);
+ rightSortBy.add(name1);
+ }
+ } else if (condition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition joinCondition = (ChildNodeJoinCondition)condition;
+ SelectorName childName = joinCondition.getChildSelectorName();
+ SelectorName parentName = joinCondition.getParentSelectorName();
+ if (leftSelectors.contains(childName)) {
+ leftSortBy.add(childName);
+ rightSortBy.add(parentName);
+ } else {
+ leftSortBy.add(parentName);
+ rightSortBy.add(childName);
+ }
+ } else if (condition instanceof EquiJoinCondition) {
+ EquiJoinCondition joinCondition = (EquiJoinCondition)condition;
+ SelectorName selector1 = joinCondition.getSelector1Name();
+ SelectorName selector2 = joinCondition.getSelector2Name();
+ Name property1 = joinCondition.getProperty1Name();
+ Name property2 = joinCondition.getProperty2Name();
+
+ // Create the Ordering for the first selector/property pair ...
+ DynamicOperand operand1 = new PropertyValue(selector1, property1);
+ Ordering ordering1 = new Ordering(operand1, Order.ASCENDING);
+ // Create the Ordering for the second selector/property pair ...
+ DynamicOperand operand2 = new PropertyValue(selector2, property2);
+ Ordering ordering2 = new Ordering(operand2, Order.ASCENDING);
+
+ if (leftSelectors.contains(selector1)) {
+ leftSortBy.add(ordering1);
+ rightSortBy.add(ordering2);
+ } else {
+ leftSortBy.add(ordering2);
+ rightSortBy.add(ordering1);
+ }
+ } else {
+ assert false;
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * {@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/ChooseJoinAlgorithm.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/Optimizer.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/Optimizer.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/Optimizer.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,44 @@
+/*
+ * 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 org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.plan.PlanNode;
+
+/**
+ * Interface for an optimizer.
+ */
+public interface Optimizer {
+
+ /**
+ * Optimize the supplied query plan and produce an executable processor plan.
+ *
+ * @param context the context in which the query is being optimized
+ * @param plan the query plan to be optimized
+ * @return the optimized query plan; never null
+ */
+ PlanNode optimize( QueryContext context,
+ PlanNode plan );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/Optimizer.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/OptimizerRule.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/OptimizerRule.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/OptimizerRule.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,49 @@
+/*
+ * 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.plan.PlanNode;
+
+/**
+ * Interface that defines an {@link Optimizer} rule.
+ */
+@Immutable
+public interface OptimizerRule {
+
+ /**
+ * Optimize the supplied plan using the supplied context, hints, and yet-to-be-run
rules.
+ *
+ * @param context the context in which the query is being optimized; never null
+ * @param plan the plan to be optimized; never null
+ * @param ruleStack the stack of rules that will be run after this rule; never null
+ * @return the optimized plan; never null
+ */
+ PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/OptimizerRule.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,381 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that attempts to push the criteria nodes in a
canonical plan down as far as possible.
+ * <p>
+ * For example, here is a single-access plan before:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECTn
+ * |
+ * ACCESS
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * ACCESS
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECTn
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * Here is another case, where multiple SELECT nodes above a simple JOIN and where each
SELECT node applies to one or more of the
+ * SOURCE nodes (via the named selectors). Each SELECT node that applies to a single
selector will get pushed toward that source,
+ * but will have the same order relative to other SELECT nodes also pushed toward that
SOURCE. However, this rules does not push
+ * SELECT nodes that apply to multiple selectors.
+ * </p>
+ * <p>
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT ('s1','s2') with the list of columns being
SELECTed (from 's1' and 's2' selectors)
+ * |
+ * SELECT1 ('s1')
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 ('s2') a single non-join constraint that are then all
AND-ed
+ * | together, and that each have the selector(s) they
apply to
+ * SELECT3 ('s1','s2')
+ * |
+ * SELECT4 ('s1')
+ * |
+ * JOIN ('s1','s2')
+ * / \
+ * / \
+ * ACCESS ACCESS
+ * ('s1') ('s2')
+ * | |
+ * SOURCE SOURCE
+ * ('s1') ('s2')
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT ('s1','s2') with the list of columns being
SELECTed (from 's1' and 's2' selectors)
+ * |
+ * SELECT3 ('s1','s2') Any SELECT plan nodes that apply to
multiple selectors are left above
+ * | the ACCESS nodes.
+ * JOIN ('s1','s2')
+ * / \
+ * / \
+ * ACCESS ACCESS
+ * ('s1') ('s2')
+ * | |
+ * SELECT1 SELECT2
+ * ('s1') ('s2')
+ * | |
+ * SELECT4 SOURCE
+ * ('s1') ('s2')
+ * |
+ * SOURCE
+ * ('s1')
+ * </pre>
+ *
+ * </p>
+ */
+@Immutable
+public class PushSelectCriteria implements OptimizerRule {
+
+ public static final PushSelectCriteria INSTANCE = new PushSelectCriteria();
+ private static final Set<Type> ORIGINATING_TYPES =
Collections.unmodifiableSet(EnumSet.of(Type.NULL,
+
Type.SOURCE,
+
Type.JOIN,
+
Type.SET_OPERATION));
+
+ /**
+ * {@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 ) {
+ // Create set of nodes that no longer need to be considered
+ Set<PlanNode> deadNodes = new HashSet<PlanNode>();
+
+ // Loop while criteria nodes are still being moved ...
+ boolean movedSomeNode = true;
+ while (movedSomeNode) {
+
+ // Reset flag to false for this iteration
+ movedSomeNode = false;
+
+ // Find all of the criteria (SELECT) nodes that can be pushed ...
+ List<PlanNode> criteriaNodes = plan.findAllAtOrBelow(Type.SELECT);
+
+ // Find all of the NULL, SOURCE, SET_OPERATION or JOIN nodes, ordered
correctly; we'll use this
+ // to look for the node on which each criteria can apply ...
+ List<PlanNode> originatingNodes =
plan.findAllAtOrBelow(ORIGINATING_TYPES);
+
+ // Starting with the lowest one first ...
+ Collections.reverse(criteriaNodes);
+ for (PlanNode criteriaNode : criteriaNodes) {
+ // Skip any node we've already tried and failed to move ...
+ if (deadNodes.contains(criteriaNode)) continue;
+
+ // Find the first originating node that has all of the required selectors
for this criteria ...
+ PlanNode originatingNode = findOriginatingNode(criteriaNode,
originatingNodes);
+ if (originatingNode == null || originatingNode == criteriaNode) {
+ deadNodes.add(criteriaNode);
+ continue;
+ }
+
+ // Try to push the criteria node down ...
+ if (!pushTowardsOriginatingNode(criteriaNode, originatingNode)) {
+ // criteria node could not be moved ...
+ deadNodes.add(criteriaNode);
+ continue;
+ }
+
+ // The criteria node was indeed moved, but it may need to be adjusted
...
+ boolean adjusted = false;
+ switch (originatingNode.getType()) {
+ case SOURCE:
+
+ break;
+ case JOIN:
+ if (!criteriaNode.hasAncestorOfType(Type.ACCESS)) {
+ // Try to push down the join criteria (only when above ACCESS
nodes) ...
+ adjusted = pushDownJoinCriteria(criteriaNode,
originatingNode);
+ }
+ break;
+ default:
+ // Nothing to change ...
+ }
+ if (adjusted) {
+ // We changed something, so make sure we go through the loop again
...
+ movedSomeNode = true;
+ } else {
+ // Nothing was changed from the push-down, so consider this criteria
node as processed ...
+ deadNodes.add(criteriaNode);
+ }
+ }
+ }
+ return plan;
+ }
+
+ /**
+ * Attempt to push down criteria that applies to the JOIN as additional constraints
on the JOIN itself.
+ *
+ * @param criteriaNode the SELECT node; may not be null
+ * @param joinNode the JOIN node; may not be null
+ * @return true if the criteria was pushed down, or false otherwise
+ */
+ protected boolean pushDownJoinCriteria( PlanNode criteriaNode,
+ PlanNode joinNode ) {
+ JoinType joinType = (JoinType)joinNode.getProperty(Property.JOIN_TYPE);
+
+ switch (joinType) {
+ case CROSS:
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ moveCriteriaIntoOnClause(criteriaNode, joinNode);
+ break;
+ case INNER:
+ moveCriteriaIntoOnClause(criteriaNode, joinNode);
+ break;
+ default:
+ // This is where we could attempt to optimize the join type ...
+ // if (optimizeJoinType(criteriaNode, joinNode) == JoinType.INNER) {
+ // // The join type has changed ...
+ // moveCriteriaIntoOnClause(criteriaNode, joinNode);
+ // return true; // since the join type has changed ...
+ // }
+ }
+ return false;
+ }
+
+ /**
+ * Move the criteria that applies to the join to be included in the actual join
criteria.
+ *
+ * @param criteriaNode the SELECT node; may not be null
+ * @param joinNode the JOIN node; may not be null
+ */
+ private void moveCriteriaIntoOnClause( PlanNode criteriaNode,
+ PlanNode joinNode ) {
+ List<Constraint> constraints =
joinNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
+ Constraint criteria = criteriaNode.getProperty(Property.SELECT_CRITERIA,
Constraint.class);
+
+ // since the parser uses EMPTY_LIST, check for size 0 also
+ if (constraints == null || constraints.isEmpty()) {
+ constraints = new LinkedList<Constraint>();
+ joinNode.setProperty(Property.JOIN_CONSTRAINTS, constraints);
+ }
+
+ if (!constraints.contains(criteria)) {
+ constraints.add(criteria);
+ if (criteriaNode.hasBooleanProperty(Property.IS_DEPENDENT)) {
+ joinNode.setProperty(Property.IS_DEPENDENT, Boolean.TRUE);
+ }
+ }
+ criteriaNode.extractFromParent();
+ }
+
+ /**
+ * Find the first node that has all of the same {@link PlanNode#getSelectors()
selectors} as the supplied criteria.
+ *
+ * @param criteriaNode the criteria
+ * @param originatingNodes the list of nodes to search
+ * @return the first (highest) node that is uses all of the same selectors as the
criteria, or null if no such node could be
+ * found
+ */
+ protected PlanNode findOriginatingNode( PlanNode criteriaNode,
+ List<PlanNode> originatingNodes ) {
+ Set<SelectorName> requiredSelectors = criteriaNode.getSelectors();
+ if (requiredSelectors.isEmpty()) return criteriaNode;
+
+ // first look for originating nodes that exactly match the required selectors
...
+ for (PlanNode originatingNode : originatingNodes) {
+ if (originatingNode.getSelectors().equals(requiredSelectors)) return
originatingNode;
+ }
+
+ // Nothing matched exactly, so can we push down to a node that contain all of the
required selectors ...
+ for (PlanNode originatingNode : originatingNodes) {
+ if (originatingNode.getSelectors().containsAll(requiredSelectors)) return
originatingNode;
+ }
+ return null;
+ }
+
+ /**
+ * Push the criteria node as close to the originating node as possible. In general,
the criteria node usually ends up being
+ * moved to be a parent of the supplied originating node, except in a couple of
cases:
+ * <ul>
+ * <li>There are already criteria nodes immediately above the originating node;
in this case, the supplied criteria node is
+ * placed above all these existing criteria nodes.</li>
+ * <li>The originating node is below a JOIN node that is itself below an ACCESS
node; in this case, the criteria node is
+ * placed immediately above the JOIN node.</li>
+ * <li>The originating node is below a LIMIT with a single SORT child node; in
this case, the criteria node is placed
+ * immediately above the LIMIT node.</li>
+ * </ul>
+ *
+ * @param criteriaNode the criteria node that is to be pushed down; may not be null
+ * @param originatingNode the target node that represents the node above which the
criteria node should be inserted; may not
+ * be null
+ * @return true if the criteria node was pushed down, or false if the criteria node
could not be pushed down
+ */
+ protected boolean pushTowardsOriginatingNode( PlanNode criteriaNode,
+ PlanNode originatingNode ) {
+ // To keep things stable, 'originatingNode' should be the top-most SELECT
(criteria) node above a SOURCE ...
+ while (originatingNode.getParent().getType() == Type.SELECT) {
+ originatingNode = originatingNode.getParent();
+ if (originatingNode == criteriaNode) return false;
+ }
+
+ // Find out the best node above which the criteria node should be placed ...
+ PlanNode bestChild = findBestChildForCriteria(criteriaNode, originatingNode);
+ if (bestChild == criteriaNode) return false;
+ criteriaNode.extractFromParent();
+ bestChild.insertAsParent(criteriaNode);
+ assert atBoundary(criteriaNode, originatingNode);
+ return true;
+ }
+
+ protected PlanNode findBestChildForCriteria( PlanNode criteriaNode,
+ PlanNode originatingNode ) {
+ // Walk the nodes, from the criteria node down to the originating node ...
+ for (PlanNode node : criteriaNode.getPathTo(originatingNode)) {
+ // Check the node to see if there is any reason why the node cannot be
pushed
+ if (node.getType() == Type.JOIN) {
+ // Pushing below a JOIN is not necessary under an ACCESS node
+ if (node.hasAncestorOfType(Type.ACCESS)) return node;
+ } else if (node.getType() == Type.LIMIT) {
+ // Don't push below a LIMIT above a SORT ...
+ if (node.getChildCount() == 1 && node.getFirstChild().getType()
== Type.SORT) {
+ return node;
+ }
+ }
+ }
+ return originatingNode;
+ }
+
+ /**
+ * Determine whether all of the nodes between the criteria node and its originating
node are criteria (SELECT) nodes.
+ *
+ * @param criteriaNode the criteria node; may not be null
+ * @param originatingNode the originating node
+ * @return true if all nodes between the criteria and originating nodes are SELECT
nodes
+ */
+ protected boolean atBoundary( PlanNode criteriaNode,
+ PlanNode originatingNode ) {
+ // Walk from source node to critNode to check each intervening node
+ PlanNode currentNode = originatingNode.getParent();
+ while (currentNode != criteriaNode) {
+ if (currentNode.getType() != Type.SELECT) return false;
+ currentNode = currentNode.getParent();
+ }
+ return true;
+ }
+
+ /**
+ * {@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/PushSelectCriteria.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,454 @@
+/*
+ * 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.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.plan.PlanNode;
+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 rewrites JOIN nodes that have {@link
EquiJoinCondition equi-join criteria} where
+ * the columns involved in the equi-join are all identity columns (that is, they form a
{@link Schemata.Table#getKeys() key} for
+ * the table). This rewrite only happens when the left and right children of the JOIN
node are both SOURCE nodes.
+ * <p>
+ * The basic idea is that in these identity equi-join cases, the following structure:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN
+ * / \
+ * / \
+ * SOURCE SOURCE
+ * </pre>
+ *
+ * is transformed into a simple SOURCE node:
+ *
+ * <pre>
+ * ...
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * Note that this rewriting removes a selector, and thus the nodes above the JOIN node
that made use of the removed selector are
+ * also modified to reference the remaining selector.
+ * </p>
+ */
+@Immutable
+public class RewriteIdentityJoins implements OptimizerRule {
+
+ public static final RewriteIdentityJoins INSTANCE = new RewriteIdentityJoins();
+
+ /**
+ * {@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 JOIN nodes ...
+ Map<SelectorName, SelectorName> rewrittenSelectors = null;
+ for (PlanNode joinNode : plan.findAllAtOrBelow(Type.JOIN)) {
+ JoinCondition condition = joinNode.getProperty(Property.JOIN_CONDITION,
JoinCondition.class);
+ if (condition instanceof EquiJoinCondition) {
+ PlanNode leftNode = joinNode.getFirstChild();
+ PlanNode rightNode = joinNode.getLastChild();
+ assert leftNode != null;
+ assert rightNode != null;
+ if (leftNode.getType() == Type.SOURCE && rightNode.getType() ==
Type.SOURCE) {
+ EquiJoinCondition equiJoin = (EquiJoinCondition)condition;
+ // Find the names (or aliases) of the tables ...
+ Schemata schemata = context.getSchemata();
+ assert schemata != null;
+ SelectorName leftTableName =
leftNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
+ SelectorName rightTableName =
rightNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
+ assert leftTableName != null;
+ assert rightTableName != null;
+ // Presumably the join condition is using at least one alias, but we
only care about the actual name ...
+ if (!leftTableName.equals(rightTableName)) {
+ // The join is not joining the same table, so this doesn't
meet the condition ...
+ continue;
+ }
+ // Find the schemata columns referenced by the join condition ...
+ Table table = schemata.getTable(leftTableName);
+ if (table == null) {
+ context.getProblems().addError(GraphI18n.tableDoesNotExist,
leftTableName);
+ continue;
+ }
+ ValueFactory<String> stringFactory =
context.getExecutionContext().getValueFactories().getStringFactory();
+ String leftColumnName =
stringFactory.create(equiJoin.getProperty1Name());
+ String rightColumnName =
stringFactory.create(equiJoin.getProperty2Name());
+ Schemata.Column leftColumn = table.getColumn(leftColumnName);
+ Schemata.Column rightColumn = table.getColumn(rightColumnName);
+ if (leftColumn == null) {
+
context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, leftColumnName,
leftTableName);
+ continue;
+ }
+ if (rightColumn == null) {
+
context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, rightColumnName,
leftTableName);
+ continue;
+ }
+ // Are the join columns (on both sides) keys?
+ if (table.hasKey(leftColumn) && (rightColumn == leftColumn ||
table.hasKey(rightColumn))) {
+ // It meets all the criteria, so rewrite this join node ...
+ if (rewrittenSelectors == null) rewrittenSelectors = new
HashMap<SelectorName, SelectorName>();
+ rewriteJoinNode(context, joinNode, equiJoin,
rewrittenSelectors);
+ }
+ }
+ }
+ }
+
+ if (rewrittenSelectors != null && !rewrittenSelectors.isEmpty()) {
+ // We re-wrote at least one JOIN, but since this only applies to JOIN nodes
that meet certain criteria,
+ // the rewriting may have changed JOIN nodes that did not meet this criteria
into nodes that now meet
+ // this criteria, so we need to re-run this rule...
+ ruleStack.addFirst(this);
+
+ // Now rewrite the various portions of the plan that make use of the
now-removed selectors ...
+ replaceReferencesToRemovedSource(context, plan, rewrittenSelectors);
+ } else {
+ // There are no-untouched JOIN nodes, which means the sole JOIN node was
rewritten as a single SOURCE node
+ assert plan.findAllAtOrBelow(Type.JOIN).isEmpty();
+ context.getHints().hasJoin = false;
+ }
+ return plan;
+ }
+
+ protected void rewriteJoinNode( QueryContext context,
+ PlanNode joinNode,
+ EquiJoinCondition joinCondition,
+ Map<SelectorName, SelectorName>
rewrittenSelectors ) {
+ // Remove the right source node from the join node ...
+ PlanNode rightSource = joinNode.getLastChild();
+ rightSource.removeFromParent();
+
+ // Replace the join node with the left source node ...
+ PlanNode leftSource = joinNode.getFirstChild();
+ joinNode.extractFromParent();
+
+ // Now record that references to the right selector name should be removed ...
+ SelectorName rightTableName = rightSource.getProperty(Property.SOURCE_NAME,
SelectorName.class);
+ SelectorName rightTableAlias = rightSource.getProperty(Property.SOURCE_ALIAS,
SelectorName.class);
+ SelectorName leftTableAlias = leftSource.getProperty(Property.SOURCE_ALIAS,
SelectorName.class);
+ if (leftTableAlias != null) {
+ if (rightTableName != null) rewrittenSelectors.put(rightTableName,
leftTableAlias);
+ if (rightTableAlias != null) rewrittenSelectors.put(rightTableAlias,
leftTableAlias);
+ } else {
+ SelectorName leftTableName = leftSource.getProperty(Property.SOURCE_NAME,
SelectorName.class);
+ assert leftTableName != null;
+ if (rightTableName != null) rewrittenSelectors.put(rightTableName,
leftTableName);
+ if (rightTableAlias != null) rewrittenSelectors.put(rightTableAlias,
leftTableName);
+ }
+ }
+
+ protected void replaceReferencesToRemovedSource( QueryContext context,
+ PlanNode planNode,
+ Map<SelectorName,
SelectorName> rewrittenSelectors ) {
+ switch (planNode.getType()) {
+ case PROJECT:
+ List<Column> columns =
planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ for (int i = 0; i != columns.size(); ++i) {
+ Column column = columns.get(i);
+ SelectorName replacement =
rewrittenSelectors.get(column.getSelectorName());
+ if (replacement != null) {
+ columns.set(i, new Column(replacement, column.getPropertyName(),
column.getColumnName()));
+ }
+ }
+ break;
+ case SELECT:
+ Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA,
Constraint.class);
+ Constraint newConstraint = replaceReferencesToRemovedSource(context,
constraint, rewrittenSelectors);
+ if (constraint != newConstraint) {
+ planNode.setProperty(Property.SELECT_CRITERIA, newConstraint);
+ }
+ break;
+ case SORT:
+ List<Object> orderBys =
planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
+ if (orderBys != null && !orderBys.isEmpty()) {
+ if (orderBys.get(0) instanceof SelectorName) {
+ for (int i = 0; i != orderBys.size(); ++i) {
+ SelectorName selectorName = (SelectorName)orderBys.get(i);
+ SelectorName replacement =
rewrittenSelectors.get(selectorName);
+ if (replacement != null) {
+ orderBys.set(i, replacement);
+ }
+ }
+ } else {
+ for (int i = 0; i != orderBys.size(); ++i) {
+ Ordering ordering = (Ordering)orderBys.get(i);
+ DynamicOperand operand = ordering.getOperand();
+ orderBys.set(i, replaceReferencesToRemovedSource(context,
operand, rewrittenSelectors));
+ }
+ }
+ }
+ break;
+ case JOIN:
+ // Update the join condition ...
+ JoinCondition joinCondition =
planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+ JoinCondition newCondition = replaceReferencesToRemovedSource(context,
joinCondition, rewrittenSelectors);
+ if (joinCondition != newCondition) {
+ planNode.setProperty(Property.JOIN_CONDITION, newCondition);
+ }
+
+ // Update the join criteria (if there are some) ...
+ List<Constraint> constraints =
planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
+ if (constraints != null && !constraints.isEmpty()) {
+ for (int i = 0; i != constraints.size(); ++i) {
+ Constraint old = constraints.get(i);
+ Constraint replacement =
replaceReferencesToRemovedSource(context, old, rewrittenSelectors);
+ if (replacement != old) {
+ constraints.set(i, replacement);
+ }
+ }
+ }
+ break;
+ case GROUP:
+ case SET_OPERATION:
+ case DUP_REMOVE:
+ case LIMIT:
+ case NULL:
+ case SOURCE:
+ case ACCESS:
+ // None of these have to be changed ...
+ break;
+ }
+
+ // Update the selectors referenced by the node ...
+ Set<SelectorName> selectorsToAdd = null;
+ for (Iterator<SelectorName> iter = planNode.getSelectors().iterator();
iter.hasNext();) {
+ SelectorName replacement = rewrittenSelectors.get(iter.next());
+ if (replacement != null) {
+ iter.remove();
+ if (selectorsToAdd == null) selectorsToAdd = new
HashSet<SelectorName>();
+ selectorsToAdd.add(replacement);
+ }
+ }
+ if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd);
+
+ // Now call recursively on the children ...
+ for (PlanNode child : planNode) {
+ replaceReferencesToRemovedSource(context, child, rewrittenSelectors);
+ }
+ }
+
+ protected DynamicOperand replaceReferencesToRemovedSource( QueryContext context,
+ DynamicOperand operand,
+ Map<SelectorName,
SelectorName> rewrittenSelectors ) {
+ if (operand instanceof FullTextSearchScore) {
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ SelectorName replacement = rewrittenSelectors.get(score.getSelectorName());
+ if (replacement == null) return score;
+ return new FullTextSearchScore(replacement);
+ }
+ if (operand instanceof Length) {
+ Length operation = (Length)operand;
+ PropertyValue wrapped = operation.getPropertyValue();
+ SelectorName replacement =
rewrittenSelectors.get(wrapped.getSelectorName());
+ if (replacement == null) return operand;
+ return new Length(new PropertyValue(replacement,
wrapped.getPropertyName()));
+ }
+ if (operand instanceof LowerCase) {
+ LowerCase operation = (LowerCase)operand;
+ SelectorName replacement =
rewrittenSelectors.get(operation.getSelectorName());
+ if (replacement == null) return operand;
+ return new LowerCase(replaceReferencesToRemovedSource(context, operand,
rewrittenSelectors));
+ }
+ if (operand instanceof UpperCase) {
+ UpperCase operation = (UpperCase)operand;
+ SelectorName replacement =
rewrittenSelectors.get(operation.getSelectorName());
+ if (replacement == null) return operand;
+ return new UpperCase(replaceReferencesToRemovedSource(context, operand,
rewrittenSelectors));
+ }
+ if (operand instanceof NodeName) {
+ NodeName name = (NodeName)operand;
+ SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+ if (replacement == null) return name;
+ return new NodeName(replacement);
+ }
+ if (operand instanceof NodeLocalName) {
+ NodeLocalName name = (NodeLocalName)operand;
+ SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+ if (replacement == null) return name;
+ return new NodeLocalName(replacement);
+ }
+ if (operand instanceof PropertyValue) {
+ PropertyValue value = (PropertyValue)operand;
+ SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
+ if (replacement == null) return operand;
+ return new Length(new PropertyValue(replacement, value.getPropertyName()));
+ }
+ return operand;
+ }
+
+ protected Constraint replaceReferencesToRemovedSource( QueryContext context,
+ Constraint constraint,
+ Map<SelectorName,
SelectorName> rewrittenSelectors ) {
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ Constraint left = replaceReferencesToRemovedSource(context, and.getLeft(),
rewrittenSelectors);
+ Constraint right = replaceReferencesToRemovedSource(context, and.getRight(),
rewrittenSelectors);
+ if (left == and.getLeft() && right == and.getRight()) return and;
+ return new And(left, right);
+ }
+ if (constraint instanceof Or) {
+ Or or = (Or)constraint;
+ Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(),
rewrittenSelectors);
+ Constraint right = replaceReferencesToRemovedSource(context, or.getRight(),
rewrittenSelectors);
+ if (left == or.getLeft() && right == or.getRight()) return or;
+ return new Or(left, right);
+ }
+ if (constraint instanceof Not) {
+ Not not = (Not)constraint;
+ Constraint wrapped = replaceReferencesToRemovedSource(context,
not.getConstraint(), rewrittenSelectors);
+ if (wrapped == not.getConstraint()) return not;
+ return new Not(wrapped);
+ }
+ if (constraint instanceof SameNode) {
+ SameNode sameNode = (SameNode)constraint;
+ SelectorName replacement =
rewrittenSelectors.get(sameNode.getSelectorName());
+ if (replacement == null) return sameNode;
+ return new SameNode(replacement, sameNode.getPath());
+ }
+ if (constraint instanceof ChildNode) {
+ ChildNode childNode = (ChildNode)constraint;
+ SelectorName replacement =
rewrittenSelectors.get(childNode.getSelectorName());
+ if (replacement == null) return childNode;
+ return new ChildNode(replacement, childNode.getParentPath());
+ }
+ if (constraint instanceof DescendantNode) {
+ DescendantNode descendantNode = (DescendantNode)constraint;
+ SelectorName replacement =
rewrittenSelectors.get(descendantNode.getSelectorName());
+ if (replacement == null) return descendantNode;
+ return new DescendantNode(replacement, descendantNode.getAncestorPath());
+ }
+ if (constraint instanceof PropertyExistence) {
+ PropertyExistence existence = (PropertyExistence)constraint;
+ SelectorName replacement =
rewrittenSelectors.get(existence.getSelectorName());
+ if (replacement == null) return existence;
+ return new PropertyExistence(replacement, existence.getPropertyName());
+ }
+ if (constraint instanceof FullTextSearch) {
+ FullTextSearch search = (FullTextSearch)constraint;
+ SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
+ if (replacement == null) return search;
+ return new PropertyExistence(replacement, search.getPropertyName());
+ }
+ if (constraint instanceof Comparison) {
+ Comparison comparison = (Comparison)constraint;
+ DynamicOperand lhs = comparison.getOperand1();
+ StaticOperand rhs = comparison.getOperand2(); // Current only a literal;
therefore, no reference to selector
+ DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs,
rewrittenSelectors);
+ if (lhs == newLhs) return comparison;
+ return new Comparison(newLhs, comparison.getOperator(), rhs);
+ }
+ return constraint;
+ }
+
+ protected JoinCondition replaceReferencesToRemovedSource( QueryContext context,
+ JoinCondition
joinCondition,
+ Map<SelectorName,
SelectorName> rewrittenSelectors ) {
+ if (joinCondition instanceof EquiJoinCondition) {
+ EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
+ SelectorName replacement1 =
rewrittenSelectors.get(condition.getSelector1Name());
+ SelectorName replacement2 =
rewrittenSelectors.get(condition.getSelector2Name());
+ if (replacement1 == condition.getSelector1Name() && replacement2 ==
condition.getSelector2Name()) return condition;
+ return new EquiJoinCondition(replacement1, condition.getProperty1Name(),
replacement2, condition.getProperty2Name());
+ }
+ if (joinCondition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
+ SelectorName replacement1 =
rewrittenSelectors.get(condition.getSelector1Name());
+ SelectorName replacement2 =
rewrittenSelectors.get(condition.getSelector2Name());
+ if (replacement1 == condition.getSelector1Name() && replacement2 ==
condition.getSelector2Name()) return condition;
+ return new SameNodeJoinCondition(replacement1, replacement2,
condition.getSelector2Path());
+ }
+ if (joinCondition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
+ SelectorName childSelector =
rewrittenSelectors.get(condition.getChildSelectorName());
+ SelectorName parentSelector =
rewrittenSelectors.get(condition.getParentSelectorName());
+ if (childSelector == condition.getChildSelectorName() &&
parentSelector == condition.getParentSelectorName()) return condition;
+ return new ChildNodeJoinCondition(parentSelector, childSelector);
+ }
+ if (joinCondition instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition condition =
(DescendantNodeJoinCondition)joinCondition;
+ SelectorName ancestor =
rewrittenSelectors.get(condition.getAncestorSelectorName());
+ SelectorName descendant =
rewrittenSelectors.get(condition.getDescendantSelectorName());
+ if (ancestor == condition.getAncestorSelectorName() && descendant ==
condition.getDescendantSelectorName()) return condition;
+ return new ChildNodeJoinCondition(ancestor, descendant);
+ }
+ return joinCondition;
+ }
+
+ /**
+ * {@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/RewriteIdentityJoins.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoins.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoins.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoins.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,76 @@
+/*
+ * 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.JoinType;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that converts {@link JoinType#RIGHT_OUTER
right outer joins} into
+ * {@link JoinType#LEFT_OUTER left outer joins}.
+ */
+@Immutable
+public class RightOuterToLeftOuterJoins implements OptimizerRule {
+
+ public static final RightOuterToLeftOuterJoins INSTANCE = new
RightOuterToLeftOuterJoins();
+
+ /**
+ * {@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 JOIN nodes ...
+ for (PlanNode joinNode : plan.findAllAtOrBelow(Type.JOIN)) {
+ if (JoinType.RIGHT_OUTER == joinNode.getProperty(Property.JOIN_TYPE,
JoinType.class)) {
+ // Swap the information ...
+ PlanNode left = joinNode.getFirstChild();
+ left.removeFromParent(); // right is now the first child ...
+ left.setParent(joinNode);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.LEFT_OUTER);
+ // The JoinCondition and Constraints don't need to be changed
+ }
+ }
+ 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/RightOuterToLeftOuterJoins.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
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
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,81 @@
+/*
+ * 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.common.collection.Problems;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+
+/**
+ * Optimizer implementation that optimizes a query using a stack of rules. Subclasses can
override the
+ * {@link #populateRuleStack(LinkedList, PlanHints)} method to define the stack of rules
they'd like to use, including the use of
+ * custom rules.
+ */
+@Immutable
+public class RuleBasedOptimizer implements Optimizer {
+
+ private final Logger logger = Logger.getLogger(getClass());
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.optimize.Optimizer#optimize(QueryContext,
PlanNode)
+ */
+ public PlanNode optimize( QueryContext context,
+ PlanNode plan ) {
+ LinkedList<OptimizerRule> rules = new LinkedList<OptimizerRule>();
+ populateRuleStack(rules, context.getHints());
+
+ Problems problems = context.getProblems();
+ while (rules.peek() != null && !problems.hasErrors()) {
+ OptimizerRule nextRule = rules.poll();
+ logger.debug("Running query optimizer rule {0}", nextRule);
+ plan = nextRule.execute(context, plan, rules);
+ }
+
+ return plan;
+ }
+
+ /**
+ * Method that is used to create the initial rule stack. This method can be
overridden by subclasses
+ *
+ * @param ruleStack the stack where the rules should be placed; never null
+ * @param hints the plan hints
+ */
+ protected void populateRuleStack( LinkedList<OptimizerRule> ruleStack,
+ PlanHints hints ) {
+ if (hints.hasJoin) {
+ ruleStack.addFirst(ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM);
+ }
+ if (hints.hasCriteria) {
+ ruleStack.addFirst(PushSelectCriteria.INSTANCE);
+ }
+ ruleStack.addFirst(AddAccessNodes.INSTANCE);
+ ruleStack.addFirst(RightOuterToLeftOuterJoins.INSTANCE);
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/ColumnExpression.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/ColumnExpression.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/ColumnExpression.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,130 @@
+/*
+ * 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.parse;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.text.Position;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
+import org.jboss.dna.graph.query.model.SelectorName;
+
+/**
+ * A representation of a column as expressed in the SQL query. Note that the selector
name may not have been explicitly specified.
+ */
+@Immutable
+class ColumnExpression {
+
+ private final SelectorName selectorName;
+ private final String propertyName;
+ private final String columnName;
+ private final Position position;
+
+ /**
+ * A column with the given name representing the named property on the node
identified by the selector.
+ *
+ * @param selectorName the selector name; may be null if no selector was explicitly
used in the query
+ * @param propertyName the name of the property
+ * @param columnName the name of the column
+ * @param position the position of the column in the query
+ */
+ ColumnExpression( SelectorName selectorName,
+ String propertyName,
+ String columnName,
+ Position position ) {
+ CheckArg.isNotNull(propertyName, "propertyName");
+ CheckArg.isNotNull(columnName, "columnName");
+ CheckArg.isNotNull(position, "position");
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.columnName = columnName;
+ this.position = position;
+ }
+
+ /**
+ * Get the column's position in the query.
+ *
+ * @return the column's position; never null
+ */
+ public Position getPosition() {
+ return position;
+ }
+
+ /**
+ * @return the name of the selector; may be null if no selector was explicitly used
in the query
+ */
+ public final SelectorName getSelectorName() {
+ return selectorName;
+ }
+
+ /**
+ * @return propertyName
+ */
+ public final String getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * @return columnName
+ */
+ public final String getColumnName() {
+ return columnName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof ColumnExpression) {
+ ColumnExpression that = (ColumnExpression)obj;
+ if (!ObjectUtil.isEqualWithNulls(this.selectorName, that.selectorName))
return false;
+ if (!ObjectUtil.isEqualWithNulls(this.propertyName, that.propertyName))
return false;
+ if (!ObjectUtil.isEqualWithNulls(this.columnName, that.columnName)) return
false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (selectorName != null) {
+ sb.append(selectorName.getName());
+ sb.append('.');
+ }
+ sb.append(propertyName);
+ if (columnName != null) {
+ sb.append(" AS ").append(columnName);
+ }
+ return sb.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/ColumnExpression.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/FullTextSearchParser.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/FullTextSearchParser.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/FullTextSearchParser.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,106 @@
+/*
+ * 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.parse;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.TokenStream;
+import org.jboss.dna.common.text.TokenStream.Tokenizer;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.query.model.FullTextSearch.Conjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.Disjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.SimpleTerm;
+import org.jboss.dna.graph.query.model.FullTextSearch.Term;
+
+/**
+ * A {@link QueryParser} implementation that parses a full-text search expression. This
grammar is based on the full-text search
+ * grammar as defined by the JCR 2.0 specification.
+ */
+public class FullTextSearchParser {
+
+ /**
+ * Parse the full-text search criteria given in the supplied string.
+ *
+ * @param fullTextSearchExpression the full-text search expression; may not be null
+ * @return the term representation of the full-text search, or null if there are no
terms
+ * @throws ParsingException if there is an error parsing the supplied string
+ * @throws IllegalArgumentException if the expression is null
+ */
+ public Term parse( String fullTextSearchExpression ) {
+ CheckArg.isNotNull(fullTextSearchExpression,
"fullTextSearchExpression");
+ Tokenizer tokenizer = TokenStream.basicTokenizer(false);
+ TokenStream stream = new TokenStream(fullTextSearchExpression, tokenizer,
false);
+ return parse(stream.start());
+ }
+
+ /**
+ * Parse the full-text search criteria from the supplied token stream. This method is
useful when the full-text search
+ * expression is included in other content.
+ *
+ * @param tokens the token stream containing the full-text search starting on the
next token
+ * @return the term representation of the full-text search, or null if there are no
terms
+ * @throws ParsingException if there is an error parsing the supplied string
+ * @throws IllegalArgumentException if the token stream is null
+ */
+ public Term parse( TokenStream tokens ) {
+ CheckArg.isNotNull(tokens, "tokens");
+ List<Term> terms = new ArrayList<Term>();
+ do {
+ Term term = parseDisjunctedTerms(tokens);
+ if (term == null) break;
+ terms.add(term);
+ } while (tokens.canConsume("OR"));
+ if (terms.isEmpty()) return null;
+ return terms.size() > 1 ? new Disjunction(terms) : terms.iterator().next();
+ }
+
+ protected Term parseDisjunctedTerms( TokenStream tokens ) {
+ List<Term> terms = new ArrayList<Term>();
+ do {
+ Term term = parseTerm(tokens);
+ if (term == null) break;
+ terms.add(term);
+ } while (tokens.hasNext() && !tokens.matches("OR"));
+ if (terms.isEmpty()) return null;
+ return terms.size() > 1 ? new Conjunction(terms) : terms.iterator().next();
+ }
+
+ protected Term parseTerm( TokenStream tokens ) {
+ if (tokens.canConsume('-')) {
+ return new SimpleTerm(removeQuotes(tokens.consume()), true);
+ }
+ return new SimpleTerm(removeQuotes(tokens.consume()), false);
+ }
+
+ /**
+ * Remove any leading and trailing single- or double-quotes from the supplied text.
+ *
+ * @param text the input text; may not be null
+ * @return the text without leading and trailing quotes, or
<code>text</code> if there were no quotes
+ */
+ protected String removeQuotes( String text ) {
+ return text.replaceFirst("^['\"]+",
"").replaceAll("['\"]+$", "");
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/FullTextSearchParser.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,44 @@
+/*
+ * 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.parse;
+
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+
+/**
+ * The basic interface defining a component that is able to parse a string query into a
{@link QueryCommand}.
+ */
+public interface QueryParser {
+
+ /**
+ * Parse the supplied query from a string representation into a {@link
QueryCommand}.
+ *
+ * @param query the query in string form; may not be null
+ * @param context the context in which the query is being parsed
+ * @return the query command
+ */
+ QueryCommand parseQuery( String query,
+ ExecutionContext context );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/QueryParser.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,954 @@
+/*
+ * 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.parse;
+
+import static org.jboss.dna.common.text.TokenStream.ANY_VALUE;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.jboss.dna.common.CommonI18n;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.Position;
+import org.jboss.dna.common.text.TokenStream;
+import org.jboss.dna.common.text.TokenStream.CharacterStream;
+import org.jboss.dna.common.text.TokenStream.Tokenizer;
+import org.jboss.dna.common.text.TokenStream.Tokens;
+import org.jboss.dna.common.xml.XmlCharacters;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.DateTime;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.ValueFactories;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.property.ValueFormatException;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Join;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.Limit;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Order;
+import org.jboss.dna.graph.query.model.Ordering;
+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.QueryCommand;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Selector;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetQuery;
+import org.jboss.dna.graph.query.model.Source;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.FullTextSearch.Term;
+import org.jboss.dna.graph.query.model.SetQuery.Operation;
+
+/**
+ * A {@link QueryParser} implementation that parses a subset of SQL select and set
queries.
+ * <p>
+ * This grammar is based on the SQL grammar as defined by the JCR 2.0 specification, with
some useful additions:
+ * <ul>
+ * <li>(UNION|INTERSECT|EXCEPT) [ALL]</li>
+ * <li>SELECT DISTINCT</li>
+ * <li>LIMIT clauses</li>
+ * </ul>
+ * </p>
+ */
+public class SqlQueryParser implements QueryParser {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.parse.QueryParser#parseQuery(String,
ExecutionContext)
+ */
+ public QueryCommand parseQuery( String query,
+ ExecutionContext context ) {
+ Tokenizer tokenizer = new SqlTokenizer(false);
+ TokenStream tokens = new TokenStream(query, tokenizer, false);
+ tokens.start();
+ QueryCommand command = null;
+ if (tokens.matches("SELECT")) {
+ command = parseQuery(tokens, context);
+ while (tokens.hasNext()) {
+ if (tokens.matchesAnyOf("UNION", "INTERSECT",
"EXCEPT")) {
+ command = parseSetQuery(tokens, command, context);
+ } else {
+ Position pos = tokens.previousPosition();
+ String msg = GraphI18n.unexpectedToken.text(tokens.consume(),
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ }
+ }
+ return command;
+ }
+
+ protected Query parseQuery( TokenStream tokens,
+ ExecutionContext context ) {
+ AtomicBoolean isDistinct = new AtomicBoolean(false);
+ List<ColumnExpression> columnExpressions = parseSelect(tokens, isDistinct,
context);
+ Source source = parseFrom(tokens, context);
+ Constraint constraint = parseWhere(tokens, context, source);
+ // Parse the order by and limit (can be in any order) ...
+ List<Ordering> orderings = parseOrderBy(tokens, context, source);
+ Limit limit = parseLimit(tokens);
+ if (orderings == null) parseOrderBy(tokens, context, source);
+
+ // Convert the column expressions to columns ...
+ List<Column> columns = new
ArrayList<Column>(columnExpressions.size());
+ for (ColumnExpression expression : columnExpressions) {
+ SelectorName selectorName = expression.getSelectorName();
+ Name propertyName = nameFrom(expression.getPropertyName(),
expression.getPosition(), context);
+ if (selectorName == null) {
+ if (source instanceof Selector) {
+ selectorName = ((Selector)source).getName();
+ } else {
+ Position pos = expression.getPosition();
+ String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(expression,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ }
+ columns.add(new Column(selectorName, propertyName,
expression.getColumnName()));
+ }
+ // Now create the query ...
+ return new Query(source, constraint, orderings, columns, limit,
isDistinct.get());
+ }
+
+ protected SetQuery parseSetQuery( TokenStream tokens,
+ QueryCommand leftHandSide,
+ ExecutionContext context ) {
+ Operation operation = null;
+ if (tokens.canConsume("UNION")) {
+ operation = Operation.UNION;
+ } else if (tokens.canConsume("INTERSECT")) {
+ operation = Operation.INTERSECT;
+ } else {
+ tokens.consume("EXCEPT");
+ operation = Operation.EXCEPT;
+ }
+ boolean all = tokens.canConsume("ALL");
+ // Parse the next select
+ QueryCommand rightQuery = parseQuery(tokens, context);
+ return new SetQuery(leftHandSide, operation, rightQuery, all);
+ }
+
+ protected List<ColumnExpression> parseSelect( TokenStream tokens,
+ AtomicBoolean isDistinct,
+ ExecutionContext context ) {
+ tokens.consume("SELECT");
+ if (tokens.canConsume("DISTINCT")) isDistinct.set(true);
+ if (tokens.canConsume('*')) {
+ return Collections.emptyList();
+ }
+ List<ColumnExpression> columns = new ArrayList<ColumnExpression>();
+ do {
+ Position position = tokens.nextPosition();
+ String propertyName = tokens.consume();
+ SelectorName selectorName = null;
+ if (tokens.canConsume('.')) {
+ // We actually read the selector name, so now read the property name ...
+ selectorName = new SelectorName(propertyName);
+ propertyName = tokens.consume();
+ }
+ String alias = propertyName;
+ if (tokens.canConsume("AS")) alias =
removeBracketsAndQuotes(tokens.consume());
+ columns.add(new ColumnExpression(selectorName, propertyName, alias,
position));
+ } while (tokens.canConsume(','));
+ return columns;
+ }
+
+ protected Source parseFrom( TokenStream tokens,
+ ExecutionContext context ) {
+ Source source = null;
+ tokens.consume("FROM");
+ source = parseNamedSelector(tokens);
+ while (tokens.hasNext()) {
+ JoinType joinType = null;
+ if (tokens.canConsume("JOIN") ||
tokens.canConsume("INNER", "JOIN")) {
+ joinType = JoinType.INNER;
+ } else if (tokens.canConsume("OUTER", "JOIN") ||
tokens.canConsume("LEFT", "JOIN")
+ || tokens.canConsume("LEFT", "OUTER",
"JOIN")) {
+ joinType = JoinType.LEFT_OUTER;
+ } else if (tokens.canConsume("RIGHT", "OUTER",
"JOIN")) {
+ joinType = JoinType.RIGHT_OUTER;
+ } else if (tokens.canConsume("FULL", "OUTER",
"JOIN")) {
+ joinType = JoinType.FULL_OUTER;
+ } else if (tokens.canConsume("CROSS", "JOIN")) {
+ joinType = JoinType.CROSS;
+ }
+ if (joinType == null) break;
+ // Read the name of the selector on the right side of the join ...
+ NamedSelector right = parseNamedSelector(tokens);
+ // Read the join condition ...
+ JoinCondition joinCondition = parseJoinCondition(tokens, context);
+ // Create the join ...
+ source = new Join(source, joinType, right, joinCondition);
+ }
+ return source;
+ }
+
+ protected JoinCondition parseJoinCondition( TokenStream tokens,
+ ExecutionContext context ) {
+ tokens.consume("ON");
+ if (tokens.canConsume("ISSAMENODE", "(")) {
+ SelectorName selector1Name = parseSelectorName(tokens);
+ tokens.consume(',');
+ SelectorName selector2Name = parseSelectorName(tokens);
+ if (tokens.canConsume('.')) {
+ Path path = parsePath(tokens, context);
+ tokens.consume(')');
+ return new SameNodeJoinCondition(selector1Name, selector2Name, path);
+ }
+ tokens.consume(')');
+ return new SameNodeJoinCondition(selector1Name, selector2Name);
+ }
+ if (tokens.canConsume("ISCHILDNODE", "(")) {
+ SelectorName child = parseSelectorName(tokens);
+ tokens.consume(',');
+ SelectorName parent = parseSelectorName(tokens);
+ tokens.consume(')');
+ return new ChildNodeJoinCondition(parent, child);
+ }
+ if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
+ SelectorName descendant = parseSelectorName(tokens);
+ tokens.consume(',');
+ SelectorName ancestor = parseSelectorName(tokens);
+ tokens.consume(')');
+ return new DescendantNodeJoinCondition(ancestor, descendant);
+ }
+ SelectorName selector1 = parseSelectorName(tokens);
+ tokens.consume('.');
+ Name property1 = parseName(tokens, context);
+ tokens.consume('=');
+ SelectorName selector2 = parseSelectorName(tokens);
+ tokens.consume('.');
+ Name property2 = parseName(tokens, context);
+ return new EquiJoinCondition(selector1, property1, selector2, property2);
+ }
+
+ protected Constraint parseWhere( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ if (tokens.canConsume("WHERE")) {
+ return parseConstraint(tokens, context, source);
+ }
+ return null;
+ }
+
+ protected Constraint parseConstraint( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ Constraint constraint = null;
+ Position pos = tokens.nextPosition();
+ if (tokens.canConsume("(")) {
+ constraint = parseConstraint(tokens, context, source);
+ tokens.consume(")");
+ } else if (tokens.canConsume("NOT")) {
+ tokens.canConsume('(');
+ constraint = new Not(parseConstraint(tokens, context, source));
+ tokens.canConsume(')');
+ } else if (tokens.canConsume("CONTAINS", "(")) {
+ // Either 'selectorName.propertyName', or 'selectorName.*' or
'propertyName' ...
+ String first = tokens.consume();
+ SelectorName selectorName = null;
+ Name propertyName = null;
+ if (tokens.canConsume(".", "*")) {
+ selectorName = new SelectorName(removeBracketsAndQuotes(first));
+ } else if (tokens.canConsume('.')) {
+ selectorName = new SelectorName(removeBracketsAndQuotes(first));
+ propertyName = parseName(tokens, context);
+ } else {
+ if (!(source instanceof Selector)) {
+ String msg =
GraphI18n.functionIsAmbiguous.text("CONTEXT()", pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ selectorName = ((Selector)source).getName();
+ propertyName = nameFrom(first, pos, context);
+ }
+ tokens.consume(',');
+
+ // Followed by the full text search expression ...
+ String expression = removeBracketsAndQuotes(tokens.consume());
+ Term term = parseFullTextSearchExpression(expression,
tokens.previousPosition());
+ tokens.consume(")");
+ constraint = new FullTextSearch(selectorName, propertyName, expression,
term);
+ } else if (tokens.canConsume("ISSAMENODE", "(")) {
+ SelectorName selectorName = null;
+ if (tokens.matches(ANY_VALUE, ")")) {
+ if (!(source instanceof Selector)) {
+ String msg =
GraphI18n.functionIsAmbiguous.text("ISSAMENODE()", pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ selectorName = ((Selector)source).getName();
+ } else {
+ selectorName = parseSelectorName(tokens);
+ tokens.consume(',');
+ }
+ Path path = parsePath(tokens, context);
+ tokens.consume(')');
+ constraint = new SameNode(selectorName, path);
+ } else if (tokens.canConsume("ISCHILDNODE", "(")) {
+ SelectorName selectorName = null;
+ if (tokens.matches(ANY_VALUE, ")")) {
+ if (!(source instanceof Selector)) {
+ String msg =
GraphI18n.functionIsAmbiguous.text("ISCHILDNODE()", pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ selectorName = ((Selector)source).getName();
+ } else {
+ selectorName = parseSelectorName(tokens);
+ tokens.consume(',');
+ }
+ Path path = parsePath(tokens, context);
+ tokens.consume(')');
+ constraint = new ChildNode(selectorName, path);
+ } else if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
+ SelectorName selectorName = null;
+ if (tokens.matches(ANY_VALUE, ")")) {
+ if (!(source instanceof Selector)) {
+ String msg =
GraphI18n.functionIsAmbiguous.text("ISDESCENDANTNODE()", pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ selectorName = ((Selector)source).getName();
+ } else {
+ selectorName = parseSelectorName(tokens);
+ tokens.consume(',');
+ }
+ Path path = parsePath(tokens, context);
+ tokens.consume(')');
+ constraint = new DescendantNode(selectorName, path);
+ } else {
+ // First try a property existance ...
+ Position pos2 = tokens.nextPosition();
+ constraint = parsePropertyExistance(tokens, context, source);
+ if (constraint == null) {
+ // Try to parse as a dynamic operand ...
+ DynamicOperand left = parseDynamicOperand(tokens, context, source);
+ if (left != null) {
+ if (tokens.matches('(') && left instanceof
PropertyValue) {
+ // This was probably a bad function that we parsed as the start
of a dynamic operation ...
+ String name =
((PropertyValue)left).getPropertyName().getLocalName(); // this may be the function name
+ String msg = GraphI18n.expectingConstraintCondition.text(name,
pos2.getLine(), pos2.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ Operator operator = parseComparisonOperator(tokens);
+ StaticOperand right = parseStaticOperand(tokens, context);
+ constraint = new Comparison(left, operator, right);
+ }
+ // else continue ...
+ }
+ }
+ if (constraint == null) {
+ String msg = GraphI18n.expectingConstraintCondition.text(tokens.consume(),
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ // AND has higher precedence than OR, so we need to evaluate it first ...
+ while (tokens.canConsume("AND")) {
+ constraint = new And(constraint, parseConstraint(tokens, context, source));
+ }
+ while (tokens.canConsume("OR")) {
+ constraint = new Or(constraint, parseConstraint(tokens, context, source));
+ }
+ return constraint;
+ }
+
+ protected Term parseFullTextSearchExpression( String expression,
+ Position position ) {
+ try {
+ return new FullTextSearchParser().parse(expression);
+ } catch (ParsingException e) {
+ // Convert the position in the exception into a position in the query.
+ Position exprPos = e.getPosition();
+ int line = position.getLine() + exprPos.getLine() - 1;
+ int column = exprPos.getLine() == 1 ? exprPos.getColumn() +
position.getColumn() : exprPos.getColumn();
+ Position queryPos = new Position(line, column);
+ throw new ParsingException(queryPos, e.getMessage());
+ }
+ }
+
+ protected Operator parseComparisonOperator( TokenStream tokens ) {
+ if (tokens.canConsume("=")) return Operator.EQUAL_TO;
+ if (tokens.canConsume("LIKE")) return Operator.LIKE;
+ if (tokens.canConsume("!", "=")) return
Operator.NOT_EQUAL_TO;
+ if (tokens.canConsume("<", ">")) return
Operator.NOT_EQUAL_TO;
+ if (tokens.canConsume("<", "=")) return
Operator.LESS_THAN_OR_EQUAL_TO;
+ if (tokens.canConsume(">", "=")) return
Operator.GREATER_THAN_OR_EQUAL_TO;
+ if (tokens.canConsume("<")) return Operator.LESS_THAN;
+ if (tokens.canConsume(">")) return Operator.GREATER_THAN;
+ Position pos = tokens.nextPosition();
+ String msg = GraphI18n.expectingComparisonOperator.text(tokens.consume(),
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+
+ protected List<Ordering> parseOrderBy( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ if (tokens.canConsume("ORDER", "BY")) {
+ List<Ordering> orderings = new ArrayList<Ordering>();
+ do {
+ orderings.add(parseOrdering(tokens, context, source));
+ } while (tokens.canConsume(','));
+ return orderings;
+ }
+ return null;
+ }
+
+ protected Ordering parseOrdering( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ DynamicOperand operand = parseDynamicOperand(tokens, context, source);
+ Order order = Order.ASCENDING;
+ if (tokens.canConsume("DESC")) order = Order.DESCENDING;
+ if (tokens.canConsume("ASC")) order = Order.ASCENDING;
+ return new Ordering(operand, order);
+ }
+
+ protected Constraint parsePropertyExistance( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ if (tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS",
"NOT", "NULL")
+ || tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS",
"NULL") || tokens.matches(ANY_VALUE, "IS", "NOT",
"NULL")
+ || tokens.matches(ANY_VALUE, "IS", "NULL")) {
+ Position pos = tokens.nextPosition();
+ String firstWord = tokens.consume();
+ SelectorName selectorName = null;
+ Name propertyName = null;
+ if (tokens.canConsume('.')) {
+ // We actually read the selector name, so now read the property name ...
+ selectorName = new SelectorName(firstWord);
+ propertyName = parseName(tokens, context);
+ } else {
+ // Otherwise the source should be a single named selector
+ if (!(source instanceof Selector)) {
+ String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ selectorName = ((Selector)source).getName();
+ propertyName = nameFrom(firstWord, pos, context);
+ }
+ if (tokens.canConsume("IS", "NOT", "NULL")) {
+ return new PropertyExistence(selectorName, propertyName);
+ }
+ tokens.consume("IS", "NULL");
+ return new Not(new PropertyExistence(selectorName, propertyName));
+ }
+ return null;
+ }
+
+ protected StaticOperand parseStaticOperand( TokenStream tokens,
+ ExecutionContext context ) {
+ if (tokens.canConsume('$')) {
+ // The variable name must conform to a valid prefix, which is defined as a
valid NCName ...
+ String value = tokens.consume();
+ if (!XmlCharacters.isValidNcName(value)) {
+ Position pos = tokens.previousPosition();
+ String msg = GraphI18n.bindVariableMustConformToNcName.text(value,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ return new BindVariableName(value);
+ }
+ return parseLiteral(tokens, context);
+ }
+
+ protected Literal parseLiteral( TokenStream tokens,
+ ExecutionContext context ) {
+ if (tokens.canConsume("CAST", "(")) {
+ // Get the value that is to be cast ...
+ Position pos = tokens.nextPosition();
+ String value = parseLiteralValue(tokens, context);
+ // Figure out the type we're supposed to cast to ...
+ ValueFactories factories = context.getValueFactories();
+ ValueFactory<?> factory = factories.getStringFactory();
+ tokens.consume("AS");
+ if (tokens.canConsume("STRING")) factory =
factories.getStringFactory();
+ else if (tokens.canConsume("BINARY")) factory =
factories.getBinaryFactory();
+ else if (tokens.canConsume("DATE")) factory =
factories.getDateFactory();
+ else if (tokens.canConsume("LONG")) factory =
factories.getLongFactory();
+ else if (tokens.canConsume("DOUBLE")) factory =
factories.getDoubleFactory();
+ else if (tokens.canConsume("DECIMAL")) factory =
factories.getDecimalFactory();
+ else if (tokens.canConsume("BOOLEAN")) factory =
factories.getBooleanFactory();
+ else if (tokens.canConsume("NAME")) factory =
factories.getNameFactory();
+ else if (tokens.canConsume("PATH")) factory =
factories.getPathFactory();
+ else if (tokens.canConsume("REFERENCE")) factory =
factories.getReferenceFactory();
+ else if (tokens.canConsume("WEAKREFERENCE")) factory =
factories.getPathFactory();
+ else if (tokens.canConsume("URI")) factory =
factories.getUriFactory();
+ else {
+ Position typePos = tokens.nextPosition();
+ String msg = GraphI18n.invalidPropertyType.text(tokens.consume(),
typePos.getLine(), typePos.getColumn());
+ throw new ParsingException(typePos, msg);
+ }
+ // Convert the supplied value to the desired value ...
+ tokens.consume(')');
+ try {
+ Object literal = factory.create(value);
+ if (literal instanceof DateTime) {
+ // Convert the timestamp to UTC, since that's how everything
should be stored ...
+ literal = ((DateTime)literal).toUtcTimeZone();
+ }
+ return new Literal(literal);
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.valueCannotBeCastToSpecifiedType.text(value,
+
pos.getLine(),
+
pos.getColumn(),
+
factory.getPropertyType().name(),
+
e.getMessage());
+ throw new ParsingException(pos, msg);
+ }
+ }
+ // Just create a literal out of the supplied value ...
+ return new Literal(parseLiteralValue(tokens, context));
+ }
+
+ protected String parseLiteralValue( TokenStream tokens,
+ ExecutionContext context ) {
+ if (tokens.matches(SqlTokenizer.QUOTED_STRING)) {
+ return removeBracketsAndQuotes(tokens.consume());
+ }
+ ValueFactory<String> stringFactory =
context.getValueFactories().getStringFactory();
+ if (tokens.canConsume("TRUE")) return
stringFactory.create(Boolean.TRUE);
+ if (tokens.canConsume("FALSE")) return
stringFactory.create(Boolean.FALSE);
+
+ // Otherwise it is an unquoted literal value ...
+ Position pos = tokens.nextPosition();
+ String sign = "";
+ if (tokens.canConsume('-')) sign = "-";
+ else if (tokens.canConsume('+')) sign = "";
+
+ // Try to parse this value as a number ...
+ String integral = tokens.consume();
+ String decimal = null;
+ if (tokens.canConsume('.')) {
+ decimal = tokens.consume();
+ String value = sign + integral + "." + decimal;
+ if (decimal.endsWith("e") && (tokens.matches('+')
|| tokens.matches('-'))) {
+ // There's more to the number ...
+ value = value + tokens.consume() + tokens.consume(); // +/-EXP
+ }
+ try {
+ // Convert to a double and then back to a string to get canonical form
...
+ return
stringFactory.create(context.getValueFactories().getDoubleFactory().create(value));
+ } catch (ValueFormatException e) {
+ String msg =
GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ }
+ if (tokens.canConsume('-')) {
+ // Looks like a date (see Section 3.6.4.3 of the JCR 2.0 specification) ...
+ // sYYYY-MM-DDThh:mm:ss.sssTZD
+ String year = integral;
+ String month = tokens.consume();
+ tokens.consume('-');
+ String dateAndHour = tokens.consume();
+ tokens.consume(':');
+ String minutes = tokens.consume();
+ tokens.consume(':');
+ String seconds = tokens.consume();
+ tokens.consume('.');
+ String subSeconds = tokens.consume(); // should contain 'T' separator
and possibly the TZ name and (if no +/-)
+ // hours
+ String tzSign = "+";
+ String tzHours = "00";
+ String tzMinutes = "00";
+ String tzDelim = ":";
+ if (tokens.canConsume('+')) {
+ // the fractionalSeconds did NOT contain the tzHours ...
+ tzHours = tokens.consume();
+ if (tokens.canConsume(':')) tzMinutes = tokens.consume();
+ } else if (tokens.canConsume('-')) {
+ // the fractionalSeconds did NOT contain the tzHours ...
+ tzSign = "-";
+ tzHours = tokens.consume();
+ if (tokens.canConsume(':')) tzMinutes = tokens.consume();
+ } else if (tokens.canConsume(':')) {
+ // fractionalSeconds DID contain the TZ hours (without + or -)
+ tzHours = tzSign = "";
+ if (tokens.canConsume(':')) tzMinutes = tokens.consume();
+ } else if (subSeconds.endsWith("Z")) {
+ tzSign = tzMinutes = tzDelim = tzHours = "";
+ } else if (subSeconds.endsWith("UTC")) {
+ subSeconds = subSeconds.length() > 3 ? subSeconds.substring(0,
subSeconds.length() - 3) : subSeconds;
+ }
+ String value = sign + year + "-" + month + "-" +
dateAndHour + ":" + minutes + ":" + seconds + "." +
subSeconds
+ + tzSign + tzHours + tzDelim + tzMinutes;
+ try {
+ // Convert to a date and then back to a string to get canonical form ...
+ DateTime dateTime =
context.getValueFactories().getDateFactory().create(value);
+ dateTime = dateTime.toUtcTimeZone();
+ return stringFactory.create(dateTime);
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.expectingLiteralAndUnableToParseAsDate.text(value,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ }
+ // try to parse an a long ...
+ String value = sign + integral;
+ try {
+ // Convert to a long and then back to a string to get canonical form ...
+ return
stringFactory.create(context.getValueFactories().getLongFactory().create(value));
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value,
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+
+ }
+
+ protected DynamicOperand parseDynamicOperand( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ DynamicOperand result = null;
+ Position pos = tokens.nextPosition();
+ if (tokens.canConsume("LENGTH", "(")) {
+ result = new Length(parsePropertyValue(tokens, context, source));
+ tokens.consume(")");
+ } else if (tokens.canConsume("LOWER", "(")) {
+ result = new LowerCase(parseDynamicOperand(tokens, context, source));
+ tokens.consume(")");
+ } else if (tokens.canConsume("UPPER", "(")) {
+ result = new UpperCase(parseDynamicOperand(tokens, context, source));
+ tokens.consume(")");
+ } else if (tokens.canConsume("NAME", "(")) {
+ if (tokens.canConsume(")")) {
+ if (source instanceof Selector) {
+ return new NodeName(((Selector)source).getName());
+ }
+ String msg = GraphI18n.functionIsAmbiguous.text("NAME()",
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ result = new NodeName(parseSelectorName(tokens));
+ tokens.consume(")");
+ } else if (tokens.canConsume("LOCALNAME", "(")) {
+ if (tokens.canConsume(")")) {
+ if (source instanceof Selector) {
+ return new NodeLocalName(((Selector)source).getName());
+ }
+ String msg = GraphI18n.functionIsAmbiguous.text("LOCALNAME()",
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ result = new NodeLocalName(parseSelectorName(tokens));
+ tokens.consume(")");
+ } else if (tokens.canConsume("SCORE", "(")) {
+ if (tokens.canConsume(")")) {
+ if (source instanceof Selector) {
+ return new FullTextSearchScore(((Selector)source).getName());
+ }
+ String msg = GraphI18n.functionIsAmbiguous.text("SCORE()",
pos.getLine(), pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ result = new FullTextSearchScore(parseSelectorName(tokens));
+ tokens.consume(")");
+ } else {
+ result = parsePropertyValue(tokens, context, source);
+ }
+ return result;
+ }
+
+ protected PropertyValue parsePropertyValue( TokenStream tokens,
+ ExecutionContext context,
+ Source source ) {
+ Position pos = tokens.nextPosition();
+ String firstWord = removeBracketsAndQuotes(tokens.consume());
+ SelectorName selectorName = null;
+ if (tokens.canConsume('.')) {
+ // We actually read the selector name, so now read the property name ...
+ selectorName = new SelectorName(firstWord);
+ Name propertyName = parseName(tokens, context);
+ return new PropertyValue(selectorName, propertyName);
+ }
+ // Otherwise the source should be a single named selector
+ if (source instanceof Selector) {
+ selectorName = ((Selector)source).getName();
+ return new PropertyValue(selectorName, nameFrom(firstWord, pos, context));
+ }
+ String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord, pos.getLine(),
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+
+ protected Limit parseLimit( TokenStream tokens ) {
+ if (tokens.canConsume("LIMIT")) {
+ int first = tokens.consumeInteger();
+ if (tokens.canConsume(',')) {
+ // This is of the 'from,to' style ...
+ int to = tokens.consumeInteger();
+ int offset = to - first;
+ if (offset < 0) {
+ Position pos = tokens.previousPosition();
+ String msg =
GraphI18n.secondValueInLimitRangeCannotBeLessThanFirst.text(first,
+
to,
+
pos.getLine(),
+
pos.getColumn());
+ throw new ParsingException(pos, msg);
+ }
+ return new Limit(offset, first);
+ }
+ if (tokens.canConsume("OFFSET")) {
+ int offset = tokens.consumeInteger();
+ return new Limit(first, offset);
+ }
+ // No offset
+ return new Limit(first, 0);
+ }
+ return null;
+ }
+
+ /**
+ * Remove any leading and trailing single-quotes, double-quotes, or square brackets
from the supplied text.
+ *
+ * @param text the input text; may not be null
+ * @return the text without leading and trailing brackets and quotes, or
<code>text</code> if there were no square brackets or
+ * quotes
+ */
+ protected String removeBracketsAndQuotes( String text ) {
+ return text.replaceFirst("^['\"\\[]+",
"").replaceAll("['\"\\]]+$", "");
+ }
+
+ protected NamedSelector parseNamedSelector( TokenStream tokens ) {
+ SelectorName name = parseSelectorName(tokens);
+ SelectorName alias = null;
+ if (tokens.canConsume("AS")) alias = parseSelectorName(tokens);
+ return new NamedSelector(name, alias);
+ }
+
+ protected SelectorName parseSelectorName( TokenStream tokens ) {
+ return new SelectorName(removeBracketsAndQuotes(tokens.consume()));
+ }
+
+ protected Path parsePath( TokenStream tokens,
+ ExecutionContext context ) {
+ return pathFrom(removeBracketsAndQuotes(tokens.consume()),
tokens.previousPosition(), context);
+ }
+
+ protected Name parseName( TokenStream tokens,
+ ExecutionContext context ) {
+ return nameFrom(removeBracketsAndQuotes(tokens.consume()),
tokens.previousPosition(), context);
+ }
+
+ protected Path pathFrom( String name,
+ Position position,
+ ExecutionContext context ) {
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+ try {
+ return pathFactory.create(name);
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.expectingValidPathAtLineAndColumn.text(name,
position.getLine(), position.getColumn());
+ throw new ParsingException(position, msg);
+ }
+ }
+
+ protected Name nameFrom( String name,
+ Position position,
+ ExecutionContext context ) {
+ NameFactory nameFactory = context.getValueFactories().getNameFactory();
+ try {
+ return nameFactory.create(removeBracketsAndQuotes(name));
+ } catch (ValueFormatException e) {
+ String msg = GraphI18n.expectingValidNameAtLineAndColumn.text(name,
position.getLine(), position.getColumn());
+ throw new ParsingException(position, msg);
+ }
+ }
+
+ /**
+ * A {@link TokenStream.Tokenizer} implementation that parses words, quoted phrases,
comments, and symbols. Words are
+ * delimited by whitespace and consist only of alpha-number characters plus the
underscore character. Quoted phrases are
+ * delimited by single-quote and double-quote characters (which may be escaped within
the quote). Comments are the characters
+ * starting with '/*' and ending with '*/', or starting with
'--' and ending with the next line terminator (or the end of
+ * the content).
+ */
+ public static class SqlTokenizer implements TokenStream.Tokenizer {
+ /**
+ * The token type for tokens that represent an unquoted string containing a
character sequence made up of non-whitespace
+ * and non-symbol characters.
+ */
+ public static final int WORD = 1;
+ /**
+ * The token type for tokens that consist of an individual "symbol"
character. The set of characters includes:
+ * <code>[]<>=-+(),</code>
+ */
+ public static final int SYMBOL = 2;
+ /**
+ * The token type for tokens that consist of other characters.
+ */
+ public static final int OTHER = 3;
+ /**
+ * The token type for tokens that consist of all the characters within
single-quotes, double-quotes, or square brackets.
+ */
+ public static final int QUOTED_STRING = 4;
+ /**
+ * The token type for tokens that consist of all the characters between
"/*" and "*/" or between "--" and the next
+ * line terminator (e.g., '\n', '\r' or "\r\n")
+ */
+ public static final int COMMENT = 6;
+
+ private final boolean useComments;
+
+ public SqlTokenizer( boolean useComments ) {
+ this.useComments = useComments;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.common.text.TokenStream.Tokenizer#tokenize(CharacterStream,
Tokens)
+ */
+ public void tokenize( CharacterStream input,
+ Tokens tokens ) throws ParsingException {
+ while (input.hasNext()) {
+ char c = input.next();
+ switch (c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ // Just skip these whitespace characters ...
+ break;
+ case '(':
+ case ')':
+ case '{':
+ case '}':
+ case '*':
+ case '.':
+ case ',':
+ case ';':
+ case '+':
+ case '%':
+ case '?':
+ case '$':
+ case ']':
+ case '!':
+ case '<':
+ case '>':
+ case '|':
+ case '=':
+ case ':':
+ tokens.addToken(input.position(), input.index(), input.index() +
1, SYMBOL);
+ break;
+ case '\'':
+ case '[':
+ case '\"':
+ int startIndex = input.index();
+ char closingChar = c == '[' ? ']' : c;
+ Position pos = input.position();
+ boolean foundClosingQuote = false;
+ while (input.hasNext()) {
+ c = input.next();
+ if (c == '\\' && input.isNext(closingChar))
{
+ c = input.next(); // consume the closingChar since it is
escaped
+ } else if (c == closingChar) {
+ foundClosingQuote = true;
+ break;
+ }
+ }
+ if (!foundClosingQuote) {
+ String msg =
CommonI18n.noMatchingDoubleQuoteFound.text(pos.getLine(), pos.getColumn());
+ if (closingChar == '\'') {
+ msg =
CommonI18n.noMatchingSingleQuoteFound.text(pos.getLine(), pos.getColumn());
+ } else if (closingChar == ']') {
+ msg =
GraphI18n.noMatchingBracketFound.text(pos.getLine(), pos.getColumn());
+ }
+ throw new ParsingException(pos, msg);
+ }
+ int endIndex = input.index() + 1; // beyond last character read
+ tokens.addToken(pos, startIndex, endIndex, QUOTED_STRING);
+ break;
+ case '-':
+ startIndex = input.index();
+ pos = input.position();
+ if (input.isNext('-')) {
+ // End-of-line comment ...
+ boolean foundLineTerminator = false;
+ while (input.hasNext()) {
+ c = input.next();
+ if (c == '\n' || c == '\r') {
+ foundLineTerminator = true;
+ break;
+ }
+ }
+ endIndex = input.index(); // the token won't include the
'\n' or '\r' character(s)
+ if (!foundLineTerminator) ++endIndex; // must point beyond
last char
+ if (c == '\r' && input.isNext('\n'))
input.next();
+ if (useComments) {
+ tokens.addToken(pos, startIndex, endIndex, COMMENT);
+ }
+ } else {
+ tokens.addToken(input.position(), input.index(),
input.index() + 1, SYMBOL);
+ break;
+ }
+ break;
+ case '/':
+ startIndex = input.index();
+ pos = input.position();
+ if (input.isNext('*')) {
+ // Multi-line comment ...
+ while (input.hasNext() && !input.isNext('*',
'/')) {
+ c = input.next();
+ }
+ if (input.hasNext()) input.next(); // consume the
'*'
+ if (input.hasNext()) input.next(); // consume the
'/'
+ if (useComments) {
+ endIndex = input.index() + 1; // the token will include
the quote characters
+ tokens.addToken(pos, startIndex, endIndex, COMMENT);
+ }
+ } else {
+ tokens.addToken(input.position(), input.index(),
input.index() + 1, SYMBOL);
+ break;
+ }
+ break;
+ default:
+ startIndex = input.index();
+ pos = input.position();
+ // Read as long as there is a valid XML character ...
+ int tokenType = (Character.isLetterOrDigit(c) || c ==
'_') ? WORD : OTHER;
+ while (input.isNextLetterOrDigit() || input.isNext('_'))
{
+ c = input.next();
+ }
+ endIndex = input.index() + 1; // beyond last character that was
included
+ tokens.addToken(pos, startIndex, endIndex, tokenType);
+ }
+ }
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/SqlQueryParser.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,422 @@
+/*
+ * 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.plan;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.AllNodes;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.Join;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Limit;
+import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.Query;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.Selector;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.SetQuery;
+import org.jboss.dna.graph.query.model.Source;
+import org.jboss.dna.graph.query.model.Visitors;
+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;
+
+/**
+ * The planner that produces a canonical query plan given a {@link QueryCommand query
command}.
+ * <p>
+ * A canonical plan always has the same structure:
+ *
+ * <pre>
+ * LIMIT if row limit or offset are used
+ * |
+ * SORTING if 'ORDER BY' is used
+ * |
+ * DUP_REMOVE if 'SELECT DISTINCT' is used
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together (see {@link #separateAndConstraints(Constraint, List)})
+ * SELECTn
+ * |
+ * SOURCE or JOIN A single SOURCE or JOIN node, depending upon the query
+ * / \
+ * / \
+ * SOJ SOJ A SOURCE or JOIN node for the left and right side of the JOIN
+ * </pre>
+ * <p>
+ * There leaves of the tree are always SOURCE nodes, so <i>conceptually</i>
data always flows through this plan from the bottom
+ * SOURCE nodes, is adjusted/filtered as it trickles up through the plan, and is then
ready to be used by the caller as it emerges
+ * from the top node of the plan.
+ * </p>
+ * <p>
+ * This canonical plan, however, is optimized and rearranged so that it performs faster.
+ * </p>
+ */
+public class CanonicalPlanner implements Planner {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.plan.Planner#createPlan(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.model.QueryCommand)
+ */
+ public PlanNode createPlan( QueryContext context,
+ QueryCommand query ) {
+ PlanNode plan = null;
+ if (query instanceof Query) {
+ plan = createCanonicalPlan(context, (Query)query);
+ } else {
+ plan = createCanonicalPlan(context, (SetQuery)query);
+ }
+ return plan;
+ }
+
+ /**
+ * Create a canonical query plan for the given query.
+ *
+ * @param context the context in which the query is being planned
+ * @param query the query to be planned
+ * @return the root node of the plan tree representing the canonical plan
+ */
+ protected PlanNode createCanonicalPlan( QueryContext context,
+ Query query ) {
+ PlanNode plan = null;
+
+ // Process the source of the query ...
+ Map<SelectorName, Table> usedSources = new HashMap<SelectorName,
Table>();
+ plan = createPlanNode(context, query.getSource(), usedSources);
+
+ // Attach criteria (on top) ...
+ plan = attachCriteria(context, plan, query.getConstraint());
+
+ // Attach the project ...
+ plan = attachProject(context, plan, query.getColumns(), usedSources);
+
+ // Attach duplicate removal ...
+ if (query.isDistinct()) {
+ plan = attachDuplicateRemoval(context, plan);
+ }
+
+ // Process the orderings and limits ...
+ plan = attachSorting(context, plan, query.getOrderings());
+ plan = attachLimits(context, plan, query.getLimits());
+ return plan;
+ }
+
+ /**
+ * Create a canonical query plan for the given set query.
+ *
+ * @param context the context in which the query is being planned
+ * @param query the set query to be planned
+ * @return the root node of the plan tree representing the canonical plan
+ */
+ protected PlanNode createCanonicalPlan( QueryContext context,
+ SetQuery query ) {
+ // Process the left and right parts of the query ...
+ PlanNode left = createPlan(context, query.getLeft());
+ PlanNode right = createPlan(context, query.getRight());
+
+ // Wrap in a set operation node ...
+ PlanNode plan = new PlanNode(Type.SET_OPERATION);
+ plan.addChildren(left, right);
+ plan.setProperty(Property.SET_OPERATION, query.getOperation());
+ plan.setProperty(Property.SET_USE_ALL, query.isAll());
+
+ // Process the orderings and limits ...
+ plan = attachSorting(context, plan, query.getOrderings());
+ plan = attachLimits(context, plan, query.getLimits());
+ return plan;
+ }
+
+ /**
+ * Create a JOIN or SOURCE node that contain the source information.
+ *
+ * @param context the execution context
+ * @param source the source to be processed; may not be null
+ * @param usedSelectors the map of {@link SelectorName}s (aliases or names) used in
the query.
+ * @return the new plan; never null
+ */
+ protected PlanNode createPlanNode( QueryContext context,
+ Source source,
+ Map<SelectorName, Table> usedSelectors ) {
+ if (source instanceof Selector) {
+ // No join required ...
+ assert source instanceof AllNodes || source instanceof NamedSelector;
+ Selector selector = (Selector)source;
+ PlanNode node = new PlanNode(Type.SOURCE);
+ if (selector.hasAlias()) {
+ node.addSelector(selector.getAlias());
+ node.setProperty(Property.SOURCE_ALIAS, selector.getAlias());
+ node.setProperty(Property.SOURCE_NAME, selector.getName());
+ } else {
+ node.addSelector(selector.getName());
+ node.setProperty(Property.SOURCE_NAME, selector.getName());
+ }
+ // Validate the source name and set the available columns ...
+ Table table = context.getSchemata().getTable(selector.getName());
+ if (table == null) {
+ context.getProblems().addError(GraphI18n.tableDoesNotExist,
+
selector.getName().getString(context.getExecutionContext()));
+ } else {
+ if (usedSelectors.put(selector.getAliasOrName(), table) != null) {
+ // There was already a table with this alias or name ...
+ }
+ node.setProperty(Property.SOURCE_COLUMNS, table.getColumns());
+ }
+ return node;
+ }
+ if (source instanceof Join) {
+ Join join = (Join)source;
+ // Set up new join node corresponding to this join predicate
+ PlanNode node = new PlanNode(Type.JOIN);
+ node.setProperty(Property.JOIN_TYPE, join.getType());
+ node.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+ node.setProperty(Property.JOIN_CONDITION, join.getJoinCondition());
+
+ context.getHints().hasJoin = true;
+ if (join.getType() == JoinType.LEFT_OUTER) {
+ context.getHints().hasOptionalJoin = true;
+ }
+
+ // Handle each child
+ Source[] clauses = new Source[] {join.getLeft(), join.getRight()};
+ for (int i = 0; i < 2; i++) {
+ node.addLastChild(createPlanNode(context, clauses[i], usedSelectors));
+
+ // Add selectors to the joinNode
+ for (PlanNode child : node.getChildren()) {
+ node.addSelectors(child.getSelectors());
+ }
+ }
+ return node;
+ }
+ // should not get here; if we do, somebody added a new type of source
+ assert false;
+ return null;
+ }
+
+ /**
+ * Attach all criteria above the join nodes. The optimizer will push these criteria
down to the appropriate source.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan, which joins all source groups
+ * @param constraint the criteria or constraint from the query
+ * @return the updated plan, or the existing plan if there were no constraints; never
null
+ */
+ protected PlanNode attachCriteria( final QueryContext context,
+ PlanNode plan,
+ Constraint constraint ) {
+ if (constraint == null) return plan;
+ context.getHints().hasCriteria = true;
+
+ // Extract the list of Constraint objects that all must be satisfied ...
+ LinkedList<Constraint> andableConstraints = new
LinkedList<Constraint>();
+ separateAndConstraints(constraint, andableConstraints);
+ assert !andableConstraints.isEmpty();
+
+ // For each of these constraints, create a criteria (SELECT) node above the
supplied (JOIN or SOURCE) node.
+ // Do this in reverse order so that the top-most SELECT node corresponds to the
first constraint.
+ while (!andableConstraints.isEmpty()) {
+ Constraint criteria = andableConstraints.removeLast();
+ // Create the select node ...
+ PlanNode criteriaNode = new PlanNode(Type.SELECT);
+ criteriaNode.setProperty(Property.SELECT_CRITERIA, criteria);
+
+ // Add selectors to the criteria node ...
+ criteriaNode.addSelectors(Visitors.getSelectorsReferencedBy(criteria));
+
+ // Is a full-text search of some kind included ...
+ Visitors.visitAll(criteria, new Visitors.AbstractVisitor() {
+ @Override
+ public void visit( FullTextSearch obj ) {
+ context.getHints().hasFullTextSearch = true;
+ }
+ });
+
+ plan = criteriaNode;
+ }
+ return plan;
+ }
+
+ /**
+ * Walk the supplied constraint to extract a list of the constraints that can be
AND-ed together. For example, given the
+ * constraint tree ((C1 AND C2) AND (C3 OR C4)), this method would result in a list
of three separate criteria: [C1,C2,(C3 OR
+ * C4)]. The resulting <code>andConstraints</code> list will contain
Constraint objects that all must be true.
+ *
+ * @param constraint the input constraint
+ * @param andableConstraints the collection into which all non-{@link And AND}
constraints should be placed
+ */
+ protected void separateAndConstraints( Constraint constraint,
+ List<Constraint> andableConstraints ) {
+ if (constraint == null) return;
+ assert andableConstraints != null;
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ separateAndConstraints(and.getLeft(), andableConstraints);
+ separateAndConstraints(and.getRight(), andableConstraints);
+ } else {
+ andableConstraints.add(constraint);
+ }
+ }
+
+ /**
+ * Attach SORT node at top of tree. The SORT may be pushed down to a source (or
sources) if possible by the optimizer.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan
+ * @param orderings list of orderings from the query
+ * @return the updated plan, or the existing plan if there were no orderings; never
null
+ */
+ protected PlanNode attachSorting( QueryContext context,
+ PlanNode plan,
+ List<Ordering> orderings ) {
+ if (orderings.isEmpty()) return plan;
+ PlanNode sortNode = new PlanNode(Type.SORT);
+
+ context.getHints().hasSort = true;
+ sortNode.setProperty(Property.SORT_ORDER_BY, orderings);
+
+ sortNode.addLastChild(plan);
+ return sortNode;
+ }
+
+ /**
+ * Attach a LIMIT node at the top of the plan tree.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan
+ * @param limit the limit definition; may be null
+ * @return the updated plan, or the existing plan if there were no limits
+ */
+ protected PlanNode attachLimits( QueryContext context,
+ PlanNode plan,
+ Limit limit ) {
+ if (limit.isUnlimited()) return plan;
+ context.getHints().hasLimit = true;
+ PlanNode limitNode = new PlanNode(Type.LIMIT);
+
+ boolean attach = false;
+ if (limit.getOffset() != 0) {
+ limitNode.setProperty(Property.LIMIT_COUNT, limit.getOffset());
+ attach = true;
+ }
+ if (!limit.isUnlimited()) {
+ limitNode.setProperty(Property.LIMIT_OFFSET, limit.getRowLimit());
+ attach = true;
+ }
+ if (attach) {
+ limitNode.addLastChild(plan);
+ plan = limitNode;
+ }
+ return plan;
+ }
+
+ /**
+ * Attach a PROJECT node at the top of the plan tree.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan
+ * @param columns the columns being projected; may be null
+ * @param selectors the selectors keyed by their alias or name
+ * @return the updated plan
+ */
+ protected PlanNode attachProject( QueryContext context,
+ PlanNode plan,
+ List<Column> columns,
+ Map<SelectorName, Table> selectors ) {
+ if (columns == null) columns = Collections.emptyList();
+ PlanNode projectNode = new PlanNode(Type.PROJECT);
+
+ if (columns.isEmpty()) {
+ columns = new LinkedList<Column>();
+ // SELECT *, so find all of the columns that are available from all the
sources ...
+ NameFactory nameFactory =
context.getExecutionContext().getValueFactories().getNameFactory();
+ for (Table table : selectors.values()) {
+ // Add the selector that is being used ...
+ projectNode.addSelector(table.getName());
+ // Compute the columns from this selector ...
+ for (Schemata.Column column : table.getColumns()) {
+ String columnName = column.getName();
+ Name propertyName = nameFactory.create(columnName);
+ columns.add(new Column(table.getName(), propertyName, columnName));
+ }
+ }
+ } else {
+ // Add the selector used by each column ...
+ for (Column column : columns) {
+ SelectorName tableName = column.getSelectorName();
+ // Add the selector that is being used ...
+ projectNode.addSelector(tableName);
+
+ // Verify that each column is available in the appropriate source ...
+ Table table = selectors.get(tableName);
+ if (table == null) {
+ context.getProblems().addError(GraphI18n.tableDoesNotExist,
+
tableName.getString(context.getExecutionContext()));
+ } else {
+ // Make sure that the column is in the table ...
+ if (table.getColumn(column.getColumnName()) == null) {
+
context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable,
+ column.getColumnName(),
+
tableName.getString(context.getExecutionContext()));
+ }
+ }
+ }
+ }
+ projectNode.setProperty(Property.PROJECT_COLUMNS, columns);
+ projectNode.addLastChild(plan);
+ return projectNode;
+ }
+
+ /**
+ * Attach DUP_REMOVE node at top of tree. The DUP_REMOVE may be pushed down to a
source (or sources) if possible by the
+ * optimizer.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan
+ * @return the updated plan
+ */
+ protected PlanNode attachDuplicateRemoval( QueryContext context,
+ PlanNode plan ) {
+ PlanNode dupNode = new PlanNode(Type.DUP_REMOVE);
+ plan.setParent(dupNode);
+ return dupNode;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/JoinAlgorithm.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/JoinAlgorithm.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/JoinAlgorithm.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,33 @@
+/*
+ * 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.plan;
+
+/**
+ * The type of join algorithms.
+ */
+public enum JoinAlgorithm {
+ // PARTITIONED_SORT,
+ NESTED_LOOP,
+ MERGE
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/JoinAlgorithm.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,74 @@
+/*
+ * 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.plan;
+
+import net.jcip.annotations.NotThreadSafe;
+
+@NotThreadSafe
+public final class PlanHints {
+
+ // This flag indicates that the plan has a criteria somewhere
+ public boolean hasCriteria = false;
+
+ // This flag indicates that the plan has a join somewhere
+ public boolean hasJoin = false;
+
+ // This flag indicates that the plan has a sort somewhere
+ public boolean hasSort = false;
+
+ // List of groups to make dependent
+ // public List makeDepGroups = null;
+
+ // flag indicates that the plan has a union somewhere
+ public boolean hasSetQuery = false;
+
+ // flag indicating that the plan has a grouping node somewhere
+ // public boolean hasAggregates = false;
+
+ // List of groups that should not be dependent
+ // public List makeNotDepGroups = null;
+
+ public boolean hasLimit = false;
+
+ public boolean hasOptionalJoin = false;
+
+ public boolean hasFullTextSearch = false;
+
+ public PlanHints() {
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("PlanHints {");
+ sb.append(" hasCriteria=").append(hasCriteria);
+ sb.append(", hasJoin=").append(hasJoin);
+ sb.append(", hasSort=").append(hasSort);
+ sb.append(", hasSetQuery=").append(hasSetQuery);
+ sb.append(", hasLimit=").append(hasLimit);
+ sb.append(", hasOptionalJoin=").append(hasOptionalJoin);
+ sb.append(", hasFullTextSearch=").append(hasFullTextSearch);
+ sb.append('}');
+ return sb.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,1141 @@
+/*
+ * 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.plan;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Command;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.Readable;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitable;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.SetQuery.Operation;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ * A representation of a single node within a plan tree.
+ */
+@NotThreadSafe
+public final class PlanNode implements Iterable<PlanNode>, Readable {
+
+ /**
+ * An enumeration dictating the type of plan tree nodes.
+ */
+ public enum Type {
+
+ /** A node that represents an access of the underlying storage. */
+ ACCESS("Access"),
+ /** A node that defines the removal of duplicate tuples. */
+ DUP_REMOVE("DupRemoval"),
+ /** A node that defines the join type, join criteria, and join strategy */
+ JOIN("Join"),
+ /** A node that defines the columns returned from the node. */
+ PROJECT("Project"),
+ /** A node that selects a filters the tuples by applying a criteria evaluation
filter node (WHERE / HAVING) */
+ SELECT("Select"),
+ /** A node that defines the columns to sort on, the sort direction for each
column, and whether to remove duplicates. */
+ SORT("Sort"),
+ /** A node that defines the 'table' from which the tuples are being
obtained */
+ SOURCE("Source"),
+ /** A node that groups sets of rows into groups (and where aggregation would be
performed) */
+ GROUP("Group"),
+ /** A node that produces no results */
+ NULL("Null"),
+ /** A node that limits the number of tuples returned */
+ LIMIT("Limit"),
+ /** A node the performs set operations on two sets of tuples, including UNION */
+ SET_OPERATION("SetOperation");
+
+ private static final Map<String, Type> TYPE_BY_SYMBOL;
+ static {
+ Map<String, Type> typesBySymbol = new HashMap<String, Type>();
+ for (Type type : Type.values()) {
+ typesBySymbol.put(type.getSymbol().toUpperCase(), type);
+ }
+ TYPE_BY_SYMBOL = Collections.unmodifiableMap(typesBySymbol);
+ }
+
+ private final String symbol;
+
+ private Type( String symbol ) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * Get the symbol representation of this node type.
+ *
+ * @return the symbol; never null and never empty
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Enum#toString()
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ /**
+ * Attempt to find the Type given a symbol. The matching is done independent of
case.
+ *
+ * @param symbol the symbol
+ * @return the Type having the supplied symbol, or null if there is no Type with
the supplied symbol
+ * @throws IllegalArgumentException if the symbol is null
+ */
+ public static Type forSymbol( String symbol ) {
+ CheckArg.isNotNull(symbol, "symbol");
+ return TYPE_BY_SYMBOL.get(symbol.toUpperCase().trim());
+ }
+ }
+
+ /**
+ * An enumeration dictating the type of plan tree nodes.
+ */
+ public enum Property {
+ /** For SELECT and JOIN nodes, a flag specifying whether the criteria is
dependent. Value is a {@link Boolean} object. */
+ IS_DEPENDENT,
+
+ /** For SELECT nodes, the criteria object that is to be applied. Value is a
{@link Constraint} object. */
+ SELECT_CRITERIA,
+
+ /** For SET_OPERATION nodes, the type of set operation to be performed. Value is
a {@link Operation} object. */
+ SET_OPERATION,
+ /** For SET_OPERATION nodes, whether the 'all' clause is used. Value is a
{@link Boolean} object. */
+ SET_USE_ALL,
+
+ /** For JOIN nodes, the type of join operation. Value is a {@link JoinType}
object. */
+ JOIN_TYPE,
+ /** For JOIN nodes, the type of join algorithm. Value is a {@link JoinAlgorithm}
object. */
+ JOIN_ALGORITHM,
+ /** For JOIN nodes, the join criteria (or join condition). Value is a {@link
JoinCondition} object. */
+ JOIN_CONDITION,
+ /**
+ * For JOIN nodes, additional criteria that have been pushed down to the join.
Value is a List of {@link Constraint}
+ * object.
+ */
+ JOIN_CONSTRAINTS,
+
+ /** For SOURCE nodes, the literal name of the selector. Value is a {@link
SelectorName} object. */
+ SOURCE_NAME,
+ /** For SOURCE nodes, the alias name of the selector. Value is a {@link
SelectorName} object. */
+ SOURCE_ALIAS,
+ /**
+ * For SOURCE nodes, the collection of columns that are available. Value is a
Collection of {@link Schemata.Column}
+ * objects.
+ */
+ SOURCE_COLUMNS,
+
+ /** For PROJECT nodes, the ordered collection of columns being projected. Value
is a Collection of {@link Column} objects. */
+ PROJECT_COLUMNS,
+
+ /**
+ * For SET_OPERATION nodes, the list of orderings for the results. Value is
either a Collection of {@link Ordering}
+ * objects or a collection of {@link SelectorName} objects (if the sorting is
being done as an input to a merge-join).
+ */
+ SORT_ORDER_BY,
+
+ /** For LIMIT nodes, the maximum number of rows to return. Value is an {@link
Integer} object. */
+ LIMIT_COUNT,
+ /** For LIMIT nodes, the offset value. Value is an {@link Integer} object. */
+ LIMIT_OFFSET,
+
+ /** For ACCESS nodes, the {@link Command} that is to be executed by the source
*/
+ // ACCESS_COMMAND,
+ BOGUS; // remove this
+ }
+
+ private Type type;
+ private PlanNode parent;
+ private LinkedList<PlanNode> children = new LinkedList<PlanNode>();
+ private List<PlanNode> childrenView = Collections.unmodifiableList(children);
+ private Map<Property, Object> nodeProperties;
+
+ /** The set of named selectors (e.g., tables) that this node deals with. */
+ private Set<SelectorName> selectors = new HashSet<SelectorName>();
+
+ /**
+ * Create a new plan node with the supplied initial type.
+ *
+ * @param type the type of the node; may not be null
+ */
+ public PlanNode( Type type ) {
+ assert type != null;
+ this.type = type;
+ }
+
+ /**
+ * Create a new plan node with the supplied initial type ad that is a child of the
supplied parent.
+ *
+ * @param type the type of the node; may not be null
+ * @param parent the parent node, or null if there is no parent
+ */
+ public PlanNode( Type type,
+ PlanNode parent ) {
+ assert type != null;
+ this.type = type;
+ if (parent != null) {
+ this.parent = parent;
+ this.parent.children.add(this);
+ }
+ }
+
+ /**
+ * Create a new plan node with the supplied initial type ad that is a child of the
supplied parent.
+ *
+ * @param type the type of the node; may not be null
+ * @param selectors the selectors that should be assigned to this node
+ */
+ public PlanNode( Type type,
+ SelectorName... selectors ) {
+ this(type);
+ for (SelectorName selector : selectors) {
+ addSelector(selector);
+ }
+ }
+
+ /**
+ * Create a new plan node with the supplied initial type ad that is a child of the
supplied parent.
+ *
+ * @param type the type of the node; may not be null
+ * @param selectors the selectors that should be assigned to this node
+ */
+ public PlanNode( Type type,
+ Iterable<SelectorName> selectors ) {
+ this(type);
+ addSelectors(selectors);
+ }
+
+ /**
+ * Create a new plan node with the supplied initial type ad that is a child of the
supplied parent.
+ *
+ * @param type the type of the node; may not be null
+ * @param parent the parent node, or null if there is no parent
+ * @param selectors the selectors that should be assigned to this node
+ */
+ public PlanNode( Type type,
+ PlanNode parent,
+ SelectorName... selectors ) {
+ this(type, parent);
+ for (SelectorName selector : selectors) {
+ addSelector(selector);
+ }
+ }
+
+ /**
+ * Create a new plan node with the supplied initial type ad that is a child of the
supplied parent.
+ *
+ * @param type the type of the node; may not be null
+ * @param parent the parent node, or null if there is no parent
+ * @param selectors the selectors that should be assigned to this node
+ */
+ public PlanNode( Type type,
+ PlanNode parent,
+ Iterable<SelectorName> selectors ) {
+ this(type, parent);
+ addSelectors(selectors);
+ }
+
+ /**
+ * Get the type for this node.
+ *
+ * @return the node type, or null if there is no node type
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Set the type for this node.
+ *
+ * @param type Sets type to the specified value; may not be null
+ */
+ public void setType( Type type ) {
+ assert type != null;
+ this.type = type;
+ }
+
+ /**
+ * Get the parent of this node.
+ *
+ * @return the parent node, or null if this node has no parent
+ */
+ public PlanNode getParent() {
+ return parent;
+ }
+
+ /**
+ * Set the parent for this node. If this node already has a parent, this method will
remove this node from the current parent.
+ * If the supplied parent is not null, then this node will be added to the supplied
parent's children.
+ *
+ * @param parent the new parent, or null if this node is to have no parent
+ */
+ public void setParent( PlanNode parent ) {
+ removeFromParent();
+ if (parent != null) {
+ this.parent = parent;
+ this.parent.children.add(this);
+ }
+ }
+
+ /**
+ * Insert the supplied node into the plan node tree immediately above this node. If
this node has a parent when this method is
+ * called, the new parent essentially takes the place of this node within the list of
children of the old parent. This method
+ * does nothing if the supplied new parent is null.
+ * <p>
+ * For example, consider a plan node tree before this method is called:
+ *
+ * <pre>
+ * A
+ * / | \
+ * / | \
+ * B C D
+ * </pre>
+ *
+ * Then after this method is called with
<code>c.insertAsParent(e)</code>, the resulting plan node tree will be:
+ *
+ * <pre>
+ * A
+ * / | \
+ * / | \
+ * B E D
+ * |
+ * |
+ * C
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * Also note that the node on which this method is called ('C' in the example
above) will always be added as the
+ * {@link #addLastChild(PlanNode) last child} to the new parent. This allows the new
parent to already have children before
+ * this method is called.
+ * </p>
+ *
+ * @param newParent the new parent; method does nothing if this is null
+ */
+ public void insertAsParent( PlanNode newParent ) {
+ if (newParent == null) return;
+ if (this.parent != null) {
+ this.parent.replaceChild(this, newParent);
+ }
+ newParent.addLastChild(this);
+ }
+
+ /**
+ * Remove this node from its parent, and return the node that used to be the parent
of this node. Note that this method
+ * removes the entire subgraph under this node.
+ *
+ * @return the node that was the parent of this node, or null if this node had no
parent
+ * @see #extractChild(PlanNode)
+ * @see #extractFromParent()
+ */
+ public PlanNode removeFromParent() {
+ PlanNode result = this.parent;
+ if (this.parent != null) {
+ // Remove this node from its current parent ...
+ this.parent.children.remove(this);
+ this.parent = null;
+ }
+ return result;
+ }
+
+ /**
+ * Get the unmodifiable list of child nodes. This list will immediately reflect any
changes made to the children (via other
+ * methods), but this list cannot be used to add or remove children.
+ *
+ * @return the list of children, which immediately reflects changes but which cannot
be modified directly; never null
+ */
+ public List<PlanNode> getChildren() {
+ return childrenView;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This iterator is immutable.
+ * </p>
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator<PlanNode> iterator() {
+ return childrenView.iterator();
+ }
+
+ /**
+ * Remove all children from this node. All nodes immediately become orphaned. The
resulting list will be mutable.
+ *
+ * @return a copy of all the of the children that were removed (and which have no
parent); never null
+ */
+ public List<PlanNode> removeAllChildren() {
+ if (this.children.isEmpty()) {
+ return new ArrayList<PlanNode>(0);
+ }
+ List<PlanNode> copyOfChildren = new
ArrayList<PlanNode>(this.children);
+ for (Iterator<PlanNode> childIter = this.children.iterator();
childIter.hasNext();) {
+ PlanNode child = childIter.next();
+ childIter.remove();
+ child.parent = null;
+ }
+ return copyOfChildren;
+ }
+
+ /**
+ * Replace the supplied child with another node. If the replacement is already a
child of this node, this method effectively
+ * swaps the position of the child and replacement nodes.
+ *
+ * @param child the node that is already a child and that is to be replaced; may not
be null and must be a child
+ * @param replacement the node that is to replace the 'child' node; may not
be null
+ * @return true if the child was successfully replaced
+ */
+ public boolean replaceChild( PlanNode child,
+ PlanNode replacement ) {
+ assert child != null;
+ assert replacement != null;
+ if (child.parent == this) {
+ int i = this.children.indexOf(child);
+ if (replacement.parent == this) {
+ // Swapping the positions ...
+ int j = this.children.indexOf(replacement);
+ this.children.set(i, replacement);
+ this.children.set(j, child);
+ return true;
+ }
+ // The replacement is not yet a child ...
+ this.children.set(i, replacement);
+ replacement.removeFromParent();
+ replacement.parent = this;
+ child.parent = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the number of child nodes.
+ *
+ * @return the number of children; never negative
+ */
+ public int getChildCount() {
+ return this.children.size();
+ }
+
+ /**
+ * Get the first child.
+ *
+ * @return the first child, or null if there are no children
+ */
+ public PlanNode getFirstChild() {
+ return this.children.isEmpty() ? null : this.children.getFirst();
+ }
+
+ /**
+ * Get the last child.
+ *
+ * @return the last child, or null if there are no children
+ */
+ public PlanNode getLastChild() {
+ return this.children.isEmpty() ? null : this.children.getLast();
+ }
+
+ /**
+ * Get the child at the supplied index.
+ *
+ * @param index the index
+ * @return the child, or null if there are no children
+ * @throws IndexOutOfBoundsException if the index is not valid given the number of
children
+ */
+ public PlanNode getChild( int index ) {
+ return this.children.isEmpty() ? null : this.children.get(index);
+ }
+
+ /**
+ * Add the supplied node to the front of the list of children.
+ *
+ * @param child the node that should be added as the first child; may not be null
+ */
+ public void addFirstChild( PlanNode child ) {
+ assert child != null;
+ this.children.addFirst(child);
+ child.removeFromParent();
+ child.parent = this;
+ }
+
+ /**
+ * Add the supplied node to the end of the list of children.
+ *
+ * @param child the node that should be added as the last child; may not be null
+ */
+ public void addLastChild( PlanNode child ) {
+ assert child != null;
+ this.children.addLast(child);
+ child.removeFromParent();
+ child.parent = this;
+ }
+
+ /**
+ * Add the supplied nodes at the end of the list of children.
+ *
+ * @param otherChildren the children to add; may not be null
+ */
+ public void addChildren( Iterable<PlanNode> otherChildren ) {
+ assert otherChildren != null;
+ for (PlanNode planNode : otherChildren) {
+ this.addLastChild(planNode);
+ }
+ }
+
+ /**
+ * Add the supplied nodes at the end of the list of children.
+ *
+ * @param first the first child to add
+ * @param second the second child to add
+ */
+ public void addChildren( PlanNode first,
+ PlanNode second ) {
+ if (first != null) this.addLastChild(first);
+ if (second != null) this.addLastChild(second);
+ }
+
+ /**
+ * Add the supplied nodes at the end of the list of children.
+ *
+ * @param first the first child to add
+ * @param second the second child to add
+ * @param third the third child to add
+ */
+ public void addChildren( PlanNode first,
+ PlanNode second,
+ PlanNode third ) {
+ if (first != null) this.addLastChild(first);
+ if (second != null) this.addLastChild(second);
+ if (third != null) this.addLastChild(third);
+ }
+
+ /**
+ * Remove the node from this node.
+ *
+ * @param child the child node; may not be null
+ * @return true if the child was removed from this node, or false if the supplied
node was not a child of this node
+ */
+ public boolean removeChild( PlanNode child ) {
+ boolean result = this.children.remove(child);
+ if (result) {
+ child.parent = null;
+ }
+ return result;
+ }
+
+ /**
+ * Remove the child node from this node, and replace that child with its first child
(if there is one).
+ *
+ * @param child the child to be extracted; may not be null and must have at most 1
child
+ * @see #extractFromParent()
+ */
+ public void extractChild( PlanNode child ) {
+ if (child.getChildCount() == 0) {
+ removeChild(child);
+ } else {
+ PlanNode grandChild = child.getFirstChild();
+ replaceChild(child, grandChild);
+ }
+ }
+
+ /**
+ * Extract this node from its parent, but replace this node with its child (if there
is one).
+ *
+ * @see #extractChild(PlanNode)
+ */
+ public void extractFromParent() {
+ this.parent.extractChild(this);
+ }
+
+ /**
+ * Get the keys for the property values that are set on this node.
+ *
+ * @return the property keys; never null but possibly empty
+ */
+ public Set<Property> getPropertyKeys() {
+ return nodeProperties != null ? nodeProperties.keySet() :
Collections.<Property>emptySet();
+ }
+
+ /**
+ * Get the node's value for this supplied property.
+ *
+ * @param propertyId the property identifier
+ * @return the value, or null if there is no property on this node
+ */
+ public Object getProperty( Property propertyId ) {
+ return nodeProperties != null ? nodeProperties.get(propertyId) : null;
+ }
+
+ /**
+ * Get the node's value for this supplied property, casting the result to the
supplied type.
+ *
+ * @param <ValueType> the type of the value expected
+ * @param propertyId the property identifier
+ * @param type the class denoting the type of value expected; may not be null
+ * @return the value, or null if there is no property on this node
+ */
+ public <ValueType> ValueType getProperty( Property propertyId,
+ Class<ValueType> type ) {
+ return nodeProperties != null ? type.cast(nodeProperties.get(propertyId)) :
null;
+ }
+
+ /**
+ * Get the node's value for this supplied property, casting the result to a
{@link Collection} of the supplied type.
+ *
+ * @param <ValueType> the type of the value expected
+ * @param propertyId the property identifier
+ * @param type the class denoting the type of value expected; may not be null
+ * @return the value, or null if there is no property on this node
+ */
+ @SuppressWarnings( "unchecked" )
+ public <ValueType> Collection<ValueType> getPropertyAsCollection(
Property propertyId,
+
Class<ValueType> type ) {
+ if (nodeProperties == null) return null;
+ return (Collection)nodeProperties.get(propertyId);
+ }
+
+ /**
+ * Get the node's value for this supplied property, casting the result to a
{@link List} of the supplied type.
+ *
+ * @param <ValueType> the type of the value expected
+ * @param propertyId the property identifier
+ * @param type the class denoting the type of value expected; may not be null
+ * @return the value, or null if there is no property on this node
+ */
+ @SuppressWarnings( "unchecked" )
+ public <ValueType> List<ValueType> getPropertyAsList( Property
propertyId,
+ Class<ValueType> type )
{
+ if (nodeProperties == null) return null;
+ return (List)nodeProperties.get(propertyId);
+ }
+
+ /**
+ * Set the node's value for the supplied property.
+ *
+ * @param propertyId the property identifier
+ * @param value the value, or null if the property is to be removed
+ * @return the previous value that was overwritten by this call, or null if there was
prior value
+ */
+ public Object setProperty( Property propertyId,
+ Object value ) {
+ if (value == null) {
+ // Removing this property ...
+ return nodeProperties != null ? nodeProperties.remove(propertyId) : null;
+ }
+ // Otherwise, we're adding the property
+ if (nodeProperties == null) nodeProperties = new HashMap<Property,
Object>();
+ return nodeProperties.put(propertyId, value);
+ }
+
+ /**
+ * Remove the node's value for this supplied property.
+ *
+ * @param propertyId the property identifier
+ * @return the value that was removed, or null if there was no property on this node
+ */
+ public Object removeProperty( Object propertyId ) {
+ return nodeProperties != null ? nodeProperties.remove(propertyId) : null;
+ }
+
+ /**
+ * Indicates if there is a non-null value for the property.
+ *
+ * @param propertyId the property identifier
+ * @return true if this node has a non-null value for that property, or false if the
value is null or there is no property
+ */
+ public boolean hasProperty( Property propertyId ) {
+ return nodeProperties != null && nodeProperties.containsKey(propertyId);
+ }
+
+ /**
+ * Indicates if there is a non-null and non-empty Collection value for the property.
+ *
+ * @param propertyId the property identifier
+ * @return true if this node has value for the supplied property and that value is a
non-empty Collection
+ */
+ public boolean hasCollectionProperty( Property propertyId ) {
+ Object value = getProperty(propertyId);
+ return (value instanceof Collection &&
!((Collection<?>)value).isEmpty());
+ }
+
+ /**
+ * Indicates if there is a non-null property value that equates to a
<code>true</code> boolean value.
+ *
+ * @param propertyId the property identifier
+ * @return true if this node has value for the supplied property and that value is a
boolean value of <code>true</code>
+ */
+ public boolean hasBooleanProperty( Property propertyId ) {
+ Object value = getProperty(propertyId);
+ return (value instanceof Boolean && ((Boolean)value).booleanValue());
+ }
+
+ /**
+ * Add a selector to this plan node. This method does nothing if the supplied
selector is null.
+ *
+ * @param symbol the symbol of the selector
+ */
+ public void addSelector( SelectorName symbol ) {
+ if (symbol != null) selectors.add(symbol);
+ }
+
+ /**
+ * Add the selectors to this plan node. This method does nothing for any supplied
selector that is null.
+ *
+ * @param first the first symbol to be added
+ * @param second the second symbol to be added
+ */
+ public void addSelector( SelectorName first,
+ SelectorName second ) {
+ if (first != null) selectors.add(first);
+ if (second != null) selectors.add(second);
+ }
+
+ /**
+ * Add the selectors to this plan node. This method does nothing for any supplied
selector that is null.
+ *
+ * @param names the symbols to be added
+ */
+ public void addSelectors( Iterable<SelectorName> names ) {
+ for (SelectorName name : names) {
+ if (name != null) selectors.add(name);
+ }
+ }
+
+ /**
+ * Get the selectors that are referenced by this plan node.
+ *
+ * @return the names of the selectors; never null but possibly empty
+ */
+ public Set<SelectorName> getSelectors() {
+ return selectors;
+ }
+
+ /**
+ * Get the path from this node (inclusive) to the supplied descendant node
(inclusive)
+ *
+ * @param descendant the descendant; may not be null, and must be a descendant of
this node
+ * @return the path from this node to the supplied descendant node; never null
+ */
+ public LinkedList<PlanNode> getPathTo( PlanNode descendant ) {
+ assert descendant != null;
+ LinkedList<PlanNode> stack = new LinkedList<PlanNode>();
+ PlanNode node = descendant;
+ while (node != this) {
+ stack.addFirst(node);
+ node = node.getParent();
+ assert node != null : "The supplied node is not a descendant of this
node";
+ }
+ stack.addFirst(this);
+ return stack;
+ }
+
+ /**
+ * Determine whether this node has an ancestor with the supplied type.
+ *
+ * @param type the type; may not be null
+ * @return true if there is at least one ancestor of the supplied type, or false
otherwise
+ */
+ public boolean hasAncestorOfType( Type type ) {
+ return hasAncestorOfType(EnumSet.of(type));
+ }
+
+ /**
+ * Determine whether this node has an ancestor with any of the supplied types.
+ *
+ * @param firstType the first type; may not be null
+ * @param additionalTypes the additional types; may not be null
+ * @return true if there is at least one ancestor that has any of the supplied types,
or false otherwise
+ */
+ public boolean hasAncestorOfType( Type firstType,
+ Type... additionalTypes ) {
+ return hasAncestorOfType(EnumSet.of(firstType, additionalTypes));
+ }
+
+ /**
+ * Determine whether this node has an ancestor with any of the supplied types.
+ *
+ * @param types the types; may not be null
+ * @return true if there is at least one ancestor that has any of the supplied types,
or false otherwise
+ */
+ public boolean hasAncestorOfType( Set<Type> types ) {
+ PlanNode node = this.parent;
+ while (node != null) {
+ if (types.contains(node.getType())) return true;
+ node = node.getParent();
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getString(ExecutionContext.DEFAULT_CONTEXT);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public final boolean equals( Object obj ) {
+ // Quite a few methods rely upon instance/reference equality ...
+ return super.equals(obj);
+ }
+
+ /**
+ * Determine whether the supplied plan is equivalent to this plan.
+ *
+ * @param other the other plan to compare with this instance
+ * @return true if the two plans are equivalent, or false otherwise
+ */
+ public boolean isSameAs( PlanNode other ) {
+ if (other == null) return false;
+ if (this.getType() != other.getType()) return false;
+ if (!this.nodeProperties.equals(other.nodeProperties)) return false;
+ if (!this.getSelectors().equals(other.getSelectors())) return false;
+ return this.getChildren().equals(other.getChildren());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.model.Readable#getString(org.jboss.dna.graph.ExecutionContext)
+ */
+ public String getString( ExecutionContext context ) {
+ StringBuilder sb = new StringBuilder();
+ getRecursiveString(context, sb, 0);
+ return sb.toString();
+ }
+
+ private void getRecursiveString( ExecutionContext context,
+ StringBuilder str,
+ int indentLevel ) {
+ for (int i = 0; i < indentLevel; ++i) {
+ str.append(" ");
+ }
+ getNodeString(context, str).append('\n');
+
+ // Recursively add children at one greater tab level
+ for (PlanNode child : this) {
+ child.getRecursiveString(context, str, indentLevel + 1);
+ }
+ }
+
+ private StringBuilder getNodeString( ExecutionContext context,
+ StringBuilder str ) {
+ str.append(this.type.getSymbol());
+ if (!selectors.isEmpty()) {
+ str.append(" [");
+ boolean first = true;
+ for (SelectorName symbol : selectors) {
+ if (first) first = false;
+ else str.append(',');
+ str.append(symbol.getName());
+ }
+ str.append(']');
+ }
+ if (nodeProperties != null && !nodeProperties.isEmpty()) {
+ str.append(" <");
+ boolean first = true;
+ for (Map.Entry<Property, Object> entry : nodeProperties.entrySet()) {
+ if (first) first = false;
+ else str.append(", ");
+ str.append(entry.getKey()).append('=');
+ Object value = entry.getValue();
+ if (value instanceof Visitable) {
+ str.append(Visitors.readable((Visitable)value, context));
+ } else if (value instanceof Collection) {
+ boolean firstItem = true;
+ str.append('[');
+ for (Object item : (Collection<?>)value) {
+ if (firstItem) firstItem = false;
+ else str.append(", ");
+ if (item instanceof Visitable) {
+ str.append(Visitors.readable((Visitable)item, context));
+ } else {
+ str.append(item);
+ }
+ }
+ str.append(']');
+ } else {
+ str.append(value);
+ }
+ }
+ str.append('>');
+ }
+ return str;
+ }
+
+ /**
+ * Starting at the parent of this node, find the lowest (also closest) ancestor that
has the specified type.
+ *
+ * @param typeToFind the type of the node to find; may not be null
+ * @return the node with the specified type, or null if there is no such ancestor
+ */
+ public PlanNode findAncestor( Type typeToFind ) {
+ return findAncestor(EnumSet.of(typeToFind));
+ }
+
+ /**
+ * Starting at the parent of this node, find the lowest (also closest) ancestor that
has one of the specified types.
+ *
+ * @param firstTypeToFind the first type to find; may not be null
+ * @param additionalTypesToFind additional types to find; may not be null
+ * @return the node with the specified type, or null if there is no such ancestor
+ */
+ public PlanNode findAncestor( Type firstTypeToFind,
+ Type... additionalTypesToFind ) {
+ return findAncestor(EnumSet.of(firstTypeToFind, additionalTypesToFind));
+ }
+
+ /**
+ * Starting at the parent of this node, find the lowest (also closest) ancestor that
has one of the specified types.
+ *
+ * @param typesToFind the set of types to find; may not be null
+ * @return the node with one of the specified types, or null if there is no such
ancestor
+ */
+ public PlanNode findAncestor( Set<Type> typesToFind ) {
+ PlanNode node = this;
+ PlanNode parent = null;
+ while ((parent = node.getParent()) != null) {
+ if (typesToFind.contains(parent.getType())) return parent;
+ node = parent;
+ }
+ return null;
+ }
+
+ public static enum Traversal {
+ LEVEL_ORDER,
+ PRE_ORDER;
+ }
+
+ /**
+ * Find all of the nodes of the specified type that are at or below this node.
+ *
+ * @param typeToFind the type of node to find; may not be null
+ * @return the collection of nodes that are at or below this node that all have the
supplied type; never null but possibly
+ * empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Type typeToFind ) {
+ return findAllAtOrBelow(EnumSet.of(typeToFind));
+ }
+
+ /**
+ * Find all of the nodes with one of the specified types that are at or below this
node.
+ *
+ * @param firstTypeToFind the first type of node to find; may not be null
+ * @param additionalTypesToFind the additional types of node to find; may not be
null
+ * @return the collection of nodes that are at or below this node that all have one
of the supplied types; never null but
+ * possibly empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Type firstTypeToFind,
+ Type... additionalTypesToFind ) {
+ return findAllAtOrBelow(EnumSet.of(firstTypeToFind, additionalTypesToFind));
+ }
+
+ /**
+ * Find all of the nodes with one of the specified types that are at or below this
node.
+ *
+ * @param typesToFind the types of node to find; may not be null
+ * @return the collection of nodes that are at or below this node that all have one
of the supplied types; never null but
+ * possibly empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Set<Type> typesToFind ) {
+ return findAllAtOrBelow(Traversal.PRE_ORDER, typesToFind);
+ }
+
+ /**
+ * Find all of the nodes of the specified type that are at or below this node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param typeToFind the type of node to find; may not be null
+ * @return the collection of nodes that are at or below this node that all have the
supplied type; never null but possibly
+ * empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Traversal order,
+ Type typeToFind ) {
+ return findAllAtOrBelow(order, EnumSet.of(typeToFind));
+ }
+
+ /**
+ * Find all of the nodes with one of the specified types that are at or below this
node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param firstTypeToFind the first type of node to find; may not be null
+ * @param additionalTypesToFind the additional types of node to find; may not be
null
+ * @return the collection of nodes that are at or below this node that all have one
of the supplied types; never null but
+ * possibly empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Traversal order,
+ Type firstTypeToFind,
+ Type... additionalTypesToFind ) {
+ return findAllAtOrBelow(order, EnumSet.of(firstTypeToFind,
additionalTypesToFind));
+ }
+
+ /**
+ * Find all of the nodes with one of the specified types that are at or below this
node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param typesToFind the types of node to find; may not be null
+ * @return the collection of nodes that are at or below this node that all have one
of the supplied types; never null but
+ * possibly empty
+ */
+ public List<PlanNode> findAllAtOrBelow( Traversal order,
+ Set<Type> typesToFind ) {
+ assert order != null;
+ List<PlanNode> results = new LinkedList<PlanNode>();
+ LinkedList<PlanNode> queue = new LinkedList<PlanNode>();
+ queue.add(this);
+ while (!queue.isEmpty()) {
+ PlanNode aNode = queue.poll();
+ if (typesToFind.contains(aNode.getType())) {
+ results.add(aNode);
+ }
+ switch (order) {
+ case LEVEL_ORDER:
+ queue.addAll(aNode.getChildren());
+ break;
+ case PRE_ORDER:
+ queue.addAll(0, aNode.getChildren());
+ break;
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Find the first node with the specified type that are at or below this node.
+ *
+ * @param typeToFind the type of node to find; may not be null
+ * @return the first node that is at or below this node that has the supplied type;
or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Type typeToFind ) {
+ return findAtOrBelow(EnumSet.of(typeToFind));
+ }
+
+ /**
+ * Find the first node with one of the specified types that are at or below this
node.
+ *
+ * @param firstTypeToFind the first type of node to find; may not be null
+ * @param additionalTypesToFind the additional types of node to find; may not be
null
+ * @return the first node that is at or below this node that has one of the supplied
types; or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Type firstTypeToFind,
+ Type... additionalTypesToFind ) {
+ return findAtOrBelow(EnumSet.of(firstTypeToFind, additionalTypesToFind));
+ }
+
+ /**
+ * Find the first node with one of the specified types that are at or below this
node.
+ *
+ * @param typesToFind the types of node to find; may not be null
+ * @return the first node that is at or below this node that has one of the supplied
types; or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Set<Type> typesToFind ) {
+ return findAtOrBelow(Traversal.PRE_ORDER, typesToFind);
+ }
+
+ /**
+ * Find the first node with the specified type that are at or below this node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param typeToFind the type of node to find; may not be null
+ * @return the first node that is at or below this node that has the supplied type;
or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Traversal order,
+ Type typeToFind ) {
+ return findAtOrBelow(order, EnumSet.of(typeToFind));
+ }
+
+ /**
+ * Find the first node with one of the specified types that are at or below this
node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param firstTypeToFind the first type of node to find; may not be null
+ * @param additionalTypesToFind the additional types of node to find; may not be
null
+ * @return the first node that is at or below this node that has one of the supplied
types; or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Traversal order,
+ Type firstTypeToFind,
+ Type... additionalTypesToFind ) {
+ return findAtOrBelow(order, EnumSet.of(firstTypeToFind, additionalTypesToFind));
+ }
+
+ /**
+ * Find the first node with one of the specified types that are at or below this
node.
+ *
+ * @param order the order to traverse; may not be null
+ * @param typesToFind the types of node to find; may not be null
+ * @return the first node that is at or below this node that has one of the supplied
types; or null if there is no such node
+ */
+ public PlanNode findAtOrBelow( Traversal order,
+ Set<Type> typesToFind ) {
+ LinkedList<PlanNode> queue = new LinkedList<PlanNode>();
+ queue.add(this);
+ while (!queue.isEmpty()) {
+ PlanNode aNode = queue.poll();
+ if (typesToFind.contains(aNode.getType())) {
+ return aNode;
+ }
+ switch (order) {
+ case LEVEL_ORDER:
+ queue.addAll(aNode.getChildren());
+ break;
+ case PRE_ORDER:
+ queue.addAll(0, aNode.getChildren());
+ break;
+ }
+ }
+ return null;
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/Planner.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/Planner.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/Planner.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,44 @@
+/*
+ * 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.plan;
+
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+
+/**
+ * Interface for a query planner.
+ */
+public interface Planner {
+
+ /**
+ * Create a canonical query plan for the given command.
+ *
+ * @param context the context in which the query is being planned
+ * @param query the query command to be planned
+ * @return the root node of the plan tree representing the canonical plan
+ */
+ public PlanNode createPlan( QueryContext context,
+ QueryCommand query );
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/Planner.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DelegatingComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DelegatingComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DelegatingComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,63 @@
+/*
+ * 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.process;
+
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public abstract class DelegatingComponent extends ProcessingComponent {
+
+ private final ProcessingComponent delegate;
+
+ protected DelegatingComponent( ProcessingComponent delegate ) {
+ this(delegate, delegate.getColumns());
+ }
+
+ protected DelegatingComponent( ProcessingComponent delegate,
+ Columns overridingColumns ) {
+ super(delegate.getContext(), overridingColumns);
+ this.delegate = delegate;
+ }
+
+ /**
+ * Get the delegate processor.
+ *
+ * @return the delegate processor
+ */
+ protected final ProcessingComponent delegate() {
+ return delegate;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#close()
+ */
+ @Override
+ public void close() {
+ super.close();
+ this.delegate.close();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DelegatingComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,84 @@
+/*
+ * 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.process;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.jboss.dna.graph.Location;
+
+/**
+ * A {@link ProcessingComponent} implementation that removes duplicates. The results from
the delegate component do not need to be
+ * sorted; in fact, if the delegate component is a {@link SortValuesComponent}, then use
{@link DistinctOfSortedComponent} instead of
+ * this class.
+ */
+public class DistinctComponent extends DelegatingComponent {
+
+ public DistinctComponent( ProcessingComponent delegate ) {
+ super(delegate);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ if (tuples.size() > 1) {
+ // Go through this list and remove any tuples that appear more than once ...
+ Iterator<Object[]> iter = tuples.iterator();
+
+ int locationCount = getColumns().getLocationCount();
+ int firstLocationIndex = getColumns().getColumnCount();
+ if (locationCount == 1) {
+ // We can determine duplicates faster/cheaper using a single
Set<Location> ...
+ Set<Location> found = new HashSet<Location>();
+ while (iter.hasNext()) {
+ Location location = (Location)iter.next()[firstLocationIndex];
+ if (!found.add(location)) {
+ // Was already found, so remove this tuple from the results ...
+ iter.remove();
+ }
+ }
+ } else {
+ assert locationCount > 1;
+ // Duplicate tuples are removed using a Set<Location[]> ...
+ Set<Location[]> found = new HashSet<Location[]>();
+ while (iter.hasNext()) {
+ Object[] tuple = iter.next();
+ Location[] locations = new Location[locationCount];
+ System.arraycopy(tuple, firstLocationIndex, locations, 0,
locationCount);
+ if (!found.add(locations)) {
+ // Was already found, so remove this tuple from the results ...
+ iter.remove();
+ }
+ }
+ }
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctOfSortedComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctOfSortedComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctOfSortedComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,64 @@
+/*
+ * 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.process;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An efficient {@link ProcessingComponent} that removes duplicates from an
already-sorted set of results.
+ *
+ * @see DistinctComponent
+ */
+public class DistinctOfSortedComponent extends DelegatingComponent {
+
+ private final Comparator<Object[]> comparator;
+
+ public DistinctOfSortedComponent( SortValuesComponent delegate ) {
+ super(delegate);
+ this.comparator = delegate.getSortingComparator();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ Iterator<Object[]> iter = tuples.iterator();
+ Object[] previous = null;
+ while (iter.hasNext()) {
+ Object[] current = iter.next();
+ if (previous != null && this.comparator.compare(previous, current) ==
0) {
+ iter.remove();
+ } else {
+ previous = current;
+ }
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/DistinctOfSortedComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ExceptComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ExceptComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ExceptComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,123 @@
+/*
+ * 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.process;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public class ExceptComponent extends SetOperationComponent {
+
+ private final Comparator<Object[]> comparator;
+
+ public ExceptComponent( QueryContext context,
+ Columns columns,
+ Iterable<ProcessingComponent> sources,
+ boolean alreadySorted,
+ boolean all ) {
+ super(context, columns, sources, alreadySorted, all);
+ this.comparator = createSortComparator(context, columns);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ Iterator<ProcessingComponent> sources = sources().iterator();
+ if (!sources.hasNext()) return emptyTuples();
+
+ // Execute all of the source components (so we can sort the results from the
smallest to the largest) ...
+ // TODO: Parallelize this ???
+ List<List<Object[]>> allTuples = new
LinkedList<List<Object[]>>();
+ while (sources.hasNext()) {
+ List<Object[]> tuples = sources.next().execute();
+ if (tuples == null) continue;
+ if (tuples.isEmpty()) return emptyTuples();
+ allTuples.add(tuples);
+ }
+ if (allTuples.isEmpty()) return emptyTuples();
+ if (allTuples.size() == 1) return allTuples.get(0); // just one source
+ // Sort the tuples by size, starting with the smallest ...
+ Collections.sort(allTuples, new Comparator<List<Object[]>>() {
+ public int compare( List<Object[]> tuples1,
+ List<Object[]> tuples2 ) {
+ return tuples1.size() - tuples2.size();
+ }
+ });
+
+ // Do the sources 2 at a time ...
+ Iterator<List<Object[]>> iter = allTuples.iterator();
+ List<Object[]> tuples = iter.next(); // already sorted ...
+ assert iter.hasNext();
+ // Process the next source with the previous ...
+ while (iter.hasNext()) {
+ List<Object[]> next = iter.next(); // already sorted ...
+ // Walk through the first and second results ...
+ Iterator<Object[]> tupleIter = tuples.iterator();
+ Iterator<Object[]> nextIter = next.iterator();
+ // There is at least one tuple in each ...
+ Object[] tuple1 = tupleIter.next();
+ Object[] tuple2 = nextIter.next();
+ while (true) {
+ int comparison = comparator.compare(tuple1, tuple2);
+ if (comparison == 0) {
+ // Both match, so remove the tuple from 'tuples' and go on
...
+ tupleIter.remove();
+ continue;
+ }
+ // No match, so leave tuple1 in 'tuples'
+ if (comparison < 0) {
+ // tuple1 is less than tuple2, so advance tupleIter ...
+ if (!tupleIter.hasNext()) {
+ // The intersection results ('tuples') has no more
tuples, so go to the next source ...
+ break;
+ }
+ tuple1 = tupleIter.next();
+ continue;
+ }
+ assert comparison > 0;
+ // tuple1 is greater than tuple2, so advance nextIter ...
+ if (!nextIter.hasNext()) {
+ // The next source has no more tuples, so leave all remaining tuples,
and go to the next source ...
+ break;
+ }
+ tuple2 = nextIter.next();
+ continue;
+ }
+ }
+ // Remove duplicates if requested to ...
+ removeDuplicatesIfRequested(tuples);
+ // Return all of the common tuples that were in all sources ...
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ExceptComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/IntersectComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/IntersectComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/IntersectComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,128 @@
+/*
+ * 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.process;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public class IntersectComponent extends SetOperationComponent {
+
+ private final Comparator<Object[]> comparator;
+
+ public IntersectComponent( QueryContext context,
+ Columns columns,
+ Iterable<ProcessingComponent> sources,
+ boolean alreadySorted,
+ boolean all ) {
+ super(context, columns, sources, alreadySorted, all);
+ this.comparator = createSortComparator(context, columns);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ Iterator<ProcessingComponent> sources = sources().iterator();
+ if (!sources.hasNext()) return emptyTuples();
+
+ // Execute all of the source components (so we can sort the results from the
smallest to the largest) ...
+ // TODO: Parallelize this ???
+ List<List<Object[]>> allTuples = new
LinkedList<List<Object[]>>();
+ while (sources.hasNext()) {
+ List<Object[]> tuples = sources.next().execute();
+ if (tuples == null) continue;
+ if (tuples.isEmpty()) return emptyTuples();
+ allTuples.add(tuples);
+ }
+ if (allTuples.isEmpty()) return emptyTuples();
+ if (allTuples.size() == 1) return allTuples.get(0); // just one source
+ // Sort the tuples by size, starting with the smallest ...
+ Collections.sort(allTuples, new Comparator<List<Object[]>>() {
+ public int compare( List<Object[]> tuples1,
+ List<Object[]> tuples2 ) {
+ return tuples1.size() - tuples2.size();
+ }
+ });
+
+ // Do the sources 2 at a time ...
+ Iterator<List<Object[]>> iter = allTuples.iterator();
+ List<Object[]> tuples = iter.next(); // already sorted ...
+ assert iter.hasNext();
+ // Process the next source with the previous ...
+ while (iter.hasNext()) {
+ List<Object[]> next = iter.next(); // already sorted ...
+ // Walk through the first and second results ...
+ Iterator<Object[]> tupleIter = tuples.iterator();
+ Iterator<Object[]> nextIter = next.iterator();
+ // There is at least one tuple in each ...
+ Object[] tuple1 = tupleIter.next();
+ Object[] tuple2 = nextIter.next();
+ while (true) {
+ int comparison = comparator.compare(tuple1, tuple2);
+ if (comparison == 0) {
+ // Both match, so leave the tuple in 'tuples' and go on ...
+ continue;
+ }
+ // No match, so remove tuple1 from 'tuples'
+ tupleIter.remove();
+ if (comparison < 0) {
+ // tuple1 is less than tuple2, so advance tupleIter ...
+ if (!tupleIter.hasNext()) {
+ // The intersection results ('tuples') has no more
tuples, so go to the next source ...
+ break;
+ }
+ tuple1 = tupleIter.next();
+ continue;
+ }
+ assert comparison > 0;
+ // tuple1 is greater than tuple2, so advance nextIter ...
+ if (!nextIter.hasNext()) {
+ // The next source has no more tuples, so remove all remaining tuples
...
+ while (tupleIter.hasNext()) {
+ tupleIter.next();
+ tupleIter.remove();
+ }
+ // then go to the next source ...
+ break;
+ }
+ tuple2 = nextIter.next();
+ continue;
+ }
+ }
+ // Remove duplicates if requested to ...
+ removeDuplicatesIfRequested(tuples);
+ // Return all of the common tuples that were in all sources ...
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/IntersectComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/JoinComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/JoinComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/JoinComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,477 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueComparators;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ *
+ */
+public abstract class JoinComponent extends ProcessingComponent {
+
+ protected static final Comparator<Location> LOCATION_COMPARATOR =
Location.comparator();
+
+ private final ProcessingComponent left;
+ private final ProcessingComponent right;
+ private final JoinCondition condition;
+ private final JoinType joinType;
+
+ protected JoinComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ JoinCondition condition,
+ JoinType joinType ) {
+ super(context, computeJoinedColumns(left.getColumns(), right.getColumns()));
+ this.left = left;
+ this.right = right;
+ this.joinType = joinType;
+ this.condition = condition;
+ assert this.left != null;
+ assert this.right != null;
+ assert this.joinType != null;
+ }
+
+ /**
+ * Get the type of join this processor represents.
+ *
+ * @return the join type; never null
+ */
+ public final JoinType getJoinType() {
+ return joinType;
+ }
+
+ /**
+ * Get the join condition.
+ *
+ * @return the join condition; never null
+ */
+ public final JoinCondition getJoinCondition() {
+ return condition;
+ }
+
+ /**
+ * Get the processing component that serves as the left side of the join.
+ *
+ * @return the left-side processing component; never null
+ */
+ protected final ProcessingComponent left() {
+ return left;
+ }
+
+ /**
+ * Get the processing component that serves as the right side of the join.
+ *
+ * @return the right-side processing component; never null
+ */
+ protected final ProcessingComponent right() {
+ return right;
+ }
+
+ /**
+ * Get the columns definition for the results from the left side of the join.
+ *
+ * @return the left-side columns that feed this join; never null
+ */
+ protected final Columns leftColunns() {
+ return left.getColumns();
+ }
+
+ /**
+ * Get the columns definition for the results from the right side of the join.
+ *
+ * @return the right-side columns that feed this join; never null
+ */
+ protected final Columns rightColumns() {
+ return right.getColumns();
+ }
+
+ protected static Columns computeJoinedColumns( Columns leftColumns,
+ Columns rightColumns ) {
+ List<Column> columns = new
ArrayList<Column>(leftColumns.getColumnCount() + rightColumns.getColumnCount());
+ columns.addAll(leftColumns.getColumns());
+ columns.addAll(rightColumns.getColumns());
+ boolean includeFullTextScores = leftColumns.hasFullTextSearchScores() ||
rightColumns.hasFullTextSearchScores();
+ return new QueryResultColumns(columns, includeFullTextScores);
+ }
+
+ protected static TupleMerger createMerger( Columns joinColumns,
+ Columns leftColumns,
+ Columns rightColumns ) {
+ final int joinTupleSize = joinColumns.getTupleSize();
+ final int joinColumnCount = joinColumns.getColumnCount();
+ final int joinLocationCount = joinColumns.getLocationCount();
+ final int leftColumnCount = leftColumns.getColumnCount();
+ final int leftLocationCount = leftColumns.getLocationCount();
+ final int leftTupleSize = leftColumns.getTupleSize();
+ final int rightColumnCount = rightColumns.getColumnCount();
+ final int rightLocationCount = rightColumns.getLocationCount();
+ final int rightTupleSize = rightColumns.getTupleSize();
+ final int startLeftLocations = joinColumnCount;
+ final int startRightLocations = startLeftLocations + leftLocationCount;
+
+ // The left and right selectors should NOT overlap ...
+ assert joinLocationCount == leftLocationCount + rightLocationCount;
+
+ // Create different implementations depending upon the options, since this save
us from having to make
+ // these decisions while doing the merges...
+ if (joinColumns.hasFullTextSearchScores()) {
+ final int leftScoreCount = leftTupleSize - leftColumnCount -
leftLocationCount;
+ final int rightScoreCount = rightTupleSize - rightColumnCount -
rightLocationCount;
+ final int startLeftScores = startRightLocations + rightLocationCount;
+ final int startRightScores = startLeftScores + leftScoreCount;
+ final boolean leftScores = leftScoreCount > 0;
+ final boolean rightScores = rightScoreCount > 0;
+
+ return new TupleMerger() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.JoinComponent.TupleMerger#merge(java.lang.Object[],
+ * java.lang.Object[])
+ */
+ public Object[] merge( Object[] leftTuple,
+ Object[] rightTuple ) {
+ Object[] result = new Object[joinTupleSize]; // initialized to null
+ // If the tuple arrays are null, then we don't need to copy
because the arrays are
+ // initialized to null values.
+ if (leftTuple != null) {
+ // Copy the left tuple values ...
+ System.arraycopy(result, 0, leftTuple, 0, leftColumnCount);
+ System.arraycopy(result, startLeftLocations, leftTuple,
leftColumnCount, leftLocationCount);
+ if (leftScores) {
+ System.arraycopy(result, startLeftScores, leftTuple,
leftLocationCount, leftScoreCount);
+ }
+ }
+ if (rightTuple != null) {
+ // Copy the right tuple values ...
+ System.arraycopy(result, leftColumnCount, rightTuple, 0,
rightColumnCount);
+ System.arraycopy(result, startRightLocations, rightTuple,
rightColumnCount, rightLocationCount);
+ if (rightScores) {
+ System.arraycopy(result, startRightScores, rightTuple,
rightLocationCount, rightScoreCount);
+ }
+ }
+ return result;
+ }
+ };
+ }
+ // There are no full-text search scores ...
+ return new TupleMerger() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.JoinComponent.TupleMerger#merge(java.lang.Object[],
java.lang.Object[])
+ */
+ public Object[] merge( Object[] leftTuple,
+ Object[] rightTuple ) {
+ Object[] result = new Object[joinTupleSize]; // initialized to null
+ // If the tuple arrays are null, then we don't need to copy because
the arrays are
+ // initialized to null values.
+ if (leftTuple != null) {
+ // Copy the left tuple values ...
+ System.arraycopy(result, 0, leftTuple, 0, leftColumnCount);
+ System.arraycopy(result, startLeftLocations, leftTuple,
leftColumnCount, leftLocationCount);
+ }
+ if (rightTuple != null) {
+ // Copy the right tuple values ...
+ System.arraycopy(result, leftColumnCount, rightTuple, 0,
rightColumnCount);
+ System.arraycopy(result, startRightLocations, rightTuple,
rightColumnCount, rightLocationCount);
+ }
+ return result;
+ }
+ };
+ }
+
+ protected static interface TupleMerger {
+ Object[] merge( Object[] leftTuple,
+ Object[] rightTuple );
+ }
+
+ /**
+ * Interface defining the value of a tuple that is used in the join condition.
+ */
+ protected static interface ValueSelector {
+ /**
+ * Obtain the value that is to be used in the join condition.
+ *
+ * @param tuple the tuple
+ * @return the value that should be used
+ */
+ Object evaluate( Object[] tuple );
+ }
+
+ /**
+ * Create a {@link ValueSelector} that obtains the value required to use the supplied
join condition.
+ *
+ * @param source the source component; may not be null
+ * @param condition the join condition; may not be null
+ * @return the value selector; never null
+ */
+ protected static ValueSelector valueSelectorFor( ProcessingComponent source,
+ JoinCondition condition ) {
+ if (condition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition joinCondition = (ChildNodeJoinCondition)condition;
+ String childSelectorName = joinCondition.getChildSelectorName().getName();
+ if (source.getColumns().hasSelector(childSelectorName)) {
+ return selectPath(source, childSelectorName);
+ }
+ String parentSelectorName = joinCondition.getParentSelectorName().getName();
+ return selectPath(source, parentSelectorName);
+ } else if (condition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition joinCondition = (SameNodeJoinCondition)condition;
+ String selector1Name = joinCondition.getSelector1Name().getName();
+ if (source.getColumns().hasSelector(selector1Name)) {
+ return selectPath(source, selector1Name);
+ }
+ String selector2Name = joinCondition.getSelector2Name().getName();
+ return selectPath(source, selector2Name);
+ } else if (condition instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition joinCondition =
(DescendantNodeJoinCondition)condition;
+ String ancestorSelectorName =
joinCondition.getAncestorSelectorName().getName();
+ if (source.getColumns().hasSelector(ancestorSelectorName)) {
+ return selectPath(source, ancestorSelectorName);
+ }
+ String descendantSelectorName =
joinCondition.getDescendantSelectorName().getName();
+ return selectPath(source, descendantSelectorName);
+ } else if (condition instanceof EquiJoinCondition) {
+ EquiJoinCondition joinCondition = (EquiJoinCondition)condition;
+ SelectorName selector1Name = joinCondition.getSelector1Name();
+ Name propName1 = joinCondition.getProperty1Name();
+ if (source.getColumns().hasSelector(selector1Name.getName())) {
+ return selectValue(source, selector1Name, propName1);
+ }
+ SelectorName selector2Name = joinCondition.getSelector2Name();
+ Name propName2 = joinCondition.getProperty2Name();
+ return selectValue(source, selector2Name, propName2);
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static ValueSelector selectPath( ProcessingComponent component,
+ String selectorName ) {
+ final int index = component.getColumns().getLocationIndex(selectorName);
+ return new ValueSelector() {
+ public Object evaluate( Object[] tuple ) {
+ Location location = (Location)tuple[index];
+ return location != null ? location.getPath() : null;
+ }
+ };
+ }
+
+ private static ValueSelector selectValue( ProcessingComponent component,
+ SelectorName selectorName,
+ Name propertyName ) {
+ final int index =
component.getColumns().getColumnIndexForProperty(selectorName.getName(), propertyName);
+ return new ValueSelector() {
+ public Object evaluate( Object[] tuple ) {
+ return tuple[index];
+ }
+ };
+ }
+
+ /**
+ * Interface defining the value of a tuple that is used in the join condition.
+ */
+ protected static interface Joinable {
+ /**
+ * Obtain the value that is to be used in the join condition.
+ *
+ * @param leftValue the value from the left tuple; never null
+ * @param rightValue the value from the right tuple; never null
+ * @return true if the tuples are to be joined
+ */
+ boolean evaluate( Object leftValue,
+ Object rightValue );
+ }
+
+ /**
+ * Create a {@link ValueSelector} that obtains the value required to use the supplied
join condition.
+ *
+ * @param left the left source component; may not be null
+ * @param right the left source component; may not be null
+ * @param condition the join condition; may not be null
+ * @return the value selector; never null
+ */
+ protected static Joinable joinableFor( ProcessingComponent left,
+ ProcessingComponent right,
+ JoinCondition condition ) {
+ if (condition instanceof SameNodeJoinCondition) {
+ return new Joinable() {
+ public boolean evaluate( Object locationA,
+ Object locationB ) {
+ Location location1 = (Location)locationA;
+ Location location2 = (Location)locationB;
+ return location1.isSame(location2);
+ }
+ };
+ } else if (condition instanceof EquiJoinCondition) {
+ return new Joinable() {
+ public boolean evaluate( Object leftValue,
+ Object rightValue ) {
+ return leftValue.equals(rightValue);
+ }
+ };
+ } else if (condition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition joinCondition = (ChildNodeJoinCondition)condition;
+ String childSelectorName = joinCondition.getChildSelectorName().getName();
+ if (left.getColumns().hasSelector(childSelectorName)) {
+ // The child is on the left ...
+ return new Joinable() {
+ public boolean evaluate( Object childLocation,
+ Object parentLocation ) {
+ Path childPath = ((Location)childLocation).getPath();
+ Path parentPath = ((Location)parentLocation).getPath();
+ return childPath.getParent().isSameAs(parentPath);
+ }
+ };
+ }
+ // The child is on the right ...
+ return new Joinable() {
+ public boolean evaluate( Object parentLocation,
+ Object childLocation ) {
+ Path childPath = ((Location)childLocation).getPath();
+ Path parentPath = ((Location)parentLocation).getPath();
+ return childPath.getParent().isSameAs(parentPath);
+ }
+ };
+ } else if (condition instanceof DescendantNodeJoinCondition) {
+ DescendantNodeJoinCondition joinCondition =
(DescendantNodeJoinCondition)condition;
+ String ancestorSelectorName =
joinCondition.getAncestorSelectorName().getName();
+ if (left.getColumns().hasSelector(ancestorSelectorName)) {
+ // The ancestor is on the left ...
+ return new Joinable() {
+ public boolean evaluate( Object ancestorLocation,
+ Object descendantLocation ) {
+ Path ancestorPath = ((Location)ancestorLocation).getPath();
+ Path descendantPath = ((Location)descendantLocation).getPath();
+ return ancestorPath.isAncestorOf(descendantPath);
+ }
+ };
+ }
+ // The ancestor is on the right ...
+ return new Joinable() {
+ public boolean evaluate( Object descendantLocation,
+ Object ancestorLocation ) {
+ Path ancestorPath = ((Location)ancestorLocation).getPath();
+ Path descendantPath = ((Location)descendantLocation).getPath();
+ return ancestorPath.isAncestorOf(descendantPath);
+ }
+ };
+ }
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Create a {@link Comparable} that can be used to compare the values required to
evaluate the supplied join condition.
+ *
+ * @param context the context in which this query is being evaluated; may not be
null
+ * @param left the left source component; may not be null
+ * @param right the left source component; may not be null
+ * @param condition the join condition; may not be null
+ * @return the comparator; never null
+ */
+ @SuppressWarnings( "unchecked" )
+ protected static Comparator<Object> comparatorFor( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ JoinCondition condition ) {
+ final Comparator<Path> pathComparator = ValueComparators.PATH_COMPARATOR;
+ if (condition instanceof SameNodeJoinCondition) {
+ return new Comparator<Object>() {
+ public int compare( Object location1,
+ Object location2 ) {
+ Path path1 = ((Location)location1).getPath();
+ Path path2 = ((Location)location2).getPath();
+ return pathComparator.compare(path1, path2);
+ }
+ };
+ }
+ if (condition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition joinCondition = (ChildNodeJoinCondition)condition;
+ String childSelectorName = joinCondition.getChildSelectorName().getName();
+ if (left.getColumns().hasSelector(childSelectorName)) {
+ // The child is on the left ...
+ return new Comparator<Object>() {
+ public int compare( Object childLocation,
+ Object parentLocation ) {
+ Path childPath = ((Location)childLocation).getPath();
+ Path parentPath = ((Location)parentLocation).getPath();
+ if (childPath.isRoot()) return parentPath.isRoot() ? 0 : -1;
+ Path parentOfChild = childPath.getParent();
+ return pathComparator.compare(parentPath, parentOfChild);
+ }
+ };
+ }
+ // The child is on the right ...
+ return new Comparator<Object>() {
+ public int compare( Object parentLocation,
+ Object childLocation ) {
+ Path childPath = ((Location)childLocation).getPath();
+ Path parentPath = ((Location)parentLocation).getPath();
+ if (childPath.isRoot()) return parentPath.isRoot() ? 0 : -1;
+ Path parentOfChild = childPath.getParent();
+ return pathComparator.compare(parentPath, parentOfChild);
+ }
+ };
+ }
+ if (condition instanceof EquiJoinCondition) {
+ EquiJoinCondition joinCondition = (EquiJoinCondition)condition;
+ SelectorName leftSelectorName = joinCondition.getSelector1Name();
+ SelectorName rightSelectorName = joinCondition.getSelector2Name();
+ Name leftPropertyName = joinCondition.getProperty1Name();
+ Name rightPropertyName = joinCondition.getProperty2Name();
+
+ ValueFactory<String> stringFactory =
context.getExecutionContext().getValueFactories().getStringFactory();
+ Schemata schemata = context.getSchemata();
+ Schemata.Table leftTable = schemata.getTable(leftSelectorName);
+ Schemata.Column leftColumn =
leftTable.getColumn(stringFactory.create(leftPropertyName));
+ PropertyType leftType = leftColumn.getPropertyType();
+
+ Schemata.Table rightTable = schemata.getTable(rightSelectorName);
+ Schemata.Column rightColumn =
rightTable.getColumn(stringFactory.create(rightPropertyName));
+ PropertyType rightType = rightColumn.getPropertyType();
+
+ return leftType == rightType ?
(Comparator<Object>)leftType.getComparator() : ValueComparators.OBJECT_COMPARATOR;
+ }
+ throw new IllegalArgumentException();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/JoinComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/LimitComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/LimitComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/LimitComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,72 @@
+/*
+ * 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.process;
+
+import java.util.List;
+import org.jboss.dna.graph.query.model.Limit;
+
+/**
+ */
+public class LimitComponent extends DelegatingComponent {
+
+ private final Limit limit;
+
+ public LimitComponent( ProcessingComponent delegate,
+ Limit limit ) {
+ super(delegate);
+ this.limit = limit;
+ assert this.limit != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ if (limit.isOffset()) {
+ if (limit.getOffset() >= tuples.size()) {
+ // There aren't enough results, so return an empty list ...
+ return emptyTuples();
+ }
+ if (limit.isUnlimited()) {
+ // An offset, but no row limit ...
+ tuples = tuples.subList(limit.getOffset(), tuples.size());
+ } else {
+ // Both an offset AND a row limit (which may be more than the number of
rows available)...
+ int toIndex = Math.min(limit.getOffset() + limit.getRowLimit(),
tuples.size());
+ tuples = tuples.subList(limit.getOffset(), toIndex);
+ }
+ } else {
+ // No offset, but perhaps there's a row limit ...
+ if (!limit.isUnlimited()) {
+ int toIndex = Math.min(limit.getRowLimit(), tuples.size());
+ tuples = tuples.subList(0, toIndex);
+ }
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/LimitComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/MergeJoinComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/MergeJoinComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/MergeJoinComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,185 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+
+/**
+ * Create a processing component that performs a merge-join algorithm. This algorithm
only makes sense for
+ * {@link EquiJoinCondition equi-joins}, {@link ChildNodeJoinCondition child-node joins},
and {@link SameNodeJoinCondition
+ * same-node joins}.
+ * <p>
+ * <b> Note that it is required that the left and right processing components
(where this component gets its results) must already
+ * be properly sorted and have had duplicates removed. </b>
+ * </p>
+ */
+@Immutable
+public class MergeJoinComponent extends JoinComponent {
+
+ public MergeJoinComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ EquiJoinCondition condition,
+ JoinType joinType ) {
+ super(context, left, right, condition, joinType);
+ }
+
+ public MergeJoinComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ ChildNodeJoinCondition condition,
+ JoinType joinType ) {
+ super(context, left, right, condition, joinType);
+ }
+
+ public MergeJoinComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ SameNodeJoinCondition condition,
+ JoinType joinType ) {
+ super(context, left, right, condition, joinType);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ // Construct the necessary components ...
+ final Columns leftColumns = left().getColumns();
+ final Columns rightColumns = right().getColumns();
+ final ValueSelector leftSelector = valueSelectorFor(left(), getJoinCondition());
+ final ValueSelector rightSelector = valueSelectorFor(right(),
getJoinCondition());
+ final Comparator<Object> comparator = comparatorFor(getContext(), left(),
right(), getJoinCondition());
+ final TupleMerger merger = createMerger(getColumns(), leftColumns,
rightColumns);
+
+ // Walk through the left and right results ...
+ List<Object[]> leftTuples = left().execute();
+ List<Object[]> rightTuples = right().execute();
+ List<Object[]> tuples = new ArrayList<Object[]>(leftTuples.size() *
rightTuples.size());
+ Iterator<Object[]> leftIter = leftTuples.iterator();
+ Iterator<Object[]> rightIter = rightTuples.iterator();
+ Object[] leftTuple = leftIter.next();
+ Object[] rightTuple = rightIter.next();
+ Object[] nextLeftTuple = null;
+ Object[] nextRightTuple = null;
+ while (true) {
+ // Get the value from the left and right side ...
+ Object leftValue = leftSelector.evaluate(leftTuple);
+ Object rightValue = rightSelector.evaluate(rightTuple);
+ // Determine if the tuples should be joined ...
+ int compare = comparator.compare(leftValue, rightValue);
+ while (compare == 0) {
+ Object[] result = merger.merge(leftTuple, rightTuple);
+ tuples.add(result);
+
+ // Peek at the next right tuple, but skip any duplicates ...
+ if (nextRightTuple == null) {
+ nextRightTuple = rightIter.next();
+ while (isSameTuple(rightColumns, nextRightTuple, rightTuple)) {
+ nextRightTuple = rightIter.next();
+ }
+ }
+
+ // Compare the leftTuple with the nextRightTuple ...
+ leftValue = leftSelector.evaluate(leftTuple);
+ rightValue = rightSelector.evaluate(nextRightTuple);
+ compare = comparator.compare(leftValue, rightValue);
+ if (compare == 0) {
+ // This is a good match, update the variables and repeat ...
+ rightTuple = nextRightTuple;
+ nextRightTuple = null;
+ continue;
+ }
+ if (compare > 0) {
+ // The rightTuple is smaller than the left, so we need to increment
the right again ...
+ rightTuple = nextRightTuple;
+ nextRightTuple = null;
+ compare = 0; // to prevent iteration below ...
+ break;
+ }
+
+ // Otherwise, the leftTuple didn't work with the nextRightTuple,
+ // so try the nextLeftTuple with the rightTuple ...
+ nextLeftTuple = leftIter.next();
+ while (isSameTuple(leftColumns, nextLeftTuple, leftTuple)) {
+ nextLeftTuple = leftIter.next();
+ }
+ leftValue = leftSelector.evaluate(nextLeftTuple);
+ rightValue = rightSelector.evaluate(rightTuple);
+ compare = comparator.compare(leftValue, rightValue);
+ if (compare == 0) {
+ // This is a good match, so update the variables and repeat ...
+ leftTuple = nextLeftTuple;
+ nextLeftTuple = null;
+ continue;
+ }
+
+ // Otherwise, neither was a good match, so advance both ...
+ leftTuple = nextLeftTuple;
+ rightTuple = nextRightTuple;
+ nextLeftTuple = null;
+ nextRightTuple = null;
+ compare = 0; // to prevent iteration below ...
+ break;
+ }
+ // There wasn't a match ...
+ if (compare < 0) {
+ // The leftValue is smaller than the right, so we need to increment the
left ...
+ if (!leftIter.hasNext()) break;
+ leftTuple = leftIter.next();
+ }
+ if (compare > 0) {
+ // The rightValue is smaller than the left, so we need to increment the
right ...
+ if (!rightIter.hasNext()) break;
+ rightTuple = rightIter.next();
+ }
+ }
+ return tuples;
+ }
+
+ protected final boolean isSameTuple( Columns columns,
+ Object[] tuple1,
+ Object[] tuple2 ) {
+ for (int i = columns.getColumnCount(); i != columns.getLocationCount(); ++i) {
+ Location location = (Location)tuple1[i];
+ Location location2 = (Location)tuple2[i];
+ if (!location.equals(location2)) return false;
+ }
+ return true;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/MergeJoinComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NestedLoopJoinComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NestedLoopJoinComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NestedLoopJoinComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,76 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+
+/**
+ *
+ */
+public class NestedLoopJoinComponent extends JoinComponent {
+
+ public NestedLoopJoinComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ JoinCondition condition,
+ JoinType joinType ) {
+ super(context, left, right, condition, joinType);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ // Construct the necessary components ...
+ final ValueSelector leftSelector = valueSelectorFor(left(), getJoinCondition());
+ final ValueSelector rightSelector = valueSelectorFor(right(),
getJoinCondition());
+ final Joinable joinable = joinableFor(left(), right(), getJoinCondition());
+ final TupleMerger merger = createMerger(getColumns(), left().getColumns(),
right().getColumns());
+
+ // Walk through the left and right results ...
+ List<Object[]> leftTuples = left().execute();
+ List<Object[]> rightTuples = right().execute();
+ List<Object[]> tuples = new ArrayList<Object[]>(leftTuples.size() *
rightTuples.size());
+ for (Object[] leftTuple : leftTuples) {
+ for (Object[] rightTuple : rightTuples) {
+ // Get the value from the left and right side ...
+ Object leftValue = leftSelector.evaluate(leftTuple);
+ Object rightValue = rightSelector.evaluate(rightTuple);
+ // Determine if the tuples should be joined ...
+ if (joinable.evaluate(leftValue, rightValue)) {
+ Object[] result = merger.merge(leftTuple, rightTuple);
+ tuples.add(result);
+ }
+ }
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NestedLoopJoinComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NoResultsComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NoResultsComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NoResultsComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,49 @@
+/*
+ * 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.process;
+
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public class NoResultsComponent extends ProcessingComponent {
+
+ public NoResultsComponent( QueryContext context,
+ Columns columns ) {
+ super(context, columns);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ return emptyTuples();
+
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/NoResultsComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,319 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+
+/**
+ * A component that performs (some) portion of the query processing by {@link #execute()
returning the tuples} that result from
+ * this stage of processing. Processing components are designed to be assembled into a
processing structure, with a single
+ * component at the top that returns the results of a query.
+ */
+@NotThreadSafe
+public abstract class ProcessingComponent {
+
+ private final QueryContext context;
+ private final Columns columns;
+
+ protected ProcessingComponent( QueryContext context,
+ Columns columns ) {
+ this.context = context;
+ this.columns = columns;
+ assert this.context != null;
+ assert this.columns != null;
+ }
+
+ /**
+ * Get the context in which this query is being executed.
+ *
+ * @return context
+ */
+ public final QueryContext getContext() {
+ return context;
+ }
+
+ /**
+ * Get the column definitions.
+ *
+ * @return the column mappings; never null
+ */
+ public final Columns getColumns() {
+ return columns;
+ }
+
+ /**
+ * Get the container for problems encountered during processing.
+ *
+ * @return the problems container; never null
+ */
+ protected final Problems problems() {
+ return context.getProblems();
+ }
+
+ /**
+ * Execute this stage of processing and return the resulting tuples that each conform
to the {@link #getColumns() columns}.
+ *
+ * @return the list of tuples, where each tuple corresonds to the {@link
#getColumns() columns}; never null
+ */
+ public abstract List<Object[]> execute();
+
+ /**
+ * Close these results, allowing any resources to be released.
+ */
+ public void close() {
+ }
+
+ /**
+ * Utility method to create a new tuples list that is empty.
+ *
+ * @return the empty tuples list; never null
+ */
+ protected List<Object[]> emptyTuples() {
+ return new ArrayList<Object[]>(0);
+ }
+
+ /**
+ * Interface for evaluating a {@link DynamicOperand} to return the resulting value.
+ */
+ protected static interface DynamicOperation {
+ /**
+ * Get the expected type of the result of this evaluation
+ *
+ * @return the property type; never null
+ */
+ PropertyType getExpectedType();
+
+ /**
+ * Perform the dynamic evaluation to obtain the desired result.
+ *
+ * @param tuple the tuple; never null
+ * @return the value that results from dynamically evaluating the operand against
the tuple; may be null
+ */
+ Object evaluate( Object[] tuple );
+ }
+
+ /**
+ * Create a {@link DynamicOperation} instance that is able to evaluate the supplied
{@link DynamicOperand}.
+ *
+ * @param context the context in which the query is being evaluated; may not be null
+ * @param columns the definition of the result columns and the tuples; may not be
null
+ * @param operand the dynamic operand that is to be evaluated by the returned object;
may not be null
+ * @return the dynamic operand operation; never null
+ */
+ protected DynamicOperation createDynamicOperation( QueryContext context,
+ Columns columns,
+ DynamicOperand operand ) {
+ assert operand != null;
+ assert columns != null;
+ assert context != null;
+ final ValueFactory<String> stringFactory =
context.getExecutionContext().getValueFactories().getStringFactory();
+ if (operand instanceof PropertyValue) {
+ PropertyValue propValue = (PropertyValue)operand;
+ Name propertyName = propValue.getPropertyName();
+ String selectorName = propValue.getSelectorName().getName();
+ final int index = columns.getColumnIndexForProperty(selectorName,
propertyName);
+ // Find the expected property type of the value ...
+ Table table = context.getSchemata().getTable(propValue.getSelectorName());
+ Column schemaColumn = table.getColumn(stringFactory.create(propertyName));
+ final PropertyType expectedType = schemaColumn.getPropertyType();
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return expectedType;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ return tuple[index];
+ }
+ };
+ }
+ if (operand instanceof Length) {
+ Length length = (Length)operand;
+ PropertyValue value = length.getPropertyValue();
+ Name propertyName = value.getPropertyName();
+ String selectorName = value.getSelectorName().getName();
+ final int index = columns.getColumnIndexForProperty(selectorName,
propertyName);
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.LONG; // length is always LONG
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Object value = tuple[index];
+ // Determine the length ...
+ if (value instanceof Binary) {
+ return ((Binary)value).getStream();
+ }
+ String result = stringFactory.create(value);
+ return result != null ? result.length() : null;
+ }
+ };
+ }
+ if (operand instanceof LowerCase) {
+ LowerCase lowerCase = (LowerCase)operand;
+ final DynamicOperation delegate = createDynamicOperation(context, columns,
lowerCase.getOperand());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.STRING;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ String result = stringFactory.create(delegate.evaluate(tuple));
+ return result != null ? result.toLowerCase() : null;
+ }
+ };
+ }
+ if (operand instanceof UpperCase) {
+ UpperCase upperCase = (UpperCase)operand;
+ final DynamicOperation delegate = createDynamicOperation(context, columns,
upperCase.getOperand());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.STRING;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ String result = stringFactory.create(delegate.evaluate(tuple));
+ return result != null ? result.toUpperCase() : null;
+ }
+ };
+ }
+ if (operand instanceof NodeName) {
+ NodeName nodeName = (NodeName)operand;
+ final int locationIndex =
columns.getLocationIndex(nodeName.getSelectorName().getName());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.STRING;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return null;
+ Path path = location.getPath();
+ assert path != null;
+ return path.isRoot() ? "" :
stringFactory.create(location.getPath().getLastSegment().getName());
+ }
+ };
+ }
+ if (operand instanceof NodeLocalName) {
+ NodeLocalName nodeName = (NodeLocalName)operand;
+ final int locationIndex =
columns.getLocationIndex(nodeName.getSelectorName().getName());
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.STRING;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return null;
+ Path path = location.getPath();
+ assert path != null;
+ return path.isRoot() ? "" :
location.getPath().getLastSegment().getName().getLocalName();
+ }
+ };
+ }
+ if (operand instanceof FullTextSearchScore) {
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ String selectorName = score.getSelectorName().getName();
+ final int index = columns.getFullTextSearchScoreIndexFor(selectorName);
+ if (index < 0) {
+ // No full-text search score for this selector, so return 0.0d;
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.DOUBLE;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ return new Double(0.0d);
+ }
+ };
+ }
+ return new DynamicOperation() {
+ public PropertyType getExpectedType() {
+ return PropertyType.DOUBLE;
+ }
+
+ public Object evaluate( Object[] tuple ) {
+ return tuple[index];
+ }
+ };
+ }
+ assert false;
+ return null;
+ }
+
+ protected Comparator<Object[]> createSortComparator( QueryContext context,
+ Columns columns ) {
+ assert context != null;
+ final int numLocations = columns.getLocationCount();
+ assert numLocations > 0;
+ final Comparator<Location> typeComparator = Location.comparator();
+ if (numLocations == 1) {
+ // We can do this a tad faster if we know there is only one Location object
...
+ final int locationIndex = columns.getColumnCount();
+ return new Comparator<Object[]>() {
+ public int compare( Object[] tuple1,
+ Object[] tuple2 ) {
+ Location value1 = (Location)tuple1[locationIndex];
+ Location value2 = (Location)tuple2[locationIndex];
+ return typeComparator.compare(value1, value2);
+ }
+ };
+ }
+ final int firstLocationIndex = columns.getColumnCount();
+ return new Comparator<Object[]>() {
+ public int compare( Object[] tuple1,
+ Object[] tuple2 ) {
+ int result = 0;
+ for (int locationIndex = firstLocationIndex; locationIndex !=
numLocations; ++locationIndex) {
+ Location value1 = (Location)tuple1[locationIndex];
+ Location value2 = (Location)tuple2[locationIndex];
+ result = typeComparator.compare(value1, value2);
+ if (result != 0) return result;
+ }
+ return result;
+ }
+ };
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProcessingComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/Processor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/Processor.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/Processor.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,50 @@
+/*
+ * 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.process;
+
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.QueryResults.Statistics;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.plan.PlanNode;
+
+/**
+ * Interface for a query processor.
+ */
+public interface Processor {
+
+ /**
+ * Process the supplied query plan for the given command and return the results.
+ *
+ * @param context the context in which the command is being processed
+ * @param command the command being executed
+ * @param statistics the time metrics up until this execution
+ * @param plan the plan to be processed
+ * @return the results of the query
+ */
+ QueryResults execute( QueryContext context,
+ QueryCommand command,
+ Statistics statistics,
+ PlanNode plan );
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/Processor.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProjectComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProjectComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProjectComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,50 @@
+/*
+ * 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.process;
+
+import java.util.List;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * A {@link ProcessingComponent} implementation that performs a {@link Type#PROJECT
PROJECT} operation to reduce the columns that
+ * appear in the results.
+ */
+public class ProjectComponent extends DelegatingComponent {
+
+ public ProjectComponent( ProcessingComponent delegate,
+ List<Column> columns ) {
+ super(delegate, delegate.getColumns().subSelect(columns));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ return delegate().execute();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ProjectComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,273 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.QueryResults.Statistics;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Limit;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SetQuery.Operation;
+import org.jboss.dna.graph.query.plan.JoinAlgorithm;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.process.SelectComponent.Analyzer;
+
+/**
+ * An abstract {@link Processor} implementation that builds a tree of {@link
ProcessingComponent} objects to perform the different
+ * parts of the query processing logic. Subclasses are required to only implement one
method: the
+ * {@link #createAccessComponent(QueryContext, PlanNode, Columns, Analyzer)} should
create a ProcessorComponent object that will
+ * perform the (low-level access) query described by the {@link PlanNode plan} given as a
parameter.
+ */
+public abstract class QueryProcessor implements Processor {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.Processor#execute(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.model.QueryCommand,
org.jboss.dna.graph.query.QueryResults.Statistics,
+ * org.jboss.dna.graph.query.plan.PlanNode)
+ */
+ public QueryResults execute( QueryContext context,
+ QueryCommand command,
+ Statistics statistics,
+ PlanNode plan ) {
+ long nanos = System.nanoTime();
+ Columns columns = null;
+ List<Object[]> tuples = null;
+ try {
+ // Find the topmost PROJECT node and build the Columns ...
+ PlanNode project = plan.findAtOrBelow(Type.PROJECT);
+ assert project != null;
+ List<Column> projectedColumns =
project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ assert projectedColumns != null;
+ assert !projectedColumns.isEmpty();
+ columns = new QueryResultColumns(projectedColumns,
context.getHints().hasFullTextSearch);
+
+ // Go through the plan and create the corresponding ProcessingComponents ...
+ Analyzer analyzer = createAnalyzer(context);
+ ProcessingComponent component = createComponent(context, plan, columns,
analyzer);
+ long nanos2 = System.nanoTime();
+ statistics = statistics.withResultsFormulationTime(nanos2 - nanos);
+
+ // Now execute the component ...
+ nanos = nanos2;
+ tuples = component.execute();
+
+ } finally {
+ statistics = statistics.withExecutionTime(System.nanoTime() - nanos);
+ }
+ if (tuples == null) tuples = Collections.emptyList();
+ return new org.jboss.dna.graph.query.process.QueryResults(context, command,
columns, statistics, tuples);
+ }
+
+ /**
+ * Create an {@link Analyzer} implementation that should be used by the non-access
{@link ProcessingComponent}s that evaluate
+ * criteria. By default, this method returns null, which means that any criteria
evaluation will likely be pushed down under
+ * an {@link Type#ACCESS ACCESS} node (and thus handled by an
+ * {@link #createAccessComponent(QueryContext, PlanNode, Columns, Analyzer) access
component}.
+ * <p>
+ * However, for more simple access components that are not capable of handling joins
and other non-trivial criteria, simply
+ * return an Analyzer implementation that implements the methods using the source.
+ * </p>
+ *
+ * @param context the context in which query is being evaluated
+ * @return the analyzer, or null if the ProcessingComponent objects that evaluate
criteria should use a best-effort approach
+ */
+ protected Analyzer createAnalyzer( QueryContext context ) {
+ return null;
+ }
+
+ protected abstract ProcessingComponent createAccessComponent( QueryContext context,
+ PlanNode accessNode,
+ Columns resultColumns,
+ Analyzer analyzer );
+
+ /**
+ * Method that is called to build up the {@link ProcessingComponent} objects that
correspond to the optimized query plan. This
+ * method is called by {@link #execute(QueryContext, QueryCommand, Statistics,
PlanNode)} for each of the various
+ * {@link PlanNode} objects in the optimized query plan, and the method is actually
recursive (since the optimized query plan
+ * forms a tree). However, whenever this call structure reaches the {@link
Type#ACCESS ACCESS} nodes in the query plan (which
+ * each represents a separate atomic low-level query to the underlying system), the
+ * {@link #createAccessComponent(QueryContext, PlanNode, Columns, Analyzer)} method
is called. Subclasses should create an
+ * appropriate ProcessingComponent implementation that performs this atomic low-level
query.
+ *
+ * @param context the context in which query is being evaluated
+ * @param node the plan node for which the ProcessingComponent is to be created
+ * @param columns the definition of the result columns for this portion of the query
+ * @param analyzer the analyzer (returned from {@link #createAnalyzer(QueryContext)})
that should be used on the components
+ * that evaluate criteria; may be null if a best-effort should be made for the
evaluation
+ * @return the processing component for this plan node; never null
+ */
+ protected ProcessingComponent createComponent( QueryContext context,
+ PlanNode node,
+ Columns columns,
+ Analyzer analyzer ) {
+ ProcessingComponent component = null;
+ switch (node.getType()) {
+ case ACCESS:
+ // Create the component under the ACCESS ...
+ assert node.getChildCount() == 1;
+ // Don't do anything special with an access node at the moment ...
+ component = createComponent(context, node.getFirstChild(), columns,
analyzer);
+ break;
+ case DUP_REMOVE:
+ // Create the component under the DUP_REMOVE ...
+ assert node.getChildCount() == 1;
+ ProcessingComponent distinctDelegate = createComponent(context,
node.getFirstChild(), columns, analyzer);
+ component = new DistinctComponent(distinctDelegate);
+ break;
+ case GROUP:
+ throw new UnsupportedOperationException();
+ case JOIN:
+ // Create the components under the JOIN ...
+ assert node.getChildCount() == 2;
+ ProcessingComponent left = createComponent(context, node.getFirstChild(),
columns, analyzer);
+ ProcessingComponent right = createComponent(context, node.getLastChild(),
columns, analyzer);
+ // Create the join component ...
+ JoinAlgorithm algorithm = node.getProperty(Property.JOIN_ALGORITHM,
JoinAlgorithm.class);
+ JoinType joinType = node.getProperty(Property.JOIN_TYPE,
JoinType.class);
+ JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION,
JoinCondition.class);
+ switch (algorithm) {
+ case MERGE:
+ if (joinCondition instanceof SameNodeJoinCondition) {
+ SameNodeJoinCondition condition =
(SameNodeJoinCondition)joinCondition;
+ component = new MergeJoinComponent(context, left, right,
condition, joinType);
+ } else if (joinCondition instanceof ChildNodeJoinCondition) {
+ ChildNodeJoinCondition condition =
(ChildNodeJoinCondition)joinCondition;
+ component = new MergeJoinComponent(context, left, right,
condition, joinType);
+ } else if (joinCondition instanceof EquiJoinCondition) {
+ EquiJoinCondition condition =
(EquiJoinCondition)joinCondition;
+ component = new MergeJoinComponent(context, left, right,
condition, joinType);
+ } else {
+ assert false : "Unable to use merge algorithm with
descendant node join conditions";
+ throw new UnsupportedOperationException();
+ }
+ break;
+ case NESTED_LOOP:
+ component = new NestedLoopJoinComponent(context, left, right,
joinCondition, joinType);
+ break;
+ }
+ // For each Constraint object applied to the JOIN, simply create a
SelectComponent on top ...
+ List<Constraint> constraints =
node.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
+ for (Constraint constraint : constraints) {
+ component = new SelectComponent(component, constraint,
context.getVariables());
+ }
+ break;
+ case LIMIT:
+ // Create the component under the LIMIT ...
+ assert node.getChildCount() == 1;
+ ProcessingComponent limitDelegate = createComponent(context,
node.getFirstChild(), columns, analyzer);
+ // Then create the limit component ...
+ Integer rowLimit = node.getProperty(Property.LIMIT_COUNT,
Integer.class);
+ Integer offset = node.getProperty(Property.LIMIT_OFFSET, Integer.class);
+ Limit limit = Limit.NONE;
+ if (rowLimit != null) limit = limit.withRowLimit(rowLimit.intValue());
+ if (offset != null) limit = limit.withOffset(offset.intValue());
+ component = new LimitComponent(limitDelegate, limit);
+ break;
+ case NULL:
+ component = new NoResultsComponent(context, columns);
+ break;
+ case PROJECT:
+ // Create the component under the PROJECT ...
+ assert node.getChildCount() == 1;
+ ProcessingComponent projectDelegate = createComponent(context,
node.getFirstChild(), columns, analyzer);
+ // Then create the project component ...
+ List<Column> projectedColumns =
node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+ component = new ProjectComponent(projectDelegate, projectedColumns);
+ break;
+ case SELECT:
+ // Create the component under the SELECT ...
+ assert node.getChildCount() == 1;
+ ProcessingComponent selectDelegate = createComponent(context,
node.getFirstChild(), columns, analyzer);
+ // Then create the select component ...
+ Constraint constraint = node.getProperty(Property.SELECT_CRITERIA,
Constraint.class);
+ component = new SelectComponent(selectDelegate, constraint,
context.getVariables(), analyzer);
+ break;
+ case SET_OPERATION:
+ // Create the components under the SET_OPERATION ...
+ List<ProcessingComponent> setDelegates = new
LinkedList<ProcessingComponent>();
+ for (PlanNode child : node) {
+ setDelegates.add(createComponent(context, child, columns,
analyzer));
+ }
+ // Then create the select component ...
+ Operation operation = node.getProperty(Property.SET_OPERATION,
Operation.class);
+ boolean all = node.getProperty(Property.SET_USE_ALL, Boolean.class);
+ boolean alreadySorted = false; // ????
+ switch (operation) {
+ case EXCEPT:
+ component = new ExceptComponent(context, columns, setDelegates,
alreadySorted, all);
+ break;
+ case INTERSECT:
+ component = new IntersectComponent(context, columns,
setDelegates, alreadySorted, all);
+ break;
+ case UNION:
+ component = new UnionComponent(context, columns, setDelegates,
alreadySorted, all);
+ break;
+ }
+ break;
+ case SORT:
+ // Create the component under the SORT ...
+ assert node.getChildCount() == 1;
+ ProcessingComponent sortDelegate = createComponent(context,
node.getFirstChild(), columns, analyzer);
+ // Then create the sort component ...
+ List<Object> orderBys =
node.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
+ if (orderBys.isEmpty()) {
+ component = sortDelegate;
+ } else {
+ if (orderBys.get(0) instanceof Ordering) {
+ List<Ordering> orderings = new
ArrayList<Ordering>(orderBys.size());
+ for (Object orderBy : orderBys) {
+ orderings.add((Ordering)orderBy);
+ }
+ component = new SortValuesComponent(sortDelegate, orderings);
+ } else {
+ // Order by the location(s) because it's before a merge-join
...
+ component = new SortLocationsComponent(sortDelegate);
+ }
+ }
+ break;
+ case SOURCE:
+ assert false : "Source nodes should always be below ACCESS nodes by
the time a plan is executed";
+ throw new UnsupportedOperationException();
+ }
+ assert component != null;
+ return component;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryProcessor.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResultColumns.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResultColumns.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResultColumns.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,475 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Column;
+
+/**
+ * Defines the columns associated with the results of a query. This definition allows the
values to be accessed
+ */
+@Immutable
+public final class QueryResultColumns implements Columns {
+
+ private static final QueryResultColumns EMPTY = new
QueryResultColumns(Collections.<Column>emptyList(), false);
+
+ /**
+ * Get an empty results column definition.
+ *
+ * @return the empty columns definition; never null
+ */
+ public static QueryResultColumns empty() {
+ return EMPTY;
+ }
+
+ private final int tupleSize;
+ private final List<Column> columns;
+ private final List<String> columnNames;
+ private final List<String> selectorNames;
+ private List<String> tupleValueNames;
+ private final Map<String, Column> columnsByName;
+ private final Map<String, Integer> columnIndexByColumnName;
+ private final Map<String, Integer> locationIndexBySelectorName;
+ private final Map<String, Integer> locationIndexByColumnName;
+ private final Map<Integer, Integer> locationIndexByColumnIndex;
+ private final Map<String, Map<Name, Integer>>
columnIndexByPropertyNameBySelectorName;
+ private final Map<String, Integer> fullTextSearchScoreIndexBySelectorName;
+
+ /**
+ * Create a new definition for the query results given the supplied columns.
+ *
+ * @param columns the columns that define the results; should never be modified
directly
+ * @param includeFullTextSearchScores true if room should be made in the tuples for
the full-text search scores for each
+ * {@link Location}, or false otherwise
+ */
+ public QueryResultColumns( List<Column> columns,
+ boolean includeFullTextSearchScores ) {
+ assert columns != null;
+ this.columns = Collections.unmodifiableList(columns);
+ this.columnsByName = new HashMap<String, Column>();
+ this.columnIndexByColumnName = new HashMap<String, Integer>();
+ Set<String> selectors = new HashSet<String>();
+ final int columnCount = columns.size();
+ Integer selectorIndex = new Integer(columnCount - 1);
+ this.locationIndexBySelectorName = new HashMap<String, Integer>();
+ this.locationIndexByColumnIndex = new HashMap<Integer, Integer>();
+ this.locationIndexByColumnName = new HashMap<String, Integer>();
+ this.columnIndexByPropertyNameBySelectorName = new HashMap<String,
Map<Name, Integer>>();
+ List<String> selectorNames = new ArrayList<String>(columnCount);
+ List<String> names = new ArrayList<String>(columnCount);
+ for (int i = 0, max = this.columns.size(); i != max; ++i) {
+ Column column = this.columns.get(i);
+ assert column != null;
+ String columnName = column.getColumnName();
+ assert columnName != null;
+ if (columnsByName.put(columnName, column) != null) {
+ assert false : "Column names must be unique";
+ }
+ names.add(columnName);
+ columnIndexByColumnName.put(columnName, new Integer(i));
+ String selectorName = column.getSelectorName().getName();
+ if (selectors.add(selectorName)) {
+ selectorNames.add(selectorName);
+ selectorIndex = new Integer(selectorIndex.intValue() + 1);
+ locationIndexBySelectorName.put(selectorName, selectorIndex);
+ }
+ locationIndexByColumnIndex.put(new Integer(i), selectorIndex);
+ locationIndexByColumnName.put(columnName, selectorIndex);
+ // Insert the entry by selector name and property name ...
+ Map<Name, Integer> byPropertyName =
columnIndexByPropertyNameBySelectorName.get(selectorName);
+ if (byPropertyName == null) {
+ byPropertyName = new HashMap<Name, Integer>();
+ columnIndexByPropertyNameBySelectorName.put(selectorName,
byPropertyName);
+ }
+ byPropertyName.put(column.getPropertyName(), new Integer(i));
+ }
+ this.selectorNames = Collections.unmodifiableList(selectorNames);
+ this.columnNames = Collections.unmodifiableList(names);
+ if (includeFullTextSearchScores) {
+ this.fullTextSearchScoreIndexBySelectorName = new HashMap<String,
Integer>();
+ int index = columnNames.size() + selectorNames.size();
+ for (String selectorName : selectorNames) {
+ fullTextSearchScoreIndexBySelectorName.put(selectorName, new
Integer(index++));
+ }
+ this.tupleSize = columnNames.size() + selectorNames.size() +
selectorNames.size();
+ } else {
+ this.fullTextSearchScoreIndexBySelectorName = null;
+ this.tupleSize = columnNames.size() + selectorNames.size();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#subSelect(java.util.List)
+ */
+ public Columns subSelect( List<Column> columns ) {
+ return new QueryResultColumns(columns, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#subSelect(org.jboss.dna.graph.query.model.Column[])
+ */
+ public Columns subSelect( Column... columns ) {
+ return new QueryResultColumns(Arrays.asList(columns), this);
+ }
+
+ private QueryResultColumns( List<Column> columns,
+ QueryResultColumns wrappedAround ) {
+ assert columns != null;
+ this.columns = Collections.unmodifiableList(columns);
+ this.columnsByName = new HashMap<String, Column>();
+ this.columnIndexByColumnName = new HashMap<String, Integer>();
+ this.locationIndexBySelectorName = new HashMap<String, Integer>();
+ this.locationIndexByColumnIndex = new HashMap<Integer, Integer>();
+ this.locationIndexByColumnName = new HashMap<String, Integer>();
+ this.columnIndexByPropertyNameBySelectorName = new HashMap<String,
Map<Name, Integer>>();
+ this.selectorNames = new ArrayList<String>(columns.size());
+ List<String> names = new ArrayList<String>(columns.size());
+ for (int i = 0, max = this.columns.size(); i != max; ++i) {
+ Column column = this.columns.get(i);
+ assert column != null;
+ String columnName = column.getColumnName();
+ assert columnName != null;
+ if (columnsByName.put(columnName, column) != null) {
+ assert false : "Column names must be unique";
+ }
+ names.add(columnName);
+ Integer columnIndex = new
Integer(wrappedAround.getColumnIndexForName(columnName));
+ columnIndexByColumnName.put(columnName, columnIndex);
+ String selectorName = column.getSelectorName().getName();
+ if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName);
+ Integer selectorIndex = new
Integer(wrappedAround.getLocationIndex(selectorName));
+ locationIndexBySelectorName.put(selectorName, selectorIndex);
+ locationIndexByColumnIndex.put(new Integer(0), selectorIndex);
+ locationIndexByColumnName.put(columnName, selectorIndex);
+ // Insert the entry by selector name and property name ...
+ Map<Name, Integer> byPropertyName =
columnIndexByPropertyNameBySelectorName.get(selectorName);
+ if (byPropertyName == null) {
+ byPropertyName = new HashMap<Name, Integer>();
+ columnIndexByPropertyNameBySelectorName.put(selectorName,
byPropertyName);
+ }
+ byPropertyName.put(column.getPropertyName(), columnIndex);
+ }
+ this.columnNames = Collections.unmodifiableList(names);
+ if (wrappedAround.fullTextSearchScoreIndexBySelectorName != null) {
+ this.fullTextSearchScoreIndexBySelectorName = new HashMap<String,
Integer>();
+ int index = columnNames.size() + selectorNames.size();
+ for (String selectorName : selectorNames) {
+ fullTextSearchScoreIndexBySelectorName.put(selectorName, new
Integer(index++));
+ }
+ this.tupleSize = columnNames.size() + selectorNames.size() +
selectorNames.size();
+ } else {
+ this.fullTextSearchScoreIndexBySelectorName = null;
+ this.tupleSize = columnNames.size() + selectorNames.size();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getColumns()
+ */
+ public List<Column> getColumns() {
+ return columns;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getColumnNames()
+ */
+ public List<String> getColumnNames() {
+ return columnNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getColumnCount()
+ */
+ public int getColumnCount() {
+ return columns.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getLocationCount()
+ */
+ public int getLocationCount() {
+ return selectorNames.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getSelectorNames()
+ */
+ public List<String> getSelectorNames() {
+ return selectorNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getTupleSize()
+ */
+ public int getTupleSize() {
+ return tupleSize;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getTupleValueNames()
+ */
+ public List<String> getTupleValueNames() {
+ if (this.tupleValueNames == null) {
+ // This is idempotent, so no need to lock ...
+ List<String> results = new ArrayList<String>(getTupleSize());
+ // Add the column names ...
+ results.addAll(columnNames);
+ // Add the location names ...
+ for (String selectorName : selectorNames) {
+ String name = "Location(" + selectorName + ")";
+ results.add(name);
+ }
+ // Add the full-text search score names ...
+ if (fullTextSearchScoreIndexBySelectorName != null) {
+ for (String selectorName : selectorNames) {
+ String name = "Score(" + selectorName + ")";
+ results.add(name);
+ }
+ }
+ this.tupleValueNames = results;
+ }
+ return this.tupleValueNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getLocationIndexForColumn(int)
+ */
+ public int getLocationIndexForColumn( int columnIndex ) {
+ Integer result = locationIndexByColumnIndex.get(new Integer(columnIndex));
+ if (result == null) {
+ throw new
IndexOutOfBoundsException(GraphI18n.columnDoesNotExistInQuery.text(columnIndex));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getLocationIndexForColumn(java.lang.String)
+ */
+ public int getLocationIndexForColumn( String columnName ) {
+ Integer result = locationIndexByColumnName.get(columnName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getLocationIndex(java.lang.String)
+ */
+ public int getLocationIndex( String selectorName ) {
+ Integer result = locationIndexBySelectorName.get(selectorName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#hasSelector(java.lang.String)
+ */
+ public boolean hasSelector( String selectorName ) {
+ return locationIndexBySelectorName.containsKey(selectorName);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#getPropertyNameForColumn(int)
+ */
+ public Name getPropertyNameForColumn( int columnIndex ) {
+ return columns.get(columnIndex).getPropertyName();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getPropertyNameForColumn(java.lang.String)
+ */
+ public Name getPropertyNameForColumn( String columnName ) {
+ Column result = columnsByName.get(columnName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
+ }
+ return result.getPropertyName();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getColumnIndexForName(java.lang.String)
+ */
+ public int getColumnIndexForName( String columnName ) {
+ Integer result = columnIndexByColumnName.get(columnName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getColumnIndexForProperty(java.lang.String,
+ * org.jboss.dna.graph.property.Name)
+ */
+ public int getColumnIndexForProperty( String selectorName,
+ Name propertyName ) {
+ Map<Name, Integer> byPropertyName =
columnIndexByPropertyNameBySelectorName.get(selectorName);
+ if (byPropertyName == null) {
+ throw new
NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
+ }
+ Integer result = byPropertyName.get(propertyName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.propertyOnSelectorIsNotUsedInQuery.text(propertyName,
selectorName));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#getFullTextSearchScoreIndexFor(java.lang.String)
+ */
+ public int getFullTextSearchScoreIndexFor( String selectorName ) {
+ if (fullTextSearchScoreIndexBySelectorName == null) return -1;
+ Integer result = fullTextSearchScoreIndexBySelectorName.get(selectorName);
+ if (result == null) {
+ throw new
NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
+ }
+ return result.intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Columns#hasFullTextSearchScores()
+ */
+ public boolean hasFullTextSearchScores() {
+ return fullTextSearchScoreIndexBySelectorName != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#includes(org.jboss.dna.graph.query.QueryResults.Columns)
+ */
+ public boolean includes( Columns other ) {
+ if (other == this) return true;
+ if (other == null) return false;
+ return this.getColumns().containsAll(other.getColumns());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Columns#isUnionCompatible(org.jboss.dna.graph.query.QueryResults.Columns)
+ */
+ public boolean isUnionCompatible( Columns other ) {
+ if (this == other) return true;
+ if (other == null) return false;
+ if (this.hasFullTextSearchScores() != other.hasFullTextSearchScores()) return
false;
+ if (this.getColumnCount() != other.getColumnCount()) return false;
+ return this.getColumns().containsAll(other.getColumns()) &&
other.getColumns().containsAll(this.getColumns());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof QueryResultColumns) {
+ QueryResultColumns that = (QueryResultColumns)obj;
+ return this.getColumns().equals(that.getColumns());
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" [");
+ boolean first = true;
+ for (Column column : getColumns()) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(column);
+ }
+ sb.append("] => Locations[");
+ first = true;
+ for (int i = 0, count = getColumnCount(); i != count; ++i) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(getLocationIndexForColumn(i));
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResultColumns.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,439 @@
+/*
+ * 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.process;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
+
+/**
+ * The resulting output of a query.
+ */
+@Immutable
+public class QueryResults implements org.jboss.dna.graph.query.QueryResults {
+
+ private final QueryContext context;
+ private final QueryCommand command;
+ private final Columns columns;
+ private final List<Object[]> tuples;
+ private final Statistics statistics;
+
+ /**
+ * Create a results object for the supplied context, command, and result columns and
with the supplied tuples.
+ *
+ * @param context the context in which the query was executed
+ * @param command the query command
+ * @param columns the definition of the query result columns
+ * @param statistics the statistics for this query; may not be null
+ * @param tuples the tuples
+ */
+ public QueryResults( QueryContext context,
+ QueryCommand command,
+ Columns columns,
+ Statistics statistics,
+ List<Object[]> tuples ) {
+ assert context != null;
+ assert command != null;
+ assert columns != null;
+ assert statistics != null;
+ this.context = context;
+ this.command = command;
+ this.columns = columns;
+ this.tuples = tuples;
+ this.statistics = statistics;
+ }
+
+ /**
+ * Create an empty {@link QueryResults} object for the supplied context, command, and
result columns.
+ *
+ * @param context the context in which the query was executed
+ * @param command the query command
+ * @param columns the definition of the query result columns
+ * @param statistics the statistics for this query; may not be null
+ */
+ public QueryResults( QueryContext context,
+ QueryCommand command,
+ Columns columns,
+ Statistics statistics ) {
+ this(context, command, columns, statistics,
Collections.<Object[]>emptyList());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getContext()
+ */
+ public ExecutionContext getContext() {
+ return context.getExecutionContext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getCommand()
+ */
+ public QueryCommand getCommand() {
+ return command;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getColumns()
+ */
+ public Columns getColumns() {
+ return columns;
+ }
+
+ /**
+ * Get a cursor that can be used to walk through the results.
+ *
+ * @return the cursor; never null, though possibly empty (meaning {@link
Cursor#hasNext()} may return true)
+ */
+ public Cursor getCursor() {
+ return new TupleCursor(columns, tuples.iterator());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getTuples()
+ */
+ public List<Object[]> getTuples() {
+ return tuples;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getProblems()
+ */
+ public Problems getProblems() {
+ return context.getProblems();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#hasErrors()
+ */
+ public boolean hasErrors() {
+ return getProblems().hasErrors();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#hasWarnings()
+ */
+ public boolean hasWarnings() {
+ return getProblems().hasWarnings();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults#getStatistics()
+ */
+ public Statistics getStatistics() {
+ return statistics;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return toString(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Get a string representation of this result object, with a maximum number of tuples
to include.
+ *
+ * @param maxTuples the maximum number of tuples to print, or {@link
Integer#MAX_VALUE} if all the tuples are to be printed
+ * @return the string representation; never null
+ */
+ public String toString( int maxTuples ) {
+ StringBuilder sb = new StringBuilder();
+ toString(sb, maxTuples);
+ return sb.toString();
+ }
+
+ /**
+ * Get a string representation of this result object.
+ *
+ * @param sb the string builder to which the results should be written; may not be
null
+ */
+ public void toString( StringBuilder sb ) {
+ toString(sb, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Get a string representation of this result object, with a maximum number of tuples
to include.
+ *
+ * @param sb the string builder to which the results should be written; may not be
null
+ * @param maxTuples the maximum number of tuples to print, or {@link
Integer#MAX_VALUE} if all the tuples are to be printed
+ */
+ public void toString( StringBuilder sb,
+ int maxTuples ) {
+ ValueFactory<String> stringFactory =
context.getExecutionContext().getValueFactories().getStringFactory();
+ int[] columnWidths = determineColumnWidths(Integer.MAX_VALUE, true,
stringFactory);
+ printDelimiterLine(sb, columnWidths, true);
+ printHeader(sb, columnWidths);
+ printDelimiterLine(sb, columnWidths, true);
+ printLines(sb, columnWidths, stringFactory, maxTuples);
+ printDelimiterLine(sb, columnWidths, false);
+ }
+
+ /**
+ * Determine the width of each column.
+ *
+ * @param maxWidth the maximum width; must be positive
+ * @param useData true if the data should be used to compute the length, or false if
just the column names should be used
+ * @param stringFactory the value factory for creating strings; may not be null
+ * @return the array of widths for each column, excluding any decorating characters;
never null
+ */
+ protected int[] determineColumnWidths( int maxWidth,
+ boolean useData,
+ ValueFactory<String> stringFactory ) {
+ assert maxWidth > 0;
+ int tupleLength = columns.getTupleSize();
+ int[] columnWidths = new int[tupleLength + 1]; // +1 for the row number column
+ for (int i = 0; i != columnWidths.length; ++i) {
+ columnWidths[i] = 0;
+ }
+ // Determine the width of the first column that shows the row number ...
+ String rowNumber = stringFactory.create(getTuples().size());
+ columnWidths[0] = rowNumber.length();
+
+ // Compute the column names ...
+ List<String> tupleValueNames = columns.getTupleValueNames();
+ for (int i = 0, j = 1, max = tupleValueNames.size(); i != max; ++i, ++j) {
+ String name = tupleValueNames.get(i);
+ columnWidths[j] = Math.max(Math.min(maxWidth, name.length()),
columnWidths[j]);
+ }
+ // Look at the data ...
+ if (useData) {
+ for (Object[] tuple : getTuples()) {
+ for (int i = 0, j = 1; i != tupleLength; ++i, ++j) {
+ String valueStr = stringOf(tuple[i], stringFactory);
+ columnWidths[j] = Math.max(Math.min(maxWidth, valueStr.length()),
columnWidths[j]);
+ }
+ }
+ }
+ return columnWidths;
+ }
+
+ protected String stringOf( Object value,
+ ValueFactory<String> stringFactory ) {
+ if (value instanceof Binary) {
+ // Just print out the SHA-1 hash in Base64, plus length
+ Binary binary = (Binary)value;
+ return "(Binary,length=" + binary.getSize() + ",SHA1=" +
Base64.encode(binary.getHash()) + ")";
+ }
+ return stringFactory.create(value);
+ }
+
+ protected void printHeader( StringBuilder sb,
+ int[] columnWidths ) {
+ // Print the row number column ...
+ sb.append("| ").append(StringUtil.justifyLeft("#",
columnWidths[0], ' ')).append(' ');
+ // Print the name line ...
+ sb.append('|');
+ int i = 1;
+ for (String name : columns.getTupleValueNames()) {
+ sb.append(' ');
+ sb.append(StringUtil.justifyLeft(name, columnWidths[i], ' '));
+ sb.append(" |");
+ ++i;
+ }
+ sb.append('\n');
+ }
+
+ protected void printLines( StringBuilder sb,
+ int[] columnWidths,
+ ValueFactory<String> stringFactory,
+ int maxRowsToPrint ) {
+ int rowNumber = 1;
+ int tupleLength = columns.getTupleSize();
+ // Should they all be printed ?
+ if (maxRowsToPrint > tuples.size()) {
+ // Print all tuples ...
+ for (Object[] tuple : getTuples()) {
+ printTuple(sb, columnWidths, stringFactory, rowNumber, tupleLength,
tuple);
+ ++rowNumber;
+ }
+ } else {
+ // Print max number of rows ...
+ for (Object[] tuple : getTuples()) {
+ printTuple(sb, columnWidths, stringFactory, rowNumber, tupleLength,
tuple);
+ if (rowNumber >= maxRowsToPrint) break;
+ ++rowNumber;
+ }
+ }
+
+ }
+
+ /**
+ * @param sb
+ * @param columnWidths
+ * @param stringFactory
+ * @param rowNumber
+ * @param tupleLength
+ * @param tuple
+ */
+ private final void printTuple( StringBuilder sb,
+ int[] columnWidths,
+ ValueFactory<String> stringFactory,
+ int rowNumber,
+ int tupleLength,
+ Object[] tuple ) {
+ // Print the row number column ...
+ sb.append("|
").append(StringUtil.justifyLeft(Integer.toString(rowNumber), columnWidths[0], '
')).append(' ');
+ // Print the remaining columns ...
+ for (int i = 0, j = 1; i != tupleLength; ++i, ++j) {
+ String valueStr = stringOf(tuple[i], stringFactory);
+ valueStr = StringUtil.justifyLeft(valueStr, columnWidths[j], ' ');
+ sb.append('|').append(' ').append(valueStr).append('
');
+ }
+ sb.append('|');
+ sb.append('\n');
+ }
+
+ protected void printDelimiterLine( StringBuilder sb,
+ int[] columnWidths,
+ boolean includeLineFeed ) {
+ sb.append('+');
+ for (int i = 0, max = columnWidths.length; i != max; ++i) {
+ for (int j = 0, width = columnWidths[i] + 2; j != width; ++j) { // +1 for
space before, +1 for space after
+ sb.append('-');
+ }
+ sb.append('+');
+ }
+ if (includeLineFeed) sb.append('\n');
+ }
+
+ /**
+ * An interface used to walk through the results.
+ */
+ public final class TupleCursor implements Cursor {
+ private final Columns columns;
+ private final Iterator<Object[]> iterator;
+ private Object[] currentTuple;
+ private int tupleIndex;
+
+ protected TupleCursor( Columns columns,
+ Iterator<Object[]> iterator ) {
+ this.iterator = iterator;
+ this.columns = columns;
+ this.tupleIndex = -1;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#hasNext()
+ */
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#next()
+ */
+ public void next() {
+ currentTuple = iterator.next();
+ ++tupleIndex;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#getLocation(int)
+ */
+ public Location getLocation( int columnNumber ) {
+ return
(Location)currentTuple[columns.getLocationIndexForColumn(columnNumber)];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.QueryResults.Cursor#getLocation(java.lang.String)
+ */
+ public Location getLocation( String selectorName ) {
+ return (Location)currentTuple[columns.getLocationIndex(selectorName)];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#getRowIndex()
+ */
+ public int getRowIndex() {
+ return tupleIndex;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#getValue(int)
+ */
+ public Object getValue( int columnNumber ) {
+ if (columnNumber >= columns.getColumnCount()) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (currentTuple == null) {
+ throw new
IllegalStateException(GraphI18n.nextMethodMustBeCalledBeforeGettingValue.text());
+ }
+ return currentTuple[columnNumber];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.QueryResults.Cursor#getValue(java.lang.String)
+ */
+ public Object getValue( String columnName ) {
+ if (currentTuple == null) {
+ throw new
IllegalStateException(GraphI18n.nextMethodMustBeCalledBeforeGettingValue.text());
+ }
+ return currentTuple[columns.getColumnIndexForName(columnName)];
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/QueryResults.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SelectComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SelectComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SelectComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,456 @@
+/*
+ * 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.process;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.StaticOperand;
+
+/**
+ */
+public class SelectComponent extends DelegatingComponent {
+
+ private final Constraint constraint;
+ private final ConstraintChecker checker;
+ private final Map<String, Object> variables;
+
+ /**
+ * Create a SELECT processing component that pass those tuples that satisfy the
supplied constraint. Certain constraints
+ * (including {@link FullTextSearch}, {@link SameNode} and {@link PropertyExistence})
are evaluated in a fairly limited
+ * fashion, essentially operating upon the tuple values themselves.
+ * <p>
+ * For example, the {@link SameNode} constraint is satisfied when the selected node
has the same path as the constraint's
+ * {@link SameNode#getPath() path}. And the {@link PropertyExistence} constraint is
satisfied when the
+ * {@link PropertyExistence#getPropertyName() property} is represented in the tuple
with a non-null value. Similarly,
+ * {@link FullTextSearch} always evaluates to true. Obviously these implementations
will likely not be sufficient for many
+ * purposes. But in cases where these particular constraints are handled in other
ways (and thus not expected to be seen by
+ * this processor), this form may be sufficient.
+ * </p>
+ * <p>
+ * For more control over the behavior, use the constructor that takes an {@link
Analyzer} implementation (see
+ * {@link #SelectComponent(ProcessingComponent, Constraint, Map, Analyzer)}).
+ * </p>
+ *
+ * @param delegate the delegate processing component that this component should use
to obtain the input tuples; may not be
+ * null
+ * @param constraint the query constraint; may not be null
+ * @param variables the map of variables keyed by their name (as used in {@link
BindVariableName} constraints); may be null
+ */
+ public SelectComponent( ProcessingComponent delegate,
+ Constraint constraint,
+ Map<String, Object> variables ) {
+ this(delegate, constraint, variables, null);
+ }
+
+ /**
+ * Create a SELECT processing component that pass those tuples that satisfy the
supplied constraint, using the supplied
+ * {@link Analyzer} for the verification of the more complex/arduous constraints.
+ *
+ * @param delegate the delegate processing component that this component should use
to obtain the input tuples; may not be
+ * null
+ * @param constraint the query constraint; may not be null
+ * @param variables the map of variables keyed by their name (as used in {@link
BindVariableName} constraints); may be null
+ * @param analyzer the analyzer; may be null
+ */
+ public SelectComponent( ProcessingComponent delegate,
+ Constraint constraint,
+ Map<String, Object> variables,
+ Analyzer analyzer ) {
+ super(delegate);
+ this.constraint = constraint;
+ this.variables = variables != null ? variables : Collections.<String,
Object>emptyMap();
+ this.checker = createChecker(delegate.getContext(), delegate.getColumns(),
this.constraint, this.variables, analyzer);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ if (!tuples.isEmpty()) {
+ // Iterate through the tuples, removing any that do not satisfy the
constraint ...
+ Iterator<Object[]> iter = tuples.iterator();
+ while (iter.hasNext()) {
+ if (!checker.satisfiesConstraints(iter.next())) {
+ iter.remove();
+ }
+ }
+ }
+ return tuples;
+ }
+
+ /**
+ * Interface used to determine whether a tuple satisfies all of the constraints
applied to the SELECT node.
+ */
+ protected static interface ConstraintChecker {
+ /**
+ * Return true if the tuple satisfies all of the constraints.
+ *
+ * @param tuple the tuple; never null
+ * @return true if the tuple satisifes the constraints, or false otherwise
+ */
+ boolean satisfiesConstraints( Object[] tuple );
+ }
+
+ /**
+ * Inteface for criteria evaluation operations that cannot be defined efficiently,
correctly, or completely using only the
+ * tuple values.
+ *
+ * @see SelectComponent#SelectComponent(ProcessingComponent, Constraint, Map,
Analyzer)
+ */
+ public static interface Analyzer {
+ /**
+ * Determine whether the node specified by the location is the same node as that
supplied by the path. This determines if
+ * the nodes at the supplied location and path are the same node.
+ *
+ * @param location the location of the node; never null
+ * @param accessibleAtPath the path that the node can be accessed via
+ * @return true if the node given by the {@link Location} is also accessible at
the supplied path, or false otherwise
+ */
+ boolean isSameNode( Location location,
+ Path accessibleAtPath );
+
+ /**
+ * Determine whether the node at the supplied location has the named property.
+ *
+ * @param location the location of the node; never null
+ * @param propertyName the name of the property
+ * @return true if the node at the supplied {@link Location} does contain the
property, or false if it does not
+ */
+ boolean hasProperty( Location location,
+ Name propertyName );
+
+ /**
+ * Determine whether the node at the supplied location satisfies the supplied
full-text query.
+ *
+ * @param location the location of the node; never null
+ * @param fullTextQuery the full-text search expression; never null
+ * @return the full-text search score of the node, or 0.0d if the node does not
satisfy the full-text query
+ */
+ double hasFullText( Location location,
+ String fullTextQuery );
+
+ /**
+ * Determine whether the named property of the node at the supplied location
satisfies the supplied full-text query.
+ *
+ * @param location the location of the node; never null
+ * @param propertyName the name of the property; never null
+ * @param fullTextQuery the full-text search expression; never null
+ * @return the full-text search score of the node, or 0.0d if the node does not
satisfy the full-text query
+ */
+ double hasFullText( Location location,
+ Name propertyName,
+ String fullTextQuery );
+ }
+
+ /**
+ * Interface defining the {@link Comparison} functionality for a specific {@link
Operator}.
+ */
+ protected static interface CompareOperation {
+ /**
+ * Perform the comparison operation.
+ *
+ * @param tupleValue the value in the tuple
+ * @param criteriaValue the right-hand-side of the comparison
+ * @return true if the comparison criteria is satisfied, or false otherwise
+ */
+ boolean evaluate( Object tupleValue,
+ Object criteriaValue );
+ }
+
+ /**
+ * Create the constraint evaluator that is used by the {@link SelectComponent} to
evaluate the supplied {@link Constraint
+ * criteria}. For the most correct behavior, specify an {@link Analyzer}
implementation.
+ *
+ * @param context the context in which the query is being evaluated; may not be null
+ * @param columns the definition of the result columns and the tuples; may not be
null
+ * @param constraint the criteria that this {@link SelectComponent} is to evaluate
+ * @param variables the variables that are to be substituted for the various {@link
BindVariableName} {@link StaticOperand
+ * operands}; may not be null
+ * @param analyzer the analyzer that should be used to evalulate the operations that
cannot be defined efficiently, correctly,
+ * or completely using only the tuple values; may be null if the tuple values
should be used to perform the evaluation
+ * in perhaps an non-ideal manner
+ * @return the constraint evaluator; never null
+ */
+ @SuppressWarnings( "unchecked" )
+ protected ConstraintChecker createChecker( QueryContext context,
+ Columns columns,
+ Constraint constraint,
+ Map<String, Object> variables,
+ final Analyzer analyzer ) {
+ if (constraint instanceof Or) {
+ Or orConstraint = (Or)constraint;
+ final ConstraintChecker left = createChecker(context, columns,
orConstraint.getLeft(), variables, analyzer);
+ final ConstraintChecker right = createChecker(context, columns,
orConstraint.getRight(), variables, analyzer);
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return left.satisfiesConstraints(tuple) ||
right.satisfiesConstraints(tuple);
+ }
+ };
+ }
+ if (constraint instanceof Not) {
+ Not notConstraint = (Not)constraint;
+ final ConstraintChecker original = createChecker(context, columns,
notConstraint.getConstraint(), variables, analyzer);
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return !original.satisfiesConstraints(tuple);
+ }
+ };
+ }
+ if (constraint instanceof And) {
+ And andConstraint = (And)constraint;
+ final ConstraintChecker left = createChecker(context, columns,
andConstraint.getLeft(), variables, analyzer);
+ final ConstraintChecker right = createChecker(context, columns,
andConstraint.getRight(), variables, analyzer);
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return left.satisfiesConstraints(tuple) &&
right.satisfiesConstraints(tuple);
+ }
+ };
+ }
+ if (constraint instanceof ChildNode) {
+ ChildNode childConstraint = (ChildNode)constraint;
+ final int locationIndex =
columns.getLocationIndex(childConstraint.getSelectorName().getName());
+ final Path parentPath = childConstraint.getParentPath();
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ assert location.hasPath();
+ return location.getPath().getParent().equals(parentPath);
+ }
+ };
+ }
+ if (constraint instanceof DescendantNode) {
+ DescendantNode descendantNode = (DescendantNode)constraint;
+ final int locationIndex =
columns.getLocationIndex(descendantNode.getSelectorName().getName());
+ final Path ancestorPath = descendantNode.getAncestorPath();
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ assert location.hasPath();
+ return location.getPath().isDecendantOf(ancestorPath);
+ }
+ };
+ }
+ if (constraint instanceof SameNode) {
+ SameNode sameNode = (SameNode)constraint;
+ final int locationIndex =
columns.getLocationIndex(sameNode.getSelectorName().getName());
+ final Path path = sameNode.getPath();
+ if (analyzer != null) {
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ return analyzer.isSameNode(location, path);
+ }
+ };
+ }
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ assert location.hasPath();
+ return location.getPath().isSameAs(path);
+ }
+ };
+ }
+ if (constraint instanceof PropertyExistence) {
+ PropertyExistence propertyExistance = (PropertyExistence)constraint;
+ String selectorName = propertyExistance.getSelectorName().getName();
+ final Name propertyName = propertyExistance.getPropertyName();
+ if (analyzer != null) {
+ final int locationIndex = columns.getLocationIndex(selectorName);
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ return analyzer.hasProperty(location, propertyName);
+ }
+ };
+ }
+ final int columnIndex = columns.getColumnIndexForProperty(selectorName,
propertyName);
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return tuple[columnIndex] != null;
+ }
+ };
+ }
+ if (constraint instanceof FullTextSearch) {
+ if (analyzer != null) {
+ FullTextSearch search = (FullTextSearch)constraint;
+ String selectorName = search.getSelectorName().getName();
+ final int locationIndex = columns.getLocationIndex(selectorName);
+ final String expression = search.getFullTextSearchExpression();
+ if (expression == null) {
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return false;
+ }
+ };
+ }
+ final Name propertyName = search.getPropertyName(); // may be null
+ final int scoreIndex =
columns.getFullTextSearchScoreIndexFor(selectorName);
+ assert scoreIndex >= 0 : "Columns do not have room for the search
scores";
+ if (propertyName != null) {
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return false;
+ double score = analyzer.hasFullText(location, propertyName,
expression);
+ // put the score on the correct tuple value ...
+ Double existing = (Double)tuple[scoreIndex];
+ if (existing != null) {
+ score = Math.max(existing.doubleValue(), score);
+ }
+ tuple[scoreIndex] = new Double(score);
+ return true;
+ }
+ };
+ }
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ Location location = (Location)tuple[locationIndex];
+ if (location == null) return false;
+ double score = analyzer.hasFullText(location, expression);
+ // put the score on the correct tuple value ...
+ Double existing = (Double)tuple[scoreIndex];
+ if (existing != null) {
+ score = Math.max(existing.doubleValue(), score);
+ }
+ tuple[scoreIndex] = new Double(score);
+ return true;
+ }
+ };
+ }
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuple ) {
+ return true;
+ }
+ };
+ }
+ if (constraint instanceof Comparison) {
+ final ValueFactory<String> stringFactory =
context.getExecutionContext().getValueFactories().getStringFactory();
+ Comparison comparison = (Comparison)constraint;
+
+ // Create the correct dynamic operation ...
+ final DynamicOperation dynamicOperation = createDynamicOperation(context,
columns, comparison.getOperand1());
+ final PropertyType expectedType = dynamicOperation.getExpectedType();
+
+ // Determine the literal value ...
+ StaticOperand staticOperand = comparison.getOperand2();
+ Object literalValue = null;
+ if (staticOperand instanceof BindVariableName) {
+ BindVariableName bindVariable = (BindVariableName)staticOperand;
+ String variableName = bindVariable.getVariableName();
+ literalValue = variables.get(variableName); // may be null
+ } else {
+ Literal literal = (Literal)staticOperand;
+ literalValue = literal.getValue();
+ }
+ // Create the correct comparator ...
+ final Comparator<Object> comparator =
(Comparator<Object>)expectedType.getComparator();
+ // Create the correct operation ...
+ ValueFactory<?> literalFactory =
context.getExecutionContext().getValueFactories().getValueFactory(expectedType);
+ final Object rhs = literalFactory.create(literalValue);
+ switch (comparison.getOperator()) {
+ case EQUAL_TO:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) == 0;
+ }
+ };
+ case GREATER_THAN:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) > 0;
+ }
+ };
+ case GREATER_THAN_OR_EQUAL_TO:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) >= 0;
+ }
+ };
+ case LESS_THAN:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) < 0;
+ }
+ };
+ case LESS_THAN_OR_EQUAL_TO:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) <= 0;
+ }
+ };
+ case NOT_EQUAL_TO:
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ return comparator.compare(dynamicOperation.evaluate(tuples),
rhs) != 0;
+ }
+ };
+ case LIKE:
+ // Convert the LIKE expression to a regular expression
+ final Pattern pattern =
createRegexFromLikeExpression(stringFactory.create(rhs));
+ return new ConstraintChecker() {
+ public boolean satisfiesConstraints( Object[] tuples ) {
+ Object tupleValue = dynamicOperation.evaluate(tuples);
+ if (tupleValue == null) return false;
+ String value = stringFactory.create(tupleValue);
+ return pattern.matcher(value).matches();
+ }
+ };
+ }
+ }
+ assert false;
+ return null;
+ }
+
+ protected static Pattern createRegexFromLikeExpression( String likeExpression ) {
+ return null;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SelectComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SetOperationComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SetOperationComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SetOperationComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,99 @@
+/*
+ * 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.process;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public abstract class SetOperationComponent extends ProcessingComponent {
+
+ private final Iterable<ProcessingComponent> sources;
+ protected final Comparator<Object[]> removeDuplicatesComparator;
+
+ protected SetOperationComponent( QueryContext context,
+ Columns columns,
+ Iterable<ProcessingComponent> sources,
+ boolean alreadySorted,
+ boolean all ) {
+ super(context, columns);
+ assert unionCompatible(columns, sources);
+ this.sources = wrapWithLocationOrdering(sources, alreadySorted);
+ this.removeDuplicatesComparator = all ? null : createSortComparator(context,
columns);
+ }
+
+ protected static boolean unionCompatible( Columns columns,
+ Iterable<ProcessingComponent> sources
) {
+ for (ProcessingComponent source : sources) {
+ if (!columns.isUnionCompatible(source.getColumns())) return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return sources
+ */
+ protected Iterable<ProcessingComponent> sources() {
+ return sources;
+ }
+
+ /**
+ * The sources' results must be sorted before the intersection can be computed.
Ensure that the sources' results are indeed
+ * sorted, and if not wrap them in a sorting component.
+ *
+ * @param sources the sources being intersected; may not be null
+ * @param alreadySorted true if the sources' results are already sorted, or false
if they must be sorted by this component
+ * @return the sources (or their wrappers); never null
+ */
+ protected static Iterable<ProcessingComponent> wrapWithLocationOrdering(
Iterable<ProcessingComponent> sources,
+ boolean
alreadySorted ) {
+ assert sources != null;
+ if (alreadySorted) return sources;
+ List<ProcessingComponent> wrapped = new
LinkedList<ProcessingComponent>();
+ for (ProcessingComponent source : sources) {
+ wrapped.add(new SortLocationsComponent(source));
+ }
+ return wrapped;
+ }
+
+ protected void removeDuplicatesIfRequested( List<Object[]> tuples ) {
+ if (removeDuplicatesComparator != null) {
+ Iterator<Object[]> iter = tuples.iterator();
+ Object[] previous = null;
+ while (iter.hasNext()) {
+ Object[] current = iter.next();
+ if (previous != null &&
removeDuplicatesComparator.compare(previous, current) == 0) {
+ iter.remove();
+ } else {
+ previous = current;
+ }
+ }
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SetOperationComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortLocationsComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortLocationsComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortLocationsComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,61 @@
+/*
+ * 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.process;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ */
+public class SortLocationsComponent extends DelegatingComponent {
+
+ private Comparator<Object[]> sortingComparator;
+
+ public SortLocationsComponent( ProcessingComponent delegate ) {
+ super(delegate);
+ this.sortingComparator = createSortComparator(delegate.getContext(),
delegate.getColumns());
+ }
+
+ public SortLocationsComponent( ProcessingComponent delegate,
+ Comparator<Object[]> sortingComparator ) {
+ super(delegate);
+ this.sortingComparator = sortingComparator;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ if (tuples.size() > 1) {
+ // Sort the tuples ...
+ Collections.sort(tuples, sortingComparator);
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortLocationsComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,128 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Order;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * A {@link ProcessingComponent} implementation that performs a {@link Type#PROJECT
PROJECT} operation to reduce the columns that
+ * appear in the results.
+ */
+public class SortValuesComponent extends DelegatingComponent {
+
+ private final Comparator<Object[]> sortingComparator;
+
+ public SortValuesComponent( ProcessingComponent delegate,
+ List<Ordering> orderings ) {
+ super(delegate);
+ this.sortingComparator = createSortComparator(delegate.getContext(),
delegate.getColumns(), orderings);
+ }
+
+ /**
+ * @return sortingComparator
+ */
+ public Comparator<Object[]> getSortingComparator() {
+ return sortingComparator;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = delegate().execute();
+ if (tuples.size() > 1 && sortingComparator != null) {
+ // Sort the tuples ...
+ Collections.sort(tuples, sortingComparator);
+ }
+ return tuples;
+ }
+
+ protected Comparator<Object[]> createSortComparator( QueryContext context,
+ Columns columns,
+ List<Ordering> orderings )
{
+ assert context != null;
+ assert orderings != null;
+ if (orderings.isEmpty()) {
+ return null;
+ }
+ if (orderings.size() == 1) {
+ return createSortComparator(context, columns, orderings.get(0));
+ }
+ // Create a comparator that uses an ordered list of comparators ...
+ final List<Comparator<Object[]>> comparators = new
ArrayList<Comparator<Object[]>>(orderings.size());
+ for (Ordering ordering : orderings) {
+ comparators.add(createSortComparator(context, columns, ordering));
+ }
+ return new Comparator<Object[]>() {
+ public int compare( Object[] tuple1,
+ Object[] tuple2 ) {
+ for (Comparator<Object[]> comparator : comparators) {
+ int result = comparator.compare(tuple1, tuple2);
+ if (result != 0) return result;
+ }
+ return 0;
+ }
+ };
+
+ }
+
+ @SuppressWarnings( "unchecked" )
+ protected Comparator<Object[]> createSortComparator( QueryContext context,
+ Columns columns,
+ Ordering ordering ) {
+ assert context != null;
+ assert ordering != null;
+ final DynamicOperation operation = createDynamicOperation(context, columns,
ordering.getOperand());
+ final Comparator<Object> typeComparator =
(Comparator<Object>)operation.getExpectedType().getComparator();
+ if (ordering.getOrder() == Order.DESCENDING) {
+ return new Comparator<Object[]>() {
+ public int compare( Object[] tuple1,
+ Object[] tuple2 ) {
+ Object value1 = operation.evaluate(tuple1);
+ Object value2 = operation.evaluate(tuple2);
+ return 0 - typeComparator.compare(value1, value2);
+ }
+ };
+ }
+ return new Comparator<Object[]>() {
+ public int compare( Object[] tuple1,
+ Object[] tuple2 ) {
+ Object value1 = operation.evaluate(tuple1);
+ Object value2 = operation.evaluate(tuple2);
+ return typeComparator.compare(value1, value2);
+ }
+ };
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/SortValuesComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/UnionComponent.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/UnionComponent.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/UnionComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,62 @@
+/*
+ * 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.process;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+
+/**
+ */
+public class UnionComponent extends SetOperationComponent {
+
+ public UnionComponent( QueryContext context,
+ Columns columns,
+ Iterable<ProcessingComponent> sources,
+ boolean alreadySorted,
+ boolean all ) {
+ super(context, columns, sources, alreadySorted, all);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ List<Object[]> tuples = new ArrayList<Object[]>();
+ for (ProcessingComponent source : sources()) {
+ List<Object[]> results = source.execute();
+ tuples.addAll(results);
+ }
+ if (removeDuplicatesComparator != null) {
+ Collections.sort(tuples, this.removeDuplicatesComparator);
+ removeDuplicatesIfRequested(tuples);
+ }
+ return tuples;
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/UnionComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ValueCache.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ValueCache.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ValueCache.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,44 @@
+/*
+ * 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.process;
+
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.QueryResults;
+
+/**
+ * Interface representing a cache of property values used in a {@link QueryResults}.
+ */
+public interface ValueCache {
+
+ /**
+ * Get the value of the named property on the node at the supplied location.
+ *
+ * @param location the location of the node; may not be null
+ * @param name the property name
+ * @return the value of the property, or null if there is no such value
+ */
+ Object getProperty( Location location,
+ Name name );
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/process/ValueCache.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableColumn.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableColumn.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableColumn.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,68 @@
+/*
+ * 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.validate;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+
+@Immutable
+class ImmutableColumn implements Column {
+ private final String name;
+ private final PropertyType type;
+
+ protected ImmutableColumn( String name,
+ PropertyType type ) {
+ this.name = name;
+ this.type = type != null ? type : PropertyType.STRING;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Column#getName()
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Column#getPropertyType()
+ */
+ public PropertyType getPropertyType() {
+ return type;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.name + "(" + type.getName() + ")";
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableColumn.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableKey.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableKey.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableKey.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,113 @@
+/*
+ * 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.validate;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+import org.jboss.dna.graph.query.validate.Schemata.Key;
+
+/**
+ *
+ */
+public class ImmutableKey implements Key {
+
+ private final Set<Column> columns;
+
+ protected ImmutableKey( Iterable<Column> columns ) {
+ assert columns != null;
+ Set<Column> columnSet = new HashSet<Column>();
+ for (Column column : columns) {
+ if (column != null) columnSet.add(column);
+ }
+ assert !columnSet.isEmpty();
+ this.columns = Collections.unmodifiableSet(columnSet);
+ }
+
+ protected ImmutableKey( Column... columns ) {
+ assert columns != null;
+ assert columns.length > 0;
+ Set<Column> columnSet = new HashSet<Column>();
+ for (Column column : columns) {
+ if (column != null) columnSet.add(column);
+ }
+ assert !columnSet.isEmpty();
+ this.columns = Collections.unmodifiableSet(columnSet);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Key#getColumns()
+ */
+ public Set<Column> getColumns() {
+ return columns;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata.Key#hasColumns(org.jboss.dna.graph.query.validate.Schemata.Column[])
+ */
+ public boolean hasColumns( Column... columns ) {
+ Set<Column> keyColumns = new HashSet<Column>(this.columns);
+ for (Column expected : columns) {
+ if (!keyColumns.remove(expected)) return false;
+ }
+ return keyColumns.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata.Key#hasColumns(java.lang.Iterable)
+ */
+ public boolean hasColumns( Iterable<Column> columns ) {
+ Set<Column> keyColumns = new HashSet<Column>(this.columns);
+ for (Column expected : columns) {
+ if (!keyColumns.remove(expected)) return false;
+ }
+ return keyColumns.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ sb.append('[');
+ for (Column column : columns) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(column);
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableKey.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,367 @@
+/*
+ * 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.validate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.SelectorName;
+
+/**
+ * An immutable {@link Schemata} implementation.
+ */
+@Immutable
+public class ImmutableSchemata implements Schemata {
+
+ /**
+ * Obtain a new instance for building Schemata objects.
+ *
+ * @return the new builder; never null
+ */
+ public static Builder createBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * A builder of immutable {@link Schemata} objects.
+ */
+ @NotThreadSafe
+ public static class Builder {
+
+ private Map<SelectorName, ImmutableTable> tables = new
HashMap<SelectorName, ImmutableTable>();
+
+ /**
+ * Add a table with the supplied name and column names. Each column will be given
a type of {@link PropertyType#STRING}.
+ * The table will also overwrite any existing table definition with the same
name.
+ *
+ * @param name the name of the new table
+ * @param columnNames the names of the columns.
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, or if no column
+ * names are given
+ */
+ public Builder addTable( String name,
+ String... columnNames ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotEmpty(columnNames, "columnNames");
+ List<Column> columns = new ArrayList<Column>();
+ int i = 0;
+ for (String columnName : columnNames) {
+ CheckArg.isNotEmpty(columnName, "columnName[" + (i++) +
"]");
+ columns.add(new ImmutableColumn(columnName, PropertyType.STRING));
+ }
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a table with the supplied name and column names and types. The table will
also overwrite any existing table
+ * definition with the same name.
+ *
+ * @param name the name of the new table
+ * @param columnNames the names of the columns
+ * @param types the types for the columns
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, if no column
+ * names are given, or if the number of types does not match the number
of columns
+ */
+ public Builder addTable( String name,
+ String[] columnNames,
+ PropertyType[] types ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotEmpty(columnNames, "columnNames");
+ CheckArg.isNotEmpty(types, "types");
+ CheckArg.isEquals(columnNames.length, "columnNames.length",
types.length, "types.length");
+ List<Column> columns = new ArrayList<Column>();
+ assert columnNames.length == types.length;
+ for (int i = 0; i != columnNames.length; ++i) {
+ String columnName = columnNames[i];
+ CheckArg.isNotEmpty(columnName, "columnName[" + i +
"]");
+ columns.add(new ImmutableColumn(columnName, types[i]));
+ }
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a table with the supplied name and single column name and type. The table
will also overwrite any existing table
+ * definition with the same name.
+ *
+ * @param name the name of the new table
+ * @param column1Name the name of the single column
+ * @param column1Type the type for the single column
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, the
column name is null or empty, or if the type
+ * is null
+ */
+ public Builder addTable( String name,
+ String column1Name,
+ PropertyType column1Type ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotNull(column1Name, "column1Name");
+ CheckArg.isNotNull(column1Type, "column1Type");
+ List<Column> columns = new ArrayList<Column>();
+ columns.add(new ImmutableColumn(column1Name, column1Type));
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a table with the supplied name and two column names and types. The table
will also overwrite any existing table
+ * definition with the same name.
+ *
+ * @param name the name of the new table
+ * @param column1Name the name of the first column
+ * @param column1Type the type for the first column
+ * @param column2Name the name of the second column
+ * @param column2Type the type for the second column
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, or any of the
+ * types is null
+ */
+ public Builder addTable( String name,
+ String column1Name,
+ PropertyType column1Type,
+ String column2Name,
+ PropertyType column2Type ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotNull(column1Name, "column1Name");
+ CheckArg.isNotNull(column1Type, "column1Type");
+ CheckArg.isNotNull(column2Name, "column2Name");
+ CheckArg.isNotNull(column2Type, "column2Type");
+ List<Column> columns = new ArrayList<Column>();
+ columns.add(new ImmutableColumn(column1Name, column1Type));
+ columns.add(new ImmutableColumn(column2Name, column2Type));
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a table with the supplied name and three column names and types. The table
will also overwrite any existing table
+ * definition with the same name.
+ *
+ * @param name the name of the new table
+ * @param column1Name the name of the first column
+ * @param column1Type the type for the first column
+ * @param column2Name the name of the second column
+ * @param column2Type the type for the second column
+ * @param column3Name the name of the third column
+ * @param column3Type the type for the third column
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, or any of the
+ * types is null
+ */
+ public Builder addTable( String name,
+ String column1Name,
+ PropertyType column1Type,
+ String column2Name,
+ PropertyType column2Type,
+ String column3Name,
+ PropertyType column3Type ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotNull(column1Name, "column1Name");
+ CheckArg.isNotNull(column1Type, "column1Type");
+ CheckArg.isNotNull(column2Name, "column2Name");
+ CheckArg.isNotNull(column2Type, "column2Type");
+ CheckArg.isNotNull(column3Name, "column3Name");
+ CheckArg.isNotNull(column3Type, "column3Type");
+ List<Column> columns = new ArrayList<Column>();
+ columns.add(new ImmutableColumn(column1Name, column1Type));
+ columns.add(new ImmutableColumn(column2Name, column2Type));
+ columns.add(new ImmutableColumn(column3Name, column3Type));
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a table with the supplied name and four column names and types. The table
will also overwrite any existing table
+ * definition with the same name.
+ *
+ * @param name the name of the new table
+ * @param column1Name the name of the first column
+ * @param column1Type the type for the first column
+ * @param column2Name the name of the second column
+ * @param column2Type the type for the second column
+ * @param column3Name the name of the third column
+ * @param column3Type the type for the third column
+ * @param column4Name the name of the fourth column
+ * @param column4Type the type for the fourth column
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, or any of the
+ * types is null
+ */
+ public Builder addTable( String name,
+ String column1Name,
+ PropertyType column1Type,
+ String column2Name,
+ PropertyType column2Type,
+ String column3Name,
+ PropertyType column3Type,
+ String column4Name,
+ PropertyType column4Type ) {
+ CheckArg.isNotEmpty(name, "name");
+ CheckArg.isNotNull(column1Name, "column1Name");
+ CheckArg.isNotNull(column1Type, "column1Type");
+ CheckArg.isNotNull(column2Name, "column2Name");
+ CheckArg.isNotNull(column2Type, "column2Type");
+ CheckArg.isNotNull(column3Name, "column3Name");
+ CheckArg.isNotNull(column3Type, "column3Type");
+ CheckArg.isNotNull(column4Name, "column4Name");
+ CheckArg.isNotNull(column4Type, "column4Type");
+ List<Column> columns = new ArrayList<Column>();
+ columns.add(new ImmutableColumn(column1Name, column1Type));
+ columns.add(new ImmutableColumn(column2Name, column2Type));
+ columns.add(new ImmutableColumn(column3Name, column3Type));
+ columns.add(new ImmutableColumn(column4Name, column4Type));
+ ImmutableTable table = new ImmutableTable(new SelectorName(name), columns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add a column with the supplied name and type to the named table. Any existing
column with that name will be replaced
+ * with the new column. If the table does not yet exist, it will be added.
+ *
+ * @param tableName the name of the new table
+ * @param columnName the names of the column
+ * @param type the type for the column
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, any
column name is null or empty, if no column
+ * names are given, or if the number of types does not match the number
of columns
+ */
+ public Builder addColumn( String tableName,
+ String columnName,
+ PropertyType type ) {
+ CheckArg.isNotEmpty(tableName, "tableName");
+ CheckArg.isNotEmpty(columnName, "columnName");
+ CheckArg.isNotNull(type, "type");
+ SelectorName selector = new SelectorName(tableName);
+ ImmutableTable existing = tables.get(selector);
+ ImmutableTable table = null;
+ if (existing == null) {
+ List<Column> columns = new ArrayList<Column>();
+ columns.add(new ImmutableColumn(columnName, type));
+ table = new ImmutableTable(selector, columns);
+ } else {
+ table = existing.withColumn(columnName, type);
+ }
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Add to the specified table a key that references the existing named columns.
+ *
+ * @param tableName the name of the new table
+ * @param columnNames the names of the (existing) columns that make up the key
+ * @return this builder, for convenience in method chaining; never null
+ * @throws IllegalArgumentException if the table name is null or empty, the array
of column names is null or empty, or if
+ * the column names do not reference existing columns in the table
+ */
+ public Builder addKey( String tableName,
+ String... columnNames ) {
+ CheckArg.isNotEmpty(tableName, "tableName");
+ CheckArg.isNotEmpty(columnNames, "columnNames");
+ ImmutableTable existing = tables.get(tableName);
+ if (existing == null) {
+ throw new
IllegalArgumentException(GraphI18n.tableDoesNotExist.text(tableName));
+ }
+ Set<Column> keyColumns = new HashSet<Column>();
+ for (String columnName : columnNames) {
+ Column existingColumn = existing.getColumnsByName().get(columnName);
+ if (existingColumn == null) {
+ String msg =
GraphI18n.schemataKeyReferencesNonExistingColumn.text(tableName, columnName);
+ throw new IllegalArgumentException(msg);
+ }
+ keyColumns.add(existingColumn);
+ }
+ ImmutableTable table = existing.withKey(keyColumns);
+ tables.put(table.getName(), table);
+ return this;
+ }
+
+ /**
+ * Build the {@link Schemata} instance, using the current state of the builder.
This method creates a snapshot of the
+ * tables (with their columns) as they exist at the moment this method is
called.
+ *
+ * @return the new Schemata; never null
+ */
+ public Schemata build() {
+ return new ImmutableSchemata(new HashMap<SelectorName,
Table>(tables));
+ }
+ }
+
+ private final Map<SelectorName, Table> tables;
+
+ protected ImmutableSchemata( Map<SelectorName, Table> tables ) {
+ this.tables = Collections.unmodifiableMap(tables);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata#getTable(org.jboss.dna.graph.query.model.SelectorName)
+ */
+ public Table getTable( SelectorName name ) {
+ return tables.get(name);
+ }
+
+ public ImmutableSchemata with( Table table ) {
+ Map<SelectorName, Table> tables = new HashMap<SelectorName,
Table>(this.tables);
+ tables.put(table.getName(), table);
+ return new ImmutableSchemata(tables);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (Table table : tables.values()) {
+ if (first) first = false;
+ else sb.append('\n');
+ sb.append(table);
+ }
+ return sb.toString();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,234 @@
+/*
+ * 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.validate;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+import org.jboss.dna.graph.query.validate.Schemata.Key;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+
+@Immutable
+class ImmutableTable implements Table {
+ private final SelectorName name;
+ private final Map<String, Column> columnsByName;
+ private final List<Column> columns;
+ private final Set<Key> keys;
+
+ protected ImmutableTable( SelectorName name,
+ Iterable<Column> columns ) {
+ this(name, columns, (Iterable<Column>[])null);
+ }
+
+ protected ImmutableTable( SelectorName name,
+ Iterable<Column> columns,
+ Iterable<Column>... keyColumns ) {
+ this.name = name;
+ // Define the columns ...
+ List<Column> columnList = new LinkedList<Column>();
+ Map<String, Column> columnMap = new HashMap<String, Column>();
+ for (Column column : columns) {
+ Column old = columnMap.put(column.getName(), column);
+ if (old != null) {
+ columnList.set(columnList.indexOf(old), column);
+ } else {
+ columnList.add(column);
+ }
+ }
+ this.columnsByName = Collections.unmodifiableMap(columnMap);
+ this.columns = Collections.unmodifiableList(columnList);
+ // Define the keys ...
+ if (keyColumns != null) {
+ Set<Key> keys = new HashSet<Key>();
+ for (Iterable<Column> keyColumnSet : keyColumns) {
+ if (keyColumnSet != null) {
+ Key key = new ImmutableKey(keyColumnSet);
+ keys.add(key);
+ }
+ }
+ this.keys = Collections.unmodifiableSet(keys);
+ } else {
+ this.keys = Collections.emptySet();
+ }
+ }
+
+ protected ImmutableTable( SelectorName name,
+ Map<String, Column> columnsByName,
+ List<Column> columns,
+ Set<Key> keys ) {
+ this.name = name;
+ this.columns = columns;
+ this.columnsByName = columnsByName;
+ this.keys = keys;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#getName()
+ */
+ public SelectorName getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata.Table#getColumn(java.lang.String)
+ */
+ public Column getColumn( String name ) {
+ return columnsByName.get(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#getColumns()
+ */
+ public List<Column> getColumns() {
+ return columns;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#getColumnsByName()
+ */
+ public Map<String, Column> getColumnsByName() {
+ return columnsByName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#getKeys()
+ */
+ public Collection<Key> getKeys() {
+ return keys;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata.Table#getKey(org.jboss.dna.graph.query.validate.Schemata.Column[])
+ */
+ public Key getKey( Column... columns ) {
+ for (Key key : keys) {
+ if (key.hasColumns(columns)) return key;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#getKey(java.lang.Iterable)
+ */
+ public Key getKey( Iterable<Column> columns ) {
+ for (Key key : keys) {
+ if (key.hasColumns(columns)) return key;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.validate.Schemata.Table#hasKey(org.jboss.dna.graph.query.validate.Schemata.Column[])
+ */
+ public boolean hasKey( Column... columns ) {
+ return getKey(columns) != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.validate.Schemata.Table#hasKey(java.lang.Iterable)
+ */
+ public boolean hasKey( Iterable<Column> columns ) {
+ return getKey(columns) != null;
+ }
+
+ public ImmutableTable withColumn( String name,
+ PropertyType type ) {
+ List<Column> newColumns = new LinkedList<Column>(columns);
+ newColumns.add(new ImmutableColumn(name, type));
+ return new ImmutableTable(getName(), newColumns);
+ }
+
+ public ImmutableTable with( SelectorName name ) {
+ return new ImmutableTable(name, columnsByName, columns, keys);
+ }
+
+ public ImmutableTable withKey( Iterable<Column> keyColumns ) {
+ Set<Key> keys = new HashSet<Key>(this.keys);
+ for (Column keyColumn : keyColumns) {
+ assert columns.contains(keyColumn);
+ }
+ if (!keys.add(new ImmutableKey(keyColumns))) return this;
+ return new ImmutableTable(name, columnsByName, columns, keys);
+ }
+
+ public ImmutableTable withKey( Column... keyColumns ) {
+ return withKey(Arrays.asList(keyColumns));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(name.getName());
+ sb.append('(');
+ boolean first = true;
+ for (Column column : columns) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(column);
+ }
+ sb.append(')');
+ if (!keys.isEmpty()) {
+ sb.append(" with keys ");
+ first = true;
+ for (Key key : keys) {
+ if (first) first = false;
+ else sb.append(", ");
+ sb.append(key);
+ }
+ }
+ return sb.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,166 @@
+/*
+ * 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.validate;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.SelectorName;
+
+/**
+ * The interface used to access the structure being queried and validate a query.
+ */
+public interface Schemata {
+
+ /**
+ * Get the information for the table with the supplied name within this schema.
+ *
+ * @param name the table name; may not be null
+ * @return the table information, or null if there is no such table
+ */
+ Table getTable( SelectorName name );
+
+ /**
+ * Information about a queryable table.
+ */
+ public interface Table {
+ /**
+ * Get the name for this table.
+ *
+ * @return the table name; never null
+ */
+ SelectorName getName();
+
+ /**
+ * Get the information for a column with the supplied name within this table.
+ *
+ * @param name the column name; may not be null
+ * @return the column information, or null if there is no such column
+ */
+ Column getColumn( String name );
+
+ /**
+ * Get the queryable columns in this table.
+ *
+ * @return the map of column objects by their name; never null
+ */
+ Map<String, Column> getColumnsByName();
+
+ /**
+ * Get the queryable columns in this table.
+ *
+ * @return the ordered column objects; never null
+ */
+ List<Column> getColumns();
+
+ /**
+ * Get the collection of keys for this table.
+ *
+ * @return the immutable collection of keys; never null, but possibly empty
+ */
+ Collection<Key> getKeys();
+
+ /**
+ * Determine whether this table has a {@link #getKeys() key} that contains
exactly those columns listed.
+ *
+ * @param columns the columns for the key
+ * @return true if this table contains a key using exactly the supplied columns,
or false otherwise
+ */
+ boolean hasKey( Column... columns );
+
+ /**
+ * Determine whether this table has a {@link #getKeys() key} that contains
exactly those columns listed.
+ *
+ * @param columns the columns for the key
+ * @return true if this table contains a key using exactly the supplied columns,
or false otherwise
+ */
+ boolean hasKey( Iterable<Column> columns );
+
+ /**
+ * Obtain this table's {@link #getKeys() key} that contains exactly those
columns listed.
+ *
+ * @param columns the columns for the key
+ * @return the key that uses exactly the supplied columns, or null if there is no
such key
+ */
+ Key getKey( Column... columns );
+
+ /**
+ * Obtain this table's {@link #getKeys() key} that contains exactly those
columns listed.
+ *
+ * @param columns the columns for the key
+ * @return the key that uses exactly the supplied columns, or null if there is no
such key
+ */
+ Key getKey( Iterable<Column> columns );
+ }
+
+ /**
+ * Information about a queryable column.
+ */
+ public interface Column {
+ /**
+ * Get the name for this column.
+ *
+ * @return the column name; never null
+ */
+ String getName();
+
+ /**
+ * Get the property type for this column.
+ *
+ * @return the property type; never null
+ */
+ PropertyType getPropertyType();
+ }
+
+ /**
+ * Information about a key for a table.
+ */
+ public interface Key {
+ /**
+ * Get the columns that make up this key.
+ *
+ * @return the key's columns; immutable and never null
+ */
+ Set<Column> getColumns();
+
+ /**
+ * Determine whether this key contains exactly those columns listed.
+ *
+ * @param columns the columns for the key
+ * @return true if this key contains exactly the supplied columns, or false
otherwise
+ */
+ boolean hasColumns( Column... columns );
+
+ /**
+ * Determine whether this key contains exactly those columns listed.
+ *
+ * @param columns the columns for the key
+ * @return true if this key contains exactly the supplied columns, or false
otherwise
+ */
+ boolean hasColumns( Iterable<Column> columns );
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
===================================================================
--- trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-09-21
18:44:47 UTC (rev 1233)
+++ trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-09-21
20:03:40 UTC (rev 1234)
@@ -114,3 +114,32 @@
nodeHasAlreadyBeenRemovedFromThisSession = Node "{0}" in workspace "{1}
has already been removed from this session
unableToMoveNodeToBeChildOfDecendent = Node "{0}" in workspace "{2}"
cannot be moved under a decendant node ("{1}")
childNotFound = Child "{0}" could not be found under "{1}" in
workspace "{2}"
+
+# Query
+tableDoesNotExist = Table '{0}' does not exist
+columnDoesNotExistOnTable = Column '{0}' does not exist on the table
'{1}'
+columnDoesNotExistInQuery = Column '{0}' does not exist in query
+selectorDoesNotExistInQuery = Selector '{0}' does not exist in query
+propertyOnSelectorIsNotUsedInQuery = Property '{0}' on selector '{1}' is
not used in query
+errorResolvingNodesFromLocationsUsingSourceAndWorkspace = Error resolving nodes from
locations using '{1}' workspace in '{0}'
+queryHasNoResults = The query has no results
+schemataKeyReferencesNonExistingColumn = Schemata key for table '{0}' references
a non-existant column '{1}'
+nextMethodMustBeCalledBeforeGettingValue = The 'next()' method must be called
before 'getValue()'
+expectingValidName = Expecting a valid name but found '{0}'
+expectingValidPath = Expecting a valid path but found '{0}'
+columnMustBeScoped = Column '{0}' must be scoped
+expectingValidNameAtLineAndColumn = Expecting a valid name but found '{0}' at
line {1}, column {2}
+expectingValidPathAtLineAndColumn = Expecting a valid path but found '{0}' at
line {1}, column {2}
+mustBeScopedAtLineAndColumn = '{0}' must be scoped at line {1}, column {2}
+unexpectedToken = Unexpected token '{0}' at line {1}, column {2}
+secondValueInLimitRangeCannotBeLessThanFirst = Second value {0} in LIMIT range cannot be
less the first value {1} at line {2}, column {3}
+expectingComparisonOperator = Expecting '=', '<>', '!=',
'<', '<=', '>', '>=', or 'LIKE' but
found '{0}' at line {1}, column {2}
+expectingConstraintCondition = Expecting a constraint, but found '{0}' at line
{1}, column {2}
+functionIsAmbiguous = The {0} function at line {1}, column {2} is ambiguous since there
is more than one selector
+bindVariableMustConformToNcName = The name of a variable must conform to a valid NCName,
but found '{0}' at line {1}, column {2}
+invalidPropertyType = Expecting 'STRING', 'BINARY', 'DATE',
'LONG', 'DOUBLE', 'DECIMAL', 'BOOLEAN', 'NAME',
'PATH', 'REFERENCE', 'WEAKREFERENCE', or 'URI', but found
'{0}' at line {1}, column {2}
+valueCannotBeCastToSpecifiedType = The literal value '{0}' at line {1}, column
{2} cannot be cast to a {3} type: {4}
+noMatchingBracketFound = No matching closing bracket for the one at line {0}, column {1}
+expectingLiteralAndUnableToParseAsLong = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a long
+expectingLiteralAndUnableToParseAsDouble = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a double
+expectingLiteralAndUnableToParseAsDate = Expecting literal and unable to parse
'{0}' at line {1}, column {2} as a date
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+
+/**
+ *
+ */
+public abstract class AbstractQueryTest {
+
+ protected SelectorName selector( String selectorName ) {
+ return new SelectorName(selectorName);
+ }
+
+ protected void assertChildren( PlanNode node,
+ PlanNode... children ) {
+ assertThat(node.getChildCount(), is(children.length));
+ for (int i = 0; i != node.getChildCount(); ++i) {
+ assertThat(node.getChild(i), is(sameInstance(children[i])));
+ }
+ }
+
+ protected void assertSelectors( PlanNode node,
+ String... selectors ) {
+ Set<SelectorName> selectorSet = new HashSet<SelectorName>();
+ for (String selectorName : selectors) {
+ selectorSet.add(new SelectorName(selectorName));
+ }
+ assertThat("Selectors don't match", node.getSelectors(),
is(selectorSet));
+ }
+
+ protected void assertSortOrderBy( PlanNode sortNode,
+ String... selectors ) {
+ List<SelectorName> expected = new
ArrayList<SelectorName>(selectors.length);
+ for (String selectorName : selectors) {
+ expected.add(new SelectorName(selectorName));
+ }
+ List<SelectorName> actualSortedBy =
sortNode.getPropertyAsList(Property.SORT_ORDER_BY, SelectorName.class);
+ assertThat("Sort node order-by doesn't match selector name list",
actualSortedBy, is(expected));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,1294 @@
+/*
+ * 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;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.hamcrest.Matcher;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class QueryBuilderTest {
+
+ private ExecutionContext context;
+ private QueryBuilder builder;
+ private QueryCommand query;
+
+ @Before
+ public void beforeEach() {
+ context = new ExecutionContext();
+ builder = new QueryBuilder(context);
+ }
+
+ protected void assertThatSql( QueryCommand query,
+ Matcher<String> expected ) {
+ assertThat(Visitors.readable(query, context), expected);
+ }
+
+ @Test
+ public void shouldBuildSelectStarFromAllNodes() {
+ query = builder.selectStar().fromAllNodes().query();
+ assertThatSql(query, is("SELECT * FROM __ALLNODES__"));
+ }
+
+ @Test
+ public void shouldBuildSelectStarFromAllNodesAs() {
+ query = builder.selectStar().fromAllNodesAs("nodes").query();
+ assertThatSql(query, is("SELECT * FROM __ALLNODES__ AS nodes"));
+ }
+
+ @Test
+ public void shouldBuildSelectStarWithoutOtherClausesAsFromAllNodes() {
+ query = builder.selectStar().query();
+ assertThatSql(query, is("SELECT * FROM __ALLNODES__"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsFromAllNodes() {
+ query = builder.select("col1",
"col2").fromAllNodes().query();
+ assertThatSql(query, is("SELECT __ALLNODES__.col1,__ALLNODES__.col2 FROM
__ALLNODES__"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsFromAllNodesAs() {
+ query = builder.select("col1",
"col2").fromAllNodesAs("nodes").query();
+ assertThatSql(query, is("SELECT nodes.col1,nodes.col2 FROM __ALLNODES__ AS
nodes"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsUsingAliasFromAllNodesAs() {
+ query = builder.select("col1",
"nodes.col2").fromAllNodesAs("nodes").query();
+ assertThatSql(query, is("SELECT nodes.col1,nodes.col2 FROM __ALLNODES__ AS
nodes"));
+ }
+
+ @Test
+ public void shouldBuildSelectStarFromOneTable() {
+ query = builder.selectStar().from("table").query();
+ assertThatSql(query, is("SELECT * FROM table"));
+ }
+
+ @Test
+ public void shouldBuildSelectStarFromOneTableAs() {
+ query = builder.selectStar().from("table AS nodes").query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsFromOneTable() {
+ query = builder.select("col1",
"col2").from("table").query();
+ assertThatSql(query, is("SELECT table.col1,table.col2 FROM table"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsFromOneTableAs() {
+ query = builder.select("col1", "col2").from("table AS
nodes").query();
+ assertThatSql(query, is("SELECT nodes.col1,nodes.col2 FROM table AS
nodes"));
+ }
+
+ @Test
+ public void shouldBuildSelectColumnsUsingAliasFromOneTableAs() {
+ query = builder.select("col1", "nodes.col2").from("table
AS nodes").query();
+ assertThatSql(query, is("SELECT nodes.col1,nodes.col2 FROM table AS
nodes"));
+ }
+
+ @Test
+ public void shouldBuildUnionFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .union()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes UNION
SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildUnionAllFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .unionAll()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes UNION
ALL SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildUnionAllFromThreeSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .union()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .unionAll()
+ .select("col5", "col6")
+ .from("table3")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes UNION
SELECT edges.col3,edges.col4 FROM table2 AS edges UNION ALL SELECT table3.col5,table3.col6
FROM table3"));
+ }
+
+ @Test
+ public void shouldBuildIntersectFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .intersect()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes
INTERSECT SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildIntersectAllFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .intersectAll()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes
INTERSECT ALL SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildExceptFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .intersect()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes
INTERSECT SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildExceptAllFromTwoSimpleSelects() {
+ query = builder.select("col1", "nodes.col2")
+ .from("table1 AS nodes")
+ .intersectAll()
+ .select("col3", "edges.col4")
+ .from("table2 AS edges")
+ .query();
+ assertThatSql(query,
+ is("SELECT nodes.col1,nodes.col2 FROM table1 AS nodes
INTERSECT ALL SELECT edges.col3,edges.col4 FROM table2 AS edges"));
+ }
+
+ @Test
+ public void shouldBuildEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2").from("table1 AS
t1").join("table2 as t2").on(" t1.c0= t2. c0").query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 INNER JOIN
table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildInnerEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2").from("table1 AS
t1").innerJoin("table2 as t2").on(" t1.c0= t2. c0").query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 INNER JOIN
table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildLeftOuterEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2").from("table1 AS
t1").leftOuterJoin("table2 as t2").on(" t1.c0= t2.
c0").query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 LEFT OUTER
JOIN table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildRightOuterEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2")
+ .from("table1 AS t1")
+ .rightOuterJoin("table2 as t2")
+ .on(" t1.c0= t2. c0")
+ .query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 RIGHT OUTER
JOIN table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildFullOuterEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2").from("table1 AS
t1").fullOuterJoin("table2 as t2").on(" t1.c0= t2.
c0").query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 FULL OUTER
JOIN table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildCrossEquiJoin() {
+ query = builder.select("t1.c1", "t2.c2").from("table1 AS
t1").crossJoin("table2 as t2").on(" t1.c0= t2. c0").query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 CROSS JOIN
table2 as t2 ON t1.c0 = t2.c0"));
+ }
+
+ @Test
+ public void shouldBuildMultiJoinUsingEquiJoinCriteria() {
+ query = builder.select("t1.c1", "t2.c2")
+ .from("table1 AS t1")
+ .join("table2 as t2")
+ .on(" t1.c0= t2. c0")
+ .join("table3 as t3")
+ .on(" t1.c0= t3. c0")
+ .query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 " + //
+ "INNER JOIN table2 as t2 ON t1.c0 = t2.c0 " +
//
+ "INNER JOIN table3 as t3 ON t1.c0 = t3.c0"));
+ }
+
+ @Test
+ public void shouldBuildMultiJoinAndCrossUsingEquiJoinCriteria() {
+ query = builder.select("t1.c1", "t2.c2")
+ .from("table1 AS t1")
+ .join("table2 as t2")
+ .on(" t1.c0= t2. c0")
+ .crossJoin("table3 as t3")
+ .on(" t1.c0= t3. c0")
+ .query();
+ assertThatSql(query, is("SELECT t1.c1,t2.c2 FROM table1 AS t1 " + //
+ "INNER JOIN table2 as t2 " + //
+ "CROSS JOIN table3 as t3 ON t1.c0 = t3.c0 ON t1.c0 =
t2.c0"));
+ }
+
+ @Test
+ public void shouldAddNoConstraintsIfConstraintBuilderIsNotUsedButIsEnded() {
+ query = builder.selectStar().from("table AS
nodes").where().end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithOneHasPropertyConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().hasProperty("nodes", "col1").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE nodes.col1 IS
NOT NULL"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithChildConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().isChild("nodes", "/parent/path").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE
ISCHILDNODE(nodes,'/parent/path')"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithDescendantConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().isBelowPath("nodes",
"/parent/path").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE
ISDESCENDANTNODE(nodes,'/parent/path')"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithSameNodeConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().isSameNode("nodes",
"/other/path").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE
ISSAMENODE(nodes,'/other/path')"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithFullTextSearchConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().search("nodes", "expression").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE
CONTAINS(nodes,'expression')"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithPropertyFullTextSearchConstraint() {
+ query = builder.selectStar().from("table AS
nodes").where().search("nodes", "property",
"expression").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE
CONTAINS(nodes.property,'expression')"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithTwoHasPropertyConstraint() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .and()
+ .hasProperty("nodes", "col2")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes WHERE (nodes.col1 IS
NOT NULL AND nodes.col2 IS NOT NULL)"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithThreeHasPropertyConstraint() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .and()
+ .hasProperty("nodes", "col2")
+ .and()
+ .hasProperty("nodes", "col3")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE ((nodes.col1 IS NOT NULL " + //
+ "AND nodes.col2 IS NOT NULL) " + //
+ "AND nodes.col3 IS NOT NULL)"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCorrectPrecedenceWithAndAndOr() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .or()
+ .hasProperty("nodes", "col2")
+ .and()
+ .hasProperty("nodes", "col3")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE (nodes.col1 IS NOT NULL " + //
+ "OR (nodes.col2 IS NOT NULL " + //
+ "AND nodes.col3 IS NOT NULL))"));
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .and()
+ .hasProperty("nodes", "col2")
+ .or()
+ .hasProperty("nodes", "col3")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE ((nodes.col1 IS NOT NULL " + //
+ "AND nodes.col2 IS NOT NULL) " + //
+ "OR nodes.col3 IS NOT NULL)"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithMixureOfLogicalWithExplicitParenthesesWithHasPropertyConstraint() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .and()
+ .openParen()
+ .hasProperty("nodes", "col2")
+ .and()
+ .hasProperty("nodes", "col3")
+ .closeParen()
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE (nodes.col1 IS NOT NULL " + //
+ "AND (nodes.col2 IS NOT NULL " + //
+ "AND nodes.col3 IS NOT NULL))"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithMixureOfLogicalWithMultipleExplicitParenthesesWithHasPropertyConstraint()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .hasProperty("nodes", "col1")
+ .and()
+ .openParen()
+ .openParen()
+ .hasProperty("nodes", "col2")
+ .and()
+ .hasProperty("nodes", "col3")
+ .closeParen()
+ .and()
+ .search("nodes", "expression")
+ .closeParen()
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE (nodes.col1 IS NOT NULL " + //
+ "AND ((nodes.col2 IS NOT NULL " + //
+ "AND nodes.col3 IS NOT NULL) " + //
+ "AND
CONTAINS(nodes,'expression')))"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) = literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) = $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthNotEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isNotEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) != literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthNotEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isNotEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) != $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLessThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isLessThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) < literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLessThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isLessThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) <
$literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLessThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isLessThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) <=
literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLessThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isLessThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) <=
$literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthGreaterThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isGreaterThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) > literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthGreaterThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isGreaterThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) >
$literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthGreaterThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isGreaterThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) >=
literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthGreaterThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isGreaterThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) >=
$literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLike() {
+ query = builder.selectStar().from("table AS
nodes").where().length("nodes",
"property").isLike("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) LIKE literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLengthLikeVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .length("nodes", "property")
+ .isLikeVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LENGTH(nodes.property) LIKE
$literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameEqualTo() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isEqualTo("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) = literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameEqualToVariable() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isEqualToVariable("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) = $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameNotEqualTo() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isNotEqualTo("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) != literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameNotEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isNotEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) != $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLessThan() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLessThan("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) < literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLessThanVariable() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLessThanVariable("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) < $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLessThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isLessThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) <= literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLessThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isLessThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) <= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameGreaterThan() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isGreaterThan("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) > literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameGreaterThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isGreaterThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) > $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameGreaterThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) >= literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameGreaterThanOrEqualToVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) >= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLike() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLike("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) LIKE literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeNameLikeVariable() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeName("nodes").isLikeVariable("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE NAME(nodes) LIKE $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameEqualTo() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isEqualTo("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) = literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) = $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameNotEqualTo() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isNotEqualTo("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) != literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameNotEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isNotEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) != $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLessThan() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isLessThan("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) < literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLessThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isLessThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) < $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLessThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isLessThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) <= literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLessThanOrEqualToVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isLessThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) <= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameGreaterThan() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isGreaterThan("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) > literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameGreaterThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isGreaterThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) > $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameGreaterThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isGreaterThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) >= literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingNodeLocalNameGreaterThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isGreaterThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) >= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLike() {
+ query = builder.selectStar().from("table AS
nodes").where().nodeLocalName("nodes").isLike("literal").end().query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) LIKE literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingNodeLocalNameLikeVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .nodeLocalName("nodes")
+ .isLikeVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOCALNAME(nodes) LIKE $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) = literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) = $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameNotEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isNotEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) != literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameNotEqualToVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isNotEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) != $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLessThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLessThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) < literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLessThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLessThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) < $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLessThanOrEqualTo()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLessThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) <= literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLessThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLessThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) <= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameGreaterThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isGreaterThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) > literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameGreaterThanVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) > $literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameGreaterThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) >= literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameGreaterThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) >= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLike() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLike("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) LIKE literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingUppercaseOfNodeNameLikeVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isLikeVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE UPPER(NAME(nodes)) LIKE $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) = literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) = $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameNotEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isNotEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) != literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameNotEqualToVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isNotEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) != $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLessThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLessThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) < literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLessThanVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLessThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) < $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLessThanOrEqualTo()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLessThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) <= literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLessThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLessThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) <= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameGreaterThan() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isGreaterThan("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) > literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameGreaterThanVariable()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) > $literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameGreaterThanOrEqualTo() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) >= literal"));
+ }
+
+ @Test
+ public void
shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameGreaterThanOrEqualToVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isGreaterThanOrEqualToVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) >= $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLike() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLike("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) LIKE literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfNodeNameLikeVariable() {
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .nodeName("nodes")
+ .isLikeVariable("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(NAME(nodes)) LIKE $literal"));
+ }
+
+ @Test
+ public void shouldBuildQueryWithCriteriaUsingLowercaseOfUppercaseOfNodeNameEqualTo()
{
+ query = builder.selectStar()
+ .from("table AS nodes")
+ .where()
+ .lowerCaseOf()
+ .upperCaseOf()
+ .nodeName("nodes")
+ .isEqualTo("literal")
+ .end()
+ .query();
+ assertThatSql(query, is("SELECT * FROM table AS nodes " + //
+ "WHERE LOWER(UPPER(NAME(nodes))) = literal"));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/QueryBuilderTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/AbstractQueryObjectTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/AbstractQueryObjectTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/AbstractQueryObjectTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,54 @@
+/*
+ * 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.model;
+
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.junit.Before;
+
+/**
+ *
+ */
+public abstract class AbstractQueryObjectTest {
+
+ protected ExecutionContext context;
+
+ @Before
+ public void beforeEach() {
+ context = new ExecutionContext();
+ }
+
+ protected Name name( String name ) {
+ return context.getValueFactories().getNameFactory().create(name);
+ }
+
+ protected Path path( String name ) {
+ return context.getValueFactories().getPathFactory().create(name);
+ }
+
+ protected SelectorName selector( String name ) {
+ return new SelectorName(name);
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/AbstractQueryObjectTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/QueryTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/QueryTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/QueryTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,202 @@
+/*
+ * 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.model;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class QueryTest extends AbstractQueryObjectTest {
+
+ private Query query;
+ private Source source;
+ private Constraint constraint;
+ private List<Ordering> orderings;
+ private List<Column> columns;
+ private Limit limits;
+ private boolean distinct;
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullSource() {
+ new Query(null);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullSourceWhenSupplyingOtherParameters() {
+ source = null;
+ constraint = mock(Constraint.class);
+ orderings = Collections.emptyList();
+ columns = Collections.emptyList();
+ limits = null;
+ new Query(source, constraint, orderings, columns, limits, distinct);
+ }
+
+ @Test
+ public void shouldAllowNullConstraint() {
+ source = mock(Source.class);
+ constraint = null;
+ orderings = Collections.emptyList();
+ columns = Collections.emptyList();
+ limits = null;
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(query.getSource(), is(sameInstance(source)));
+ assertThat(query.getConstraint(), is(nullValue()));
+ assertThat(query.getOrderings(), is(sameInstance(orderings)));
+ assertThat(query.getColumns(), is(sameInstance(columns)));
+ }
+
+ @Test
+ public void shouldAllowNullOrderingsList() {
+ source = mock(Source.class);
+ constraint = mock(Constraint.class);
+ orderings = null;
+ columns = Collections.emptyList();
+ limits = null;
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(query.getSource(), is(sameInstance(source)));
+ assertThat(query.getConstraint(), is(sameInstance(constraint)));
+ assertThat(query.getOrderings().isEmpty(), is(true));
+ assertThat(query.getColumns(), is(sameInstance(columns)));
+ }
+
+ @Test
+ public void shouldAllowNullColumnsList() {
+ source = mock(Source.class);
+ constraint = mock(Constraint.class);
+ orderings = Collections.emptyList();
+ columns = null;
+ limits = null;
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(query.getSource(), is(sameInstance(source)));
+ assertThat(query.getConstraint(), is(sameInstance(constraint)));
+ assertThat(query.getOrderings(), is(sameInstance(orderings)));
+ assertThat(query.getColumns().isEmpty(), is(true));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullParameters() {
+ source = mock(Source.class);
+ constraint = mock(Constraint.class);
+ orderings = Collections.emptyList();
+ columns = Collections.emptyList();
+ limits = null;
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(query.getSource(), is(sameInstance(source)));
+ assertThat(query.getConstraint(), is(sameInstance(constraint)));
+ assertThat(query.getOrderings(), is(sameInstance(orderings)));
+ assertThat(query.getColumns(), is(sameInstance(columns)));
+ }
+
+ @Test
+ public void shouldConstructReadableString() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.singletonList(new
Column(selector("selector1")));
+ constraint = new PropertyExistence(selector("selector1"),
name("jcr:uuid"));
+ orderings = Collections.singletonList(new Ordering(new
NodeName(selector("selector1")), Order.ASCENDING));
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context),
+ is("SELECT selector1.* FROM nt:unstructured WHERE
selector1.jcr:uuid IS NOT NULL ORDER BY NAME(selector1) ASC"));
+ }
+
+ @Test
+ public void shouldConstructReadableStringWithLimits() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.singletonList(new
Column(selector("selector1")));
+ constraint = new PropertyExistence(selector("selector1"),
name("jcr:uuid"));
+ orderings = Collections.singletonList(new Ordering(new
NodeName(selector("selector1")), Order.ASCENDING));
+ limits = new Limit(10, 100);
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context),
+ is("SELECT selector1.* FROM nt:unstructured WHERE
selector1.jcr:uuid IS NOT NULL ORDER BY NAME(selector1) ASC LIMIT 10 OFFSET 100"));
+ }
+
+ @Test
+ public void shouldConstructReadableStringWithNoColumns() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.emptyList();
+ constraint = new PropertyExistence(selector("selector1"),
name("jcr:uuid"));
+ orderings = Collections.singletonList(new Ordering(new
NodeName(selector("selector1")), Order.ASCENDING));
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context),
+ is("SELECT * FROM nt:unstructured WHERE selector1.jcr:uuid IS NOT
NULL ORDER BY NAME(selector1) ASC"));
+ }
+
+ @Test
+ public void shouldConstructReadableStringWithNoOrderings() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.singletonList(new
Column(selector("selector1")));
+ constraint = new PropertyExistence(selector("selector1"),
name("jcr:uuid"));
+ orderings = Collections.emptyList();
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context),
+ is("SELECT selector1.* FROM nt:unstructured WHERE
selector1.jcr:uuid IS NOT NULL"));
+ }
+
+ @Test
+ public void shouldConstructReadableStringWithNoConstraint() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.singletonList(new
Column(selector("selector1")));
+ constraint = null;
+ orderings = Collections.singletonList(new Ordering(new
NodeName(selector("selector1")), Order.ASCENDING));
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context), is("SELECT selector1.* FROM
nt:unstructured ORDER BY NAME(selector1) ASC"));
+ }
+
+ @Test
+ public void
shouldConstructReadableStringWithDistinctAndNoConstraintOrColumnsOrOrderings() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.emptyList();
+ constraint = null;
+ orderings = Collections.emptyList();
+ distinct = true;
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context), is("SELECT DISTINCT * FROM
nt:unstructured"));
+
+ source = new AllNodes();
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context), is("SELECT DISTINCT * FROM
__ALLNODES__"));
+ }
+
+ @Test
+ public void shouldConstructReadableStringWithNoConstraintOrColumnsOrOrderings() {
+ source = new NamedSelector(selector("nt:unstructured"));
+ columns = Collections.emptyList();
+ constraint = null;
+ orderings = Collections.emptyList();
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM
nt:unstructured"));
+
+ source = new AllNodes();
+ query = new Query(source, constraint, orderings, columns, limits, distinct);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM
__ALLNODES__"));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/QueryTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/SetQueryTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/SetQueryTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/SetQueryTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,127 @@
+/*
+ * 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.model;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SetQueryTest extends AbstractQueryObjectTest {
+
+ private SetQuery query;
+ private QueryCommand left;
+ private QueryCommand right;
+ private SetQuery.Operation operation;
+ private boolean all;
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullSource() {
+ new Query(null);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullLeftQueryCommand() {
+ left = null;
+ right = mock(QueryCommand.class);
+ operation = SetQuery.Operation.UNION;
+ new SetQuery(left, operation, right, all);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullRightQueryCommand() {
+ left = mock(QueryCommand.class);
+ right = null;
+ operation = SetQuery.Operation.UNION;
+ new SetQuery(left, operation, right, all);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCreateWithNullOperation() {
+ left = mock(QueryCommand.class);
+ right = mock(QueryCommand.class);
+ operation = null;
+ new SetQuery(left, operation, right, all);
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndUnionOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.UNION;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A UNION
SELECT * FROM B"));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndUnionAllOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.UNION;
+ all = true;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A UNION ALL
SELECT * FROM B"));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndIntersectOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.INTERSECT;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A INTERSECT
SELECT * FROM B"));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndIntersectAllOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.INTERSECT;
+ all = true;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A INTERSECT
ALL SELECT * FROM B"));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndExceptOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.EXCEPT;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A EXCEPT
SELECT * FROM B"));
+ }
+
+ @Test
+ public void shouldCreateWithNonNullQueryCommandsAndExceptAllOperation() {
+ left = new Query(new NamedSelector(selector("A")));
+ right = new Query(new NamedSelector(selector("B")));
+ operation = SetQuery.Operation.EXCEPT;
+ all = true;
+ query = new SetQuery(left, operation, right, all);
+ assertThat(Visitors.readable(query, context), is("SELECT * FROM A EXCEPT ALL
SELECT * FROM B"));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/model/SetQueryTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/AddAccessNodesTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/AddAccessNodesTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/AddAccessNodesTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,90 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.LinkedList;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.optimize.AddAccessNodes;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class AddAccessNodesTest extends AbstractQueryTest {
+
+ private AddAccessNodes rule;
+ private QueryContext context;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ rule = AddAccessNodes.INSTANCE;
+ }
+
+ /**
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * ACCESS
+ * |
+ * SOURCE
+ * </pre>
+ */
+ @Test
+ public void shouldAddAccessNodeAboveSourceNode() {
+ PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"));
+ PlanNode source = new PlanNode(Type.SOURCE, project,
selector("Selector1"));
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, project, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(project)));
+ PlanNode access = project.getFirstChild();
+ assertThat(access.getType(), is(Type.ACCESS));
+ assertSelectors(access, "Selector1");
+ assertChildren(access, source);
+ assertChildren(source);
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/AddAccessNodesTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithmTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithmTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithmTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,160 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.LinkedList;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.optimize.ChooseJoinAlgorithm;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.plan.JoinAlgorithm;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+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.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class ChooseJoinAlgorithmTest extends AbstractQueryTest {
+
+ private ChooseJoinAlgorithm bestRule;
+ private ChooseJoinAlgorithm nestedRule;
+ private QueryContext context;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ bestRule = ChooseJoinAlgorithm.USE_BEST_JOIN_ALGORITHM;
+ nestedRule = ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM;
+ }
+
+ /**
+ * The {@link ChooseJoinAlgorithm#USE_ONLY_NESTED_JOIN_ALGORITHM} instance will
convert this simple tree:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN
+ * / \
+ * ... ...
+ * </pre>
+ *
+ * into this:
+ *
+ * <pre>
+ * ...
+ * |
+ * JOIN ({@link Property#JOIN_ALGORITHM JOIN_ALGORITHM}={@link
JoinAlgorithm#NESTED_LOOP NESTED_LOOP})
+ * / \
+ * ... ...
+ * </pre>
+ */
+ @Test
+ public void shouldHaveNestedRuleAlwaysSetJoinAlgorithmToNestedLoop() {
+ PlanNode join = new PlanNode(Type.JOIN, selector("Selector1"),
selector("Selector2"));
+ PlanNode s1Source = new PlanNode(Type.SOURCE, join,
selector("Selector1"));
+ PlanNode s2Source = new PlanNode(Type.SOURCE, join,
selector("Selector2"));
+ // Set the join type ...
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+
+ // Execute the rule ...
+ PlanNode result = nestedRule.execute(context, join, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(join)));
+ assertThat(join.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.INNER));
+ assertThat(join.getProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.class),
is(JoinAlgorithm.NESTED_LOOP));
+ assertChildren(join, s1Source, s2Source);
+ }
+
+ @Test
+ public void
shouldHaveBestRuleAlwaysSetJoinAlgorithmToNestedLoopIfConditionIsDescendantNode() {
+ PlanNode join = new PlanNode(Type.JOIN, selector("Ancestor"),
selector("Descendant"));
+ PlanNode ancestorSource = new PlanNode(Type.SOURCE, join,
selector("Ancestor"));
+ PlanNode descendantSource = new PlanNode(Type.SOURCE, join,
selector("Descendant"));
+ // Set the join type and condition ...
+ JoinCondition joinCondition = new
DescendantNodeJoinCondition(selector("Ancestor"),
selector("Descendant"));
+ join.setProperty(Property.JOIN_CONDITION, joinCondition);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+
+ // Execute the rule ...
+ PlanNode result = bestRule.execute(context, join, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(join)));
+ assertThat(join.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.INNER));
+ assertThat(join.getProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.class),
is(JoinAlgorithm.NESTED_LOOP));
+ assertThat(join.getProperty(Property.JOIN_CONDITION, JoinCondition.class),
is(sameInstance(joinCondition)));
+ assertChildren(join, ancestorSource, descendantSource);
+ }
+
+ @Test
+ public void shouldHaveBestRuleSetJoinAlgorithmToMergeIfConditionIsNotDescendantNode()
{
+ PlanNode join = new PlanNode(Type.JOIN, selector("Parent"),
selector("Child"));
+ PlanNode parentSource = new PlanNode(Type.SOURCE, join,
selector("Parent"));
+ PlanNode childSource = new PlanNode(Type.SOURCE, join,
selector("Child"));
+ // Set the join type and condition ...
+ JoinCondition joinCondition = new
ChildNodeJoinCondition(selector("Parent"), selector("Child"));
+ join.setProperty(Property.JOIN_CONDITION, joinCondition);
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+
+ // Execute the rule ...
+ PlanNode result = bestRule.execute(context, join, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(join)));
+ assertThat(join.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.INNER));
+ assertThat(join.getProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.class),
is(JoinAlgorithm.MERGE));
+ assertThat(join.getProperty(Property.JOIN_CONDITION, JoinCondition.class),
is(sameInstance(joinCondition)));
+
+ PlanNode leftDup = join.getFirstChild();
+ assertThat(leftDup.getType(), is(Type.DUP_REMOVE));
+ assertSelectors(leftDup, "Parent");
+ PlanNode leftSort = leftDup.getFirstChild();
+ assertThat(leftSort.getType(), is(Type.SORT));
+ assertSortOrderBy(leftSort, "Parent");
+ assertSelectors(leftSort, "Parent");
+ assertChildren(leftDup, leftSort);
+ assertChildren(leftSort, parentSource);
+
+ PlanNode rightDup = join.getLastChild();
+ assertThat(rightDup.getType(), is(Type.DUP_REMOVE));
+ assertSelectors(rightDup, "Child");
+ PlanNode rightSort = rightDup.getLastChild();
+ assertThat(rightSort.getType(), is(Type.SORT));
+ assertSortOrderBy(rightSort, "Child");
+ assertSelectors(rightSort, "Child");
+ assertChildren(rightDup, rightSort);
+ assertChildren(rightSort, childSource);
+
+ assertChildren(join, leftDup, rightDup);
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ChooseJoinAlgorithmTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/PushSelectCriteriaTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/PushSelectCriteriaTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/PushSelectCriteriaTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,226 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.LinkedList;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.PushSelectCriteria;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+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.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class PushSelectCriteriaTest extends AbstractQueryTest {
+
+ private PushSelectCriteria rule;
+ private QueryContext context;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ rule = PushSelectCriteria.INSTANCE;
+ }
+
+ /**
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECTn
+ * |
+ * ACCESS
+ * |
+ * SOURCE
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT with the list of columns being SELECTed
+ * |
+ * ACCESS
+ * |
+ * SELECT1
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 a single non-join constraint that are then all AND-ed
+ * | together
+ * SELECTn
+ * |
+ * SOURCE
+ * </pre>
+ */
+ @Test
+ public void
shouldPushDownAllSelectNodesThatApplyToSelectorBelowAccessNodeButAboveSourceNodeUsingSameSelector()
{
+ // Each of the PROJECT, SELECT, and SELECT nodes must have the names of the
selectors that they apply to ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"));
+ PlanNode select1 = new PlanNode(Type.SELECT, project,
selector("Selector1"));
+ PlanNode select2 = new PlanNode(Type.SELECT, select1,
selector("Selector1"));
+ PlanNode select3 = new PlanNode(Type.SELECT, select2,
selector("Selector1"));
+ PlanNode select4 = new PlanNode(Type.SELECT, select3,
selector("Selector1"));
+ PlanNode access = new PlanNode(Type.ACCESS, select4,
selector("Selector1"));
+ PlanNode source = new PlanNode(Type.SOURCE, access,
selector("Selector1"));
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, project, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(project)));
+ assertChildren(project, access);
+ assertChildren(access, select1);
+ assertChildren(select1, select2);
+ assertChildren(select2, select3);
+ assertChildren(select3, select4);
+ assertChildren(select4, source);
+ assertChildren(source);
+ }
+
+ @Test
+ public void shouldNotPushDownSelectNodesThatUseDifferentSelectorNamesThanSourceNode()
{
+ // Each of the PROJECT, SELECT, and SELECT nodes must have the names of the
selectors that they apply to ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"));
+ PlanNode select1 = new PlanNode(Type.SELECT, project,
selector("Selector2"));
+ PlanNode select2 = new PlanNode(Type.SELECT, select1,
selector("Selector1"));
+ PlanNode select3 = new PlanNode(Type.SELECT, select2,
selector("Selector2"));
+ PlanNode select4 = new PlanNode(Type.SELECT, select3,
selector("Selector1"));
+ PlanNode access = new PlanNode(Type.ACCESS, select4,
selector("Selector1"));
+ PlanNode source = new PlanNode(Type.SOURCE, access,
selector("Selector1"));
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, project, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(project)));
+ assertChildren(project, select1);
+ assertChildren(select1, select3);
+ assertChildren(select3, access);
+ assertChildren(access, select2);
+ assertChildren(select2, select4);
+ assertChildren(select4, source);
+ assertChildren(source);
+ }
+
+ /**
+ * Before:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT ('s1','s2') with the list of columns being
SELECTed (from 's1' and 's2' selectors)
+ * |
+ * SELECT1 ('s1')
+ * | One or more SELECT plan nodes that each have
+ * SELECT2 ('s2') a single non-join constraint that are then
all AND-ed
+ * | together, and that each have the selector(s) they
apply to
+ * SELECT3 ('s1','s2')
+ * |
+ * SELECT4 ('s1')
+ * |
+ * JOIN ('s1','s2')
+ * / \
+ * / \
+ * ACCESS ACCESS
+ * ('s1') ('s2')
+ * | |
+ * SOURCE SOURCE
+ * ('s1') ('s2')
+ * </pre>
+ *
+ * And after:
+ *
+ * <pre>
+ * ...
+ * |
+ * PROJECT ('s1','s2') with the list of columns being
SELECTed (from 's1' and 's2' selectors)
+ * |
+ * SELECT3 ('s1','s2') Any SELECT plan nodes that apply
to multiple selectors are left above
+ * | the ACCESS nodes.
+ * JOIN ('s1','s2')
+ * / \
+ * / \
+ * ACCESS ACCESS
+ * ('s1') ('s2')
+ * | |
+ * SELECT1 SELECT2
+ * ('s1') ('s2')
+ * | |
+ * SELECT4 SOURCE
+ * ('s1') ('s2')
+ * |
+ * SOURCE
+ * ('s1')
+ * </pre>
+ */
+ @Test
+ public void
shouldPushDownAllSelectNodesThatApplyToOneSelectorToBelowAccessNodeForThatSelector() {
+ // Each of the PROJECT, SELECT, and SELECT nodes must have the names of the
selectors that they apply to ...
+ PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"),
selector("Selector2"));
+ PlanNode select1 = new PlanNode(Type.SELECT, project,
selector("Selector1"));
+ PlanNode select2 = new PlanNode(Type.SELECT, select1,
selector("Selector2"));
+ PlanNode select3 = new PlanNode(Type.SELECT, select2,
selector("Selector1"), selector("Selector2"));
+ PlanNode select4 = new PlanNode(Type.SELECT, select3,
selector("Selector1"));
+ PlanNode join = new PlanNode(Type.JOIN, select4, selector("Selector1"),
selector("Selector2"));
+ PlanNode s1Access = new PlanNode(Type.ACCESS, join,
selector("Selector1"));
+ PlanNode s1Source = new PlanNode(Type.SOURCE, s1Access,
selector("Selector1"));
+ PlanNode s2Access = new PlanNode(Type.ACCESS, join,
selector("Selector2"));
+ PlanNode s2Source = new PlanNode(Type.SOURCE, s2Access,
selector("Selector2"));
+ // Set the join type ...
+ join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, project, new
LinkedList<OptimizerRule>());
+
+ System.out.println(result);
+
+ assertThat(result, is(sameInstance(project)));
+ assertChildren(project, select3);
+ assertChildren(select3, join);
+ assertChildren(join, s1Access, s2Access);
+ assertChildren(s1Access, select1);
+ assertChildren(select1, select4);
+ assertChildren(select4, s1Source);
+ assertChildren(s2Access, select2);
+ assertChildren(select2, s2Source);
+ assertChildren(s2Source);
+ assertChildren(s1Source);
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/PushSelectCriteriaTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoinsTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoinsTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoinsTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,154 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.LinkedList;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.RightOuterToLeftOuterJoins;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+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.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class RightOuterToLeftOuterJoinsTest extends AbstractQueryTest {
+
+ private RightOuterToLeftOuterJoins rule;
+ private QueryContext context;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ rule = RightOuterToLeftOuterJoins.INSTANCE;
+ }
+
+ @Test
+ public void shouldDoNothingWithLeftOuterJoin() {
+ // Create a LEFT_OUTER join ...
+ PlanNode joinNode = new PlanNode(Type.JOIN);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.LEFT_OUTER);
+ PlanNode lhs = new PlanNode(Type.SOURCE, joinNode);
+ PlanNode rhs = new PlanNode(Type.SOURCE, joinNode);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, joinNode, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(joinNode)));
+
+ // Verify nothing has changed ...
+ assertThat(joinNode.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.LEFT_OUTER));
+ assertThat(joinNode.getFirstChild(), is(sameInstance(lhs)));
+ assertThat(joinNode.getLastChild(), is(sameInstance(rhs)));
+ assertThat(joinNode.getChildCount(), is(2));
+ }
+
+ @Test
+ public void shouldDoNothingWithCrossJoin() {
+ // Create a LEFT_OUTER join ...
+ PlanNode joinNode = new PlanNode(Type.JOIN);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.CROSS);
+ PlanNode lhs = new PlanNode(Type.SOURCE, joinNode);
+ PlanNode rhs = new PlanNode(Type.SOURCE, joinNode);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, joinNode, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(joinNode)));
+
+ // Verify nothing has changed ...
+ assertThat(joinNode.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.CROSS));
+ assertThat(joinNode.getFirstChild(), is(sameInstance(lhs)));
+ assertThat(joinNode.getLastChild(), is(sameInstance(rhs)));
+ assertThat(joinNode.getChildCount(), is(2));
+ }
+
+ @Test
+ public void shouldDoNothingWithFullOuterJoin() {
+ // Create a LEFT_OUTER join ...
+ PlanNode joinNode = new PlanNode(Type.JOIN);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.FULL_OUTER);
+ PlanNode lhs = new PlanNode(Type.SOURCE, joinNode);
+ PlanNode rhs = new PlanNode(Type.SOURCE, joinNode);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, joinNode, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(joinNode)));
+
+ // Verify nothing has changed ...
+ assertThat(joinNode.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.FULL_OUTER));
+ assertThat(joinNode.getFirstChild(), is(sameInstance(lhs)));
+ assertThat(joinNode.getLastChild(), is(sameInstance(rhs)));
+ assertThat(joinNode.getChildCount(), is(2));
+ }
+
+ @Test
+ public void shouldDoNothingWithInnerJoin() {
+ // Create a LEFT_OUTER join ...
+ PlanNode joinNode = new PlanNode(Type.JOIN);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+ PlanNode lhs = new PlanNode(Type.SOURCE, joinNode);
+ PlanNode rhs = new PlanNode(Type.SOURCE, joinNode);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, joinNode, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(joinNode)));
+
+ // Verify nothing has changed ...
+ assertThat(joinNode.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.INNER));
+ assertThat(joinNode.getFirstChild(), is(sameInstance(lhs)));
+ assertThat(joinNode.getLastChild(), is(sameInstance(rhs)));
+ assertThat(joinNode.getChildCount(), is(2));
+ }
+
+ @Test
+ public void shouldChangeRightOuterJoinToLeftOuterJoinAndReverseChildNodes() {
+ // Create a RIGHT_OUTER join ...
+ PlanNode joinNode = new PlanNode(Type.JOIN);
+ joinNode.setProperty(Property.JOIN_TYPE, JoinType.RIGHT_OUTER);
+ PlanNode lhs = new PlanNode(Type.SOURCE, joinNode);
+ PlanNode rhs = new PlanNode(Type.SOURCE, joinNode);
+
+ // Execute the rule ...
+ PlanNode result = rule.execute(context, joinNode, new
LinkedList<OptimizerRule>());
+ assertThat(result, is(sameInstance(joinNode)));
+
+ // Verify the change ...
+ assertThat(joinNode.getProperty(Property.JOIN_TYPE, JoinType.class),
is(JoinType.LEFT_OUTER));
+ assertThat(joinNode.getFirstChild(), is(sameInstance(rhs)));
+ assertThat(joinNode.getLastChild(), is(sameInstance(lhs)));
+ assertThat(joinNode.getChildCount(), is(2));
+ }
+
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RightOuterToLeftOuterJoinsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
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
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,114 @@
+/*
+ * 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 static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class RuleBasedOptimizerTest {
+
+ private RuleBasedOptimizer optimizer;
+ private List<OptimizerRule> rules;
+ private List<Integer> ruleExecutionOrder;
+ private QueryContext context;
+ private PlanNode node;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ node = new PlanNode(Type.ACCESS);
+
+ ruleExecutionOrder = new ArrayList<Integer>();
+ rules = new ArrayList<OptimizerRule>();
+
+ // Add rules that, when executed, add their number to the
'ruleExecutionOrder' list ...
+ for (int i = 0; i != 5; ++i) {
+ final int ruleNumber = i;
+ this.rules.add(new OptimizerRule() {
+ @SuppressWarnings( "synthetic-access" )
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+ ruleExecutionOrder.add(ruleNumber);
+ return plan;
+ }
+ });
+ }
+
+ // Create a rule-based optimizer that uses a stack of completely artificial mock
rules ...
+ this.optimizer = new RuleBasedOptimizer() {
+ @SuppressWarnings( "synthetic-access" )
+ @Override
+ protected void populateRuleStack( LinkedList<OptimizerRule> ruleStack,
+ PlanHints hints ) {
+ ruleStack.addAll(rules);
+ }
+ };
+ }
+
+ @Test
+ public void shouldExecuteEachRuleInSequence() {
+ optimizer.optimize(context, node);
+ for (int i = 0; i != rules.size(); ++i) {
+ assertThat(ruleExecutionOrder.get(i), is(i));
+ }
+ }
+
+ @Test
+ public void shouldStopExecutingRulesIfThereIsAnErrorInTheProblems() {
+ // Change of the rules to generate an error ...
+ this.rules.set(3, new OptimizerRule() {
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList<OptimizerRule> ruleStack ) {
+ context.getProblems().addError(GraphI18n.closedConnectionMayNotBeUsed);
+ return plan;
+ }
+ });
+
+ optimizer.optimize(context, node);
+ assertThat(ruleExecutionOrder.get(0), is(0));
+ assertThat(ruleExecutionOrder.get(1), is(1));
+ assertThat(ruleExecutionOrder.get(2), is(2));
+ assertThat(ruleExecutionOrder.size(), is(3));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/FullTextSearchParserTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/FullTextSearchParserTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/FullTextSearchParserTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,207 @@
+/*
+ * 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.parse;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.graph.query.model.FullTextSearch.CompoundTerm;
+import org.jboss.dna.graph.query.model.FullTextSearch.Conjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.Disjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.SimpleTerm;
+import org.jboss.dna.graph.query.model.FullTextSearch.Term;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FullTextSearchParserTest {
+
+ private FullTextSearchParser parser;
+
+ @Before
+ public void beforeEach() {
+ parser = new FullTextSearchParser();
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToParseNullString() {
+ parser.parse((String)null);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseEmptyString() {
+ parser.parse("");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseBlankString() {
+ parser.parse(" ");
+ }
+
+ @Test
+ public void shouldParseStringWithOneUnquotedTerm() {
+ Term result = parser.parse("term1");
+ assertSimpleTerm(result, "term1", false, false);
+ }
+
+ @Test
+ public void shouldParseStringWithOneSingleQuotedTermWithOneWord() {
+ Term result = parser.parse("'term1'");
+ assertSimpleTerm(result, "term1", false, false);
+ }
+
+ @Test
+ public void shouldParseStringWithOneSingleQuotedTermWithMultipleWords() {
+ Term result = parser.parse("'term1 has two words'");
+ assertSimpleTerm(result, "term1 has two words", false, true);
+ }
+
+ @Test
+ public void shouldParseStringWithOneDoubleQuotedTermWithOneWord() {
+ Term result = parser.parse("\"term1\"");
+ assertSimpleTerm(result, "term1", false, false);
+ }
+
+ @Test
+ public void shouldParseStringWithOneDoubleQuotedTermWithMultipleWords() {
+ Term result = parser.parse("\"term1 has two words\"");
+ assertSimpleTerm(result, "term1 has two words", false, true);
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleUnquotedTerms() {
+ Term result = parser.parse("term1 term2 term3");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Conjunction.class)));
+ Conjunction conjunction = (Conjunction)result;
+ assertHasSimpleTerms(conjunction, "term1", "term2",
"term3");
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleUnquotedTermsWithNegatedTerms() {
+ Term result = parser.parse("term1 term2 -term3");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Conjunction.class)));
+ Conjunction conjunction = (Conjunction)result;
+ assertHasSimpleTerms(conjunction, "term1", "term2",
"-term3");
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleQuotedAndUnquotedTermsWithNegatedTerms() {
+ Term result = parser.parse("term1 \"term2 and 2a\" -term3
-'term 4'");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Conjunction.class)));
+ Conjunction conjunction = (Conjunction)result;
+ assertHasSimpleTerms(conjunction, "term1", "term2 and 2a",
"-term3", "-term 4");
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleUnquotedORedTerms() {
+ Term result = parser.parse("term1 OR term2 OR term3");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Disjunction.class)));
+ Disjunction disjunction = (Disjunction)result;
+ assertHasSimpleTerms(disjunction, "term1", "term2",
"term3");
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleUnquotedORedTermsWithNegatedTerms() {
+ Term result = parser.parse("term1 OR term2 OR -term3");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Disjunction.class)));
+ Disjunction disjunction = (Disjunction)result;
+ assertHasSimpleTerms(disjunction, "term1", "term2",
"-term3");
+ }
+
+ @Test
+ public void shouldParseStringWithMultipleUnquotedANDedTermsORedTogether() {
+ Term result = parser.parse("term1 term2 OR -term3 -term4");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Disjunction.class)));
+ Disjunction disjunction = (Disjunction)result;
+ assertThat(disjunction.getTerms().size(), is(2));
+ Conjunction conjunction1 = (Conjunction)disjunction.getTerms().get(0);
+ Conjunction conjunction2 = (Conjunction)disjunction.getTerms().get(1);
+ assertHasSimpleTerms(conjunction1, "term1", "term2");
+ assertHasSimpleTerms(conjunction2, "-term3", "term4");
+ }
+
+ @Test
+ public void shouldParseStringWithTwoANDedUnquotedTermsORedWithMultipleUnquotedTerms()
{
+ Term result = parser.parse("term1 term2 OR -term3 OR -term4 OR
term5");
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Disjunction.class)));
+ Disjunction disjunction = (Disjunction)result;
+ assertThat(disjunction.getTerms().size(), is(4));
+ Conjunction conjunction1 = (Conjunction)disjunction.getTerms().get(0);
+ SimpleTerm term3 = (SimpleTerm)disjunction.getTerms().get(1);
+ SimpleTerm term4 = (SimpleTerm)disjunction.getTerms().get(2);
+ SimpleTerm term5 = (SimpleTerm)disjunction.getTerms().get(3);
+ assertHasSimpleTerms(conjunction1, "term1", "term2");
+ assertSimpleTerm(term3, "term3", true, false);
+ assertSimpleTerm(term4, "term4", true, false);
+ assertSimpleTerm(term5, "term5", false, false);
+ }
+
+ public static void assertHasSimpleTerms( CompoundTerm compoundTerm,
+ String... terms ) {
+ List<Term> expectedTerms = new ArrayList<Term>();
+ for (String term : terms) {
+ SimpleTerm expected = new SimpleTerm(term, false);
+ if (term.startsWith("-")) {
+ term = term.substring(1);
+ expected = new SimpleTerm(term, true);
+ }
+ expectedTerms.add(expected);
+ }
+ assertHasTerms(compoundTerm, expectedTerms.toArray(new
Term[expectedTerms.size()]));
+ }
+
+ public static void assertSimpleTerm( Term term,
+ String value,
+ boolean excluded,
+ boolean quotingRequired ) {
+ assertThat(term, is(notNullValue()));
+ assertThat(term, is(instanceOf(SimpleTerm.class)));
+ SimpleTerm simpleTerm = (SimpleTerm)term;
+ assertThat(simpleTerm.getValue(), is(value));
+ assertThat(simpleTerm.isExcluded(), is(excluded));
+ assertThat(simpleTerm.isQuotingRequired(), is(quotingRequired));
+ }
+
+ public static void assertHasTerms( CompoundTerm compoundTerm,
+ Term... terms ) {
+ Iterator<Term> iterator = compoundTerm.iterator();
+ for (int i = 0; i != 0; i++) {
+ Term expected = terms[i];
+ assertThat(iterator.hasNext(), is(true));
+ Term term = iterator.next();
+ assertThat(term, is(expected));
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/FullTextSearchParserTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,1682 @@
+/*
+ * 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.parse;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import java.util.List;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.Position;
+import org.jboss.dna.common.text.TokenStream;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.BindVariableName;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Join;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.Limit;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NamedSelector;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Order;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Source;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.FullTextSearch.Conjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.Disjunction;
+import org.jboss.dna.graph.query.model.FullTextSearch.SimpleTerm;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SqlQueryParserTest {
+
+ private ExecutionContext context;
+ private SqlQueryParser parser;
+
+ @Before
+ public void beforeEach() {
+ context = new ExecutionContext();
+ parser = new SqlQueryParser();
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseQuery
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseNominalQueries() {
+ parse("SELECT * FROM tableA");
+ parse("SELECT column1 FROM tableA");
+ parse("SELECT tableA.column1 FROM tableA");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA INNER JOIN tableB
ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA OUTER JOIN tableB
ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA LEFT OUTER JOIN
tableB ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA RIGHT OUTER JOIN
tableB ON tableA.id = tableB.id");
+ }
+
+ @Test
+ public void shouldParseQueriesWithNonSqlColumnNames() {
+ parse("SELECT * FROM [dna:tableA]");
+ parse("SELECT [jcr:column1] FROM [dna:tableA]");
+ parse("SELECT 'jcr:column1' FROM 'dna:tableA'");
+ parse("SELECT \"jcr:column1\" FROM
\"dna:tableA\"");
+ }
+
+ @Test
+ public void shouldParseQueriesSelectingFromAllTables() {
+ parse("SELECT * FROM __AllTables__");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseQueriesWithNoFromClause() {
+ parse("SELECT 'jcr:column1'");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseQueriesWithIncompleteFromClause() {
+ parse("SELECT 'jcr:column1' FROM ");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseQueriesWithUnmatchedSingleQuoteCharacters() {
+ parse("SELECT 'jcr:column1' FROM \"dna:tableA'");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseQueriesWithUnmatchedDoubleQuoteCharacters() {
+ parse("SELECT \"jcr:column1' FROM \"dna:tableA\"");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseQueriesWithUnmatchedBracketQuoteCharacters() {
+ parse("SELECT [jcr:column1' FROM [dna:tableA]");
+ }
+
+ @Test
+ public void shouldParseQueriesWithSelectStar() {
+ parse("SELECT * FROM tableA");
+ parse("SELECT tableA.* FROM tableA");
+ parse("SELECT tableA.column1, tableB.* FROM tableA JOIN tableB ON tableA.id
= tableB.id");
+ parse("SELECT tableA.*, tableB.column2 FROM tableA JOIN tableB ON tableA.id
= tableB.id");
+ parse("SELECT tableA.*, tableB.* FROM tableA JOIN tableB ON tableA.id =
tableB.id");
+ }
+
+ @Test
+ public void shouldParseQueriesWithAllKindsOfJoins() {
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA INNER JOIN tableB
ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA OUTER JOIN tableB
ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA LEFT OUTER JOIN
tableB ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA RIGHT OUTER JOIN
tableB ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA FULL OUTER JOIN
tableB ON tableA.id = tableB.id");
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA CROSS JOIN tableB
ON tableA.id = tableB.id");
+ }
+
+ @Test
+ public void shouldParseQueriesWithMultipleJoins() {
+ parse("SELECT * FROM tableA JOIN tableB ON tableA.id = tableB.id");
+ parse("SELECT * FROM tableA JOIN tableB ON tableA.id = tableB.id JOIN tableC
ON tableA.id2 = tableC.id2");
+ }
+
+ @Test
+ public void shouldParseQueriesWithEquiJoinCriteria() {
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
tableA.id = tableB.id");
+ parse("SELECT * FROM tableA JOIN tableB ON tableA.id = tableB.id JOIN tableC
ON tableA.id2 = tableC.id2");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseEquiJoinCriteriaMissingPropertyName() {
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
tableA = tableB.id");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseEquiJoinCriteriaMissingTableName() {
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
column1 = tableB.id");
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseEquiJoinCriteriaMissingEquals() {
+ parse("SELECT tableA.column1, tableB.column2 FROM tableA JOIN tableB ON
column1 tableB.id");
+ }
+
+ @Test
+ public void shouldParseQueriesOnMultpleLines() {
+ parse("SELECT * \nFROM tableA");
+ parse("SELECT \ncolumn1 \nFROM tableA");
+ parse("SELECT \ntableA.column1 \nFROM\n tableA");
+ parse("SELECT tableA.\ncolumn1, \ntableB.column2 \nFROM tableA JOIN \ntableB
ON tableA.id \n= tableB.\nid");
+ }
+
+ @Test
+ public void shouldParseQueriesThatUseDifferentCaseForKeywords() {
+ parse("select * from tableA");
+ parse("SeLeCt * from tableA");
+ parse("select column1 from tableA");
+ parse("select tableA.column1 from tableA");
+ parse("select tableA.column1, tableB.column2 from tableA join tableB on
tableA.id = tableB.id");
+ }
+
+ @Test
+ public void shouldParseUnionQueries() {
+ parse("SELECT * FROM tableA UNION SELECT * FROM tableB");
+ parse("SELECT * FROM tableA UNION ALL SELECT * FROM tableB");
+ parse("SELECT * FROM tableA UNION SELECT * FROM tableB UNION SELECT * FROM
tableC");
+ parse("SELECT * FROM tableA UNION ALL SELECT * FROM tableB UNION SELECT *
FROM tableC");
+ }
+
+ @Test
+ public void shouldParseIntersectQueries() {
+ parse("SELECT * FROM tableA INTERSECT SELECT * FROM tableB");
+ parse("SELECT * FROM tableA INTERSECT ALL SELECT * FROM tableB");
+ parse("SELECT * FROM tableA INTERSECT SELECT * FROM tableB INTERSECT SELECT
* FROM tableC");
+ parse("SELECT * FROM tableA INTERSECT ALL SELECT * FROM tableB INTERSECT ALL
SELECT * FROM tableC");
+ }
+
+ @Test
+ public void shouldParseExceptQueries() {
+ parse("SELECT * FROM tableA EXCEPT SELECT * FROM tableB");
+ parse("SELECT * FROM tableA EXCEPT ALL SELECT * FROM tableB");
+ parse("SELECT * FROM tableA EXCEPT SELECT * FROM tableB EXCEPT SELECT * FROM
tableC");
+ parse("SELECT * FROM tableA EXCEPT ALL SELECT * FROM tableB EXCEPT ALL
SELECT * FROM tableC");
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseSetQuery
+ //
----------------------------------------------------------------------------------------------------------------
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseSelect
+ //
----------------------------------------------------------------------------------------------------------------
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseFrom
+ //
----------------------------------------------------------------------------------------------------------------
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseJoinCondition
+ //
----------------------------------------------------------------------------------------------------------------
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseWhere
+ //
----------------------------------------------------------------------------------------------------------------
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithValidExpressions() {
+ assertParseConstraint("ISSAMENODE('/a/b')");
+ assertParseConstraint("ISSAMENODE('/a/b') AND
NOT(ISCHILDNODE('/parent'))");
+ assertParseConstraint("ISSAMENODE('/a/b') AND
(NOT(ISCHILDNODE('/parent')))");
+ assertParseConstraint("ISSAMENODE('/a/b') AND (NOT(tableA.id <
1234)))");
+ }
+
+ protected void assertParseConstraint( String expression ) {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ parser.parseConstraint(tokens(expression), context, selector);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - parentheses
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithOuterParentheses() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("(
ISSAMENODE('/a/b') )"), context, selector);
+ assertThat(constraint, is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)constraint;
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b")));
+ }
+
+ @Test
+ public void shouldParseConstraintFromStringWithMultipleOuterParentheses() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parseConstraint(tokens("(((
ISSAMENODE('/a/b') )))"), context, selector);
+ assertThat(constraint, is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)constraint;
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithParenthesesAndConjunctionAndDisjunctions() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE('/a/b') OR
(ISSAMENODE('/c/d') AND ISSAMENODE('/e/f'))"),
+ context,
+ selector);
+ assertThat(constraint, is(instanceOf(Or.class)));
+ Or or = (Or)constraint;
+
+ assertThat(or.getLeft(), is(instanceOf(SameNode.class)));
+ SameNode first = (SameNode)or.getLeft();
+ assertThat(first.getSelectorName(), is(selectorName("tableA")));
+ assertThat(first.getPath(), is(path("/a/b")));
+
+ assertThat(or.getRight(), is(instanceOf(And.class)));
+ And and = (And)or.getRight();
+
+ assertThat(and.getLeft(), is(instanceOf(SameNode.class)));
+ SameNode second = (SameNode)and.getLeft();
+ assertThat(second.getSelectorName(), is(selectorName("tableA")));
+ assertThat(second.getPath(), is(path("/c/d")));
+
+ assertThat(and.getRight(), is(instanceOf(SameNode.class)));
+ SameNode third = (SameNode)and.getRight();
+ assertThat(third.getSelectorName(), is(selectorName("tableA")));
+ assertThat(third.getPath(), is(path("/e/f")));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - AND
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithAndExpressionWithNoParentheses() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE('/a/b/c') AND
CONTAINS(p1,term1)"), context, selector);
+ assertThat(constraint, is(instanceOf(And.class)));
+ And and = (And)constraint;
+
+ assertThat(and.getLeft(), is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)and.getLeft();
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+
+ assertThat(and.getRight(), is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search = (FullTextSearch)and.getRight();
+ assertThat(search.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search.getPropertyName(), is(name("p1")));
+ assertThat(search.getFullTextSearchExpression(), is("term1"));
+ }
+
+ @Test
+ public void shouldParseConstraintFromStringWithMultipleAndExpressions() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE('/a/b/c') AND CONTAINS(p1,term1)
AND CONTAINS(p2,term2)"),
+ context,
+ selector);
+ assertThat(constraint, is(instanceOf(And.class)));
+ And and = (And)constraint;
+
+ assertThat(and.getLeft(), is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)and.getLeft();
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+
+ assertThat(and.getRight(), is(instanceOf(And.class)));
+ And secondAnd = (And)and.getRight();
+
+ assertThat(secondAnd.getLeft(), is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search1 = (FullTextSearch)secondAnd.getLeft();
+ assertThat(search1.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search1.getPropertyName(), is(name("p1")));
+ assertThat(search1.getFullTextSearchExpression(), is("term1"));
+
+ assertThat(secondAnd.getRight(), is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search2 = (FullTextSearch)secondAnd.getRight();
+ assertThat(search2.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search2.getPropertyName(), is(name("p2")));
+ assertThat(search2.getFullTextSearchExpression(), is("term2"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithAndExpressionWithNoSecondConstraint() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ parser.parseConstraint(tokens("ISSAMENODE('/a/b/c') AND WHAT THE
HECK IS THIS"), context, selector);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - OR
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithOrExpressionWithNoParentheses() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE('/a/b/c') OR
CONTAINS(p1,term1)"), context, selector);
+ assertThat(constraint, is(instanceOf(Or.class)));
+ Or or = (Or)constraint;
+
+ assertThat(or.getLeft(), is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)or.getLeft();
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+
+ assertThat(or.getRight(), is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search = (FullTextSearch)or.getRight();
+ assertThat(search.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search.getPropertyName(), is(name("p1")));
+ assertThat(search.getFullTextSearchExpression(), is("term1"));
+
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithOrExpressionWithNoSecondConstraint() {
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ parser.parseConstraint(tokens("ISSAMENODE('/a/b/c') OR WHAT THE HECK
IS THIS"), context, selector);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - NOT
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseConstraintFromStringWithNotSameNodeExpression() {
+ Constraint constraint =
parser.parseConstraint(tokens("NOT(ISSAMENODE(tableA,'/a/b/c'))"),
context, mock(Source.class));
+ assertThat(constraint, is(instanceOf(Not.class)));
+ Not not = (Not)constraint;
+ assertThat(not.getConstraint(), is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)not.getConstraint();
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithNotConstraintWithOutOpeningParenthesis() {
+ parser.parseConstraint(tokens("NOT CONTAINS(propertyA 'term1 term2
-term3')"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithNotConstraintWithOutClosingParenthesis() {
+ parser.parseConstraint(tokens("NOT( CONTAINS(propertyA 'term1 term2
-term3') BLAH"), context, mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - CONTAINS
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsContainsExpressionWithPropertyAndNoSelectorNameOnlyIfThereIsOneSelectorSource()
{
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("CONTAINS(propertyA,'term1 term2
-term3')"), context, selector);
+ assertThat(constraint, is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search = (FullTextSearch)constraint;
+ assertThat(search.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search.getPropertyName(), is(name("propertyA")));
+ assertThat(search.getFullTextSearchExpression(), is("term1 term2
-term3"));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsContainsExpressionWithSelectorNameAndProperty() {
+ Constraint constraint =
parser.parseConstraint(tokens("CONTAINS(tableA.propertyA,'term1 term2
-term3')"),
+ context,
+ mock(Source.class));
+ assertThat(constraint, is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search = (FullTextSearch)constraint;
+ assertThat(search.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search.getPropertyName(), is(name("propertyA")));
+ assertThat(search.getFullTextSearchExpression(), is("term1 term2
-term3"));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsContainsExpressionWithSelectorNameAndAnyProperty() {
+ Constraint constraint =
parser.parseConstraint(tokens("CONTAINS(tableA.*,'term1 term2
-term3')"),
+ context,
+ mock(Source.class));
+ assertThat(constraint, is(instanceOf(FullTextSearch.class)));
+ FullTextSearch search = (FullTextSearch)constraint;
+ assertThat(search.getSelectorName(), is(selectorName("tableA")));
+ assertThat(search.getPropertyName(), is(nullValue()));
+ assertThat(search.getFullTextSearchExpression(), is("term1 term2
-term3"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithContainsExpressionWithNoCommaAfterSelectorName()
{
+ parser.parseConstraint(tokens("CONTAINS(propertyA 'term1 term2
-term3')"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithContainsExpressionWithNoClosingParenthesis() {
+ parser.parseConstraint(tokens("CONTAINS(propertyA,'term1 term2
-term3' OTHER"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithContainsExpressionWithNoOpeningParenthesis() {
+ parser.parseConstraint(tokens("CONTAINS propertyA,'term1 term2
-term3')"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithContainsExpressionWithNoSelectorNameIfSourceIsNotSelector()
{
+ parser.parseConstraint(tokens("CONTAINS(propertyA,'term1 term2
-term3')"), context, mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - ISSAMENODE
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsSameNodeExpressionWithPathOnlyIfThereIsOneSelectorSource()
{
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE('/a/b/c')"), context,
selector);
+ assertThat(constraint, is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)constraint;
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsSameNodeExpressionWithSelectorNameAndPath() {
+ Constraint constraint =
parser.parseConstraint(tokens("ISSAMENODE(tableA,'/a/b/c')"), context,
mock(Source.class));
+ assertThat(constraint, is(instanceOf(SameNode.class)));
+ SameNode same = (SameNode)constraint;
+ assertThat(same.getSelectorName(), is(selectorName("tableA")));
+ assertThat(same.getPath(), is(path("/a/b/c")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsSameNodeExpressionWithNoCommaAfterSelectorName()
{
+ parser.parseConstraint(tokens("ISSAMENODE(tableA '/a/b/c')"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsSameNodeExpressionWithNoClosingParenthesis() {
+ parser.parseConstraint(tokens("ISSAMENODE(tableA,'/a/b/c'
AND"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsSameNodeExpressionWithNoOpeningParenthesis() {
+ parser.parseConstraint(tokens("ISSAMENODE tableA,'/a/b/c')"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsSameNodeExpressionWithNoSelectorNameIfSourceIsNotSelector()
{
+ parser.parseConstraint(tokens("ISSAMENODE('/a/b/c')"), context,
mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - ISCHILDNODE
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsChildNodeExpressionWithPathOnlyIfThereIsOneSelectorSource()
{
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISCHILDNODE('/a/b/c')"), context,
selector);
+ assertThat(constraint, is(instanceOf(ChildNode.class)));
+ ChildNode child = (ChildNode)constraint;
+ assertThat(child.getSelectorName(), is(selectorName("tableA")));
+ assertThat(child.getParentPath(), is(path("/a/b/c")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsChildNodeExpressionWithSelectorNameAndPath() {
+ Constraint constraint =
parser.parseConstraint(tokens("ISCHILDNODE(tableA,'/a/b/c')"), context,
mock(Source.class));
+ assertThat(constraint, is(instanceOf(ChildNode.class)));
+ ChildNode child = (ChildNode)constraint;
+ assertThat(child.getSelectorName(), is(selectorName("tableA")));
+ assertThat(child.getParentPath(), is(path("/a/b/c")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsChildNodeExpressionWithNoCommaAfterSelectorName()
{
+ parser.parseConstraint(tokens("ISCHILDNODE(tableA '/a/b/c')"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsChildNodeExpressionWithNoClosingParenthesis()
{
+ parser.parseConstraint(tokens("ISCHILDNODE(tableA,'/a/b/c'
AND"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsChildNodeExpressionWithNoOpeningParenthesis()
{
+ parser.parseConstraint(tokens("ISCHILDNODE tableA,'/a/b/c')"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsChildNodeExpressionWithNoSelectorNameIfSourceIsNotSelector()
{
+ parser.parseConstraint(tokens("ISCHILDNODE('/a/b/c')"),
context, mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseConstraint - ISDESCENDANTNODE
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsDescendantNodeExpressionWithPathOnlyIfThereIsOneSelectorSource()
{
+ NamedSelector selector = new NamedSelector(selectorName("tableA"));
+ Constraint constraint =
parser.parseConstraint(tokens("ISDESCENDANTNODE('/a/b/c')"), context,
selector);
+ assertThat(constraint, is(instanceOf(DescendantNode.class)));
+ DescendantNode descendant = (DescendantNode)constraint;
+ assertThat(descendant.getSelectorName(), is(selectorName("tableA")));
+ assertThat(descendant.getAncestorPath(), is(path("/a/b/c")));
+ }
+
+ @Test
+ public void
shouldParseConstraintFromStringWithIsDescendantNodeExpressionWithSelectorNameAndPath() {
+ Constraint constraint =
parser.parseConstraint(tokens("ISDESCENDANTNODE(tableA,'/a/b/c')"),
context, mock(Source.class));
+ assertThat(constraint, is(instanceOf(DescendantNode.class)));
+ DescendantNode descendant = (DescendantNode)constraint;
+ assertThat(descendant.getSelectorName(), is(selectorName("tableA")));
+ assertThat(descendant.getAncestorPath(), is(path("/a/b/c")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsDescendantNodeExpressionWithNoCommaAfterSelectorName()
{
+ parser.parseConstraint(tokens("ISDESCENDANTNODE(tableA
'/a/b/c')"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsDescendantNodeExpressionWithNoClosingParenthesis()
{
+ parser.parseConstraint(tokens("ISDESCENDANTNODE(tableA,'/a/b/c'
AND"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsDescendantNodeExpressionWithNoOpeningParenthesis()
{
+ parser.parseConstraint(tokens("ISDESCENDANTNODE
tableA,'/a/b/c')"), context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseConstraintFromStringWithIsDescendantNodeExpressionWithNoSelectorNameIfSourceIsNotSelector()
{
+ parser.parseConstraint(tokens("ISDESCENDANTNODE('/a/b/c')"),
context, mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseFullTextSearchExpression
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseFullTextSearchExpressionFromStringWithValidExpression() {
+ Position pos = new Position(100, 13);
+ FullTextSearch.Term result = parser.parseFullTextSearchExpression("term1
term2 OR -term3 OR -term4 OR term5", pos);
+ assertThat(result, is(notNullValue()));
+ assertThat(result, is(instanceOf(Disjunction.class)));
+ Disjunction disjunction = (Disjunction)result;
+ assertThat(disjunction.getTerms().size(), is(4));
+ Conjunction conjunction1 = (Conjunction)disjunction.getTerms().get(0);
+ SimpleTerm term3 = (SimpleTerm)disjunction.getTerms().get(1);
+ SimpleTerm term4 = (SimpleTerm)disjunction.getTerms().get(2);
+ SimpleTerm term5 = (SimpleTerm)disjunction.getTerms().get(3);
+ FullTextSearchParserTest.assertHasSimpleTerms(conjunction1, "term1",
"term2");
+ FullTextSearchParserTest.assertSimpleTerm(term3, "term3", true,
false);
+ FullTextSearchParserTest.assertSimpleTerm(term4, "term4", true,
false);
+ FullTextSearchParserTest.assertSimpleTerm(term5, "term5", false,
false);
+ }
+
+ @Test
+ public void shouldConvertPositionWhenUnableToParseFullTextSearchExpression() {
+ try {
+ parser.parseFullTextSearchExpression("", new Position(100, 13));
+ fail("Should have thrown an exception");
+ } catch (ParsingException e) {
+ assertThat(e.getPosition().getLine(), is(100));
+ assertThat(e.getPosition().getColumn(), is(13));
+ }
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseComparisonOperator
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseComparisonOperator() {
+ // Same case
+ for (Operator operator : Operator.values()) {
+ assertThat(parser.parseComparisonOperator(tokens(operator.getSymbol())),
is(operator));
+ }
+ // Upper case
+ for (Operator operator : Operator.values()) {
+
assertThat(parser.parseComparisonOperator(tokens(operator.getSymbol().toUpperCase())),
is(operator));
+ }
+ // Lower case
+ for (Operator operator : Operator.values()) {
+
assertThat(parser.parseComparisonOperator(tokens(operator.getSymbol().toLowerCase())),
is(operator));
+ }
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseComparisonOperatorIfOperatorIsUnknown() {
+ parser.parseComparisonOperator(tokens("FOO"));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseOrderBy
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParserOrderByWithOneOrdering() {
+ List<Ordering> orderBy = parser.parseOrderBy(tokens("ORDER BY
NAME(tableA) ASC"), context, mock(Source.class));
+ assertThat(orderBy.size(), is(1));
+ Ordering first = orderBy.get(0);
+ assertThat(first.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(first.getOrder(), is(Order.ASCENDING));
+ }
+
+ @Test
+ public void shouldParserOrderByWithTwoOrderings() {
+ List<Ordering> orderBy = parser.parseOrderBy(tokens("ORDER BY
NAME(tableA) ASC, SCORE(tableB) DESC"),
+ context,
+ mock(Source.class));
+ assertThat(orderBy.size(), is(2));
+ Ordering first = orderBy.get(0);
+ assertThat(first.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(first.getOrder(), is(Order.ASCENDING));
+ Ordering second = orderBy.get(1);
+ assertThat(second.getOperand(), is(instanceOf(FullTextSearchScore.class)));
+ assertThat(second.getOrder(), is(Order.DESCENDING));
+ }
+
+ @Test
+ public void shouldParserOrderByWithMultipleOrderings() {
+ List<Ordering> orderBy = parser.parseOrderBy(tokens("ORDER BY
NAME(tableA) ASC, SCORE(tableB) DESC, LENGTH(tableC.id) ASC"),
+ context,
+ mock(Source.class));
+ assertThat(orderBy.size(), is(3));
+ Ordering first = orderBy.get(0);
+ assertThat(first.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(first.getOrder(), is(Order.ASCENDING));
+ Ordering second = orderBy.get(1);
+ assertThat(second.getOperand(), is(instanceOf(FullTextSearchScore.class)));
+ assertThat(second.getOrder(), is(Order.DESCENDING));
+ Ordering third = orderBy.get(2);
+ assertThat(third.getOperand(), is(instanceOf(Length.class)));
+ assertThat(third.getOrder(), is(Order.ASCENDING));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseOrderByIfCommaNotFollowedByAnotherOrdering() {
+ parser.parseOrderBy(tokens("ORDER BY NAME(tableA) ASC, NOT A VALID
ORDERING"), context, mock(Source.class));
+ }
+
+ @Test
+ public void shouldReturnNullFromParseOrderByWithoutOrderByKeywords() {
+ assertThat(parser.parseOrderBy(tokens("NOT ORDER BY"), context,
mock(Source.class)), is(nullValue()));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseOrdering
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseOrderingFromDynamicOperandFollowedByAscendingKeyword() {
+ Ordering ordering = parser.parseOrdering(tokens("NAME(tableA) ASC"),
context, mock(Source.class));
+ assertThat(ordering.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(ordering.getOrder(), is(Order.ASCENDING));
+ }
+
+ @Test
+ public void shouldParseOrderingFromDynamicOperandFollowedByDecendingKeyword() {
+ Ordering ordering = parser.parseOrdering(tokens("NAME(tableA) DESC"),
context, mock(Source.class));
+ assertThat(ordering.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(ordering.getOrder(), is(Order.DESCENDING));
+ }
+
+ @Test
+ public void
shouldParseOrderingFromDynamicOperandAndDefaultToAscendingWhenNotFollowedByAscendingOrDescendingKeyword()
{
+ Ordering ordering = parser.parseOrdering(tokens("NAME(tableA) OTHER"),
context, mock(Source.class));
+ assertThat(ordering.getOperand(), is(instanceOf(NodeName.class)));
+ assertThat(ordering.getOrder(), is(Order.ASCENDING));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parsePropertyExistance
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParsePropertyExistanceFromPropertyNameWithSelectorNameAndPropertyNameFollowedByIsNotNull()
{
+ Constraint constraint =
parser.parsePropertyExistance(tokens("tableA.property1 IS NOT NULL"), context,
mock(Source.class));
+ assertThat(constraint, is(instanceOf(PropertyExistence.class)));
+ PropertyExistence p = (PropertyExistence)constraint;
+ assertThat(p.getPropertyName(), is(name("property1")));
+ assertThat(p.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParsePropertyExistanceFromPropertyNameWithPropertyNameAndNoSelectorNameFollowedByIsNotNull()
{
+ NamedSelector source = new NamedSelector(selectorName("tableA"));
+ Constraint constraint = parser.parsePropertyExistance(tokens("property1 IS
NOT NULL"), context, source);
+ assertThat(constraint, is(instanceOf(PropertyExistence.class)));
+ PropertyExistence p = (PropertyExistence)constraint;
+ assertThat(p.getPropertyName(), is(name("property1")));
+ assertThat(p.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParsePropertyExistanceFromPropertyNameWithNoSelectorNameIfSourceIsNotSelector()
{
+ parser.parsePropertyExistance(tokens("property1 IS NOT NULL"), context,
mock(Source.class));
+ }
+
+ @Test
+ public void
shouldParseNotPropertyExistanceFromPropertyNameWithSelectorNameAndPropertyNameFollowedByIsNull()
{
+ Constraint constraint =
parser.parsePropertyExistance(tokens("tableA.property1 IS NULL"), context,
mock(Source.class));
+ assertThat(constraint, is(instanceOf(Not.class)));
+ Not not = (Not)constraint;
+ assertThat(not.getConstraint(), is(instanceOf(PropertyExistence.class)));
+ PropertyExistence p = (PropertyExistence)not.getConstraint();
+ assertThat(p.getPropertyName(), is(name("property1")));
+ assertThat(p.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldReturnNullFromParsePropertyExistanceIfExpressionDoesNotMatchPattern() {
+ Source s = mock(Source.class);
+ assertThat(parser.parsePropertyExistance(tokens("tableA WILL NOT"),
context, s), is(nullValue()));
+ assertThat(parser.parsePropertyExistance(tokens("tableA.property1 NOT
NULL"), context, s), is(nullValue()));
+ assertThat(parser.parsePropertyExistance(tokens("tableA.property1 IS NOT
SOMETHING"), context, s), is(nullValue()));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseStaticOperand
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseStaticOperandFromStringWithBindVariable() {
+ StaticOperand operand = parser.parseStaticOperand(tokens("$VAR"),
context);
+ assertThat(operand, is(instanceOf(BindVariableName.class)));
+ BindVariableName var = (BindVariableName)operand;
+ assertThat(var.getVariableName(), is("VAR"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseStaticOperandFromStringWithBindVariableWithNoVariableName() {
+ parser.parseStaticOperand(tokens("$"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseStaticOperandFromStringWithBindVariableWithCharactersThatAreNotFromNCName()
{
+ parser.parseStaticOperand(tokens("$#2VAR"), context);
+ }
+
+ @Test
+ public void shouldParseStaticOperandFromStringWithLiteralValue() {
+ StaticOperand operand = parser.parseStaticOperand(tokens("CAST(123 AS
DOUBLE)"), context);
+ assertThat(operand, is(instanceOf(Literal.class)));
+ Literal literal = (Literal)operand;
+ assertThat((Double)literal.getValue(),
is(context.getValueFactories().getDoubleFactory().create("123")));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseLiteral
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastBooleanLiteralToString() {
+ assertThat((String)parser.parseLiteral(tokens("CAST(true AS STRING)"),
context).getValue(), is(Boolean.TRUE.toString()));
+ assertThat((String)parser.parseLiteral(tokens("CAST(false AS STRING)"),
context).getValue(), is(Boolean.FALSE.toString()));
+ assertThat((String)parser.parseLiteral(tokens("CAST(TRUE AS STRING)"),
context).getValue(), is(Boolean.TRUE.toString()));
+ assertThat((String)parser.parseLiteral(tokens("CAST(FALSE AS STRING)"),
context).getValue(), is(Boolean.FALSE.toString()));
+ assertThat((String)parser.parseLiteral(tokens("CAST('true' AS
stRinG)"), context).getValue(), is(Boolean.TRUE.toString()));
+ assertThat((String)parser.parseLiteral(tokens("CAST(\"false\" AS
string)"), context).getValue(),
+ is(Boolean.FALSE.toString()));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastBooleanLiteralToBinary() {
+ Binary binaryTrue = context.getValueFactories().getBinaryFactory().create(true);
+ Binary binaryFalse =
context.getValueFactories().getBinaryFactory().create(false);
+ assertThat((Binary)parser.parseLiteral(tokens("CAST(true AS BINARY)"),
context).getValue(), is(binaryTrue));
+ assertThat((Binary)parser.parseLiteral(tokens("CAST(false AS BINARY)"),
context).getValue(), is(binaryFalse));
+ assertThat((Binary)parser.parseLiteral(tokens("CAST(TRUE AS BINARY)"),
context).getValue(), is(binaryTrue));
+ assertThat((Binary)parser.parseLiteral(tokens("CAST(FALSE AS BINARY)"),
context).getValue(), is(binaryFalse));
+ assertThat((Binary)parser.parseLiteral(tokens("CAST('true' AS
biNarY)"), context).getValue(), is(binaryTrue));
+ assertThat((Binary)parser.parseLiteral(tokens("CAST(\"false\" AS
binary)"), context).getValue(), is(binaryFalse));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastBooleanLiteralToLong() {
+ parser.parseLiteral(tokens("CAST(true AS LONG)"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastBooleanLiteralToDouble() {
+ parser.parseLiteral(tokens("CAST(true AS DOUBLE)"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastBooleanLiteralToDate() {
+ parser.parseLiteral(tokens("CAST(true AS DATE)"), context);
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastLongLiteralToString() {
+ assertThat((String)parser.parseLiteral(tokens("CAST(123 AS STRING)"),
context).getValue(), is("123"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(+123 AS STRING)"),
context).getValue(), is("123"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(-123 AS STRING)"),
context).getValue(), is("-123"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(0 AS STRING)"),
context).getValue(), is("0"));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastLongLiteralToLong() {
+ assertThat((Long)parser.parseLiteral(tokens("CAST(123 AS LONG)"),
context).getValue(), is(123L));
+ assertThat((Long)parser.parseLiteral(tokens("CAST(+123 AS LONG)"),
context).getValue(), is(123L));
+ assertThat((Long)parser.parseLiteral(tokens("CAST(-123 AS LONG)"),
context).getValue(), is(-123L));
+ assertThat((Long)parser.parseLiteral(tokens("CAST(0 AS LONG)"),
context).getValue(), is(0L));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastDoubleLiteralToString() {
+ assertThat((String)parser.parseLiteral(tokens("CAST(1.23 AS STRING)"),
context).getValue(), is("1.23"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(+1.23 AS STRING)"),
context).getValue(), is("1.23"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(-1.23 AS STRING)"),
context).getValue(), is("-1.23"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(1.23e10 AS
STRING)"), context).getValue(), is("1.23E10"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(1.23e+10 AS
STRING)"), context).getValue(), is("1.23E10"));
+ assertThat((String)parser.parseLiteral(tokens("CAST(1.23e-10 AS
STRING)"), context).getValue(), is("1.23E-10"));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastDateLiteralToString() {
+ assertThat((String)parser.parseLiteral(tokens("CAST(2009-03-22T03:22:45.345Z
AS STRING)"), context).getValue(),
+ is("2009-03-22T03:22:45.345Z"));
+
assertThat((String)parser.parseLiteral(tokens("CAST(2009-03-22T03:22:45.345UTC AS
STRING)"), context).getValue(),
+ is("2009-03-22T03:22:45.345Z"));
+
assertThat((String)parser.parseLiteral(tokens("CAST(2009-03-22T03:22:45.3-01:00 AS
STRING)"), context).getValue(),
+ is("2009-03-22T04:22:45.300Z"));
+
assertThat((String)parser.parseLiteral(tokens("CAST(2009-03-22T03:22:45.345+01:00 AS
STRING)"), context).getValue(),
+ is("2009-03-22T02:22:45.345Z"));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastStringLiteralToName() {
+ assertThat((Name)parser.parseLiteral(tokens("CAST([dna:name] AS
NAME)"), context).getValue(), is(name("dna:name")));
+ assertThat((Name)parser.parseLiteral(tokens("CAST('dna:name' AS
NAME)"), context).getValue(), is(name("dna:name")));
+ assertThat((Name)parser.parseLiteral(tokens("CAST(\"dna:name\" AS
NAME)"), context).getValue(), is(name("dna:name")));
+ }
+
+ @Test
+ public void shouldParseLiteralFromStringWithCastStringLiteralToPath() {
+ assertThat((Path)parser.parseLiteral(tokens("CAST([/dna:name/a/b] AS
PATH)"), context).getValue(),
+ is(path("/dna:name/a/b")));
+ }
+
+ @Test
+ public void
shouldParseLiteralFromStringWithUncastLiteralValueAndRepresentValueAsStringRepresentation()
{
+ assertThat(parser.parseLiteral(tokens("true"), context).getValue(),
is((Object)Boolean.TRUE.toString()));
+ assertThat(parser.parseLiteral(tokens("false"), context).getValue(),
is((Object)Boolean.FALSE.toString()));
+ assertThat(parser.parseLiteral(tokens("TRUE"), context).getValue(),
is((Object)Boolean.TRUE.toString()));
+ assertThat(parser.parseLiteral(tokens("FALSE"), context).getValue(),
is((Object)Boolean.FALSE.toString()));
+ assertThat(parser.parseLiteral(tokens("123"), context).getValue(),
is((Object)"123"));
+ assertThat(parser.parseLiteral(tokens("+123"), context).getValue(),
is((Object)"123"));
+ assertThat(parser.parseLiteral(tokens("-123"), context).getValue(),
is((Object)"-123"));
+ assertThat(parser.parseLiteral(tokens("1.23"), context).getValue(),
is((Object)"1.23"));
+ assertThat(parser.parseLiteral(tokens("+1.23"), context).getValue(),
is((Object)"1.23"));
+ assertThat(parser.parseLiteral(tokens("-1.23"), context).getValue(),
is((Object)"-1.23"));
+ assertThat(parser.parseLiteral(tokens("1.23e10"), context).getValue(),
is((Object)"1.23E10"));
+ assertThat(parser.parseLiteral(tokens("1.23e+10"), context).getValue(),
is((Object)"1.23E10"));
+ assertThat(parser.parseLiteral(tokens("1.23e-10"), context).getValue(),
is((Object)"1.23E-10"));
+ assertThat(parser.parseLiteral(tokens("0"), context).getValue(),
is((Object)"0"));
+ assertThat(parser.parseLiteral(tokens("2009-03-22T03:22:45.345Z"),
context).getValue(),
+ is((Object)"2009-03-22T03:22:45.345Z"));
+ assertThat(parser.parseLiteral(tokens("2009-03-22T03:22:45.345UTC"),
context).getValue(),
+ is((Object)"2009-03-22T03:22:45.345Z"));
+ assertThat(parser.parseLiteral(tokens("2009-03-22T03:22:45.3-01:00"),
context).getValue(),
+ is((Object)"2009-03-22T04:22:45.300Z"));
+ assertThat(parser.parseLiteral(tokens("2009-03-22T03:22:45.345+01:00"),
context).getValue(),
+ is((Object)"2009-03-22T02:22:45.345Z"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastAndNoEndingParenthesis() {
+ parser.parseLiteral(tokens("CAST(123 AS STRING OTHER"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastAndNoOpeningParenthesis() {
+ parser.parseLiteral(tokens("CAST 123 AS STRING) OTHER"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastAndInvalidType() {
+ parser.parseLiteral(tokens("CAST(123 AS FOOD) OTHER"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastAndNoAsKeyword() {
+ parser.parseLiteral(tokens("CAST(123 STRING) OTHER"), context);
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLiteralFromStringWithCastAndNoLiteralValueBeforeAs() {
+ parser.parseLiteral(tokens("CAST(AS STRING) OTHER"), context);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseLiteralValue - unquoted
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseLiteralValueFromStringWithPositiveAndNegativeIntegerValues()
{
+ assertThat(parser.parseLiteralValue(tokens("123"), context),
is("123"));
+ assertThat(parser.parseLiteralValue(tokens("-123"), context),
is("-123"));
+ assertThat(parser.parseLiteralValue(tokens("- 123"), context),
is("-123"));
+ assertThat(parser.parseLiteralValue(tokens("+123"), context),
is("123"));
+ assertThat(parser.parseLiteralValue(tokens("+ 123"), context),
is("123"));
+ assertThat(parser.parseLiteralValue(tokens("0"), context),
is("0"));
+ }
+
+ @Test
+ public void shouldParseLiteralValueFromStringWithPositiveAndNegativeDecimalValues()
{
+ assertThat(parser.parseLiteralValue(tokens("1.23"), context),
is("1.23"));
+ assertThat(parser.parseLiteralValue(tokens("-1.23"), context),
is("-1.23"));
+ assertThat(parser.parseLiteralValue(tokens("+0.123"), context),
is("0.123"));
+ }
+
+ @Test
+ public void
shouldParseLiteralValueFromStringWithPositiveAndNegativeDecimalValuesInScientificNotation()
{
+ assertThat(parser.parseLiteralValue(tokens("1.23"), context),
is("1.23"));
+ assertThat(parser.parseLiteralValue(tokens("1.23e10"), context),
is("1.23E10"));
+ assertThat(parser.parseLiteralValue(tokens("- 1.23e10"), context),
is("-1.23E10"));
+ assertThat(parser.parseLiteralValue(tokens("- 1.23e-10"), context),
is("-1.23E-10"));
+ }
+
+ @Test
+ public void shouldParseLiteralValueFromStringWithBooleanValues() {
+ assertThat(parser.parseLiteralValue(tokens("true"), context),
is(Boolean.TRUE.toString()));
+ assertThat(parser.parseLiteralValue(tokens("false"), context),
is(Boolean.FALSE.toString()));
+ assertThat(parser.parseLiteralValue(tokens("TRUE"), context),
is(Boolean.TRUE.toString()));
+ assertThat(parser.parseLiteralValue(tokens("FALSE"), context),
is(Boolean.FALSE.toString()));
+ }
+
+ @Test
+ public void shouldParseLiteralValueFromStringWithDateValues() {
+ // sYYYY-MM-DDThh:mm:ss.sssTZD
+ assertThat(parser.parseLiteralValue(tokens("2009-03-22T03:22:45.345Z"),
context), is("2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("2009-03-22T03:22:45.345UTC"),
context), is("2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("2009-03-22T03:22:45.3-01:00"),
context), is("2009-03-22T04:22:45.300Z"));
+
assertThat(parser.parseLiteralValue(tokens("2009-03-22T03:22:45.345+01:00"),
context), is("2009-03-22T02:22:45.345Z"));
+
+
assertThat(parser.parseLiteralValue(tokens("-2009-03-22T03:22:45.345Z"),
context), is("-2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("-2009-03-22T03:22:45.345UTC"),
context), is("-2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("-2009-03-22T03:22:45.3-01:00"),
context), is("-2009-03-22T04:22:45.300Z"));
+
assertThat(parser.parseLiteralValue(tokens("-2009-03-22T03:22:45.345+01:00"),
context), is("-2009-03-22T02:22:45.345Z"));
+
+
assertThat(parser.parseLiteralValue(tokens("+2009-03-22T03:22:45.345Z"),
context), is("2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("+2009-03-22T03:22:45.345UTC"),
context), is("2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("+2009-03-22T03:22:45.3-01:00"),
context), is("2009-03-22T04:22:45.300Z"));
+
assertThat(parser.parseLiteralValue(tokens("+2009-03-22T03:22:45.345+01:00"),
context), is("2009-03-22T02:22:45.345Z"));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseLiteralValue - quoted
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseLiteralValueFromQuotedStringWithPositiveAndNegativeIntegerValues() {
+ assertThat(parser.parseLiteralValue(tokens("'123'"), context),
is("123"));
+ assertThat(parser.parseLiteralValue(tokens("'-123'"), context),
is("-123"));
+ assertThat(parser.parseLiteralValue(tokens("'- 123'"),
context), is("- 123"));
+ assertThat(parser.parseLiteralValue(tokens("'+123'"), context),
is("+123"));
+ assertThat(parser.parseLiteralValue(tokens("'+ 123'"),
context), is("+ 123"));
+ assertThat(parser.parseLiteralValue(tokens("'0'"), context),
is("0"));
+ }
+
+ @Test
+ public void
shouldParseLiteralValueFromQuotedStringWithPositiveAndNegativeDecimalValues() {
+ assertThat(parser.parseLiteralValue(tokens("'1.23'"), context),
is("1.23"));
+ assertThat(parser.parseLiteralValue(tokens("'-1.23'"),
context), is("-1.23"));
+ assertThat(parser.parseLiteralValue(tokens("'+0.123'"),
context), is("+0.123"));
+ }
+
+ @Test
+ public void
shouldParseLiteralValueFromQuotedStringWithPositiveAndNegativeDecimalValuesInScientificNotation()
{
+ assertThat(parser.parseLiteralValue(tokens("'1.23'"), context),
is("1.23"));
+ assertThat(parser.parseLiteralValue(tokens("'1.23e10'"),
context), is("1.23e10"));
+ assertThat(parser.parseLiteralValue(tokens("'- 1.23e10'"),
context), is("- 1.23e10"));
+ assertThat(parser.parseLiteralValue(tokens("'- 1.23e-10'"),
context), is("- 1.23e-10"));
+ }
+
+ @Test
+ public void shouldParseLiteralValueFromQuotedStringWithBooleanValues() {
+ assertThat(parser.parseLiteralValue(tokens("'true'"), context),
is("true"));
+ assertThat(parser.parseLiteralValue(tokens("'false'"),
context), is("false"));
+ assertThat(parser.parseLiteralValue(tokens("'TRUE'"), context),
is("TRUE"));
+ assertThat(parser.parseLiteralValue(tokens("'FALSE'"),
context), is("FALSE"));
+ }
+
+ @Test
+ public void shouldParseLiteralValueFromQuotedStringWithDateValues() {
+ // sYYYY-MM-DDThh:mm:ss.sssTZD
+
assertThat(parser.parseLiteralValue(tokens("'2009-03-22T03:22:45.345Z'"),
context), is("2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("'2009-03-22T03:22:45.345UTC'"),
context), is("2009-03-22T03:22:45.345UTC"));
+
assertThat(parser.parseLiteralValue(tokens("'2009-03-22T03:22:45.3-01:00'"),
context), is("2009-03-22T03:22:45.3-01:00"));
+
assertThat(parser.parseLiteralValue(tokens("'2009-03-22T03:22:45.345+01:00'"),
context),
+ is("2009-03-22T03:22:45.345+01:00"));
+
+
assertThat(parser.parseLiteralValue(tokens("'-2009-03-22T03:22:45.345Z'"),
context), is("-2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("'-2009-03-22T03:22:45.345UTC'"),
context), is("-2009-03-22T03:22:45.345UTC"));
+
assertThat(parser.parseLiteralValue(tokens("'-2009-03-22T03:22:45.3-01:00'"),
context),
+ is("-2009-03-22T03:22:45.3-01:00"));
+
assertThat(parser.parseLiteralValue(tokens("'-2009-03-22T03:22:45.345+01:00'"),
context),
+ is("-2009-03-22T03:22:45.345+01:00"));
+
+
assertThat(parser.parseLiteralValue(tokens("'+2009-03-22T03:22:45.345Z'"),
context), is("+2009-03-22T03:22:45.345Z"));
+
assertThat(parser.parseLiteralValue(tokens("'+2009-03-22T03:22:45.345UTC'"),
context), is("+2009-03-22T03:22:45.345UTC"));
+
assertThat(parser.parseLiteralValue(tokens("'+2009-03-22T03:22:45.3-01:00'"),
context),
+ is("+2009-03-22T03:22:45.3-01:00"));
+
assertThat(parser.parseLiteralValue(tokens("'+2009-03-22T03:22:45.345+01:00'"),
context),
+ is("+2009-03-22T03:22:45.345+01:00"));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - LENGTH
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseDynamicOperandFromStringContainingLengthOfPropertyValue() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("LENGTH(tableA.property)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(Length.class)));
+ Length length = (Length)operand;
+ assertThat(length.getPropertyValue().getPropertyName(),
is(name("property")));
+ assertThat(length.getPropertyValue().getSelectorName(),
is(selectorName("tableA")));
+ assertThat(length.getSelectorName(), is(selectorName("tableA")));
+
+ Source source = new NamedSelector(selectorName("tableA"));
+ operand = parser.parseDynamicOperand(tokens("LENGTH(property)"),
context, source);
+ assertThat(operand, is(instanceOf(Length.class)));
+ length = (Length)operand;
+ assertThat(length.getPropertyValue().getPropertyName(),
is(name("property")));
+ assertThat(length.getPropertyValue().getSelectorName(),
is(selectorName("tableA")));
+ assertThat(length.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLengthWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("LENGTH(tableA.property other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLengthWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("LENGTH tableA.property other"),
context, mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - LOWER
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingLowerOfAnotherDynamicOperand() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("LOWER(tableA.property)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(LowerCase.class)));
+ LowerCase lower = (LowerCase)operand;
+ assertThat(lower.getSelectorName(), is(selectorName("tableA")));
+ assertThat(lower.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)lower.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+
+ Source source = new NamedSelector(selectorName("tableA"));
+ operand = parser.parseDynamicOperand(tokens("LOWER(property)"),
context, source);
+ assertThat(operand, is(instanceOf(LowerCase.class)));
+ lower = (LowerCase)operand;
+ assertThat(lower.getSelectorName(), is(selectorName("tableA")));
+ assertThat(lower.getOperand(), is(instanceOf(PropertyValue.class)));
+ value = (PropertyValue)lower.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingLowerOfUpperCaseOfAnotherOperand() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("LOWER(UPPER(tableA.property))"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(LowerCase.class)));
+ LowerCase lower = (LowerCase)operand;
+ assertThat(lower.getSelectorName(), is(selectorName("tableA")));
+ assertThat(lower.getOperand(), is(instanceOf(UpperCase.class)));
+ UpperCase upper = (UpperCase)lower.getOperand();
+ assertThat(upper.getSelectorName(), is(selectorName("tableA")));
+ assertThat(upper.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)upper.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLowerWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("LOWER(tableA.property other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLowerWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("LOWER tableA.property other"),
context, mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - UPPER
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingUpperOfAnotherDynamicOperand() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("UPPER(tableA.property)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(UpperCase.class)));
+ UpperCase upper = (UpperCase)operand;
+ assertThat(upper.getSelectorName(), is(selectorName("tableA")));
+ assertThat(upper.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)upper.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+
+ Source source = new NamedSelector(selectorName("tableA"));
+ operand = parser.parseDynamicOperand(tokens("UPPER(property)"),
context, source);
+ assertThat(operand, is(instanceOf(UpperCase.class)));
+ upper = (UpperCase)operand;
+ assertThat(upper.getSelectorName(), is(selectorName("tableA")));
+ assertThat(upper.getOperand(), is(instanceOf(PropertyValue.class)));
+ value = (PropertyValue)upper.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingUpperOfLowerCaseOfAnotherOperand() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("UPPER(LOWER(tableA.property))"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(UpperCase.class)));
+ UpperCase upper = (UpperCase)operand;
+ assertThat(upper.getSelectorName(), is(selectorName("tableA")));
+ assertThat(upper.getOperand(), is(instanceOf(LowerCase.class)));
+ LowerCase lower = (LowerCase)upper.getOperand();
+ assertThat(lower.getSelectorName(), is(selectorName("tableA")));
+ assertThat(lower.getOperand(), is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)lower.getOperand();
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingUpperWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("UPPER(tableA.property other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingUpperWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("Upper tableA.property other"),
context, mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - NAME
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseDynamicOperandFromStringContainingNameOfSelector() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("NAME(tableA)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(NodeName.class)));
+ NodeName name = (NodeName)operand;
+ assertThat(name.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingNameWithNoSelectorOnlyIfThereIsOneSelectorAsSource()
{
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand = parser.parseDynamicOperand(tokens("NAME()"),
context, source);
+ assertThat(operand, is(instanceOf(NodeName.class)));
+ NodeName name = (NodeName)operand;
+ assertThat(name.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingNameWithNoSelectorIfTheSourceIsNotASelector()
{
+ parser.parseDynamicOperand(tokens("NAME()"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingNameWithSelectorNameAndProperty() {
+ parser.parseDynamicOperand(tokens("NAME(tableA.property) other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingNameWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("NAME(tableA other"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingNameWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("Name tableA other"), context,
mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - LOCALNAME
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseDynamicOperandFromStringContainingLocalNameOfSelector() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("LOCALNAME(tableA)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(NodeLocalName.class)));
+ NodeLocalName name = (NodeLocalName)operand;
+ assertThat(name.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingLocalNameWithNoSelectorOnlyIfThereIsOneSelectorAsSource()
{
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("LOCALNAME()"), context, source);
+ assertThat(operand, is(instanceOf(NodeLocalName.class)));
+ NodeLocalName name = (NodeLocalName)operand;
+ assertThat(name.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLocalNameWithNoSelectorIfTheSourceIsNotASelector()
{
+ parser.parseDynamicOperand(tokens("LOCALNAME()"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLocalNameWithSelectorNameAndProperty()
{
+ parser.parseDynamicOperand(tokens("LOCALNAME(tableA.property) other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLocalNameWithoutClosingParenthesis() {
+ parser.parseDynamicOperand(tokens("LOCALNAME(tableA other"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingLocalNameWithoutOpeningParenthesis() {
+ parser.parseDynamicOperand(tokens("LocalName tableA other"), context,
mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - SCORE
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingFullTextSearchScoreOfSelector() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("SCORE(tableA)"), context,
mock(Source.class));
+ assertThat(operand, is(instanceOf(FullTextSearchScore.class)));
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ assertThat(score.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringContainingFullTextSearchScoreWithNoSelectorOnlyIfThereIsOneSelectorAsSource()
{
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand = parser.parseDynamicOperand(tokens("SCORE()"),
context, source);
+ assertThat(operand, is(instanceOf(FullTextSearchScore.class)));
+ FullTextSearchScore score = (FullTextSearchScore)operand;
+ assertThat(score.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingFullTextSearchScoreWithNoSelectorIfTheSourceIsNotASelector()
{
+ parser.parseDynamicOperand(tokens("SCORE()"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingFullTextSearchScoreWithWithSelectorNameAndProperty()
{
+ parser.parseDynamicOperand(tokens("SCORE(tableA.property) other"),
context, mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingFullTextSearchScoreWithoutClosingParenthesis()
{
+ parser.parseDynamicOperand(tokens("SCORE(tableA other"), context,
mock(Source.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParseDynamicOperandFromStringContainingFullTextSearchScoreWithoutOpeningParenthesis()
{
+ parser.parseDynamicOperand(tokens("Score tableA other"), context,
mock(Source.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseDynamicOperand - PropertyValue
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringWithUnquotedSelectorNameAndUnquotedPropertyName() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("tableA.property"), context,
mock(Join.class));
+ assertThat(operand, is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)operand;
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringWithQuotedSelectorNameAndUnquotedPropertyName() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("[dna:tableA].property"), context,
mock(Join.class));
+ assertThat(operand, is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)operand;
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("dna:tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringWithQuotedSelectorNameAndQuotedPropertyName() {
+ DynamicOperand operand =
parser.parseDynamicOperand(tokens("[dna:tableA].[dna:property]"), context,
mock(Join.class));
+ assertThat(operand, is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)operand;
+ assertThat(value.getPropertyName(), is(name("dna:property")));
+ assertThat(value.getSelectorName(), is(selectorName("dna:tableA")));
+ }
+
+ @Test
+ public void
shouldParseDynamicOperandFromStringWithOnlyPropertyNameIfSourceIsSelector() {
+ Source source = new NamedSelector(selectorName("tableA"));
+ DynamicOperand operand = parser.parseDynamicOperand(tokens("property"),
context, source);
+ assertThat(operand, is(instanceOf(PropertyValue.class)));
+ PropertyValue value = (PropertyValue)operand;
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToDynamicOperandValueFromStringWithOnlyPropertyNameIfSourceIsNotSelector() {
+ parser.parsePropertyValue(tokens("property"), context,
mock(Join.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseDynamicOperandFromStringWithOnlySelectorNameAndPeriod()
{
+ parser.parsePropertyValue(tokens("tableA. "), context,
mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parsePropertyValue
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void
shouldParsePropertyValueFromStringWithUnquotedSelectorNameAndUnquotedPropertyName() {
+ PropertyValue value =
parser.parsePropertyValue(tokens("tableA.property"), context,
mock(Join.class));
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test
+ public void
shouldParsePropertyValueFromStringWithQuotedSelectorNameAndUnquotedPropertyName() {
+ PropertyValue value =
parser.parsePropertyValue(tokens("[dna:tableA].property"), context,
mock(Join.class));
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("dna:tableA")));
+ }
+
+ @Test
+ public void
shouldParsePropertyValueFromStringWithQuotedSelectorNameAndQuotedPropertyName() {
+ PropertyValue value =
parser.parsePropertyValue(tokens("[dna:tableA].[dna:property]"), context,
mock(Join.class));
+ assertThat(value.getPropertyName(), is(name("dna:property")));
+ assertThat(value.getSelectorName(), is(selectorName("dna:tableA")));
+ }
+
+ @Test
+ public void
shouldParsePropertyValueFromStringWithOnlyPropertyNameIfSourceIsSelector() {
+ Source source = new NamedSelector(selectorName("tableA"));
+ PropertyValue value = parser.parsePropertyValue(tokens("property"),
context, source);
+ assertThat(value.getPropertyName(), is(name("property")));
+ assertThat(value.getSelectorName(), is(selectorName("tableA")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void
shouldFailToParsePropertyValueFromStringWithOnlyPropertyNameIfSourceIsNotSelector() {
+ parser.parsePropertyValue(tokens("property"), context,
mock(Join.class));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParsePropertyValueFromStringWithOnlySelectorNameAndPeriod()
{
+ parser.parsePropertyValue(tokens("tableA. "), context,
mock(Join.class));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseLimit
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseLimitFromFormWithJustOneNumber() {
+ Limit limit = parser.parseLimit(tokens("LIMIT 10"));
+ assertThat(limit.getRowLimit(), is(10));
+ assertThat(limit.getOffset(), is(0));
+
+ limit = parser.parseLimit(tokens("LIMIT 10 NONOFFSET"));
+ assertThat(limit.getRowLimit(), is(10));
+ assertThat(limit.getOffset(), is(0));
+ }
+
+ @Test
+ public void shouldParseLimitFromFormWithRowLimitAndOffset() {
+ Limit limit = parser.parseLimit(tokens("LIMIT 10 OFFSET 30"));
+ assertThat(limit.getRowLimit(), is(10));
+ assertThat(limit.getOffset(), is(30));
+
+ limit = parser.parseLimit(tokens("LIMIT 10 OFFSET 30 OTHER"));
+ assertThat(limit.getRowLimit(), is(10));
+ assertThat(limit.getOffset(), is(30));
+ }
+
+ @Test
+ public void shouldParseLimitFromFormWithTwoCommaSeparatedNumbers() {
+ Limit limit = parser.parseLimit(tokens("LIMIT 10,30"));
+ assertThat(limit.getRowLimit(), is(20));
+ assertThat(limit.getOffset(), is(10));
+ }
+
+ @Test
+ public void shouldReturnNullFromParseLimitWithNoLimitKeyword() {
+ assertThat(parser.parseLimit(tokens("OTHER")), is(nullValue()));
+ assertThat(parser.parseLimit(tokens(" ")), is(nullValue()));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLimitIfRowLimitNumberTokenIsNotAnInteger() {
+ parser.parseLimit(tokens("LIMIT 10a OFFSET 30"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLimitIfOffsetNumberTokenIsNotAnInteger() {
+ parser.parseLimit(tokens("LIMIT 10 OFFSET 30a"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLimitIfStartingRowNumberTokenIsNotAnInteger() {
+ parser.parseLimit(tokens("LIMIT 10a,20"));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseLimitIfEndingRowNumberTokenIsNotAnInteger() {
+ parser.parseLimit(tokens("LIMIT 10,20a"));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseNamedSelector
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseNamedSelectorFromUnquotedNameWithNoAlias() {
+ NamedSelector selector = parser.parseNamedSelector(tokens("name"));
+ assertThat(selector.getName(), is(selectorName("name")));
+ assertThat(selector.getAlias(), is(nullValue()));
+ assertThat(selector.getAliasOrName(), is(selectorName("name")));
+ }
+
+ @Test
+ public void shouldParseNamedSelectorFromUnquotedNameWithUnquotedAlias() {
+ NamedSelector selector = parser.parseNamedSelector(tokens("name AS
alias"));
+ assertThat(selector.getName(), is(selectorName("name")));
+ assertThat(selector.getAlias(), is(selectorName("alias")));
+ assertThat(selector.getAliasOrName(), is(selectorName("alias")));
+ }
+
+ @Test
+ public void shouldParseNamedSelectorFromQuotedNameWithUnquotedAlias() {
+ NamedSelector selector = parser.parseNamedSelector(tokens("'name' AS
alias"));
+ assertThat(selector.getName(), is(selectorName("name")));
+ assertThat(selector.getAlias(), is(selectorName("alias")));
+ assertThat(selector.getAliasOrName(), is(selectorName("alias")));
+ }
+
+ @Test
+ public void shouldParseNamedSelectorFromQuotedNameWithQuotedAlias() {
+ NamedSelector selector = parser.parseNamedSelector(tokens("'name' AS
[alias]"));
+ assertThat(selector.getName(), is(selectorName("name")));
+ assertThat(selector.getAlias(), is(selectorName("alias")));
+ assertThat(selector.getAliasOrName(), is(selectorName("alias")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailInParseNamedSelectorIfNoMoreTokens() {
+ parser.parseNamedSelector(tokens(" "));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseSelectorName
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseSelectorNameFromUnquotedString() {
+ assertThat(parser.parseSelectorName(tokens("name")),
is(selectorName("name")));
+ }
+
+ @Test
+ public void shouldParseSelectorNameFromQuotedString() {
+ assertThat(parser.parseSelectorName(tokens("'name'")),
is(selectorName("name")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailInParseSelectorNameIfNoMoreTokens() {
+ parser.parseSelectorName(tokens(" "));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parseName
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParseNameFromSingleQuotedString() {
+ assertThat(parser.parseName(tokens("'jcr:name'"), context),
is(name("jcr:name")));
+ }
+
+ @Test
+ public void shouldParseNameFromDoubleQuotedString() {
+ assertThat(parser.parseName(tokens("\"jcr:name\""), context),
is(name("jcr:name")));
+ }
+
+ @Test
+ public void shouldParseNameFromBracketedString() {
+ assertThat(parser.parseName(tokens("[jcr:name]"), context),
is(name("jcr:name")));
+ }
+
+ @Test
+ public void shouldParseNameFromUnquotedStringWithoutPrefix() {
+ assertThat(parser.parseName(tokens("name"), context),
is(name("name")));
+ }
+
+ @Test
+ public void shouldParseNameFromSingleQuotedStringWithoutPrefix() {
+ assertThat(parser.parseName(tokens("'name'"), context),
is(name("name")));
+ }
+
+ @Test
+ public void shouldParseNameFromDoubleQuotedStringWithoutPrefix() {
+ assertThat(parser.parseName(tokens("\"name\""), context),
is(name("name")));
+ }
+
+ @Test
+ public void shouldParseNameFromBracketedStringWithoutPrefix() {
+ assertThat(parser.parseName(tokens("[name]"), context),
is(name("name")));
+ }
+
+ @Test
+ public void shouldParseNameFromBracketedAndQuotedStringWithoutPrefix() {
+ assertThat(parser.parseName(tokens("['name']"), context),
is(name("name")));
+ assertThat(parser.parseName(tokens("[\"name\"]"), context),
is(name("name")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailToParseNameIfNoMoreTokens() {
+ parser.parseName(tokens(" "), context);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // parsePath
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldParsePathFromUnquotedStringConsistingOfSql92Identifiers() {
+ String identifier =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+ assertThat(parser.parsePath(tokens(identifier), context), is(path(identifier)));
+ }
+
+ @Test
+ public void shouldParsePathFromSingleQuotedString() {
+ assertThat(parser.parsePath(tokens("'/a/b/c/dna:something/d'"),
context), is(path("/a/b/c/dna:something/d")));
+ }
+
+ @Test
+ public void shouldParsePathFromDoubleQuotedString() {
+
assertThat(parser.parsePath(tokens("\"/a/b/c/dna:something/d\""),
context), is(path("/a/b/c/dna:something/d")));
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldFailInParsePathIfNoMoreTokens() {
+ parser.parsePath(tokens(" "), context);
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // removeBracketsAndQuotes
+ //
----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void shouldRemoveBracketsAndQuotes() {
+ assertThat(parser.removeBracketsAndQuotes("string"),
is("string"));
+ assertThat(parser.removeBracketsAndQuotes("[string]"),
is("string"));
+ assertThat(parser.removeBracketsAndQuotes("'string'"),
is("string"));
+ assertThat(parser.removeBracketsAndQuotes("\"string\""),
is("string"));
+ assertThat(parser.removeBracketsAndQuotes("word one and two"),
is("word one and two"));
+ assertThat(parser.removeBracketsAndQuotes("[word one and two]"),
is("word one and two"));
+ assertThat(parser.removeBracketsAndQuotes("'word one and
two'"), is("word one and two"));
+ assertThat(parser.removeBracketsAndQuotes("\"word one and
two\""), is("word one and two"));
+ }
+
+ //
----------------------------------------------------------------------------------------------------------------
+ // Utility methods
+ //
----------------------------------------------------------------------------------------------------------------
+
+ protected void parse( String query ) {
+ parser.parseQuery(query, context);
+ }
+
+ protected SelectorName selectorName( String name ) {
+ return new SelectorName(name);
+ }
+
+ protected Name name( String name ) {
+ return context.getValueFactories().getNameFactory().create(name);
+ }
+
+ protected Path path( String path ) {
+ return context.getValueFactories().getPathFactory().create(path);
+ }
+
+ protected TokenStream tokens( String content ) {
+ return new TokenStream(content, new SqlQueryParser.SqlTokenizer(false),
false).start();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlQueryParserTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Copied:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlTokenizerTest.java (from
rev 1233, trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndTokenizerTest.java)
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlTokenizerTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/parse/SqlTokenizerTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,355 @@
+/*
+ * 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.parse;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.LinkedList;
+import org.jboss.dna.common.text.ParsingException;
+import org.jboss.dna.common.text.Position;
+import org.jboss.dna.common.text.TokenStream.CharacterArrayStream;
+import org.jboss.dna.common.text.TokenStream.Tokens;
+import org.jboss.dna.graph.query.parse.SqlQueryParser.SqlTokenizer;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SqlTokenizerTest {
+
+ private SqlTokenizer tokenizer;
+ private Tokens tokenFactory;
+ private LinkedList<int[]> tokenValues;
+
+ @Before
+ public void beforeEach() {
+ tokenizer = new SqlTokenizer(true);
+ final LinkedList<int[]> tokenValues = new LinkedList<int[]>();
+ tokenFactory = new Tokens() {
+ public void addToken( Position position,
+ int index ) {
+ int[] token = new int[] {index, index + 1, 0};
+ tokenValues.add(token);
+ }
+
+ public void addToken( Position position,
+ int startIndex,
+ int endIndex ) {
+ int[] token = new int[] {startIndex, endIndex, 0};
+ tokenValues.add(token);
+ }
+
+ public void addToken( Position position,
+ int startIndex,
+ int endIndex,
+ int type ) {
+ int[] token = new int[] {startIndex, endIndex, type};
+ tokenValues.add(token);
+ }
+ };
+ this.tokenValues = tokenValues;
+ }
+
+ protected void tokenize( String input ) {
+ tokenizer.tokenize(new CharacterArrayStream(input.toCharArray()), tokenFactory);
+ }
+
+ protected void assertNextTokenIs( int startIndex,
+ int endIndex,
+ int type ) {
+ int[] token = tokenValues.removeFirst();
+ assertThat(token[0], is(startIndex));
+ assertThat(token[1], is(endIndex));
+ assertThat(token[2], is(type));
+ }
+
+ protected void assertNoMoreTokens() {
+ assertThat(tokenValues.isEmpty(), is(true));
+ }
+
+ @Test
+ public void shouldCreateNoTokensForEmptyContent() {
+ tokenize("");
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateNoTokensForContentWithOnlyWhitespace() {
+ tokenize(" \t \n \r\n \r ");
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForEachSymbolCharacter() {
+ String content = "(){}*,:+%?$]!<>|=:";
+ int numSymbols = content.length();
+ tokenize(content);
+ for (int i = 0; i != numSymbols; ++i) {
+ assertNextTokenIs(i, i + 1, SqlTokenizer.SYMBOL);
+ }
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldNotConsiderDoubleSlashAsComment() {
+ String content = "++//this\n";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 3, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(3, 4, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(4, content.length() - 1, SqlTokenizer.WORD); // -1 because
'\n' is not included
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForEndOfLineCommentUsingDoubleDash() {
+ String content = "++--this is a comment\n";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, content.length() - 1, SqlTokenizer.COMMENT); // -1 because
'\n' is not included
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForMultiLineComment() {
+ String content = "==/*this is a comment*/-";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, content.length() - 1, SqlTokenizer.COMMENT);
+ assertNextTokenIs(content.length() - 1, content.length(), SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForMultiLineCommentAtEndOfContent() {
+ String content = "==/*this is a comment*/";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, content.length(), SqlTokenizer.COMMENT);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForMultiLineCommentWithoutTerminatingCharacters() {
+ String content = "==/*this is a comment";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, content.length(), SqlTokenizer.COMMENT);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForMultiLineCommentWithoutAllTerminatingCharacters() {
+ String content = "==/*this is a comment*";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, content.length(), SqlTokenizer.COMMENT);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForSingleQuotedString() {
+ String content = "=='this is a single-quoted \n string'-";
+ assertThat(content.charAt(2), is('\''));
+ assertThat(content.charAt(35), is('\''));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 36, SqlTokenizer.QUOTED_STRING);
+ assertNextTokenIs(36, 37, SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForSingleQuotedStringWithEscapedSingleQuoteCharacters()
{
+ String content = "=='this \"is\" a \\'single-quoted\\'
\n string'-";
+ assertThat(content.charAt(2), is('\''));
+ assertThat(content.charAt(41), is('\''));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 42, SqlTokenizer.QUOTED_STRING);
+ assertNextTokenIs(42, 43, SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForSingleQuotedStringAtEndOfContent() {
+ String content = "=='this is a single-quoted \n string'";
+ assertThat(content.charAt(2), is('\''));
+ assertThat(content.charAt(35), is('\''));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 36, SqlTokenizer.QUOTED_STRING);
+ assertNoMoreTokens();
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldCreateTokenForSingleQuotedStringWithoutClosingQuote() {
+ String content = "=='this is a single-quoted \n string";
+ tokenize(content);
+ }
+
+ @Test
+ public void shouldCreateTokenForDoubleQuotedString() {
+ String content = "==\"this is a double-quoted \n string\"-";
+ assertThat(content.charAt(2), is('"'));
+ assertThat(content.charAt(35), is('"'));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 36, SqlTokenizer.QUOTED_STRING);
+ assertNextTokenIs(36, 37, SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForDoubleQuotedStringWithEscapedDoubleQuoteCharacters()
{
+ String content = "==\"this 'is' a
\\\"double-quoted\\\" \n string\"-";
+ assertThat(content.charAt(2), is('"'));
+ assertThat(content.charAt(41), is('"'));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 42, SqlTokenizer.QUOTED_STRING);
+ assertNextTokenIs(42, 43, SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokenForDoubleQuotedStringAtEndOfContent() {
+ String content = "==\"this is a double-quoted \n string\"";
+ assertThat(content.charAt(2), is('"'));
+ assertThat(content.charAt(35), is('"'));
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(2, 36, SqlTokenizer.QUOTED_STRING);
+ assertNoMoreTokens();
+ }
+
+ @Test( expected = ParsingException.class )
+ public void shouldCreateTokenForDoubleQuotedStringWithoutClosingQuote() {
+ String content = "==\"this is a double-quoted \n string";
+ tokenize(content);
+ }
+
+ @Test
+ public void shouldCreateTokensForWordsWithAlphabeticCharacters() {
+ String content = "This is a series of words.";
+ tokenize(content);
+ assertNextTokenIs(0, 4, SqlTokenizer.WORD);
+ assertNextTokenIs(5, 7, SqlTokenizer.WORD);
+ assertNextTokenIs(8, 9, SqlTokenizer.WORD);
+ assertNextTokenIs(10, 16, SqlTokenizer.WORD);
+ assertNextTokenIs(17, 19, SqlTokenizer.WORD);
+ assertNextTokenIs(20, 25, SqlTokenizer.WORD);
+ assertNextTokenIs(25, 26, SqlTokenizer.SYMBOL);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokensForWordsWithNumericCharacters() {
+ String content = "1234 4 5353.324";
+ tokenize(content);
+ assertNextTokenIs(0, 4, SqlTokenizer.WORD);
+ assertNextTokenIs(5, 6, SqlTokenizer.WORD);
+ assertNextTokenIs(7, 11, SqlTokenizer.WORD);
+ assertNextTokenIs(11, 12, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(12, 15, SqlTokenizer.WORD);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldCreateTokensForWordsWithAlphaNumericCharacters() {
+ String content = "123a 5353.324e100";
+ tokenize(content);
+ assertNextTokenIs(0, 4, SqlTokenizer.WORD);
+ assertNextTokenIs(5, 9, SqlTokenizer.WORD);
+ assertNextTokenIs(9, 10, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(10, 17, SqlTokenizer.WORD);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldParseAlphaNumericAndUnderscoreCharactersAsOneWordToken() {
+ String content =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
+ tokenize(content);
+ assertNextTokenIs(0, 63, SqlTokenizer.WORD);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldParseUnquotedNameWithPrefixAsSeparateTokens() {
+ String content = "dna:name";
+ tokenize(content);
+ assertNextTokenIs(0, 3, SqlTokenizer.WORD);
+ assertNextTokenIs(3, 4, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(4, 8, SqlTokenizer.WORD);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldParseQuotedNameWithPrefixAsOneToken() {
+ String content = "'dna:name'";
+ tokenize(content);
+ assertNextTokenIs(0, 10, SqlTokenizer.QUOTED_STRING);
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldParseUnquotedPathAsSeparateTokens() {
+ String content = "/a/b/c/dna:name[1]/e/f";
+ tokenize(content);
+ assertNextTokenIs(0, 1, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(1, 2, SqlTokenizer.WORD); // a
+ assertNextTokenIs(2, 3, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(3, 4, SqlTokenizer.WORD); // b
+ assertNextTokenIs(4, 5, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(5, 6, SqlTokenizer.WORD); // c
+ assertNextTokenIs(6, 7, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(7, 10, SqlTokenizer.WORD); // dna
+ assertNextTokenIs(10, 11, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(11, 15, SqlTokenizer.WORD); // name
+ assertNextTokenIs(15, 18, SqlTokenizer.QUOTED_STRING); // [1]
+ assertNextTokenIs(18, 19, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(19, 20, SqlTokenizer.WORD); // e
+ assertNextTokenIs(20, 21, SqlTokenizer.SYMBOL);
+ assertNextTokenIs(21, 22, SqlTokenizer.WORD); // f
+ assertNoMoreTokens();
+ }
+
+ @Test
+ public void shouldParseQuotedPathAsOneToken() {
+ String content = "'/a/b/c/dna:name[1]/e/f'";
+ tokenize(content);
+ assertNextTokenIs(0, 24, SqlTokenizer.QUOTED_STRING);
+ assertNoMoreTokens();
+ }
+}
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,165 @@
+/*
+ * 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.plan;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.Collection;
+import java.util.List;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.SimpleProblems;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.QueryBuilder;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+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.ImmutableSchemata;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class CanonicalPlannerTest {
+
+ private CanonicalPlanner planner;
+ private ExecutionContext context;
+ private QueryBuilder builder;
+ private PlanHints hints;
+ private QueryCommand query;
+ private PlanNode plan;
+ private Problems problems;
+ private Schemata schemata;
+ private ImmutableSchemata.Builder schemataBuilder;
+ private QueryContext queryContext;
+
+ @Before
+ public void beforeEach() {
+ planner = new CanonicalPlanner();
+ context = new ExecutionContext();
+ hints = new PlanHints();
+ builder = new QueryBuilder(context);
+ problems = new SimpleProblems();
+ schemataBuilder = ImmutableSchemata.createBuilder();
+ }
+
+ protected SelectorName selector( String name ) {
+ return new SelectorName(name);
+ }
+
+ @SuppressWarnings( "unchecked" )
+ protected void assertProjectNode( PlanNode node,
+ String... columnNames ) {
+ assertThat(node.getType(), is(Type.PROJECT));
+ if (columnNames.length != 0) {
+ assertThat(node.hasCollectionProperty(Property.PROJECT_COLUMNS), is(true));
+ }
+ List<Column> columns = node.getProperty(Property.PROJECT_COLUMNS,
List.class);
+ assertThat(columns.size(), is(columnNames.length));
+ for (int i = 0; i != columns.size(); ++i) {
+ Column column = columns.get(i);
+ assertThat(column.getColumnName(), is(columnNames[i]));
+ }
+ }
+
+ @SuppressWarnings( "unchecked" )
+ protected void assertSourceNode( PlanNode node,
+ String sourceName,
+ String sourceAlias,
+ String... availableColumns ) {
+ assertThat(node.getType(), is(Type.SOURCE));
+ assertThat(node.getProperty(Property.SOURCE_NAME, SelectorName.class),
is(selector(sourceName)));
+ if (sourceAlias != null) {
+ assertThat(node.getProperty(Property.SOURCE_ALIAS, SelectorName.class),
is(selector(sourceAlias)));
+ } else {
+ assertThat(node.hasProperty(Property.SOURCE_ALIAS), is(false));
+ }
+ Collection<Schemata.Column> columns =
(Collection)node.getProperty(Property.SOURCE_COLUMNS);
+ assertThat(columns.size(), is(availableColumns.length));
+ int i = 0;
+ for (Schemata.Column column : columns) {
+ String expectedName = availableColumns[i++];
+ assertThat(column.getName(), is(expectedName));
+ }
+ }
+
+ @Test
+ public void shouldProducePlanForSelectStarFromTable() {
+ schemata = schemataBuilder.addTable("__ALLNODES__",
"column1", "column2", "column3").build();
+ query = builder.selectStar().fromAllNodes().query();
+ queryContext = new QueryContext(context, hints, schemata, problems);
+ plan = planner.createPlan(queryContext, query);
+ assertThat(problems.isEmpty(), is(true));
+ assertProjectNode(plan, "column1", "column2",
"column3");
+ assertThat(plan.getType(), is(PlanNode.Type.PROJECT));
+ assertThat(plan.getChildCount(), is(1));
+ PlanNode source = plan.getFirstChild();
+ assertSourceNode(source, "__ALLNODES__", null, "column1",
"column2", "column3");
+ assertThat(source.getChildCount(), is(0));
+ System.out.println(plan);
+ }
+
+ @Test
+ public void shouldProduceErrorWhenSelectingNonExistantTable() {
+ schemata = schemataBuilder.addTable("someTable", "column1",
"column2", "column3").build();
+ query = builder.selectStar().fromAllNodes().query();
+ queryContext = new QueryContext(context, hints, schemata, problems);
+ plan = planner.createPlan(queryContext, query);
+ assertThat(problems.hasErrors(), is(true));
+ }
+
+ @Test
+ public void shouldProduceErrorWhenSelectingNonExistantColumnOnExistingTable() {
+ schemata = schemataBuilder.addTable("someTable", "column1",
"column2", "column3").build();
+ query = builder.select("column1",
"column4").from("someTable").query();
+ queryContext = new QueryContext(context, hints, schemata, problems);
+ plan = planner.createPlan(queryContext, query);
+ assertThat(problems.hasErrors(), is(true));
+ }
+
+ @Test
+ public void shouldProducePlanWhenSelectingAllColumnsOnExistingTable() {
+ schemata = schemataBuilder.addTable("someTable", "column1",
"column2", "column3").build();
+ query = builder.selectStar().from("someTable").query();
+ queryContext = new QueryContext(context, hints, schemata, problems);
+ plan = planner.createPlan(queryContext, query);
+ System.out.println(plan);
+ assertThat(problems.hasErrors(), is(false));
+ assertThat(problems.isEmpty(), is(true));
+ assertProjectNode(plan, "column1", "column2",
"column3");
+ assertThat(plan.getType(), is(PlanNode.Type.PROJECT));
+ assertThat(plan.getChildCount(), is(1));
+ PlanNode source = plan.getFirstChild();
+ assertSourceNode(source, "someTable", null, "column1",
"column2", "column3");
+ assertThat(source.getChildCount(), is(0));
+ }
+
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanHintsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanHintsTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanHintsTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,47 @@
+/*
+ * 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.plan;
+
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class PlanHintsTest {
+
+ private PlanHints hints;
+
+ @Before
+ public void beforeEach() {
+ this.hints = new PlanHints();
+ }
+
+ @Test
+ public void shouldProduceToString() {
+ this.hints.toString();
+ }
+
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanHintsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanNodeTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanNodeTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanNodeTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,558 @@
+/*
+ * 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.plan;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class PlanNodeTest {
+
+ private PlanNode node;
+ private PlanNode parent;
+
+ @Before
+ public void beforeEach() {
+ node = new PlanNode(Type.GROUP);
+ }
+
+ @After
+ public void afterEach() {
+ this.node = null;
+ this.parent = null;
+ }
+
+ @Test
+ public void shouldFindTypeGivenSymbolWithSameCase() {
+ for (Type type : Type.values()) {
+ assertThat(Type.forSymbol(type.getSymbol()), is(sameInstance(type)));
+ }
+ }
+
+ @Test
+ public void shouldFindTypeGivenSymbolWithLowerCase() {
+ for (Type type : Type.values()) {
+ assertThat(Type.forSymbol(type.getSymbol().toLowerCase()),
is(sameInstance(type)));
+ }
+ }
+
+ @Test
+ public void shouldFindTypeGivenSymbolWithUpperCase() {
+ for (Type type : Type.values()) {
+ assertThat(Type.forSymbol(type.getSymbol().toUpperCase()),
is(sameInstance(type)));
+ }
+ }
+
+ @Test
+ public void shouldFindTypeGivenSymbolWithLeadingAndTrailingWhitespace() {
+ for (Type type : Type.values()) {
+ assertThat(Type.forSymbol(" \t " + type.getSymbol() + " \t
\n"), is(sameInstance(type)));
+ }
+ }
+
+ @Test
+ public void shouldCreatePlanNodeWithTypeAndNoParent() {
+ for (Type type : Type.values()) {
+ node = new PlanNode(type);
+ assertThat(node.getType(), is(type));
+ assertThat(node.getParent(), is(nullValue()));
+ }
+ }
+
+ @Test
+ public void shouldCreatePlanNodeWithTypeAndParent() {
+ for (Type type : Type.values()) {
+ parent = new PlanNode(Type.JOIN);
+ node = new PlanNode(type, parent);
+ assertThat(node.getType(), is(type));
+ assertThat(node.getParent(), is(sameInstance(parent)));
+ assertThat(parent.getFirstChild(), is(sameInstance(node)));
+ assertThat(parent.getChildCount(), is(1));
+ }
+ }
+
+ @Test
+ public void shouldAddNodeToParentWhenConstructingChildNodeWithTypeAndParent() {
+ parent = new PlanNode(Type.JOIN);
+ int counter = 0;
+ for (Type type : Type.values()) {
+ node = new PlanNode(type, parent);
+ ++counter;
+ assertThat(node.getType(), is(type));
+ assertThat(node.getParent(), is(sameInstance(parent)));
+ assertThat(parent.getLastChild(), is(sameInstance(node)));
+ assertThat(parent.getChildCount(), is(counter));
+ }
+ }
+
+ @Test
+ public void shouldSetType() {
+ node = new PlanNode(Type.JOIN);
+ for (Type type : Type.values()) {
+ node.setType(type);
+ assertThat(node.getType(), is(type));
+ }
+ }
+
+ @Test
+ public void shouldGetFirstChildAndLastChildWithOneChild() {
+ parent = new PlanNode(Type.JOIN);
+ node = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(node)));
+ assertThat(parent.getLastChild(), is(sameInstance(node)));
+ }
+
+ @Test
+ public void shouldGetFirstChildAndLastChildWithTwoChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getLastChild(), is(sameInstance(child2)));
+ }
+
+ @Test
+ public void shouldGetFirstChildAndLastChildWithMoreThanTwoChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ new PlanNode(Type.ACCESS, parent);
+ PlanNode child3 = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getLastChild(), is(sameInstance(child3)));
+ }
+
+ @Test
+ public void shouldGetFirstChildAndLastChildWithNoChildren() {
+ parent = new PlanNode(Type.JOIN);
+ assertThat(parent.getFirstChild(), is(nullValue()));
+ assertThat(parent.getLastChild(), is(nullValue()));
+ }
+
+ @Test
+ public void shouldRemoveNodeFromExistingParentWhenSettingParentToNull() {
+ parent = new PlanNode(Type.JOIN);
+ node = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(node)));
+ assertThat(parent.getChildCount(), is(1));
+ node.setParent(null);
+ assertThat(parent.getChildCount(), is(0));
+ assertThat(node.getParent(), is(nullValue()));
+ }
+
+ @Test
+ public void shouldInsertNewParentNodeInBetweenExistingParentAndChild() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getLastChild(), is(sameInstance(child3)));
+ assertThat(parent.getChildCount(), is(3));
+ node = new PlanNode(Type.GROUP);
+ child2.insertAsParent(node);
+ assertThat(parent.getChildCount(), is(3));
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(node)));
+ assertThat(parent.getLastChild(), is(sameInstance(child3)));
+ assertThat(node.getParent(), is(sameInstance(parent)));
+ assertThat(child2.getParent(), is(sameInstance(node)));
+ }
+
+ @Test
+ public void shouldInsertNewParentNodeInAboveNodeWithoutParent() {
+ PlanNode child1 = new PlanNode(Type.ACCESS);
+ node = new PlanNode(Type.GROUP);
+ PlanNode nodeChild = new PlanNode(Type.JOIN, node);
+ // Perform the insertAsParent ...
+ child1.insertAsParent(node);
+ assertThat(node.getParent(), is(nullValue()));
+ assertThat(node.getChildCount(), is(2));
+ assertThat(node.getFirstChild(), is(sameInstance(nodeChild)));
+ assertThat(node.getLastChild(), is(sameInstance(child1)));
+ assertThat(child1.getParent(), is(sameInstance(node)));
+ }
+
+ @Test
+ public void shouldRemoveFromParentWhenThereIsAParent() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ PlanNode grandChild21 = new PlanNode(Type.LIMIT, child2);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getLastChild(), is(sameInstance(child3)));
+ assertThat(parent.getChildCount(), is(3));
+ assertThat(child2.getFirstChild(), is(sameInstance(grandChild21)));
+ // Perform the removeFromParent ...
+ assertThat(child2.removeFromParent(), is(sameInstance(parent)));
+ assertThat(parent.getFirstChild(), is(sameInstance(child1)));
+ assertThat(parent.getLastChild(), is(sameInstance(child3)));
+ assertThat(parent.getChildCount(), is(2));
+ // There should still be the child in the removed node ...
+ assertThat(child2.getFirstChild(), is(sameInstance(grandChild21)));
+ }
+
+ @Test
+ public void shouldRemoveFromParentWhenThereIsNoParent() {
+ node = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, node);
+ assertThat(node.getFirstChild(), is(sameInstance(child1)));
+ assertThat(node.getChildCount(), is(1));
+ // Perform the removeFromParent ...
+ assertThat(node.removeFromParent(), is(nullValue()));
+ assertThat(node.getFirstChild(), is(sameInstance(child1)));
+ assertThat(node.getChildCount(), is(1));
+ }
+
+ @Test
+ public void shouldReturnListOfChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ List<PlanNode> children = parent.getChildren();
+ assertThat(children.get(0), is(sameInstance(child1)));
+ assertThat(children.get(1), is(sameInstance(child2)));
+ assertThat(children.get(2), is(sameInstance(child3)));
+ assertThat(children.size(), is(3));
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldReturnImmutableListOfChildren() {
+ parent = new PlanNode(Type.JOIN);
+ new PlanNode(Type.ACCESS, parent);
+ new PlanNode(Type.DUP_REMOVE, parent);
+ new PlanNode(Type.GROUP, parent);
+ parent.getChildren().clear();
+ }
+
+ @Test
+ public void shouldReturnIteratorOfChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ Iterator<PlanNode> children = parent.iterator();
+ assertThat(children.next(), is(sameInstance(child1)));
+ assertThat(children.next(), is(sameInstance(child2)));
+ assertThat(children.next(), is(sameInstance(child3)));
+ assertThat(children.hasNext(), is(false));
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldReturnImmutableIteratorOfChildren() {
+ parent = new PlanNode(Type.JOIN);
+ new PlanNode(Type.ACCESS, parent);
+ new PlanNode(Type.DUP_REMOVE, parent);
+ new PlanNode(Type.GROUP, parent);
+ Iterator<PlanNode> iter = parent.iterator();
+ iter.next();
+ iter.remove();
+ }
+
+ @Test
+ public void shouldRemoveAllChildrenOfParentWithNoChildrenByReturningEmptyList() {
+ parent = new PlanNode(Type.JOIN);
+ // Perform the remove, and verify the list has all the children ...
+ List<PlanNode> children = parent.removeAllChildren();
+ assertThat(children.size(), is(0));
+ assertThat(parent.getChildCount(), is(0));
+ // Add a new child to the parent ...
+ PlanNode child1a = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1a)));
+ // The returned copy should not be modified ...
+ assertThat(children.size(), is(0));
+ }
+
+ @Test
+ public void shouldRemoveAllChildrenAndReturnCopyOfListOfChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ // Perform the remove, and verify the list has all the children ...
+ List<PlanNode> children = parent.removeAllChildren();
+ assertThat(children.get(0), is(sameInstance(child1)));
+ assertThat(children.get(1), is(sameInstance(child2)));
+ assertThat(children.get(2), is(sameInstance(child3)));
+ assertThat(children.size(), is(3));
+ assertThat(parent.getChildCount(), is(0));
+ // Add a new child to the parent ...
+ PlanNode child1a = new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getFirstChild(), is(sameInstance(child1a)));
+ // The returned copy should not be modified ...
+ assertThat(children.get(0), is(sameInstance(child1)));
+ assertThat(children.get(1), is(sameInstance(child2)));
+ assertThat(children.get(2), is(sameInstance(child3)));
+ assertThat(children.size(), is(3));
+ }
+
+ @Test
+ public void shouldReturnCorrectChildCount() {
+ parent = new PlanNode(Type.JOIN);
+ assertThat(parent.getChildCount(), is(0));
+ for (int i = 0; i != 10; ++i) {
+ new PlanNode(Type.ACCESS, parent);
+ assertThat(parent.getChildCount(), is(i + 1));
+ }
+ }
+
+ @Test
+ public void shouldAddChildrenAtEnd() {
+ parent = new PlanNode(Type.JOIN);
+ List<PlanNode> children = new ArrayList<PlanNode>();
+ children.add(new PlanNode(Type.ACCESS, parent));
+ children.add(new PlanNode(Type.GROUP, parent));
+ children.add(new PlanNode(Type.NULL, parent));
+ parent.addChildren(children);
+ int index = 0;
+ for (PlanNode child : children) {
+ assertThat(parent.getChild(index++), is(sameInstance(child)));
+ }
+ }
+
+ @Test
+ public void shouldRemoveChild() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Perform the remove, and verify children have changed ...
+ assertThat(parent.removeChild(child2), is(true));
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child3)));
+ }
+
+ @Test
+ public void shouldNotRemoveChildIfNotReallyAChild() {
+ node = new PlanNode(Type.PROJECT);
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Try to remove the non-child, and verify the children have no changed ...
+ assertThat(parent.removeChild(node), is(false));
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ }
+
+ @Test
+ public void shouldNotRemoveChildIfReferenceIsNull() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Try to remove the non-child, and verify the children have no changed ...
+ assertThat(parent.removeChild(null), is(false));
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ }
+
+ @Test
+ public void shouldExtractChildByRemovingIfChildHasNoChildren() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Perform the extraction ...
+ parent.extractChild(child2);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child3)));
+ assertThat(parent.getChildCount(), is(2));
+ }
+
+ @Test
+ public void shouldExtractChildByReplacingWithFirstGrandchild() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ PlanNode grandChild1 = new PlanNode(Type.SELECT, child2);
+ PlanNode grandChild2 = new PlanNode(Type.SET_OPERATION, child2);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Perform the extraction ...
+ parent.extractChild(child2);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(grandChild1)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ assertThat(parent.getChildCount(), is(3));
+ // The old child should still contain just the remaining child(ren) ...
+ assertThat(child2.getFirstChild(), is(sameInstance(grandChild2)));
+ assertThat(child2.getParent(), is(nullValue()));
+ }
+
+ @Test
+ public void shouldReplaceChild() {
+ PlanNode parentOfReplacement = new PlanNode(Type.SORT);
+ PlanNode replacement = new PlanNode(Type.SELECT, parentOfReplacement);
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Perform the replacement ...
+ assertThat(parent.replaceChild(child2, replacement), is(true));
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(replacement)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ assertThat(replacement.getParent(), is(sameInstance(parent)));
+ assertThat(child1.getParent(), is(sameInstance(parent)));
+ assertThat(child2.getParent(), is(nullValue()));
+ assertThat(child3.getParent(), is(sameInstance(parent)));
+ // The replacement should no longer be a child of its former parent ...
+ assertThat(parentOfReplacement.getChildCount(), is(0));
+ }
+
+ @Test
+ public void shouldReplaceChildWithAnotherChildToSwapPositions() {
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ // Perform the replacement ...
+ assertThat(parent.replaceChild(child2, child3), is(true));
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child3)));
+ assertThat(parent.getChild(2), is(sameInstance(child2)));
+ assertThat(child1.getParent(), is(sameInstance(parent)));
+ assertThat(child2.getParent(), is(sameInstance(parent)));
+ assertThat(child3.getParent(), is(sameInstance(parent)));
+ }
+
+ @Test
+ public void shouldNotReplaceChildIfChildNodeIsNotReallyAChild() {
+ PlanNode nonChild = new PlanNode(Type.PROJECT);
+ PlanNode replacement = new PlanNode(Type.SELECT);
+ parent = new PlanNode(Type.JOIN);
+ PlanNode child1 = new PlanNode(Type.ACCESS, parent);
+ PlanNode child2 = new PlanNode(Type.DUP_REMOVE, parent);
+ PlanNode child3 = new PlanNode(Type.GROUP, parent);
+ assertThat(parent.getChild(0), is(sameInstance(child1)));
+ assertThat(parent.getChild(1), is(sameInstance(child2)));
+ assertThat(parent.getChild(2), is(sameInstance(child3)));
+ assertThat(parent.replaceChild(nonChild, replacement), is(false));
+ }
+
+ @Test
+ public void shouldGetPathThatIncludesStartAndEndNodes() {
+ PlanNode root = new PlanNode(Type.JOIN);
+ PlanNode node1 = new PlanNode(Type.ACCESS, root);
+ PlanNode node2 = new PlanNode(Type.DUP_REMOVE, node1);
+ PlanNode node3 = new PlanNode(Type.GROUP, node2);
+ PlanNode node4 = new PlanNode(Type.SELECT, node3);
+ PlanNode node5 = new PlanNode(Type.SET_OPERATION, node4);
+ assertThat(root.getPathTo(root), is(path(root)));
+ assertThat(root.getPathTo(node1), is(path(root, node1)));
+ assertThat(root.getPathTo(node2), is(path(root, node1, node2)));
+ assertThat(root.getPathTo(node3), is(path(root, node1, node2, node3)));
+ assertThat(root.getPathTo(node4), is(path(root, node1, node2, node3, node4)));
+ assertThat(root.getPathTo(node5), is(path(root, node1, node2, node3, node4,
node5)));
+
+ assertThat(node1.getPathTo(node1), is(path(node1)));
+ assertThat(node1.getPathTo(node2), is(path(node1, node2)));
+ assertThat(node1.getPathTo(node3), is(path(node1, node2, node3)));
+ assertThat(node1.getPathTo(node4), is(path(node1, node2, node3, node4)));
+ assertThat(node1.getPathTo(node5), is(path(node1, node2, node3, node4, node5)));
+
+ assertThat(node2.getPathTo(node2), is(path(node2)));
+ assertThat(node2.getPathTo(node3), is(path(node2, node3)));
+ assertThat(node2.getPathTo(node4), is(path(node2, node3, node4)));
+ assertThat(node2.getPathTo(node5), is(path(node2, node3, node4, node5)));
+ }
+
+ protected LinkedList<PlanNode> path( PlanNode... expectedNodes ) {
+ LinkedList<PlanNode> result = new LinkedList<PlanNode>();
+ for (PlanNode node : expectedNodes) {
+ result.add(node);
+ }
+ return result;
+ }
+
+ @Test
+ public void shouldNotAddNullSelectorNames() {
+ Collection<SelectorName> names = Collections.singletonList(null);
+ node.addSelectors(names);
+ assertThat(node.getSelectors().isEmpty(), is(true));
+ node.addSelector(null);
+ assertThat(node.getSelectors().isEmpty(), is(true));
+ SelectorName name = new SelectorName("something");
+ node.addSelector(name, null);
+ assertThat(node.getSelectors().size(), is(1));
+ assertThat(node.getSelectors().contains(name), is(true));
+ }
+
+ @Test
+ public void shouldCorrectlyDetermineIfAncestorHasType() {
+ PlanNode root = new PlanNode(Type.JOIN);
+ PlanNode node1 = new PlanNode(Type.ACCESS, root);
+ PlanNode node2 = new PlanNode(Type.DUP_REMOVE, node1);
+ PlanNode node3 = new PlanNode(Type.GROUP, node2);
+ PlanNode node4 = new PlanNode(Type.SELECT, node3);
+ PlanNode node5 = new PlanNode(Type.SET_OPERATION, node4);
+ assertThat(node5.hasAncestorOfType(Type.SET_OPERATION), is(false)); // no
ancestor, just self
+ assertThat(node5.hasAncestorOfType(Type.SOURCE), is(false));
+ assertThat(node5.hasAncestorOfType(Type.DUP_REMOVE), is(true));
+ assertThat(node5.hasAncestorOfType(Type.DUP_REMOVE, Type.SELECT), is(true));
+ assertThat(node5.hasAncestorOfType(Type.DUP_REMOVE, Type.SELECT, Type.SOURCE),
is(true));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/PlanNodeTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,239 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.Order;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.process.QueryResultColumns;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ *
+ */
+public abstract class AbstractQueryResultsTest extends AbstractQueryTest {
+
+ protected ExecutionContext executionContext = ExecutionContext.DEFAULT_CONTEXT;
+
+ protected Name name( String name ) {
+ return executionContext.getValueFactories().getNameFactory().create(name);
+ }
+
+ protected Path path( String name ) {
+ return executionContext.getValueFactories().getPathFactory().create(name);
+ }
+
+ protected Schemata schemataFor( Columns columns ) {
+ return schemataFor(columns, new PropertyType[] {});
+ }
+
+ protected Schemata schemataFor( Columns columns,
+ PropertyType... types ) {
+ ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder();
+ for (String selectorName : columns.getSelectorNames()) {
+ final SelectorName selector = selector(selectorName);
+ int i = 0;
+ for (Column column : columns.getColumns()) {
+ final String name = column.getColumnName();
+ final PropertyType type = types != null && types.length > i
&& types[i] != null ? types[i] : PropertyType.STRING;
+ if (column.getSelectorName().equals(selector)) {
+ builder.addColumn(selectorName, name, type);
+ ++i;
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ protected Columns resultColumns( String selectorName,
+ String... columnNames ) {
+ // Define the columns ...
+ List<Column> columnObj = new ArrayList<Column>();
+ SelectorName selector = selector(selectorName);
+ for (String columnName : columnNames) {
+ columnObj.add(new Column(selector, name(columnName), columnName));
+ }
+ return new QueryResultColumns(columnObj, false);
+ }
+
+ protected Columns resultColumnsWithSearchResults( String selectorName,
+ String... columnNames ) {
+ // Define the columns ...
+ List<Column> columnObj = new ArrayList<Column>();
+ SelectorName selector = selector(selectorName);
+ for (String columnName : columnNames) {
+ columnObj.add(new Column(selector, name(columnName), columnName));
+ }
+ return new QueryResultColumns(columnObj, true);
+ }
+
+ protected Object[] tuple( Columns columns,
+ String path,
+ Object... values ) {
+ return tuple(columns, path(path), values);
+ }
+
+ protected Object[] tuple( Columns columns,
+ Path path,
+ Object... values ) {
+ return tuple(columns, Location.create(path), values);
+ }
+
+ protected Object[] tuple( Columns columns,
+ String[] paths,
+ Object... values ) {
+ Location[] locations = new Location[paths.length];
+ for (int i = 0; i != paths.length; ++i) {
+ locations[i] = Location.create(path(paths[i]));
+ }
+ return tuple(columns, locations, values);
+ }
+
+ protected Object[] tuple( Columns columns,
+ Path[] paths,
+ Object... values ) {
+ Location[] locations = new Location[paths.length];
+ for (int i = 0; i != paths.length; ++i) {
+ locations[i] = Location.create(paths[i]);
+ }
+ return tuple(columns, locations, values);
+ }
+
+ protected Object[] tuple( Columns columns,
+ Location location,
+ Object... values ) {
+ return tuple(columns, new Location[] {location}, values);
+ }
+
+ protected Object[] tuple( Columns columns,
+ Location[] locations,
+ Object... values ) {
+ Object[] results = new Object[columns.getTupleSize()];
+ // Set the column values ...
+ assertThat(values.length, is(columns.getColumnCount()));
+ for (int i = 0; i != columns.getColumnCount(); ++i) {
+ results[i] = values[i];
+ }
+ // Set the location ...
+ int index = columns.getColumnCount();
+ for (Location location : locations) {
+ results[index++] = location;
+ }
+ // Set the full-text-search results ...
+ if (columns.hasFullTextSearchScores()) {
+ results[index++] = 1.0;
+ }
+ return results;
+ }
+
+ protected Object[] tuple( Columns columns,
+ Location[] locations,
+ double[] scores,
+ Object... values ) {
+ assertThat(values.length, is(columns.getColumnCount()));
+ assertThat(locations.length, is(columns.getLocationCount()));
+ assertThat(scores.length, is(columns.getLocationCount()));
+ Object[] results = new Object[columns.getTupleSize()];
+ // Set the column values ...
+ for (int i = 0; i != columns.getColumnCount(); ++i) {
+ results[i] = values[i];
+ }
+ // Set the location ...
+ int index = columns.getColumnCount();
+ for (Location location : locations) {
+ results[index++] = location;
+ }
+ // Set the full-text-search results ...
+ if (columns.hasFullTextSearchScores()) {
+ for (double score : scores) {
+ results[index++] = score;
+ }
+ }
+ return results;
+ }
+
+ protected Ordering orderByPropertyValue( Column column ) {
+ return orderByPropertyValue(column, Order.ASCENDING);
+ }
+
+ protected Ordering orderByPropertyValue( Column column,
+ Order order ) {
+ return new Ordering(new PropertyValue(column.getSelectorName(),
column.getPropertyName()), order);
+ }
+
+ protected Ordering orderByPropertyLength( Column column ) {
+ return orderByPropertyValue(column, Order.ASCENDING);
+ }
+
+ protected Ordering orderByPropertyLength( Column column,
+ Order order ) {
+ return new Ordering(new Length(new PropertyValue(column.getSelectorName(),
column.getPropertyName())), order);
+ }
+
+ protected Ordering orderByNodeName( String selectorName ) {
+ return orderByNodeName(selectorName, Order.ASCENDING);
+ }
+
+ protected Ordering orderByNodeName( String selectorName,
+ Order order ) {
+ return new Ordering(new NodeName(selector(selectorName)), order);
+ }
+
+ protected Ordering orderByNodeLocalName( String selectorName ) {
+ return orderByNodeLocalName(selectorName, Order.ASCENDING);
+ }
+
+ protected Ordering orderByNodeLocalName( String selectorName,
+ Order order ) {
+ return new Ordering(new NodeLocalName(selector(selectorName)), order);
+ }
+
+ protected Ordering orderByFullTextSearchScore( String selectorName ) {
+ return orderByFullTextSearchScore(selectorName, Order.ASCENDING);
+ }
+
+ protected Ordering orderByFullTextSearchScore( String selectorName,
+ Order order ) {
+ return new Ordering(new FullTextSearchScore(selector(selectorName)), order);
+ }
+
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/DistinctComponentTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/DistinctComponentTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/DistinctComponentTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,97 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.process.DistinctComponent;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class DistinctComponentTest extends AbstractQueryResultsTest {
+
+ private DistinctComponent component;
+ private QueryContext context;
+ private Columns columns;
+ private List<Object[]> inputTuples;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ inputTuples = new ArrayList<Object[]>();
+ // Define the columns for the results ...
+ columns = resultColumns("Selector1", "ColA",
"ColB", "ColC");
+ // And define the delegating component ...
+ ProcessingComponent delegate = new ProcessingComponent(context, columns) {
+ @SuppressWarnings( "synthetic-access" )
+ @Override
+ public List<Object[]> execute() {
+ return new ArrayList<Object[]>(inputTuples);
+ }
+ };
+ // Create the component we're testing ...
+ component = new DistinctComponent(delegate);
+ }
+
+ @Test
+ public void
shouldReturnAllResultsIfThereAreNoDuplicatesInTuplesContainingOneLocation() {
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ assertThat(component.execute(), is(inputTuples));
+ }
+
+ @Test
+ public void shouldReturnNoDuplicatesFromTuplesContainingOneLocation() {
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c0", "v1",
"v2", "v3"));
+ List<Object[]> expected = new ArrayList<Object[]>(inputTuples);
+ expected.remove(5);
+ expected.remove(4);
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnEmptyResultsWhenDelegateReturnsEmptyResults() {
+ assertThat(component.execute().isEmpty(), is(true));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/DistinctComponentTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,237 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.QueryResults.Cursor;
+import org.jboss.dna.graph.query.QueryResults.Statistics;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.process.QueryResultColumns;
+import org.jboss.dna.graph.query.process.QueryResults;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ *
+ */
+public class QueryResultColumnsTest extends AbstractQueryResultsTest {
+
+ private QueryContext context;
+ private List<Column> columnList;
+ private Columns columns;
+ private QueryResults results;
+ private List<Object[]> tuples;
+ @Mock
+ private Schemata schemata;
+ @Mock
+ private QueryCommand command;
+ @Mock
+ private Statistics statistics;
+
+ @Before
+ public void beforeEach() {
+ MockitoAnnotations.initMocks(this);
+ context = new QueryContext(executionContext, new PlanHints(), schemata);
+ columnList = new ArrayList<Column>();
+ columnList.add(new Column(selector("table1"), name("colA"),
"colA"));
+ columnList.add(new Column(selector("table1"), name("colB"),
"colB"));
+ columnList.add(new Column(selector("table1"), name("colC"),
"colC"));
+ columnList.add(new Column(selector("table2"), name("colA"),
"colA2"));
+ columnList.add(new Column(selector("table2"), name("colB"),
"colB2"));
+ columnList.add(new Column(selector("table2"), name("colX"),
"colX"));
+ columns = new QueryResultColumns(columnList, false);
+ tuples = new ArrayList<Object[]>();
+ tuples.add(tuple(columns, new String[] {"/a/b/c", "/a/x/y"},
1, 2, 3, "2a", "2b", "x"));
+ tuples.add(tuple(columns, new String[] {"/a/b/d", "/a/x/y"},
4, 5, 6, "2a", "2b", "x"));
+ results = new QueryResults(context, command, columns, statistics);
+ }
+
+ @Test
+ public void shouldReturnSameColumnsPassedIntoConstructor() {
+ assertThat(results.getColumns(), is(sameInstance(columns)));
+ }
+
+ @Test
+ public void shouldReturnSameStatisticsPassedIntoConstructor() {
+ assertThat(results.getStatistics(), is(sameInstance(statistics)));
+ }
+
+ @Test
+ public void shouldReturnSameQueryCommandPassedIntoConstructor() {
+ assertThat(results.getCommand(), is(sameInstance(command)));
+ }
+
+ @Test
+ public void shouldReturnSameProblemsObjectAsInQueryContext() {
+ assertThat(results.getProblems(), is(sameInstance(context.getProblems())));
+ }
+
+ @Test
+ public void shouldReturnSameTuplesListPassedIntoConstructor() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ assertThat(results.getTuples(), is(sameInstance(tuples)));
+ }
+
+ @Test
+ public void shouldHaveNoTuplesIfConstructedWithNoTuples() {
+ assertThat(results.getTuples().isEmpty(), is(true));
+ assertThat(results.getCursor().hasNext(), is(false));
+ }
+
+ @Test
+ public void shouldHaveNoTuplesIfConstructedWithEmptyTuplesList() {
+ tuples.clear();
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ assertThat(results.getTuples().isEmpty(), is(true));
+ assertThat(results.getCursor().hasNext(), is(false));
+ }
+
+ @Test
+ public void shouldReturnMutableTuplesList() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ assertThat(results.getTuples().isEmpty(), is(false));
+ results.getTuples().clear();
+ assertThat(results.getTuples().isEmpty(), is(true));
+ assertThat(tuples.isEmpty(), is(true));
+ }
+
+ @Test
+ public void shouldReturnCursorThatAccessesTuples() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ Cursor cursor = results.getCursor();
+ Iterator<Object[]> expectedIter = tuples.iterator();
+ int rowNumber = 0;
+ while (cursor.hasNext() && expectedIter.hasNext()) {
+ cursor.next();
+ Object[] tuple = expectedIter.next();
+ // Check the column values by column name and index ...
+ for (Column column : results.getColumns().getColumns()) {
+ String columnName = column.getColumnName();
+ int columnIndex = columns.getColumnIndexForName(columnName);
+ assertThat(cursor.getValue(columnName), is(tuple[columnIndex]));
+ assertThat(cursor.getValue(columnIndex), is(tuple[columnIndex]));
+ // Get the location for this column ...
+ int locationIndex =
columns.getLocationIndex(column.getSelectorName().getName());
+ Location location = (Location)tuple[locationIndex];
+ assertThat(cursor.getLocation(columnIndex), is(location));
+ }
+ // Check the locations by selector name and index ...
+ for (String selectorName : results.getColumns().getSelectorNames()) {
+ int locationIndex = columns.getLocationIndex(selectorName);
+ Location location = (Location)tuple[locationIndex];
+ assertThat(cursor.getLocation(selectorName), is(location));
+ assertThat(location.hasPath(), is(true));
+ }
+ // Check the row index ...
+ assertThat(cursor.getRowIndex(), is(rowNumber++));
+ }
+ assertThat(cursor.hasNext(), is(false));
+ assertThat(expectedIter.hasNext(), is(false));
+ }
+
+ @Test( expected = IllegalStateException.class )
+ public void shouldRequireNextOnCursorToBeCalledBeforeGettingValueUsingColumnIndex()
{
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ Cursor cursor = results.getCursor();
+ assertThat(cursor.hasNext(), is(true));
+ cursor.getValue(0);
+ }
+
+ @Test( expected = IllegalStateException.class )
+ public void shouldRequireNextOnCursorToBeCalledBeforeGettingValueUsingColumnName() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ Cursor cursor = results.getCursor();
+ assertThat(cursor.hasNext(), is(true));
+ cursor.getValue("colA");
+ }
+
+ @Test
+ public void shouldPrintToStringAllResults() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ List<String> lines = StringUtil.splitLines(results.toString());
+ assertThat(lines.size(), is(tuples.size() + 4)); // = delim + header + delim +
(...lines...) + delim
+ }
+
+ @Test
+ public void shouldPrintToStringBuilderAllResults() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ StringBuilder sb = new StringBuilder();
+ results.toString(sb);
+ List<String> lines = StringUtil.splitLines(sb.toString());
+ assertThat(lines.size(), is(tuples.size() + 4)); // = delim + header + delim +
(...lines...) + delim
+ }
+
+ @Test
+ public void shouldPrintToStringBuilderAllResultsEvenWhenNoTuples() {
+ tuples.clear();
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ StringBuilder sb = new StringBuilder();
+ results.toString(sb);
+ List<String> lines = StringUtil.splitLines(sb.toString());
+ assertThat(lines.size(), is(4)); // = delim + header + delim + (...lines...) +
delim
+ }
+
+ @Test
+ public void shouldPrintToStringBuilderOnlyFirstLinesOfResults() {
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ StringBuilder sb = new StringBuilder();
+ results.toString(sb, 1);
+ List<String> lines = StringUtil.splitLines(sb.toString());
+ assertThat(lines.size(), is(1 + 4)); // = delim + header + delim + (...lines...)
+ delim
+ }
+
+ @Test
+ public void shouldPrintToStringBuilderOnlyFirstLinesOfResultsEvenWhenNoTuples() {
+ tuples.clear();
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ StringBuilder sb = new StringBuilder();
+ results.toString(sb, 3);
+ List<String> lines = StringUtil.splitLines(sb.toString());
+ assertThat(lines.size(), is(4)); // = delim + header + delim + (...lines...) +
delim
+ }
+
+ @Test
+ public void
shouldPrintToStringBuilderAllResultsWhenMaxRowParameterIsLargerThanNumberOfTuples() {
+ tuples.clear();
+ results = new QueryResults(context, command, columns, statistics, tuples);
+ StringBuilder sb = new StringBuilder();
+ results.toString(sb, 3);
+ List<String> lines = StringUtil.splitLines(sb.toString());
+ assertThat(lines.size(), is(tuples.size() + 4)); // = delim + header + delim +
(...lines...) + delim
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultColumnsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultsTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultsTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,363 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.process.QueryResultColumns;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class QueryResultsTest extends AbstractQueryResultsTest {
+
+ private List<Column> columnList;
+ private QueryResultColumns columnsWithoutScores;
+ private QueryResultColumns columnsWithScores;
+
+ @Before
+ public void beforeEach() {
+ columnList = new ArrayList<Column>();
+ columnList.add(new Column(selector("table1"), name("colA"),
"colA"));
+ columnList.add(new Column(selector("table1"), name("colB"),
"colB"));
+ columnList.add(new Column(selector("table1"), name("colC"),
"colC"));
+ columnList.add(new Column(selector("table2"), name("colA"),
"colA2"));
+ columnList.add(new Column(selector("table2"), name("colB"),
"colB2"));
+ columnList.add(new Column(selector("table2"), name("colX"),
"colX"));
+ columnsWithoutScores = new QueryResultColumns(columnList, false);
+ columnsWithScores = new QueryResultColumns(columnList, true);
+ }
+
+ @Test
+ public void shouldHaveCorrectTupleSize() {
+ assertThat(columnsWithScores.getTupleSize(), is(columnList.size() + 2 + 2));
+ assertThat(columnsWithoutScores.getTupleSize(), is(columnList.size() + 2 + 0));
+ }
+
+ @Test
+ public void shouldHaveCorrectTupleNames() {
+ List<String> expected = new ArrayList<String>();
+ expected.add("colA");
+ expected.add("colB");
+ expected.add("colC");
+ expected.add("colA2");
+ expected.add("colB2");
+ expected.add("colX");
+ expected.add("Location(table1)");
+ expected.add("Location(table2)");
+ assertThat(columnsWithoutScores.getTupleValueNames(), is(expected));
+ expected.add("Score(table1)");
+ expected.add("Score(table2)");
+ assertThat(columnsWithScores.getTupleValueNames(), is(expected));
+ }
+
+ @Test
+ public void shouldHaveCorrectSelectorNames() {
+ List<String> expected = new ArrayList<String>();
+ expected.add("table1");
+ expected.add("table2");
+ assertThat(columnsWithoutScores.getSelectorNames(), is(expected));
+ assertThat(columnsWithScores.getSelectorNames(), is(expected));
+ }
+
+ @Test
+ public void shouldHaveCorrectNumberOfColumns() {
+ assertThat(columnsWithScores.getColumnCount(), is(columnList.size()));
+ assertThat(columnsWithoutScores.getColumnCount(), is(columnList.size()));
+ }
+
+ @Test
+ public void shouldReturnColumns() {
+ assertThat(columnsWithScores.getColumns(), is(columnList));
+ assertThat(columnsWithoutScores.getColumns(), is(columnList));
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldReturnImmutableColumns() {
+ assertThat(columnsWithoutScores.getColumns().isEmpty(), is(false));
+ columnsWithoutScores.getColumns().clear();
+ }
+
+ @Test
+ public void shouldReturnColumnNames() {
+ List<String> names = new ArrayList<String>();
+ for (Column column : columnList) {
+ names.add(column.getColumnName());
+ }
+ assertThat(columnsWithScores.getColumnNames(), is(names));
+ assertThat(columnsWithoutScores.getColumnNames(), is(names));
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfColumnGivenColumnName() {
+ for (Column column : columnList) {
+
assertThat(columnsWithoutScores.getColumnIndexForName(column.getColumnName()),
is(columnList.indexOf(column)));
+ }
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfColumnGivenColumnSelectorAndPropertyName() {
+ for (Column column : columnList) {
+
assertThat(columnsWithoutScores.getColumnIndexForProperty(column.getSelectorName().getName(),
+
column.getPropertyName()), is(columnList.indexOf(column)));
+ }
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenColumnNameWithIncorrectCase() {
+ columnsWithScores.getColumnIndexForName("cola");
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenNonExistantColumnName() {
+ columnsWithScores.getColumnIndexForName("non-existant");
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenNullColumnName() {
+ columnsWithScores.getColumnIndexForName(null);
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenUnusedSelectorName() {
+ columnsWithScores.getColumnIndexForProperty("non-existant",
name("colA"));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenNullSelectorName() {
+ columnsWithScores.getColumnIndexForProperty(null, name("colA"));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenEmptySelectorName() {
+ columnsWithScores.getColumnIndexForProperty("",
name("colA"));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenUnusedPropertyNameName() {
+ columnsWithScores.getColumnIndexForProperty("table1",
name("non-existant"));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfColumnGivenNullPropertyNameName() {
+ columnsWithScores.getColumnIndexForProperty("table1", null);
+ }
+
+ @Test
+ public void shouldHaveCorrectNumberOfLocations() {
+ assertThat(columnsWithScores.getLocationCount(), is(2));
+ assertThat(columnsWithoutScores.getLocationCount(), is(2));
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfLocationGivenSelectorName() {
+ assertThat(columnsWithoutScores.getLocationIndex("table1"),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndex("table2"),
is(columnList.size() + 1));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenUnusedSelectorName() {
+ columnsWithScores.getLocationIndex("non-existant");
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenNullSelectorName() {
+ columnsWithScores.getLocationIndex(null);
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenEmptySelectorName() {
+ columnsWithScores.getLocationIndex("");
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfLocationGivenColumnName() {
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colA"),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colB"),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colC"),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colA2"),
is(columnList.size() + 1));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colB2"),
is(columnList.size() + 1));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn("colX"),
is(columnList.size() + 1));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenUnusedColumnName() {
+ columnsWithScores.getLocationIndexForColumn("non-existant");
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenNullColumnName() {
+ columnsWithScores.getLocationIndexForColumn(null);
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfLocationGivenEmptyColumnName() {
+ columnsWithScores.getLocationIndexForColumn("");
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfLocationGivenColumnIndex() {
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(0),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(1),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(2),
is(columnList.size() + 0));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(3),
is(columnList.size() + 1));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(4),
is(columnList.size() + 1));
+ assertThat(columnsWithoutScores.getLocationIndexForColumn(5),
is(columnList.size() + 1));
+ }
+
+ @Test( expected = IndexOutOfBoundsException.class )
+ public void
shouldFailToFindIndexOfLocationGivenColumnIndexEqualToOrLargerThanNumberOfColumns() {
+ columnsWithScores.getLocationIndexForColumn(columnList.size());
+ }
+
+ @Test( expected = IndexOutOfBoundsException.class )
+ public void shouldFailToFindIndexOfLocationGivenColumnIndexLessThanZero() {
+ columnsWithScores.getLocationIndexForColumn(-1);
+ }
+
+ @Test
+ public void shouldCorrectlyReportWhetherScoresAreIncluded() {
+ assertThat(columnsWithScores.hasFullTextSearchScores(), is(true));
+ assertThat(columnsWithoutScores.hasFullTextSearchScores(), is(false));
+ }
+
+ @Test
+ public void shouldReturnCorrectIndexOfFullTextSearchScoreGivenSelectorName() {
+ assertThat(columnsWithScores.getFullTextSearchScoreIndexFor("table1"),
is(columnList.size() + 2 + 0));
+ assertThat(columnsWithScores.getFullTextSearchScoreIndexFor("table2"),
is(columnList.size() + 2 + 1));
+ }
+
+ @Test
+ public void
shouldReturnNegativeOneForIndexOfFullTextSearchScoreGivenValidSelectorNameButWhereNoScoresAreIncluded()
{
+
assertThat(columnsWithoutScores.getFullTextSearchScoreIndexFor("table1"),
is(-1));
+
assertThat(columnsWithoutScores.getFullTextSearchScoreIndexFor("table2"),
is(-1));
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfFullTextSearchScoreGivenUnusedSelectorName() {
+ columnsWithScores.getFullTextSearchScoreIndexFor("non-existant");
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfFullTextSearchScoreGivenNullSelectorName() {
+ columnsWithScores.getFullTextSearchScoreIndexFor(null);
+ }
+
+ @Test( expected = NoSuchElementException.class )
+ public void shouldFailToFindIndexOfFullTextSearchScoreGivenEmptySelectorName() {
+ columnsWithScores.getFullTextSearchScoreIndexFor("");
+ }
+
+ @Test
+ public void shouldIncludeSelf() {
+ assertThat(columnsWithScores.includes(columnsWithScores), is(true));
+ assertThat(columnsWithoutScores.includes(columnsWithoutScores), is(true));
+ }
+
+ @Test
+ public void
shouldIncludeColumnsObjectWithSubsetOfColumnObjectsAndIndependentOfFullTextSearchScores()
{
+ List<Column> subset = new ArrayList<Column>();
+ subset.add(columnList.get(0));
+ subset.add(columnList.get(1));
+ subset.add(columnList.get(4));
+ Columns other = new QueryResultColumns(subset, false);
+ assertThat(columnsWithScores.includes(other), is(true));
+ assertThat(columnsWithoutScores.includes(other), is(true));
+ assertThat(columnsWithoutScores.includes(columnsWithScores), is(true));
+ }
+
+ @Test
+ public void shouldEqualSelf() {
+ assertThat(columnsWithScores.equals(columnsWithScores), is(true));
+ assertThat(columnsWithoutScores.equals(columnsWithoutScores), is(true));
+ }
+
+ @Test
+ public void shouldEqualIndependentOfInclusionOfFullTextSearchScores() {
+ assertThat(columnsWithoutScores.equals(columnsWithScores), is(true));
+ }
+
+ @Test
+ public void shouldNotBeUnionCompatibleUnlessBothHaveFullTextSearchScores() {
+ Columns other = new QueryResultColumns(columnsWithoutScores.getColumns(),
!columnsWithoutScores.hasFullTextSearchScores());
+ assertThat(columnsWithoutScores.isUnionCompatible(other), is(false));
+ }
+
+ @Test
+ public void shouldNotBeUnionCompatibleUnlessBothDoNotHaveFullTextSearchScores() {
+ Columns other = new QueryResultColumns(columnsWithScores.getColumns(),
!columnsWithScores.hasFullTextSearchScores());
+ assertThat(columnsWithScores.isUnionCompatible(other), is(false));
+ }
+
+ @Test
+ public void shouldBeUnionCompatibleWithEquivalentColumns() {
+ List<Column> columnListCopy = new ArrayList<Column>();
+ for (Column column : columnsWithScores.getColumns()) {
+ columnListCopy.add(new Column(column.getSelectorName(),
column.getPropertyName(), column.getColumnName()));
+ }
+ Columns other = new QueryResultColumns(columnListCopy,
columnsWithScores.hasFullTextSearchScores());
+ assertThat(columnsWithScores.isUnionCompatible(other), is(true));
+ }
+
+ @Test
+ public void shouldNotBeUnionCompatibleWithSubsetOfColumns() {
+ List<Column> columnListCopy = new ArrayList<Column>();
+ for (Column column : columnsWithScores.getColumns()) {
+ columnListCopy.add(new Column(column.getSelectorName(),
column.getPropertyName(), column.getColumnName()));
+ }
+ columnListCopy.remove(3);
+ Columns other = new QueryResultColumns(columnListCopy,
columnsWithScores.hasFullTextSearchScores());
+ assertThat(columnsWithScores.isUnionCompatible(other), is(false));
+ }
+
+ @Test
+ public void shouldNotBeUnionCompatibleWithExtraColumns() {
+ List<Column> columnListCopy = new ArrayList<Column>();
+ for (Column column : columnsWithScores.getColumns()) {
+ columnListCopy.add(new Column(column.getSelectorName(),
column.getPropertyName(), column.getColumnName()));
+ }
+ columnListCopy.add(new Column(selector("table2"),
name("colZ"), "colZ"));
+ Columns other = new QueryResultColumns(columnListCopy,
columnsWithScores.hasFullTextSearchScores());
+ assertThat(columnsWithScores.isUnionCompatible(other), is(false));
+ }
+
+ @Test
+ public void shouldBeUnionCompatibleWithSameColumns() {
+ Columns other = new QueryResultColumns(columnsWithScores.getColumns(),
columnsWithScores.hasFullTextSearchScores());
+ assertThat(columnsWithScores.isUnionCompatible(other), is(true));
+ }
+
+ @Test
+ public void shouldHaveToString() {
+ columnsWithScores.toString();
+ columnsWithoutScores.toString();
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/QueryResultsTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortLocationsComponentTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortLocationsComponentTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortLocationsComponentTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,107 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.SortLocationsComponent;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SortLocationsComponentTest extends AbstractQueryResultsTest {
+
+ private SortLocationsComponent component;
+ private QueryContext context;
+ private Columns columns;
+ private List<Object[]> inputTuples;
+
+ @Before
+ public void beforeEach() {
+ context = new QueryContext(new ExecutionContext(), new PlanHints(),
mock(Schemata.class));
+ inputTuples = new ArrayList<Object[]>();
+ // Define the columns for the results ...
+ columns = resultColumns("Selector1", "ColA",
"ColB", "ColC");
+ // And define the delegating component ...
+ ProcessingComponent delegate = new ProcessingComponent(context, columns) {
+ @SuppressWarnings( "synthetic-access" )
+ @Override
+ public List<Object[]> execute() {
+ return new ArrayList<Object[]>(inputTuples);
+ }
+ };
+ // Create the component we're testing ...
+ component = new SortLocationsComponent(delegate);
+ }
+
+ @Test
+ public void shouldReturnAllResultsInPathOrderForTuplesContainingOneLocation() {
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void
shouldReturnAllResultsInPathOrderForTuplesContainingOneLocationAndWithDuplicateTuples() {
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1",
"v2", "v3"));
+ inputTuples.add(tuple(columns, "/a/b/c0", "v1",
"v2", "v3"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(6));
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(5));
+ expected.add(inputTuples.get(1));
+ expected.add(inputTuples.get(4));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnEmptyResultsWhenDelegateReturnsEmptyResults() {
+ assertThat(component.execute().isEmpty(), is(true));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortLocationsComponentTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
(rev 0)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,203 @@
+/*
+ * 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.process;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.SortValuesComponent;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SortValuesComponentTest extends AbstractQueryResultsTest {
+
+ private SortValuesComponent component;
+ private ProcessingComponent delegate;
+ private QueryContext context;
+ private Schemata schemata;
+ private Columns columns;
+ private List<Object[]> inputTuples;
+ private List<Ordering> orderings;
+
+ @Before
+ public void beforeEach() {
+ // Define the columns for the results ...
+ columns = resultColumns("Selector1", "ColA",
"ColB", "ColC");
+ schemata = schemataFor(columns, PropertyType.STRING, PropertyType.LONG,
PropertyType.STRING);
+ // Define the context ...
+ context = new QueryContext(new ExecutionContext(), new PlanHints(), schemata);
+ inputTuples = new ArrayList<Object[]>();
+ // And define the delegating component ...
+ delegate = new ProcessingComponent(context, columns) {
+ @SuppressWarnings( "synthetic-access" )
+ @Override
+ public List<Object[]> execute() {
+ return new ArrayList<Object[]>(inputTuples);
+ }
+ };
+ // Create the component we're testing ...
+ orderings = new ArrayList<Ordering>();
+ component = new SortValuesComponent(delegate, orderings);
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByNodeName() {
+ orderings.add(orderByNodeName("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b3/c2", 100, 100, "v2"));
+ inputTuples.add(tuple(columns, "/a/b4/c3", "v3", 100,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByNodeNameWhenThereAreDuplicateTuples() {
+ orderings.add(orderByNodeName("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b2/c4", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b3/c2", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b4/c3", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b5/c4", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b6/c3", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b7/c0", "v1", 100,
"v3"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(6));
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(5));
+ expected.add(inputTuples.get(1));
+ expected.add(inputTuples.get(4));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByNodeLocalName() {
+ orderings.add(orderByNodeName("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b3/c2", 100, 100, "v2"));
+ inputTuples.add(tuple(columns, "/a/b4/c3", "v3", 100,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByNodeLocalNameWhenThereAreDuplicateTuples()
{
+ orderings.add(orderByNodeName("Selector1"));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b1/dna:c1", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b2/dna:c4", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b3/dna:c2", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b4/dna:c3", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b5/jcr:c4", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b6/dna:c3", "v1", 100,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b7/dna:c0", "v1", 100,
"v3"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(6));
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(5));
+ expected.add(inputTuples.get(1));
+ expected.add(inputTuples.get(4));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByValueLengthOfLong() {
+ orderings.add(orderByPropertyLength(columns.getColumns().get(1)));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1", 1L,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1", 1114L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1", 113L,
"v2"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1", 12L,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsOrderedByValueLengthOfString() {
+ orderings.add(orderByPropertyLength(columns.getColumns().get(0)));
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1", 100L,
"v4"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1111", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v111", 100L,
"v2"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v11", 100L,
"v1"));
+ List<Object[]> expected = new ArrayList<Object[]>();
+ expected.add(inputTuples.get(0));
+ expected.add(inputTuples.get(3));
+ expected.add(inputTuples.get(2));
+ expected.add(inputTuples.get(1));
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnAllResultsInSuppliedOrderWhenThereAreNoOrderings() {
+ orderings.clear();
+ component = new SortValuesComponent(delegate, orderings);
+ inputTuples.add(tuple(columns, "/a/b/c1", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c2", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c4", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c3", "v1", 100L,
"v3"));
+ inputTuples.add(tuple(columns, "/a/b/c0", "v1", 100L,
"v3"));
+ List<Object[]> expected = new ArrayList<Object[]>(inputTuples);
+ assertThat(component.execute(), is(expected));
+ }
+
+ @Test
+ public void shouldReturnEmptyResultsWhenDelegateReturnsEmptyResults() {
+ assertThat(component.execute().isEmpty(), is(true));
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/SortValuesComponentTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/.classpath
===================================================================
--- trunk/dna-search/.classpath (rev 0)
+++ trunk/dna-search/.classpath 2009-09-21 20:03:40 UTC (rev 1234)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" output="target/test-classes"
path="src/test/java"/>
+ <classpathentry kind="src" output="target/test-classes"
path="src/test/resources"/>
+ <classpathentry kind="con"
path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con"
path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
Property changes on: trunk/dna-search/.classpath
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/.project
===================================================================
--- trunk/dna-search/.project (rev 0)
+++ trunk/dna-search/.project 2009-09-21 20:03:40 UTC (rev 1234)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>dna-search</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ </natures>
+</projectDescription>
Property changes on: trunk/dna-search/.project
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/pom.xml
===================================================================
--- trunk/dna-search/pom.xml (rev 0)
+++ trunk/dna-search/pom.xml 2009-09-21 20:03:40 UTC (rev 1234)
@@ -0,0 +1,97 @@
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna</artifactId>
+ <version>0.7-SNAPSHOT</version>
+ </parent>
+ <!-- The groupId and version values are inherited from parent -->
+ <artifactId>dna-search</artifactId>
+ <packaging>jar</packaging>
+ <name>JBoss DNA Search</name>
+ <description>JBoss DNA search and query engine library</description>
+ <
url>http://labs.jboss.org/dna</url>
+
+ <!--
+ Define the dependencies. Note that all version and scopes default to those
+ defined in the dependencyManagement section of the parent pom.
+ -->
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-common</artifactId>
+ <version>${pom.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-graph</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-graph</artifactId>
+ <version>${pom.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ Lucene
+ -->
+ <dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-core</artifactId>
+ <version>2.4.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-analyzers</artifactId>
+ <version>2.4.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-snowball</artifactId>
+ <version>2.4.1</version>
+ </dependency>
+ <!--
+ Testing (note the scope)
+ -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ </dependency>
+ <!--
+ Logging (require SLF4J API for compiling, but use Log4J and its SLF4J binding for
testing)
+ -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ Java Concurrency in Practice annotations
+ -->
+ <dependency>
+ <groupId>net.jcip</groupId>
+ <artifactId>jcip-annotations</artifactId>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
Property changes on: trunk/dna-search/pom.xml
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,46 @@
+/*
+ * 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.search;
+
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Interface used to obtain the Lucene {@link Directory} instance that should be used for
a workspace given the name of the
+ * workspace. There are several implementations (see {@link DirectoryConfigurations}),
but custom implementations can always be used.
+ */
+@ThreadSafe
+public interface DirectoryConfiguration {
+ /**
+ * Get the {@link Directory} that should be used for the workspace with the supplied
name.
+ *
+ * @param workspaceName the workspace name
+ * @param indexName the name of the index to be created
+ * @return the directory; never null
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem creating the directory
+ */
+ Directory getDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException;
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfiguration.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,386 @@
+/*
+ * 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.search;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.RAMDirectory;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.text.NoOpEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.HashCode;
+
+/**
+ * A family of {@link DirectoryConfiguration} implementations.
+ */
+public class DirectoryConfigurations {
+
+ /**
+ * Return a new {@link DirectoryConfiguration} that creates in-memory directories.
+ *
+ * @return the new directory configuration; never null
+ */
+ public static final DirectoryConfiguration inMemory() {
+ return new RamDirectoryFactory();
+ }
+
+ /**
+ * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final DirectoryConfiguration using( File parent ) {
+ return new FileSystemDirectoryFromNameFactory(parent);
+ }
+
+ /**
+ * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final DirectoryConfiguration using( File parent,
+ LockFactory lockFactory ) {
+ return new FileSystemDirectoryFromNameFactory(parent, lockFactory);
+ }
+
+ /**
+ * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final DirectoryConfiguration using( File parent,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ return new FileSystemDirectoryFromNameFactory(parent, workspaceNameEncoder,
indexNameEncoder);
+ }
+
+ /**
+ * Return a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note that
this has ramifications on the allowable
+ * workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @return the new directory configuration; never null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ public static final DirectoryConfiguration using( File parent,
+ LockFactory lockFactory,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ return new FileSystemDirectoryFromNameFactory(parent, lockFactory,
workspaceNameEncoder, indexNameEncoder);
+ }
+
+ /**
+ * A {@link DirectoryConfiguration} implementation that creates {@link Directory}
instances of the supplied type for each
+ * workspace and pools the results, ensuring that the same {@link Directory} instance
is always returned for the same
+ * workspace name.
+ *
+ * @param <DirectoryType> the concrete type of the directory
+ */
+ @ThreadSafe
+ protected static abstract class PoolingDirectoryFactory<DirectoryType extends
Directory> implements DirectoryConfiguration {
+ private final ConcurrentHashMap<IndexId, DirectoryType> directories = new
ConcurrentHashMap<IndexId, DirectoryType>();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.DirectoryConfiguration#getDirectory(java.lang.String,
java.lang.String)
+ */
+ public Directory getDirectory( String workspaceName,
+ String indexName ) throws SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ IndexId id = new IndexId(workspaceName, indexName);
+ DirectoryType result = directories.get(id);
+ if (result == null) {
+ DirectoryType newDirectory = createDirectory(workspaceName, indexName);
+ result = directories.putIfAbsent(id, newDirectory);
+ if (result == null) result = newDirectory;
+ }
+ return result;
+ }
+
+ /**
+ * Method implemented by subclasses to create a new Directory implementation.
+ *
+ * @param workspaceName the name of the workspace for which the {@link Directory}
is to be created; never null
+ * @param indexName the name of the index to be created
+ * @return the new directory; may not be null
+ * @throws SearchEngineException if there is a problem creating the directory
+ */
+ protected abstract DirectoryType createDirectory( String workspaceName,
+ String indexName ) throws
SearchEngineException;
+ }
+
+ /**
+ * A {@link DirectoryConfiguration} implementation that creates {@link RAMDirectory}
instances for each workspace and index
+ * name. Each factory instance maintains a pool of {@link RAMDirectory} instances,
ensuring that the same {@link RAMDirectory}
+ * is always returned for the same workspace name.
+ */
+ @ThreadSafe
+ public static class RamDirectoryFactory extends
PoolingDirectoryFactory<RAMDirectory> {
+ protected RamDirectoryFactory() {
+ }
+
+ @Override
+ protected RAMDirectory createDirectory( String workspaceName,
+ String indexName ) {
+ return new RAMDirectory();
+ }
+ }
+
+ /**
+ * A {@link DirectoryConfiguration} implementation that creates {@link FSDirectory}
instances for each workspace and index
+ * name. This factory is created with a parent directory under which all workspace
and index directories are created.
+ * <p>
+ * This uses the supplied encoders to translate the workspace and index names into
valid directory names. By default, no
+ * encoding is performed, meaning that the workspace and index names are used
explicitly as directory names. This default
+ * behavior, then, means that not all values of workspace names or index names will
work. If you want to be sure that all
+ * workspace names work, supply an encoder for the workspace names. (Index names are
currently such that they will always be
+ * valid directory names, but you can always supply an encoder if you'd like.)
+ * </p>
+ */
+ public static class FileSystemDirectoryFromNameFactory extends
PoolingDirectoryFactory<FSDirectory> {
+ private final File parentFile;
+ private final LockFactory lockFactory;
+ private final TextEncoder workspaceNameEncoder;
+ private final TextEncoder indexNameEncoder;
+
+ /**
+ * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent ) {
+ this(parent, null, null, null);
+ }
+
+ /**
+ * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ LockFactory lockFactory ) {
+ this(parent, lockFactory, null, null);
+ }
+
+ /**
+ * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ this(parent, null, workspaceNameEncoder, indexNameEncoder);
+ }
+
+ /**
+ * Create a new {@link DirectoryConfiguration} that creates {@link FSDirectory}
instances mapped to folders under a parent
+ * folder, where the workspace name is used to create the workspace folder. Note
that this has ramifications on the
+ * allowable workspace names.
+ *
+ * @param parent the parent folder
+ * @param lockFactory the lock factory; may be null
+ * @param workspaceNameEncoder the encoder that should be used for encoding the
workspace name into a directory name
+ * @param indexNameEncoder the encoder that should be used for encoding the index
name into a directory name
+ * @throws IllegalArgumentException if the parent file is null
+ */
+ protected FileSystemDirectoryFromNameFactory( File parent,
+ LockFactory lockFactory,
+ TextEncoder workspaceNameEncoder,
+ TextEncoder indexNameEncoder ) {
+ CheckArg.isNotNull(parent, "parent");
+ this.parentFile = parent;
+ this.lockFactory = lockFactory;
+ this.workspaceNameEncoder = workspaceNameEncoder != null ?
workspaceNameEncoder : new NoOpEncoder();
+ this.indexNameEncoder = indexNameEncoder != null ? indexNameEncoder : new
NoOpEncoder();
+ }
+
+ @Override
+ protected FSDirectory createDirectory( String workspaceName,
+ String indexName ) {
+ File workspaceFile = new File(parentFile,
workspaceNameEncoder.encode(workspaceName));
+ if (!workspaceFile.exists()) {
+ workspaceFile.mkdirs();
+ } else {
+ if (!workspaceFile.isDirectory()) {
+ I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ if (!workspaceFile.canRead()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeRead;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ if (!workspaceFile.canWrite()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
+ throw new
SearchEngineException(msg.text(workspaceFile.getAbsolutePath(), workspaceName));
+ }
+ }
+ File directory = workspaceFile;
+ if (indexName != null) {
+ File indexFile = new File(workspaceFile,
indexNameEncoder.encode(indexName));
+ if (!indexFile.exists()) {
+ indexFile.mkdirs();
+ } else {
+ if (!indexFile.isDirectory()) {
+ I18n msg = SearchI18n.locationForIndexesIsNotDirectory;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ if (!indexFile.canRead()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeRead;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ if (!indexFile.canWrite()) {
+ I18n msg = SearchI18n.locationForIndexesCannotBeWritten;
+ throw new
SearchEngineException(msg.text(indexFile.getAbsolutePath(), workspaceName));
+ }
+ }
+ directory = indexFile;
+ }
+ try {
+ return create(directory, lockFactory);
+ } catch (IOException e) {
+ throw new SearchEngineException(e);
+ }
+ }
+
+ /**
+ * Override this method to define which subclass of {@link FSDirectory} should be
created.
+ *
+ * @param directory the file system directory; never null
+ * @param lockFactory the lock factory; may be null
+ * @return the {@link FSDirectory} instance
+ * @throws IOException if there is a problem creating the FSDirectory instance
+ */
+ protected FSDirectory create( File directory,
+ LockFactory lockFactory ) throws IOException {
+ return FSDirectory.getDirectory(directory, lockFactory);
+ }
+ }
+
+ @Immutable
+ protected static final class IndexId {
+ private final String workspaceName;
+ private final String indexName;
+ private final int hc;
+
+ protected IndexId( String workspaceName,
+ String indexName ) {
+ assert workspaceName != null;
+ this.workspaceName = workspaceName;
+ this.indexName = indexName;
+ this.hc = HashCode.compute(this.workspaceName, this.indexName);
+ }
+
+ /**
+ * @return indexName
+ */
+ public String getIndexName() {
+ return indexName;
+ }
+
+ /**
+ * @return workspaceName
+ */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof IndexId) {
+ IndexId that = (IndexId)obj;
+ if (this.hashCode() != that.hashCode()) return false;
+ if (!this.workspaceName.equals(that.workspaceName)) return false;
+ if (!this.indexName.equals(that.indexName)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return indexName != null ? workspaceName + "/" + this.indexName :
this.workspaceName;
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/DirectoryConfigurations.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/EncodingNamespaceRegistry.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/EncodingNamespaceRegistry.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/EncodingNamespaceRegistry.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,233 @@
+/*
+ * 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.search;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.text.NoOpEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.graph.DnaLexicon;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.JcrMixLexicon;
+import org.jboss.dna.graph.JcrNtLexicon;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.basic.BasicNamespace;
+
+/**
+ * A {@link NamespaceRegistry} implementation that uses encoded representations of the
namespace URIs for the namespace prefixes.
+ */
+@ThreadSafe
+class EncodingNamespaceRegistry implements NamespaceRegistry {
+
+ public static final Set<String> DEFAULT_FIXED_NAMESPACES =
Collections.unmodifiableSet(new HashSet<String>(
+
Arrays.asList(new String[] {
+
"",
+
DnaLexicon.Namespace.URI,
+
JcrLexicon.Namespace.URI,
+
JcrNtLexicon.Namespace.URI,
+
JcrMixLexicon.Namespace.URI})));
+
+ private final NamespaceRegistry registry;
+ private final TextEncoder encoder;
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ @GuardedBy( "lock" )
+ private final Map<String, String> uriToEncodedPrefix = new HashMap<String,
String>();
+ @GuardedBy( "lock" )
+ private final Map<String, String> encodedPrefixToUri = new HashMap<String,
String>();
+ private final Set<String> fixedNamespaceUris;
+
+ /**
+ * @param registry the original registry
+ * @param encoder the encoder; may be null if no encoding should be used
+ */
+ EncodingNamespaceRegistry( NamespaceRegistry registry,
+ TextEncoder encoder ) {
+ this(registry, encoder, null);
+ }
+
+ /**
+ * @param registry the original registry
+ * @param encoder the encoder; may be null if no encoding should be used
+ * @param fixedUris the set of URIs that is to be fixed and not encoded; or null if
the default namespaces are to be fixed
+ */
+ EncodingNamespaceRegistry( NamespaceRegistry registry,
+ TextEncoder encoder,
+ Set<String> fixedUris ) {
+ this.registry = registry;
+ this.encoder = encoder != null ? encoder : new NoOpEncoder();
+ this.fixedNamespaceUris = fixedUris != null ? Collections.unmodifiableSet(new
HashSet<String>(fixedUris)) : DEFAULT_FIXED_NAMESPACES;
+ assert this.registry != null;
+ assert this.encoder != null;
+ assert this.fixedNamespaceUris != null;
+ }
+
+ /**
+ * @return fixedNamespaceUris
+ */
+ public Set<String> getFixedNamespaceUris() {
+ return fixedNamespaceUris;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.NamespaceRegistry#getDefaultNamespaceUri()
+ */
+ public String getDefaultNamespaceUri() {
+ return this.registry.getDefaultNamespaceUri();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.property.NamespaceRegistry#getNamespaceForPrefix(java.lang.String)
+ */
+ public String getNamespaceForPrefix( String prefix ) {
+ // First look in the map ...
+ String result = null;
+ try {
+ lock.readLock().lock();
+ result = encodedPrefixToUri.get(prefix);
+ if (result != null) return result;
+ } finally {
+ lock.readLock().unlock();
+ }
+
+ // Make sure we have encoded all the namespaces in the registry ...
+ Set<Namespace> namespaces = new
HashSet<Namespace>(this.registry.getNamespaces());
+ Set<Namespace> encodedNamespaces = this.getNamespaces();
+ namespaces.removeAll(encodedNamespaces);
+ try {
+ lock.writeLock().lock();
+ for (Namespace namespace : namespaces) {
+ String namespaceUri = namespace.getNamespaceUri();
+ String encoded = fixedNamespaceUris.contains(namespaceUri) ?
namespace.getPrefix() : encoder.encode(namespaceUri);
+ uriToEncodedPrefix.put(namespaceUri, encoded);
+ encodedPrefixToUri.put(encoded, namespaceUri);
+ if (result == null && encoded.equals(prefix)) result =
namespaceUri;
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ if (result != null) return result;
+
+ // There's nothing, so just delegate to the registry ...
+ return this.registry.getNamespaceForPrefix(prefix);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.NamespaceRegistry#getRegisteredNamespaceUris()
+ */
+ public Set<String> getRegisteredNamespaceUris() {
+ return this.registry.getRegisteredNamespaceUris();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.property.NamespaceRegistry#isRegisteredNamespaceUri(java.lang.String)
+ */
+ public boolean isRegisteredNamespaceUri( String namespaceUri ) {
+ return this.registry.isRegisteredNamespaceUri(namespaceUri);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.property.NamespaceRegistry#getPrefixForNamespaceUri(java.lang.String,
boolean)
+ */
+ public String getPrefixForNamespaceUri( String namespaceUri,
+ boolean generateIfMissing ) {
+ if (fixedNamespaceUris.contains(namespaceUri)) {
+ return this.registry.getPrefixForNamespaceUri(namespaceUri,
generateIfMissing);
+ }
+ String encoded = null;
+ try {
+ lock.readLock().lock();
+ encoded = uriToEncodedPrefix.get(namespaceUri);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (encoded == null) {
+ encoded = encoder.encode(namespaceUri);
+ try {
+ lock.writeLock().lock();
+ uriToEncodedPrefix.put(namespaceUri, encoded);
+ encodedPrefixToUri.put(encoded, namespaceUri);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ return encoded;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaces()
+ */
+ public Set<Namespace> getNamespaces() {
+ Set<Namespace> results = new HashSet<Namespace>();
+ try {
+ lock.readLock().lock();
+ for (Map.Entry<String, String> entry : uriToEncodedPrefix.entrySet())
{
+ String uri = entry.getKey();
+ String prefix = entry.getValue();
+ results.add(new BasicNamespace(prefix, uri));
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ return results;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.NamespaceRegistry#register(java.lang.String,
java.lang.String)
+ */
+ public String register( String prefix,
+ String namespaceUri ) {
+ return this.registry.register(prefix, namespaceUri);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.property.NamespaceRegistry#unregister(java.lang.String)
+ */
+ public boolean unregister( String namespaceUri ) {
+ return this.registry.unregister(namespaceUri);
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/EncodingNamespaceRegistry.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexContext.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexContext.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexContext.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,278 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+import net.jcip.annotations.NotThreadSafe;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.Directory;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.DateTimeFactory;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.ValueFactory;
+
+/**
+ * A set of index readers and writers.
+ */
+@NotThreadSafe
+final class IndexContext {
+
+ private final ExecutionContext context;
+ private final Directory pathsIndexDirectory;
+ private final Directory contentIndexDirectory;
+ private final Analyzer analyzer;
+ private final boolean overwrite;
+ private final ValueFactory<String> stringFactory;
+ private final DateTimeFactory dateFactory;
+ private IndexReader pathsReader;
+ private IndexWriter pathsWriter;
+ private IndexSearcher pathsSearcher;
+ private IndexReader contentReader;
+ private IndexWriter contentWriter;
+ private IndexSearcher contentSearcher;
+
+ IndexContext( ExecutionContext context,
+ Directory pathsIndexDirectory,
+ Directory contentIndexDirectory,
+ Analyzer analyzer,
+ boolean overwrite ) {
+ assert context != null;
+ assert pathsIndexDirectory != null;
+ assert contentIndexDirectory != null;
+ this.context = context;
+ this.pathsIndexDirectory = pathsIndexDirectory;
+ this.contentIndexDirectory = contentIndexDirectory;
+ this.analyzer = analyzer;
+ this.overwrite = overwrite;
+ this.stringFactory = context.getValueFactories().getStringFactory();
+ this.dateFactory = context.getValueFactories().getDateFactory();
+ }
+
+ /**
+ * @return context
+ */
+ public ExecutionContext context() {
+ return context;
+ }
+
+ /**
+ * @return stringFactory
+ */
+ public ValueFactory<String> stringFactory() {
+ return stringFactory;
+ }
+
+ public DateTimeFactory dateFactory() {
+ return dateFactory;
+ }
+
+ public PathFactory pathFactory() {
+ return context.getValueFactories().getPathFactory();
+ }
+
+ public IndexReader getPathsReader() throws IOException {
+ if (pathsReader == null) {
+ pathsReader = IndexReader.open(pathsIndexDirectory);
+ }
+ return pathsReader;
+ }
+
+ public IndexReader getContentReader() throws IOException {
+ if (contentReader == null) {
+ contentReader = IndexReader.open(contentIndexDirectory);
+ }
+ return contentReader;
+ }
+
+ public IndexWriter getPathsWriter() throws IOException {
+ if (pathsWriter == null) {
+ pathsWriter = new IndexWriter(pathsIndexDirectory, analyzer, overwrite,
MaxFieldLength.UNLIMITED);
+ }
+ return pathsWriter;
+ }
+
+ public IndexWriter getContentWriter() throws IOException {
+ if (contentWriter == null) {
+ contentWriter = new IndexWriter(contentIndexDirectory, analyzer, overwrite,
MaxFieldLength.UNLIMITED);
+ }
+ return contentWriter;
+ }
+
+ public IndexSearcher getPathsSearcher() throws IOException {
+ if (pathsSearcher == null) {
+ pathsSearcher = new IndexSearcher(getPathsReader());
+ }
+ return pathsSearcher;
+ }
+
+ public IndexSearcher getContentSearcher() throws IOException {
+ if (contentSearcher == null) {
+ contentSearcher = new IndexSearcher(getContentReader());
+ }
+ return contentSearcher;
+ }
+
+ public boolean hasWriters() {
+ return pathsWriter != null || contentWriter != null;
+ }
+
+ public void commit() throws IOException {
+ IOException ioError = null;
+ RuntimeException runtimeError = null;
+ if (pathsReader != null) {
+ try {
+ pathsReader.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsReader = null;
+ }
+ }
+ if (contentReader != null) {
+ try {
+ contentReader.close();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ contentReader = null;
+ }
+ }
+ if (pathsWriter != null) {
+ try {
+ pathsWriter.commit();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ try {
+ pathsWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsWriter = null;
+ }
+ }
+ }
+ if (contentWriter != null) {
+ try {
+ contentWriter.commit();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ try {
+ contentWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ contentWriter = null;
+ }
+ }
+ }
+ if (ioError != null) throw ioError;
+ if (runtimeError != null) throw runtimeError;
+ }
+
+ public void rollback() throws IOException {
+ IOException ioError = null;
+ RuntimeException runtimeError = null;
+ if (pathsReader != null) {
+ try {
+ pathsReader.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsReader = null;
+ }
+ }
+ if (contentReader != null) {
+ try {
+ contentReader.close();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ contentReader = null;
+ }
+ }
+ if (pathsWriter != null) {
+ try {
+ pathsWriter.rollback();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ try {
+ pathsWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ pathsWriter = null;
+ }
+ }
+ }
+ if (contentWriter != null) {
+ try {
+ contentWriter.rollback();
+ } catch (IOException e) {
+ if (ioError == null) ioError = e;
+ } catch (RuntimeException e) {
+ if (runtimeError == null) runtimeError = e;
+ } finally {
+ try {
+ contentWriter.close();
+ } catch (IOException e) {
+ ioError = e;
+ } catch (RuntimeException e) {
+ runtimeError = e;
+ } finally {
+ contentWriter = null;
+ }
+ }
+ }
+ if (ioError != null) throw ioError;
+ if (runtimeError != null) throw runtimeError;
+ }
+
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexContext.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingRules.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingRules.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingRules.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,628 @@
+/*
+ * 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.search;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.NotThreadSafe;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.property.Name;
+
+/**
+ * The set of rules that dictate how properties should be indexed.
+ */
+@Immutable
+public class IndexingRules {
+
+ public static final int INDEX = 2 << 0;
+ public static final int ANALYZE = 2 << 1;
+ public static final int STORE = 2 << 2;
+ public static final int STORE_COMPRESSED = 2 << 3;
+ public static final int ANALYZED_WITHOUT_NORMS = 2 << 4;
+ public static final int FULL_TEXT = 2 << 5;
+ public static final int TREAT_AS_DATE = 2 << 6;
+
+ /**
+ * A single rule that dictates how a single property should be indexed.
+ *
+ * @see IndexingRules#getRule(Name)
+ */
+ @Immutable
+ public static interface Rule {
+ boolean isIncluded();
+
+ boolean isSkipped();
+
+ boolean isAnalyzed();
+
+ boolean isAnalyzedWithoutNorms();
+
+ boolean isStored();
+
+ boolean isStoredCompressed();
+
+ boolean isFullText();
+
+ boolean isDate();
+
+ int getMask();
+
+ Field.Store getStoreOption();
+
+ Field.Index getIndexOption();
+ }
+
+ public static final Rule SKIP = new SkipRule();
+
+ @Immutable
+ protected static class SkipRule implements Rule {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getMask()
+ */
+ public int getMask() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isAnalyzed()
+ */
+ public boolean isAnalyzed() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isAnalyzedWithoutNorms()
+ */
+ public boolean isAnalyzedWithoutNorms() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isFullText()
+ */
+ public boolean isFullText() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isIncluded()
+ */
+ public boolean isIncluded() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isSkipped()
+ */
+ public boolean isSkipped() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isStored()
+ */
+ public boolean isStored() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isStoredCompressed()
+ */
+ public boolean isStoredCompressed() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isDate()
+ */
+ public boolean isDate() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getIndexOption()
+ */
+ public Index getIndexOption() {
+ return Field.Index.NO;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getStoreOption()
+ */
+ public Store getStoreOption() {
+ return Field.Store.NO;
+ }
+ }
+
+ @Immutable
+ public static final class GeneralRule implements Rule {
+ private final int value;
+ private final Field.Store store;
+ private final Field.Index index;
+
+ protected GeneralRule( int value ) {
+ this.value = value;
+ this.index = isAnalyzed() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
+ this.store = isStored() ? Field.Store.YES : Field.Store.NO;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getMask()
+ */
+ public int getMask() {
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isAnalyzed()
+ */
+ public boolean isAnalyzed() {
+ return (value & ANALYZE) == ANALYZE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isAnalyzedWithoutNorms()
+ */
+ public boolean isAnalyzedWithoutNorms() {
+ return (value & ANALYZED_WITHOUT_NORMS) == ANALYZED_WITHOUT_NORMS;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isFullText()
+ */
+ public boolean isFullText() {
+ return (value & FULL_TEXT) == FULL_TEXT;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isIncluded()
+ */
+ public boolean isIncluded() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isSkipped()
+ */
+ public boolean isSkipped() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isStored()
+ */
+ public boolean isStored() {
+ return (value & STORE) == STORE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isStoredCompressed()
+ */
+ public boolean isStoredCompressed() {
+ return (value & STORE_COMPRESSED) == STORE_COMPRESSED;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#isDate()
+ */
+ public boolean isDate() {
+ return (value & TREAT_AS_DATE) == TREAT_AS_DATE;
+ }
+
+ protected Rule with( int options ) {
+ return createRule(value | options);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getIndexOption()
+ */
+ public Index getIndexOption() {
+ return index;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingRules.Rule#getStoreOption()
+ */
+ public Store getStoreOption() {
+ return store;
+ }
+ }
+
+ private static final ConcurrentHashMap<Integer, Rule> CACHE = new
ConcurrentHashMap<Integer, Rule>();
+
+ protected static Rule createRule( int value ) {
+ if (value <= 0) {
+ return SKIP;
+ }
+ Integer key = new Integer(value);
+ Rule rule = CACHE.get(key);
+ if (rule == null) {
+ Rule newRule = new GeneralRule(value);
+ rule = CACHE.putIfAbsent(value, newRule);
+ if (rule == null) rule = newRule;
+ }
+ return rule;
+ }
+
+ private final Map<Name, Rule> rulesByName;
+ private final Rule defaultRule;
+
+ protected IndexingRules( Map<Name, Rule> rulesByName,
+ Rule defaultRule ) {
+ this.rulesByName = rulesByName;
+ this.defaultRule = defaultRule != null ? defaultRule : SKIP;
+ assert this.defaultRule != null;
+ }
+
+ /**
+ * Get the rule associated with the given property name.
+ *
+ * @param name the property name, or null if the default rule is to be returned
+ * @return the rule; never null
+ */
+ public Rule getRule( Name name ) {
+ Rule result = rulesByName.get(name);
+ return result != null ? result : this.defaultRule;
+ }
+
+ /**
+ * Return a new builder that can be used to create {@link IndexingRules} objects.
+ *
+ * @return a builder; never null
+ */
+ public static Builder createBuilder() {
+ return new Builder(new HashMap<Name, Rule>());
+ }
+
+ /**
+ * Return a new builder that can be used to create {@link IndexingRules} objects.
+ *
+ * @param initialRules the rules that the builder should start with
+ * @return a builder; never null
+ * @throws IllegalArgumentException if the initial rules reference is null
+ */
+ public static Builder createBuilder( IndexingRules initialRules ) {
+ CheckArg.isNotNull(initialRules, "initialRules");
+ return new
Builder(initialRules.rulesByName).defaultTo(initialRules.defaultRule);
+ }
+
+ /**
+ * A builder of immutable {@link IndexingRules} objects.
+ */
+ @NotThreadSafe
+ public static class Builder {
+ private final Map<Name, Rule> rulesByName;
+ private Rule defaultRule;
+
+ Builder( Map<Name, Rule> rulesByName ) {
+ assert rulesByName != null;
+ this.rulesByName = rulesByName;
+ }
+
+ /**
+ * Set the default rules.
+ *
+ * @param rule the default rule to use
+ * @return this builder for convenience and method chaining; never null
+ * @throws IllegalArgumentException if the rule mask is negative
+ */
+ public Builder defaultTo( Rule rule ) {
+ CheckArg.isNotNull(rule, "rule");
+ defaultRule = rule;
+ return this;
+ }
+
+ /**
+ * Set the default rules.
+ *
+ * @param ruleMask the bitmask of rule to use
+ * @return this builder for convenience and method chaining; never null
+ * @throws IllegalArgumentException if the rule mask is negative
+ */
+ public Builder defaultTo( int ruleMask ) {
+ CheckArg.isNonNegative(ruleMask, "options");
+ if (ruleMask == 0) {
+ defaultRule = SKIP;
+ } else {
+ // Make sure the index flag is set ...
+ ruleMask |= INDEX;
+ defaultRule = createRule(ruleMask);
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be skipped from indexing.
+ *
+ * @param namesToIndex the names of the properties that are to be skipped
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder skip( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ rulesByName.put(name, SKIP);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set the properties with the supplied names to use the supplied rules.
+ *
+ * @param ruleMask the bitmask of rules to use
+ * @param namesToIndex the names of the properties that are to be skipped
+ * @return this builder for convenience and method chaining; never null
+ * @throws IllegalArgumentException if the rule mask is negative
+ */
+ public Builder set( int ruleMask,
+ Name... namesToIndex ) {
+ CheckArg.isNonNegative(ruleMask, "options");
+ if (namesToIndex != null) {
+ if (ruleMask > 0) {
+ skip(namesToIndex);
+ } else {
+ // Make sure the index flag is set ...
+ ruleMask |= INDEX;
+ Rule rule = createRule(ruleMask);
+ for (Name name : namesToIndex) {
+ rulesByName.put(name, rule);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to use the supplied rule mask.
This does not remove any other rules for
+ * these properties.
+ *
+ * @param ruleMask the bitmask of rules to add
+ * @param namesToIndex the names of the properties that are to be skipped
+ * @return this builder for convenience and method chaining; never null
+ * @throws IllegalArgumentException if the rule mask is negative
+ */
+ public Builder add( int ruleMask,
+ Name... namesToIndex ) {
+ CheckArg.isNonNegative(ruleMask, "options");
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, ruleMask);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be indexed. This does not
remove any other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be indexed
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder index( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, INDEX);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be analyzed (and obviously
indexed). This does not remove any other
+ * rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be analyzed
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder analyze( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, ANALYZE | INDEX);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be stored (and obviously
indexed). This does not remove any other rules
+ * for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be stored
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder store( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, STORE | INDEX);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be included in full-text
searches (and obviously indexed). This does not
+ * remove any other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be included in
full-text searches
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder fullText( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, FULL_TEXT | INDEX);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be treated as dates (and
obviously indexed). This does not remove any
+ * other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be included in
full-text searches
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder treatAsDates( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, TREAT_AS_DATE | INDEX);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be indexed, analyzed and
stored. This does not remove any other rules
+ * for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be indexed,
analyzed and stored
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder analyzeAndStore( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, INDEX | ANALYZE | STORE);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be indexed, analyzed, stored
and included in full-text searches. This
+ * does not remove any other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be indexed,
analyzed, stored and included in full-text
+ * searches
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder analyzeAndStoreAndFullText( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, INDEX | ANALYZE | STORE | FULL_TEXT);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be indexed, analyzed and
included in full-text searches. This does not
+ * remove any other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be indexed,
analyzed and included in full-text searches
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder analyzeAndFullText( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, INDEX | ANALYZE | FULL_TEXT);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Mark the properties with the supplied names to be indexed, stored and included
in full-text searches. This does not
+ * remove any other rules for these properties.
+ *
+ * @param namesToIndex the names of the properties that are to be indexed, stored
and included in full-text searches
+ * @return this builder for convenience and method chaining; never null
+ */
+ public Builder storeAndFullText( Name... namesToIndex ) {
+ if (namesToIndex != null) {
+ for (Name name : namesToIndex) {
+ add(name, INDEX | STORE | FULL_TEXT);
+ }
+ }
+ return this;
+ }
+
+ protected void add( Name name,
+ int option ) {
+ Rule rule = rulesByName.get(name);
+ if (rule != null) {
+ option |= rule.getMask();
+ }
+ rulesByName.put(name, createRule(option));
+ }
+
+ /**
+ * Build the indexing rules.
+ *
+ * @return the immutable indexing rules.
+ */
+ public IndexingRules build() {
+ return new IndexingRules(Collections.unmodifiableMap(new HashMap<Name,
Rule>(rulesByName)), defaultRule);
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingRules.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingStrategy.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingStrategy.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingStrategy.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,121 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.queryParser.ParseException;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Node;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.request.ChangeRequest;
+
+/**
+ * Interface defining the behaviors associated with indexing graph content.
+ */
+interface IndexingStrategy {
+
+ int getChangeCountForAutomaticOptimization();
+
+ TextEncoder getNamespaceEncoder();
+
+ /**
+ * Index the node given the index writers. Note that implementors should simply just
use the writers to add documents to the
+ * index(es), and should never call any of the writer lifecycle methods (e.g., {@link
IndexWriter#commit()},
+ * {@link IndexWriter#rollback()}, etc.).
+ *
+ * @param node the node to be indexed; never null
+ * @param indexes the set of index readers and writers; never null
+ * @throws IOException if there is a problem indexing or using the writers
+ */
+ void index( Node node,
+ IndexContext indexes ) throws IOException;
+
+ /**
+ * Update the indexes to reflect the supplied changes to the graph content. Note that
implementors should simply just use the
+ * writers to add documents to the index(es), and should never call any of the writer
lifecycle methods (e.g.,
+ * {@link IndexWriter#commit()}, {@link IndexWriter#rollback()}, etc.).
+ *
+ * @param changes the set of changes to the content
+ * @param indexes the set of index readers and writers; never null
+ * @return the (approximate) number of nodes that were affected by the changes
+ * @throws IOException if there is a problem indexing or using the writers
+ */
+ int apply( Iterable<ChangeRequest> changes,
+ IndexContext indexes ) throws IOException;
+
+ /**
+ * Remove from the index(es) all of the information pertaining to the nodes at or
below the supplied path. Note that
+ * implementors should simply just use the writers to add documents to the index(es),
and should never call any of the writer
+ * lifecycle methods (e.g., {@link IndexWriter#commit()}, {@link
IndexWriter#rollback()}, etc.).
+ *
+ * @param path the path identifying the graph content that is to be removed; never
null
+ * @param indexes the set of index readers and writers; never null
+ * @return the (approximate) number of nodes that were affected by the changes
+ * @throws IOException if there is a problem indexing or using the writers
+ */
+ int deleteBelow( Path path,
+ IndexContext indexes ) throws IOException;
+
+ /**
+ * Create the analyzer that is used for reading and updating the indexes.
+ *
+ * @return the analyzer; may not be null
+ */
+ Analyzer createAnalyzer();
+
+ /**
+ * Perform a full-text search given the supplied query.
+ *
+ * @param fullTextString the full-text query; never null or blank
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @param indexes the set of index readers and writers; never null
+ * @param results the list where the results should be accumulated; never null
+ * @throws IOException if there is a problem indexing or using the writers
+ * @throws ParseException if there is a problem parsing the query
+ */
+ void performQuery( String fullTextString,
+ int maxResults,
+ int offset,
+ IndexContext indexes,
+ List<Location> results ) throws IOException,
ParseException;
+
+ /**
+ * Perform a query of the content.
+ *
+ * @param query the query; never null
+ * @param indexes the set of index readers and writers; never null
+ * @return the results of the query
+ * @throws IOException if there is a problem indexing or using the writers
+ * @throws ParseException if there is a problem parsing the query
+ */
+ QueryResults performQuery( QueryCommand query,
+ IndexContext indexes ) throws IOException,
ParseException;
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/IndexingStrategy.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,57 @@
+/*
+ * 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.search;
+
+import java.util.List;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+
+/**
+ * A {@link ProcessingComponent} implementation that is used by the {@link
LuceneQueryEngine.LuceneProcessor} to perform atomic
+ * queries against the Lucene indexes.
+ */
+class LuceneQueryComponent extends ProcessingComponent {
+
+ private final PlanNode accessNode;
+
+ LuceneQueryComponent( QueryContext context,
+ Columns columns,
+ PlanNode accessNode ) {
+ super(context, columns);
+ this.accessNode = accessNode;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List<Object[]> execute() {
+ if (accessNode != null) return null;
+ return null;
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryComponent.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,112 @@
+/*
+ * 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.search;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import org.apache.lucene.queryParser.ParseException;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.QueryEngine;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.QueryResults.Columns;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.optimize.Optimizer;
+import org.jboss.dna.graph.query.optimize.OptimizerRule;
+import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.process.ProcessingComponent;
+import org.jboss.dna.graph.query.process.QueryProcessor;
+import org.jboss.dna.graph.query.process.SelectComponent.Analyzer;
+import org.jboss.dna.graph.query.validate.Schemata;
+
+/**
+ *
+ */
+class LuceneQueryEngine {
+
+ private QueryEngine engine;
+
+ public LuceneQueryEngine( Schemata schemata ) {
+ engine = new QueryEngine(new CanonicalPlanner(), new LuceneOptimizer(), new
LuceneProcessor(), schemata);
+ }
+
+ /**
+ * Execute the supplied query by planning, optimizing, and then processing it.
+ *
+ * @param query the query that is to be executed
+ * @param indexes the indexes that should be used to execute the query; never null
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ * @throws IOException if there is a problem indexing or using the writers
+ * @throws ParseException if there is a problem parsing the query
+ */
+ public QueryResults execute( QueryCommand query,
+ IndexContext indexes ) throws IOException,
ParseException {
+ return engine.execute(indexes.context(), query, new PlanHints());
+ }
+
+ /**
+ * An {@link Optimizer} implementation that specializes the {@link
RuleBasedOptimizer} by using custom rules.
+ */
+ protected static class LuceneOptimizer extends RuleBasedOptimizer {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.optimize.RuleBasedOptimizer#populateRuleStack(java.util.LinkedList,
+ * org.jboss.dna.graph.query.plan.PlanHints)
+ */
+ @Override
+ protected void populateRuleStack( LinkedList<OptimizerRule> ruleStack,
+ PlanHints hints ) {
+ super.populateRuleStack(ruleStack, hints);
+ // Add any custom rules here, either at the front of the stack or at the end
+ }
+ }
+
+ /**
+ * A query processor that operates against Lucene indexes. All functionality is
inherited from the {@link QueryProcessor},
+ * except for the creation of the {@link ProcessingComponent} that does the low-level
atomic queries (against the Lucene
+ * indexes).
+ */
+ protected static class LuceneProcessor extends QueryProcessor {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.query.process.QueryProcessor#createAccessComponent(org.jboss.dna.graph.query.QueryContext,
+ * org.jboss.dna.graph.query.plan.PlanNode,
org.jboss.dna.graph.query.QueryResults.Columns,
+ * org.jboss.dna.graph.query.process.SelectComponent.Analyzer)
+ */
+ @Override
+ protected ProcessingComponent createAccessComponent( QueryContext context,
+ PlanNode accessNode,
+ Columns resultColumns,
+ Analyzer analyzer ) {
+ return new LuceneQueryComponent(context, resultColumns, accessNode);
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/LuceneQueryEngine.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,305 @@
+/*
+ * 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.search;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.store.Directory;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySource;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.request.ChangeRequest;
+import org.jboss.dna.graph.request.InvalidWorkspaceException;
+
+/**
+ * A component that acts as a search engine for the content within a single {@link
RepositorySource}. This engine manages a set of
+ * indexes and provides search functionality for each of the workspaces within the
source, and provides various methods to
+ * (re)index the content contained with source's workspaces and keep the indexes
up-to-date via changes.
+ */
+@ThreadSafe
+public class SearchEngine {
+
+ private final ExecutionContext context;
+ private final String sourceName;
+ private final RepositoryConnectionFactory connectionFactory;
+ private final DirectoryConfiguration directoryFactory;
+ private final IndexingStrategy indexingStrategy;
+ private final PathFactory pathFactory;
+ @GuardedBy( "workspaceEngineLock" )
+ private final Map<String, WorkspaceSearchEngine> workspaceEnginesByName;
+ private final ReadWriteLock workspaceEngineLock = new ReentrantReadWriteLock();
+
+ /**
+ * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
+ * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
+ * {@link DirectoryConfiguration directory factory} that defines where each
workspace's indexes should be placed.
+ *
+ * @param context the execution context in which all indexing operations should be
performed
+ * @param sourceName the name of the {@link RepositorySource}
+ * @param connectionFactory the connection factory
+ * @param directoryFactory the factory for Lucene {@link Directory directories}
+ * @param indexingStrategy the indexing strategy that governs how properties are to
be indexed; or null if the default
+ * strategy should be used
+ * @throws IllegalArgumentException if any of the parameters (other than indexing
strategy) are null
+ */
+ public SearchEngine( ExecutionContext context,
+ String sourceName,
+ RepositoryConnectionFactory connectionFactory,
+ DirectoryConfiguration directoryFactory,
+ IndexingStrategy indexingStrategy ) {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(sourceName, "sourceName");
+ CheckArg.isNotNull(connectionFactory, "connectionFactory");
+ CheckArg.isNotNull(directoryFactory, "directoryFactory");
+ this.sourceName = sourceName;
+ this.connectionFactory = connectionFactory;
+ this.directoryFactory = directoryFactory;
+ this.context = context;
+ this.pathFactory = context.getValueFactories().getPathFactory();
+ this.workspaceEnginesByName = new HashMap<String,
WorkspaceSearchEngine>();
+ this.indexingStrategy = indexingStrategy != null ? indexingStrategy : new
StoreLittleIndexingStrategy();
+ }
+
+ /**
+ * Get the name of the RepositorySource that this engine is to use.
+ *
+ * @return the source name; never null
+ */
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ /**
+ * Get the context in which all indexing operations execute.
+ *
+ * @return the execution context; never null
+ */
+ public ExecutionContext getContext() {
+ return context;
+ }
+
+ /**
+ * Utility to create a Graph for the source.
+ *
+ * @return the graph instance; never null
+ */
+ final Graph graph() {
+ return Graph.create(sourceName, connectionFactory, context);
+ }
+
+ /**
+ * Get the search engine for the workspace with the supplied name.
+ *
+ * @param workspaceName the name of the workspace
+ * @return the workspace's search engine
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ protected WorkspaceSearchEngine getWorkspaceEngine( String workspaceName ) {
+ WorkspaceSearchEngine engine = null;
+ try {
+ workspaceEngineLock.readLock().lock();
+ engine = workspaceEnginesByName.get(workspaceName);
+ } finally {
+ workspaceEngineLock.readLock().unlock();
+ }
+
+ if (engine == null) {
+ // Verify the workspace does exist ...
+ if (!graph().getWorkspaces().contains(workspaceName)) {
+ String msg =
GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, getSourceName());
+ throw new InvalidWorkspaceException(msg);
+ }
+ try {
+ workspaceEngineLock.writeLock().lock();
+ // Check whether another thread got in and created the engine while we
waited ...
+ engine = workspaceEnginesByName.get(workspaceName);
+ if (engine == null) {
+ // Create the engine and register it ...
+ engine = new WorkspaceSearchEngine(context, directoryFactory,
indexingStrategy, sourceName, workspaceName,
+ connectionFactory);
+ workspaceEnginesByName.put(workspaceName, engine);
+ }
+ } finally {
+ workspaceEngineLock.writeLock().unlock();
+ }
+ }
+ return engine;
+ }
+
+ /**
+ * Index all of the content at or below the supplied path in the named workspace
within the {@link #getSourceName() source}.
+ *
+ * @param workspaceName the name of the workspace
+ * @param startingPoint the path that represents the content to be indexed
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the workspace name or path are null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void indexContent( String workspaceName,
+ Path startingPoint,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ CheckArg.isNotNull(startingPoint, "startingPoint");
+ getWorkspaceEngine(workspaceName).indexContent(startingPoint, depthPerRead);
+ }
+
+ /**
+ * Index all of the content in the named workspace within the {@link #getSourceName()
source}.
+ *
+ * @param workspaceName the name of the workspace
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void indexContent( String workspaceName,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ indexContent(workspaceName, pathFactory.createRootPath(), depthPerRead);
+ }
+
+ /**
+ * Index (or re-index) all of the content in all of the workspaces within the
source.
+ *
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void indexContent( int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ Path rootPath = pathFactory.createRootPath();
+ for (String workspaceName : graph().getWorkspaces()) {
+ getWorkspaceEngine(workspaceName).indexContent(rootPath, depthPerRead);
+ }
+ }
+
+ /**
+ * Update the indexes with the supplied set of changes to the content.
+ *
+ * @param changes the set of changes to the content
+ * @throws IllegalArgumentException if the path is null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void indexChanges( final Iterable<ChangeRequest> changes ) throws
SearchEngineException {
+ // First break up all the changes into different collections, one collection per
workspace ...
+ Map<String, Collection<ChangeRequest>> changesByWorkspace = new
HashMap<String, Collection<ChangeRequest>>();
+ for (ChangeRequest request : changes) {
+ String workspaceName = request.changedWorkspace();
+ Collection<ChangeRequest> changesForWorkspace =
changesByWorkspace.get(workspaceName);
+ if (changesForWorkspace == null) {
+ changesForWorkspace = new LinkedList<ChangeRequest>();
+ changesByWorkspace.put(workspaceName, changesForWorkspace);
+ }
+ changesForWorkspace.add(request);
+ }
+ // Now update the indexes for each workspace (serially). This minimizes the time
that each workspace
+ // locks its indexes for writing.
+ for (Map.Entry<String, Collection<ChangeRequest>> entry :
changesByWorkspace.entrySet()) {
+ String workspaceName = entry.getKey();
+ Collection<ChangeRequest> changesForWorkspace = entry.getValue();
+ getWorkspaceEngine(workspaceName).indexChanges(changesForWorkspace);
+ }
+ }
+
+ /**
+ * Invoke the engine's garbage collection on all indexes used by all workspaces
in the source. This method reclaims space and
+ * optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
+ *
+ * @throws SearchEngineException if there is a problem during optimization
+ */
+ public void optimize() throws SearchEngineException {
+ for (String workspaceName : graph().getWorkspaces()) {
+ getWorkspaceEngine(workspaceName).optimize();
+ }
+ }
+
+ /**
+ * Invoke the engine's garbage collection for the indexes associated with the
specified workspace. This method reclaims space
+ * and optimizes the index. This should be done on a periodic basis after changes are
made to the engine's indexes.
+ *
+ * @param workspaceName the name of the workspace
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem during optimization
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public void optimize( String workspaceName ) throws SearchEngineException {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ getWorkspaceEngine(workspaceName).optimize();
+ }
+
+ /**
+ * Perform a full-text search of the content in the named workspace, given the
maximum number of results and the offset
+ * defining the first result the caller is interested in.
+ *
+ * @param workspaceName the name of the workspace
+ * @param fullTextSearch the full-text search to be performed; may not be null
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @return the activity that will perform the work
+ * @throws IllegalArgumentException if the workspace name is null
+ * @throws SearchEngineException if there is a problem during optimization
+ * @throws InvalidWorkspaceException if the workspace does not exist
+ */
+ public List<Location> fullTextSearch( String workspaceName,
+ String fullTextSearch,
+ int maxResults,
+ int offset ) {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ return getWorkspaceEngine(workspaceName).fullTextSearch(fullTextSearch,
maxResults, offset);
+ }
+
+ /**
+ * Perform a query of the content in the named workspace, given the Abstract Query
Model representation of the query.
+ *
+ * @param workspaceName the name of the workspace
+ * @param query the query that is to be executed, in the form of the Abstract Query
Model
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ public QueryResults execute( String workspaceName,
+ QueryCommand query ) {
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ return getWorkspaceEngine(workspaceName).execute(query);
+ }
+
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngine.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,67 @@
+/*
+ * 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.search;
+
+/**
+ * An exception that represents a problem within a search engine.
+ */
+public class SearchEngineException extends RuntimeException {
+
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ */
+ public SearchEngineException() {
+ }
+
+ /**
+ * @param message
+ */
+ public SearchEngineException( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * @param cause
+ */
+ public SearchEngineException( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public SearchEngineException( String message,
+ Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/SearchEngineException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
(rev 0)
+++ trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,68 @@
+/*
+ * 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.search;
+
+import java.util.Locale;
+import java.util.Set;
+import org.jboss.dna.common.CommonI18n;
+import org.jboss.dna.common.i18n.I18n;
+
+/**
+ *
+ */
+public class SearchI18n {
+
+ public static I18n locationForIndexesIsNotDirectory;
+ public static I18n locationForIndexesCannotBeRead;
+ public static I18n locationForIndexesCannotBeWritten;
+ public static I18n errorWhileIndexingContentAtPath;
+ public static I18n errorWhileRemovingContentAtPath;
+ public static I18n errorWhileUpdatingContent;
+ public static I18n errorWhileCommittingIndexChanges;
+ public static I18n errorCreatingIndexWriter;
+ public static I18n errorWhileOptimizingIndexes;
+ public static I18n errorWhilePerformingSearch;
+ public static I18n errorWhilePerformingQuery;
+ public static I18n errorWhileInitializingSearchEngine;
+
+ static {
+ try {
+ I18n.initialize(SearchI18n.class);
+ } catch (final Exception err) {
+ System.err.println(err);
+ }
+ }
+
+ public static Set<Locale> getLocalizationProblemLocales() {
+ return I18n.getLocalizationProblemLocales(CommonI18n.class);
+ }
+
+ public static Set<String> getLocalizationProblems() {
+ return I18n.getLocalizationProblems(CommonI18n.class);
+ }
+
+ public static Set<String> getLocalizationProblems( Locale locale ) {
+ return I18n.getLocalizationProblems(CommonI18n.class, locale);
+ }
+}
Property changes on: trunk/dna-search/src/main/java/org/jboss/dna/search/SearchI18n.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added:
trunk/dna-search/src/main/java/org/jboss/dna/search/StoreLittleIndexingStrategy.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/StoreLittleIndexingStrategy.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/StoreLittleIndexingStrategy.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,375 @@
+/*
+ * 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.search;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.document.FieldSelectorResult;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocCollector;
+import org.apache.lucene.search.TopDocs;
+import org.jboss.dna.common.text.NoOpEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.graph.DnaLexicon;
+import org.jboss.dna.graph.JcrLexicon;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Node;
+import org.jboss.dna.graph.property.Binary;
+import org.jboss.dna.graph.property.DateTime;
+import org.jboss.dna.graph.property.DateTimeFactory;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.request.ChangeRequest;
+import org.jboss.dna.search.IndexingRules.Rule;
+
+/**
+ * A simple {@link IndexingStrategy} implementation that relies upon very few fields to
be stored in the indexes.
+ */
+@ThreadSafe
+class StoreLittleIndexingStrategy implements IndexingStrategy {
+
+ static class PathIndex {
+ public static final String PATH = "path";
+ public static final String UUID = "uuid";
+ }
+
+ static class ContentIndex {
+ public static final String UUID = PathIndex.UUID;
+ public static final String FULL_TEXT = "fts";
+ }
+
+ public static final int SIZE_OF_DELETE_BATCHES = 100;
+
+ private ThreadLocal<DateFormat> dateFormatter = new
ThreadLocal<DateFormat>() {
+ @Override
+ protected DateFormat initialValue() {
+ return new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ }
+ };
+
+ private static final FieldSelector UUID_FIELD_SELECTOR = new FieldSelector() {
+ private static final long serialVersionUID = 1L;
+
+ public FieldSelectorResult accept( String fieldName ) {
+ return PathIndex.UUID.equals(fieldName) ? FieldSelectorResult.LOAD_AND_BREAK
: FieldSelectorResult.NO_LOAD;
+ }
+ };
+
+ /**
+ * The default set of {@link IndexingRules} used by {@link
StoreLittleIndexingStrategy} instances when no rules are provided.
+ */
+ public static final IndexingRules DEFAULT_RULES;
+
+ static {
+ IndexingRules.Builder builder = IndexingRules.createBuilder();
+ // Configure the default behavior ...
+ builder.defaultTo(IndexingRules.INDEX | IndexingRules.ANALYZE);
+ // Configure the UUID properties to be just indexed (not stored, not analyzed,
not included in full-text) ...
+ builder.index(JcrLexicon.UUID, DnaLexicon.UUID);
+ // Configure the properties that we'll treat as dates ...
+ builder.treatAsDates(JcrLexicon.CREATED, JcrLexicon.LAST_MODIFIED);
+ DEFAULT_RULES = builder.build();
+ }
+
+ private final IndexingRules rules;
+ private final Logger logger;
+ private final LuceneQueryEngine queryEngine;
+
+ /**
+ * Create a new indexing strategy instance that does not support queries.
+ */
+ public StoreLittleIndexingStrategy() {
+ this(null, null);
+ }
+
+ /**
+ * Create a new indexing strategy instance.
+ *
+ * @param schemata the schemata that defines the structure that can be queried; may
be null if queries are not going to be
+ * used
+ * @param rules the indexing rules that govern how properties are to be index, or
null if the {@link #DEFAULT_RULES default
+ * rules} are to be used
+ */
+ public StoreLittleIndexingStrategy( Schemata schemata,
+ IndexingRules rules ) {
+ this.rules = rules != null ? rules : DEFAULT_RULES;
+ this.logger = Logger.getLogger(getClass());
+ this.queryEngine = new LuceneQueryEngine(schemata);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#getNamespaceEncoder()
+ */
+ public TextEncoder getNamespaceEncoder() {
+ return new NoOpEncoder();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.search.IndexingStrategy#getChangeCountForAutomaticOptimization()
+ */
+ public int getChangeCountForAutomaticOptimization() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#createAnalyzer()
+ */
+ public Analyzer createAnalyzer() {
+ return new StandardAnalyzer();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Because this strategy uses multiple indexes, and since there's no correlation
between the documents in those indexes, we
+ * need to perform the delete in multiple steps. First, we need to perform a query to
find out which nodes exist below a
+ * certain path. Then, we need to delete those nodes from the paths index. Finally,
we need to delete the corresponding
+ * documents in the content index that represent those same nodes.
+ * </p>
+ * <p>
+ * Since we don't know how many documents there will be, we perform these steps
in batches, where each batch limits the number
+ * of results to a maximum number. We repeat batches as long as we find more results.
This approach has the advantage that
+ * we'll never bring in a large number of results, and it allows us to delete the
documents from the content node using a
+ * query.
+ * </p>
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#deleteBelow(Path, IndexContext)
+ */
+ public int deleteBelow( Path path,
+ IndexContext indexes ) throws IOException {
+ // Perform a query using the reader to find those nodes at/below the path ...
+ try {
+ IndexReader pathReader = indexes.getPathsReader();
+ IndexSearcher pathSearcher = new IndexSearcher(pathReader);
+ String pathStr = indexes.stringFactory().create(path) + "/";
+ PrefixQuery query = new PrefixQuery(new Term(PathIndex.PATH, pathStr));
+ int numberDeleted = 0;
+ while (true) {
+ // Execute the query and get the results ...
+ TopDocs results = pathSearcher.search(query, SIZE_OF_DELETE_BATCHES);
+ int numResultsInBatch = results.scoreDocs.length;
+ // Walk the results, delete the doc, and add to the query that we'll
use against the content index ...
+ IndexReader contentReader = indexes.getContentReader();
+ for (ScoreDoc result : results.scoreDocs) {
+ int docId = result.doc;
+ // Find the UUID of the node ...
+ Document doc = pathReader.document(docId, UUID_FIELD_SELECTOR);
+ String uuid = doc.get(PathIndex.UUID);
+ // Delete the document from the paths index ...
+ pathReader.deleteDocument(docId);
+ // Delete the corresponding document from the content index ...
+ contentReader.deleteDocuments(new Term(ContentIndex.UUID, uuid));
+ }
+ numberDeleted += numResultsInBatch;
+ if (numResultsInBatch < SIZE_OF_DELETE_BATCHES) break;
+ }
+ indexes.commit();
+ return numberDeleted;
+ } catch (FileNotFoundException e) {
+ // There are no index files yet, so nothing to delete ...
+ return 0;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#index(Node, IndexContext)
+ */
+ public void index( Node node,
+ IndexContext indexes ) throws IOException {
+ ValueFactory<String> strings = indexes.stringFactory();
+ Location location = node.getLocation();
+ UUID uuid = location.getUuid();
+ if (uuid == null) uuid = UUID.randomUUID();
+ Path path = location.getPath();
+ String pathStr = path.isRoot() ? "/" :
strings.create(location.getPath()) + "/";
+ String uuidStr = uuid.toString();
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("indexing {0}", pathStr);
+ }
+
+ // Create a separate document for the path, which makes it easier to handle moves
since the path can
+ // be changed without changing any other content fields ...
+ Document doc = new Document();
+ doc.add(new Field(PathIndex.PATH, pathStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ doc.add(new Field(PathIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ indexes.getPathsWriter().addDocument(doc);
+
+ // Create the document for the content (properties) ...
+ doc = new Document();
+ doc.add(new Field(ContentIndex.UUID, uuidStr, Field.Store.YES,
Field.Index.NOT_ANALYZED));
+ String stringValue = null;
+ StringBuilder fullTextSearchValue = null;
+ for (Property property : node.getProperties()) {
+ Name name = property.getName();
+ Rule rule = rules.getRule(name);
+ if (rule.isSkipped()) continue;
+ String nameString = strings.create(name);
+ if (rule.isDate()) {
+ DateTimeFactory dateFactory = indexes.dateFactory();
+ for (Object value : property) {
+ if (value == null) continue;
+ DateTime dateValue = dateFactory.create(value);
+ stringValue = dateFormatter.get().format(dateValue.toDate());
+ // Add a separate field for each property value ...
+ doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
rule.getIndexOption()));
+ // Dates are not added to the full-text search field (since this
wouldn't make sense)
+ }
+ continue;
+ }
+ for (Object value : property) {
+ if (value == null) continue;
+ if (value instanceof Binary) {
+ // don't include binary values as individual fields but do
include them in the full-text search ...
+ // TODO : add to full-text search ...
+ continue;
+ }
+ stringValue = strings.create(value);
+ // Add a separate field for each property value ...
+ doc.add(new Field(nameString, stringValue, rule.getStoreOption(),
rule.getIndexOption()));
+ // And add to the full-text field ...
+ if (rule.isFullText()) {
+ if (fullTextSearchValue == null) {
+ fullTextSearchValue = new StringBuilder();
+ } else {
+ fullTextSearchValue.append(' ');
+ }
+ fullTextSearchValue.append(stringValue);
+ }
+ }
+ }
+ // Add the full-text-search field ...
+ if (fullTextSearchValue != null) {
+ doc.add(new Field(ContentIndex.FULL_TEXT, fullTextSearchValue.toString(),
Field.Store.NO, Field.Index.ANALYZED));
+ }
+ indexes.getContentWriter().addDocument(doc);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#performQuery(String, int, int,
IndexContext, List)
+ */
+ public void performQuery( String fullTextString,
+ int maxResults,
+ int offset,
+ IndexContext indexes,
+ List<Location> results ) throws IOException,
ParseException {
+ assert fullTextString != null;
+ assert fullTextString.length() > 0;
+ assert offset >= 0;
+ assert maxResults > 0;
+ assert indexes != null;
+ assert results != null;
+
+ // Parse the full-text search and search against the 'fts' field ...
+ QueryParser parser = new QueryParser(ContentIndex.FULL_TEXT, createAnalyzer());
+ Query query = parser.parse(fullTextString);
+ TopDocCollector collector = new TopDocCollector(maxResults + offset);
+ indexes.getContentSearcher().search(query, collector);
+
+ // Collect the results ...
+ TopDocs docs = collector.topDocs();
+ IndexReader contentReader = indexes.getContentReader();
+ IndexReader pathReader = indexes.getPathsReader();
+ IndexSearcher pathSearcher = indexes.getPathsSearcher();
+ ScoreDoc[] scoreDocs = docs.scoreDocs;
+ int numberOfResults = scoreDocs.length;
+ if (numberOfResults > offset) {
+ // There are enough results to satisfy the offset ...
+ for (int i = offset, num = scoreDocs.length; i != num; ++i) {
+ ScoreDoc result = scoreDocs[i];
+ int docId = result.doc;
+ // Find the UUID of the node (this UUID might be artificial, so we have
to find the path) ...
+ Document doc = contentReader.document(docId, UUID_FIELD_SELECTOR);
+ String uuid = doc.get(ContentIndex.UUID);
+ // Find the path for this node (is there a better way to do this than one
search per UUID?) ...
+ TopDocs pathDocs = pathSearcher.search(new TermQuery(new
Term(PathIndex.UUID, uuid)), 1);
+ if (pathDocs.scoreDocs.length < 1) {
+ // No path record found ...
+ continue;
+ }
+ Document pathDoc = pathReader.document(pathDocs.scoreDocs[0].doc);
+ Path path = indexes.pathFactory().create(pathDoc.get(PathIndex.PATH));
+ // Now add the location ...
+ results.add(Location.create(path, UUID.fromString(uuid)));
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#performQuery(QueryCommand,
IndexContext)
+ */
+ public QueryResults performQuery( QueryCommand query,
+ IndexContext indexes ) throws IOException,
ParseException {
+ return this.queryEngine.execute(query, indexes);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.search.IndexingStrategy#apply(Iterable, IndexContext)
+ */
+ public int apply( Iterable<ChangeRequest> changes,
+ IndexContext indexes ) /*throws IOException*/{
+ for (ChangeRequest change : changes) {
+ if (change != null) continue;
+ }
+ return 0;
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/StoreLittleIndexingStrategy.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/java/org/jboss/dna/search/WorkspaceSearchEngine.java
===================================================================
--- trunk/dna-search/src/main/java/org/jboss/dna/search/WorkspaceSearchEngine.java
(rev 0)
+++
trunk/dna-search/src/main/java/org/jboss/dna/search/WorkspaceSearchEngine.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,548 @@
+/*
+ * 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.search;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.store.Directory;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.Subgraph;
+import org.jboss.dna.graph.SubgraphNode;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySource;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.QueryResults;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.request.ChangeRequest;
+
+/**
+ * A search engine dedicated to a single workspace.
+ */
+@ThreadSafe
+public class WorkspaceSearchEngine {
+
+ protected static final String PATHS_INDEX_NAME = "paths";
+ protected static final String CONTENT_INDEX_NAME = "content";
+
+ private final Directory pathsDirectory;
+ private final Directory contentDirectory;
+ private final ExecutionContext context;
+ private final ExecutionContext encodedContext;
+ private final String sourceName;
+ private final String workspaceName;
+ private final RepositoryConnectionFactory connectionFactory;
+ private final IndexingStrategy indexingStrategy;
+ protected final AtomicInteger modifiedNodesSinceLastOptimize = new AtomicInteger(0);
+
+ /**
+ * Create a search engine instance given the supplied {@link ExecutionContext
execution context}, name of the
+ * {@link RepositorySource}, the {@link RepositoryConnectionFactory factory for
RepositorySource connections}, and the
+ * {@link DirectoryConfiguration directory factory} that defines where each
workspace's indexes should be placed.
+ *
+ * @param context the execution context in which all indexing operations should be
performed
+ * @param directoryFactory the factory from which can be obtained the Lucene
directory where the indexes should be persisted
+ * @param indexingStrategy the indexing strategy that governs how properties are to
be indexed; may not be null
+ * @param sourceName the name of the {@link RepositorySource}
+ * @param workspaceName the name of the workspace
+ * @param connectionFactory the connection factory
+ * @throws IllegalArgumentException if any of the parameters are null
+ * @throws SearchEngineException if there is a problem initializing this engine
+ */
+ protected WorkspaceSearchEngine( ExecutionContext context,
+ DirectoryConfiguration directoryFactory,
+ IndexingStrategy indexingStrategy,
+ String sourceName,
+ String workspaceName,
+ RepositoryConnectionFactory connectionFactory )
throws SearchEngineException {
+ CheckArg.isNotNull(context, "context");
+ CheckArg.isNotNull(directoryFactory, "directoryFactory");
+ CheckArg.isNotNull(sourceName, "sourceName");
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ CheckArg.isNotNull(connectionFactory, "connectionFactory");
+ CheckArg.isNotNull(indexingStrategy, "indexingStrategy");
+ this.sourceName = sourceName;
+ this.workspaceName = workspaceName;
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ this.indexingStrategy = indexingStrategy;
+ this.encodedContext = context.with(new
EncodingNamespaceRegistry(context.getNamespaceRegistry(),
+
this.indexingStrategy.getNamespaceEncoder()));
+ this.pathsDirectory = directoryFactory.getDirectory(workspaceName,
PATHS_INDEX_NAME);
+ this.contentDirectory = directoryFactory.getDirectory(workspaceName,
CONTENT_INDEX_NAME);
+
+ initialize();
+ }
+
+ protected void initialize() throws SearchEngineException {
+ // Always create the index if not there ...
+ try {
+ Analyzer analyzer = this.indexingStrategy.createAnalyzer();
+ ensureIndexesExist(this.pathsDirectory, analyzer);
+ ensureIndexesExist(this.contentDirectory, analyzer);
+ } catch (IOException e) {
+ String msg =
SearchI18n.errorWhileInitializingSearchEngine.text(workspaceName, sourceName,
e.getMessage());
+ throw new SearchEngineException(msg, e);
+ }
+ }
+
+ private static void ensureIndexesExist( Directory directory,
+ Analyzer analyzer ) throws IOException {
+ IndexWriter writer = null;
+ Throwable error = null;
+ try {
+ writer = new IndexWriter(directory, analyzer, false,
MaxFieldLength.UNLIMITED);
+ } catch (FileNotFoundException e) {
+ // The index files don't yet exist, so we need to create them ...
+ try {
+ writer = new IndexWriter(directory, analyzer, true,
MaxFieldLength.UNLIMITED);
+ } catch (Throwable t) {
+ error = t;
+ }
+ } catch (Throwable t) {
+ error = t;
+ } finally {
+ if (writer != null) {
+ // Either way, make sure we close the writer that we created ...
+ try {
+ writer.close();
+ } catch (IOException e) {
+ if (error == null) throw e;
+ } catch (RuntimeException e) {
+ if (error == null) throw e;
+ }
+ }
+ }
+ }
+
+ final Graph graph() {
+ Graph graph = Graph.create(sourceName, connectionFactory, context);
+ graph.useWorkspace(workspaceName);
+ return graph;
+ }
+
+ final String workspaceName() {
+ return workspaceName;
+ }
+
+ final String sourceName() {
+ return sourceName;
+ }
+
+ final String readable( Path path ) {
+ return context.getValueFactories().getStringFactory().create(path);
+ }
+
+ final IndexingStrategy strategy() {
+ return indexingStrategy;
+ }
+
+ /**
+ * Index all of the content at or below the supplied path.
+ *
+ * @param startingPoint the path that represents the content to be indexed
+ * @param depthPerBatch the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the path is null or the depth is not positive
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void indexContent( Path startingPoint,
+ int depthPerBatch ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(startingPoint, "startingPoint");
+ indexContent(Location.create(startingPoint), depthPerBatch);
+ }
+
+ /**
+ * Index all of the content at or below the supplied location.
+ *
+ * @param startingPoint the location that represents the content to be indexed
+ * @param depthPerRead the depth of each subgraph read operation
+ * @throws IllegalArgumentException if the location is null or the depth is not
positive
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void indexContent( Location startingPoint,
+ int depthPerRead ) throws RepositorySourceException,
SearchEngineException {
+ CheckArg.isNotNull(startingPoint, "startingPoint");
+ CheckArg.isPositive(depthPerRead, "depthPerBatch");
+ assert startingPoint.hasPath();
+
+ if (startingPoint.getPath().isRoot()) {
+ // More efficient to just start over with a new index ...
+ execute(true, addContent(startingPoint, depthPerRead));
+ } else {
+ // Have to first remove the content below the starting point, then add it
again ...
+ execute(false, removeContent(startingPoint), addContent(startingPoint,
depthPerRead));
+ }
+ }
+
+ /**
+ * Update the indexes with the supplied set of changes to the content.
+ *
+ * @param changes the set of changes to the content
+ * @throws IllegalArgumentException if the path is null
+ * @throws RepositorySourceException if there is a problem accessing the content
+ * @throws SearchEngineException if there is a problem updating the indexes
+ */
+ public void indexChanges( final Iterable<ChangeRequest> changes ) throws
SearchEngineException {
+ CheckArg.isNotNull(changes, "changes");
+ execute(false, updateContent(changes));
+ }
+
+ /**
+ * Invoke the engine's garbage collection on all indexes used by this workspace.
This method reclaims space and optimizes the
+ * index. This should be done on a periodic basis after changes are made to the
engine's indexes.
+ *
+ * @throws SearchEngineException if there is a problem during optimization
+ */
+ public void optimize() throws SearchEngineException {
+ execute(false, optimizeContent());
+ }
+
+ /**
+ * Create an activity that will perform a full-text search given the supplied query.
+ *
+ * @param fullTextSearch the full-text search to be performed; may not be null
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @return the activity that will perform the work
+ */
+ public List<Location> fullTextSearch( final String fullTextSearch,
+ final int maxResults,
+ final int offset ) {
+ return execute(false, searchContent(fullTextSearch, maxResults,
offset)).getResults();
+ }
+
+ /**
+ * Create an activity that will perform a query of the content in this workspace,
given the Abstract Query Model
+ * representation of the query.
+ *
+ * @param query the query that is to be executed, in the form of the Abstract Query
Model
+ * @return the query results; never null
+ * @throws IllegalArgumentException if the context or query references are null
+ */
+ public QueryResults execute( QueryCommand query ) {
+ return execute(false, queryContent(query)).getResults();
+ }
+
+ /**
+ * Execute the supplied activities against the indexes.
+ *
+ * @param <ActivityType> the type of activity
+ * @param overwrite true if the existing indexes should be overwritten, or false if
they should be used
+ * @param activity the activity to execute
+ * @return the same activity that was supplied as a parameter, returned as a
convenience
+ * @throws SearchEngineException if there is a problem performing the activities
+ */
+ protected final <ActivityType extends Activity> ActivityType execute( boolean
overwrite,
+ ActivityType
activity ) throws SearchEngineException {
+ execute(overwrite, new Activity[] {activity});
+ return activity;
+ }
+
+ /**
+ * Execute the supplied activities against the indexes.
+ *
+ * @param overwrite true if the existing indexes should be overwritten, or false if
they should be used
+ * @param activities the activities to execute
+ * @throws SearchEngineException if there is a problem performing the activities
+ */
+ protected final void execute( boolean overwrite,
+ Activity... activities ) throws SearchEngineException
{
+ Analyzer analyzer = this.indexingStrategy.createAnalyzer();
+ IndexContext indexes = new IndexContext(encodedContext, pathsDirectory,
contentDirectory, analyzer, overwrite);
+
+ // Execute the various activities ...
+ Throwable error = null;
+ try {
+ for (Activity activity : activities) {
+ try {
+ activity.execute(indexes);
+ } catch (IOException e) {
+ error = e;
+ throw new SearchEngineException(activity.messageFor(e), e);
+ } catch (ParseException e) {
+ error = e;
+ throw new SearchEngineException(activity.messageFor(e), e);
+ } catch (RuntimeException e) {
+ error = e;
+ throw e;
+ }
+ }
+ if (indexes.hasWriters()) {
+ // Determine if there have been enough changes made to run the optimizer
...
+ int maxChanges =
this.indexingStrategy.getChangeCountForAutomaticOptimization();
+ if (maxChanges > 0 &&
this.modifiedNodesSinceLastOptimize.get() >= maxChanges) {
+ Activity optimizer = optimizeContent();
+ try {
+ optimizer.execute(indexes);
+ } catch (ParseException e) {
+ error = e;
+ throw new SearchEngineException(optimizer.messageFor(e), e);
+ } catch (IOException e) {
+ error = e;
+ throw new SearchEngineException(optimizer.messageFor(e), e);
+ } catch (RuntimeException e) {
+ error = e;
+ throw e;
+ }
+ }
+ }
+ } finally {
+ try {
+ if (error == null) {
+ indexes.commit();
+ } else {
+ indexes.rollback();
+ }
+ } catch (IOException e2) {
+ // We don't want to lose the existing error, if there is one ...
+ if (error == null) {
+ I18n msg = SearchI18n.errorWhileCommittingIndexChanges;
+ throw new SearchEngineException(msg.text(workspaceName(),
sourceName(), e2.getMessage()), e2);
+ }
+ }
+ }
+ }
+
+ /**
+ * Interface for activities that will be executed against the set of indexes. These
activities don't have to commit or roll
+ * back the writer, nor do they have to translate the exceptions, since this is done
by the
+ * {@link WorkspaceSearchEngine#execute(boolean, Activity...)} method.
+ */
+ protected interface Activity {
+
+ /**
+ * Perform the activity by using the index writer.
+ *
+ * @param indexes the set of indexes to use; never null
+ * @throws IOException if there is an error using the writer
+ * @throws ParseException if there is an error due to parsing
+ */
+ void execute( IndexContext indexes ) throws IOException, ParseException;
+
+ /**
+ * Translate an exception obtained during {@link #execute(IndexContext)
execution} into a single message.
+ *
+ * @param t the exception
+ * @return the error message
+ */
+ String messageFor( Throwable t );
+ }
+
+ protected interface Search extends Activity {
+ /**
+ * Get the results of the search.
+ *
+ * @return the list of {@link Location} objects for each node satisfying the
results; never null
+ */
+ List<Location> getResults();
+ }
+
+ protected interface Query extends Activity {
+ /**
+ * Get the results of the query.
+ *
+ * @return the results of a query; never null
+ */
+ QueryResults getResults();
+ }
+
+ /**
+ * Create an activity that will read from the source the content at the supplied
location and add the content to the search
+ * index.
+ *
+ * @param location the location of the content to read; may not be null
+ * @param depthPerRead the depth of each read operation; always positive
+ * @return the activity that will perform the work
+ */
+ protected Activity addContent( final Location location,
+ final int depthPerRead ) {
+ return new Activity() {
+ public void execute( IndexContext indexes ) throws IOException {
+
+ // Create a queue that we'll use to walk the content ...
+ LinkedList<Location> locationsToRead = new
LinkedList<Location>();
+ locationsToRead.add(location);
+ int count = 0;
+
+ // Now read and index the content ...
+ Graph graph = graph();
+ while (!locationsToRead.isEmpty()) {
+ Location location = locationsToRead.poll();
+ if (location == null) continue;
+ Subgraph subgraph =
graph.getSubgraphOfDepth(depthPerRead).at(location);
+ // Index all of the nodes within this subgraph ...
+ for (SubgraphNode node : subgraph) {
+ // Index the node ...
+ strategy().index(node, indexes);
+ ++count;
+
+ // Process the children ...
+ for (Location child : node.getChildren()) {
+ if (!subgraph.includes(child)) {
+ // Record this location as needing to be read ...
+ locationsToRead.add(child);
+ }
+ }
+ }
+ }
+ modifiedNodesSinceLastOptimize.addAndGet(count);
+ }
+
+ public String messageFor( Throwable error ) {
+ String path = readable(location.getPath());
+ return SearchI18n.errorWhileIndexingContentAtPath.text(path,
workspaceName(), sourceName(), error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will remove from the indexes all documents that represent
content at or below the specified
+ * location.
+ *
+ * @param location the location of the content to removed; may not be null
+ * @return the activity that will perform the work
+ */
+ protected Activity removeContent( final Location location ) {
+ return new Activity() {
+
+ public void execute( IndexContext indexes ) throws IOException {
+ // Delete the content at/below the path ...
+
modifiedNodesSinceLastOptimize.addAndGet(strategy().deleteBelow(location.getPath(),
indexes));
+ }
+
+ public String messageFor( Throwable error ) {
+ String path = readable(location.getPath());
+ return SearchI18n.errorWhileRemovingContentAtPath.text(path,
workspaceName(), sourceName(), error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will optimize the indexes.
+ *
+ * @return the activity that will perform the work
+ */
+ protected Activity optimizeContent() {
+ return new Activity() {
+ public void execute( IndexContext indexes ) throws IOException {
+ // Don't block ...
+ indexes.getContentWriter().optimize();
+ indexes.getPathsWriter().optimize();
+ }
+
+ public String messageFor( Throwable error ) {
+ return SearchI18n.errorWhileOptimizingIndexes.text(workspaceName(),
sourceName(), error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will update the indexes with changes that were already
made to the content.
+ *
+ * @param changes the changes that have been made to the content; may not be null
+ * @return the activity that will perform the work
+ */
+ protected Activity updateContent( final Iterable<ChangeRequest> changes ) {
+ return new Activity() {
+
+ public void execute( IndexContext indexes ) throws IOException {
+ // Iterate over the changes ...
+ modifiedNodesSinceLastOptimize.addAndGet(strategy().apply(changes,
indexes));
+ }
+
+ public String messageFor( Throwable error ) {
+ return SearchI18n.errorWhileUpdatingContent.text(workspaceName(),
sourceName(), error.getMessage());
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will perform a full-text search given the supplied query.
+ *
+ * @param fullTextSearch the full-text search to be performed; may not be null
+ * @param maxResults the maximum number of results that are to be returned; always
positive
+ * @param offset the number of initial results to skip, or 0 if the first results are
to be returned
+ * @return the activity that will perform the work
+ */
+ protected Search searchContent( final String fullTextSearch,
+ final int maxResults,
+ final int offset ) {
+ final List<Location> results = new ArrayList<Location>(maxResults);
+ return new Search() {
+ public void execute( IndexContext indexes ) throws IOException,
ParseException {
+ strategy().performQuery(fullTextSearch, maxResults, offset, indexes,
results);
+ }
+
+ public String messageFor( Throwable error ) {
+ return SearchI18n.errorWhilePerformingSearch.text(fullTextSearch,
+ workspaceName(),
+ sourceName(),
+ error.getMessage());
+ }
+
+ public List<Location> getResults() {
+ return results;
+ }
+ };
+ }
+
+ /**
+ * Create an activity that will perform a query against the index.
+ *
+ * @param query the query to be performed; may not be null
+ * @return the activity that will perform the work
+ */
+ protected Query queryContent( final QueryCommand query ) {
+ return new Query() {
+ private QueryResults results = null;
+
+ public void execute( IndexContext indexes ) throws IOException,
ParseException {
+ results = strategy().performQuery(query, indexes);
+ }
+
+ public String messageFor( Throwable error ) {
+ return SearchI18n.errorWhilePerformingQuery.text(query, workspaceName(),
sourceName(), error.getMessage());
+ }
+
+ public QueryResults getResults() {
+ return results;
+ }
+ };
+ }
+}
Property changes on:
trunk/dna-search/src/main/java/org/jboss/dna/search/WorkspaceSearchEngine.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties
===================================================================
--- trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties
(rev 0)
+++
trunk/dna-search/src/main/resources/org/jboss/dna/search/SearchI18n.properties 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+locationForIndexesIsNotDirectory = Location "{0}" cannot be used for search
indexes for workspace "{1}" because it is a directory
+locationForIndexesCannotBeRead = Location "{0}" cannot be used for search
indexes for workspace "{1}" because it cannot be read
+locationForIndexesCannotBeWritten = Location "{0}" cannot be used for search
indexes for workspace "{1}" because its contents cannot be written or updated
+
+errorWhileIndexingContentAtPath = Error while indexing the content at "{0}" in
the "{1}" workspace of the "{2}" source: {3}
+errorWhileRemovingContentAtPath = Error while removing the content at/below
"{0}" in the "{1}" workspace of the "{2}" source: {3}
+errorWhileUpdatingContent = Error while updating content in the "{0}" workspace
of the "{1}" source: {2}
+errorWhileCommittingIndexChanges = Error while committing changes to the indexes for the
"{0}" workspace of the "{1}" source: {2}
+errorCreatingIndexWriter = Error attempting to create an index writer for the
"{0}" index for the "{1}" workspace of the "{2}" source:
{3}
+errorWhileOptimizingIndexes = Error while optimizing the indexes for the "{0}"
workspace of the "{1}" source: {2}
+errorWhilePerformingSearch = Error while searching for "{0}" in the
"{1}" workspace of the "{2}" source: {3}
+errorWhilePerformingQuery = Error while performing the query "{0}" against the
content in the "{1}" workspace of the "{2}" source: {3}
+errorWhileInitializingSearchEngine = Error while initializing the search engine for the
"{0}" workspace of the "{1}" source: {2}
\ No newline at end of file
Added:
trunk/dna-search/src/test/java/org/jboss/dna/search/EncodingNamespaceRegistryTest.java
===================================================================
---
trunk/dna-search/src/test/java/org/jboss/dna/search/EncodingNamespaceRegistryTest.java
(rev 0)
+++
trunk/dna-search/src/test/java/org/jboss/dna/search/EncodingNamespaceRegistryTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,102 @@
+/*
+ * 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.search;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.util.Collection;
+import org.jboss.dna.common.text.SecureHashTextEncoder;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.SecureHash.Algorithm;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.NamespaceRegistry.Namespace;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class EncodingNamespaceRegistryTest {
+
+ private ExecutionContext context;
+ private NamespaceRegistry registry;
+ private EncodingNamespaceRegistry encodedRegistry;
+ private TextEncoder encoder;
+ private ExecutionContext encodedContext;
+
+ @Before
+ public void beforeEach() {
+ this.context = new ExecutionContext();
+ this.registry = this.context.getNamespaceRegistry();
+ this.encoder = new SecureHashTextEncoder(Algorithm.SHA_1, 10);
+ this.encodedRegistry = new EncodingNamespaceRegistry(registry, encoder);
+ this.encodedContext = context.with(encodedRegistry);
+ }
+
+ @Test
+ public void shouldHaveEncodedPrefixesForAllRegisteredNamespacesExceptFixedOnes() {
+ Collection<Namespace> namespaces = registry.getNamespaces();
+ assertThat(namespaces.size() > 4, is(true));
+ for (Namespace namespace : namespaces) {
+ String uri = namespace.getNamespaceUri();
+ String actualEncodedPrefix = encodedRegistry.getPrefixForNamespaceUri(uri,
false);
+ if (encodedRegistry.getFixedNamespaceUris().contains(uri)) {
+ assertThat(actualEncodedPrefix, is(namespace.getPrefix()));
+ } else {
+ String expectedEncodedPrefix = encoder.encode(uri);
+ assertThat(expectedEncodedPrefix, is(actualEncodedPrefix));
+ }
+ String actualUri =
encodedRegistry.getNamespaceForPrefix(actualEncodedPrefix);
+ assertThat(uri, is(actualUri));
+ }
+ }
+
+ @Test
+ public void shouldAllowPathConversionToAndFromString() {
+ String uri1 = "http://acme.com/wabbler";
+ String uri2 = "http://troublemakers.com/contixity";
+ String uri3 = "http://example.com/infinitiy";
+ String ns1 = "wab";
+ String ns2 = "ctx";
+ String ns3 = "inf";
+ registry.register(ns1, uri1);
+ registry.register(ns2, uri2);
+ registry.register(ns3, uri3);
+ String pathStr = "/wab:part1/wab:part2/ctx:part3/inf:part4/dna:part5";
+ Path actualPath = context.getValueFactories().getPathFactory().create(pathStr);
+ String actualPathStr =
context.getValueFactories().getStringFactory().create(actualPath);
+ assertThat(pathStr, is(actualPathStr));
+ String encodedPathStr =
encodedContext.getValueFactories().getStringFactory().create(actualPath);
+ String encodedPrefix1 = encoder.encode(uri1);
+ String encodedPrefix2 = encoder.encode(uri2);
+ String encodedPrefix3 = encoder.encode(uri3);
+ String expectedPathStr = "/" + encodedPrefix1 + ":part1/" +
encodedPrefix1 + ":part2/" + encodedPrefix2 + ":part3/"
+ + encodedPrefix3 + ":part4/dna:part5";
+ assertThat(expectedPathStr, is(encodedPathStr));
+ Path actualPath2 =
encodedContext.getValueFactories().getPathFactory().create(encodedPathStr);
+ assertThat(actualPath, is(actualPath2));
+ }
+}
Property changes on:
trunk/dna-search/src/test/java/org/jboss/dna/search/EncodingNamespaceRegistryTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
(rev 0)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,58 @@
+/*
+ * 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.search;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.search.IndexingRules.Builder;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class IndexingRulesTest {
+
+ private Builder builder;
+ private IndexingRules rules;
+
+ @Before
+ public void beforeEach() {
+ builder = IndexingRules.createBuilder();
+ rules = builder.build();
+ }
+
+ @Test
+ public void shouldBuildValidRulesFromBuilderThatIsNotInvoked() {
+ builder = IndexingRules.createBuilder();
+ rules = builder.build();
+ }
+
+ @Test
+ public void shouldBuildValidRulesFromBuilderAfterJustSettingDefaultRules() {
+ builder.defaultTo(IndexingRules.FULL_TEXT);
+ rules = builder.build();
+ assertThat(rules.getRule(null).isFullText(), is(true));
+ }
+}
Property changes on:
trunk/dna-search/src/test/java/org/jboss/dna/search/IndexingRulesTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
(rev 0)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,159 @@
+/*
+ * 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.search;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.connector.RepositoryConnection;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.request.InvalidWorkspaceException;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SearchEngineTest {
+
+ private SearchEngine engine;
+ private ExecutionContext context;
+ private String sourceName;
+ private String workspaceName1;
+ private String workspaceName2;
+ private InMemoryRepositorySource source;
+ private RepositoryConnectionFactory connectionFactory;
+ private DirectoryConfiguration directoryFactory;
+ private IndexingStrategy indexingStrategy;
+ private Schemata schemata;
+ private Graph content;
+
+ @Before
+ public void beforeEach() throws Exception {
+ context = new ExecutionContext();
+ sourceName = "sourceA";
+ workspaceName1 = "workspace1";
+ workspaceName2 = "workspace2";
+
+ // Set up the source and graph instance ...
+ source = new InMemoryRepositorySource();
+ source.setName(sourceName);
+ content = Graph.create(source, context);
+
+ // Create the workspaces ...
+ content.createWorkspace().named(workspaceName1);
+ content.createWorkspace().named(workspaceName2);
+
+ // Load some content ...
+ content.useWorkspace(workspaceName1);
+
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("cars.xml")).into("/");
+ content.useWorkspace(workspaceName2);
+
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("aircraft.xml")).into("/");
+
+ // Set up the connection factory ...
+ connectionFactory = new RepositoryConnectionFactory() {
+ @SuppressWarnings( "synthetic-access" )
+ public RepositoryConnection createConnection( String sourceName ) throws
RepositorySourceException {
+ return source.getConnection();
+ }
+ };
+
+ // Set up the schemata for the queries ...
+ schemata = mock(Schemata.class);
+
+ // Set up the indexing strategy ...
+ IndexingRules rules =
IndexingRules.createBuilder(StoreLittleIndexingStrategy.DEFAULT_RULES)
+ .defaultTo(IndexingRules.INDEX |
IndexingRules.ANALYZE | IndexingRules.FULL_TEXT)
+ .build();
+ indexingStrategy = new StoreLittleIndexingStrategy(schemata, rules);
+
+ // Now set up the search engine ...
+ directoryFactory = DirectoryConfigurations.inMemory();
+ engine = new SearchEngine(context, sourceName, connectionFactory,
directoryFactory, indexingStrategy);
+ }
+
+ protected Path path( String string ) {
+ return context.getValueFactories().getPathFactory().create(string);
+ }
+
+ @Test
+ public void shouldHaveLoadedTestContentIntoRepositorySource() {
+ content.useWorkspace(workspaceName1);
+ assertThat(content.getNodeAt("/Cars/Hybrid/Toyota
Prius").getProperty("msrp").getFirstValue(),
is((Object)"$21,500"));
+ content.useWorkspace(workspaceName2);
+ assertThat(content.getNodeAt("/Aircraft/Commercial/Boeing
787").getProperty("range").getFirstValue(),
+ is((Object)"3050nm"));
+ }
+
+ @Test
+ public void shouldHaveExecutionContext() {
+ assertThat(engine.getContext(), is(sameInstance(context)));
+ }
+
+ @Test
+ public void shouldHaveSourceName() {
+ assertThat(engine.getSourceName(), is(sourceName));
+ }
+
+ @Test
+ public void shouldFindExistingWorkspaces() {
+ assertThat(engine.getWorkspaceEngine(workspaceName1), is(notNullValue()));
+ assertThat(engine.getWorkspaceEngine(workspaceName2), is(notNullValue()));
+ }
+
+ @Test( expected = InvalidWorkspaceException.class )
+ public void shouldNotFindNonExistingWorkspaces() {
+ engine.getWorkspaceEngine("Non-existant workspace");
+ }
+
+ @Test
+ public void shouldIndexAllContentInRepositorySource() {
+ engine.indexContent(3);
+ }
+
+ @Test
+ public void shouldIndexAllContentInWorkspace() {
+ engine.indexContent(workspaceName1, 3);
+ engine.indexContent(workspaceName2, 5);
+ }
+
+ @Test
+ public void shouldIndexAllContentInWorkspaceBelowPath() {
+ engine.indexContent(workspaceName1, path("/Cars/Hybrid"), 3);
+ engine.indexContent(workspaceName2, path("/Aircraft/Commercial"), 5);
+ }
+
+ @Test
+ public void shouldReIndexAllContentInWorkspaceBelowPath() {
+ for (int i = 0; i != 0; i++) {
+ engine.indexContent(workspaceName1, path("/Cars/Hybrid"), 3);
+ engine.indexContent(workspaceName2, path("/Aircraft/Commercial"),
5);
+ }
+ }
+}
Property changes on:
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchEngineTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/java/org/jboss/dna/search/SearchI18nTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/SearchI18nTest.java
(rev 0)
+++ trunk/dna-search/src/test/java/org/jboss/dna/search/SearchI18nTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,33 @@
+/*
+ * 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.search;
+
+import org.jboss.dna.common.AbstractI18nTest;
+
+public class SearchI18nTest extends AbstractI18nTest {
+
+ public SearchI18nTest() {
+ super(SearchI18n.class);
+ }
+}
Property changes on:
trunk/dna-search/src/test/java/org/jboss/dna/search/SearchI18nTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/java/org/jboss/dna/search/WorkspaceSearchEngineTest.java
===================================================================
--- trunk/dna-search/src/test/java/org/jboss/dna/search/WorkspaceSearchEngineTest.java
(rev 0)
+++
trunk/dna-search/src/test/java/org/jboss/dna/search/WorkspaceSearchEngineTest.java 2009-09-21
20:03:40 UTC (rev 1234)
@@ -0,0 +1,177 @@
+/*
+ * 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.search;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.RepositoryConnection;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+public class WorkspaceSearchEngineTest {
+
+ private WorkspaceSearchEngine engine;
+ private ExecutionContext context;
+ private String sourceName;
+ private String workspaceName;
+ private InMemoryRepositorySource source;
+ private RepositoryConnectionFactory connectionFactory;
+ private DirectoryConfiguration directoryFactory;
+ private IndexingStrategy indexingStrategy;
+ private Graph content;
+ private Schemata schemata;
+
+ @Before
+ public void beforeEach() throws Exception {
+ context = new ExecutionContext();
+ sourceName = "sourceA";
+ workspaceName = "workspace1";
+
+ // Set up the source and graph instance ...
+ source = new InMemoryRepositorySource();
+ source.setName(sourceName);
+ source.setDefaultWorkspaceName(workspaceName);
+ content = Graph.create(source, context);
+
+ // Load some content ...
+
content.importXmlFrom(getClass().getClassLoader().getResourceAsStream("cars.xml")).into("/");
+
+ // Set up the connection factory ...
+ connectionFactory = new RepositoryConnectionFactory() {
+ @SuppressWarnings( "synthetic-access" )
+ public RepositoryConnection createConnection( String sourceName ) throws
RepositorySourceException {
+ return source.getConnection();
+ }
+ };
+
+ // Set up the schemata for the queries ...
+ schemata = mock(Schemata.class);
+
+ // Set up the indexing strategy ...
+ IndexingRules rules =
IndexingRules.createBuilder(StoreLittleIndexingStrategy.DEFAULT_RULES)
+ .defaultTo(IndexingRules.INDEX |
IndexingRules.ANALYZE | IndexingRules.FULL_TEXT)
+ .build();
+ indexingStrategy = new StoreLittleIndexingStrategy(schemata, rules);
+
+ // Now set up the search engine ...
+ directoryFactory = DirectoryConfigurations.inMemory();
+ engine = new WorkspaceSearchEngine(context, directoryFactory, indexingStrategy,
sourceName, workspaceName,
+ connectionFactory);
+ }
+
+ protected Path path( String string ) {
+ return context.getValueFactories().getPathFactory().create(string);
+ }
+
+ protected void assertSearchResults( String fullTextSearch,
+ Path... expectedPaths ) {
+ int numExpected = expectedPaths.length;
+ List<Location> results = engine.fullTextSearch(fullTextSearch, numExpected,
0);
+ int numFound = results.size();
+ assertThat("Different number of results were found", numExpected,
is(numFound));
+ Path[] actualPaths = new Path[numFound];
+ int i = 0;
+ for (Location actual : results) {
+ actualPaths[i++] = actual.getPath();
+ }
+ assertThat(expectedPaths, is(actualPaths));
+ }
+
+ @Test
+ public void shouldHaveLoadedTestContentIntoRepositorySource() {
+ assertThat(content.getNodeAt("/Cars/Hybrid/Toyota
Prius").getProperty("msrp").getFirstValue(),
is((Object)"$21,500"));
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfOne() {
+ engine.indexContent(path("/"), 1);
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfTwo() {
+ engine.indexContent(path("/"), 2);
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfThree() {
+ engine.indexContent(path("/"), 3);
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfFour() {
+ engine.indexContent(path("/"), 4);
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtRootAndUsingDepthOfTen() {
+ engine.indexContent(path("/"), 10);
+ }
+
+ @Test
+ public void shouldIndexRepositoryContentStartingAtNonRootNode() {
+ engine.indexContent(path("/Cars"), 10);
+ }
+
+ @Test
+ public void shouldReIndexRepositoryContentStartingAtNonRootNode() {
+ for (int i = 0; i != 3; ++i) {
+ engine.indexContent(path("/Cars"), 10);
+ }
+ }
+
+ @Test
+ public void shouldFindNodesByFullTextSearch() {
+ engine.indexContent(path("/"), 100);
+ List<Location> results = engine.fullTextSearch("Toyota Prius",
10, 0);
+ assertThat(results, is(notNullValue()));
+ assertThat(results.size(), is(2));
+ assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Prius")));
+ assertThat(results.get(1).getPath(), is(path("/Cars/Hybrid/Toyota
Highlander")));
+ }
+
+ @Test
+ public void shouldFindNodesByFullTextSearchWithOffset() {
+ engine.indexContent(path("/"), 100);
+ List<Location> results = engine.fullTextSearch("toyota prius", 1,
0);
+ assertThat(results, is(notNullValue()));
+ assertThat(results.size(), is(1));
+ assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Prius")));
+
+ results = engine.fullTextSearch("+Toyota", 1, 1);
+ assertThat(results, is(notNullValue()));
+ assertThat(results.size(), is(1));
+ assertThat(results.get(0).getPath(), is(path("/Cars/Hybrid/Toyota
Highlander")));
+ }
+}
Property changes on:
trunk/dna-search/src/test/java/org/jboss/dna/search/WorkspaceSearchEngineTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/resources/aircraft.xml
===================================================================
--- trunk/dna-search/src/test/resources/aircraft.xml (rev 0)
+++ trunk/dna-search/src/test/resources/aircraft.xml 2009-09-21 20:03:40 UTC (rev 1234)
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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 distribution; if not, write to:
+ ~ Free Software Foundation, Inc.
+ ~ 51 Franklin Street, Fifth Floor
+ ~ Boston, MA 02110-1301 USA
+ -->
+<Aircraft
xmlns:jcr="http://www.jcp.org/jcr/1.0">
+ <Business>
+ <aircraft jcr:name="Gulfstream V" maker="Gulfstream"
model="G-V" introduced="1995" range="5800nm"
cruiseSpeed="488kt" crew="2" emptyWeight="46200lb"
url="http://en.wikipedia.org/wiki/Gulfstream_V"/>
+ <aircraft jcr:name="Learjet 45" maker="Learjet"
model="LJ45" introduced="1995" numberBuilt="264+"
crew="2" emptyWeight="13695lb" range="2120nm"
cruiseSpeed="457kt"
url="http://en.wikipedia.org/wiki/Learjet_45"/>
+ </Business>
+ <Commercial>
+ <aircraft jcr:name="Boeing 777" maker="Boeing"
model="777-200LR" introduced="1995" numberBuilt="731+"
maxRange="7500nm" emptyWeight="326000lb"
cruiseSpeed="560mph"
url="http://en.wikipedia.org/wiki/Boeing_777"/>
+ <aircraft jcr:name="Boeing 767" maker="Boeing"
model="767-200" introduced="1982" numberBuilt="966+"
maxRange="3950nm" emptyWeight="176650lb"
cruiseSpeed="530mph"
url="http://en.wikipedia.org/wiki/Boeing_767"/>
+ <aircraft jcr:name="Boeing 787" maker="Boeing"
model="787-3" introduced="2009" range="3050nm"
emptyWeight="223000lb" cruiseSpeed="561mph"
url="http://en.wikipedia.org/wiki/Boeing_787"/>
+ <aircraft jcr:name="Boeing 757" maker="Boeing"
model="757-200" introduced="1983" numberBuilt="1050"
range="3900nm" maxWeight="255000lb" cruiseSpeed="530mph"
url="http://en.wikipedia.org/wiki/Boeing_757"/>
+ <aircraft jcr:name="Airbus A380" maker="Airbus"
model="A380-800" introduced="2007" numberBuilt="18"
range="8200nm" maxWeight="1235000lb" cruiseSpeed="647mph"
url="http://en.wikipedia.org/wiki/Airbus_a380"/>
+ <aircraft jcr:name="Airbus A340" maker="Airbus"
model="A340-200" introduced="1993" numberBuilt="354"
range="8000nm" maxWeight="606300lb" cruiseSpeed="557mph"
url="http://en.wikipedia.org/wiki/Airbus_A-340"/>
+ <aircraft jcr:name="Airbus A310" maker="Airbus"
model="A310-200" introduced="1983" numberBuilt="255"
cruiseSpeed="850km/h" emptyWeight="176312lb" range="3670nm"
url="http://en.wikipedia.org/wiki/Airbus_A-310"/>
+ <aircraft jcr:name="Embraer RJ-175" maker="Embraer"
model="ERJ170-200" introduced="2004" range="3334km"
cruiseSpeed="481kt" emptyWeight="21810kg"
url="http://en.wikipedia.org/wiki/EMBRAER_170"/>
+ </Commercial>
+ <Vintage>
+ <aircraft jcr:name="Fokker Trimotor" maker="Fokker"
model="F.VII" introduced="1925" cruiseSpeed="170km/h"
emptyWeight="3050kg" crew="2"
url="http://en.wikipedia.org/wiki/Fokker_trimotor"/>
+ <aircraft jcr:name="P-38 Lightning" maker="Lockheed"
model="P-38" designedBy="Kelly Johnson" introduced="1941"
numberBuilt="10037" rateOfClimb="4750ft/min" range="1300mi"
emptyWeight="12780lb" crew="1"
url="http://en.wikipedia.org/wiki/P-38_Lightning"/>
+ <aircraft jcr:name="A6M Zero" maker="Mitsubishi"
model="A6M" designedBy="Jiro Horikoshi" introduced="1940"
numberBuilt="11000" crew="1" emptyWeight="3704lb"
serviceCeiling="33000ft" maxSpeed="331mph" range="1929mi"
rateOfClimb="3100ft/min"
url="http://en.wikipedia.org/wiki/A6M_Zero"/>
+ <aircraft jcr:name="Bf 109" maker="Messerschmitt"
model="Bf 109" introduced="1937"
url="http://en.wikipedia.org/wiki/BF_109"/>
+ <aircraft jcr:name="Wright Flyer" maker="Wright Brothers"
introduced="1903" range="852ft" maxSpeed="30mph"
emptyWeight="605lb" crew="1"/>
+ </Vintage>
+ <Homebuilt>
+ <aircraft jcr:name="Long-EZ" maker="Rutan Aircraft
Factory" model="61" emptyWeight="760lb"
fuelCapacity="200L" maxSpeed="185kt" since="1976"
range="1200nm"
url="http://en.wikipedia.org/wiki/Rutan_Long-EZ"/>
+ <aircraft jcr:name="Cirrus VK-30" maker="Cirrus Design"
model="VK-30" emptyWeight="2400lb" maxLoad="1200lb"
maxSpeed="250mph" rateOfClimb="1500ft/min" range="1300mi"
url="http://en.wikipedia.org/wiki/Cirrus_VK-30"/>
+ <aircraft jcr:name="Van's RV-4" maker="Van's
Aircraft" model="RV-4" introduced="1980"
emptyWeight="905lb" maxLoad="500lb" maxSpeed="200mph"
rateOfClimb="2450ft/min" range="725mi"
url="http://en.wikipedia.org/wiki/Van%27s_Aircraft_RV-4"/>
+ </Homebuilt>
+</Aircraft>
\ No newline at end of file
Property changes on: trunk/dna-search/src/test/resources/aircraft.xml
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/resources/cars.xml
===================================================================
--- trunk/dna-search/src/test/resources/cars.xml (rev 0)
+++ trunk/dna-search/src/test/resources/cars.xml 2009-09-21 20:03:40 UTC (rev 1234)
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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 distribution; if not, write to:
+ ~ Free Software Foundation, Inc.
+ ~ 51 Franklin Street, Fifth Floor
+ ~ Boston, MA 02110-1301 USA
+ -->
+<Cars
xmlns:jcr="http://www.jcp.org/jcr/1.0">
+ <Hybrid>
+ <car jcr:name="Toyota Prius" maker="Toyota"
model="Prius" year="2008" msrp="$21,500"
userRating="4.2" valueRating="5" mpgCity="48"
mpgHighway="45"/>
+ <car jcr:name="Toyota Highlander" maker="Toyota"
model="Highlander" year="2008" msrp="$34,200"
userRating="4" valueRating="5" mpgCity="27"
mpgHighway="25"/>
+ <car jcr:name="Nissan Altima" maker="Nissan"
model="Altima" year="2008" msrp="$18,260"
mpgCity="23" mpgHighway="32"/>
+ </Hybrid>
+ <Sports>
+ <car jcr:name="Aston Martin DB9" maker="Aston Martin"
model="DB9" year="2008" msrp="$171,600"
userRating="5" mpgCity="12" mpgHighway="19"
lengthInInches="185.5" wheelbaseInInches="108.0" engine="5,935 cc
5.9 liters V 12"/>
+ <car jcr:name="Infiniti G37" maker="Infiniti"
model="G37" year="2008" msrp="$34,900"
userRating="3.5" valueRating="4" mpgCity="18"
mpgHighway="24" />
+ </Sports>
+ <Luxury>
+ <car jcr:name="Cadillac DTS" maker="Cadillac"
model="DTS" year="2008" engine="3.6-liter V6"
userRating="0"/>
+ <car jcr:name="Bentley Continental" maker="Bentley"
model="Continental" year="2008" msrp="$170,990"
mpgCity="10" mpgHighway="17" />
+ <car jcr:name="Lexus IS350" maker="Lexus"
model="IS350" year="2008" msrp="$36,305"
mpgCity="18" mpgHighway="25" userRating="4"
valueRating="5" />
+ </Luxury>
+ <Utility>
+ <car jcr:name="Land Rover LR2" maker="Land Rover"
model="LR2" year="2008" msrp="$33,985"
userRating="4.5" valueRating="5" mpgCity="16"
mpgHighway="23" />
+ <car jcr:name="Land Rover LR3" maker="Land Rover"
model="LR3" year="2008" msrp="$48,525"
userRating="5" valueRating="2" mpgCity="12"
mpgHighway="17" />
+ <car jcr:name="Hummer H3" maker="Hummer"
model="H3" year="2008" msrp="$30,595"
userRating="3.5" valueRating="4" mpgCity="13"
mpgHighway="16" />
+ <car jcr:name="Ford F-150" maker="Ford"
model="F-150" year="2008" msrp="$23,910"
userRating="4" valueRating="1" mpgCity="14"
mpgHighway="20" />
+ </Utility>
+</Cars>
\ No newline at end of file
Property changes on: trunk/dna-search/src/test/resources/cars.xml
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Added: trunk/dna-search/src/test/resources/log4j.properties
===================================================================
--- trunk/dna-search/src/test/resources/log4j.properties (rev 0)
+++ trunk/dna-search/src/test/resources/log4j.properties 2009-09-21 20:03:40 UTC (rev
1234)
@@ -0,0 +1,13 @@
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %m%n
+
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Set up the default logging to be INFO level, then override specific units
+log4j.logger.org.jboss.dna=INFO
+#log4j.logger.org.jboss.dna.search.SimpleIndexingStrategy=TRACE
+
Modified: trunk/pom.xml
===================================================================
--- trunk/pom.xml 2009-09-21 18:44:47 UTC (rev 1233)
+++ trunk/pom.xml 2009-09-21 20:03:40 UTC (rev 1234)
@@ -114,6 +114,7 @@
<module>dna-common</module>
<module>dna-graph</module>
<module>dna-repository</module>
+ <module>dna-search</module>
<module>dna-cnd</module>
<module>dna-jcr</module>
<module>extensions/dna-classloader-maven</module>
@@ -825,6 +826,11 @@
</dependency>
<dependency>
<groupId>org.jboss.dna</groupId>
+ <artifactId>dna-search</artifactId>
+ <version>${pom.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
<artifactId>dna-repository</artifactId>
<version>${pom.version}</version>
</dependency>