You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2012/06/19 22:27:07 UTC

svn commit: r1351839 - in /lucene/dev/trunk: lucene/suggest/src/java/org/apache/lucene/search/spell/ solr/ solr/core/src/java/org/apache/solr/schema/ solr/core/src/java/org/apache/solr/search/ solr/core/src/test/org/apache/solr/search/

Author: hossman
Date: Tue Jun 19 20:27:06 2012
New Revision: 1351839

URL: http://svn.apache.org/viewvc?rev=1351839&view=rev
Log:
SOLR-3548: Fixed a bug in the cachability of queries using the {!join} parser or the strdist() function, as well as some minor improvements to the hashCode implementation of {!bbox} and {!geofilt} queries

Added:
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java   (with props)
Modified:
    lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/JaroWinklerDistance.java
    lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/LevensteinDistance.java
    lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/NGramDistance.java
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/LatLonType.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java

Modified: lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/JaroWinklerDistance.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/JaroWinklerDistance.java?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/JaroWinklerDistance.java (original)
+++ lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/JaroWinklerDistance.java Tue Jun 19 20:27:06 2012
@@ -114,4 +114,25 @@ public class JaroWinklerDistance impleme
   public float getThreshold() {
     return threshold;
   }
+
+  @Override
+  public int hashCode() {
+    return 113 * Float.floatToIntBits(threshold) * getClass().hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (null == obj || getClass() != obj.getClass()) return false;
+    
+    JaroWinklerDistance o = (JaroWinklerDistance)obj;
+    return (Float.floatToIntBits(o.threshold) 
+            == Float.floatToIntBits(this.threshold));
+  }
+
+  @Override
+  public String toString() {
+    return "jarowinkler(" + threshold + ")";
+  }
+
 }

Modified: lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/LevensteinDistance.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/LevensteinDistance.java?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/LevensteinDistance.java (original)
+++ lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/LevensteinDistance.java Tue Jun 19 20:27:06 2012
@@ -106,4 +106,20 @@ public final class LevensteinDistance im
         return 1.0f - ((float) p[n] / Math.max(other.length(), sa.length));
     }
 
+  @Override
+  public int hashCode() {
+    return 163 * getClass().hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (null == obj) return false;
+    return (getClass() == obj.getClass());
+  }
+
+  @Override
+  public String toString() {
+    return "levenstein";
+  }
 }

Modified: lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/NGramDistance.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/NGramDistance.java?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/NGramDistance.java (original)
+++ lucene/dev/trunk/lucene/suggest/src/java/org/apache/lucene/search/spell/NGramDistance.java Tue Jun 19 20:27:06 2012
@@ -141,4 +141,22 @@ public class NGramDistance implements St
     return 1.0f - (p[sl] / Math.max(tl, sl));
   }
 
+  @Override
+  public int hashCode() {
+    return 1427 * n * getClass().hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (null == obj || getClass() != obj.getClass()) return false;
+
+    NGramDistance o = (NGramDistance)obj;
+    return o.n == this.n;
+  }
+
+  @Override
+  public String toString() {
+    return "ngram(" + n + ")";
+  }
 }

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Tue Jun 19 20:27:06 2012
@@ -511,6 +511,11 @@ Bug Fixes
 
 * SOLR-3522: fixed parsing of the 'literal()' function (hossman)
 
+* SOLR-3548: Fixed a bug in the cachability of queries using the {!join} 
+  parser or the strdist() function, as well as some minor improvements to 
+  the hashCode implementation of {!bbox} and {!geofilt} queries.
+  (hossman)
+
 Other Changes
 ----------------------
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/LatLonType.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/LatLonType.java?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/LatLonType.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/LatLonType.java Tue Jun 19 20:27:06 2012
@@ -595,6 +595,7 @@ class SpatialDistanceQuery extends Exten
     // don't bother making the hash expensive - the center latitude + min longitude will be very uinque 
     long hash = Double.doubleToLongBits(latCenter);
     hash = hash * 31 + Double.doubleToLongBits(lonMin);
