You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2016/07/29 02:45:59 UTC

lucene-solr:master: SOLR-9279: new function queries: gt, gte, lt, lte, eq Lucene Queries module: new ComparisonBoolFunction base class

Repository: lucene-solr
Updated Branches:
  refs/heads/master cead204fb -> d12b93e27


SOLR-9279: new function queries: gt, gte, lt, lte, eq
Lucene Queries module: new ComparisonBoolFunction base class


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

Branch: refs/heads/master
Commit: d12b93e2729036b0c04621114429c25739499243
Parents: cead204
Author: David Smiley <ds...@apache.org>
Authored: Thu Jul 28 22:45:43 2016 -0400
Committer: David Smiley <ds...@apache.org>
Committed: Thu Jul 28 22:45:43 2016 -0400

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../valuesource/ComparisonBoolFunction.java     | 105 +++++++++++++++++++
 solr/CHANGES.txt                                |   3 +
 .../apache/solr/search/ValueSourceParser.java   |  52 +++++++++
 .../function/SolrComparisonBoolFunction.java    |  58 ++++++++++
 .../apache/solr/search/QueryEqualityTest.java   |  27 ++++-
 .../solr/search/function/TestFunctionQuery.java |  69 +++++++++++-
 7 files changed, 311 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 6958660..a685839 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -65,6 +65,9 @@ New Features
   Polygon instances from a standard GeoJSON string (Robert Muir, Mike
   McCandless)
 
+* SOLR-9279: Queries module: new ComparisonBoolFunction base class
+  (Doug Turnbull via David Smiley)
+
 Bug Fixes
 
 * LUCENE-6662: Fixed potential resource leaks. (Rishabh Patel via Adrien Grand)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ComparisonBoolFunction.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ComparisonBoolFunction.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ComparisonBoolFunction.java
new file mode 100644
index 0000000..82d723a
--- /dev/null
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/ComparisonBoolFunction.java
@@ -0,0 +1,105 @@
+/*
+ * 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.lucene.queries.function.valuesource;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.docvalues.BoolDocValues;
+import org.apache.lucene.search.IndexSearcher;
+
+/**
+ * Base class for comparison operators useful within an "if"/conditional.
+ */
+public abstract class ComparisonBoolFunction extends BoolFunction {
+
+  private final ValueSource lhs;
+  private final ValueSource rhs;
+  private final String name;
+
+  public ComparisonBoolFunction(ValueSource lhs, ValueSource rhs, String name) {
+    this.lhs = lhs;
+    this.rhs = rhs;
+    this.name = name;
+  }
+
+  /** Perform the comparison, returning true or false */
+  public abstract boolean compare(int doc, FunctionValues lhs, FunctionValues rhs);
+
+  /** Uniquely identify the operation (ie "gt", "lt" "gte", etc) */
+  public String name() {
+    return this.name;
+  }
+
+  @Override
+  public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
+    final FunctionValues lhsVal = this.lhs.getValues(context, readerContext);
+    final FunctionValues rhsVal = this.rhs.getValues(context, readerContext);
+    final String compLabel = this.name();
+
+    return new BoolDocValues(this) {
+      @Override
+      public boolean boolVal(int doc) {
+        return compare(doc, lhsVal, rhsVal);
+      }
+
+      @Override
+      public String toString(int doc) {
+        return compLabel + "(" + lhsVal.toString(doc) + "," + rhsVal.toString(doc) + ")";
+      }
+
+      @Override
+      public boolean exists(int doc) {
+        return lhsVal.exists(doc) && rhsVal.exists(doc);
+      }
+
+    };
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this.getClass() != o.getClass()) return false;
+    ComparisonBoolFunction other = (ComparisonBoolFunction)o;
+    return name().equals(other.name())
+        && lhs.equals(other.lhs)
+        && rhs.equals(other.rhs);  }
+
+  @Override
+  public int hashCode() {
+    int h = this.getClass().hashCode();
+    h = h * 31 + this.name().hashCode();
+    h = h * 31 + lhs.hashCode();
+    h = h * 31 + rhs.hashCode();
+    return h;
+  }
+
+  @Override
+  public String description() {
+      return name() + "(" + lhs.description() + "," + rhs.description() + ")";
+  }
+
+  @Override
+  public void createWeight(Map context, IndexSearcher searcher) throws IOException {
+    lhs.createWeight(context, searcher);
+    rhs.createWeight(context, searcher);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 22d4e99..58743b1 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -108,6 +108,9 @@ New Features
   doing a core backup, and in replication.  Snapshot metadata is stored in a new snapshot_metadata/ dir.
   (Hrishikesh Gadre via David Smiley)
 
+* SOLR-9279: New boolean comparison function queries comparing numeric arguments: gt, gte, lt, lte, eq
+  (Doug Turnbull, David Smiley)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
index 65a4d0d..dd0db96 100644
--- a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
@@ -64,6 +64,7 @@ import org.apache.solr.search.facet.UniqueAgg;
 import org.apache.solr.search.function.CollapseScoreFunction;
 import org.apache.solr.search.function.OrdFieldSource;
 import org.apache.solr.search.function.ReverseOrdFieldSource;
+import org.apache.solr.search.function.SolrComparisonBoolFunction;
 import org.apache.solr.search.function.distance.GeoDistValueSourceParser;
 import org.apache.solr.search.function.distance.GeohashFunction;
 import org.apache.solr.search.function.distance.GeohashHaversineFunction;
@@ -822,6 +823,57 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
       }
     });
 
