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<Path>() {
@Override
public Range<Path> 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<Constraint> 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: {
// If one of them is empty, return the other ...
if (first.isEmpty()) return second;
if (second.isEmpty()) return first;
// This is really just a sequence with the two parts ...
rows = NodeSequence.append(first, second);
break;
}
case INTERSECT: {
// If one of them is empty, there are no results ...
if (first.isEmpty()) return first;
if (second.isEmpty()) return second;
rows = new IntersectSequence(workspaceName, first, second, types, bufferManager, cache, pack, useHeap);
break;
}
case EXCEPT: {
// If the second is empty, there's nothing to exclude ...
if (second.isEmpty()) return first;
rows = new ExceptSequence(workspaceName, first, second, types, bufferManager, cache, pack, useHeap);
break;
}
}
if (!all) {