docs: protected RowFilter createRowFilter( final Constraint constraint,
                                           final QueryContext context,
                                           Columns columns,
                                           QuerySources sources ) {
          assert constraint != null;
          assert context != null;
          assert columns != null;
          assert sources != null;
          if (constraint instanceof Or) {
              Or orConstraint = (Or)constraint;
              final RowFilter left = createRowFilter(orConstraint.left(), context, columns, sources);
              final RowFilter right = createRowFilter(orConstraint.right(), context, columns, sources);
              return new RowFilter() {
                  @Override
                  public boolean isCurrentRowValid( Batch batch ) {
                      return left.isCurrentRowValid(batch) || right.isCurrentRowValid(batch);
                  }
  
                  @Override
                  public String toString() {
                      return "(or " + left + "," + right + " )";
                  }
              };
          }
          if (constraint instanceof Not) {
              Not notConstraint = (Not)constraint;
              final RowFilter not = createRowFilter(notConstraint.getConstraint(), context, columns, sources);
              return new RowFilter() {
                  @Override
                  public boolean isCurrentRowValid( Batch batch ) {
                      return !not.isCurrentRowValid(batch);
                  }
  
                  @Override
                  public String toString() {
                      return "(not " + not + " )";
                  }
              };
          }
          if (constraint instanceof And) {
              And andConstraint = (And)constraint;
              final RowFilter left = createRowFilter(andConstraint.left(), context, columns, sources);
              final RowFilter right = createRowFilter(andConstraint.right(), context, columns, sources);
              return new RowFilter() {
                  @Override
                  public boolean isCurrentRowValid( Batch batch ) {
                      return left.isCurrentRowValid(batch) && right.isCurrentRowValid(batch);
                  }
  
                  @Override
                  public String toString() {
                      return "(and " + left + "," + right + " )";
                  }
              };
          }
          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 boolean isParentRoot = parentPath.isRoot();
              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 (isParentRoot) return true;
                      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");
              final PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory();
              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 (Path.class.isAssignableFrom(defaultType.getType())) {
                                  leftHandValue = leftHandValue instanceof Object[] ?
                                                  pathFactory.create((Object[])leftHandValue) : 
                                                  pathFactory.create(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;
                      org.modeshape.jcr.value.Property property = node.getProperty(propertyName, cache);
                      if (property == null) return false;
                      for (Object value : property) {
                          if (value == null) continue;
                          // The property value should be a LIKE expression ...
                          String regex = toRegularExpression(value.toString());
                          final Pattern pattern = Pattern.compile(regex);
                          if (pattern.matcher(literalStr).matches()) return true;
                      }
                      return false;
                  }
              };
          }
          assert false;
          return NodeSequence.PASS_ROW_FILTER;
      }