You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dp...@apache.org on 2017/08/26 02:14:34 UTC

[1/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x 83a32ff13 -> bd587e1f7


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
deleted file mode 100644
index 690e2fd..0000000
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualsEvaluatorTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.stream.eval;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
-import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
-import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-import org.junit.Test;
-
-import junit.framework.Assert;
-
-public class EqualsEvaluatorTest extends LuceneTestCase {
-
-  StreamFactory factory;
-  Map<String, Object> values;
-  
-  public EqualsEvaluatorTest() {
-    super();
-    
-    factory = new StreamFactory()
-      .withFunctionName("eq", EqualsEvaluator.class)
-      .withFunctionName("val", RawValueEvaluator.class)
-      ;
-    values = new HashMap<String,Object>();
-  }
-    
-  @Test
-  public void operationFieldName() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(sum(a),val(9))");    
-    Object result;
-    
-    values.clear();
-    values.put("sum(a)", 9);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-
-  }
-  
-  @Test
-  public void eqTwoIntegers() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    Object result;
-    
-    values.clear();
-    values.put("a", 1);
-    values.put("b", 1);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", 1);
-    values.put("b", 1.0);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", 1.0);
-    values.put("b", 1);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", 1);
-    values.put("b", 2);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-    
-    values.clear();
-    values.put("a", -1);
-    values.put("b", -1);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", 1);
-    values.put("b", 2.0);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-    
-    values.clear();
-    values.put("a", 1.0);
-    values.put("b", 2);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-  }
-
-  @Test
-  public void eqTwoStrings() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    Object result;
-    String foo = "foo";
-    String bar = "bar";
-    
-    values.clear();
-    values.put("a", "foo");
-    values.put("b", "foo");
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", "foo");
-    values.put("b", "bar");
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, false);
-    
-    values.clear();
-    values.put("a", "foo bar baz");
-    values.put("b", "foo bar baz");
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", "foo bar baz");
-    values.put("b", "foo bar jaz");
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-    
-    values.clear();
-    values.put("a", foo);
-    values.put("b", foo);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", foo);
-    values.put("b", bar);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-  }
-  
-  @Test
-  public void eqTwoBooleans() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    Object result;
-    Boolean t = true;
-    Boolean f = false;
-    
-    values.clear();
-    values.put("a", true);
-    values.put("b",true);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", false);
-    values.put("b",false);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", true);
-    values.put("b", false);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, false);
-    
-    values.clear();
-    values.put("a", false);
-    values.put("b", true);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-    
-    values.clear();
-    values.put("a", t);
-    values.put("b", f);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(false, result);
-    
-    values.clear();
-    values.put("a", t);
-    values.put("b", t);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-    
-    values.clear();
-    values.put("a", f);
-    values.put("b", f);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Boolean);
-    Assert.assertEquals(true, result);
-  }
-
-  @Test(expected = IOException.class)
-  public void eqDifferentTypes1() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    
-    values.clear();
-    values.put("a", true);
-    values.put("b",1);
-    evaluator.evaluate(new Tuple(values));
-  }
-
-  @Test(expected = IOException.class)
-  public void eqDifferentTypes2() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    
-    values.clear();
-    values.put("a", 1);
-    values.put("b",false);
-    evaluator.evaluate(new Tuple(values));
-  }
-  
-  @Test(expected = IOException.class)
-  public void eqDifferentTypes3() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    
-    values.clear();
-    values.put("a", "1");
-    values.put("b",1);
-    evaluator.evaluate(new Tuple(values));
-  }
-  
-  @Test(expected = IOException.class)
-  public void eqDifferentTypes4() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
-    
-    values.clear();
-    values.put("a", "true");
-    values.put("b",true);
-    evaluator.evaluate(new Tuple(values));
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldEvaluatorTest.java
deleted file mode 100644
index b1c67c7..0000000
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldEvaluatorTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.stream.eval;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.PriorityQueue;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.eval.FieldEvaluator;
-import org.junit.Test;
-
-import junit.framework.Assert;
-
-public class FieldEvaluatorTest extends LuceneTestCase {
-
-  Map<String, Object> values;
-  
-  public FieldEvaluatorTest() {
-    super();
-    
-    values = new HashMap<String,Object>();
-  }
-    
-  @SuppressWarnings("serial")
-  @Test
-  public void listTypes() throws Exception{
-    values.clear();
-    values.put("a", new ArrayList<Boolean>(){{ add(true); add(false); }});
-    values.put("b", new ArrayList<Double>(){{ add(0.0); add(1.1); }});
-    values.put("c", new ArrayList<Integer>(){{ add(0); add(1); }});
-    values.put("d", new ArrayList<Long>(){{ add(0L); add(1L); }});
-    values.put("e", new ArrayList<String>(){{ add("first"); add("second"); }});
-    
-    Tuple tuple = new Tuple(values);
-    
-    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
-      Assert.assertTrue(new FieldEvaluator(fieldName).evaluate(tuple) instanceof Collection);
-      Assert.assertEquals(2, ((Collection<?>)new FieldEvaluator(fieldName).evaluate(tuple)).size());
-    }
-    
-    Assert.assertEquals(false, ((Collection<?>)new FieldEvaluator("a").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1.1, ((Collection<?>)new FieldEvaluator("b").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1, ((Collection<?>)new FieldEvaluator("c").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1L, ((Collection<?>)new FieldEvaluator("d").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals("second", ((Collection<?>)new FieldEvaluator("e").evaluate(tuple)).toArray()[1]);
-  }
-  
-  @Test
-  public void arrayTypes() throws Exception{
-    values.clear();
-    values.put("a", new Boolean[]{ true, false });
-    values.put("b", new Double[]{ 0.0, 1.1 });
-    values.put("c", new Integer[]{ 0, 1 });
-    values.put("d", new Long[]{ 0L, 1L });
-    values.put("e", new String[]{ "first", "second" });
-    
-    Tuple tuple = new Tuple(values);
-    
-    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
-      Assert.assertTrue(new FieldEvaluator(fieldName).evaluate(tuple) instanceof Collection);
-      Assert.assertEquals(2, ((Collection<?>)new FieldEvaluator(fieldName).evaluate(tuple)).size());
-    }
-    
-    Assert.assertEquals(false, ((Collection<?>)new FieldEvaluator("a").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1.1, ((Collection<?>)new FieldEvaluator("b").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1, ((Collection<?>)new FieldEvaluator("c").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1L, ((Collection<?>)new FieldEvaluator("d").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals("second", ((Collection<?>)new FieldEvaluator("e").evaluate(tuple)).toArray()[1]);
-  }
-  
-  @SuppressWarnings("serial")
-  @Test
-  public void iterableTypes() throws Exception{
-    values.clear();
-    
-    values.put("a", new PriorityQueue<Boolean>(){{ add(true); add(false); }});
-    values.put("b", new PriorityQueue<Double>(){{ add(0.0); add(1.1); }});
-    values.put("c", new PriorityQueue<Integer>(){{ add(0); add(1); }});
-    values.put("d", new PriorityQueue<Long>(){{ add(0L); add(1L); }});
-    values.put("e", new PriorityQueue<String>(){{ add("first"); add("second"); }});
-    
-    Tuple tuple = new Tuple(values);
-    
-    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
-      Assert.assertTrue(new FieldEvaluator(fieldName).evaluate(tuple) instanceof Collection);
-      Assert.assertEquals(2, ((Collection<?>)new FieldEvaluator(fieldName).evaluate(tuple)).size());
-    }
-    
-    // the priority queue is doing natural ordering, so false is first
-    Assert.assertEquals(true, ((Collection<?>)new FieldEvaluator("a").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1.1, ((Collection<?>)new FieldEvaluator("b").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1, ((Collection<?>)new FieldEvaluator("c").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals(1L, ((Collection<?>)new FieldEvaluator("d").evaluate(tuple)).toArray()[1]);
-    Assert.assertEquals("second", ((Collection<?>)new FieldEvaluator("e").evaluate(tuple)).toArray()[1]);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldValueEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldValueEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldValueEvaluatorTest.java
new file mode 100644
index 0000000..d432ff3
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/FieldValueEvaluatorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PriorityQueue;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.FieldValueEvaluator;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class FieldValueEvaluatorTest extends LuceneTestCase {
+
+  Map<String, Object> values;
+  
+  public FieldValueEvaluatorTest() {
+    super();
+    
+    values = new HashMap<String,Object>();
+  }
+    
+  @SuppressWarnings("serial")
+  @Test
+  public void listTypes() throws Exception{
+    values.clear();
+    values.put("a", new ArrayList<Boolean>(){{ add(true); add(false); }});
+    values.put("b", new ArrayList<Double>(){{ add(0.0); add(1.1); }});
+    values.put("c", new ArrayList<Integer>(){{ add(0); add(1); }});
+    values.put("d", new ArrayList<Long>(){{ add(0L); add(1L); }});
+    values.put("e", new ArrayList<String>(){{ add("first"); add("second"); }});
+    
+    Tuple tuple = new Tuple(values);
+    
+    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
+      Assert.assertTrue(new FieldValueEvaluator(fieldName).evaluate(tuple) instanceof Collection);
+      Assert.assertEquals(2, ((Collection<?>)new FieldValueEvaluator(fieldName).evaluate(tuple)).size());
+    }
+    
+    Assert.assertEquals(false, ((Collection<?>)new FieldValueEvaluator("a").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1.1, ((Collection<?>)new FieldValueEvaluator("b").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1, ((Collection<?>)new FieldValueEvaluator("c").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1L, ((Collection<?>)new FieldValueEvaluator("d").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals("second", ((Collection<?>)new FieldValueEvaluator("e").evaluate(tuple)).toArray()[1]);
+  }
+  
+  @Test
+  public void arrayTypes() throws Exception{
+    values.clear();
+    values.put("a", new Boolean[]{ true, false });
+    values.put("b", new Double[]{ 0.0, 1.1 });
+    values.put("c", new Integer[]{ 0, 1 });
+    values.put("d", new Long[]{ 0L, 1L });
+    values.put("e", new String[]{ "first", "second" });
+    
+    Tuple tuple = new Tuple(values);
+    
+    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
+      Assert.assertTrue(new FieldValueEvaluator(fieldName).evaluate(tuple) instanceof Collection);
+      Assert.assertEquals(2, ((Collection<?>)new FieldValueEvaluator(fieldName).evaluate(tuple)).size());
+    }
+    
+    Assert.assertEquals(false, ((Collection<?>)new FieldValueEvaluator("a").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1.1, ((Collection<?>)new FieldValueEvaluator("b").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1, ((Collection<?>)new FieldValueEvaluator("c").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1L, ((Collection<?>)new FieldValueEvaluator("d").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals("second", ((Collection<?>)new FieldValueEvaluator("e").evaluate(tuple)).toArray()[1]);
+  }
+  
+  @SuppressWarnings("serial")
+  @Test
+  public void iterableTypes() throws Exception{
+    values.clear();
+    
+    values.put("a", new PriorityQueue<Boolean>(){{ add(true); add(false); }});
+    values.put("b", new PriorityQueue<Double>(){{ add(0.0); add(1.1); }});
+    values.put("c", new PriorityQueue<Integer>(){{ add(0); add(1); }});
+    values.put("d", new PriorityQueue<Long>(){{ add(0L); add(1L); }});
+    values.put("e", new PriorityQueue<String>(){{ add("first"); add("second"); }});
+    
+    Tuple tuple = new Tuple(values);
+    
+    for(String fieldName : new String[]{ "a", "b", "c", "d", "e" }){
+      Assert.assertTrue(new FieldValueEvaluator(fieldName).evaluate(tuple) instanceof Collection);
+      Assert.assertEquals(2, ((Collection<?>)new FieldValueEvaluator(fieldName).evaluate(tuple)).size());
+    }
+    
+    // the priority queue is doing natural ordering, so false is first
+    Assert.assertEquals(true, ((Collection<?>)new FieldValueEvaluator("a").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1.1, ((Collection<?>)new FieldValueEvaluator("b").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1, ((Collection<?>)new FieldValueEvaluator("c").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals(1L, ((Collection<?>)new FieldValueEvaluator("d").evaluate(tuple)).toArray()[1]);
+    Assert.assertEquals("second", ((Collection<?>)new FieldValueEvaluator("e").evaluate(tuple)).toArray()[1]);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/HyperbolicTangentEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/HyperbolicTangentEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/HyperbolicTangentEvaluatorTest.java
index e526a08..62cc3af 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/HyperbolicTangentEvaluatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/HyperbolicTangentEvaluatorTest.java
@@ -53,8 +53,8 @@ public class HyperbolicTangentEvaluatorTest extends LuceneTestCase {
       Assert.assertNull(result);
     }
     else{
-      Assert.assertTrue(result instanceof Double);
-      Assert.assertEquals(Math.tanh(value), result);
+      Assert.assertTrue(result instanceof Number);
+      Assert.assertEquals(Math.tanh(value), ((Number)result).doubleValue());
     }
   }
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
index ef473fc..e5a1acd 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/MultiplyEvaluatorTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.client.solrj.io.stream.eval;
 
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -69,9 +68,22 @@ public class MultiplyEvaluatorTest extends LuceneTestCase {
     Assert.assertEquals(2.31D, result);
   }
 
-  @Test(expected = IOException.class)
+  @Test
   public void multOneField() throws Exception{
-    factory.constructEvaluator("mult(a)");
+    StreamEvaluator evaluator = factory.constructEvaluator("mult(a)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 6);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(6L, result);
+    
+    values.clear();
+    values.put("a", 6.5);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(6.5D, result);
   }
   
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalDistributionEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalDistributionEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalDistributionEvaluatorTest.java
new file mode 100644
index 0000000..b72f962
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalDistributionEvaluatorTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.NormalDistributionEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class NormalDistributionEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public NormalDistributionEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("norm", NormalDistributionEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    values.clear();
+    values.put("l1", 3);
+    values.put("l2", 7);
+
+    NormalDistribution dist = new NormalDistribution(3,7);
+    Assert.assertEquals(dist.getNumericalMean(),
+        ((NormalDistribution) factory.constructEvaluator("norm(l1,l2)").evaluate(new Tuple(values))).getNumericalMean());
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalizeEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalizeEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalizeEvaluatorTest.java
new file mode 100644
index 0000000..ac90157
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/NormalizeEvaluatorTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.NormalizeEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class NormalizeEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String,Object> values;
+
+  public NormalizeEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("norm", NormalizeEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    
+    int[] ints = new int[]{ 3, 5, 6, 7, 8 };
+    long[] longs = new long[]{ 2L, 3L, 5L, 8L };
+    double[] doubles = new double[]{ 3.4, 4.5, 6.7 };
+    int[] singleInt = new int[]{ 6 };
+    
+    values.clear();
+    values.put("ints", ints);
+    values.put("longs", longs);
+    values.put("doubles", doubles);
+    values.put("singleInt", singleInt);
+    Tuple tuple = new Tuple(values);
+
+    assertSimilar(StatUtils.normalize(Arrays.stream(ints).mapToDouble(Double::new).toArray()), factory.constructEvaluator("norm(ints)").evaluate(tuple));
+    assertSimilar(StatUtils.normalize(Arrays.stream(longs).mapToDouble(Double::new).toArray()), factory.constructEvaluator("norm(longs)").evaluate(tuple));
+    assertSimilar(StatUtils.normalize(doubles), factory.constructEvaluator("norm(doubles)").evaluate(tuple));
+    assertSimilar(StatUtils.normalize(Arrays.stream(singleInt).mapToDouble(Double::new).toArray()), factory.constructEvaluator("norm(singleInt)").evaluate(tuple));
+  }
+  
+  private void assertSimilar(double[] expected, Object actual){
+    assertTrue(actual instanceof List<?>);
+    assertEquals(0, ((List<?>)actual).stream().filter(item -> !(item instanceof Number)).count());
+    
+    double[] actualD = ((List<?>)actual).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray();
+    Assert.assertTrue(Arrays.equals(expected, actualD));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RecursiveEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RecursiveEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RecursiveEvaluatorTest.java
new file mode 100644
index 0000000..8e4bbdb
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RecursiveEvaluatorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class RecursiveEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public RecursiveEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("and", AndEvaluator.class)
+      .withFunctionName("gt", GreaterThanEvaluator.class)
+      .withFunctionName("lt", LessThanEvaluator.class)
+      .withFunctionName("add", AddEvaluator.class)
+      .withFunctionName("sub", SubtractEvaluator.class)
+      .withFunctionName("mult", MultiplyEvaluator.class)
+      .withFunctionName("if", IfThenElseEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+    
+  @Test
+  public void compoundTest() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("add(mult(a,b),if(gt(c,d),e,f),g)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 10);
+    values.put("b", -3);
+    values.put("c", "foo");
+    values.put("d", "bar");
+    values.put("e", 9);
+    values.put("f", 2);
+    values.put("g", 5);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Long);
+    Assert.assertEquals(-16L, result);
+    
+    values.clear();
+    values.put("a", .1);
+    values.put("b", -3);
+    values.put("c", "foo");
+    values.put("d", "bar");
+    values.put("e", 9);
+    values.put("f", 2);
+    values.put("g", 5);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Double);
+    Assert.assertEquals(13.7, result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RegressionEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RegressionEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RegressionEvaluatorTest.java
new file mode 100644
index 0000000..25924ee
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/RegressionEvaluatorTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.stat.regression.SimpleRegression;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.RegressionEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class RegressionEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String,Object> values;
+
+  public RegressionEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("regress", RegressionEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    double[] l1 = new double[] {3.4, 4.5, 6.7};
+    double[] l2 = new double[] {1.2, 3.2, 3};
+    
+    values.clear();
+    values.put("l1", l1);
+    values.put("l2", l2);
+
+    Tuple result = (Tuple)factory.constructEvaluator("regress(l1,l2)").evaluate(new Tuple(values));
+    
+    SimpleRegression regression = new SimpleRegression();
+    regression.addData(l1[0],l2[0]);
+    regression.addData(l1[1],l2[1]);
+    regression.addData(l1[2],l2[2]);
+    
+    Assert.assertEquals(result.getDouble("slope"), regression.getSlope());
+    
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ReverseEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ReverseEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ReverseEvaluatorTest.java
new file mode 100644
index 0000000..3daf16f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ReverseEvaluatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.ReverseEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class ReverseEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public ReverseEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("reverse", ReverseEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    double[] l1 = new double[] {3.4, 6.7, 4.5};
+    
+    values.clear();
+    values.put("l1", l1);
+
+    List result = ((List<?>)factory.constructEvaluator("reverse(l1)").evaluate(new Tuple(values)));
+
+    Assert.assertEquals(4.5, result.get(0));
+    Assert.assertEquals(6.7, result.get(1));
+    Assert.assertEquals(3.4, result.get(2));
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/TemporalEvaluatorsTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/TemporalEvaluatorsTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/TemporalEvaluatorsTest.java
index 8205cea..421c6b0 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/TemporalEvaluatorsTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/TemporalEvaluatorsTest.java
@@ -32,6 +32,7 @@ import java.util.TimeZone;
 
 import org.apache.commons.collections.map.HashedMap;
 import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDay;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfQuarter;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfYear;
@@ -39,7 +40,6 @@ import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorEpoch;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorHour;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMinute;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMonth;
-import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorQuarter;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorSecond;
 import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorWeek;
@@ -165,30 +165,30 @@ public class TemporalEvaluatorsTest {
   public void testAllFunctions() throws Exception {
 
     //year, month, day, dayofyear, hour, minute, quarter, week, second, epoch
-    testFunction("year(a)", "1995-12-31T23:59:59Z", 1995);
-    testFunction("month(a)","1995-12-31T23:59:59Z", 12);
-    testFunction("day(a)",  "1995-12-31T23:59:59Z", 31);
-    testFunction("dayOfYear(a)",  "1995-12-31T23:59:59Z", 365);
-    testFunction("dayOfQuarter(a)",  "1995-12-31T23:59:59Z", 92);
-    testFunction("hour(a)",   "1995-12-31T23:59:59Z", 23);
-    testFunction("minute(a)", "1995-12-31T23:59:59Z", 59);
-    testFunction("quarter(a)","1995-12-31T23:59:59Z", 4);
-    testFunction("week(a)",   "1995-12-31T23:59:59Z", 52);
-    testFunction("second(a)", "1995-12-31T23:59:58Z", 58);
+    testFunction("year(a)", "1995-12-31T23:59:59Z", 1995L);
+    testFunction("month(a)","1995-12-31T23:59:59Z", 12L);
+    testFunction("day(a)",  "1995-12-31T23:59:59Z", 31L);
+    testFunction("dayOfYear(a)",  "1995-12-31T23:59:59Z", 365L);
+    testFunction("dayOfQuarter(a)",  "1995-12-31T23:59:59Z", 92L);
+    testFunction("hour(a)",   "1995-12-31T23:59:59Z", 23L);
+    testFunction("minute(a)", "1995-12-31T23:59:59Z", 59L);
+    testFunction("quarter(a)","1995-12-31T23:59:59Z", 4L);
+    testFunction("week(a)",   "1995-12-31T23:59:59Z", 52L);
+    testFunction("second(a)", "1995-12-31T23:59:58Z", 58L);
     testFunction("epoch(a)",  "1995-12-31T23:59:59Z", 820454399000l);
 
-    testFunction("year(a)", "2017-03-17T10:30:45Z", 2017);
-    testFunction("year('a')", "2017-03-17T10:30:45Z", 2017);
-    testFunction("month(a)","2017-03-17T10:30:45Z", 3);
-    testFunction("day(a)",  "2017-03-17T10:30:45Z", 17);
-    testFunction("day('a')",  "2017-03-17T10:30:45Z", 17);
-    testFunction("dayOfYear(a)",  "2017-03-17T10:30:45Z", 76);
-    testFunction("dayOfQuarter(a)",  "2017-03-17T10:30:45Z", 76);
-    testFunction("hour(a)",   "2017-03-17T10:30:45Z", 10);
-    testFunction("minute(a)", "2017-03-17T10:30:45Z", 30);
-    testFunction("quarter(a)","2017-03-17T10:30:45Z", 1);
-    testFunction("week(a)",   "2017-03-17T10:30:45Z", 11);
-    testFunction("second(a)", "2017-03-17T10:30:45Z", 45);
+    testFunction("year(a)", "2017-03-17T10:30:45Z", 2017L);
+    testFunction("year('a')", "2017-03-17T10:30:45Z", 2017L);
+    testFunction("month(a)","2017-03-17T10:30:45Z", 3L);
+    testFunction("day(a)",  "2017-03-17T10:30:45Z", 17L);
+    testFunction("day('a')",  "2017-03-17T10:30:45Z", 17L);
+    testFunction("dayOfYear(a)",  "2017-03-17T10:30:45Z", 76L);
+    testFunction("dayOfQuarter(a)",  "2017-03-17T10:30:45Z", 76L);
+    testFunction("hour(a)",   "2017-03-17T10:30:45Z", 10L);
+    testFunction("minute(a)", "2017-03-17T10:30:45Z", 30L);
+    testFunction("quarter(a)","2017-03-17T10:30:45Z", 1L);
+    testFunction("week(a)",   "2017-03-17T10:30:45Z", 11L);
+    testFunction("second(a)", "2017-03-17T10:30:45Z", 45L);
     testFunction("epoch(a)",  "2017-03-17T10:30:45Z", 1489746645000l);
 
     testFunction("epoch(a)",  new Date(1489746645500l).toInstant().toString(), 1489746645500l);
@@ -287,7 +287,7 @@ public class TemporalEvaluatorsTest {
     values.put("a", value);
     Object result = evaluator.evaluate(new Tuple(values));
     assertTrue(result instanceof Number);
-    assertEquals(expected, result);
+    assertEquals(expected.longValue(), result);
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/UniformDistributionEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/UniformDistributionEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/UniformDistributionEvaluatorTest.java
new file mode 100644
index 0000000..f7c474e
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/UniformDistributionEvaluatorTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.UniformDistributionEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class UniformDistributionEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public UniformDistributionEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("unif", UniformDistributionEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+
+    values.clear();
+    values.put("l1", 3);
+    values.put("l2", 7);
+
+    UniformRealDistribution dist = new UniformRealDistribution(3,7);
+    Assert.assertEquals(dist.getNumericalMean(),
+        ((UniformRealDistribution) factory.constructEvaluator("unif(l1,l2)").evaluate(new Tuple(values))).getNumericalMean());
+  }
+    
+}


[6/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Posted by dp...@apache.org.
SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/bd587e1f
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/bd587e1f
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/bd587e1f

Branch: refs/heads/branch_7x
Commit: bd587e1f719df9cdf922ec6ea46e7e7d23a86364
Parents: 83a32ff
Author: Dennis Gove <dp...@gmail.com>
Authored: Tue Aug 22 07:30:35 2017 -0400
Committer: Dennis Gove <dp...@gmail.com>
Committed: Fri Aug 25 22:12:02 2017 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   5 +
 .../apache/solr/handler/AnalyzeEvaluator.java   |   5 +-
 .../org/apache/solr/handler/StreamHandler.java  | 139 +++++++++-
 .../org/apache/solr/handler/sql/SolrTable.java  |  12 +-
 .../solrj/io/eval/AbsoluteValueEvaluator.java   |  32 +--
 .../client/solrj/io/eval/AddAllEvaluator.java   |  69 -----
 .../solr/client/solrj/io/eval/AddEvaluator.java |  48 ++--
 .../solr/client/solrj/io/eval/AndEvaluator.java |  47 +---
 .../client/solrj/io/eval/AnovaEvaluator.java    |  69 ++---
 .../client/solrj/io/eval/AppendEvaluator.java   |  53 ++++
 .../solrj/io/eval/ArcCosineEvaluator.java       |  33 +--
 .../client/solrj/io/eval/ArcSineEvaluator.java  |  33 +--
 .../solrj/io/eval/ArcTangentEvaluator.java      |  33 +--
 .../client/solrj/io/eval/ArrayEvaluator.java    |  85 ++----
 .../solr/client/solrj/io/eval/AscEvaluator.java |  75 +++---
 .../client/solrj/io/eval/BooleanEvaluator.java  |  83 ------
 .../client/solrj/io/eval/CeilingEvaluator.java  |  36 ++-
 .../client/solrj/io/eval/CoalesceEvaluator.java |  21 +-
 .../client/solrj/io/eval/ColumnEvaluator.java   |  83 +++---
 .../client/solrj/io/eval/ComplexEvaluator.java  | 126 ---------
 .../solrj/io/eval/ConditionalEvaluator.java     |  56 ----
 .../solrj/io/eval/ConversionEvaluator.java      | 144 +++++-----
 .../solrj/io/eval/ConvolutionEvaluator.java     |  77 ++----
 .../client/solrj/io/eval/CopyOfEvaluator.java   |  85 +++---
 .../solrj/io/eval/CopyOfRangeEvaluator.java     | 104 ++++----
 .../solrj/io/eval/CorrelationEvaluator.java     |  70 ++---
 .../client/solrj/io/eval/CosineEvaluator.java   |  33 +--
 .../solrj/io/eval/CovarianceEvaluator.java      |  70 ++---
 .../solrj/io/eval/CubedRootEvaluator.java       |  33 +--
 .../io/eval/CumulativeProbabilityEvaluator.java |  61 ++---
 .../client/solrj/io/eval/DescribeEvaluator.java |  63 ++---
 .../client/solrj/io/eval/DistanceEvaluator.java |  82 ------
 .../client/solrj/io/eval/DivideEvaluator.java   |  44 ++--
 .../io/eval/EmpiricalDistributionEvaluator.java |  56 ++--
 .../client/solrj/io/eval/EqualToEvaluator.java  |  73 +++++
 .../client/solrj/io/eval/EqualsEvaluator.java   | 109 --------
 .../io/eval/EuclideanDistanceEvaluator.java     |  56 ++++
 .../solrj/io/eval/EvaluatorException.java       |   4 +-
 .../solrj/io/eval/ExclusiveOrEvaluator.java     |  42 +--
 .../client/solrj/io/eval/FieldEvaluator.java    | 100 -------
 .../solrj/io/eval/FieldValueEvaluator.java      | 100 +++++++
 .../solrj/io/eval/FindDelayEvaluator.java       |  98 +++----
 .../client/solrj/io/eval/FloorEvaluator.java    |  36 ++-
 .../io/eval/GreaterThanEqualToEvaluator.java    |  45 +---
 .../solrj/io/eval/GreaterThanEvaluator.java     |  45 +---
 .../solrj/io/eval/HistogramEvaluator.java       | 108 ++++----
 .../io/eval/HyperbolicCosineEvaluator.java      |  33 +--
 .../solrj/io/eval/HyperbolicSineEvaluator.java  |  33 +--
 .../io/eval/HyperbolicTangentEvaluator.java     |  33 +--
 .../solrj/io/eval/IfThenElseEvaluator.java      |  31 +--
 .../io/eval/KolmogorovSmirnovEvaluator.java     |  93 +++----
 .../client/solrj/io/eval/LengthEvaluator.java   |  56 ++--
 .../solrj/io/eval/LessThanEqualToEvaluator.java |  45 +---
 .../client/solrj/io/eval/LessThanEvaluator.java |  45 +---
 .../client/solrj/io/eval/ManyValueWorker.java   |  25 ++
 .../client/solrj/io/eval/ModuloEvaluator.java   |  42 +--
 .../solrj/io/eval/MovingAverageEvaluator.java   |  82 +++---
 .../client/solrj/io/eval/MultiplyEvaluator.java |  49 ++--
 .../solrj/io/eval/NaturalLogEvaluator.java      |  35 ++-
 .../io/eval/NormalDistributionEvaluator.java    |  45 +---
 .../solrj/io/eval/NormalizeEvaluator.java       |  65 ++---
 .../solr/client/solrj/io/eval/NotEvaluator.java |  45 ++--
 .../client/solrj/io/eval/NumberEvaluator.java   |  89 -------
 .../client/solrj/io/eval/OneValueWorker.java    |  34 +++
 .../solr/client/solrj/io/eval/OrEvaluator.java  |  47 +---
 .../solrj/io/eval/PercentileEvaluator.java      |  69 ++---
 .../client/solrj/io/eval/PowerEvaluator.java    |  29 +-
 .../client/solrj/io/eval/PredictEvaluator.java  |  79 ++----
 .../client/solrj/io/eval/RankEvaluator.java     |  66 ++---
 .../client/solrj/io/eval/RawValueEvaluator.java |   2 +-
 .../io/eval/RecursiveBooleanEvaluator.java      | 112 ++++++++
 .../solrj/io/eval/RecursiveEvaluator.java       | 240 +++++++++++++++++
 .../io/eval/RecursiveNumericEvaluator.java      |  75 ++++++
 .../io/eval/RecursiveNumericListEvaluator.java  |  67 +++++
 .../solrj/io/eval/RecursiveObjectEvaluator.java |  35 +++
 .../io/eval/RecursiveTemporalEvaluator.java     | 114 ++++++++
 .../solrj/io/eval/RegressionEvaluator.java      |  87 +++---
 .../solrj/io/eval/ResidualsEvaluator.java       |  99 +++----
 .../client/solrj/io/eval/ReverseEvaluator.java  |  63 ++---
 .../client/solrj/io/eval/RoundEvaluator.java    |  33 +--
 .../client/solrj/io/eval/SampleEvaluator.java   |  59 ++---
 .../client/solrj/io/eval/ScaleEvaluator.java    |  79 ++----
 .../client/solrj/io/eval/SequenceEvaluator.java |  67 ++---
 .../client/solrj/io/eval/SimpleEvaluator.java   |  36 ---
 .../client/solrj/io/eval/SineEvaluator.java     |  33 +--
 .../client/solrj/io/eval/SourceEvaluator.java   |  36 +++
 .../solrj/io/eval/SquareRootEvaluator.java      |  33 +--
 .../client/solrj/io/eval/StreamEvaluator.java   |   2 +-
 .../solrj/io/eval/StreamEvaluatorException.java |  32 +++
 .../client/solrj/io/eval/SubtractEvaluator.java |  65 ++++-
 .../client/solrj/io/eval/TangentEvaluator.java  |  33 +--
 .../client/solrj/io/eval/TemporalEvaluator.java | 136 ----------
 .../solrj/io/eval/TemporalEvaluatorDay.java     |  16 +-
 .../io/eval/TemporalEvaluatorDayOfQuarter.java  |  18 +-
 .../io/eval/TemporalEvaluatorDayOfYear.java     |  18 +-
 .../solrj/io/eval/TemporalEvaluatorEpoch.java   |  22 +-
 .../solrj/io/eval/TemporalEvaluatorHour.java    |  18 +-
 .../solrj/io/eval/TemporalEvaluatorMinute.java  |  18 +-
 .../solrj/io/eval/TemporalEvaluatorMonth.java   |  18 +-
 .../solrj/io/eval/TemporalEvaluatorQuarter.java |  18 +-
 .../solrj/io/eval/TemporalEvaluatorSecond.java  |  18 +-
 .../solrj/io/eval/TemporalEvaluatorWeek.java    |  18 +-
 .../solrj/io/eval/TemporalEvaluatorYear.java    |  18 +-
 .../client/solrj/io/eval/TwoValueWorker.java    |  34 +++
 .../io/eval/UniformDistributionEvaluator.java   |  45 +---
 .../client/solrj/io/eval/UuidEvaluator.java     |  16 +-
 .../solr/client/solrj/io/eval/ValueWorker.java  |  25 ++
 .../solrj/io/stream/CartesianProductStream.java |   4 +-
 .../client/solrj/io/stream/HavingStream.java    |  20 +-
 .../solrj/io/stream/expr/StreamFactory.java     |   8 +-
 .../solrj/io/stream/StreamExpressionTest.java   |  43 +--
 .../solrj/io/stream/eval/AddEvaluatorTest.java  |  20 +-
 .../io/stream/eval/AppendEvaluatorTest.java     |  62 +++++
 .../solrj/io/stream/eval/AscEvaluatorTest.java  | 107 ++++++++
 .../io/stream/eval/CoalesceEvaluatorTest.java   |   4 +-
 .../io/stream/eval/CompoundEvaluatorTest.java   |  85 ------
 .../stream/eval/ConversionEvaluatorsTest.java   |   2 +-
 .../stream/eval/CorrelationEvaluatorTest.java   |  55 ++++
 .../CumulativeProbabilityEvaluatorTest.java     |  56 ++++
 .../EmpiricalDistributionEvaluatorTest.java     |  57 ++++
 .../io/stream/eval/EqualToEvaluatorTest.java    | 263 +++++++++++++++++++
 .../io/stream/eval/EqualsEvaluatorTest.java     | 263 -------------------
 .../io/stream/eval/FieldEvaluatorTest.java      | 114 --------
 .../io/stream/eval/FieldValueEvaluatorTest.java | 114 ++++++++
 .../eval/HyperbolicTangentEvaluatorTest.java    |   4 +-
 .../io/stream/eval/MultiplyEvaluatorTest.java   |  18 +-
 .../eval/NormalDistributionEvaluatorTest.java   |  54 ++++
 .../io/stream/eval/NormalizeEvaluatorTest.java  |  74 ++++++
 .../io/stream/eval/RecursiveEvaluatorTest.java  |  85 ++++++
 .../io/stream/eval/RegressionEvaluatorTest.java |  63 +++++
 .../io/stream/eval/ReverseEvaluatorTest.java    |  57 ++++
 .../io/stream/eval/TemporalEvaluatorsTest.java  |  48 ++--
 .../eval/UniformDistributionEvaluatorTest.java  |  55 ++++
 133 files changed, 3919 insertions(+), 3725 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index b84777e..babd5e1 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -43,6 +43,11 @@ Upgrade Notes
 
 * SOLR-11254: the abstract DocTransformer class now has an abstract score-less transform method variant.
 
+* SOLR-11283: all Stream Evaluators in solrj.io.eval have been refactored to have a simplier and more
+  robust structure. This simplifies and condenses the code required to implement a new Evaluator and 
+  makes it much easier for evaluators to handle differing data types (primitives, objects, arrays,
+  lists, and so forth). (Dennis Gove)
+
 New Features
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/core/src/java/org/apache/solr/handler/AnalyzeEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/AnalyzeEvaluator.java b/solr/core/src/java/org/apache/solr/handler/AnalyzeEvaluator.java
index d5bc70d..4112e04 100644
--- a/solr/core/src/java/org/apache/solr/handler/AnalyzeEvaluator.java
+++ b/solr/core/src/java/org/apache/solr/handler/AnalyzeEvaluator.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.SourceEvaluator;
 import org.apache.solr.client.solrj.io.stream.StreamContext;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
@@ -30,13 +31,11 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-import org.apache.solr.client.solrj.io.eval.*;
-
 import org.apache.solr.common.SolrException;
 import org.apache.lucene.analysis.*;
 import org.apache.solr.core.SolrCore;
 
-public class AnalyzeEvaluator extends SimpleEvaluator {
+public class AnalyzeEvaluator extends SourceEvaluator {
   private static final long serialVersionUID = 1L;
 
   private String fieldName;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
index 399c92b..8b6a6c0 100644
--- a/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/StreamHandler.java
@@ -33,14 +33,140 @@ import org.apache.solr.client.solrj.io.ModelCache;
 import org.apache.solr.client.solrj.io.SolrClientCache;
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
-import org.apache.solr.client.solrj.io.eval.*;
+import org.apache.solr.client.solrj.io.eval.AbsoluteValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.AddEvaluator;
+import org.apache.solr.client.solrj.io.eval.AndEvaluator;
+import org.apache.solr.client.solrj.io.eval.AnovaEvaluator;
+import org.apache.solr.client.solrj.io.eval.AppendEvaluator;
+import org.apache.solr.client.solrj.io.eval.ArcCosineEvaluator;
+import org.apache.solr.client.solrj.io.eval.ArcSineEvaluator;
+import org.apache.solr.client.solrj.io.eval.ArcTangentEvaluator;
+import org.apache.solr.client.solrj.io.eval.ArrayEvaluator;
+import org.apache.solr.client.solrj.io.eval.AscEvaluator;
+import org.apache.solr.client.solrj.io.eval.CeilingEvaluator;
+import org.apache.solr.client.solrj.io.eval.CoalesceEvaluator;
+import org.apache.solr.client.solrj.io.eval.ColumnEvaluator;
+import org.apache.solr.client.solrj.io.eval.ConversionEvaluator;
+import org.apache.solr.client.solrj.io.eval.ConvolutionEvaluator;
+import org.apache.solr.client.solrj.io.eval.CopyOfEvaluator;
+import org.apache.solr.client.solrj.io.eval.CopyOfRangeEvaluator;
+import org.apache.solr.client.solrj.io.eval.CorrelationEvaluator;
+import org.apache.solr.client.solrj.io.eval.CosineEvaluator;
+import org.apache.solr.client.solrj.io.eval.CovarianceEvaluator;
+import org.apache.solr.client.solrj.io.eval.CubedRootEvaluator;
+import org.apache.solr.client.solrj.io.eval.CumulativeProbabilityEvaluator;
+import org.apache.solr.client.solrj.io.eval.DescribeEvaluator;
+import org.apache.solr.client.solrj.io.eval.DivideEvaluator;
+import org.apache.solr.client.solrj.io.eval.EmpiricalDistributionEvaluator;
+import org.apache.solr.client.solrj.io.eval.EqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.EuclideanDistanceEvaluator;
+import org.apache.solr.client.solrj.io.eval.ExclusiveOrEvaluator;
+import org.apache.solr.client.solrj.io.eval.FindDelayEvaluator;
+import org.apache.solr.client.solrj.io.eval.FloorEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.HistogramEvaluator;
+import org.apache.solr.client.solrj.io.eval.HyperbolicCosineEvaluator;
+import org.apache.solr.client.solrj.io.eval.HyperbolicSineEvaluator;
+import org.apache.solr.client.solrj.io.eval.HyperbolicTangentEvaluator;
+import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
+import org.apache.solr.client.solrj.io.eval.KolmogorovSmirnovEvaluator;
+import org.apache.solr.client.solrj.io.eval.LengthEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
+import org.apache.solr.client.solrj.io.eval.ModuloEvaluator;
+import org.apache.solr.client.solrj.io.eval.MovingAverageEvaluator;
+import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
+import org.apache.solr.client.solrj.io.eval.NaturalLogEvaluator;
+import org.apache.solr.client.solrj.io.eval.NormalDistributionEvaluator;
+import org.apache.solr.client.solrj.io.eval.NormalizeEvaluator;
+import org.apache.solr.client.solrj.io.eval.NotEvaluator;
+import org.apache.solr.client.solrj.io.eval.OrEvaluator;
+import org.apache.solr.client.solrj.io.eval.PercentileEvaluator;
+import org.apache.solr.client.solrj.io.eval.PowerEvaluator;
+import org.apache.solr.client.solrj.io.eval.PredictEvaluator;
+import org.apache.solr.client.solrj.io.eval.RankEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.RegressionEvaluator;
+import org.apache.solr.client.solrj.io.eval.ResidualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.ReverseEvaluator;
+import org.apache.solr.client.solrj.io.eval.RoundEvaluator;
+import org.apache.solr.client.solrj.io.eval.SampleEvaluator;
+import org.apache.solr.client.solrj.io.eval.ScaleEvaluator;
+import org.apache.solr.client.solrj.io.eval.SequenceEvaluator;
+import org.apache.solr.client.solrj.io.eval.SineEvaluator;
+import org.apache.solr.client.solrj.io.eval.SquareRootEvaluator;
+import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
+import org.apache.solr.client.solrj.io.eval.TangentEvaluator;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDay;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfQuarter;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorDayOfYear;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorEpoch;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorHour;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMinute;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorMonth;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorQuarter;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorSecond;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorWeek;
+import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorYear;
+import org.apache.solr.client.solrj.io.eval.UniformDistributionEvaluator;
+import org.apache.solr.client.solrj.io.eval.UuidEvaluator;
 import org.apache.solr.client.solrj.io.graph.GatherNodesStream;
 import org.apache.solr.client.solrj.io.graph.ShortestPathStream;
 import org.apache.solr.client.solrj.io.ops.ConcatOperation;
 import org.apache.solr.client.solrj.io.ops.DistinctOperation;
 import org.apache.solr.client.solrj.io.ops.GroupOperation;
 import org.apache.solr.client.solrj.io.ops.ReplaceOperation;
-import org.apache.solr.client.solrj.io.stream.*;
+import org.apache.solr.client.solrj.io.stream.CalculatorStream;
+import org.apache.solr.client.solrj.io.stream.CartesianProductStream;
+import org.apache.solr.client.solrj.io.stream.CellStream;
+import org.apache.solr.client.solrj.io.stream.CloudSolrStream;
+import org.apache.solr.client.solrj.io.stream.CommitStream;
+import org.apache.solr.client.solrj.io.stream.ComplementStream;
+import org.apache.solr.client.solrj.io.stream.DaemonStream;
+import org.apache.solr.client.solrj.io.stream.EchoStream;
+import org.apache.solr.client.solrj.io.stream.EvalStream;
+import org.apache.solr.client.solrj.io.stream.ExceptionStream;
+import org.apache.solr.client.solrj.io.stream.ExecutorStream;
+import org.apache.solr.client.solrj.io.stream.FacetStream;
+import org.apache.solr.client.solrj.io.stream.FeaturesSelectionStream;
+import org.apache.solr.client.solrj.io.stream.FetchStream;
+import org.apache.solr.client.solrj.io.stream.GetStream;
+import org.apache.solr.client.solrj.io.stream.HashJoinStream;
+import org.apache.solr.client.solrj.io.stream.HavingStream;
+import org.apache.solr.client.solrj.io.stream.InnerJoinStream;
+import org.apache.solr.client.solrj.io.stream.IntersectStream;
+import org.apache.solr.client.solrj.io.stream.JDBCStream;
+import org.apache.solr.client.solrj.io.stream.KnnStream;
+import org.apache.solr.client.solrj.io.stream.LeftOuterJoinStream;
+import org.apache.solr.client.solrj.io.stream.LetStream;
+import org.apache.solr.client.solrj.io.stream.ListStream;
+import org.apache.solr.client.solrj.io.stream.MergeStream;
+import org.apache.solr.client.solrj.io.stream.ModelStream;
+import org.apache.solr.client.solrj.io.stream.NullStream;
+import org.apache.solr.client.solrj.io.stream.OuterHashJoinStream;
+import org.apache.solr.client.solrj.io.stream.ParallelStream;
+import org.apache.solr.client.solrj.io.stream.PlotStream;
+import org.apache.solr.client.solrj.io.stream.PriorityStream;
+import org.apache.solr.client.solrj.io.stream.RandomStream;
+import org.apache.solr.client.solrj.io.stream.RankStream;
+import org.apache.solr.client.solrj.io.stream.ReducerStream;
+import org.apache.solr.client.solrj.io.stream.RollupStream;
+import org.apache.solr.client.solrj.io.stream.ScoreNodesStream;
+import org.apache.solr.client.solrj.io.stream.SelectStream;
+import org.apache.solr.client.solrj.io.stream.ShuffleStream;
+import org.apache.solr.client.solrj.io.stream.SignificantTermsStream;
+import org.apache.solr.client.solrj.io.stream.SortStream;
+import org.apache.solr.client.solrj.io.stream.SqlStream;
+import org.apache.solr.client.solrj.io.stream.StatsStream;
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+import org.apache.solr.client.solrj.io.stream.TextLogitStream;
+import org.apache.solr.client.solrj.io.stream.TimeSeriesStream;
+import org.apache.solr.client.solrj.io.stream.TopicStream;
+import org.apache.solr.client.solrj.io.stream.TupStream;
+import org.apache.solr.client.solrj.io.stream.TupleStream;
+import org.apache.solr.client.solrj.io.stream.UniqueStream;
+import org.apache.solr.client.solrj.io.stream.UpdateStream;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
 import org.apache.solr.client.solrj.io.stream.expr.Expressible;
@@ -197,11 +323,11 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
         .withFunctionName("array", ArrayEvaluator.class)
         .withFunctionName("col", ColumnEvaluator.class)
         .withFunctionName("conv", ConvolutionEvaluator.class)
-        .withFunctionName("copyOf", CopyOfEvaluator.class)
         .withFunctionName("copyOfRange", CopyOfRangeEvaluator.class)
+        .withFunctionName("copyOf", CopyOfEvaluator.class)
         .withFunctionName("cov", CovarianceEvaluator.class)
         .withFunctionName("describe", DescribeEvaluator.class)
-        .withFunctionName("distance", DistanceEvaluator.class)
+        .withFunctionName("distance", EuclideanDistanceEvaluator.class)
         .withFunctionName("empiricalDistribution", EmpiricalDistributionEvaluator.class)
         .withFunctionName("finddelay", FindDelayEvaluator.class)
         .withFunctionName("hist", HistogramEvaluator.class)
@@ -215,7 +341,8 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
         .withFunctionName("rev", ReverseEvaluator.class)
         .withFunctionName("scale", ScaleEvaluator.class)
         .withFunctionName("sequence", SequenceEvaluator.class)
-        .withFunctionName("addAll", AddAllEvaluator.class)
+        .withFunctionName("addAll", AppendEvaluator.class)
+        .withFunctionName("append", AppendEvaluator.class)
         .withFunctionName("residuals", ResidualsEvaluator.class)
         .withFunctionName("plot", PlotStream.class)
         .withFunctionName("normalDistribution", NormalDistributionEvaluator.class)
@@ -229,7 +356,7 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
         // Boolean Stream Evaluators
         .withFunctionName("and", AndEvaluator.class)
         .withFunctionName("eor", ExclusiveOrEvaluator.class)
-        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("eq", EqualToEvaluator.class)
         .withFunctionName("gt", GreaterThanEvaluator.class)
         .withFunctionName("gteq", GreaterThanEqualToEvaluator.class)
         .withFunctionName("lt", LessThanEvaluator.class)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java
index f863680..c0b35cf 100644
--- a/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java
+++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrTable.java
@@ -36,8 +36,8 @@ import org.apache.solr.client.solrj.io.eq.FieldEqualitor;
 import org.apache.solr.client.solrj.io.eq.MultipleFieldEqualitor;
 import org.apache.solr.client.solrj.io.eq.StreamEqualitor;
 import org.apache.solr.client.solrj.io.eval.AndEvaluator;
-import org.apache.solr.client.solrj.io.eval.BooleanEvaluator;
-import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.RecursiveBooleanEvaluator;
+import org.apache.solr.client.solrj.io.eval.EqualToEvaluator;
 import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
 import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
 import org.apache.solr.client.solrj.io.eval.LessThanEqualToEvaluator;
@@ -497,7 +497,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
         .withFunctionName("and", AndEvaluator.class)
         .withFunctionName("or", OrEvaluator.class)
         .withFunctionName("not", NotEvaluator.class)
-        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("eq", EqualToEvaluator.class)
         .withFunctionName("gt", GreaterThanEvaluator.class)
         .withFunctionName("lt", LessThanEvaluator.class)
         .withFunctionName("val", RawValueEvaluator.class)
@@ -506,7 +506,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
         .withFunctionName("gteq", GreaterThanEqualToEvaluator.class);
 
     if(havingPredicate != null) {
-      BooleanEvaluator booleanOperation = (BooleanEvaluator)factory.constructEvaluator(StreamExpressionParser.parse(havingPredicate));
+      RecursiveBooleanEvaluator booleanOperation = (RecursiveBooleanEvaluator)factory.constructEvaluator(StreamExpressionParser.parse(havingPredicate));
       tupleStream = new HavingStream(tupleStream, booleanOperation);
     }
 
@@ -633,7 +633,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
         .withFunctionName("and", AndEvaluator.class)
         .withFunctionName("or", OrEvaluator.class)
         .withFunctionName("not", NotEvaluator.class)
-        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("eq", EqualToEvaluator.class)
         .withFunctionName("val", RawValueEvaluator.class)
         .withFunctionName("gt", GreaterThanEvaluator.class)
         .withFunctionName("lt", LessThanEvaluator.class)
@@ -641,7 +641,7 @@ class SolrTable extends AbstractQueryableTable implements TranslatableTable {
         .withFunctionName("gteq", GreaterThanEqualToEvaluator.class);
 
     if(havingPredicate != null) {
-      BooleanEvaluator booleanOperation = (BooleanEvaluator)factory.constructEvaluator(StreamExpressionParser.parse(havingPredicate));
+      RecursiveBooleanEvaluator booleanOperation = (RecursiveBooleanEvaluator)factory.constructEvaluator(StreamExpressionParser.parse(havingPredicate));
       tupleStream = new HavingStream(tupleStream, booleanOperation);
     }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
index 7ad4a16..b5cc1c7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AbsoluteValueEvaluator.java
@@ -21,37 +21,31 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class AbsoluteValueEvaluator extends NumberEvaluator {
+public class AbsoluteValueEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public AbsoluteValueEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return normalizeType(results.get(0).abs());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue));
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.abs(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddAllEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddAllEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddAllEvaluator.java
deleted file mode 100644
index 4370e1b..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddAllEvaluator.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class AddAllEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public AddAllEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-
-    if(subEvaluators.size() == 0) {
-      throw new IOException("addAll evaluator expects atleast one array parameter");
-    }
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    List<Number> all = new ArrayList();
-    for(StreamEvaluator subEvaluator : subEvaluators) {
-      List<Number> numbers = (List<Number>)subEvaluator.evaluate(tuple);
-      all.addAll(numbers);
-    }
-
-    return all;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
index fee7d8c..dbcd6c8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AddEvaluator.java
@@ -18,41 +18,59 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class AddEvaluator extends NumberEvaluator {
+public class AddEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public AddEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 1){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least 1 value but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
+    }
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(0 == values.length){
       return null;
     }
     
-    BigDecimal result = null;
-    if(results.size() > 0){
-      result = results.get(0);
-      for(int idx = 1; idx < results.size(); ++idx){
-        result = result.add(results.get(idx));
-      }
+    BigDecimal result = BigDecimal.ZERO;
+    for(Object value : values){
+      result = add(result, value);
     }
     
-    return normalizeType(result);
+    return result;
   }
+  
+  private BigDecimal add(BigDecimal left, Object right) throws IOException{
+    if(null == left || null == right){
+      return null;
+    }
+    else if(right instanceof BigDecimal){
+      return left.add((BigDecimal)right);
+    }
+    else if(right instanceof Number){
+      return add(left, new BigDecimal(right.toString()));
+    }
+    else if(right instanceof List){
+      return add(left, doWork(((List<?>)right).toArray()));
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", right.getClass().getName(), right.toString());
+    }
+
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
index a4d968b..cab876e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AndEvaluator.java
@@ -17,59 +17,23 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class AndEvaluator extends BooleanEvaluator {
+public class AndEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public AndEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(0), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -77,11 +41,12 @@ public class AndEvaluator extends BooleanEvaluator {
       return new BooleanChecker(){
         @Override
         public boolean test(Object left, Object right) {
-          return (boolean)left && (boolean)right;
+          return (Boolean)left && (Boolean)right;
         }
       };
     }
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AnovaEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AnovaEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AnovaEvaluator.java
index ca962f1..c1d6d93 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AnovaEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AnovaEvaluator.java
@@ -17,66 +17,49 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.stat.inference.OneWayAnova;
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class AnovaEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public AnovaEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class AnovaEvaluator extends RecursiveNumericListEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public AnovaEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
-  public Tuple evaluate(Tuple tuple) throws IOException {
-
-    List<double[]> list = new ArrayList();
-    for(StreamEvaluator subEvaluator : subEvaluators) {
-      List<Number> nums = (List<Number>)subEvaluator.evaluate(tuple);
-      double[] darray = new double[nums.size()];
-      for(int i=0; i< nums.size(); i++) {
-        darray[i]=nums.get(i).doubleValue();
-      }
-      list.add(darray);
-    }
-
+  @Override
+  public Object doWork(Object... values) throws IOException {
+    
+    // at this point we know every incoming value is an array of BigDecimals
+    
+    List<double[]> anovaInput = Arrays.stream(values)
+        // for each List, convert to double[]
+        .map(value -> ((List<BigDecimal>)value).stream().mapToDouble(BigDecimal::doubleValue).toArray())
+        // turn into List<double[]>
+        .collect(Collectors.toList());
+    
     OneWayAnova anova = new OneWayAnova();
-    double p = anova.anovaPValue(list);
-    double f = anova.anovaFValue(list);
-    Map m = new HashMap();
+    double p = anova.anovaPValue(anovaInput);
+    double f = anova.anovaFValue(anovaInput);
+    Map<String,Number> m = new HashMap<>();
     m.put("p-value", p);
     m.put("f-ratio", f);
     return new Tuple(m);
   }
+  
 
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AppendEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AppendEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AppendEvaluator.java
new file mode 100644
index 0000000..58a17a8
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AppendEvaluator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class AppendEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public AppendEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+
+  @Override
+  public Object doWork(Object ... values){
+    List<Object> newList = new ArrayList<>();
+    
+    for(Object value : values){
+      if(null == value){
+        // skip nulls
+        continue;
+      }
+      else if(value instanceof Collection<?>){
+        newList.addAll((Collection<?>)value);
+      }
+      else{
+        newList.add(value);
+      }
+    }
+    
+    return newList;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcCosineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcCosineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcCosineEvaluator.java
index ee76542..7dfe992 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcCosineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcCosineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ArcCosineEvaluator extends NumberEvaluator {
+public class ArcCosineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ArcCosineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.acos(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.acos(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcSineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcSineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcSineEvaluator.java
index 23d860c..0750621 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcSineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcSineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ArcSineEvaluator extends NumberEvaluator {
+public class ArcSineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ArcSineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.asin(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.asin(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcTangentEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcTangentEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcTangentEvaluator.java
index 65e0174..95901ee 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcTangentEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArcTangentEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ArcTangentEvaluator extends NumberEvaluator {
+public class ArcTangentEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ArcTangentEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.atan(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.atan(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArrayEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArrayEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArrayEvaluator.java
index 127f587..67dbf1a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArrayEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ArrayEvaluator.java
@@ -17,88 +17,53 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionNamedParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ArrayEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-  private String sortOrder;
+public class ArrayEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  private Comparator<Comparable> sortComparator;
   
-  public ArrayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+  public ArrayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory, Arrays.asList("sort"));
     
-    sortOrder = extractSortOrder(expression, factory);
-  }
-  
-  private String extractSortOrder(StreamExpression expression, StreamFactory factory) throws IOException{
     StreamExpressionNamedParameter sortParam = factory.getNamedOperand(expression, "sort");
-    if(null == sortParam){
-      return null; // this is ok
-    }
-    
-    if(sortParam.getParameter() instanceof StreamExpressionValue){
+    if(null != sortParam && sortParam.getParameter() instanceof StreamExpressionValue){
       String sortOrder = ((StreamExpressionValue)sortParam.getParameter()).getValue().trim().toLowerCase(Locale.ROOT);
       if("asc".equals(sortOrder) || "desc".equals(sortOrder)){
-        return sortOrder;
+        sortComparator = "asc".equals(sortOrder) ? (left,right) -> left.compareTo(right) : (left,right) -> right.compareTo(left);
       }
-    }
-    
-    throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - invalid 'sort' parameter - expecting either 'asc' or 'desc'", expression));
+      else{
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - invalid 'sort' parameter - expecting either 'asc' or 'desc'", expression));
+      }
+    }    
   }
 
-  public List<Object> evaluate(Tuple tuple) throws IOException {
-    List<Object> list = new ArrayList<>();
-    for(StreamEvaluator subEvaluator : subEvaluators) {
-      Object value = subEvaluator.evaluate(tuple);
-      
-      // if we want sorting but the evaluated value is not comparable then we have an error
-      if(null != sortOrder && !(value instanceof Comparable<?>)){
-        String message = String.format(Locale.ROOT,"Failed to evaluate to a comparable object - evaluator '%s' resulted in type '%s' and value '%s'",
-            subEvaluator.toExpression(constructingFactory),
-            value.getClass().getName(),
-            value.toString());
-        throw new IOException(message);
+  @Override
+  public Object doWork(Object ... values) throws IOException{
+  
+    List<Object> newList = Arrays.stream(values).collect(Collectors.toList());
+    
+    if(null != sortComparator){
+      // validate everything is comparable
+      for(Object value : newList){
+        if(!(value instanceof Comparable<?>)){
+          throw new IOException(String.format(Locale.ROOT, "Unable to evaluate because a non-Comparable value ('%s') was found and sorting was requested", value.toString()));
+        }
       }
       
-      list.add(value);
+      newList = newList.stream().map(value -> (Comparable)value).sorted(sortComparator).collect(Collectors.toList());
     }
     
-    if(null != sortOrder){
-      // Because of the type checking above we know that the value is at least Comparable
-      Comparator<Comparable> comparator = "asc".equals(sortOrder) ? (left,right) -> left.compareTo(right) : (left,right) -> right.compareTo(left);
-      list = list.stream().map(value -> (Comparable<Object>)value).sorted(comparator).collect(Collectors.toList());
-    }
-
-    return list;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    return newList;
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AscEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AscEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AscEvaluator.java
index 8e5e4a1..0d7321c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AscEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/AscEvaluator.java
@@ -17,64 +17,51 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class AscEvaluator extends ComplexEvaluator implements Expressible {
-
+public class AscEvaluator extends RecursiveObjectEvaluator implements OneValueWorker {
   private static final long serialVersionUID = 1;
 
   public AscEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
-    }
-
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> asc = new ArrayList();
-    asc.addAll(numbers1);
-    Collections.sort(asc, new Comparator<Number>() {
-      @Override
-      public int compare(Number a, Number b) {
-        return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
+  @Override
+  public Object doWork(Object value) throws IOException {
+    if(null == value){
+      return value;
+    }
+    else if(!(value instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for value, expecting a List",toExpression(constructingFactory), value.getClass().getSimpleName()));
+    }
+    
+    List<?> list = (List<?>)value;
+    
+    if(0 == list.size()){
+      return list;
+    }
 
+    // Validate all of same type and are comparable
+    Object checkingObject = list.get(0);
+    for(int idx = 0; idx < list.size(); ++idx){
+      Object item = list.get(0);
+      
+      if(null == item){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found null value",toExpression(constructingFactory)));
       }
-    });
-
-    return asc;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+      else if(!(item instanceof Comparable<?>)){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found non-comparable value %s of type %s",toExpression(constructingFactory), item.toString(), item.getClass().getSimpleName()));
+      }
+      else if(!item.getClass().getCanonicalName().equals(checkingObject.getClass().getCanonicalName())){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - value %s is of type %s but we are expeting type %s",toExpression(constructingFactory), item.toString(), item.getClass().getSimpleName(), checkingObject.getClass().getCanonicalName()));
+      }
+    }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    return list.stream().sorted((left,right) -> ((Comparable)left).compareTo((Comparable)right)).collect(Collectors.toList());
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
deleted file mode 100644
index 447ab86..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/BooleanEvaluator.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class BooleanEvaluator extends ComplexEvaluator {
-  protected static final long serialVersionUID = 1L;
-  
-  public BooleanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    super(expression, factory);
-  }
-  
-  // restrict result to a Boolean
-  public abstract Boolean evaluate(Tuple tuple) throws IOException;
-  
-  public List<Object> evaluateAll(final Tuple tuple) throws IOException {
-    List<Object> results = new ArrayList<Object>();
-    for(StreamEvaluator subEvaluator : subEvaluators){
-      results.add(subEvaluator.evaluate(tuple));
-    }
-    
-    return results;
-  }
-
-  public interface Checker {
-    default boolean isNullAllowed(){
-      return false;
-    }
-    boolean isCorrectType(Object value);
-    boolean test(Object left, Object right);
-  }
-  
-  public interface NullChecker extends Checker {
-    default boolean isNullAllowed(){
-      return true;
-    }
-    default boolean isCorrectType(Object value){
-      return true;
-    }
-    default boolean test(Object left, Object right){
-      return null == left && null == right;
-    }
-  }
-  
-  public interface BooleanChecker extends Checker {
-    default boolean isCorrectType(Object value){
-      return value instanceof Boolean;
-    }
-  }
-  
-  public interface NumberChecker extends Checker {
-    default boolean isCorrectType(Object value){
-      return value instanceof Number;
-    }
-  }
-  
-  public interface StringChecker extends Checker {
-    default boolean isCorrectType(Object value){
-      return value instanceof String;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CeilingEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CeilingEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CeilingEvaluator.java
index 368385e..7f11900 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CeilingEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CeilingEvaluator.java
@@ -18,41 +18,35 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CeilingEvaluator extends NumberEvaluator {
+public class CeilingEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
-
+  
   public CeilingEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    List<BigDecimal> results = evaluateAll(tuple);
-
-    // we're still doing these checks because if we ever add an array-flatten evaluator,
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-
-    return normalizeType(results.get(0).setScale(0, RoundingMode.CEILING));
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.ceil(((BigDecimal)value).doubleValue());
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CoalesceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CoalesceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CoalesceEvaluator.java
index a9f08b0..6c25110 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CoalesceEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CoalesceEvaluator.java
@@ -19,31 +19,28 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CoalesceEvaluator extends ComplexEvaluator {
+public class CoalesceEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public CoalesceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 1){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-    
-    for(StreamEvaluator evaluator : subEvaluators){
-      Object result = evaluator.evaluate(tuple);
-      if(null != result){
-        return result;
+  public Object doWork(Object... values) throws IOException {
+    for(Object value : values){
+      if(null != value){
+        return value;
       }
     }
-        
-    return null;    
+    
+    return null;
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ColumnEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ColumnEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ColumnEvaluator.java
index dbb17b0..360cc38 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ColumnEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ColumnEvaluator.java
@@ -17,60 +17,61 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ColumnEvaluator extends SimpleEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-  private String name;
-  private String fieldName;
- ;
-  public ColumnEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
-    String name = factory.getValueOperand(expression, 0);
-    String fieldName = factory.getValueOperand(expression, 1);
-    init(name, fieldName);
+public class ColumnEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public ColumnEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    init();
   }
-
-  private void init(String name, String fieldName) {
-    this.name = name;
-    this.fieldName = fieldName;
+  
+  public ColumnEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
+    super(expression, factory, ignoredNamedParameters);
+    
+    init();
   }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    List<Tuple> tuples = (List<Tuple>)tuple.get(name);
-    List<Number> column = new ArrayList(tuples.size());
-    for(Tuple t : tuples) {
-      Object o = t.get(fieldName);
-      if(o instanceof Number) {
-        column.add((Number)o);
-      } else {
-        throw new IOException("Found non-numeric in column:"+o.toString());
-      }
+  
+  private void init() throws IOException{
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 2 parameters but found %d", toExpression(constructingFactory), containedEvaluators.size()));
     }
-    return column;
   }
-
+      
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
+  public Object evaluate(Tuple tuple) throws IOException {    
+    try{
+      
+      Object firstLevel = containedEvaluators.get(0).evaluate(tuple);
+      
+      if(!(firstLevel instanceof List<?>) || 0 != ((List<?>)firstLevel).stream().filter(value -> !(value instanceof Tuple)).count()){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a list of tuples but found %s", toExpression(constructingFactory), firstLevel.getClass().getSimpleName()));
+      }
+
+      List<Object> column = new ArrayList<>();
+      for(Object innerTuple : (List<?>)firstLevel){
+        column.add(containedEvaluators.get(1).evaluate((Tuple)innerTuple));
+      }
+
+      return normalizeOutputType(column);
+    }
+    catch(UncheckedIOException e){
+      throw e.getCause();
+    }
   }
 
   @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object... values) throws IOException {
+    // Nothing to do here
+    throw new IOException("This call should never occur");
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
deleted file mode 100644
index ca1f0de..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ComplexEvaluator.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import org.apache.solr.client.solrj.io.stream.StreamContext;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class ComplexEvaluator implements StreamEvaluator {
-  protected static final long serialVersionUID = 1L;
-  protected StreamContext streamContext;
-  
-  protected UUID nodeId = UUID.randomUUID();
-  
-  protected StreamFactory constructingFactory;
-  protected List<StreamEvaluator> subEvaluators = new ArrayList<StreamEvaluator>();
-  
-  public ComplexEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    this(expression, factory, new ArrayList<>());
-  }
-  
-  public ComplexEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
-    constructingFactory = factory;
-    
-    // We have to do this because order of the parameters matter
-    List<StreamExpressionParameter> parameters = factory.getOperandsOfType(expression, StreamExpressionParameter.class);
-    
-    for(StreamExpressionParameter parameter : parameters){
-      if(parameter instanceof StreamExpression){
-        // possible evaluator
-        StreamExpression streamExpression = (StreamExpression)parameter;
-        if(factory.doesRepresentTypes(streamExpression, ComplexEvaluator.class)){
-          subEvaluators.add(factory.constructEvaluator(streamExpression));
-        }
-        else if(factory.doesRepresentTypes(streamExpression, SimpleEvaluator.class)){
-          subEvaluators.add(factory.constructEvaluator(streamExpression));
-        }
-        else{
-          // Will be treated as a field name
-          subEvaluators.add(new FieldEvaluator(streamExpression.toString()));
-        }
-      }
-      else if(parameter instanceof StreamExpressionValue){
-        if(0 != ((StreamExpressionValue)parameter).getValue().length()){
-          // special case - if evaluates to a number, boolean, or null then we'll treat it 
-          // as a RawValueEvaluator
-          Object value = factory.constructPrimitiveObject(((StreamExpressionValue)parameter).getValue());
-          if(null == value || value instanceof Boolean || value instanceof Number){
-            subEvaluators.add(new RawValueEvaluator(value));
-          }
-          else if(value instanceof String){
-            subEvaluators.add(new FieldEvaluator((String)value));
-          }
-        }
-      }
-    }
-    
-    Set<String> namedParameters = factory.getNamedOperands(expression).stream().map(param -> param.getName()).collect(Collectors.toSet());
-    long ignorableCount = ignoredNamedParameters.stream().filter(name -> namedParameters.contains(name)).count();
-    
-    if(0 != expression.getParameters().size() - subEvaluators.size() - ignorableCount){
-      if(namedParameters.isEmpty()){
-        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators or field names", expression));
-      }
-      else{
-        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators, field names, or named parameters [%s]", expression, namedParameters.stream().collect(Collectors.joining(","))));
-      }
-    }
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    
-    for(StreamEvaluator evaluator : subEvaluators){
-      expression.addParameter(evaluator.toExpression(factory));
-    }
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-      .withExpressionType(ExpressionType.EVALUATOR)
-      .withFunctionName(factory.getFunctionName(getClass()))
-      .withImplementingClass(getClass().getName())
-      .withExpression(toExpression(factory).toString());
-  }
-  
-  public void setStreamContext(StreamContext context) {
-    this.streamContext = context;
-    
-    for(StreamEvaluator subEvaluator : subEvaluators){
-      subEvaluator.setStreamContext(context);
-    }
-  }
-  public StreamContext getStreamContext(){
-    return streamContext;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
deleted file mode 100644
index 9c534ed..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConditionalEvaluator.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class ConditionalEvaluator extends ComplexEvaluator {
-  protected static final long serialVersionUID = 1L;
-  
-  public ConditionalEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    super(expression, factory);
-  }
-  
-  public List<Object> evaluateAll(final Tuple tuple) throws IOException {
-    List<Object> results = new ArrayList<Object>();
-    for(StreamEvaluator subEvaluator : subEvaluators){
-      results.add(subEvaluator.evaluate(tuple));
-    }
-    
-    return results;
-  }
-
-  public interface Checker {
-    default boolean isNullAllowed(){
-      return false;
-    }
-    boolean isCorrectType(Object value);
-    boolean test(Object left, Object right);
-  }
-    
-  public interface BooleanChecker extends Checker {
-    default boolean isCorrectType(Object value){
-      return value instanceof Boolean;
-    }
-  }  
-}


[4/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Posted by dp...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
index 4ffaee5..f50a521 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
@@ -14,83 +14,63 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import org.apache.commons.math3.util.MathArrays;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class FindDelayEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public FindDelayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class FindDelayEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public FindDelayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    //Reverse the second column.
-    //The convolve function will reverse it back.
-    //This allows correlation to be represented using the convolution math.
-    int rIndex=0;
-    for(int i=numbers2.size()-1; i>=0; i--) {
-      column2[rIndex++] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
     }
 
-    double[] convolution = MathArrays.convolve(column1, column2);
-    double max = -Double.MAX_VALUE;
-    double maxIndex = -1;
-
-    for(int i=0; i< convolution.length; i++) {
-      double abs = Math.abs(convolution[i]);
-      if(abs > max) {
-        max = abs;
-        maxIndex = i;
+    // Get first and second lists as arrays, where second is in reverse order
+    double[] firstArray = ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray();
+    double[] secondArray = StreamSupport.stream(Spliterators.spliteratorUnknownSize(
+        ((LinkedList)((List)second).stream().collect(Collectors.toCollection(LinkedList::new))).descendingIterator(),
+        Spliterator.ORDERED), false).mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray();
+    
+    double[] convolution = MathArrays.convolve(firstArray, secondArray);
+    double maxValue = -Double.MAX_VALUE;
+    double indexOfMaxValue = -1;
+
+    for(int idx = 0; idx < convolution.length; ++idx) {
+      double abs = Math.abs(convolution[idx]);
+      if(abs > maxValue) {
+        maxValue = abs;
+        indexOfMaxValue = idx;
       }
     }
 
-    return (maxIndex+1)-column2.length;
-  }
+    return (indexOfMaxValue + 1) - secondArray.length;
 
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
index 96a221e..c6d8b84 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
@@ -18,41 +18,35 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class FloorEvaluator extends NumberEvaluator {
+public class FloorEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
-
+  
   public FloorEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    List<BigDecimal> results = evaluateAll(tuple);
-
-    // we're still doing these checks because if we ever add an array-flatten evaluator,
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-
-    return normalizeType(results.get(0).setScale(0, RoundingMode.FLOOR));
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.floor(((BigDecimal)value).doubleValue());
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
index 80cde67..d8e4a0c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class GreaterThanEqualToEvaluator extends BooleanEvaluator {
+public class GreaterThanEqualToEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public GreaterThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class GreaterThanEqualToEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
index 65f844a..0d7aa59 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class GreaterThanEvaluator extends BooleanEvaluator {
+public class GreaterThanEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public GreaterThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class GreaterThanEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
index beabe3a..f58f319 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
@@ -18,6 +18,7 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -25,77 +26,62 @@ import java.util.Map;
 
 import org.apache.commons.math3.random.EmpiricalDistribution;
 import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HistogramEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public HistogramEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class HistogramEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public HistogramEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
-  public List<Tuple> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
     }
-
-    int bins = 10;
-    if(subEvaluators.size() == 2) {
-      StreamEvaluator binsEval = subEvaluators.get(1);
-      Number binsNum = (Number) binsEval.evaluate(tuple);
-      bins = binsNum.intValue();
+    
+    List<?> sourceValues;
+    Integer bins = 10;
+    
+    if(values.length >= 1){
+      sourceValues = values[0] instanceof List<?> ? (List<?>)values[0] : Arrays.asList(values[0]); 
+            
+      if(values.length >= 2){
+        if(values[1] instanceof Number){
+          bins = ((Number)values[1]).intValue();
+        }
+        else{
+          throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - if second parameter is provided then it must be a valid number but found %s instead",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+        }        
+      }      
     }
-
-    EmpiricalDistribution empiricalDistribution = new EmpiricalDistribution(bins);
-    empiricalDistribution.load(column1);
-
-    List<Tuple> binList = new ArrayList();
-
-    List<SummaryStatistics> summaries = empiricalDistribution.getBinStats();
-    for(SummaryStatistics statisticalSummary : summaries) {
-      Map map = new HashMap();
-      map.put("max", statisticalSummary.getMax());
-      map.put("mean", statisticalSummary.getMean());
-      map.put("min", statisticalSummary.getMin());
-      map.put("stdev", statisticalSummary.getStandardDeviation());
-      map.put("sum", statisticalSummary.getSum());
-      map.put("N", statisticalSummary.getN());
-      map.put("var", statisticalSummary.getVariance());
-      binList.add(new Tuple(map));
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",toExpression(constructingFactory),containedEvaluators.size()));
     }
 
-    return binList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    EmpiricalDistribution distribution = new EmpiricalDistribution(bins);
+    distribution.load(((List<?>)sourceValues).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray());;
+
+    List<Map<String,Number>> histogramBins = new ArrayList<>();
+    for(SummaryStatistics binSummary : distribution.getBinStats()) {
+      Map<String,Number> map = new HashMap<>();
+      map.put("max", binSummary.getMax());
+      map.put("mean", binSummary.getMean());
+      map.put("min", binSummary.getMin());
+      map.put("stdev", binSummary.getStandardDeviation());
+      map.put("sum", binSummary.getSum());
+      map.put("N", binSummary.getN());
+      map.put("var", binSummary.getVariance());
+      histogramBins.add(map);
+    }
+    
+    return histogramBins;
   }
-}
\ No newline at end of file
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
index 28f5e37..a9e7c0a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicCosineEvaluator extends NumberEvaluator {
+public class HyperbolicCosineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicCosineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.cosh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.cosh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
index ed1be4f..c23f64e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicSineEvaluator extends NumberEvaluator {
+public class HyperbolicSineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicSineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.sinh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.sinh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
index 4f6b5e1..4f8136e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicTangentEvaluator extends NumberEvaluator {
+public class HyperbolicTangentEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicTangentEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.tanh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.tanh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
index d3e23ab..9453fb0 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
@@ -17,43 +17,32 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class IfThenElseEvaluator extends ConditionalEvaluator {
+public class IfThenElseEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public IfThenElseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(3 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression,subEvaluators.size()));
+    if(3 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression, containedEvaluators.size()));
     }
-    
-    if(!(subEvaluators.get(0) instanceof BooleanEvaluator)){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a boolean as the first parameter but found %s",expression,subEvaluators.get(0).getClass().getSimpleName()));
-    }
-
   }
 
   @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(3 != results.size()){
-      String message = String.format(Locale.ROOT,"%s(...) only works with 3 values but %s were provided", constructingFactory.getFunctionName(getClass()), results.size());
-      throw new IOException(message);
+  public Object doWork(Object... values) throws IOException {
+    if(3 != values.length){
+      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with 3 values but %d were provided", constructingFactory.getFunctionName(getClass()), values.length));
     }
     
-    if(!(results.get(0) instanceof Boolean)){
-      throw new IOException(String.format(Locale.ROOT,"$s(...) only works with a boolean as the first parameter but found %s",results.get(0).getClass().getSimpleName()));
+    if(!(values[0] instanceof Boolean)){
+      throw new IOException(String.format(Locale.ROOT,"$s(...) only works with a boolean as the first parameter but found %s",values[0].getClass().getSimpleName()));
     }
-  
-    return (boolean)results.get(0) ? results.get(1) : results.get(2);
+    
+    return (boolean)values[0] ? values[1] : values[2];
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
index aa7c537..2f7c236 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
@@ -17,86 +17,57 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.commons.math3.distribution.RealDistribution;
 import org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest;
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class KolmogorovSmirnovEvaluator extends ComplexEvaluator implements Expressible {
+public class KolmogorovSmirnovEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public KolmogorovSmirnovEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(subEvaluators.size() != 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
-    }
   }
-
-  public Tuple evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator se1 = subEvaluators.get(0);
-    StreamEvaluator se2 = subEvaluators.get(1);
-
-    KolmogorovSmirnovTest ks = new KolmogorovSmirnovTest();
-    List<Number> sample = (List<Number>)se2.evaluate(tuple);
-    double[] data = new double[sample.size()];
-
-    for(int i=0; i<data.length; i++) {
-      data[i] = sample.get(i).doubleValue();
+  
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first || (first instanceof List<?> && 0 != ((List<?>)first).stream().filter(item -> null == item).count())){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    Object o = se1.evaluate(tuple);
-
-    if(o instanceof RealDistribution) {
-      RealDistribution realDistribution = (RealDistribution)o;
-      double d = ks.kolmogorovSmirnovStatistic(realDistribution, data);
-      double p = ks.kolmogorovSmirnovTest(realDistribution, data);
-
-
-      Map m = new HashMap();
-      m.put("p-value", p);
-      m.put("d-statistic", d);
+    if(null == second || (second instanceof List<?> && 0 != ((List<?>)second).stream().filter(item -> null == item).count())){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(second instanceof List<?>) || 0 != ((List<?>)second).stream().filter(item -> !(item instanceof Number)).count()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a List of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    KolmogorovSmirnovTest ks = new KolmogorovSmirnovTest();
+    double[] data = ((List<?>)second).stream().mapToDouble(item -> ((Number)item).doubleValue()).toArray();
+    
+    if(first instanceof RealDistribution){
+      RealDistribution realDistribution = (RealDistribution)first;
+
+      Map<String,Double> m = new HashMap<>();
+      m.put("p-value", ks.kolmogorovSmirnovTest(realDistribution, data));
+      m.put("d-statistic", ks.kolmogorovSmirnovStatistic(realDistribution, data));
       return new Tuple(m);
-    } else {
-      List<Number> sample2 = (List<Number>)o;
-      double[] data2 = new double[sample2.size()];
-      for(int i=0; i<data2.length; i++) {
-        data2[i] = sample2.get(i).doubleValue();
-      }
-
-      double d = ks.kolmogorovSmirnovStatistic(data, data2);
-      //double p = ks.(data, data2);
-      Map m = new HashMap();
-      //m.put("p-value", p);
-      m.put("d-statistic", d);
+    }
+    else if(first instanceof List<?> && 0 == ((List<?>)first).stream().filter(item -> !(item instanceof Number)).count()){
+      double[] data2 = ((List<?>)first).stream().mapToDouble(item -> ((Number)item).doubleValue()).toArray();
+      
+      Map<String,Double> m = new HashMap<>();
+      m.put("d-statistic", ks.kolmogorovSmirnovTest(data, data2));
       return new Tuple(m);
     }
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RealDistribution or list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
index b070fe8..f703db6 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
@@ -20,54 +20,30 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LengthEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public LengthEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class LengthEvaluator extends RecursiveObjectEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public LengthEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    Object result = subEvaluators.get(0).evaluate(tuple);
-    
-    if(null == result){
+  
+  @Override
+  public Object doWork(Object value) throws IOException{
+    if(null == value){
       throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is null", constructingFactory.getFunctionName(getClass())));
     }
-    
-    if(result instanceof Collection<?>){
-      // Cause other evaluators expect Long instead of Integer
-      return new Long(((Collection<?>)result).size());
+    else if(value instanceof Collection<?>){
+      return ((Collection<?>)value).size();
+    }
+    else{
+      throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is not a collection, instead a %s was found", constructingFactory.getFunctionName(getClass()), value.getClass().getSimpleName()));
     }
-    
-    throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is not a collection, instead a %s was found", constructingFactory.getFunctionName(getClass()), result.getClass().getSimpleName()));
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
index 1590b8f..b5b216b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LessThanEqualToEvaluator extends BooleanEvaluator {
+public class LessThanEqualToEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public LessThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class LessThanEqualToEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
index af389ae..51cbb1d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LessThanEvaluator extends BooleanEvaluator {
+public class LessThanEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public LessThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class LessThanEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
new file mode 100644
index 0000000..311885b
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+
+public interface ManyValueWorker extends ValueWorker {
+
+  Object doWork(Object ... values) throws IOException;
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
index a965440..9e76204 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
@@ -19,57 +19,57 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.MathContext;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ModuloEvaluator extends NumberEvaluator {
+public class ModuloEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ModuloEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
   }
 
+  // override to give a slightly better error message than the default
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // two found in the constructor could become != 2
-    if(2 != results.size()){
+  public Object doWork(Object ... values) throws IOException{
+    if(2 != values.length){
       String message = null;
-      if(1 == results.size()){
+      if(1 == values.length){
         message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but 1 was provided", constructingFactory.getFunctionName(getClass())); 
       }
       else{
-        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
+        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), values.length);
       }
       throw new IOException(message);
     }
     
-    BigDecimal numerator = results.get(0);
-    BigDecimal denominator = results.get(1);
-    
-    if(null == numerator){
+    return doWork(values[0], values[1]);
+  }
+
+
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null numerator", constructingFactory.getFunctionName(getClass())));
     }
     
-    if(null == denominator){
+    if(null == second){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null denominator", constructingFactory.getFunctionName(getClass())));
     }
     
+    BigDecimal numerator = (BigDecimal)first;
+    BigDecimal denominator = (BigDecimal)second;
+    
     if(0 == denominator.compareTo(BigDecimal.ZERO)){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a 0 denominator", constructingFactory.getFunctionName(getClass())));
     }
     
-    return normalizeType(numerator.remainder(denominator, MathContext.DECIMAL64));
+    return numerator.remainder(denominator, MathContext.DECIMAL64);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
index bb4909b..6e71bf1 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
@@ -22,63 +22,45 @@ import java.util.List;
 import java.util.Locale;
 
 import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class MovingAverageEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public MovingAverageEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class MovingAverageEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public MovingAverageEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval = subEvaluators.get(0);
-    StreamEvaluator windowEval = subEvaluators.get(1);
-
-    int window = ((Number)windowEval.evaluate(tuple)).intValue();
-    List<Number> numbers = (List<Number>)colEval.evaluate(tuple);
-
-    if(window > numbers.size()) {
-      throw new IOException("The window size cannot be larger then the array");
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    List<Number> moving = new ArrayList();
-
-    DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics(window);
-    for(int i=0; i<numbers.size(); i++) {
-      descriptiveStatistics.addValue(numbers.get(i).doubleValue());
-      if(descriptiveStatistics.getN() >= window) {
-        moving.add(descriptiveStatistics.getMean());
-      }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
     }
-
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a List",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    List<?> values = (List<?>)first;
+    int window = ((Number)second).intValue();
+    
+    List<Number> moving = new ArrayList<>();
+    DescriptiveStatistics slider = new DescriptiveStatistics(window);
+    for(Object value : values){
+      slider.addValue(((Number)value).doubleValue());
+      
+      if(slider.getN() >= window){
+        moving.add(slider.getMean());
+      }      
+    }
+    
     return moving;
   }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
index 636624c..11b608c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
@@ -18,42 +18,59 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.MathContext;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class MultiplyEvaluator extends NumberEvaluator {
+public class MultiplyEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public MultiplyEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
+    }
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(0 == values.length){
       return null;
     }
     
-    BigDecimal result = null;
-    if(results.size() > 0){
-      result = results.get(0);
-      for(int idx = 1; idx < results.size(); ++idx){
-        result = result.multiply(results.get(idx), MathContext.DECIMAL64);
-      }
+    BigDecimal result = BigDecimal.ONE;
+    for(Object value : values){
+      result = multiply(result, value);
     }
     
-    return normalizeType(result);
+    return result;
   }
+  
+  private BigDecimal multiply(BigDecimal left, Object right) throws IOException{
+    if(null == left || null == right){
+      return null;
+    }
+    else if(right instanceof BigDecimal){
+      return left.multiply((BigDecimal)right);
+    }
+    else if(right instanceof Number){
+      return multiply(left, new BigDecimal(right.toString()));
+    }
+    else if(right instanceof List){
+      return multiply(left, doWork(((List<?>)right).toArray()));
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", right.getClass().getName(), right.toString());
+    }
+
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
index 5280600..b59ba38 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NaturalLogEvaluator extends NumberEvaluator {
+public class NaturalLogEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
-
+  
   public NaturalLogEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    List<BigDecimal> results = evaluateAll(tuple);
-
-    // we're still doing these checks because if we ever add an array-flatten evaluator,
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-
-    return Math.log(results.get(0).doubleValue());
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.log(((BigDecimal)value).doubleValue());
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
index 698402c..f6a445b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
@@ -19,51 +19,30 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.Locale;
 
-
 import org.apache.commons.math3.distribution.NormalDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NormalDistributionEvaluator extends ComplexEvaluator implements Expressible {
+public class NormalDistributionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public NormalDistributionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one column but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator numEval1 = subEvaluators.get(0);
-    StreamEvaluator numEval2 = subEvaluators.get(1);
-
-    Number mean = (Number)numEval1.evaluate(tuple);
-    Number stdDev = (Number)numEval2.evaluate(tuple);
-
-    return new NormalDistribution(mean.doubleValue(), stdDev.doubleValue());
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    Number mean = (Number)first;
+    Number standardDeviation = (Number)second;
+    
+    return new NormalDistribution(mean.doubleValue(), standardDeviation.doubleValue());
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
index c85ac20..afe4c5d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
@@ -17,58 +17,37 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.stat.StatUtils;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NormalizeEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public NormalizeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class NormalizeEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public NormalizeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
-    }
-
-    double[] normalized = StatUtils.normalize(column1);
-
-    List<Number> normalizeList = new ArrayList(normalized.length);
-    for(double d : normalized) {
-      normalizeList.add(d);
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
-
-    return normalizeList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
   }
 
   @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List){
+      return Arrays.stream(StatUtils.normalize(((List<?>)value).stream().mapToDouble(innerValue -> ((Number)innerValue).doubleValue()).toArray())).mapToObj(Double::new).collect(Collectors.toList());
+    }
+    else{
+      return doWork(Arrays.asList((BigDecimal)value));
+    }
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
index 90054c5..03a90d2 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
@@ -19,41 +19,44 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NotEvaluator extends BooleanEvaluator {
+public class NotEvaluator extends RecursiveBooleanEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public NotEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(1 != results.size()){
-      String message = String.format(Locale.ROOT,"%s(...) only works with 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
-      throw new IOException(message);
+  
+  protected Checker constructChecker(Object value) throws IOException{
+    return null;
+  }
+  
+  public Object doWork(Object ... values) throws IOException{
+    if(1 != values.length){
+      throw new IOException(String.format(Locale.ROOT, "Expecting 1 value but found %d", values.length));
     }
+    
+    return doWork(values[0]);
+  }
 
-    Object result = results.get(0);
-    if(null == result){
-      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+  public Object doWork(Object value) {
+    if(null == value){
+      return null;
     }
-    if(!(result instanceof Boolean)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) of a non-boolean value [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue));
+    }
+    else{
+      // we know it's a boolean
+      return !((Boolean)value);
     }
-    
-    return !((Boolean)result);
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
deleted file mode 100644
index 5434c01..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class NumberEvaluator extends ComplexEvaluator {
-  protected static final long serialVersionUID = 1L;
-  
-  public NumberEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    super(expression, factory);
-  }
-  
-  // restrict result to a Number
-  public abstract Number evaluate(Tuple tuple) throws IOException;
-  
-  public List<BigDecimal> evaluateAll(final Tuple tuple) throws IOException {
-    // evaluate each and confirm they are all either null or numeric
-    List<BigDecimal> results = new ArrayList<BigDecimal>();
-    for(StreamEvaluator subEvaluator : subEvaluators){
-      Object result = subEvaluator.evaluate(tuple);
-      
-      if(null == result){
-        results.add(null);
-      }
-      else if(result instanceof Number){
-        results.add(new BigDecimal(result.toString()));
-      } else if(result instanceof List) {
-        List l = (List) result;
-        for(Object o : l) {
-          if(o instanceof Number) {
-            results.add(new BigDecimal(o.toString()));
-          } else {
-            String message = String.format(Locale.ROOT,"Failed to evaluate to a numeric value - evaluator '%s' resulted in type '%s' and value '%s'",
-                                           subEvaluator.toExpression(constructingFactory),
-                                           o.getClass().getName(),
-                                           o.toString());
-            throw new IOException(message);
-          }
-        }
-      }
-      else{
-        String message = String.format(Locale.ROOT,"Failed to evaluate to a numeric value - evaluator '%s' resulted in type '%s' and value '%s'", 
-                                        subEvaluator.toExpression(constructingFactory),
-                                        result.getClass().getName(),
-                                        result.toString());
-        throw new IOException(message);
-      }
-    }
-    
-    return results;
-  }
-  
-  public Number normalizeType(BigDecimal value){
-    if(null == value){
-      return null;
-    }
-    
-    if(value.signum() == 0 || value.scale() <= 0 || value.stripTrailingZeros().scale() <= 0){
-      return value.longValue();
-    }
-    
-    return value.doubleValue();
-
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
new file mode 100644
index 0000000..5f6557fc
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.Locale;
+
+public interface OneValueWorker extends ValueWorker {
+
+  Object doWork(Object value) throws IOException;
+  
+  default Object doWork(Object ... values) throws IOException{
+    if(1 != values.length){
+      throw new IOException(String.format(Locale.ROOT, "Expecting 1 value but found %d", values.length));
+    }
+    
+    return doWork(values[0]);
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
index 0c1886c..4e38537 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
@@ -17,59 +17,23 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class OrEvaluator extends BooleanEvaluator {
+public class OrEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public OrEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(0), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -77,11 +41,12 @@ public class OrEvaluator extends BooleanEvaluator {
       return new BooleanChecker(){
         @Override
         public boolean test(Object left, Object right) {
-          return (boolean)left || (boolean)right;
+          return (Boolean)left || (Boolean)right;
         }
       };
     }
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }


[3/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Posted by dp...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
index 6a51360..b545f4b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
@@ -14,62 +14,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.io.IOException;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-import org.apache.commons.math3.stat.descriptive.rank.Percentile;
-
-public class PercentileEvaluator extends ComplexEvaluator implements Expressible {
 
-  private static final long serialVersionUID = 1;
-
-  public PercentileEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class PercentileEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public PercentileEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values (array and number) but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval = subEvaluators.get(0);
-    List<Number> column = (List<Number>)colEval.evaluate(tuple);
-
-    double[] data = new double[column.size()];
-    for(int i=0; i<data.length; i++) {
-      data[i] = column.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a List",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
     Percentile percentile = new Percentile();
-    percentile.setData(data);
-    StreamEvaluator numEval = subEvaluators.get(1);
-    Number num  = (Number)numEval.evaluate(tuple);
-    return percentile.evaluate(num.doubleValue());
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    percentile.setData(((List<?>)first).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray());
+    return percentile.evaluate(((Number)second).doubleValue());    
   }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
index 415ecc8..12957f7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
@@ -18,41 +18,32 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class PowerEvaluator extends NumberEvaluator {
+public class PowerEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public PowerEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly two values but found %d",expression,subEvaluators.size()));
+
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object first, Object second) throws IOException{
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(null == first || null == second){
       return null;
     }
     
-    BigDecimal value = results.get(0);
-    BigDecimal exponent = results.get(1);
-    
-    double result = Math.pow(value.doubleValue(), exponent.doubleValue());
-    if(Double.isNaN(result)){
-      return result;
-    }
-    
-    return normalizeType(BigDecimal.valueOf(result));
+    BigDecimal value = (BigDecimal)first;
+    BigDecimal exponent = (BigDecimal)second;
+        
+    return Math.pow(value.doubleValue(), exponent.doubleValue());
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
index ed9034a..5d9368b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
@@ -14,67 +14,46 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.Locale;
 import java.util.List;
-import java.util.ArrayList;
+import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class PredictEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public PredictEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class PredictEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public PredictEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values (regression result and a number) but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator d = subEvaluators.get(1);
-
-    RegressionEvaluator.RegressionTuple rt= (RegressionEvaluator.RegressionTuple)r.evaluate(tuple);
-
-    Object o = d.evaluate(tuple);
-    if(o instanceof Number) {
-      Number n = (Number)o;
-      return rt.predict(n.doubleValue());
-    } else {
-      List<Number> list = (List<Number>)o;
-      List<Number> predications = new ArrayList();
-      for(Number n : list) {
-        predications.add(rt.predict(n.doubleValue()));
-      }
-      return predications;
-    }
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof RegressionEvaluator.RegressionTuple)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RegressionTuple",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number) && !(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    RegressionEvaluator.RegressionTuple regressedTuple = (RegressionEvaluator.RegressionTuple)first;
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    if(second instanceof Number){
+      return regressedTuple.predict(((Number)second).doubleValue());
+    }
+    else{
+      return ((List<?>)second).stream().map(value -> regressedTuple.predict(((Number)value).doubleValue())).collect(Collectors.toList());
+    }
   }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
index 88a730d..3b193c7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
@@ -17,64 +17,38 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.stat.ranking.NaturalRanking;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class RankEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public RankEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class RankEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public RankEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
-
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval = subEvaluators.get(0);
-
-    List<Number> numbers = (List<Number>)colEval.evaluate(tuple);
-    double[] values = new double[numbers.size()];
-    for(int i=0; i<numbers.size(); i++) {
-      values[i] = numbers.get(i).doubleValue();
+  @Override
+  public Object doWork(Object value){
+    if(null == value){
+      return null;
     }
-
-    NaturalRanking rank = new NaturalRanking();
-    double[] ranked = rank.rank(values);
-    List<Number> rankedList = new ArrayList();
-    for(int i=0; i<numbers.size(); i++) {
-      rankedList.add(ranked[i]);
+    else if(value instanceof List){
+      NaturalRanking rank = new NaturalRanking();      
+      return Arrays.stream(rank.rank(((List<?>)value).stream().mapToDouble(innerValue -> ((Number)innerValue).doubleValue()).toArray())).mapToObj(Double::new).collect(Collectors.toList());
+    }
+    else{
+      return doWork(Arrays.asList((BigDecimal)value));
     }
-
-    return rankedList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
index e83d481..026f8a2 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
@@ -28,7 +28,7 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class RawValueEvaluator extends SimpleEvaluator {
+public class RawValueEvaluator extends SourceEvaluator {
   private static final long serialVersionUID = 1L;
   
   private Object value;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
new file mode 100644
index 0000000..f555ee3
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveBooleanEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  protected abstract Checker constructChecker(Object value) throws IOException;
+  
+  public RecursiveBooleanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else{
+      return value;
+    }
+  }
+  
+  public Object doWork(Object ... values) throws IOException {
+    if(values.length < 2){
+      String message = null;
+      if(1 == values.length){
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
+      }
+      else{
+        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
+      }
+      throw new IOException(message);
+    }
+    
+    Checker checker = constructChecker(values[0]);
+    if(Arrays.stream(values).anyMatch(result -> null == result)){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+    }
+    if(Arrays.stream(values).anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), Arrays.stream(values).map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < values.length; ++idx){
+      if(!checker.test(values[idx - 1], values[idx])){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  public interface Checker {
+    default boolean isNullAllowed(){
+      return false;
+    }
+    boolean isCorrectType(Object value);
+    boolean test(Object left, Object right);
+  }
+  
+  public interface NullChecker extends Checker {
+    default boolean isNullAllowed(){
+      return true;
+    }
+    default boolean isCorrectType(Object value){
+      return true;
+    }
+    default boolean test(Object left, Object right){
+      return null == left && null == right;
+    }
+  }
+  
+  public interface BooleanChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Boolean;
+    }
+  }
+  
+  public interface NumberChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Number;
+    }
+  }
+  
+  public interface StringChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof String;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
new file mode 100644
index 0000000..8df46b1
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveEvaluator implements StreamEvaluator, ValueWorker {
+  protected static final long serialVersionUID = 1L;
+  protected StreamContext streamContext;
+  
+  protected UUID nodeId = UUID.randomUUID();
+  
+  protected StreamFactory constructingFactory;
+  
+  protected List<StreamEvaluator> containedEvaluators = new ArrayList<StreamEvaluator>();
+  
+  public RecursiveEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    this(expression, factory, new ArrayList<>());
+  }
+  
+  protected Object normalizeInputType(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return null;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return (BigDecimal)value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof Collection){
+      return ((Collection<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else if(value.getClass().isArray()){
+      Stream<?> stream = Stream.empty();
+      if(value instanceof double[]){
+        stream = Arrays.stream((double[])value).boxed();
+      }
+      else if(value instanceof int[]){
+        stream = Arrays.stream((int[])value).boxed();
+      }
+      else if(value instanceof long[]){
+        stream = Arrays.stream((long[])value).boxed();
+      }
+      else if(value instanceof String[]){
+        stream = Arrays.stream((String[])value);
+      }      
+      return stream.map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // anything else can just be returned as is
+      return value;
+    }
+
+  }
+  
+  protected Object normalizeOutputType(Object value) {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof BigDecimal){
+      BigDecimal bd = (BigDecimal)value;
+      if(bd.signum() == 0 || bd.scale() <= 0 || bd.stripTrailingZeros().scale() <= 0){
+        try{
+          return bd.longValueExact();
+        }
+        catch(ArithmeticException e){
+          // value was too big for a long, so use a double which can handle scientific notation
+        }
+      }
+      
+      return bd.doubleValue();
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return value;
+      }
+      
+      // could be a long so recurse back in as a BigDecimal
+      return normalizeOutputType(new BigDecimal((Double)value));
+    }
+    else if(value instanceof Number){
+      return normalizeOutputType(new BigDecimal(((Number)value).toString()));
+    }
+    else if(value instanceof List){
+      // normalize each value in the list
+      return ((List<?>)value).stream().map(innerValue -> normalizeOutputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // anything else can just be returned as is
+      return value;
+    }
+
+  }
+    
+  public RecursiveEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
+    this.constructingFactory = factory;
+    
+    // We have to do this because order of the parameters matter
+    List<StreamExpressionParameter> parameters = factory.getOperandsOfType(expression, StreamExpressionParameter.class);
+    
+    for(StreamExpressionParameter parameter : parameters){
+      if(parameter instanceof StreamExpression){
+        // possible evaluator
+        StreamExpression streamExpression = (StreamExpression)parameter;
+        if(factory.doesRepresentTypes(streamExpression, RecursiveEvaluator.class)){
+          containedEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else if(factory.doesRepresentTypes(streamExpression, SourceEvaluator.class)){
+          containedEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else{
+          // Will be treated as a field name
+          containedEvaluators.add(new FieldValueEvaluator(streamExpression.toString()));
+        }
+      }
+      else if(parameter instanceof StreamExpressionValue){
+        if(0 != ((StreamExpressionValue)parameter).getValue().length()){
+          // special case - if evaluates to a number, boolean, or null then we'll treat it 
+          // as a RawValueEvaluator
+          Object value = factory.constructPrimitiveObject(((StreamExpressionValue)parameter).getValue());
+          if(null == value || value instanceof Boolean || value instanceof Number){
+            containedEvaluators.add(new RawValueEvaluator(value));
+          }
+          else if(value instanceof String){
+            containedEvaluators.add(new FieldValueEvaluator((String)value));
+          }
+        }
+      }
+    }
+    
+    Set<String> namedParameters = factory.getNamedOperands(expression).stream().map(param -> param.getName()).collect(Collectors.toSet());
+    long ignorableCount = ignoredNamedParameters.stream().filter(name -> namedParameters.contains(name)).count();
+    
+    if(0 != expression.getParameters().size() - containedEvaluators.size() - ignorableCount){
+      if(namedParameters.isEmpty()){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators or field names", expression));
+      }
+      else{
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators, field names, or named parameters [%s]", expression, namedParameters.stream().collect(Collectors.joining(","))));
+      }
+    }
+  }
+  
+  @Override
+  public Object evaluate(Tuple tuple) throws IOException {    
+    try{
+      List<Object> containedResults = recursivelyEvaluate(tuple);
+      // this needs to be treated as an array of objects when going into doWork(Object ... values)
+      return normalizeOutputType(doWork(containedResults.toArray()));
+    }
+    catch(UncheckedIOException e){
+      throw e.getCause();
+    }
+  }  
+  
+  public List<Object> recursivelyEvaluate(Tuple tuple) throws IOException {
+    List<Object> results = new ArrayList<>();
+    try{
+      for(StreamEvaluator containedEvaluator : containedEvaluators){
+        results.add(normalizeInputType(containedEvaluator.evaluate(tuple)));
+      }
+    }
+    catch(StreamEvaluatorException e){
+      throw new IOException(String.format(Locale.ROOT, "Failed to evaluate expression %s - %s", toExpression(constructingFactory), e.getMessage()), e);
+    }
+    
+    return results;
+  }
+
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
+    
+    for(StreamEvaluator evaluator : containedEvaluators){
+      expression.addParameter(evaluator.toExpression(factory));
+    }
+    return expression;
+  }
+
+  @Override
+  public Explanation toExplanation(StreamFactory factory) throws IOException {
+    return new Explanation(nodeId.toString())
+      .withExpressionType(ExpressionType.EVALUATOR)
+      .withFunctionName(factory.getFunctionName(getClass()))
+      .withImplementingClass(getClass().getName())
+      .withExpression(toExpression(factory).toString());
+  }
+  
+  public void setStreamContext(StreamContext context) {
+    this.streamContext = context;
+    
+    for(StreamEvaluator containedEvaluator : containedEvaluators){
+      containedEvaluator.setStreamContext(context);
+    }
+  }
+  public StreamContext getStreamContext(){
+    return streamContext;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
new file mode 100644
index 0000000..a1cdeb5
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveNumericEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveNumericEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return Double.NaN;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof Collection){
+      return ((Collection<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else if(value.getClass().isArray()){
+      Stream<?> stream = Stream.empty();
+      if(value instanceof double[]){
+        stream = Arrays.stream((double[])value).boxed();
+      }
+      else if(value instanceof int[]){
+        stream = Arrays.stream((int[])value).boxed();
+      }
+      else if(value instanceof long[]){
+        stream = Arrays.stream((long[])value).boxed();
+      }
+      else if(value instanceof String[]){
+        stream = Arrays.stream((String[])value);
+      }      
+      return stream.map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+  }  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
new file mode 100644
index 0000000..08305b1
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveNumericListEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveNumericListEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> convertToNumber(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric list value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+  }
+  
+  private BigDecimal convertToNumber(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return null;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return (BigDecimal)value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
new file mode 100644
index 0000000..7c6f282
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveObjectEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveObjectEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  public RecursiveObjectEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
+    super(expression, factory, ignoredNamedParameters);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
new file mode 100644
index 0000000..e092130
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveTemporalEvaluator extends RecursiveEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  private String functionName;
+  
+  public RecursiveTemporalEvaluator(StreamExpression expression, StreamFactory factory, String functionName) throws IOException{
+    super(expression, factory);
+    this.functionName = functionName;
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,containedEvaluators.size()));
+    }
+  }
+  
+  public String getFunction() {
+    return functionName;
+  }
+  
+  protected abstract Object getDatePart(TemporalAccessor value);
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return value;
+    }
+    
+    // There is potential for various types to be coming in which are valid temporal values.
+    // The order in which we check these types is actually somewhat critical in how we handle them.
+    
+    if(value instanceof Instant){
+      // Convert to LocalDateTime at UTC
+      return LocalDateTime.ofInstant((Instant)value, ZoneOffset.UTC);
+    }
+    else if(value instanceof TemporalAccessor){
+      // Any other TemporalAccessor can just be returned
+      return value;
+    }
+    else if(value instanceof Long){
+      // Convert to Instant and recurse in
+      return normalizeInputType(Instant.ofEpochMilli((Long)value));
+    }
+    else if(value instanceof Date){
+      // Convert to Instant and recurse in
+      return normalizeInputType(((Date)value).toInstant());
+    }
+    else if(value instanceof String){
+      String valueStr = (String)value;      
+      if (!valueStr.isEmpty()) {
+        try {
+          // Convert to Instant and recurse in
+          return normalizeInputType(Instant.parse(valueStr));
+        } catch (DateTimeParseException e) {
+          throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The String must be formatted in the ISO_INSTANT date format.", valueStr)));
+        }
+      }
+    }
+    else if(value instanceof List){
+      // for each list value, recurse in
+      return ((List<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+
+    throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The parameter must be a string formatted ISO_INSTANT or of type Long,Instant,Date,LocalDateTime or TemporalAccessor.", String.valueOf(value))));
+  }
+  
+  public Object doWork(Object value) {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List<?>){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // We know it's an Instant
+      try {
+        return getDatePart((TemporalAccessor)value);
+      } catch (UnsupportedTemporalTypeException utte) {
+        throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "It is not possible to call '%s' function on %s", getFunction(), value.getClass().getName())));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
index f61c409..b38b6b3 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
@@ -17,6 +17,7 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -24,50 +25,44 @@ import java.util.Map;
 
 import org.apache.commons.math3.stat.regression.SimpleRegression;
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class RegressionEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public RegressionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class RegressionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public RegressionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two columns but found %d",expression,subEvaluators.size()));
-    }
-
   }
 
-  public Tuple evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    List<?> l1 = (List<?>)first;
+    List<?> l2 = (List<?>)second;
+    
+    if(l2.size() < l1.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - first list (%d) has more values than the second list (%d)",toExpression(constructingFactory), l1.size(), l2.size()));      
     }
 
     SimpleRegression regression = new SimpleRegression();
-    for(int i=0; i<column1.length; i++) {
-      regression.addData(column1[i], column2[i]);
+    for(int idx = 0; idx < l1.size(); ++idx){
+      regression.addData(((BigDecimal)l1.get(idx)).doubleValue(), ((BigDecimal)l2.get(idx)).doubleValue());
     }
-
-    Map map = new HashMap();
+    
+    Map<String,Number> map = new HashMap<>();
     map.put("slope", regression.getSlope());
     map.put("intercept", regression.getIntercept());
     map.put("R", regression.getR());
@@ -79,35 +74,21 @@ public class RegressionEvaluator extends ComplexEvaluator implements Expressible
     map.put("totalSumSquares", regression.getTotalSumSquares());
     map.put("significance", regression.getSignificance());
     map.put("meanSquareError", regression.getMeanSquareError());
+    
     return new RegressionTuple(regression, map);
   }
-
+  
   public static class RegressionTuple extends Tuple {
-
     private SimpleRegression simpleRegression;
 
-    public RegressionTuple(SimpleRegression simpleRegression, Map map) {
+    public RegressionTuple(SimpleRegression simpleRegression, Map<?,?> map) {
       super(map);
       this.simpleRegression = simpleRegression;
     }
 
-    public double predict(double d) {
-      return this.simpleRegression.predict(d);
+    public double predict(double value) {
+      return this.simpleRegression.predict(value);
     }
   }
 
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
index 9a9c869..8c5ca34 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
@@ -14,69 +14,70 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Locale;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ResidualsEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ResidualsEvaluator(StreamExpression expression,
-                            StreamFactory factory) throws IOException {
+public class ResidualsEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ResidualsEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(3 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values (regression result and two numeric arrays) but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator a = subEvaluators.get(1);
-    StreamEvaluator b = subEvaluators.get(2);
-
-    RegressionEvaluator.RegressionTuple rt= (RegressionEvaluator.RegressionTuple)r.evaluate(tuple);
-    List<Number> listA = (List<Number>)a.evaluate(tuple);
-    List<Number> listB = (List<Number>)b.evaluate(tuple);
-    List<Number> residuals = new ArrayList();
+  @Override
+  public Object doWork(Object ... values) throws IOException{
+    if(3 != values.length){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - three values expected but found %d",toExpression(constructingFactory), values.length));
+    }
+    
+    if(Arrays.stream(values).filter(value -> null == value).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null value found",toExpression(constructingFactory)));
+    }
 
-    for(int i=0; i<listA.size(); i++) {
-      double valueA = listA.get(i).doubleValue();
-      double prediction = rt.predict(valueA);
-      double valueB = listB.get(i).doubleValue();
-      double residual = valueB - prediction;
+    if(!(values[0] instanceof RegressionEvaluator.RegressionTuple)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RegressionTuple",toExpression(constructingFactory), values[0].getClass().getSimpleName()));
+    }
+    if(!(values[1] instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+    }
+    if(!(values[2] instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the third value, expecting a list",toExpression(constructingFactory), values[2].getClass().getSimpleName()));
+    }
+    if(((List<?>)values[1]).stream().filter(value -> !(value instanceof Number)).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a list of numbers for the second value",toExpression(constructingFactory)));
+    }
+    if(((List<?>)values[2]).stream().filter(value -> !(value instanceof Number)).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a list of numbers for the third value",toExpression(constructingFactory)));
+    }    
+    
+    RegressionEvaluator.RegressionTuple regressedTuple = (RegressionEvaluator.RegressionTuple)values[0];
+    List<?> l1 = (List<?>)values[1];
+    List<?> l2 = (List<?>)values[2];
+    
+    if(l2.size() < l1.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - first list (%d) has more values than the second list (%d)",toExpression(constructingFactory), l1.size(), l2.size()));      
+    }
+    
+    List<Number> residuals = new ArrayList<>();
+    for(int idx = 0; idx < l1.size(); ++idx){
+      double value1 = ((Number)l1.get(idx)).doubleValue();
+      double value2 = ((Number)l2.get(idx)).doubleValue();
+      
+      double prediction = regressedTuple.predict(value1);
+      double residual = value2 - prediction;
+      
       residuals.add(residual);
     }
-
+    
     return residuals;
   }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
index 56e0b63..7ece9c9 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
@@ -21,52 +21,37 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ReverseEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ReverseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class ReverseEvaluator extends RecursiveObjectEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ReverseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
-
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> rev = new ArrayList();
-    for(int i=numbers1.size()-1; i>=0; i--) {
-      rev.add(numbers1.get(i));
-    }
-
-    return rev;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
   }
 
   @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List){
+      List<?> actual = (List<?>)value;
+      
+      List<Object> reversed = new ArrayList<>();
+      for(int idx = actual.size() - 1; idx >= 0; --idx){
+        reversed.add(actual.get(idx));
+      }
+      
+      return reversed;
+    }
+    else{
+      return value;
+    }
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
index 959ad39..0cfb3d4 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class RoundEvaluator extends NumberEvaluator {
+public class RoundEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public RoundEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.round(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.round(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
index 8b725cf..5732f85 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
@@ -18,57 +18,40 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.distribution.RealDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-import java.util.List;
-import java.util.ArrayList;
-
-public class SampleEvaluator extends ComplexEvaluator implements Expressible {
+public class SampleEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public SampleEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values (regression result and a number) but found %d",expression,subEvaluators.size()));
-    }
   }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator d = subEvaluators.get(1);
-    Number number = (Number)d.evaluate(tuple);
-    RealDistribution rd= (RealDistribution)r.evaluate(tuple);
-    double[] sample = rd.sample(number.intValue());
-    List<Number> list = new ArrayList();
-    for(double n : sample) {
-      list.add(n);
-    }
-    return list;
-  }
-
+  
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof RealDistribution)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RealDistribution",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    RealDistribution realDistribution = (RealDistribution)first;
+    
+    return Arrays.stream(realDistribution.sample(((Number)second).intValue())).mapToObj(item -> item).collect(Collectors.toList());    
   }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
index 806e6f6..99950da 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
@@ -17,67 +17,46 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.util.MathArrays;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ScaleEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ScaleEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class ScaleEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ScaleEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
-
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator numEval = subEvaluators.get(0);
-    StreamEvaluator colEval1 = subEvaluators.get(1);
-
-    Number num = (Number)numEval.evaluate(tuple);
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
-    }
-
-    double[] scaled = MathArrays.scale(num.doubleValue(), column1);
 
-    List<Number> scaledList = new ArrayList(scaled.length);
-    for(double d : scaled) {
-      scaledList.add(d);
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
-
-    return scaledList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
   }
 
   @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object first, Object second) throws IOException{
+    
+    if(null == first || null == second){
+      return null;
+    }
+    
+    // we know these are all numbers or lists of numbers
+    if(first instanceof List<?>){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting Number as first value but found a list", toExpression(constructingFactory)));
+    }
+    
+    double[] scaleOver;
+    if(second instanceof Number){
+      scaleOver = Arrays.asList((Number)second).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray();
+    }
+    else{
+      scaleOver = ((List<?>)second).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray();
+    }
+      
+    return Arrays.stream(MathArrays.scale(((Number)first).doubleValue(), scaleOver)).mapToObj(Double::new).collect(Collectors.toList());    
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
index c6db106..ee9dece 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
@@ -22,58 +22,35 @@ import java.util.List;
 import java.util.Locale;
 
 import org.apache.commons.math3.util.MathArrays;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class SequenceEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public SequenceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class SequenceEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public SequenceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(3 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression,subEvaluators.size()));
-    }
-
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator sizeEval = subEvaluators.get(0);
-    StreamEvaluator startEval = subEvaluators.get(1);
-    StreamEvaluator strideEval = subEvaluators.get(2);
-
-    Number sizeNum = (Number)sizeEval.evaluate(tuple);
-    Number startNum = (Number)startEval.evaluate(tuple);
-    Number strideNum = (Number)strideEval.evaluate(tuple);
-
-    int[] sequence = MathArrays.sequence(sizeNum.intValue(), startNum.intValue(), strideNum.intValue());
-    List<Number> numbers = new ArrayList(sequence.length);
-    for(int i : sequence) {
-      numbers.add(i);
+    if(3 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression, containedEvaluators.size()));
     }
-
-    return numbers;
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object... values) throws IOException {
+    if(3 != values.length){
+      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with 3 values but %d were provided", constructingFactory.getFunctionName(getClass()), values.length));
+    }
+    
+    Number sizeNum = (Number)values[0];
+    Number startNum = (Number)values[1];
+    Number strideNum = (Number)values[2];
+    
+    List<Number> sequence = new ArrayList<>(sizeNum.intValue());
+    for(int number : MathArrays.sequence(sizeNum.intValue(), startNum.intValue(), strideNum.intValue())){
+      sequence.add(number);
+    }
+    
+    return sequence;
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
deleted file mode 100644
index 356c495..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.util.UUID;
-
-import org.apache.solr.client.solrj.io.stream.StreamContext;
-
-public abstract class SimpleEvaluator implements StreamEvaluator {
-  private static final long serialVersionUID = 1L;
-  
-  protected UUID nodeId = UUID.randomUUID();
-  protected StreamContext streamContext;
-
-  public void setStreamContext(StreamContext streamContext) {
-    this.streamContext = streamContext;
-  }
-  public StreamContext getStreamContext(){
-    return streamContext;
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
index c5ac397..5426100 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class SineEvaluator extends NumberEvaluator {
+public class SineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public SineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.sin(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.sin(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java
new file mode 100644
index 0000000..f391129
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.util.UUID;
+
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+
+public abstract class SourceEvaluator implements StreamEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  protected UUID nodeId = UUID.randomUUID();
+  protected StreamContext streamContext;
+
+  public void setStreamContext(StreamContext streamContext) {
+    this.streamContext = streamContext;
+  }
+  public StreamContext getStreamContext(){
+    return streamContext;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
index a39fdb2..e20109e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class SquareRootEvaluator extends NumberEvaluator {
+public class SquareRootEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public SquareRootEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.sqrt(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.sqrt(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
index 0b0d509..934f4e4 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
@@ -50,4 +50,4 @@ public interface StreamEvaluator extends Expressible, Serializable {
     return null;
   }
   
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java
new file mode 100644
index 0000000..de9689e
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.util.Locale;
+
+public class StreamEvaluatorException extends RuntimeException {
+
+  private static final long serialVersionUID = 1L;
+
+  public StreamEvaluatorException(String message, Object ... args){
+    super(String.format(Locale.ROOT, message, args));
+  }
+  
+  public StreamEvaluatorException(Throwable cause, String message, Object ... args){
+    super(String.format(Locale.ROOT, message, args), cause);
+  }
+}


[5/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Posted by dp...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConversionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConversionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConversionEvaluator.java
index 2849b49..f6ac23a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConversionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConversionEvaluator.java
@@ -14,153 +14,147 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ConversionEvaluator extends ComplexEvaluator {
+public class ConversionEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
 
+  private interface Converter {
+    public double convert(double value);
+  }
   enum LENGTH_CONSTANT {MILES, YARDS, FEET, INCHES, MILLIMETERS, CENTIMETERS, METERS, KILOMETERS};
-
+  
   private LENGTH_CONSTANT from;
   private LENGTH_CONSTANT to;
-  private Convert convert;
-
-  public ConversionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+  
+  private Converter converter;
+  
+  public ConversionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if (3 != subEvaluators.size()) {
-      throw new EvaluatorException(new IOException(String.format(Locale.ROOT, "Invalid expression %s - expecting 3 value but found %d", expression, subEvaluators.size())));
+    
+    if(3 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 3 parameters but found %d", super.toExpression(constructingFactory), containedEvaluators.size()));
     }
-
+    
+    if(0 != containedEvaluators.subList(0, 2).stream().filter(item -> !(item instanceof RawValueEvaluator) && !(item instanceof FieldValueEvaluator)).count()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - first two parameters must be strings", super.toExpression(constructingFactory)));      
+    }
+    
+    String fromString = containedEvaluators.get(0).toExpression(factory).toString().toUpperCase(Locale.ROOT);
+    String toString = containedEvaluators.get(1).toExpression(factory).toString().toUpperCase(Locale.ROOT);
+    
     try {
-      from = LENGTH_CONSTANT.valueOf(subEvaluators.get(0).toExpression(factory).toString().toUpperCase(Locale.ROOT));
-      to = LENGTH_CONSTANT.valueOf(subEvaluators.get(1).toExpression(factory).toString().toUpperCase(Locale.ROOT));
-      this.convert = getConvert(from, to);
+      from = LENGTH_CONSTANT.valueOf(fromString);
+      to = LENGTH_CONSTANT.valueOf(toString);
+      this.converter = constructConverter(from, to);
+      
     } catch (IllegalArgumentException e) {
-      throw new EvaluatorException(e);
-    }
-  }
-
-  private String listParams() {
-    StringBuffer buf = new StringBuffer();
-    for(LENGTH_CONSTANT lc : LENGTH_CONSTANT.values()) {
-      if(buf.length() > 0) {
-        buf.append(", ");
-      }
-        buf.append(lc.toString());
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - '%s' and '%s' are not both valid conversion types", super.toExpression(constructingFactory), fromString, toString));
     }
-    return buf.toString();
+    
+    // Remove evaluators 0 and 1 because we don't actually want those used
+    containedEvaluators = containedEvaluators.subList(2, 3);
   }
 
   @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator streamEvaluator = subEvaluators.get(2);
-    Object tupleValue = streamEvaluator.evaluate(tuple);
-
-    if (tupleValue == null) return null;
-
-    Number number = (Number)tupleValue;
-    double d = number.doubleValue();
-    return convert.convert(d);
+  public Object doWork(Object value) throws IOException{
+    
+    if(null == value){
+      return null;
+    }
+    
+    return converter.convert(((Number)value).doubleValue());
   }
-
-  private Convert getConvert(LENGTH_CONSTANT from, LENGTH_CONSTANT to) throws IOException {
+  
+  private Converter constructConverter(LENGTH_CONSTANT from, LENGTH_CONSTANT to) throws IOException {
     switch(from) {
       case INCHES:
         switch(to) {
           case MILLIMETERS:
-            return (double d) -> d*25.4;
+            return (double value) -> value * 25.4;
           case CENTIMETERS:
-            return (double d) -> d*2.54;
+            return (double value) -> value * 2.54;
           case METERS:
-            return (double d) -> d*0.0254;
+            return (double value) -> value * 0.0254;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case FEET:
         switch(to) {
           case METERS:
-            return (double d) -> d * .30;
+            return (double value) -> value * .30;
+          default:
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case YARDS:
         switch(to) {
           case METERS:
-            return (double d) -> d * .91;
+            return (double value) -> value * .91;
           case KILOMETERS:
-            return (double d) -> d * 0.00091;
+            return (double value) -> value * 0.00091;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case MILES:
         switch(to) {
           case KILOMETERS:
-            return (double d) -> d * 1.61;
+            return (double value) -> value * 1.61;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case MILLIMETERS:
         switch (to) {
           case INCHES:
-            return (double d) -> d * 0.039;
+            return (double value) -> value * 0.039;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case CENTIMETERS:
         switch(to) {
           case INCHES:
-            return (double d) -> d * 0.39;
+            return (double value) -> value * 0.39;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case METERS:
         switch(to) {
           case FEET:
-            return (double d) -> d * 3.28;
+            return (double value) -> value * 3.28;
           default:
-            throw new EvaluatorException("No conversion available from "+from+" to "+to);
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       case KILOMETERS:
         switch(to) {
           case MILES:
-            return (double d) -> d * 0.62;
+            return (double value) -> value * 0.62;
           case FEET:
-            return (double d) -> d * 3280.8;
+            return (double value) -> value * 3280.8;
+          default:
+            throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
         }
       default:
-        throw new EvaluatorException("No conversion available from "+from);
+        throw new EvaluatorException(String.format(Locale.ROOT,  "No conversion available from %s to %s", from, to));
     }
   }
-
-  private interface Convert {
-    public double convert(double d);
-  }
-
+  
   @Override
   public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(this.getClass()));
-
-    for (StreamEvaluator evaluator : subEvaluators) {
+    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
+    
+    expression.addParameter(from.toString().toLowerCase(Locale.ROOT));
+    expression.addParameter(to.toString().toLowerCase(Locale.ROOT));
+    
+    for(StreamEvaluator evaluator : containedEvaluators){
       expression.addParameter(evaluator.toExpression(factory));
     }
-
     return expression;
   }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(Explanation.ExpressionType.EVALUATOR)
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConvolutionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConvolutionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConvolutionEvaluator.java
index e91620e..0d1816a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConvolutionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ConvolutionEvaluator.java
@@ -17,70 +17,43 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.ArrayList;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.util.MathArrays;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ConvolutionEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ConvolutionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class ConvolutionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ConvolutionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
     }
-
-    double[] conArray = MathArrays.convolve(column1, column2);
-    List<Number> conList = new ArrayList();
-    for(double d :conArray) {
-      conList.add(d);
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
     }
 
-    return conList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    return Arrays.stream(
+        MathArrays.convolve(
+          ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray(),
+          ((List)second).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray()
+        )
+    ).mapToObj(Double::new).collect(Collectors.toList());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfEvaluator.java
index d379c41..4934d99 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfEvaluator.java
@@ -17,66 +17,53 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CopyOfEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public CopyOfEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class CopyOfEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public CopyOfEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
+    
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
+    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] vals = new double[numbers1.size()];
-
-    for(int i=0; i<vals.length; i++) {
-      vals[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
     }
-
-    if(subEvaluators.size() == 2) {
-      StreamEvaluator lengthEval = subEvaluators.get(1);
-      Number lengthNum = (Number)lengthEval.evaluate(tuple);
-      int length = lengthNum.intValue();
-      vals = Arrays.copyOf(vals, length);
-    } else {
-      vals = Arrays.copyOf(vals, vals.length);
+    
+    List<?> sourceValues;
+    Integer length;
+    
+    if(values.length >= 1){
+      sourceValues = values[0] instanceof List<?> ? (List<?>)values[0] : Arrays.asList(values[0]); 
+      
+      // default to full array
+      length = sourceValues.size();
+      
+      if(values.length >= 2){
+        if(values[1] instanceof Number){
+          length = ((Number)values[1]).intValue();
+        }
+        else{
+          throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - if second parameter is provided then it must be a valid number but found %s instead",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+        }        
+      }      
     }
-
-    List<Number> copyOf = new ArrayList(vals.length);
-
-    for(int i=0; i<vals.length; i++) {
-      copyOf.add(vals[i]);
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",toExpression(constructingFactory),containedEvaluators.size()));
     }
 
-    return copyOf;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    return sourceValues.subList(0, Math.min(length, sourceValues.size()));
   }
-}
\ No newline at end of file
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfRangeEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfRangeEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfRangeEvaluator.java
index f1e56dd..685ef30 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfRangeEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CopyOfRangeEvaluator.java
@@ -17,67 +17,73 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CopyOfRangeEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public CopyOfRangeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class CopyOfRangeEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public CopyOfRangeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
+    
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
+    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] vals = new double[numbers1.size()];
-
-    for(int i=0; i<vals.length; i++) {
-      vals[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
     }
-
-    StreamEvaluator startIndexEval = subEvaluators.get(1);
-    Number startIndexNum = (Number)startIndexEval.evaluate(tuple);
-    int startIndex = startIndexNum.intValue();
-
-    StreamEvaluator endIndexEval = subEvaluators.get(2);
-    Number endIndexNum = (Number)endIndexEval.evaluate(tuple);
-    int endIndex = endIndexNum.intValue();
-
-    vals = Arrays.copyOfRange(vals, startIndex, endIndex);
-
-    List<Number> copyOf = new ArrayList(vals.length);
-
-    for(int i=0; i<vals.length; i++) {
-      copyOf.add(vals[i]);
+    
+    List<?> sourceValues;
+    Integer startIdx;
+    Integer endIdx;
+    
+    if(values.length >= 1){
+      sourceValues = values[0] instanceof List<?> ? (List<?>)values[0] : Arrays.asList(values[0]); 
+      
+      // default to full array
+      startIdx = 0;
+      endIdx = sourceValues.size() - 1;
+      
+      if(values.length >= 2){
+        if(values[1] instanceof Number){
+          startIdx = ((Number)values[1]).intValue();
+        }
+        else{
+          throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - if second parameter is provided then it must be a valid number but found %s instead",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+        }
+        
+        if(values.length >= 3){
+          if(values[2] instanceof Number){
+            endIdx = ((Number)values[2]).intValue();
+          }
+          else{
+            throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - if third parameter is provided then it must be a valid number but found %s instead",toExpression(constructingFactory), values[2].getClass().getSimpleName()));
+          }
+        }
+      }      
+    }
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",toExpression(constructingFactory),containedEvaluators.size()));
     }
 
-    return copyOf;
-  }
+    if(startIdx > endIdx){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - startIdx (%d) must be less than endIdx (%d)", toExpression(constructingFactory), startIdx, endIdx));
+    }
 
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+    if(endIdx >= sourceValues.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - endIdx (%d) must be less than size of source array (%d)", toExpression(constructingFactory), endIdx, sourceValues.size()));
+    }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    return Arrays.stream(Arrays.copyOfRange(sourceValues.toArray(), startIdx, endIdx)).collect(Collectors.toList());
   }
-}
\ No newline at end of file
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CorrelationEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CorrelationEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CorrelationEvaluator.java
index 73c9f39..74d9166 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CorrelationEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CorrelationEvaluator.java
@@ -17,65 +17,41 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
 
 import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CorrelationEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public CorrelationEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class CorrelationEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public CorrelationEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
     }
 
     PearsonsCorrelation pearsonsCorrelation = new PearsonsCorrelation();
-
-    return pearsonsCorrelation.correlation(column1, column2);
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    
+    return pearsonsCorrelation.correlation(
+      ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray(),
+      ((List)second).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray()
+    );
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CosineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CosineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CosineEvaluator.java
index f8fcf6a..4904d1b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CosineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CosineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CosineEvaluator extends NumberEvaluator {
+public class CosineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public CosineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.cos(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.cos(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CovarianceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CovarianceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CovarianceEvaluator.java
index 9371d78..5a568a5 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CovarianceEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CovarianceEvaluator.java
@@ -17,65 +17,41 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
 
 import org.apache.commons.math3.stat.correlation.Covariance;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CovarianceEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public CovarianceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class CovarianceEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public CovarianceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
     }
 
     Covariance covariance = new Covariance();
-
-    return covariance.covariance(column1, column2);
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    
+    return covariance.covariance(
+      ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray(),
+      ((List)second).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray()
+    );
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CubedRootEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CubedRootEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CubedRootEvaluator.java
index 9b7c952..e041707 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CubedRootEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CubedRootEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CubedRootEvaluator extends NumberEvaluator {
+public class CubedRootEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public CubedRootEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.cbrt(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.cbrt(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CumulativeProbabilityEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CumulativeProbabilityEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CumulativeProbabilityEvaluator.java
index 8d61294..8ecf35d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CumulativeProbabilityEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/CumulativeProbabilityEvaluator.java
@@ -14,54 +14,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.util.Locale;
 
 import org.apache.commons.math3.distribution.RealDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class CumulativeProbabilityEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public CumulativeProbabilityEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class CumulativeProbabilityEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public CumulativeProbabilityEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values (regression result and a number) but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Number evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator a = subEvaluators.get(0);
-    StreamEvaluator b = subEvaluators.get(1);
-    Number number1 = (Number)b.evaluate(tuple);
-    RealDistribution rd= (RealDistribution)a.evaluate(tuple);
-
-    return rd.cumulativeProbability(number1.doubleValue());
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof RealDistribution)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RealDistribution",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    RealDistribution rd = (RealDistribution)first;
+    Number predictOver = (Number)second;
+    
+    return rd.cumulativeProbability(predictOver.doubleValue());
   }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DescribeEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DescribeEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DescribeEvaluator.java
index e4c7523..4509a8c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DescribeEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DescribeEvaluator.java
@@ -17,6 +17,7 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -24,40 +25,32 @@ import java.util.Map;
 
 import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class DescribeEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public DescribeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class DescribeEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public DescribeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one column but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly one value but found %d",expression,containedEvaluators.size()));
     }
-
   }
-
-  public Tuple evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval = subEvaluators.get(0);
-
-    List<Number> numbers = (List<Number>)colEval.evaluate(tuple);
-    DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics();
-
-    for(Number n : numbers) {
-      descriptiveStatistics.addValue(n.doubleValue());
+  
+  @Override
+  public Object doWork(Object value) throws IOException {
+    
+    if(!(value instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a numeric list but found %s", toExpression(constructingFactory), value.getClass().getSimpleName()));
     }
+    
+    // we know each value is a BigDecimal or a list of BigDecimals
+    DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics();
+    ((List<?>)value).stream().mapToDouble(innerValue -> ((BigDecimal)innerValue).doubleValue()).forEach(innerValue -> descriptiveStatistics.addValue(innerValue));
 
-
-    Map map = new HashMap();
-
+    Map<String,Number> map = new HashMap<>();
     map.put("max", descriptiveStatistics.getMax());
     map.put("mean", descriptiveStatistics.getMean());
     map.put("min", descriptiveStatistics.getMin());
@@ -72,21 +65,5 @@ public class DescribeEvaluator extends ComplexEvaluator implements Expressible {
     map.put("sumsq", descriptiveStatistics.getSumsq());
 
     return new Tuple(map);
-  }
-
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+  }  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DistanceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DistanceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DistanceEvaluator.java
deleted file mode 100644
index 12591ad..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DistanceEvaluator.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-import org.apache.commons.math3.ml.distance.EuclideanDistance;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class DistanceEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public DistanceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
-
-  }
-
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
-    }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = numbers2.get(i).doubleValue();
-    }
-
-    EuclideanDistance distance = new EuclideanDistance();
-    return distance.compute(column1, column2);
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
index de32c6e..387880b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/DivideEvaluator.java
@@ -19,57 +19,57 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.MathContext;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class DivideEvaluator extends NumberEvaluator {
+public class DivideEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public DivideEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
   }
 
+  // override to give a slightly better error message than the default
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // two found in the constructor could become != 2
-    if(2 != results.size()){
+  public Object doWork(Object ... values) throws IOException{
+    if(2 != values.length){
       String message = null;
-      if(1 == results.size()){
+      if(1 == values.length){
         message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but 1 was provided", constructingFactory.getFunctionName(getClass())); 
       }
       else{
-        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
+        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), values.length);
       }
       throw new IOException(message);
     }
     
-    BigDecimal numerator = results.get(0);
-    BigDecimal denominator = results.get(1);
-    
-    if(null == numerator){
+    return doWork(values[0], values[1]);
+  }
+
+
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null numerator", constructingFactory.getFunctionName(getClass())));
     }
     
-    if(null == denominator){
+    if(null == second){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null denominator", constructingFactory.getFunctionName(getClass())));
     }
-    
+
+    BigDecimal numerator = (BigDecimal)first;
+    BigDecimal denominator = (BigDecimal)second;
+        
     if(0 == denominator.compareTo(BigDecimal.ZERO)){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a 0 denominator", constructingFactory.getFunctionName(getClass())));
     }
     
-    return normalizeType(numerator.divide(denominator, MathContext.DECIMAL64));
+    return numerator.divide(denominator, MathContext.DECIMAL64);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EmpiricalDistributionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EmpiricalDistributionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EmpiricalDistributionEvaluator.java
index e57c225..e89db56 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EmpiricalDistributionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EmpiricalDistributionEvaluator.java
@@ -17,62 +17,38 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
-import java.util.Arrays;
 
 import org.apache.commons.math3.random.EmpiricalDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class EmpiricalDistributionEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public EmpiricalDistributionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class EmpiricalDistributionEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public EmpiricalDistributionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one column but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly one value but found %d",expression,containedEvaluators.size()));
     }
   }
+  
+  @Override
+  public Object doWork(Object value) throws IOException {
 
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+    if(!(value instanceof List<?>)){
+      throw new StreamEvaluatorException("List value expected but found type %s for value %s", value.getClass().getName(), value.toString());
     }
 
-    Arrays.sort(column1);
     EmpiricalDistribution empiricalDistribution = new EmpiricalDistribution();
-    empiricalDistribution.load(column1);
+    
+    double[] backingValues = ((List<?>)value).stream().mapToDouble(innerValue -> ((BigDecimal)innerValue).doubleValue()).sorted().toArray();
+    empiricalDistribution.load(backingValues);
 
     return empiricalDistribution;
   }
+}
 
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualToEvaluator.java
new file mode 100644
index 0000000..dccb818
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualToEvaluator.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class EqualToEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public EqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+    
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
+    }
+  }
+  
+  protected Checker constructChecker(Object fromValue) throws IOException{
+    if(null == fromValue){
+      return new NullChecker() {
+        @Override
+        public boolean test(Object left, Object right) {
+          return null == left && null == right;
+        }
+      };
+    }
+    else if(fromValue instanceof Boolean){
+      return new BooleanChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return (boolean)left == (boolean)right;
+        }
+      };
+    }
+    else if(fromValue instanceof Number){
+      return new NumberChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return 0 == (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString()));
+        }
+      };
+    }
+    else if(fromValue instanceof String){
+      return new StringChecker(){
+        @Override
+        public boolean test(Object left, Object right) {
+          return left.equals(right);
+        }
+      };
+    }
+    
+    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
deleted file mode 100644
index d2e6a9f..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EqualsEvaluator.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.Locale;
-import java.util.stream.Collectors;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class EqualsEvaluator extends BooleanEvaluator {
-  protected static final long serialVersionUID = 1L;
-  
-  public EqualsEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    super(expression, factory);
-    
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
-      return false;
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(0), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
-  
-  private Checker constructChecker(Object fromValue) throws IOException{
-    if(null == fromValue){
-      return new NullChecker() {
-        @Override
-        public boolean test(Object left, Object right) {
-          return null == left && null == right;
-        }
-      };
-    }
-    else if(fromValue instanceof Boolean){
-      return new BooleanChecker(){
-        @Override
-        public boolean test(Object left, Object right) {
-          return (boolean)left == (boolean)right;
-        }
-      };
-    }
-    else if(fromValue instanceof Number){
-      return new NumberChecker(){
-        @Override
-        public boolean test(Object left, Object right) {
-          return 0 == (new BigDecimal(left.toString())).compareTo(new BigDecimal(right.toString()));
-        }
-      };
-    }
-    else if(fromValue instanceof String){
-      return new StringChecker(){
-        @Override
-        public boolean test(Object left, Object right) {
-          return left.equals(right);
-        }
-      };
-    }
-    
-    throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EuclideanDistanceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EuclideanDistanceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EuclideanDistanceEvaluator.java
new file mode 100644
index 0000000..3fa0c12
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EuclideanDistanceEvaluator.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.math3.ml.distance.EuclideanDistance;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class EuclideanDistanceEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public EuclideanDistanceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+
+    EuclideanDistance distance = new EuclideanDistance();
+    return distance.compute(
+      ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray(),
+      ((List)second).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray()
+    );
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EvaluatorException.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EvaluatorException.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EvaluatorException.java
index d2098c2..f4d545f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EvaluatorException.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/EvaluatorException.java
@@ -20,6 +20,8 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 
 public class EvaluatorException extends IOException {
+  private static final long serialVersionUID = 1L;
+
   public EvaluatorException(Throwable t) {
     super(t);
   }
@@ -27,4 +29,4 @@ public class EvaluatorException extends IOException {
   public EvaluatorException(String message) {
     super(message);
   }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
index 66ea2b0..3ae5b22 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ExclusiveOrEvaluator.java
@@ -17,33 +17,28 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
+import java.util.Arrays;
 import java.util.Locale;
 import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ExclusiveOrEvaluator extends BooleanEvaluator {
+public class ExclusiveOrEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ExclusiveOrEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
+  
+  public Object doWork(Object ... values) throws IOException {
+    if(values.length < 2){
       String message = null;
-      if(1 == results.size()){
+      if(1 == values.length){
         message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
       }
       else{
@@ -52,13 +47,26 @@ public class ExclusiveOrEvaluator extends BooleanEvaluator {
       throw new IOException(message);
     }
     
-    if(results.stream().anyMatch(result -> null == result)){
+    Checker checker = constructChecker(values[0]);
+    if(Arrays.stream(values).anyMatch(result -> null == result)){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
-    if(results.stream().anyMatch(result -> !(result instanceof Boolean))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of non-boolean values [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    if(Arrays.stream(values).anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), Arrays.stream(values).map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
     }
 
-    return 1 == results.stream().filter(result -> (boolean)result).count();
+    return 1 == Arrays.stream(values).filter(result -> (boolean)result).count();
   }
+
+  @Override
+  protected Checker constructChecker(Object value) throws IOException {
+    return new BooleanChecker(){
+      @Override
+      public boolean test(Object left, Object right) {
+        // does nothing useful
+        return false;
+      }
+    };
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
deleted file mode 100644
index 4d3c568..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldEvaluator.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public class FieldEvaluator extends SimpleEvaluator {
-  private static final long serialVersionUID = 1L;
-  
-  private String fieldName;
-  
-  public FieldEvaluator(String fieldName) {
-    if(fieldName.startsWith("'") && fieldName.endsWith("'") && fieldName.length() > 1){
-      fieldName = fieldName.substring(1, fieldName.length() - 1);
-    }
-    
-    this.fieldName = fieldName;
-  }
-  
-  @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-    Object value = tuple.get(fieldName);
-    
-    // This is somewhat radical.
-    // Here, we allow for the use of the context to provide alternative values
-    // when they are not available in the provided tuple. This means that all
-    // evaluators can evaluate over both a stream's tuple and the context, and
-    // can even evaluate over fields from both of them in the same evaluation
-    if(null == value && null != getStreamContext()){
-      value = getStreamContext().getLets().get(fieldName);
-      
-      // If what's contained in the context is itself an evaluator then
-      // we need to evaluate it
-      if(value instanceof StreamEvaluator){
-        value = ((StreamEvaluator)value).evaluate(tuple);
-      }
-    }
-    
-    // if we have an array then convert to an ArrayList
-    // if we have an iterable that is not a list then convert to ArrayList
-    // lists are good to go
-    if(null != value){
-      if(value instanceof Object[]){
-        Object[] array = (Object[])value;
-        List<Object> list = new ArrayList<Object>(array.length);
-        for(Object obj : array){
-          list.add(obj);
-        }
-        return list;
-      }
-      else if(value instanceof Iterable && !(value instanceof List<?>)){
-        Iterable<?> iter = (Iterable<?>)value;
-        List<Object> list = new ArrayList<Object>();
-        for(Object obj : iter){
-          list.add(obj);
-        }
-        return list;
-      }
-    }
-    
-    return value;
-  }
-  
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    return new StreamExpressionValue(fieldName);
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-      .withExpressionType(ExpressionType.EVALUATOR)
-      .withImplementingClass(getClass().getName())
-      .withExpression(toExpression(factory).toString());
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldValueEvaluator.java
new file mode 100644
index 0000000..81943bf
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FieldValueEvaluator.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation;
+import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public class FieldValueEvaluator extends SourceEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  private String fieldName;
+  
+  public FieldValueEvaluator(String fieldName) {
+    if(fieldName.startsWith("'") && fieldName.endsWith("'") && fieldName.length() > 1){
+      fieldName = fieldName.substring(1, fieldName.length() - 1);
+    }
+    
+    this.fieldName = fieldName;
+  }
+  
+  @Override
+  public Object evaluate(Tuple tuple) throws IOException {
+    Object value = tuple.get(fieldName);
+    
+    // This is somewhat radical.
+    // Here, we allow for the use of the context to provide alternative values
+    // when they are not available in the provided tuple. This means that all
+    // evaluators can evaluate over both a stream's tuple and the context, and
+    // can even evaluate over fields from both of them in the same evaluation
+    if(null == value && null != getStreamContext()){
+      value = getStreamContext().getLets().get(fieldName);
+      
+      // If what's contained in the context is itself an evaluator then
+      // we need to evaluate it
+      if(value instanceof StreamEvaluator){
+        value = ((StreamEvaluator)value).evaluate(tuple);
+      }
+    }
+    
+    // if we have an array then convert to an ArrayList
+    // if we have an iterable that is not a list then convert to ArrayList
+    // lists are good to go
+    if(null != value){
+      if(value instanceof Object[]){
+        Object[] array = (Object[])value;
+        List<Object> list = new ArrayList<Object>(array.length);
+        for(Object obj : array){
+          list.add(obj);
+        }
+        return list;
+      }
+      else if(value instanceof Iterable && !(value instanceof List<?>)){
+        Iterable<?> iter = (Iterable<?>)value;
+        List<Object> list = new ArrayList<Object>();
+        for(Object obj : iter){
+          list.add(obj);
+        }
+        return list;
+      }
+    }
+    
+    return value;
+  }
+  
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    return new StreamExpressionValue(fieldName);
+  }
+
+  @Override
+  public Explanation toExplanation(StreamFactory factory) throws IOException {
+    return new Explanation(nodeId.toString())
+      .withExpressionType(ExpressionType.EVALUATOR)
+      .withImplementingClass(getClass().getName())
+      .withExpression(toExpression(factory).toString());
+  }
+
+}


[2/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them

Posted by dp...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
index f555947..5677f50 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SubtractEvaluator.java
@@ -18,41 +18,78 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class SubtractEvaluator extends NumberEvaluator {
+public class SubtractEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public SubtractEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
+    }
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(0 == values.length){
       return null;
     }
     
-    BigDecimal result = null;
-    if(results.size() > 0){
-      result = results.get(0);
-      for(int idx = 1; idx < results.size(); ++idx){
-        result = result.subtract(results.get(idx));
+    List<BigDecimal> flattenedValues = flatten(Arrays.stream(values).collect(Collectors.toList()));
+    
+    BigDecimal result = flattenedValues.get(0);
+    for(int idx = 1; idx < flattenedValues.size(); ++idx){
+      result = subtract(result, flattenedValues.get(idx));
+    }
+    
+    return result;
+  }
+  
+  private List<BigDecimal> flatten(Collection<?> values){
+    List<BigDecimal> flattened = new ArrayList<>();
+    
+    for(Object value : values){
+      if(null == value){
+        flattened.add(null);
+      }
+      if(value instanceof Collection<?>){
+        flattened.addAll(flatten((Collection<?>)value));
       }
+      else if(value instanceof BigDecimal){
+        flattened.add((BigDecimal)value);
+      }
+      else if(value instanceof Number){
+        flattened.add(new BigDecimal(value.toString()));
+      }
+      else{
+        throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+      }
+    }
+    
+    return flattened;
+  }
+    
+  private BigDecimal subtract(BigDecimal left, Object right) throws IOException{
+    
+    if(null == right){
+      return null;
     }
     
-    return normalizeType(result);
+    return left.subtract((BigDecimal)right);
   }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TangentEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TangentEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TangentEvaluator.java
index 525ff1e..eb7e4dc 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TangentEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TangentEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class TangentEvaluator extends NumberEvaluator {
+public class TangentEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public TangentEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.tan(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.tan(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluator.java
deleted file mode 100644
index 4af1f354..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluator.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.TemporalAccessor;
-import java.time.temporal.UnsupportedTemporalTypeException;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-/**
- * A generic date evaluator for use with a TemporalAccessor
- */
-public abstract class TemporalEvaluator extends ComplexEvaluator {
-
-  private String field;
-
-  public TemporalEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-
-    if (1 != subEvaluators.size()) {
-      throw new IOException(String.format(Locale.ROOT, "Invalid expression %s - expecting one value but found %d", expression, subEvaluators.size()));
-    }
-  }
-
-  @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    Instant instant = null;
-    TemporalAccessor date = null;
-
-    //First evaluate the parameter
-    StreamEvaluator streamEvaluator = subEvaluators.get(0);
-    Object tupleValue = streamEvaluator.evaluate(tuple);
-
-    if (tupleValue == null) return null;
-
-    if(field == null) {
-      field = streamEvaluator.toExpression(constructingFactory).toString();
-    }
-
-    Map tupleContext = streamContext.getTupleContext();
-    date = (LocalDateTime)tupleContext.get(field); // Check to see if the date has already been created for this field
-
-    if(date == null) {
-      if (tupleValue instanceof String) {
-        instant = getInstant((String) tupleValue);
-      } else if (tupleValue instanceof Long) {
-        instant = Instant.ofEpochMilli((Long) tupleValue);
-      } else if (tupleValue instanceof Instant) {
-        instant = (Instant) tupleValue;
-      } else if (tupleValue instanceof Date) {
-        instant = ((Date) tupleValue).toInstant();
-      } else if (tupleValue instanceof TemporalAccessor) {
-        date = ((TemporalAccessor) tupleValue);
-        tupleContext.put(field, date); // Cache the date in the TupleContext
-      }
-    }
-
-    if (instant != null) {
-      if (TemporalEvaluatorEpoch.FUNCTION_NAME.equals(getFunction())) return instant.toEpochMilli();
-      date = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
-      tupleContext.put(field, date); // Cache the date in the TupleContext
-    }
-
-    if (date != null) {
-      try {
-        return evaluateDate(date);
-      } catch (UnsupportedTemporalTypeException utte) {
-        throw new IOException(String.format(Locale.ROOT, "It is not possible to call '%s' function on %s", getFunction(), date.getClass().getName()));
-      }
-    }
-
-    throw new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The parameter must be a string formatted ISO_INSTANT or of type Long,Instant,Date,LocalDateTime or TemporalAccessor.", String.valueOf(tupleValue)));
-  }
-
-  public abstract Object evaluateDate(TemporalAccessor aDate) throws IOException;
-  public abstract String getFunction();
-
-  protected Instant getInstant(String dateStr) throws IOException {
-
-    if (dateStr != null && !dateStr.isEmpty()) {
-      try {
-        return Instant.parse(dateStr);
-      } catch (DateTimeParseException e) {
-        throw new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The String must be formatted in the ISO_INSTANT date format.", dateStr));
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(getFunction());
-
-    for (StreamEvaluator evaluator : subEvaluators) {
-      expression.addParameter(evaluator.toExpression(factory));
-    }
-
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(Explanation.ExpressionType.EVALUATOR)
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDay.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDay.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDay.java
index 88b5043..3af2307 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDay.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDay.java
@@ -27,22 +27,18 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 /**
  * Provides a day stream evaluator
  */
-public class TemporalEvaluatorDay extends TemporalEvaluator {
-
+public class TemporalEvaluatorDay extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "day";
 
   public TemporalEvaluatorDay(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.DAY_OF_MONTH);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.DAY_OF_MONTH);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfQuarter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfQuarter.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfQuarter.java
index f034377..2d911c2 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfQuarter.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfQuarter.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a dayOfQuarter stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorDayOfQuarter extends TemporalEvaluator {
-
+public class TemporalEvaluatorDayOfQuarter extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "dayOfQuarter";
 
   public TemporalEvaluatorDayOfQuarter(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(IsoFields.DAY_OF_QUARTER);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(IsoFields.DAY_OF_QUARTER);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfYear.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfYear.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfYear.java
index 339f938..6457dec 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfYear.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorDayOfYear.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a dayOfYear stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorDayOfYear extends TemporalEvaluator {
-
+public class TemporalEvaluatorDayOfYear extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "dayOfYear";
 
   public TemporalEvaluatorDayOfYear(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.DAY_OF_YEAR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.DAY_OF_YEAR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorEpoch.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorEpoch.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorEpoch.java
index a8554b3..7bfe266 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorEpoch.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorEpoch.java
@@ -21,33 +21,25 @@ import java.io.IOException;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.temporal.TemporalAccessor;
-import java.util.Locale;
 
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a epoch stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorEpoch extends TemporalEvaluator {
-
+public class TemporalEvaluatorEpoch extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "epoch";
 
   public TemporalEvaluatorEpoch(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) throws IOException {
-    if (aDate instanceof LocalDateTime) {
-      return ((LocalDateTime)aDate).atZone(ZoneOffset.UTC).toInstant().toEpochMilli();
-    }
-    throw new IOException(String.format(Locale.ROOT, "Unsupported function '%s' called on %s", FUNCTION_NAME, aDate.toString()));
+  protected Object getDatePart(TemporalAccessor value) {
+    return ((LocalDateTime)value).atZone(ZoneOffset.UTC).toInstant().toEpochMilli();    
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorHour.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorHour.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorHour.java
index 5480870..d89af48 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorHour.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorHour.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a hour stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorHour extends TemporalEvaluator {
-
+public class TemporalEvaluatorHour extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "hour";
 
   public TemporalEvaluatorHour(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.HOUR_OF_DAY);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.HOUR_OF_DAY);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMinute.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMinute.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMinute.java
index 9438e9d..9fe4b5d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMinute.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMinute.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a minute stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorMinute extends TemporalEvaluator {
-
+public class TemporalEvaluatorMinute extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "minute";
 
   public TemporalEvaluatorMinute(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.MINUTE_OF_HOUR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.MINUTE_OF_HOUR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMonth.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMonth.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMonth.java
index b9a70fe..684069a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMonth.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorMonth.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a month stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorMonth extends TemporalEvaluator {
-
+public class TemporalEvaluatorMonth extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "month";
 
   public TemporalEvaluatorMonth(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.MONTH_OF_YEAR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.MONTH_OF_YEAR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorQuarter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorQuarter.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorQuarter.java
index 0144311..4de9d2d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorQuarter.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorQuarter.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a quarter stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorQuarter extends TemporalEvaluator {
-
+public class TemporalEvaluatorQuarter extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "quarter";
 
   public TemporalEvaluatorQuarter(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(IsoFields.QUARTER_OF_YEAR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(IsoFields.QUARTER_OF_YEAR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorSecond.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorSecond.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorSecond.java
index f5b71fc..fb9e865 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorSecond.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorSecond.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a second stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorSecond extends TemporalEvaluator {
-
+public class TemporalEvaluatorSecond extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "second";
 
   public TemporalEvaluatorSecond(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.SECOND_OF_MINUTE);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.SECOND_OF_MINUTE);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorWeek.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorWeek.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorWeek.java
index 1a2974a..9c89468 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorWeek.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorWeek.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a week stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorWeek extends TemporalEvaluator {
-
+public class TemporalEvaluatorWeek extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "week";
 
   public TemporalEvaluatorWeek(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorYear.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorYear.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorYear.java
index 0b8d69c..65c186f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorYear.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TemporalEvaluatorYear.java
@@ -25,24 +25,20 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
 /**
- * Provides a year stream evaluator
+ * Provides a day stream evaluator
  */
-public class TemporalEvaluatorYear extends TemporalEvaluator {
-
+public class TemporalEvaluatorYear extends RecursiveTemporalEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
   public static final String FUNCTION_NAME = "year";
 
   public TemporalEvaluatorYear(StreamExpression expression, StreamFactory factory) throws IOException {
-    super(expression, factory);
-  }
-
-  @Override
-  public String getFunction() {
-    return FUNCTION_NAME;
+    super(expression, factory, FUNCTION_NAME);
   }
 
   @Override
-  public Object evaluateDate(TemporalAccessor aDate) {
-    return aDate.get(ChronoField.YEAR);
+  protected Object getDatePart(TemporalAccessor value) {
+    return value.get(ChronoField.YEAR);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TwoValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TwoValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TwoValueWorker.java
new file mode 100644
index 0000000..d8bd0dd
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/TwoValueWorker.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.Locale;
+
+public interface TwoValueWorker extends ValueWorker {
+
+  Object doWork(Object value1, Object value2) throws IOException;
+  
+  default Object doWork(Object ... values) throws IOException{
+    if(2 != values.length){
+      throw new IOException(String.format(Locale.ROOT, "Expecting 2 values but found %d", values.length));
+    }
+    
+    return doWork(values[0], values[1]);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UniformDistributionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UniformDistributionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UniformDistributionEvaluator.java
index dec6aa4..bfdf959 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UniformDistributionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UniformDistributionEvaluator.java
@@ -19,51 +19,30 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.Locale;
 
-
 import org.apache.commons.math3.distribution.UniformRealDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class UniformDistributionEvaluator extends ComplexEvaluator implements Expressible {
+public class UniformDistributionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public UniformDistributionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one column but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator numEval1 = subEvaluators.get(0);
-    StreamEvaluator numEval2 = subEvaluators.get(1);
-
-    Number lower = (Number)numEval1.evaluate(tuple);
-    Number upper = (Number)numEval2.evaluate(tuple);
-
-    return new UniformRealDistribution(lower.doubleValue(), upper.doubleValue());
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    Number lower = (Number)first;
+    Number upper = (Number)second;
+    
+    return new UniformRealDistribution(lower.doubleValue(), upper.doubleValue());
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UuidEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UuidEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UuidEvaluator.java
index 0f22ff2..ee0f9eb 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UuidEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/UuidEvaluator.java
@@ -26,18 +26,18 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class UuidEvaluator extends SimpleEvaluator {
-  protected static final long serialVersionUID = 1L;
-
-  public UuidEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    // no parameters are used
+public class UuidEvaluator extends SourceEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  public UuidEvaluator(StreamExpression expression, StreamFactory factory) {
+    
   }
-
+  
   @Override
-  public UUID evaluate(Tuple tuple) throws IOException {
+  public Object evaluate(Tuple tuple) throws IOException {
     return UUID.randomUUID();
   }
-
+  
   @Override
   public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
     return new StreamExpression(factory.getFunctionName(getClass()));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ValueWorker.java
new file mode 100644
index 0000000..98c6b3b
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ValueWorker.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+
+public interface ValueWorker {
+
+  Object doWork(Object ... values) throws IOException;
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/CartesianProductStream.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/CartesianProductStream.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/CartesianProductStream.java
index c96b9fe..0c4ce8d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/CartesianProductStream.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/CartesianProductStream.java
@@ -28,7 +28,7 @@ import java.util.Map;
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.FieldComparator;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
-import org.apache.solr.client.solrj.io.eval.FieldEvaluator;
+import org.apache.solr.client.solrj.io.eval.FieldValueEvaluator;
 import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
@@ -115,7 +115,7 @@ public class CartesianProductStream extends TupleStream implements Expressible {
       }
       if(!wasHandledAsEvaluatorFunction){
         // treat as a straight field evaluator
-        evaluator = new FieldEvaluator(evaluatorPart);
+        evaluator = new FieldValueEvaluator(evaluatorPart);
         if(null == asNamePart){
           asNamePart = evaluatorPart; // just use the field name
         }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
index 2f74bc5..b25ff4f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/HavingStream.java
@@ -23,7 +23,7 @@ import java.util.Locale;
 
 import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.comp.StreamComparator;
-import org.apache.solr.client.solrj.io.eval.BooleanEvaluator;
+import org.apache.solr.client.solrj.io.eval.RecursiveBooleanEvaluator;
 import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation;
 import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
@@ -42,12 +42,12 @@ public class HavingStream extends TupleStream implements Expressible {
   private static final long serialVersionUID = 1;
 
   private TupleStream stream;
-  private BooleanEvaluator evaluator;
+  private RecursiveBooleanEvaluator evaluator;
   private StreamContext streamContext;
 
   private transient Tuple currentGroupHead;
 
-  public HavingStream(TupleStream stream, BooleanEvaluator evaluator) throws IOException {
+  public HavingStream(TupleStream stream, RecursiveBooleanEvaluator evaluator) throws IOException {
     init(stream, evaluator);
   }
 
@@ -55,7 +55,7 @@ public class HavingStream extends TupleStream implements Expressible {
   public HavingStream(StreamExpression expression, StreamFactory factory) throws IOException{
     // grab all parameters out
     List<StreamExpression> streamExpressions = factory.getExpressionOperandsRepresentingTypes(expression, Expressible.class, TupleStream.class);
-    List<StreamExpression> evaluatorExpressions = factory.getExpressionOperandsRepresentingTypes(expression, BooleanEvaluator.class);
+    List<StreamExpression> evaluatorExpressions = factory.getExpressionOperandsRepresentingTypes(expression, RecursiveBooleanEvaluator.class);
 
     // validate expression contains only what we want.
     if(expression.getParameters().size() != streamExpressions.size() + 1){
@@ -71,17 +71,17 @@ public class HavingStream extends TupleStream implements Expressible {
     if(evaluatorExpressions != null && evaluatorExpressions.size() == 1) {
       StreamExpression ex = evaluatorExpressions.get(0);
       evaluator = factory.constructEvaluator(ex);
-      if(!(evaluator instanceof BooleanEvaluator)) {
-        throw new IOException("The HavingStream requires a BooleanEvaluator. A StreamEvaluator was provided.");
+      if(!(evaluator instanceof RecursiveBooleanEvaluator)) {
+        throw new IOException("The HavingStream requires a RecursiveBooleanEvaluator. A StreamEvaluator was provided.");
       }
     } else {
-      throw new IOException("The HavingStream requires a BooleanEvaluator.");
+      throw new IOException("The HavingStream requires a RecursiveBooleanEvaluator.");
     }
 
-    init(factory.constructStream(streamExpressions.get(0)), (BooleanEvaluator)evaluator);
+    init(factory.constructStream(streamExpressions.get(0)), (RecursiveBooleanEvaluator)evaluator);
   }
 
-  private void init(TupleStream stream, BooleanEvaluator evaluator) throws IOException{
+  private void init(TupleStream stream, RecursiveBooleanEvaluator evaluator) throws IOException{
     this.stream = stream;
     this.evaluator = evaluator;
   }
@@ -155,7 +155,7 @@ public class HavingStream extends TupleStream implements Expressible {
       }
 
       streamContext.getTupleContext().clear();
-      if(evaluator.evaluate(tuple)){
+      if((boolean)evaluator.evaluate(tuple)){
         return tuple;
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
index 74e1de8..f4c9b58 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/stream/expr/StreamFactory.java
@@ -361,16 +361,16 @@ public class StreamFactory implements Serializable {
     
     throw new IOException(String.format(Locale.ROOT,"Invalid operation expression %s - function '%s' is unknown (not mapped to a valid StreamOperation)", expression, expression.getFunctionName()));
   }
-
-  public StreamEvaluator constructEvaluator(String expressionClause) throws IOException {
+  
+  public org.apache.solr.client.solrj.io.eval.StreamEvaluator constructEvaluator(String expressionClause) throws IOException {
     return constructEvaluator(StreamExpressionParser.parse(expressionClause));
   }
-  public StreamEvaluator constructEvaluator(StreamExpression expression) throws IOException{
+  public org.apache.solr.client.solrj.io.eval.StreamEvaluator constructEvaluator(StreamExpression expression) throws IOException{
     String function = expression.getFunctionName();
     if(functionNames.containsKey(function)){
       Class<? extends Expressible> clazz = functionNames.get(function);
       if(Expressible.class.isAssignableFrom(clazz) && StreamEvaluator.class.isAssignableFrom(clazz)){
-        return (StreamEvaluator)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this});
+        return (org.apache.solr.client.solrj.io.eval.StreamEvaluator)createInstance(functionNames.get(function), new Class[]{ StreamExpression.class, StreamFactory.class }, new Object[]{ expression, this});
       }
     }
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
index 14632b4..670b39d 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/StreamExpressionTest.java
@@ -25,6 +25,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.LuceneTestCase.Slow;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
@@ -35,7 +37,7 @@ import org.apache.solr.client.solrj.io.comp.ComparatorOrder;
 import org.apache.solr.client.solrj.io.comp.FieldComparator;
 import org.apache.solr.client.solrj.io.eval.AddEvaluator;
 import org.apache.solr.client.solrj.io.eval.AndEvaluator;
-import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
+import org.apache.solr.client.solrj.io.eval.EqualToEvaluator;
 import org.apache.solr.client.solrj.io.eval.GreaterThanEqualToEvaluator;
 import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
 import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
@@ -1091,7 +1093,7 @@ public class StreamExpressionTest extends SolrCloudTestCase {
         .withFunctionName("not", NotEvaluator.class)
         .withFunctionName("gt", GreaterThanEvaluator.class)
         .withFunctionName("lt", LessThanEvaluator.class)
-        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("eq", EqualToEvaluator.class)
         .withFunctionName("lteq", LessThanEqualToEvaluator.class)
         .withFunctionName("gteq", GreaterThanEqualToEvaluator.class);
 
@@ -1198,7 +1200,7 @@ public class StreamExpressionTest extends SolrCloudTestCase {
         .withFunctionName("not", NotEvaluator.class)
         .withFunctionName("gt", GreaterThanEvaluator.class)
         .withFunctionName("lt", LessThanEvaluator.class)
-        .withFunctionName("eq", EqualsEvaluator.class)
+        .withFunctionName("eq", EqualToEvaluator.class)
         .withFunctionName("lteq", LessThanEqualToEvaluator.class)
         .withFunctionName("gteq", GreaterThanEqualToEvaluator.class)
         .withFunctionName("val", RawValueEvaluator.class)
@@ -5717,17 +5719,11 @@ public class StreamExpressionTest extends SolrCloudTestCase {
     List<Tuple> tuples = getTuples(solrStream);
     assertTrue(tuples.size() == 1);
     List<Number> copy1 = (List<Number>)tuples.get(0).get("copy1");
-    assertTrue(copy1.size() == 10);
+    assertTrue(copy1.size() == 4);
     assertTrue(copy1.get(0).doubleValue() == 100D);
     assertTrue(copy1.get(1).doubleValue() == 500D);
     assertTrue(copy1.get(2).doubleValue() == 300D);
     assertTrue(copy1.get(3).doubleValue() == 400D);
-    assertTrue(copy1.get(4).doubleValue() == 0D);
-    assertTrue(copy1.get(5).doubleValue() == 0D);
-    assertTrue(copy1.get(6).doubleValue() == 0D);
-    assertTrue(copy1.get(7).doubleValue() == 0D);
-    assertTrue(copy1.get(8).doubleValue() == 0D);
-    assertTrue(copy1.get(9).doubleValue() == 0D);
 
     List<Number> copy2 = (List<Number>)tuples.get(0).get("copy2");
     assertTrue(copy2.size() == 4);
@@ -5993,6 +5989,17 @@ public class StreamExpressionTest extends SolrCloudTestCase {
     assertTrue(out.get(8).intValue() == 9);
   }
 
+  @Test
+  public void fakeTest(){
+    NormalDistribution a = new NormalDistribution(10, 2);
+    NormalDistribution c = new NormalDistribution(100, 6);
+    double[] d = c.sample(250);
+    
+    KolmogorovSmirnovTest ks = new KolmogorovSmirnovTest();
+    double pv = ks.kolmogorovSmirnovStatistic(a, d);
+    
+    String s = "";
+  }
 
   @Test
   public void testDistributions() throws Exception {
@@ -6252,13 +6259,13 @@ public class StreamExpressionTest extends SolrCloudTestCase {
     assertTrue(tuples.size() == 1);
     List<Number> convolution = (List<Number>)(tuples.get(0)).get("conv");
     assertTrue(convolution.size() == 7);
-    assertTrue(convolution.get(0).equals(20000D));
-    assertTrue(convolution.get(1).equals(20000D));
-    assertTrue(convolution.get(2).equals(25000D));
-    assertTrue(convolution.get(3).equals(30000D));
-    assertTrue(convolution.get(4).equals(15000D));
-    assertTrue(convolution.get(5).equals(10000D));
-    assertTrue(convolution.get(6).equals(5000D));
+    assertTrue(convolution.get(0).equals(20000L));
+    assertTrue(convolution.get(1).equals(20000L));
+    assertTrue(convolution.get(2).equals(25000L));
+    assertTrue(convolution.get(3).equals(30000L));
+    assertTrue(convolution.get(4).equals(15000L));
+    assertTrue(convolution.get(5).equals(10000L));
+    assertTrue(convolution.get(6).equals(5000L));
   }
 
 
@@ -6311,7 +6318,7 @@ public class StreamExpressionTest extends SolrCloudTestCase {
     double prediction = tuple.getDouble("p");
     assertTrue(prediction == 600.0D);
     List<Number> predictions = (List<Number>)tuple.get("pl");
-    assertList(predictions, 200.0, 400.0, 600.0, 200.0, 400.0, 800.0, 1200.0);
+    assertList(predictions, 200L, 400L, 600L, 200L, 400L, 800L, 1200L);
   }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
index 59f89c0..d3674d7 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AddEvaluatorTest.java
@@ -258,13 +258,13 @@ public class AddEvaluatorTest extends LuceneTestCase {
     Assert.assertEquals(6 * 123456789123456789L, result);
     
     values.clear();
-    values.put("a", 4.123456789123456);
-    values.put("b", 4.123456789123456);
-    values.put("c", 4.123456789123456);
-    values.put("d", 4.123456789123456);
+    values.put("a", 4.12345678);
+    values.put("b", 4.12345678);
+    values.put("c", 4.12345678);
+    values.put("d", 4.12345678);
     result = evaluator.evaluate(new Tuple(values));
     Assert.assertTrue(result instanceof Double);
-    Assert.assertEquals(6 * 4.123456789123456, result);
+    Assert.assertEquals(6 * 4.12345678, result);
   }
   
   @Test
@@ -318,13 +318,13 @@ public class AddEvaluatorTest extends LuceneTestCase {
     Assert.assertEquals(4 * 123456789123456789L, result);
     
     values.clear();
-    values.put("a", -4.123456789123456);
-    values.put("b", -4.123456789123456);
-    values.put("c", -4.123456789123456);
-    values.put("d", -4.123456789123456);
+    values.put("a", -4.12345678);
+    values.put("b", -4.12345678);
+    values.put("c", -4.12345678);
+    values.put("d", -4.12345678);
     result = evaluator.evaluate(new Tuple(values));
     Assert.assertTrue(result instanceof Double);
-    Assert.assertEquals(6 * -4.123456789123456, result);
+    Assert.assertEquals(6 * -4.12345678, result);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AppendEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AppendEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AppendEvaluatorTest.java
new file mode 100644
index 0000000..d63a450
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AppendEvaluatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AppendEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class AppendEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public AppendEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("append", AppendEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+    
+  @Test
+  public void multiField() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("append(a,b,c)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1L);
+    values.put("b", Arrays.asList("foo","bar","baz"));
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof List);
+    Assert.assertEquals(1L, ((List)result).get(0));
+    Assert.assertEquals("foo", ((List)result).get(1));
+    Assert.assertEquals("bar", ((List)result).get(2));
+    Assert.assertEquals("baz", ((List)result).get(3));
+    
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AscEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AscEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AscEvaluatorTest.java
new file mode 100644
index 0000000..d9d4493
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/AscEvaluatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.AscEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class AscEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public AscEvaluatorTest() {
+    super();
+
+    factory = new StreamFactory()
+        .withFunctionName("asc", AscEvaluator.class);
+      values = new HashMap<String,Object>();
+    }
+      
+    @Test
+    public void integerSortTest() throws Exception{
+      StreamEvaluator evaluator = factory.constructEvaluator("asc(a)");
+      Object result;
+      
+      values.clear();
+      values.put("a", Arrays.asList(2,4,1,3,5,8,7));
+      result = evaluator.evaluate(new Tuple(values));
+      Assert.assertTrue(result instanceof List<?>);
+      Assert.assertEquals(7, ((List<?>)result).size());
+      checkOrder(Arrays.asList(1L,2L,3L,4L,5L,7L,8L), (List<Object>)result);
+    }
+
+    @Test
+    public void doubleSortTest() throws Exception{
+      StreamEvaluator evaluator = factory.constructEvaluator("asc(a)");
+      Object result;
+      
+      values.clear();
+      values.put("a", Arrays.asList(2.3, 2.1, 2.7, 2.6, 2.5));
+      result = evaluator.evaluate(new Tuple(values));
+      Assert.assertTrue(result instanceof List<?>);
+      Assert.assertEquals(5, ((List<?>)result).size());
+      checkOrder(Arrays.asList(2.1, 2.3, 2.5, 2.6, 2.7), (List<Object>)result);
+    }
+
+    @Test
+    public void doubleWithIntegersSortTest() throws Exception{
+      StreamEvaluator evaluator = factory.constructEvaluator("asc(a)");
+      Object result;
+      
+      values.clear();
+      values.put("a", Arrays.asList(2.3, 2.1, 2.0, 2.7, 2.6, 2.5, 3));
+      result = evaluator.evaluate(new Tuple(values));
+      Assert.assertTrue(result instanceof List<?>);
+      Assert.assertEquals(7, ((List<?>)result).size());
+      checkOrder(Arrays.asList(2L, 2.1, 2.3, 2.5, 2.6, 2.7, 3L), (List<Object>)result);
+    }
+
+    @Test
+    public void stringSortTest() throws Exception{
+      StreamEvaluator evaluator = factory.constructEvaluator("asc(a)");
+      Object result;
+      
+      values.clear();
+      values.put("a", Arrays.asList("a","c","b","e","d"));
+      result = evaluator.evaluate(new Tuple(values));
+      Assert.assertTrue(result instanceof List<?>);
+      Assert.assertEquals(5, ((List<?>)result).size());
+      checkOrder(Arrays.asList("a","b","c","d","e"), (List<Object>)result);
+    }
+
+    private <T> void checkOrder(List<?> expected, List<?> actual){
+      Assert.assertEquals(expected.size(), actual.size());
+      for(int idx = 0; idx < expected.size(); ++idx){
+        Comparable<Object> expectedValue = (Comparable<Object>)expected.get(idx);
+        Comparable<Object> actualValue = (Comparable<Object>)actual.get(idx);
+        
+        Assert.assertEquals(0, expectedValue.compareTo(actualValue));
+      }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CoalesceEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CoalesceEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CoalesceEvaluatorTest.java
index 79f46e7..549c267 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CoalesceEvaluatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CoalesceEvaluatorTest.java
@@ -50,7 +50,7 @@ public class CoalesceEvaluatorTest extends LuceneTestCase {
     values.put("a", null);
     values.put("b", 2);
     result = evaluator.evaluate(new Tuple(values));
-    Assert.assertEquals(2, result);
+    Assert.assertEquals(2L, result);
     
     values.clear();
     values.put("a", 1.1);
@@ -107,6 +107,6 @@ public class CoalesceEvaluatorTest extends LuceneTestCase {
     values.put("c", null);
     values.put("d", 4);
     result = evaluator.evaluate(new Tuple(values));
-    Assert.assertEquals(1, result);
+    Assert.assertEquals(1L, result);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
deleted file mode 100644
index 74b44b3..0000000
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CompoundEvaluatorTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.client.solrj.io.stream.eval;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.eval.AddEvaluator;
-import org.apache.solr.client.solrj.io.eval.AndEvaluator;
-import org.apache.solr.client.solrj.io.eval.GreaterThanEvaluator;
-import org.apache.solr.client.solrj.io.eval.IfThenElseEvaluator;
-import org.apache.solr.client.solrj.io.eval.LessThanEvaluator;
-import org.apache.solr.client.solrj.io.eval.MultiplyEvaluator;
-import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
-import org.apache.solr.client.solrj.io.eval.SubtractEvaluator;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-import org.junit.Test;
-
-import junit.framework.Assert;
-
-public class CompoundEvaluatorTest extends LuceneTestCase {
-
-  StreamFactory factory;
-  Map<String, Object> values;
-  
-  public CompoundEvaluatorTest() {
-    super();
-    
-    factory = new StreamFactory()
-      .withFunctionName("and", AndEvaluator.class)
-      .withFunctionName("gt", GreaterThanEvaluator.class)
-      .withFunctionName("lt", LessThanEvaluator.class)
-      .withFunctionName("add", AddEvaluator.class)
-      .withFunctionName("sub", SubtractEvaluator.class)
-      .withFunctionName("mult", MultiplyEvaluator.class)
-      .withFunctionName("if", IfThenElseEvaluator.class);
-    values = new HashMap<String,Object>();
-  }
-    
-  @Test
-  public void compoundTest() throws Exception{
-    StreamEvaluator evaluator = factory.constructEvaluator("add(mult(a,b),if(gt(c,d),e,f),g)");
-    Object result;
-    
-    values.clear();
-    values.put("a", 10);
-    values.put("b", -3);
-    values.put("c", "foo");
-    values.put("d", "bar");
-    values.put("e", 9);
-    values.put("f", 2);
-    values.put("g", 5);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Long);
-    Assert.assertEquals(-16L, result);
-    
-    values.clear();
-    values.put("a", .1);
-    values.put("b", -3);
-    values.put("c", "foo");
-    values.put("d", "bar");
-    values.put("e", 9);
-    values.put("f", 2);
-    values.put("g", 5);
-    result = evaluator.evaluate(new Tuple(values));
-    Assert.assertTrue(result instanceof Double);
-    Assert.assertEquals(13.7, result);
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ConversionEvaluatorsTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ConversionEvaluatorsTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ConversionEvaluatorsTest.java
index 94124ad..2194b8f 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ConversionEvaluatorsTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/ConversionEvaluatorsTest.java
@@ -62,7 +62,7 @@ public class ConversionEvaluatorsTest {
       evaluator.setStreamContext(streamContext);
       assertTrue(false);
     } catch (IOException e) {
-      assertTrue(e.getCause().getCause().getMessage().contains("Invalid expression convert(inches) - expecting 3 value but found 1"));
+      assertEquals("Invalid expression convert(inches) - expecting exactly 3 parameters but found 1", e.getCause().getCause().getMessage());
     }
 
     try {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CorrelationEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CorrelationEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CorrelationEvaluatorTest.java
new file mode 100644
index 0000000..d915d21
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CorrelationEvaluatorTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.CorrelationEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class CorrelationEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public CorrelationEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("corr", CorrelationEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    double[] l1 = new double[] {3.4, 4.5, 6.7};
+    double[] l2 = new double[] {1.2, 3.2, 3};
+    
+    values.clear();
+    values.put("l1", l1);
+    values.put("l2", l2);
+    
+    Assert.assertEquals(new PearsonsCorrelation().correlation(l1, l2), factory.constructEvaluator("corr(l1,l2)").evaluate(new Tuple(values)));
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CumulativeProbabilityEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CumulativeProbabilityEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CumulativeProbabilityEvaluatorTest.java
new file mode 100644
index 0000000..5ad165f
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/CumulativeProbabilityEvaluatorTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.CumulativeProbabilityEvaluator;
+import org.apache.solr.client.solrj.io.eval.NormalDistributionEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class CumulativeProbabilityEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public CumulativeProbabilityEvaluatorTest() {
+    super();
+    factory = new StreamFactory()
+        .withFunctionName("prob", CumulativeProbabilityEvaluator.class)
+        .withFunctionName("norm", NormalDistributionEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    values.clear();
+    values.put("l1", 3);
+    values.put("l2", 7);
+
+    NormalDistribution actual = new NormalDistribution(3,7);
+    Assert.assertEquals(actual.cumulativeProbability(2), factory.constructEvaluator("prob(norm(l1,l2),2)").evaluate(new Tuple(values)));
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EmpiricalDistributionEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EmpiricalDistributionEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EmpiricalDistributionEvaluatorTest.java
new file mode 100644
index 0000000..5b2414e
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EmpiricalDistributionEvaluatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.random.EmpiricalDistribution;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.EmpiricalDistributionEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class EmpiricalDistributionEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String,Object> values;
+
+  public EmpiricalDistributionEvaluatorTest() {
+    super();
+    factory = new StreamFactory().withFunctionName("emp", EmpiricalDistributionEvaluator.class);
+    values = new HashMap<String,Object>();
+  }
+
+  @Test
+  public void test() throws IOException {
+    double[] l1 = new double[] {3.4, 4.5, 6.7};
+    double[] l2 = new double[] {1.2, 3.2, 3};
+
+    values.clear();
+    values.put("l1", l1);
+    values.put("l2", l2);
+
+    EmpiricalDistribution dist = new EmpiricalDistribution();
+    dist.load(l1);
+    Assert.assertEquals(dist.getNumericalMean(),
+        ((EmpiricalDistribution) factory.constructEvaluator("emp(l1)").evaluate(new Tuple(values))).getNumericalMean());
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualToEvaluatorTest.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualToEvaluatorTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualToEvaluatorTest.java
new file mode 100644
index 0000000..996b0e4
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/io/stream/eval/EqualToEvaluatorTest.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.io.stream.eval;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.eval.EqualToEvaluator;
+import org.apache.solr.client.solrj.io.eval.RawValueEvaluator;
+import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+public class EqualToEvaluatorTest extends LuceneTestCase {
+
+  StreamFactory factory;
+  Map<String, Object> values;
+  
+  public EqualToEvaluatorTest() {
+    super();
+    
+    factory = new StreamFactory()
+      .withFunctionName("eq", EqualToEvaluator.class)
+      .withFunctionName("val", RawValueEvaluator.class)
+      ;
+    values = new HashMap<String,Object>();
+  }
+    
+  @Test
+  public void operationFieldName() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(sum(a),val(9))");    
+    Object result;
+    
+    values.clear();
+    values.put("sum(a)", 9);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+
+  }
+  
+  @Test
+  public void eqTwoIntegers() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 1.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", -1);
+    values.put("b", -1);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b", 2.0);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", 1.0);
+    values.put("b", 2);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+
+  @Test
+  public void eqTwoStrings() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    String foo = "foo";
+    String bar = "bar";
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "foo");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo");
+    values.put("b", "bar");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, false);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar baz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", "foo bar baz");
+    values.put("b", "foo bar jaz");
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", foo);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", foo);
+    values.put("b", bar);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+  }
+  
+  @Test
+  public void eqTwoBooleans() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    Object result;
+    Boolean t = true;
+    Boolean f = false;
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b",false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b", false);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, false);
+    
+    values.clear();
+    values.put("a", false);
+    values.put("b", true);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", t);
+    values.put("b", f);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(false, result);
+    
+    values.clear();
+    values.put("a", t);
+    values.put("b", t);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+    
+    values.clear();
+    values.put("a", f);
+    values.put("b", f);
+    result = evaluator.evaluate(new Tuple(values));
+    Assert.assertTrue(result instanceof Boolean);
+    Assert.assertEquals(true, result);
+  }
+
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes1() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", true);
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes2() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", 1);
+    values.put("b",false);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes3() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", "1");
+    values.put("b",1);
+    evaluator.evaluate(new Tuple(values));
+  }
+  
+  @Test(expected = IOException.class)
+  public void eqDifferentTypes4() throws Exception{
+    StreamEvaluator evaluator = factory.constructEvaluator("eq(a,b)");
+    
+    values.clear();
+    values.put("a", "true");
+    values.put("b",true);
+    evaluator.evaluate(new Tuple(values));
+  }
+
+}