Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr.query.engine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.NoSuchElementException;
import java.util.Set;
import java.util.regex.Pattern;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.query.qom.BindVariableValue;
import javax.jcr.query.qom.StaticOperand;
import org.modeshape.common.SystemFailureException;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.collection.ArrayListMultimap;
import org.modeshape.common.collection.Multimap;
import org.modeshape.common.collection.Problems;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.GraphI18n;
import org.modeshape.jcr.JcrLexicon;
import org.modeshape.jcr.ModeShapeLexicon;
import org.modeshape.jcr.NodeTypes;
import org.modeshape.jcr.RepositoryIndexes;
import org.modeshape.jcr.api.query.QueryCancelledException;
import org.modeshape.jcr.api.query.qom.Operator;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.CachedNodeSupplier;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.PropertyTypeUtil;
import org.modeshape.jcr.cache.RepositoryCache;
import org.modeshape.jcr.query.BufferManager;
import org.modeshape.jcr.query.NodeSequence;
import org.modeshape.jcr.query.NodeSequence.Batch;
import org.modeshape.jcr.query.NodeSequence.RowAccessor;
import org.modeshape.jcr.query.NodeSequence.RowFilter;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.QueryEngine;
import org.modeshape.jcr.query.QueryEngineBuilder;
import org.modeshape.jcr.query.QueryResults;
import org.modeshape.jcr.query.QueryResults.Columns;
import org.modeshape.jcr.query.QueryResults.Statistics;
import org.modeshape.jcr.query.RowExtractors;
import org.modeshape.jcr.query.RowExtractors.ExtractFromRow;
import org.modeshape.jcr.query.engine.process.DependentQuery;
import org.modeshape.jcr.query.engine.process.DistinctSequence;
import org.modeshape.jcr.query.engine.process.ExceptSequence;
import org.modeshape.jcr.query.engine.process.HashJoinSequence;
import org.modeshape.jcr.query.engine.process.IntersectSequence;
import org.modeshape.jcr.query.engine.process.JoinSequence.Range;
import org.modeshape.jcr.query.engine.process.JoinSequence.RangeProducer;
import org.modeshape.jcr.query.engine.process.SortingSequence;
import org.modeshape.jcr.query.model.And;
import org.modeshape.jcr.query.model.ArithmeticOperand;
import org.modeshape.jcr.query.model.Between;
import org.modeshape.jcr.query.model.BindVariableName;
import org.modeshape.jcr.query.model.ChildNode;
import org.modeshape.jcr.query.model.ChildNodeJoinCondition;
import org.modeshape.jcr.query.model.Column;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.Constraint;
import org.modeshape.jcr.query.model.DescendantNode;
import org.modeshape.jcr.query.model.DescendantNodeJoinCondition;
import org.modeshape.jcr.query.model.DynamicOperand;
import org.modeshape.jcr.query.model.EquiJoinCondition;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.FullTextSearchScore;
import org.modeshape.jcr.query.model.JoinCondition;
import org.modeshape.jcr.query.model.JoinType;
import org.modeshape.jcr.query.model.Length;
import org.modeshape.jcr.query.model.Limit;
import org.modeshape.jcr.query.model.Literal;
import org.modeshape.jcr.query.model.LiteralValue;
import org.modeshape.jcr.query.model.LowerCase;
import org.modeshape.jcr.query.model.NodeDepth;
import org.modeshape.jcr.query.model.NodeId;
import org.modeshape.jcr.query.model.NodeLocalName;
import org.modeshape.jcr.query.model.NodeName;
import org.modeshape.jcr.query.model.NodePath;
import org.modeshape.jcr.query.model.Not;
import org.modeshape.jcr.query.model.NullOrder;
import org.modeshape.jcr.query.model.Or;
import org.modeshape.jcr.query.model.Ordering;
import org.modeshape.jcr.query.model.PropertyExistence;
import org.modeshape.jcr.query.model.PropertyValue;
import org.modeshape.jcr.query.model.QueryCommand;
import org.modeshape.jcr.query.model.ReferenceValue;
import org.modeshape.jcr.query.model.Relike;
import org.modeshape.jcr.query.model.SameNode;
import org.modeshape.jcr.query.model.SameNodeJoinCondition;
import org.modeshape.jcr.query.model.SelectorName;
import org.modeshape.jcr.query.model.SetCriteria;
import org.modeshape.jcr.query.model.SetQuery.Operation;
import org.modeshape.jcr.query.model.TypeSystem;
import org.modeshape.jcr.query.model.TypeSystem.TypeFactory;
import org.modeshape.jcr.query.model.UpperCase;
import org.modeshape.jcr.query.model.Visitors;
import org.modeshape.jcr.query.optimize.Optimizer;
import org.modeshape.jcr.query.optimize.RuleBasedOptimizer;
import org.modeshape.jcr.query.plan.JoinAlgorithm;
import org.modeshape.jcr.query.plan.PlanHints;
import org.modeshape.jcr.query.plan.PlanNode;
import org.modeshape.jcr.query.plan.PlanNode.Property;
import org.modeshape.jcr.query.plan.PlanNode.Traversal;
import org.modeshape.jcr.query.plan.PlanNode.Type;
import org.modeshape.jcr.query.plan.Planner;
import org.modeshape.jcr.query.validate.Schemata;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.PathFactory;
import org.modeshape.jcr.value.PropertyType;
import org.modeshape.jcr.value.StringFactory;
import org.modeshape.jcr.value.binary.BinaryStore;
/**
* A {@link QueryEngine} implementation that always scans all nodes in the workspace(s) and filtering out any node that does not
* satisfy the criteria. This scanning is not very efficient and can result in slow queries, especially when the repository is
* quite large or when the number of nodes that satisfies the query's criteria is a small fraction of all possible nodes in the
* workspace(s).
*
* However, this fully-functional QueryEngine implementation is designed to be subclassed when the nodes for a particular source
* (and optionally criteria) can be found more quickly. In such cases, the subclass should override the
* {@link #createNodeSequenceForSource(QueryCommand, QueryContext, PlanNode, Columns, QuerySources)} method or the
* {@link #createNodeSequenceForSource(QueryCommand, QueryContext, PlanNode, IndexPlan, Columns, QuerySources)} method and return
* a NodeSequence that contains only the applicable nodes.
*
*/
@Immutable
public class ScanningQueryEngine implements org.modeshape.jcr.query.QueryEngine {
/** We don't use the standard logging convention here; we want clients to easily configure logging for the indexes */
protected static final Logger LOGGER = Logger.getLogger("org.modeshape.jcr.query");
protected static final Set PSEUDO_COLUMN_NAMES;
static {
Set names = new HashSet<>();
names.add(JcrLexicon.NAME);
names.add(JcrLexicon.PATH);
names.add(JcrLexicon.SCORE);
names.add(JcrLexicon.UUID);
names.add(ModeShapeLexicon.LOCALNAME);
names.add(ModeShapeLexicon.DEPTH);
names.add(ModeShapeLexicon.ID);
PSEUDO_COLUMN_NAMES = Collections.unmodifiableSet(names);
}
public static class Builder extends QueryEngineBuilder {
@Override
public QueryEngine build() {
return new ScanningQueryEngine(context(), repositoryName(), planner(), optimizer());
}
@Override
protected Optimizer defaultOptimizer() {
return new RuleBasedOptimizer();
}
}
public static Builder builder() {
return new Builder();
}
protected final String repositoryName;
protected final Planner planner;
protected final Optimizer optimizer;
public ScanningQueryEngine( ExecutionContext context,
String repositoryName,
Planner planner,
Optimizer optimizer ) {
assert planner != null;
assert optimizer != null;
this.repositoryName = repositoryName;
this.planner = planner;
this.optimizer = optimizer;
}
/**
* Execute the supplied query by planning, optimizing, and then processing it.
*
* @param queryContext the context in which the query should be executed; same instance as returned by
* {@link #createQueryContext}
* @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
* @throws QueryCancelledException if the query was cancelled
* @throws RepositoryException if there was a problem executing the query
*/
@Override
public QueryResults execute( final QueryContext queryContext,
QueryCommand query ) throws QueryCancelledException, RepositoryException {
CheckArg.isNotNull(queryContext, "queryContext");
CheckArg.isNotNull(query, "query");
final ScanQueryContext context = (ScanQueryContext)queryContext;
checkCancelled(context);
// Validate that all of the referenced variables have been provided ...
Visitors.visitAll(query, new Visitors.AbstractVisitor() {
@Override
public void visit( BindVariableName obj ) {
if (!context.getVariables().keySet().contains(obj.getBindVariableName())) {
context.getProblems().addError(GraphI18n.missingVariableValue, obj.getBindVariableName());
}
}
});
boolean trace = LOGGER.isTraceEnabled();
if (trace) {
LOGGER.trace("Beginning to process query {3} against workspace(s) {0} in '{1}' repository: {2}",
context.getWorkspaceNames(), repositoryName, query, context.id());
}
// Create the canonical plan ...
long start = System.nanoTime();
PlanNode plan = planner.createPlan(context, query);
long duration = Math.abs(System.nanoTime() - start);
Statistics stats = new Statistics(duration);
final String workspaceName = context.getWorkspaceNames().iterator().next();
if (trace) {
LOGGER.trace("Computed canonical query plan for query {0}: {1}", context.id(), plan);
}
checkCancelled(context);
Columns resultColumns = null;
if (!context.getProblems().hasErrors()) {
// Optimize the plan ...
start = System.nanoTime();
PlanNode optimizedPlan = optimizer.optimize(context, plan);
duration = Math.abs(System.nanoTime() - start);
stats = stats.withOptimizationTime(duration);
if (trace) {
LOGGER.trace("Computed optimized query plan for query {0}:\n{1}", context.id(), optimizedPlan);
}
// Find the query result columns ...
start = System.nanoTime();
// Determine the Columns object for specific nodes in the plan, and store them in the context ...
optimizedPlan.apply(Traversal.POST_ORDER, new PlanNode.Operation() {
@Override
public void apply( PlanNode node ) {
Columns columns = null;
switch (node.getType()) {
case PROJECT:
case SOURCE:
columns = determineProjectedColumns(node, context);
assert columns != null;
break;
case JOIN:
Columns leftColumns = context.columnsFor(node.getFirstChild());
Columns rightColumns = context.columnsFor(node.getLastChild());
columns = leftColumns.with(rightColumns);
assert columns != null;
break;
case DEPENDENT_QUERY:
columns = context.columnsFor(node.getLastChild());
assert columns != null;
break;
case SET_OPERATION:
leftColumns = context.columnsFor(node.getFirstChild());
rightColumns = context.columnsFor(node.getLastChild());
assert leftColumns.isUnionCompatible(rightColumns);
columns = leftColumns;
assert columns != null;
break;
case INDEX:
// do nothing with indexes ...
break;
default:
assert node.getChildCount() == 1;
columns = context.columnsFor(node.getFirstChild());
assert columns != null;
break;
}
if (columns != null) {
context.addColumnsFor(node, columns);
}
}
});
resultColumns = context.columnsFor(optimizedPlan);
assert resultColumns != null;
duration = Math.abs(System.nanoTime() - start);
stats = stats.withResultsFormulationTime(duration);
if (trace) {
LOGGER.trace("Computed output columns for query {0}: {1}", context.id(), resultColumns);
}
if (!context.getProblems().hasErrors()) {
checkCancelled(context);
// Execute the plan ...
try {
start = System.nanoTime();
if (trace) {
LOGGER.trace("Start executing query {0}", context.id());
}
QueryResults results = executeOptimizedQuery(context, query, stats, optimizedPlan);
if (trace) {
LOGGER.trace("Stopped executing query {0}: {1}", context.id(), stats);
}
return results;
} finally {
duration = Math.abs(System.nanoTime() - start);
stats = stats.withExecutionTime(duration);
}
}
}
// Check whether the query was cancelled during execution ...
checkCancelled(context);
if (resultColumns == null) resultColumns = ResultColumns.EMPTY;
// There were problems somewhere ...
int width = resultColumns.getColumns().size();
CachedNodeSupplier cachedNodes = context.getNodeCache(workspaceName);
return new Results(resultColumns, stats, NodeSequence.emptySequence(width), cachedNodes, context.getProblems(), null);
}
/**
* Compute the columns that are defined in the supplied {@link PlanNode plan node}. If the supplied plan node is not a
* {@link Type#PROJECT project node}, the method finds the first PROJECT node below the given node.
*
* @param optimizedPlan the optimized plan node in a query plan; may not be null
* @param context the query context; may not be null
* @return the representation of the projected columns; never null
*/
protected Columns determineProjectedColumns( PlanNode optimizedPlan,
final ScanQueryContext context ) {
final PlanHints hints = context.getHints();
// Look for which columns to include in the results; this will be defined by the highest PROJECT node ...
PlanNode project = optimizedPlan;
if (project.getType() != Type.PROJECT) {
project = optimizedPlan.findAtOrBelow(Traversal.LEVEL_ORDER, Type.PROJECT);
}
if (project != null) {
List columns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
List columnTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class);
// Determine whether to include the full-text search scores in the results ...
boolean includeFullTextSearchScores = hints.hasFullTextSearch;
if (!includeFullTextSearchScores) {
for (PlanNode select : optimizedPlan.findAllAtOrBelow(Type.SELECT)) {
Constraint constraint = select.getProperty(Property.SELECT_CRITERIA, Constraint.class);
if (QueryUtil.includeFullTextScores(constraint)) {
includeFullTextSearchScores = true;
break;
}
}
}
// The projected columns may not include all of the selectors from the child of the PROJECT node.
// So, we need to figure out the selector indexes based upon the ResultColumn for the child ...
Columns childColumns = context.columnsFor(project.getFirstChild());
return new ResultColumns(columns, columnTypes, includeFullTextSearchScores, childColumns);
}
// Look for a SOURCE ...
if (optimizedPlan.getType() == Type.SOURCE) {
PlanNode source = optimizedPlan;
List schemataColumns = source.getPropertyAsList(Property.SOURCE_COLUMNS, Schemata.Column.class);
List columns = new ArrayList<>(schemataColumns.size());
List columnTypes = new ArrayList<>(schemataColumns.size());
SelectorName selector = source.getSelectors().iterator().next();
for (Schemata.Column schemataColumn : schemataColumns) {
Column column = new Column(selector, schemataColumn.getName(), schemataColumn.getName());
columns.add(column);
columnTypes.add(schemataColumn.getPropertyTypeName());
}
return new ResultColumns(columns, columnTypes, hints.hasFullTextSearch, null);
}
return ResultColumns.EMPTY;
}
private void checkCancelled( QueryContext context ) throws QueryCancelledException {
if (context.isCancelled()) {
throw new QueryCancelledException();
}
}
@Override
public void shutdown() {
// nothing to do
}
@Override
public QueryContext createQueryContext( ExecutionContext context,
RepositoryCache repositoryCache,
Set workspaceNames,
Map overriddenNodeCachesByWorkspaceName,
Schemata schemata,
RepositoryIndexes indexDefns,
NodeTypes nodeTypes,
BufferManager bufferManager,
PlanHints hints,
Map variables ) {
return new ScanQueryContext(context, repositoryCache, workspaceNames, overriddenNodeCachesByWorkspaceName, schemata,
indexDefns, nodeTypes, bufferManager, hints, null, variables,
new HashMap());
}
/**
* Execute the optimized query defined by the supplied {@link PlanNode plan node}.
*
* @param context the context in which the query is to be executed; may not be null
* @param command the original query; may not be null
* @param statistics the statistics for the current query execution
* @param plan the optimized plan for the query; may not be null
* @return the query results; never null but possibly empty
*/
protected QueryResults executeOptimizedQuery( final ScanQueryContext context,
QueryCommand command,
Statistics statistics,
PlanNode plan ) {
long nanos = System.nanoTime();
Columns columns = null;
NodeSequence rows = null;
final String workspaceName = context.getWorkspaceNames().iterator().next();
try {
// Find the topmost PROJECT node and build the Columns ...
PlanNode project = plan.findAtOrBelow(Type.PROJECT);
assert project != null;
columns = context.columnsFor(plan);
assert columns != null;
boolean trace = LOGGER.isTraceEnabled();
if (context.getHints().planOnly) {
if (trace) {
LOGGER.trace("Request for only query plan when executing query {0}", context.id());
}
rows = NodeSequence.emptySequence(columns.getColumns().size());
} else {
boolean includeSystemContent = context.getHints().includeSystemContent;
final QuerySources sources = new QuerySources(context.getRepositoryCache(), context.getNodeTypes(),
workspaceName, includeSystemContent);
rows = createNodeSequence(command, context, plan, columns, sources);
long nanos2 = System.nanoTime();
statistics = statistics.withResultsFormulationTime(Math.abs(nanos2 - nanos));
nanos = nanos2;
if (rows == null) {
// There must have been an error or was cancelled ...
assert context.getProblems().hasErrors() || context.isCancelled();
rows = NodeSequence.emptySequence(columns.getColumns().size());
}
if (trace) {
LOGGER.trace("The execution function for {0}: {1}", context.id(), rows);
}
}
} finally {
statistics = statistics.withExecutionTime(Math.abs(System.nanoTime() - nanos));
}
final String planDesc = context.getHints().showPlan ? plan.getString() : null;
CachedNodeSupplier cachedNodes = context.getNodeCache(workspaceName);
return new Results(columns, statistics, rows, cachedNodes, context.getProblems(), planDesc);
}
/**
* Create a node sequence containing the results of the original query as defined by the supplied plan.
*
* @param originalQuery the original query command; may not be null
* @param context the context in which the query is to be executed; may not be null
* @param plan the optimized plan for the query; may not be null
* @param columns the result column definition; may not be null
* @param sources the query sources for the repository; may not be null
* @return the sequence of results; null only if the type of plan is not understood
*/
protected NodeSequence createNodeSequence( QueryCommand originalQuery,
ScanQueryContext context,
PlanNode plan,
Columns columns,
QuerySources sources ) {
NodeSequence rows = null;
final String workspaceName = sources.getWorkspaceName();
final NodeCache cache = context.getNodeCache(workspaceName);
final TypeSystem types = context.getTypeSystem();
final BufferManager bufferManager = context.getBufferManager();
switch (plan.getType()) {
case ACCESS:
// If the ACCESS node is known to never have results ...
if (plan.hasProperty(Property.ACCESS_NO_RESULTS)) {
rows = NodeSequence.emptySequence(columns.getColumns().size());
} else {
// Create the sequence for the plan node under the the ACCESS node ...
assert plan.getChildCount() == 1;
rows = createNodeSequence(originalQuery, context, plan.getFirstChild(), columns, sources);
}
break;
case DEPENDENT_QUERY:
assert plan.getChildCount() == 2;
// Create the independent query from the left ...
PlanNode indepPlan = plan.getFirstChild();
Columns indepColumns = context.columnsFor(indepPlan);
String variableName = indepPlan.getProperty(Property.VARIABLE_NAME, String.class);
NodeSequence independent = createNodeSequence(originalQuery, context, indepPlan, indepColumns, sources);
// Create an extractor to get the value specified in the columns ...
Column column = indepColumns.getColumns().get(0);
boolean allowMultiValued = false;
String typeName = indepColumns.getColumnTypeForProperty(column.getSelectorName(), column.getPropertyName());
TypeFactory> type = context.getTypeSystem().getTypeFactory(typeName);
ExtractFromRow indepExtractor = createExtractFromRow(column.getSelectorName(), column.getPropertyName(), context,
indepColumns, sources, type, allowMultiValued);
// Create the sequence for the dependent query ...
PlanNode depPlan = plan.getLastChild();
Columns depColumns = context.columnsFor(depPlan);
NodeSequence dependent = createNodeSequence(originalQuery, context, depPlan, depColumns, sources);
// now create the dependent query ...
rows = new DependentQuery(independent, indepExtractor, type, dependent, variableName, context.getVariables());
break;
case DUP_REMOVE:
assert plan.getChildCount() == 1;
if (plan.getFirstChild().getType() == Type.SORT) {
// There is a SORT below this DUP_REMOVE, and we can do that in one fell swoop with the sort ...
rows = createNodeSequence(originalQuery, context, plan.getFirstChild(), columns, sources);
} else {
// Create the sequence for the plan node under the DUP_REMOVE ...
rows = createNodeSequence(originalQuery, context, plan.getFirstChild(), columns, sources);
if (!rows.isEmpty() && !(rows instanceof DistinctSequence)) {
// Wrap that with a sequence that removes duplicates ...
boolean useHeap = false;
rows = new DistinctSequence(rows, context.getTypeSystem(), context.getBufferManager(), useHeap);
}
}
break;
case GROUP:
throw new UnsupportedOperationException();
case JOIN:
// Create the components under the JOIN ...
assert plan.getChildCount() == 2;
PlanNode leftPlan = plan.getFirstChild();
PlanNode rightPlan = plan.getLastChild();
// Define the columns for each side, taken from the supplied columns ...
Columns leftColumns = context.columnsFor(leftPlan);
Columns rightColumns = context.columnsFor(rightPlan);
// Query context for the join (must remove isExists condition).
ScanQueryContext joinQueryContext = context;
if (context.getHints().isExistsQuery) {
// must not push down a LIMIT 1 condition to joins.
PlanHints joinPlanHints = context.getHints().clone();
joinPlanHints.isExistsQuery = false;
joinQueryContext = context.with(joinPlanHints);
}
NodeSequence left = createNodeSequence(originalQuery, joinQueryContext, leftPlan, leftColumns, sources);
NodeSequence right = createNodeSequence(originalQuery, joinQueryContext, rightPlan, rightColumns, sources);
// Figure out the join algorithm ...
JoinAlgorithm algorithm = plan.getProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.class);
JoinType joinType = plan.getProperty(Property.JOIN_TYPE, JoinType.class);
JoinCondition joinCondition = plan.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
boolean pack = false;
boolean useHeap = false;
if (0 >= right.getRowCount() && right.getRowCount() < 100) useHeap = true;
ExtractFromRow leftExtractor = null;
ExtractFromRow rightExtractor = null;
RangeProducer> rangeProducer = null;
switch (algorithm) {
case NESTED_LOOP:
// rows = new NestedLoopJoinComponent(context, left, right, joinCondition, joinType);
// break;
case MERGE:
if (joinCondition instanceof SameNodeJoinCondition) {
SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
// check if the JOIN was not reversed by an optimization
boolean joinReversed = !leftColumns.getSelectorNames().contains(condition.getSelector1Name());
int leftIndex;
int rightIndex;
if (joinReversed) {
// figure out the row indexes for the different selectors ...
leftIndex = leftColumns.getSelectorIndex(condition.getSelector2Name());
rightIndex = rightColumns.getSelectorIndex(condition.getSelector1Name());
} else {
leftIndex = leftColumns.getSelectorIndex(condition.getSelector1Name());
rightIndex = rightColumns.getSelectorIndex(condition.getSelector2Name());
}
String relativePath = condition.getSelector2Path();
if (relativePath != null) {
// Get extractors that will get the path of the nodes ...
PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory();
Path relPath = pathFactory.create(relativePath);
if (joinReversed) {
leftExtractor = RowExtractors.extractRelativePath(leftIndex, relPath, cache, types);
rightExtractor = RowExtractors.extractPath(rightIndex, cache, types);
} else {
leftExtractor = RowExtractors.extractPath(leftIndex, cache, types);
rightExtractor = RowExtractors.extractRelativePath(rightIndex, relPath, cache, types);
}
} else {
// The nodes must be the same node ...
leftExtractor = RowExtractors.extractNodeKey(leftIndex, cache, types);
rightExtractor = RowExtractors.extractNodeKey(rightIndex, cache, types);
}
} else if (joinCondition instanceof ChildNodeJoinCondition) {
ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
assert leftColumns.getSelectorNames().contains(condition.getParentSelectorName());
int leftIndex = leftColumns.getSelectorIndex(condition.getParentSelectorName());
int rightIndex = rightColumns.getSelectorIndex(condition.getChildSelectorName());
leftExtractor = RowExtractors.extractNodeKey(leftIndex, cache, types);
rightExtractor = RowExtractors.extractParentNodeKey(rightIndex, cache, types);
} else if (joinCondition instanceof EquiJoinCondition) {
EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
// check if the JOIN was not reversed by an optimization
boolean joinReversed = !leftColumns.getSelectorNames().contains(condition.getSelector1Name());
String sel1 = condition.getSelector1Name();
String sel2 = condition.getSelector2Name();
String prop1 = condition.getProperty1Name();
String prop2 = condition.getProperty2Name();
if (joinReversed) {
leftExtractor = createExtractFromRow(sel2, prop2, joinQueryContext, leftColumns, sources, null,
true);
rightExtractor = createExtractFromRow(sel1, prop1, joinQueryContext, rightColumns, sources, null,
true);
} else {
leftExtractor = createExtractFromRow(sel1, prop1, joinQueryContext, leftColumns, sources, null,
true);
rightExtractor = createExtractFromRow(sel2, prop2, joinQueryContext, rightColumns, sources, null,
true);
}
} else if (joinCondition instanceof DescendantNodeJoinCondition) {
DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
// For this to work, we want the ancestors to be on the left, so that the descendants can quickly
// be found given a path of each ancestor ...
assert leftColumns.getSelectorNames().contains(condition.getAncestorSelectorName());
String ancestorSelector = condition.getAncestorSelectorName();
String descendantSelector = condition.getDescendantSelectorName();
int ancestorSelectorIndex = leftColumns.getSelectorIndex(ancestorSelector);
int descendantSelectorIndex = rightColumns.getSelectorIndex(descendantSelector);
leftExtractor = RowExtractors.extractPath(ancestorSelectorIndex, cache, types);
rightExtractor = RowExtractors.extractPath(descendantSelectorIndex, cache, types);
// This is the only time we need a RangeProducer ...
final PathFactory paths = context.getExecutionContext().getValueFactories().getPathFactory();
rangeProducer = new RangeProducer() {
@Override
public Range getRange( Path leftPath ) {
if (leftPath.isRoot()) {
// All paths are descendants of the root
return new Range<>(leftPath, false, null, true);
}
// Given the path of the node on the left side of the join, find the range of all paths
// that might be considered descendants of the left path....
boolean includeLower = false; // we don't want to include the left node; only descendants
// The upper bound path is the same as the left path, just with an incremented SNS ...
Path.Segment lastSegment = leftPath.getLastSegment();
Path.Segment upperSegment = paths.createSegment(lastSegment.getName(),
lastSegment.getIndex() + 1);
Path upperBoundPath = paths.create(leftPath.getParent(), upperSegment);
return new Range<>(leftPath, includeLower, upperBoundPath, false);
}
};
} else {
assert false : "Unable to use merge algorithm with join conditions: " + joinCondition;
throw new UnsupportedOperationException();
}
break;
}
// Perform conversion if required ...
assert leftExtractor != null;
assert rightExtractor != null;
TypeFactory> leftType = leftExtractor.getType();
TypeFactory> rightType = rightExtractor.getType();
if (!leftType.equals(rightType)) {
// wrap the right extractor with a converting extractor ...
final TypeFactory> commonType = context.getTypeSystem().getCompatibleType(leftType, rightType);
if (!leftType.equals(commonType)) leftExtractor = RowExtractors.convert(leftExtractor, commonType);
if (!rightType.equals(commonType)) rightExtractor = RowExtractors.convert(rightExtractor, commonType);
}
rows = new HashJoinSequence(workspaceName, left, right, leftExtractor, rightExtractor, joinType,
context.getBufferManager(), cache, rangeProducer, pack, useHeap);
// For each Constraint object applied to the JOIN, simply create a SelectComponent on top ...
RowFilter filter = null;
List constraints = plan.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
if (constraints != null) {
for (Constraint constraint : constraints) {
RowFilter constraintFilter = createRowFilter(constraint, context, columns, sources);
filter = NodeSequence.requireBoth(filter, constraintFilter);
}
}
rows = NodeSequence.filter(rows, filter); // even if filter is null
break;
case LIMIT:
// Create the sequence for the plan node under the LIMIT ...
assert plan.getChildCount() == 1;
rows = createNodeSequence(originalQuery, context, plan.getFirstChild(), columns, sources);
// Calculate the limit ...
Integer rowLimit = plan.getProperty(Property.LIMIT_COUNT, Integer.class);
Integer offset = plan.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());
// Then create the limited sequence ...
if (!limit.isUnlimited()) {
rows = NodeSequence.limit(rows, limit);
}
break;
case NULL:
// No results ...
rows = NodeSequence.emptySequence(columns.getColumns().size());
break;
case PROJECT:
// Nothing to do, since the projected columns will be accessed as needed when the results are processed. Instead,
// just process the PROJECT node's only child ...
PlanNode child = plan.getFirstChild();
columns = context.columnsFor(child);
rows = createNodeSequence(originalQuery, context, child, columns, sources);
break;
case SELECT:
// Create the sequence for the plan node under the SELECT ...
assert plan.getChildCount() == 1;
rows = createNodeSequence(originalQuery, context, plan.getFirstChild(), columns, sources);
Constraint constraint = plan.getProperty(Property.SELECT_CRITERIA, Constraint.class);
filter = createRowFilter(constraint, context, columns, sources);
rows = NodeSequence.filter(rows, filter);
break;
case SET_OPERATION:
Operation operation = plan.getProperty(Property.SET_OPERATION, Operation.class);
boolean all = plan.getProperty(Property.SET_USE_ALL, Boolean.class);
PlanNode firstPlan = plan.getFirstChild();
PlanNode secondPlan = plan.getLastChild();
Columns firstColumns = context.columnsFor(firstPlan);
Columns secondColumns = context.columnsFor(secondPlan);
NodeSequence first = createNodeSequence(originalQuery, context, firstPlan, firstColumns, sources);
NodeSequence second = createNodeSequence(originalQuery, context, secondPlan, secondColumns, sources);
useHeap = 0 >= second.getRowCount() && second.getRowCount() < 100;
if (first.width() != second.width()) {
// A set operation requires that the 'first' and 'second' sequences have the same width, but this is
// not necessarily the case (e.g., when one side involves a JOIN but the other does not). The columns
// will dictate which subset of selector indexes in the sequences should be used.
first = NodeSequence.slice(first, firstColumns);
second = NodeSequence.slice(second, secondColumns);
assert first.width() == second.width();
}
pack = false;
switch (operation) {
case UNION: {
// This is really just a sequence with the two parts ...
rows = NodeSequence.append(first, second);
break;
}
case INTERSECT: {
rows = new IntersectSequence(workspaceName, first, second, types, bufferManager, cache, pack, useHeap);
break;
}
case EXCEPT: {
rows = new ExceptSequence(workspaceName, first, second, types, bufferManager, cache, pack, useHeap);
break;
}
}
if (!all) {
useHeap = false;
rows = new DistinctSequence(rows, context.getTypeSystem(), context.getBufferManager(), useHeap);
}
break;
case SORT:
assert plan.getChildCount() == 1;
PlanNode delegate = plan.getFirstChild();
boolean allowDuplicates = true;
if (delegate.getType() == Type.DUP_REMOVE) {
// This SORT already removes duplicates, so we can skip the first child ...
delegate = delegate.getFirstChild();
allowDuplicates = false;
}
PlanNode parent = plan.getParent();
if (parent != null && parent.getType() == Type.DUP_REMOVE) {
// The parent is a DUP_REMOVE (shouldn't really happen in an optimized plan), we should disallow duplicates
// ...
allowDuplicates = false;
}
// Create the sequence for the delegate plan node ...
rows = createNodeSequence(originalQuery, context, delegate, columns, sources);
if (!rows.isEmpty()) {
// Prepare to wrap this delegate sequence based upon the SORT_ORDER_BY ...
List