+    hash = hash * 31 + (long)super.hashCode();
     return (int)(hash >> 32 + hash);
   }
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java?rev=1351839&r1=1351838&r2=1351839&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/JoinQParserPlugin.java Tue Jun 19 20:27:06 2012
@@ -549,7 +549,7 @@ class JoinQuery extends Query {
 
   @Override
   public boolean equals(Object o) {
-    if (getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
     JoinQuery other = (JoinQuery)o;
     return this.fromField.equals(other.fromField)
            && this.toField.equals(other.toField)
@@ -562,7 +562,9 @@ class JoinQuery extends Query {
 
   @Override
   public int hashCode() {
-    int h = q.hashCode() + (int)fromCoreOpenTime;
+    int h = super.hashCode();
+    h = h * 31 + q.hashCode();
+    h = h * 31 + (int)fromCoreOpenTime;
     h = h * 31 + fromField.hashCode();
     h = h * 31 + toField.hashCode();
     return h;

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java?rev=1351839&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java Tue Jun 19 20:27:06 2012
@@ -0,0 +1,752 @@
+package org.apache.solr.search;
+/*
+ * 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.
+ */
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.QueryUtils;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestInfo;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.schema.IndexSchema;
+
+import java.util.Set;
+import java.util.HashSet;
+
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+
+
+
+/**
+ * Sanity checks that queries (generated by the QParser and ValueSourceParser 
+ * framework) are appropraitely {@link Object#equals} and 
+ * {@link Object#hashCode()} equivilent.  If you are adding a new default 
+ * QParser or ValueSourceParser, you will most likely get a failure from 
+ * {@link #testParserCoverage} until you add a new test method to this class.
+ *
+ * @see ValueSourceParser#standardValueSourceParsers
+ * @see QParserPlugin.standardPlugins
+ * @see QueryUtils
+ **/
+public class QueryEqualityTest extends SolrTestCaseJ4 {
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml","schema15.xml");
+  }
+
+  /** @see #testParserCoverage */
+  @AfterClass
+  public static void afterClassParserCoverageTest() {
+
+    if ( ! doAssertParserCoverage) return;
+
+    for (int i=0; i < QParserPlugin.standardPlugins.length; i+=2) {
+      assertTrue("qparser #"+i+" name not a string", 
+                 QParserPlugin.standardPlugins[i] instanceof String);
+      final String name = (String)QParserPlugin.standardPlugins[i];
+      assertTrue("testParserCoverage was run w/o any other method explicitly testing qparser: " + name, qParsersTested.contains(name));
+    }
+
+    for (final String name : ValueSourceParser.standardValueSourceParsers.keySet()) {
+      assertTrue("testParserCoverage was run w/o any other method explicitly testing val parser: " + name, valParsersTested.contains(name));
+    }
+
+  }
+
+  /** @see #testParserCoverage */
+  private static boolean doAssertParserCoverage = false;
+  /** @see #testParserCoverage */
+  private static final Set<String> qParsersTested = new HashSet<String>();
+  /** @see #testParserCoverage */
+  private static final Set<String> valParsersTested = new HashSet<String>();
+
+
+
+  public void testQueryLucene() throws Exception {
+    assertQueryEquals("lucene", "{!lucene}apache solr", 
+                      "apache  solr", "apache solr ");
+    assertQueryEquals("lucene", "+apache +solr", "apache AND solr", 
+                      " +apache +solr");
+  }
+
+  public void testQueryLucenePlusSort() throws Exception {
+    assertQueryEquals("lucenePlusSort", 
+                      "apache solr", "apache  solr", "apache solr ; score desc");
+    assertQueryEquals("lucenePlusSort", 
+                      "+apache +solr", "apache AND solr", " +apache +solr; score desc");
+  }
+
+  public void testQueryPrefix() throws Exception {
+    SolrQueryRequest req = req("myField","foo_s");
+    try {
+      assertQueryEquals("prefix", req, 
+                        "{!prefix f=$myField}asdf", 
+                        "{!prefix f=foo_s}asdf");
+    } finally {
+      req.close();
+    }
+  }
+  
+  public void testQueryBoost() throws Exception {
+    SolrQueryRequest req = req("df","foo_s","myBoost","sum(3,foo_i)");
+    try {
+      assertQueryEquals("boost", req, 
+                        "{!boost b=$myBoost}asdf", 
+                        "{!boost b=$myBoost v=asdf}", 
+                        "{!boost b=sum(3,foo_i)}foo_s:asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryDismax() throws Exception {
+    for (final String type : new String[]{"dismax","edismax"}) {
+      assertQueryEquals(type, "{!"+type+"}apache solr",
+                        "apache solr", "apache  solr", "apache solr ");
+      assertQueryEquals(type, "+apache +solr", "apache AND solr", 
+                        " +apache +solr");
+    }
+  }
+  public void testField() throws Exception {
+    SolrQueryRequest req = req("myField","foo_s");
+    try {
+      assertQueryEquals("field", req, 
+                        "{!field f=$myField}asdf", 
+                        "{!field f=$myField v=asdf}", 
+                        "{!field f=foo_s}asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryRaw() throws Exception {
+    SolrQueryRequest req = req("myField","foo_s");
+    try {
+      assertQueryEquals("raw", req, 
+                        "{!raw f=$myField}asdf", 
+                        "{!raw f=$myField v=asdf}", 
+                        "{!raw f=foo_s}asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryTerm() throws Exception {
+    SolrQueryRequest req = req("myField","foo_s");
+    try {
+      assertQueryEquals("term", req, 
+                        "{!term f=$myField}asdf", 
+                        "{!term f=$myField v=asdf}", 
+                        "{!term f=foo_s}asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryNested() throws Exception {
+    SolrQueryRequest req = req("df", "foo_s");
+    try {
+      assertQueryEquals("query", req, 
+                        "{!query defType=lucene}asdf", 
+                        "{!query v='foo_s:asdf'}", 
+                        "{!query}foo_s:asdf", 
+                        "{!query}asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryFunc() throws Exception {
+    // more involved tests of specific functions in other methods
+    SolrQueryRequest req = req("myVar", "5",
+                               "myField","foo_i",
+                               "myInner","product(4,foo_i)");
+    try {
+      assertQueryEquals("func", req, 
+                        "{!func}sum(4,5)",
+                        "{!func}sum(4,$myVar)",
+                        "sum(4,5)");
+      assertQueryEquals("func", req, 
+                        "{!func}sum(1,2,3,4,5)",
+                        "{!func}sum(1,2,3,4,$myVar)",
+                        "sum(1,2,3,4,5)");
+      assertQueryEquals("func", req,
+                        "{!func}sum(4,$myInner)",
+                        "{!func}sum(4,product(4,foo_i))",
+                        "{!func}sum(4,product(4,$myField))",
+                        "{!func}sum(4,product(4,field(foo_i)))");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryFrange() throws Exception {
+    SolrQueryRequest req = req("myVar", "5",
+                               "low","0.2",
+                               "high", "20.4",
+                               "myField","foo_i",
+                               "myInner","product(4,foo_i)");
+    try {
+      assertQueryEquals("frange", req, 
+                        "{!frange l=0.2 h=20.4}sum(4,5)",
+                        "{!frange l=$low h=$high}sum(4,$myVar)");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQueryGeofilt() throws Exception {
+    checkQuerySpatial("geofilt");
+  }
+  public void testQueryBbox() throws Exception {
+    checkQuerySpatial("bbox");
+  }
+
+  private void checkQuerySpatial(final String type) throws Exception {
+    SolrQueryRequest req = req("myVar", "5",
+                               "d","109",
+                               "pt","10.312,-20.556",
+                               "sfield","store");
+    try {
+      assertQueryEquals(type, req, 
+                        "{!"+type+" d=109}",
+                        "{!"+type+" sfield=$sfield}",
+                        "{!"+type+" sfield=store d=109}",
+                        "{!"+type+" sfield=store d=$d pt=$pt}",
+                        "{!"+type+" sfield=store d=$d pt=10.312,-20.556}",
+                        "{!"+type+"}");
+      // diff SpatialQueryable FieldTypes matter for determining final query
+      assertQueryEquals(type, req, 
+                        "{!"+type+" sfield=point_hash}",
+                        "{!"+type+" sfield=point_hash d=109}",
+                        "{!"+type+" sfield=point_hash d=$d pt=$pt}",
+                        "{!"+type+" sfield=point_hash d=$d pt=10.312,-20.556}");
+      assertQueryEquals(type, req, 
+                        "{!"+type+" sfield=point}",
+                        "{!"+type+" sfield=point d=109}",
+                        "{!"+type+" sfield=point d=$d pt=$pt}",
+                        "{!"+type+" sfield=point d=$d pt=10.312,-20.556}");
+    } finally {
+      req.close();
+    }
+  }
+  public void testQueryJoin() throws Exception {
+    SolrQueryRequest req = req("myVar", "5",
+                               "df","text",
+                               "ff","foo_s",
+                               "tt", "bar_s");
+
+    try {
+      assertQueryEquals("join", req, 
+                        "{!join from=foo_s to=bar_s}asdf",
+                        "{!join from=$ff to=$tt}asdf",
+                        "{!join from=$ff to='bar_s'}text:asdf");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testQuerySurround() throws Exception {
+    assertQueryEquals("surround", "{!surround}and(apache,solr)", 
+                      "and(apache,solr)", "apache AND solr");
+  }
+
+  public void testFuncTestfunc() throws Exception {
+    assertFuncEquals("testfunc(foo_i)","testfunc(field(foo_i))"); 
+    assertFuncEquals("testfunc(23)"); 
+    assertFuncEquals("testfunc(sum(23,foo_i))",
+                     "testfunc(sum(23,field(foo_i)))"); 
+  }
+  public void testFuncOrd() throws Exception {
+    assertFuncEquals("ord(foo_s)","ord(foo_s    )"); 
+  }
+
+  public void testFuncLiteral() throws Exception {
+    SolrQueryRequest req = req("someVar","a string");
+    try {
+      assertFuncEquals(req, 
+                       "literal('a string')","literal(\"a string\")",
+                       "literal($someVar)"); 
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncRord() throws Exception {
+    assertFuncEquals("rord(foo_s)","rord(foo_s    )"); 
+  }
+  public void testFuncTop() throws Exception {
+    assertFuncEquals("top(sum(3,foo_i))");
+  }
+  public void testFuncLinear() throws Exception {
+    SolrQueryRequest req = req("someVar","27");
+    try {
+      assertFuncEquals(req, 
+                       "linear(foo_i,$someVar,42)",
+                       "linear(foo_i,   27,   42)");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncRecip() throws Exception {
+    SolrQueryRequest req = req("someVar","27");
+    try {
+      assertFuncEquals(req, 
+                       "recip(foo_i,$someVar,42,   27   )",
+                       "recip(foo_i,   27,   42,$someVar)");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncScale() throws Exception {
+    SolrQueryRequest req = req("someVar","27");
+    try {
+      assertFuncEquals(req, 
+                       "scale(field(foo_i),$someVar,42)",
+                       "scale(foo_i, 27, 42)");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncDiv() throws Exception {
+    assertFuncEquals("div(5,4)", "div(5, 4)");
+    assertFuncEquals("div(foo_i,4)", "div(foo_i, 4)", 
+                     "div(field('foo_i'), 4)");
+    assertFuncEquals("div(foo_i,sub(4,field('bar_i')))", 
+                     "div(field(foo_i), sub(4,bar_i))");
+
+  }
+  public void testFuncMap() throws Exception {
+    assertFuncEquals("map(field(foo_i), 0, 45, 100)",
+                     "map(foo_i, 0.0, 45, 100)");
+  }
+
+  public void testFuncSum() throws Exception {
+    assertFuncEquals("sum(5,4)", "add(5, 4)");
+    assertFuncEquals("sum(5,4,3,2,1)", "add(5, 4, 3, 2, 1)");
+    assertFuncEquals("sum(foo_i,4)", "sum(foo_i, 4)", 
+                     "sum(field('foo_i'), 4)");
+    assertFuncEquals("add(foo_i,sub(4,field('bar_i')))", 
+                     "sum(field(foo_i), sub(4,bar_i))");
+
+  }
+
+  public void testFuncProduct() throws Exception {
+    assertFuncEquals("product(5,4,3,2,1)", "mul(5, 4, 3, 2, 1)");
+    assertFuncEquals("product(5,4)", "mul(5, 4)");
+    assertFuncEquals("product(foo_i,4)", "product(foo_i, 4)", 
+                     "product(field('foo_i'), 4)");
+    assertFuncEquals("mul(foo_i,sub(4,field('bar_i')))", 
+                     "product(field(foo_i), sub(4,bar_i))");
+
+  }
+  public void testFuncSub() throws Exception {
+    assertFuncEquals("sub(5,4)", "sub(5, 4)");
+    assertFuncEquals("sub(foo_i,4)", "sub(foo_i, 4)");
+    assertFuncEquals("sub(foo_i,sum(4,bar_i))", "sub(foo_i, sum(4,bar_i))");
+  }
+  public void testFuncVector() throws Exception {
+    assertFuncEquals("vector(5,4, field(foo_i))", "vector(5, 4, foo_i)");
+    assertFuncEquals("vector(foo_i,4)", "vector(foo_i, 4)");
+    assertFuncEquals("vector(foo_i,sum(4,bar_i))", "vector(foo_i, sum(4,bar_i))");
+  }
+  public void testFuncQuery() throws Exception {
+    SolrQueryRequest req = req("myQ","asdf");
+    try {
+      assertFuncEquals(req,
+                       "query($myQ)",
+                       "query($myQ,0)",
+                       "query({!lucene v=$myQ},0)");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncBoost() throws Exception {
+    SolrQueryRequest req = req("myQ","asdf");
+    try {
+      assertFuncEquals(req,
+                       "boost($myQ,sum(4,5))",
+                       "boost({!lucene v=$myQ},sum(4,5))");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncJoindf() throws Exception {
+    assertFuncEquals("joindf(foo,bar)");
+  }
+
+  public void testFuncGeodist() throws Exception {
+    SolrQueryRequest req = req("pt","10.312,-20.556",
+                               "sfield","store");
+    try {
+      assertFuncEquals(req, 
+                       "geodist()",
+                       "geodist($sfield,$pt)",
+                       "geodist(store,$pt)",
+                       "geodist(field(store),$pt)",
+                       "geodist(store,10.312,-20.556)");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncHsin() throws Exception {
+    assertFuncEquals("hsin(45,true,0,0,45,45)");
+  }
+  public void testFuncGhhsin() throws Exception {
+    assertFuncEquals("ghhsin(45,point_hash,'asdf')",
+                     "ghhsin(45,field(point_hash),'asdf')");
+  }
+  public void testFuncGeohash() throws Exception {
+    assertFuncEquals("geohash(45,99)");
+  }
+  public void testFuncDist() throws Exception {
+    assertFuncEquals("dist(2,45,99,101,111)",
+                     "dist(2,vector(45,99),vector(101,111))");
+  }
+  public void testFuncSqedist() throws Exception {
+    assertFuncEquals("sqedist(45,99,101,111)",
+                     "sqedist(vector(45,99),vector(101,111))");
+  }
+  public void testFuncMin() throws Exception {
+    assertFuncEquals("min(5,4,3,2,1)", "min(5, 4, 3, 2, 1)");
+    assertFuncEquals("min(foo_i,4)", "min(field('foo_i'), 4)");
+    assertFuncEquals("min(foo_i,sub(4,field('bar_i')))", 
+                     "min(field(foo_i), sub(4,bar_i))");
+  }
+  public void testFuncMax() throws Exception {
+    assertFuncEquals("max(5,4,3,2,1)", "max(5, 4, 3, 2, 1)");
+    assertFuncEquals("max(foo_i,4)", "max(field('foo_i'), 4)");
+    assertFuncEquals("max(foo_i,sub(4,field('bar_i')))", 
+                     "max(field(foo_i), sub(4,bar_i))");
+  }
+
+  public void testFuncMs() throws Exception {
+    // Note ms() takes in field name, not field(...)
+    assertFuncEquals("ms()", "ms(NOW)");
+    assertFuncEquals("ms(2000-01-01T00:00:00Z)",
+                     "ms('2000-01-01T00:00:00Z')");
+    assertFuncEquals("ms(myDateField_dt)",
+                     "ms('myDateField_dt')");
+    assertFuncEquals("ms(2000-01-01T00:00:00Z,myDateField_dt)",
+                     "ms('2000-01-01T00:00:00Z','myDateField_dt')");
+    assertFuncEquals("ms(myDateField_dt, NOW)",
+                     "ms('myDateField_dt', NOW)");
+  }
+  public void testFuncMathConsts() throws Exception {
+    assertFuncEquals("pi()");
+    assertFuncEquals("e()");
+  }
+  public void testFuncTerms() throws Exception {
+    SolrQueryRequest req = req("myField","field_t","myTerm","my term");
+    try {
+      for (final String type : new String[]{"docfreq","termfreq",
+                                            "totaltermfreq","ttf",
+                                            "idf","tf"}) {
+        // NOTE: these functions takes a field *name* not a field(..) source
+        assertFuncEquals(req,
+                         type + "('field_t','my term')",
+                         type + "(field_t,'my term')",
+                         type + "(field_t,$myTerm)",
+                         type + "(field_t,$myTerm)",
+                         type + "($myField,$myTerm)");
+      }
+
+      // ttf is an alias for totaltermfreq
+      assertFuncEquals(req, 
+                       "ttf(field_t,'my term')", "ttf('field_t','my term')", 
+                       "totaltermfreq(field_t,'my term')");
+
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncSttf() throws Exception {
+    // sttf is an alias for sumtotaltermfreq
+    assertFuncEquals("sttf(foo_t)", "sttf('foo_t')",
+                     "sumtotaltermfreq(foo_t)", "sumtotaltermfreq('foo_t')");
+    assertFuncEquals("sumtotaltermfreq('foo_t')");
+  }
+  public void testFuncNorm() throws Exception {
+    assertFuncEquals("norm(foo_t)","norm('foo_t')");
+  }
+  public void testFuncMaxdoc() throws Exception {
+    assertFuncEquals("maxdoc()");
+  }
+  public void testFuncNumdocs() throws Exception {
+    assertFuncEquals("numdocs()");
+  }
+
+  public void testFuncBools() throws Exception {
+    SolrQueryRequest req = req("myTrue","true","myFalse","false");
+    try {
+      assertFuncEquals(req, "true","$myTrue");
+      assertFuncEquals(req, "false","$myFalse");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncExists() throws Exception {
+    SolrQueryRequest req = req("myField","field_t","myQ","asdf");
+    try {
+      assertFuncEquals(req, 
+                       "exists(field_t)",
+                       "exists($myField)",
+                       "exists(field('field_t'))",
+                       "exists(field($myField))");
+      assertFuncEquals(req, 
+                       "exists(query($myQ))",
+                       "exists(query({!lucene v=$myQ}))");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncNot() throws Exception {
+    SolrQueryRequest req = req("myField","field_b", "myTrue","true");
+    try {
+      assertFuncEquals(req, "not(true)", "not($myTrue)"); 
+      assertFuncEquals(req, "not(not(true))", "not(not($myTrue))"); 
+      assertFuncEquals(req, 
+                       "not(field_b)",
+                       "not($myField)",
+                       "not(field('field_b'))",
+                       "not(field($myField))");
+      assertFuncEquals(req, 
+                       "not(exists(field_b))",
+                       "not(exists($myField))",
+                       "not(exists(field('field_b')))",
+                       "not(exists(field($myField)))");
+      
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncDoubleValueBools() throws Exception {
+    SolrQueryRequest req = req("myField","field_b","myTrue","true");
+    try {
+      for (final String type : new String[]{"and","or","xor"}) {
+        assertFuncEquals(req,
+                         type + "(field_b,true)",
+                         type + "(field_b,$myTrue)",
+                         type + "(field('field_b'),true)",
+                         type + "(field($myField),$myTrue)",
+                         type + "($myField,$myTrue)");
+      }
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncIf() throws Exception {
+    SolrQueryRequest req = req("myBoolField","foo_b",
+                               "myIntField","bar_i",
+                               "myTrue","true");
+    try {
+      assertFuncEquals(req, 
+                       "if(foo_b,bar_i,25)",
+                       "if($myBoolField,bar_i,25)",
+                       "if(field('foo_b'),$myIntField,25)",
+                       "if(field($myBoolField),field('bar_i'),25)");
+      assertFuncEquals(req, 
+                       "if(true,37,field($myIntField))",
+                       "if($myTrue,37,$myIntField)");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncDef() throws Exception {
+    SolrQueryRequest req = req("myField","bar_f");
+
+    try {
+      assertFuncEquals(req, 
+                       "def(bar_f,25)",
+                       "def($myField,25)",
+                       "def(field('bar_f'),25)");
+      assertFuncEquals(req, 
+                       "def(ceil(bar_f),25)",
+                       "def(ceil($myField),25)",
+                       "def(ceil(field('bar_f')),25)");
+    } finally {
+      req.close();
+    }
+  }
+
+  public void testFuncSingleValueMathFuncs() throws Exception {
+    SolrQueryRequest req = req("myVal","45", "myField","foo_i");
+    for (final String func : new String[] {"abs","rad","deg","sqrt","cbrt",
+                                           "log","ln","exp","sin","cos","tan",
+                                           "asin","acos","atan",
+                                           "sinh","cosh","tanh",
+                                           "ceil","floor","rint"}) {
+      try {
+        assertFuncEquals(req,
+                         func + "(field(foo_i))", func + "(foo_i)", 
+                         func + "($myField)");
+        assertFuncEquals(req, func + "(45)", func+ "($myVal)");
+      } finally {
+        req.close();
+      }
+    }
+  }
+
+  public void testFuncDoubleValueMathFuncs() throws Exception {
+    SolrQueryRequest req = req("myVal","45", "myOtherVal", "27",
+                               "myField","foo_i");
+    for (final String func : new String[] {"pow","hypot","atan2"}) {
+      try {
+        assertFuncEquals(req,
+                         func + "(field(foo_i),$myVal)", func+"(foo_i,$myVal)", 
+                         func + "($myField,45)");
+        assertFuncEquals(req, 
+                         func+"(45,$myOtherVal)", func+"($myVal,27)",
+                         func+"($myVal,$myOtherVal)");
+                         
+      } finally {
+        req.close();
+      }
+    }
+  }
+
+  public void testFuncStrdist() throws Exception {
+    SolrQueryRequest req = req("myVal","zot", "myOtherVal", "yak",
+                               "myField","foo_s1");
+    try {
+      assertFuncEquals(req,
+                       "strdist(\"zot\",literal('yak'),edit)", 
+                       "strdist(literal(\"zot\"),'yak',   edit  )", 
+                       "strdist(literal($myVal),literal($myOtherVal),edit)");
+      assertFuncEquals(req,
+                       "strdist(\"zot\",literal($myOtherVal),ngram)", 
+                       "strdist(\"zot\",'yak', ngram, 2)");
+      assertFuncEquals(req,
+                       "strdist(field('foo_s1'),literal($myOtherVal),jw)", 
+                       "strdist(field($myField),\"yak\",jw)", 
+                       "strdist($myField,'yak', jw)");
+    } finally {
+      req.close();
+    }
+  }
+  public void testFuncField() throws Exception {
+    assertFuncEquals("field(\"foo_i\")", 
+                     "field('foo_i\')", 
+                     "foo_i");
+  }
+
+  /**
+   * this test does not assert anything itself, it simply toggles a static 
+   * boolean informing an @AfterClass method to assert that every default 
+   * qparser and valuesource parser configured was recorded by 
+   * assertQueryEquals and assertFuncEquals.
+   */
+  public void testParserCoverage() {
+    doAssertParserCoverage = true;
+  }
+
+
+  /**
+   * NOTE: defType is not only used to pick the parser, but also to record 
+   * the parser being tested for coverage sanity checking
+   * @see #testParserCoverage
+   * @see #assertQueryEquals
+   */
+  protected void assertQueryEquals(final String defType,
+                                   final String... inputs) throws Exception {
+    SolrQueryRequest req = req();
+    try {
+      assertQueryEquals(defType, req, inputs);
+    } finally {
+      req.close();
+    }
+  }
+
+  /**
+   * NOTE: defType is not only used to pick the parser, but also to record 
+   * the parser being tested for coverage sanity checking
+   *
+   * @see QueryUtils#check
+   * @see QueryUtils#checkEquals
+   * @see #testParserCoverage
+   */
+  protected void assertQueryEquals(final String defType,
+                                   final SolrQueryRequest req,
+                                   final String... inputs) throws Exception {
+    qParsersTested.add(defType);
+
+    final Query[] queries = new Query[inputs.length];
+
+    try {
+      SolrQueryResponse rsp = new SolrQueryResponse();
+      SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req,rsp));
+      for (int i = 0; i < inputs.length; i++) {
+        queries[i] = (QParser.getParser(inputs[i], defType, req).getQuery());
+      }
+    } finally {
+      SolrRequestInfo.clearRequestInfo();
+    }
+
+    for (int i = 0; i < queries.length; i++) {
+      QueryUtils.check(queries[i]);
+      // yes starting j=0 is redundent, we're making sure every query 
+      // is equal to itself, and that the quality checks work regardless 
+      // of which caller/callee is used.
+      for (int j = 0; j < queries.length; j++) {
+        QueryUtils.checkEqual(queries[i], queries[j]);
+      }
+    }
+  }
+
+  /**
+   * the function name for val parser coverage checking is extracted from
+   * the first input
+   * @see #assertQueryEquals
+   * @see #testParserCoverage
+   */
+  protected void assertFuncEquals(final String... inputs) throws Exception {
+    SolrQueryRequest req = req();
+    try {
+      assertFuncEquals(req, inputs);
+    } finally {
+      req.close();
+    }
+  }
+
+  /**
+   * the function name for val parser coverage checking is extracted from
+   * the first input
+   * @see #assertQueryEquals
+   * @see #testParserCoverage
+   */
+  protected void assertFuncEquals(final SolrQueryRequest req,
+                                  final String... inputs) throws Exception {
+    // pull out the function name
+    final String funcName = (new QueryParsing.StrParser(inputs[0])).getId();
+    valParsersTested.add(funcName);
+
+    assertQueryEquals(FunctionQParserPlugin.NAME, req, inputs);
+  }
+
+
+
+}