+    addParser("gt", new ValueSourceParser() {
+      @Override
+      public ValueSource parse(FunctionQParser fp) throws SyntaxError {
+        ValueSource lhsValSource = fp.parseValueSource();
+        ValueSource rhsValSource = fp.parseValueSource();
+
+        return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "gt", (cmp) -> cmp > 0);
+      }
+    });
+
+    addParser("lt", new ValueSourceParser() {
+      @Override
+      public ValueSource parse(FunctionQParser fp) throws SyntaxError {
+        ValueSource lhsValSource = fp.parseValueSource();
+        ValueSource rhsValSource = fp.parseValueSource();
+
+        return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "lt", (cmp) -> cmp < 0);
+      }
+    });
+
+    addParser("gte", new ValueSourceParser() {
+      @Override
+      public ValueSource parse(FunctionQParser fp) throws SyntaxError {
+        ValueSource lhsValSource = fp.parseValueSource();
+        ValueSource rhsValSource = fp.parseValueSource();
+
+        return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "gte", (cmp) -> cmp >= 0);
+
+      }
+    });
+
+    addParser("lte", new ValueSourceParser() {
+      @Override
+      public ValueSource parse(FunctionQParser fp) throws SyntaxError {
+        ValueSource lhsValSource = fp.parseValueSource();
+        ValueSource rhsValSource = fp.parseValueSource();
+
+        return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "lte", (cmp) -> cmp <= 0);
+      }
+    });
+
+    addParser("eq", new ValueSourceParser() {
+      @Override
+      public ValueSource parse(FunctionQParser fp) throws SyntaxError {
+        ValueSource lhsValSource = fp.parseValueSource();
+        ValueSource rhsValSource = fp.parseValueSource();
+
+        return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "eq", (cmp) -> cmp == 0);
+      }
+    });
+
     addParser("def", new ValueSourceParser() {
       @Override
       public ValueSource parse(FunctionQParser fp) throws SyntaxError {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/solr/core/src/java/org/apache/solr/search/function/SolrComparisonBoolFunction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/function/SolrComparisonBoolFunction.java b/solr/core/src/java/org/apache/solr/search/function/SolrComparisonBoolFunction.java
new file mode 100644
index 0000000..c994fbb
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/function/SolrComparisonBoolFunction.java
@@ -0,0 +1,58 @@
+/*
+ * 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.search.function;
+
+import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.docvalues.IntDocValues;
+import org.apache.lucene.queries.function.docvalues.LongDocValues;
+import org.apache.lucene.queries.function.valuesource.ComparisonBoolFunction;
+
+/**
+ * Refines {@link ComparisonBoolFunction} to compare based on a 'long' or 'double' depending on if the
+ * any of the FunctionValues are {@link LongDocValues}.
+ */
+public class SolrComparisonBoolFunction extends ComparisonBoolFunction {
+
+  private final Compare cmp;
+
+  public interface Compare {
+    boolean compare(int integer);
+  }
+
+  public SolrComparisonBoolFunction(ValueSource lhs, ValueSource rhs, String name, Compare cmp) {
+    super(lhs, rhs, name);
+    this.cmp = cmp;
+  }
+
+  @Override
+  public boolean compare(int doc, FunctionValues lhs, FunctionValues rhs) {
+    // TODO consider a separate FunctionValues impl, one for Long, one for Double
+    // performs the safest possible numeric comparison, if both lhs and rhs are Longs, then
+    // we perform a Long comparison to avoid the issues with precision when casting to doubles
+    boolean lhsAnInt = (lhs instanceof LongDocValues || lhs instanceof IntDocValues);
+    boolean rhsAnInt = (rhs instanceof LongDocValues || rhs instanceof IntDocValues);
+    if (lhsAnInt && rhsAnInt) {
+      return cmp.compare(Long.compare(lhs.longVal(doc), rhs.longVal(doc)));
+    } else {
+      return cmp.compare(Double.compare(lhs.doubleVal(doc), rhs.doubleVal(doc)));
+    }
+  }
+
+  // note: don't override equals; the "name" will be unique and is already compared
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
index 2a89473..9c51844 100644
--- a/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
+++ b/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
@@ -15,6 +15,11 @@
  * limitations under the License.
  */
 package org.apache.solr.search;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.QueryUtils;
 import org.apache.solr.SolrTestCaseJ4;
@@ -24,10 +29,6 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
 
 
 /**
@@ -1075,4 +1076,22 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
     // assertFuncEquals("agg_multistat(foo_i)", "agg_multistat(foo_i)");
   }
 
+  public void testCompares() throws Exception {
+    assertFuncEquals("gt(foo_i,2)", "gt(foo_i, 2)");
+    assertFuncEquals("gt(foo_i,2)", "gt(foo_i,2)");
+    assertFuncEquals("lt(foo_i,2)", "lt(foo_i,2)");
+    assertFuncEquals("lte(foo_i,2)", "lte(foo_i,2)");
+    assertFuncEquals("gte(foo_i,2)", "gte(foo_i,2)");
+    assertFuncEquals("eq(foo_i,2)", "eq(foo_i,2)");
+
+    boolean equals = false;
+    try {
+      assertFuncEquals("eq(foo_i,2)", "lt(foo_i,2)");
+      equals = true;
+    } catch (AssertionError e) {
+      //expected
+    }
+    assertFalse(equals);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d12b93e2/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java b/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
index f94a9ee..8c65b58 100644
--- a/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
+++ b/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
@@ -98,7 +98,7 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
     return sb.toString();
   }
 
-  void singleTest(String field, String funcTemplate, List<String> args, float... results) {
+  protected void singleTest(String field, String funcTemplate, List<String> args, float... results) {
     String parseableQuery = func(field, funcTemplate);
 
     List<String> nargs = new ArrayList<>(Arrays.asList("q", parseableQuery
@@ -793,4 +793,69 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
     }
   }
 
-}
+  @Test
+  public void testNumericComparisons() throws Exception {
+    assertU(adoc("id", "1", "age_i", "35"));
+    assertU(adoc("id", "2", "age_i", "25"));
+    assertU(commit());
+
+    // test weighting of functions
+    assertJQ(req("q", "id:1", "fl", "a:gt(age_i,30),b:lt(age_i,30)")
+        , "/response/docs/[0]=={'a':true,'b':false}");
+
+    assertJQ(req("q", "id:1", "fl", "a:exists(gt(foo_i,30))")
+        , "/response/docs/[0]=={'a':false}");
+
+    singleTest("age_i", "if(gt(age_i,30),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/2);
+
+    singleTest("age_i", "if(lt(age_i,30),5,2)",
+               /*id*/1, /*score*/2,
+               /*id*/2, /*score*/5);
+
+    singleTest("age_i", "if(lt(age_i,34.5),5,2)",
+               /*id*/1, /*score*/2,
+               /*id*/2, /*score*/5);
+
+    singleTest("age_i", "if(lte(age_i,35),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/5);
+
+    singleTest("age_i", "if(gte(age_i,25),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/5);
+
+    singleTest("age_i", "if(lte(age_i,25),5,2)",
+               /*id*/1, /*score*/2,
+               /*id*/2, /*score*/5);
+
+    singleTest("age_i", "if(gte(age_i,35),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/2);
+
+
+    singleTest("age_i", "if(eq(age_i,30),5,2)",
+               /*id*/1, /*score*/2,
+               /*id*/2, /*score*/2);
+
+    singleTest("age_i", "if(eq(age_i,35),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/2);
+  }
+
+  public void testLongComparisons() {
+    assertU(adoc("id", "1", "number_of_atoms_in_universe_l", Long.toString(Long.MAX_VALUE)));
+    assertU(adoc("id", "2", "number_of_atoms_in_universe_l", Long.toString(Long.MAX_VALUE - 1)));
+    assertU(commit());
+
+    singleTest("number_of_atoms_in_universe_l", "if(gt(number_of_atoms_in_universe_l," + Long.toString(Long.MAX_VALUE - 1) + "),5,2)",
+               /*id*/1, /*score*/5,
+               /*id*/2, /*score*/2);
+
+    singleTest("number_of_atoms_in_universe_l", "if(lt(number_of_atoms_in_universe_l," + Long.toString(Long.MAX_VALUE) + "),5,2)",
+               /*id*/2, /*score*/5,
+               /*id*/1, /*score*/2);
+  }
+
+  }