public RoutineExpression(
     Routine routine,
     List<ExpressionNode> operands,
     DataTypeDescriptor sqlType,
     ValueNode sqlSource,
     TInstance type) {
   super(sqlType, sqlSource, type);
   this.routine = routine;
   this.operands = operands;
   if (routine.getParameters().size() != operands.size())
     throw new WrongExpressionArityException(routine.getParameters().size(), operands.size());
 }
 private TPreparedExpression assembleRoutine(
     ExpressionNode routineNode,
     Routine routine,
     List<ExpressionNode> operandNodes,
     ColumnExpressionContext columnContext,
     SubqueryOperatorAssembler subqueryAssembler) {
   List<TPreparedExpression> inputs =
       assembleExpressions(operandNodes, columnContext, subqueryAssembler);
   switch (routine.getCallingConvention()) {
     case JAVA:
       return new ServerJavaMethodTExpression(routine, inputs);
     case SCRIPT_FUNCTION_JAVA:
     case SCRIPT_FUNCTION_JSON:
       return new ScriptFunctionJavaRoutineTExpression(routine, inputs);
     case SCRIPT_BINDINGS:
     case SCRIPT_BINDINGS_JSON:
       return new ScriptBindingsRoutineTExpression(routine, inputs);
     default:
       throw new AkibanInternalException("Unimplemented routine " + routine.getName());
   }
 }
 @Override
 public String toString() {
   StringBuilder str = new StringBuilder(routine.getName().toString());
   str.append("(");
   boolean first = true;
   for (ExpressionNode operand : operands) {
     if (first) first = false;
     else str.append(",");
     str.append(operand);
   }
   str.append(")");
   return str.toString();
 }
 @Override
 public int hashCode() {
   int hash = routine.getName().hashCode();
   hash += operands.hashCode();
   return hash;
 }
 @Override
 public boolean equals(Object obj) {
   if (!(obj instanceof RoutineExpression)) return false;
   RoutineExpression other = (RoutineExpression) obj;
   return (routine.equals(other.routine) && operands.equals(other.operands));
 }