docs: protected ExtractFromRow createExtractFromRow( DynamicOperand operand,
                                                     QueryContext context,
                                                     Columns columns,
                                                     final QuerySources sources,
                                                     TypeFactory<?> defaultType,
                                                     boolean allowMultiValued,
                                                     boolean isLike ) {
          assert operand != null;
          assert context != null;
          assert columns != null;
          assert sources != null;
          if (operand instanceof PropertyValue) {
              PropertyValue propValue = (PropertyValue)operand;
              String propertyName = propValue.getPropertyName();
              String selectorName = propValue.selectorName().name();
              return createExtractFromRow(selectorName, propertyName, context, columns, sources, defaultType, allowMultiValued);
          }
          if (operand instanceof ReferenceValue) {
              ReferenceValue refValue = (ReferenceValue)operand;
              String propertyName = refValue.getPropertyName();
              String selectorName = refValue.selectorName().name();
              if (propertyName == null) {
                  return createExtractReferencesFromRow(selectorName, context, columns, sources, defaultType);
              }
              return createExtractFromRow(selectorName, propertyName, context, columns, sources, defaultType, allowMultiValued);
          }
          if (operand instanceof Length) {
              Length length = (Length)operand;
              final PropertyValue value = length.getPropertyValue();
              String propertyName = value.getPropertyName();
              String selectorName = value.selectorName().name();
              final ExtractFromRow getPropValue = createExtractFromRow(selectorName, propertyName, context, columns, sources,
                                                                       defaultType, allowMultiValued);
              final TypeFactory<?> longType = context.getTypeSystem().getLongFactory();
              return new ExtractFromRow() {
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      Object typedValue = getPropValue.getValueInRow(row);
                      return getPropValue.getType().length(typedValue);
                  }
  
                  @Override
                  public TypeFactory<?> getType() {
                      return longType;
                  }
  
                  @Override
                  public String toString() {
                      return "(length " + getPropValue + ")";
                  }
              };
          }
          final TypeFactory<String> stringFactory = context.getTypeSystem().getStringFactory();
          if (operand instanceof LowerCase) {
              LowerCase lowerCase = (LowerCase)operand;
              final ExtractFromRow delegate = createExtractFromRow(lowerCase.getOperand(), context, columns, sources, defaultType,
                                                                   allowMultiValued, false);
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      Object valueInRow = delegate.getValueInRow(row);
                      if (valueInRow == null) {
                          return null;
                      }
                      if (valueInRow instanceof Object[]) {
                          // multi valued prop
                          Object[] values = (Object[])valueInRow;
                          Object[] lowerCasedValues = new Object[values.length];
                          for (int i = 0; i < values.length; i++) {  
                              String valueString = stringFactory.create(values[i]);
                              lowerCasedValues[i] = valueString.toLowerCase();
                          }
                          return lowerCasedValues;
                      } else {
                          // single valued prop
                          return stringFactory.create(valueInRow).toLowerCase();
                      }
                  }
  
                  @Override
                  public String toString() {
                      return "(lowercase " + delegate + ")";
                  }
              };
          }
          if (operand instanceof UpperCase) {
              final UpperCase upperCase = (UpperCase)operand;
              final ExtractFromRow delegate = createExtractFromRow(upperCase.getOperand(), context, columns, sources, defaultType,
                                                                   allowMultiValued, false);
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      Object valueInRow = delegate.getValueInRow(row);
                      if (valueInRow == null) {
                          return null;
                      }
                      if (valueInRow instanceof Object[]) {
                          // multi valued prop
                          Object[] values = (Object[])valueInRow;
                          Object[] upperCasedValues = new Object[values.length];
                          for (int i = 0; i < values.length; i++) {
                              String valueString = stringFactory.create(values[i]);
                              upperCasedValues[i] = valueString.toUpperCase();
                          }
                          return upperCasedValues;
                      } else {
                          // single valued prop
                          return stringFactory.create(valueInRow).toUpperCase();
                      }
                  }
  
                  @Override
                  public String toString() {
                      return "(uppercase " + delegate + ")";
                  }
              };
          }
          if (operand instanceof Cast) {
              final Cast cast = (Cast)operand;
              final ExtractFromRow delegate = createExtractFromRow(cast.getOperand(), context, columns, sources, defaultType,
                                                                   allowMultiValued, false);
              final String desiredTypeName = cast.getDesiredTypeName();
              final TypeFactory<?> typeFactory = context.getTypeSystem().getTypeFactory(desiredTypeName);
              final Class<?> desiredType = typeFactory.getType();
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return typeFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      Object valueInRow = delegate.getValueInRow(row);
                      if (valueInRow == null) {
                          return null;
                      }
                      if (valueInRow instanceof Object[]) {
                          // multi valued prop
                          Object[] values = (Object[])valueInRow;
                          Object[] convertedValues = new Object[values.length];
                          for (int i = 0; i < values.length; i++) {
                              Object originalValue = values[i];
                              if (desiredType.isAssignableFrom(originalValue.getClass())) {
                                  // short circuit if someone is trying to do a cast to same type as the original
                                  return values;
                              }
                              Object convertedValue = typeFactory.create(originalValue);
                              convertedValues[i] = convertedValue;
                          }
                          return convertedValues;
                      } else {
                          // single valued prop
                          if (desiredType.isAssignableFrom(valueInRow.getClass()))  {
                              // short circuit if someone is trying to do a cast to same type as the original
                              return valueInRow;
                          }
                          return typeFactory.create(valueInRow);
                      }
                  }
  
                  @Override
                  public String toString() {
                      return "(cast " + delegate + ")";
                  }
              };
          }
          if (operand instanceof NodeDepth) {
              final NodeDepth nodeDepth = (NodeDepth)operand;
              final int indexInRow = columns.getSelectorIndex(nodeDepth.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              final TypeFactory<?> longType = context.getTypeSystem().getLongFactory();
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return longType; // depth is always a long type
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      return new Long(node.getDepth(cache));
                  }
  
                  @Override
                  public String toString() {
                      return "(nodeDepth " + nodeDepth.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof ChildCount) {
              final ChildCount childCount = (ChildCount)operand;
              final int indexInRow = columns.getSelectorIndex(childCount.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              final TypeFactory<?> longType = context.getTypeSystem().getLongFactory();
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return longType; // count is always a long type
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      return new Long(node.getChildReferences(cache).size());
                  }
  
                  @Override
                  public String toString() {
                      return "(childCount " + childCount.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof NodeId) {
              final NodeId nodeId = (NodeId)operand;
              final int indexInRow = columns.getSelectorIndex(nodeId.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              final NodeKey root = cache.getRootKey();
              final TypeFactory<?> stringType = context.getTypeSystem().getStringFactory();
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringType; // ID is always a string type
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      return sources.getIdentifier(node, root);
                  }
  
                  @Override
                  public String toString() {
                      return "(nodeId " + nodeId.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof NodePath) {
              final NodePath nodePath = (NodePath)operand;
              final int indexInRow = columns.getSelectorIndex(nodePath.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              if (isLike) {
                  return new ExtractFromRow() {
                      @Override
                      public TypeFactory<?> getType() {
                          return stringFactory;
                      }
  
                      @Override
                      public Object getValueInRow( RowAccessor row ) {
                          CachedNode node = row.getNode(indexInRow);
                          if (node == null) return null;
                          Path path = node.getPath(cache);
                          if (path.isRoot()) {
                              return stringFactory.create(path);
                          }
                          // And the path that always has the SNS index ...
                          StringBuilder sb = new StringBuilder();
                          for (Path.Segment segment : path) {
                              // Add the segment WITH the index ...
                              sb.append("/");
                              sb.append(stringFactory.create(segment.getName()));
                              sb.append('[').append(segment.getIndex()).append(']');
                          }
                          return sb.toString();
                      }
  
                      @Override
                      public String toString() {
                          return "(nodePath " + nodePath.getSelectorName() + ")";
                      }
                  };
              }
              // Otherwise, just return the single path ...
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      Path path = node.getPath(cache);
                      return stringFactory.create(path);
                  }
  
                  @Override
                  public String toString() {
                      return "(nodePath " + nodePath.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof NodeName) {
              final NodeName nodeName = (NodeName)operand;
              final int indexInRow = columns.getSelectorIndex(nodeName.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      Name name = node.getName(cache);
                      return stringFactory.create(name);
                  }
  
                  @Override
                  public String toString() {
                      return "(nodeName " + nodeName.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof NodeLocalName) {
              final NodeLocalName nodeName = (NodeLocalName)operand;
              final int indexInRow = columns.getSelectorIndex(nodeName.getSelectorName());
              final NodeCache cache = context.getNodeCache(sources.getWorkspaceName());
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return stringFactory;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      CachedNode node = row.getNode(indexInRow);
                      if (node == null) return null;
                      Name name = node.getName(cache);
                      return name.getLocalName(); // works even for root
                  }
  
                  @Override
                  public String toString() {
                      return "(localName " + nodeName.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof FullTextSearchScore) {
              final FullTextSearchScore fts = (FullTextSearchScore)operand;
              final int indexInRow = columns.getSelectorIndex(fts.getSelectorName());
              final TypeFactory<Double> doubleType = context.getTypeSystem().getDoubleFactory();
              return new ExtractFromRow() {
                  @Override
                  public TypeFactory<?> getType() {
                      return doubleType;
                  }
  
                  @Override
                  public Object getValueInRow( RowAccessor row ) {
                      return new Double(row.getScore(indexInRow)); // must convert the float to a double value
                  }
  
                  @Override
                  public String toString() {
                      return "(fullTextScore " + fts.getSelectorName() + ")";
                  }
              };
          }
          if (operand instanceof ArithmeticOperand) {
              // This works on single-valued properties only ...
              ArithmeticOperand arith = (ArithmeticOperand)operand;
              final ExtractFromRow leftOp = createExtractFromRow(arith.getLeft(), context, columns, sources, defaultType, false,
                                                                 false);
              final ExtractFromRow rightOp = createExtractFromRow(arith.getRight(), context, columns, sources, defaultType, false,
                                                                  false);
              // compute the expected (common) type ...
              TypeFactory<?> leftType = leftOp.getType();
              TypeFactory<?> rightType = rightOp.getType();
              final TypeSystem typeSystem = context.getTypeSystem();
              final String commonType = typeSystem.getCompatibleType(leftType.getTypeName(), rightType.getTypeName());
              if (typeSystem.getDoubleFactory().getTypeName().equals(commonType)) {
                  final TypeFactory<Double> commonTypeFactory = typeSystem.getDoubleFactory();
                  switch (arith.operator()) {
                      case ADD:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Double right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Double left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null) return left;
                                  if (left == null) return right;
                                  return left.doubleValue() / right.doubleValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(double + " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case SUBTRACT:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Double right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Double left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null) return left;
                                  if (left == null) left = 0.0d;
                                  return left.doubleValue() * right.doubleValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(double - " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case MULTIPLY:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Double right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Double left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null || left == null) return null;
                                  return left.doubleValue() * right.doubleValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(double x " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case DIVIDE:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Double right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Double left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null || left == null) return null;
                                  return left.doubleValue() / right.doubleValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(double / " + leftOp + "," + rightOp + ")";
                              }
                          };
                  }
              } else if (typeSystem.getLongFactory().getTypeName().equals(commonType)) {
                  final TypeFactory<Long> commonTypeFactory = typeSystem.getLongFactory();
                  switch (arith.operator()) {
                      case ADD:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Long right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Long left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null) return left;
                                  if (left == null) return right;
                                  return left.longValue() / right.longValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(long + " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case SUBTRACT:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Long right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Long left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null) return left;
                                  if (left == null) left = 0L;
                                  return left.longValue() * right.longValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(long - " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case MULTIPLY:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Long right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Long left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null || left == null) return null;
                                  return left.longValue() * right.longValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(long x " + leftOp + "," + rightOp + ")";
                              }
                          };
                      case DIVIDE:
                          return new ExtractFromRow() {
                              @Override
                              public TypeFactory<?> getType() {
                                  return commonTypeFactory;
                              }
  
                              @Override
                              public Object getValueInRow( RowAccessor row ) {
                                  Long right = commonTypeFactory.create(rightOp.getValueInRow(row));
                                  Long left = commonTypeFactory.create(leftOp.getValueInRow(row));
                                  if (right == null || left == null) return null;
                                  return left.longValue() / right.longValue();
                              }
  
                              @Override
                              public String toString() {
                                  return "(long / " + leftOp + "," + rightOp + ")";
                              }
                          };
                  }
              }
          }
          assert false;
          return null;
      }