}
if (constraint instanceof ChildNode) {
ChildNode childConstraint = (ChildNode)constraint;
PathFactory paths = context.getExecutionContext().getValueFactories().getPathFactory();
final Path parentPath = paths.create(childConstraint.getParentPath());
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
final CachedNode parent = sources.getNodeAtPath(parentPath, cache);
if (parent == null) {
return NodeSequence.NO_PASS_ROW_FILTER;
}
final NodeKey parentKey = parent.getKey();
final String selectorName = childConstraint.getSelectorName();
final int index = columns.getSelectorIndex(selectorName);
return new RowFilter() {
@Override
public boolean isCurrentRowValid( Batch batch ) {
CachedNode node = batch.getNode(index);
if (node == null) return false;
if (parentKey.equals(node.getParentKey(cache))) return true;
// Don't have to check the additional parents since we only find shared nodes in the original location ...
return false;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
if (constraint instanceof DescendantNode) {
DescendantNode descendantNode = (DescendantNode)constraint;
PathFactory paths = context.getExecutionContext().getValueFactories().getPathFactory();
final Path ancestorPath = paths.create(descendantNode.getAncestorPath());
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
final CachedNode ancestor = sources.getNodeAtPath(ancestorPath, cache);
if (ancestor == null) {
return NodeSequence.NO_PASS_ROW_FILTER;
}
final NodeKey ancestorKey = ancestor.getKey();
final String selectorName = descendantNode.getSelectorName();
final int index = columns.getSelectorIndex(selectorName);
return new RowFilter() {
@Override
public boolean isCurrentRowValid( Batch batch ) {
CachedNode node = batch.getNode(index);
while (node != null) {
NodeKey parentKey = node.getParentKey(cache);
if (parentKey == null) return false;
if (ancestorKey.equals(parentKey)) return true;
// Don't have to check the additional parents since we only find shared nodes in the original location ...
node = cache.getNode(parentKey);
}
return false;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
if (constraint instanceof SameNode) {
SameNode sameNode = (SameNode)constraint;
PathFactory paths = context.getExecutionContext().getValueFactories().getPathFactory();
final Path path = paths.create(sameNode.getPath());
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
final CachedNode node = sources.getNodeAtPath(path, cache);
if (node == null) {
return NodeSequence.NO_PASS_ROW_FILTER;
}
final NodeKey nodeKey = node.getKey();
final String selectorName = sameNode.getSelectorName();
final int index = columns.getSelectorIndex(selectorName);
return new RowFilter() {
@Override
public boolean isCurrentRowValid( Batch batch ) {
CachedNode node = batch.getNode(index);
return node != null && nodeKey.equals(node.getKey());
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
if (constraint instanceof PropertyExistence) {
PropertyExistence propertyExistance = (PropertyExistence)constraint;
NameFactory names = context.getExecutionContext().getValueFactories().getNameFactory();
final Name propertyName = names.create(propertyExistance.getPropertyName());
final String selectorName = propertyExistance.selectorName().name();
final int index = columns.getSelectorIndex(selectorName);
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
assert index >= 0;
return new RowFilter() {
@Override
public boolean isCurrentRowValid( Batch batch ) {
CachedNode node = batch.getNode(index);
return node != null && node.hasProperty(propertyName, cache);
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
if (constraint instanceof Between) {
Between between = (Between)constraint;
final StaticOperand lower = between.getLowerBound();
final StaticOperand upper = between.getUpperBound();
final boolean includeLower = between.isLowerBoundIncluded();
final boolean includeUpper = between.isUpperBoundIncluded();
DynamicOperand dynamicOperand = between.getOperand();
final TypeFactory<?> defaultType = determineType(dynamicOperand, context, columns);
final ExtractFromRow operation = createExtractFromRow(dynamicOperand, context, columns, sources, defaultType, true,
false);
// Determine the literal value in the static operand ...
return new RowFilterSupplier() {
@Override
protected RowFilter createFilter() {
// Evaluate the operand, which may have variables ...
final Object lowerLiteralValue = literalValue(lower, context, defaultType);
final Object upperLiteralValue = literalValue(upper, context, defaultType);
// Create the correct operation ...
final TypeFactory<?> expectedType = operation.getType();
final Object lowerValue = expectedType.create(lowerLiteralValue);
final Object upperValue = expectedType.create(upperLiteralValue);
@SuppressWarnings( "unchecked" )
final Comparator<Object> comparator = (Comparator<Object>)expectedType.getComparator();
if (includeLower) {
if (includeUpper) {
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, lowerValue) >= 0
&& comparator.compare(leftHandValue, upperValue) <= 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
// Don't include upper ...
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, lowerValue) >= 0
&& comparator.compare(leftHandValue, upperValue) < 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
assert !includeLower;
// Don't include lower
if (includeUpper) {
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, lowerValue) > 0
&& comparator.compare(leftHandValue, upperValue) <= 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
// Don't include upper or lower ...
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, lowerValue) > 0
&& comparator.compare(leftHandValue, upperValue) < 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
};
}
if (constraint instanceof Comparison) {
Comparison comparison = (Comparison)constraint;
// Create the correct dynamic operation ...
final DynamicOperand dynamicOperand = comparison.getOperand1();
final Operator operator = comparison.operator();
final StaticOperand staticOperand = comparison.getOperand2();
final TypeFactory<?> actualType = determineType(dynamicOperand, context, columns);
TypeFactory<?> expectedType = null;
ExtractFromRow op = null;
if (operator == Operator.LIKE) {
expectedType = context.getTypeSystem().getStringFactory();
op = createExtractFromRow(dynamicOperand, context, columns, sources, expectedType, true, true);
if (op.getType() != expectedType) {
// Need to convert the extracted value(s) to strings because this is a LIKE operation ...
op = RowExtractors.convert(op, expectedType);
}
} else {
expectedType = actualType;
op = createExtractFromRow(dynamicOperand, context, columns, sources, expectedType, true, false);
}
final TypeFactory<?> defaultType = expectedType;
final ExtractFromRow operation = op;
// Determine the literal value in the static operand ...
return new RowFilterSupplier() {
@Override
protected RowFilter createFilter() {
// Evaluate the operand, which may have variables ...
final Object literalValue = literalValue(staticOperand, context, defaultType);
// Create the correct operation ...
final TypeFactory<?> expectedType = operation.getType();
final Object rhs = expectedType.create(literalValue);
@SuppressWarnings( "unchecked" )
final Comparator<Object> comparator = (Comparator<Object>)expectedType.getComparator();
switch (operator) {
case EQUAL_TO:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) == 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case NOT_EQUAL_TO:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) != 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case GREATER_THAN:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) > 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case GREATER_THAN_OR_EQUAL_TO:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) >= 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case LESS_THAN:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) < 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case LESS_THAN_OR_EQUAL_TO:
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
return comparator.compare(leftHandValue, rhs) <= 0;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
case LIKE:
// Convert the LIKE expression to a regular expression
final TypeSystem types = context.getTypeSystem();
String expression = types.asString(rhs).trim();
if ("%".equals(expression)) {
// We'll accept any non-null value ...
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
return leftHandValue != null;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
if (Path.class.isAssignableFrom(actualType.getType())) {
// This LIKE is dealing with paths and SNS wildcards, so we have to extract path values that
// have SNS indexes in all segments ...
final PathFactory paths = context.getExecutionContext().getValueFactories().getPathFactory();
expression = QueryUtil.addSnsIndexesToLikeExpression(expression);
String regex = QueryUtil.toRegularExpression(expression);
final Pattern pattern = Pattern.compile(regex);
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
// Get the value as a path and construct a string representation with SNS indexes
// in the correct spot ...
Path path = paths.create(leftHandValue);
String strValue = null;
if (path.isRoot()) {
strValue = "/";
} else {
StringBuilder sb = new StringBuilder();
for (Path.Segment segment : path) {
sb.append('/').append(types.asString(segment.getName()));
sb.append('[').append(segment.getIndex()).append(']');
}
strValue = sb.toString();
}
return pattern.matcher(strValue).matches();
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
String regex = QueryUtil.toRegularExpression(expression);
final Pattern pattern = Pattern.compile(regex);
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue == null) return false; // null values never match
String value = types.asString(leftHandValue);
return pattern.matcher(value).matches();
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
assert false : "Should not get here";
return null;
}
};
}
if (constraint instanceof SetCriteria) {
final SetCriteria setCriteria = (SetCriteria)constraint;
DynamicOperand operand = setCriteria.getOperand();
final TypeFactory<?> defaultType = determineType(operand, context, columns);
// If the set criteria contains a bind variable, then the operand filter should lazily evaluate the bind variable ...
final ExtractFromRow operation = createExtractFromRow(operand, context, columns, sources, defaultType, true, false);
final boolean trace = LOGGER.isTraceEnabled() && !defaultType.getTypeName().equals("NAME");
return new RowFilterSupplier() {
@Override
protected RowFilter createFilter() {
final Set<?> values = ScanningQueryEngine.literalValues(setCriteria, context, defaultType);
return new DynamicOperandFilter(operation) {
@Override
protected boolean evaluate( Object leftHandValue ) {
if (leftHandValue instanceof Object[]) {
for (Object leftValue : (Object[])leftHandValue) {
if (values.contains(leftValue)) {
if (trace) LOGGER.trace("Found '{0}' in values: {1}", leftHandValue, values);
return true;
}
}
if (trace) LOGGER.trace("Failed to find '{0}' in values: {1}", leftHandValue, values);
return false;
}
if (values.contains(leftHandValue)) {
if (trace) {
LOGGER.trace("Found '{0}' in values: {1}", leftHandValue, values);
}
return true;
}
if (trace) {
LOGGER.trace("Failed to find '{0}' in values: {1}", leftHandValue, values);
}
return false;
}
@Override
public String toString() {
return "(filter " + Visitors.readable(constraint) + ")";
}
};
}
};
}
if (constraint instanceof FullTextSearch) {
final TypeFactory<String> strings = context.getTypeSystem().getStringFactory();
final StaticOperand ftsExpression = ((FullTextSearch)constraint).getFullTextSearchExpression();
final FullTextSearch fts;
if (ftsExpression instanceof BindVariableName) {
Object searchExpression = literalValue(ftsExpression, context, strings);
if (searchExpression != null) {
fts = ((FullTextSearch)constraint).withFullTextExpression(searchExpression.toString());
} else {
fts = (FullTextSearch)constraint;
}
} else {
fts = (FullTextSearch)constraint;
}
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
final BinaryStore binaries = context.getExecutionContext().getBinaryStore();
String selectorName = fts.getSelectorName();
String propertyName = fts.getPropertyName();
final int index = columns.getSelectorIndex(selectorName);
ExtractFromRow fullTextExtractor = null;
if (propertyName != null) {
// This is to search just the designated property of the node (name, all property values) ...
final ExtractFromRow propertyValueExtractor = createExtractFromRow(selectorName, propertyName, context, columns,
sources, strings, true);
fullTextExtractor = new ExtractFromRow() {
@Override
public TypeFactory<?> getType() {
return strings;
}
@Override
public Object getValueInRow( RowAccessor row ) {
Object result = propertyValueExtractor.getValueInRow(row);
if (result == null) return null;
StringBuilder fullTextString = new StringBuilder();
RowExtractors.extractFullTextFrom(result, strings, binaries, fullTextString);
return fullTextString.toString();
}
};
} else {
// This is to search all aspects of the node (name, all property values) ...
fullTextExtractor = RowExtractors.extractFullText(index, cache, context.getTypeSystem(), binaries);
}
// Return a filter that processes all of the text ...
final ExtractFromRow extractor = fullTextExtractor;
return new DynamicOperandFilter(extractor) {
@Override
protected boolean evaluate( Object leftHandValue ) {
/**
* The term will match the extracted value "as-is" via regex, without any stemming or punctuation removal.
* This means that the matching is done in a much more strict way than what Lucene did in 3.x If we were to
* implement stemming or hyphen removal, we would need to do it *both* in the row extractor
* (RowExtractors.extractFullText) and in the term where the regex is built
*/
return fts.getTerm().matches(leftHandValue.toString());
}
};
}
if (constraint instanceof Relike) {
Relike relike = (Relike)constraint;
StaticOperand staticOperand = relike.getOperand1();
Object literalValue = literalValue(staticOperand, context, context.getTypeSystem().getStringFactory());
if (literalValue == null) {
return NodeSequence.NO_PASS_ROW_FILTER;
}
final String literalStr = literalValue.toString();
PropertyValue propertyValue = relike.getOperand2();
NameFactory names = context.getExecutionContext().getValueFactories().getNameFactory();
final Name propertyName = names.create(propertyValue.getPropertyName());
final String selectorName = propertyValue.getSelectorName();
final int index = columns.getSelectorIndex(selectorName);
final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
return new RowFilter() {
@Override
public boolean isCurrentRowValid( Batch batch ) {
CachedNode node = batch.getNode(index);
if (node == null) return false;