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 2014/12/14 05:59:01 UTC

svn commit: r1645384 - in /lucene/dev/branches/branch_5x: ./ solr/ solr/core/ solr/core/src/java/org/apache/solr/request/ solr/core/src/java/org/apache/solr/schema/ solr/core/src/test-files/solr/collection1/conf/ solr/core/src/test/org/apache/solr/requ...

Author: dsmiley
Date: Sun Dec 14 04:59:00 2014
New Revision: 1645384

URL: http://svn.apache.org/r1645384
Log:
SOLR-6827: DateRangeField support for facet.range, exclusive ranges, DateMath

Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/solr/   (props changed)
    lucene/dev/branches/branch_5x/solr/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_5x/solr/core/   (props changed)
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/AbstractSpatialFieldType.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/DateRangeField.java
    lucene/dev/branches/branch_5x/solr/core/src/test-files/solr/collection1/conf/schema.xml
    lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
    lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/schema/DateRangeFieldTest.java

Modified: lucene/dev/branches/branch_5x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/CHANGES.txt?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/solr/CHANGES.txt Sun Dec 14 04:59:00 2014
@@ -104,8 +104,9 @@ New Features
     modifying solr configuration files.  (Erick Erickson)
   - SOLR-5539: Admin UI - Remove ability to create/modify files (steffkes)
 
-* SOLR-6103: Added DateRangeField for indexing date ranges, especially
-  multi-valued ones. Based on LUCENE-5648. (David Smiley)
+* SOLR-6103: Added DateRangeField for indexing date ranges, especially multi-valued ones.
+  Supports facet.range, DateMath, and is mostly interoperable with TrieDateField.
+  Based on LUCENE-5648. (David Smiley)
 
 * SOLR-6403: TransactionLog replay status logging. (Mark Miller)
 

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/request/SimpleFacets.java Sun Dec 14 04:59:00 2014
@@ -37,10 +37,10 @@ import java.util.concurrent.SynchronousQ
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.DocsEnum;
 import org.apache.lucene.index.Fields;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.MultiDocsEnum;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
@@ -74,6 +74,7 @@ import org.apache.solr.common.util.StrUt
 import org.apache.solr.handler.component.ResponseBuilder;
 import org.apache.solr.request.IntervalFacets.FacetInterval;
 import org.apache.solr.schema.BoolField;
+import org.apache.solr.schema.DateRangeField;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
@@ -1080,6 +1081,8 @@ public class SimpleFacets {
               (SolrException.ErrorCode.BAD_REQUEST,
                   "Unable to range facet on tried field of unexpected type:" + f);
       }
+    } else if (ft instanceof DateRangeField) {
+      calc = new DateRangeFieldEndpointCalculator(sf, null);
     } else {
       throw new SolrException
           (SolrException.ErrorCode.BAD_REQUEST,
@@ -1420,6 +1423,7 @@ public class SimpleFacets {
   }
   private static class DateRangeEndpointCalculator 
     extends RangeEndpointCalculator<Date> {
+    private static final String TYPE_ERR_MSG = "SchemaField must use field type extending TrieDateField or DateRangeField";
     private final Date now;
     public DateRangeEndpointCalculator(final SchemaField f, 
                                        final Date now) { 
@@ -1427,7 +1431,7 @@ public class SimpleFacets {
       this.now = now;
       if (! (field.getType() instanceof TrieDateField) ) {
         throw new IllegalArgumentException
-          ("SchemaField must use field type extending TrieDateField");
+          (TYPE_ERR_MSG);
       }
     }
     @Override
@@ -1440,6 +1444,36 @@ public class SimpleFacets {
     }
     @Override
     protected Object parseGap(final String rawval) {
+      return rawval;
+    }
+    @Override
+    public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
+      final DateMathParser dmp = new DateMathParser();
+      dmp.setNow(value);
+      return dmp.parseMath(gap);
+    }
+  }
+  private static class DateRangeFieldEndpointCalculator
+      extends RangeEndpointCalculator<Date> {
+    private final Date now;
+    public DateRangeFieldEndpointCalculator(final SchemaField f,
+                                       final Date now) {
+      super(f);
+      this.now = now;
+      if (! (field.getType() instanceof DateRangeField) ) {
+        throw new IllegalArgumentException(DateRangeEndpointCalculator.TYPE_ERR_MSG);
+      }
+    }
+    @Override
+    public String formatValue(Date val) {
+      return TrieDateField.formatExternal(val);
+    }
+    @Override
+    protected Date parseVal(String rawval) {
+      return ((DateRangeField)field.getType()).parseMath(now, rawval);
+    }
+    @Override
+    protected Object parseGap(final String rawval) {
       return rawval;
     }
     @Override

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/AbstractSpatialFieldType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/AbstractSpatialFieldType.java?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/AbstractSpatialFieldType.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/AbstractSpatialFieldType.java Sun Dec 14 04:59:00 2014
@@ -17,6 +17,19 @@ package org.apache.solr.schema;
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
 import com.google.common.base.Throwables;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
@@ -51,19 +64,6 @@ import org.apache.solr.util.SpatialUtils
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-
 /**
  * Abstract base class for Solr FieldTypes based on a Lucene 4 {@link SpatialStrategy}.
  *
@@ -159,7 +159,7 @@ public abstract class AbstractSpatialFie
   @Override
   public List<IndexableField> createFields(SchemaField field, Object val, float boost) {
     String shapeStr = null;
-    Shape shape = null;
+    Shape shape;
     if (val instanceof Shape) {
       shape = ((Shape) val);
     } else {
@@ -178,14 +178,17 @@ public abstract class AbstractSpatialFie
     }
 
     if (field.stored()) {
-      if (shapeStr == null)
-        shapeStr = shapeToString(shape);
-      result.add(new StoredField(field.getName(), shapeStr));
+      result.add(new StoredField(field.getName(), getStoredValue(shape, shapeStr)));
     }
 
     return result;
   }
 
+  /** Called by {@link #createFields(SchemaField, Object, float)} to get the stored value. */
+  protected String getStoredValue(Shape shape, String shapeStr) {
+    return (shapeStr == null) ? shapeToString(shape) : shapeStr;
+  }
+
   protected Shape parseShape(String str) {
     if (str.length() == 0)
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "empty string shape");

Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/DateRangeField.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/DateRangeField.java?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/DateRangeField.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/schema/DateRangeField.java Sun Dec 14 04:59:00 2014
@@ -35,9 +35,13 @@ import org.apache.lucene.spatial.query.S
 import org.apache.lucene.spatial.query.SpatialOperation;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.search.QParser;
+import org.apache.solr.search.SyntaxError;
 
 /**
+ * A field for indexed dates and date ranges. It's mostly compatible with TrieDateField.
+ *
  * @see NumberRangePrefixTreeStrategy
  * @see DateRangePrefixTree
  */
@@ -45,7 +49,9 @@ public class DateRangeField extends Abst
 
   private static final String OP_PARAM = "op";//local-param to resolve SpatialOperation
 
-  private final DateRangePrefixTree tree = DateRangePrefixTree.INSTANCE;
+  private static final DateRangePrefixTree tree = DateRangePrefixTree.INSTANCE;
+
+  private static final TrieDateField trieDateField = new TrieDateField();//used for utility methods
 
   @Override
   protected void init(IndexSchema schema, Map<String, String> args) {
@@ -64,24 +70,74 @@ public class DateRangeField extends Abst
 
   @Override
   public List<IndexableField> createFields(SchemaField field, Object val, float boost) {
-    if (val instanceof Date || val instanceof Calendar)//From URP
+    if (val instanceof Date || val instanceof Calendar)//From URP?
       val = tree.toUnitShape(val);
     return super.createFields(field, val, boost);
   }
 
   @Override
+  protected String getStoredValue(Shape shape, String shapeStr) {
+    if (shape instanceof UnitNRShape) {
+      UnitNRShape unitShape = (UnitNRShape) shape;
+      if (unitShape.getLevel() == tree.getMaxLevels()) {
+        //fully precise date. We can be fully compatible with TrieDateField.
+        Date date = tree.toCalendar(unitShape).getTime();
+        return TrieDateField.formatExternal(date);
+      }
+    }
+    return (shapeStr == null ? shape.toString() : shapeStr);//we don't normalize ranges here; should we?
+  }
+
+  @Override
   protected NRShape parseShape(String str) {
-    try {
-      return tree.parseShape(str);
-    } catch (ParseException e) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-          "Couldn't parse date because: "+ e.getMessage(), e);
+    if (str.contains(" TO ")) {
+      //TODO parsing range syntax doesn't support DateMath on either side or exclusive/inclusive
+      try {
+        return tree.parseShape(str);
+      } catch (ParseException e) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+            "Couldn't parse date because: "+ e.getMessage(), e);
+      }
+    } else {
+      return tree.toShape(parseCalendar(str));
+    }
+  }
+
+  private Calendar parseCalendar(String str) {
+    if (str.startsWith("NOW") || str.lastIndexOf('Z') >= 0) {
+      //use Solr standard date format parsing rules.
+      //TODO parse a Calendar instead of a Date, rounded according to DateMath syntax.
+      Date date = trieDateField.parseMath(null, str);
+      Calendar cal = tree.newCal();
+      cal.setTime(date);
+      return cal;
+    } else {
+      try {
+        return tree.parseCalendar(str);
+      } catch (ParseException e) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+            "Couldn't parse date because: "+ e.getMessage(), e);
+      }
+
     }
   }
 
+  /** For easy compatibility with {@link TrieDateField#parseMath(Date, String)}. */
+  public Date parseMath(Date now, String rawval) {
+    return trieDateField.parseMath(now, rawval);
+  }
+
   @Override
   protected String shapeToString(Shape shape) {
-    return shape.toString();//generally round-trips for DateRangePrefixTree
+    if (shape instanceof UnitNRShape) {
+      UnitNRShape unitShape = (UnitNRShape) shape;
+      if (unitShape.getLevel() == tree.getMaxLevels()) {
+        //fully precise date. We can be fully compatible with TrieDateField.
+        Date date = tree.toCalendar(unitShape).getTime();
+        return TrieDateField.formatExternal(date);
+      }
+    }
+    return shape.toString();//range shape
   }
 
   @Override
@@ -99,15 +155,38 @@ public class DateRangeField extends Abst
   }
 
   @Override
-  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive, boolean maxInclusive) {
-    if (!minInclusive || !maxInclusive)
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "exclusive range boundary not supported");
-    if (part1 == null)
-      part1 = "*";
-    if (part2 == null)
-      part2 = "*";
-    Shape shape = tree.toRangeShape((UnitNRShape) parseShape(part1), (UnitNRShape) parseShape(part2));
+  public Query getRangeQuery(QParser parser, SchemaField field, String startStr, String endStr, boolean minInclusive, boolean maxInclusive) {
+    if (parser == null) {//null when invoked by SimpleFacets.  But getQueryFromSpatialArgs expects to get localParams.
+      final SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();
+      parser = new QParser("", null, requestInfo.getReq().getParams(), requestInfo.getReq()) {
+        @Override
+        public Query parse() throws SyntaxError {
+          throw new IllegalStateException();
+        }
+      };
+    }
+
+    Calendar startCal;
+    if (startStr == null) {
+      startCal = tree.newCal();
+    } else {
+      startCal = parseCalendar(startStr);
+      if (!minInclusive) {
+        startCal.add(Calendar.MILLISECOND, 1);
+      }
+    }
+    Calendar endCal;
+    if (endStr == null) {
+      endCal = tree.newCal();
+    } else {
+      endCal = parseCalendar(endStr);
+      if (!maxInclusive) {
+        endCal.add(Calendar.MILLISECOND, -1);
+      }
+    }
+    Shape shape = tree.toRangeShape(tree.toShape(startCal), tree.toShape(endCal));
     SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, shape);
     return getQueryFromSpatialArgs(parser, field, spatialArgs);
   }
+
 }

Modified: lucene/dev/branches/branch_5x/solr/core/src/test-files/solr/collection1/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test-files/solr/collection1/conf/schema.xml?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test-files/solr/collection1/conf/schema.xml (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test-files/solr/collection1/conf/schema.xml Sun Dec 14 04:59:00 2014
@@ -575,6 +575,7 @@
    <dynamicField name="*_tdt" type="tdate"  indexed="true"  stored="true"/>
    <dynamicField name="*_tdt1" type="tdate"  indexed="true"  stored="true" multiValued="false"/>
    <dynamicField name="*_tdtdv" type="tdatedv"  indexed="true"  stored="true"/>
+   <dynamicField name="*_drf" type="dateRange"  indexed="true"  stored="true"/>
 
    <dynamicField name="*_sI" type="string"  indexed="true"  stored="false"/>
    <dynamicField name="*_sS" type="string"  indexed="false" stored="true"/>

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java Sun Dec 14 04:59:00 2014
@@ -17,7 +17,12 @@
 
 package org.apache.solr.request;
 
-import org.noggit.ObjectBuilder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -25,10 +30,7 @@ import org.apache.solr.schema.SchemaFiel
 import org.apache.solr.util.TimeZoneUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
+import org.noggit.ObjectBuilder;
 
 
 public class SimpleFacetsTest extends SolrTestCaseJ4 {
@@ -53,6 +55,20 @@ public class SimpleFacetsTest extends So
   // committing randomly gives different looking segments each time
   static void add_doc(String... fieldsAndValues) {
     do {
+      //do our own copy-field:
+      List<String> fieldsAndValuesList = new ArrayList<>(Arrays.asList(fieldsAndValues));
+      int idx = fieldsAndValuesList.indexOf("a_tdt");
+      if (idx >= 0) {
+        fieldsAndValuesList.add("a_drf");
+        fieldsAndValuesList.add(fieldsAndValuesList.get(idx + 1));//copy
+      }
+      idx = fieldsAndValuesList.indexOf("bday");
+      if (idx >= 0) {
+        fieldsAndValuesList.add("bday_drf");
+        fieldsAndValuesList.add(fieldsAndValuesList.get(idx + 1));//copy
+      }
+      fieldsAndValues = fieldsAndValuesList.toArray(new String[fieldsAndValuesList.size()]);
+
       pendingDocs.add(fieldsAndValues);      
     } while (random().nextInt(100) <= random_dupe_percent);
 
@@ -690,6 +706,7 @@ public class SimpleFacetsTest extends So
     final String ooo = "00:00:00.000Z";
     final String xxx = "15:15:15.155Z";
 
+    //note: add_doc duplicates bday to bday_drf and a_tdt to a_drf (date range field)
     add_doc(i, "201",  f, "1976-07-04T12:08:56.235Z", ff, "1900-01-01T"+ooo);
     add_doc(i, "202",  f, "1976-07-05T00:00:00.000Z", ff, "1976-07-01T"+ooo);
     add_doc(i, "203",  f, "1976-07-15T00:07:67.890Z", ff, "1976-07-04T"+ooo);
@@ -716,6 +733,11 @@ public class SimpleFacetsTest extends So
     helpTestDateFacets("bday", true);
   }
 
+  @Test
+  public void testDateRangeFieldFacets() {
+    helpTestDateFacets("bday_drf", true);
+  }
+
   private void helpTestDateFacets(final String fieldName, 
                                   final boolean rangeMode) {
     final String p = rangeMode ? "facet.range" : "facet.date";
@@ -913,8 +935,13 @@ public class SimpleFacetsTest extends So
     helpTestDateFacetsWithIncludeOption("a_tdt", true);
   }
 
-  /** similar to helpTestDateFacets, but for differnet fields with test data 
-      exactly on on boundary marks */
+  @Test
+  public void testDateRangeFieldDateRangeFacetsWithIncludeOption() {
+    helpTestDateFacetsWithIncludeOption("a_drf", true);
+  }
+
+  /** Similar to helpTestDateFacets, but for different fields with test data
+      exactly on boundary marks */
   private void helpTestDateFacetsWithIncludeOption(final String fieldName,
                                                    final boolean rangeMode) {
     final String p = rangeMode ? "facet.range" : "facet.date";

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/schema/DateRangeFieldTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/schema/DateRangeFieldTest.java?rev=1645384&r1=1645383&r2=1645384&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/schema/DateRangeFieldTest.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/schema/DateRangeFieldTest.java Sun Dec 14 04:59:00 2014
@@ -3,7 +3,7 @@ package org.apache.solr.schema;
 /*
  * 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.
+ * this work for additional information regarding copyright ownership.NRP
  * 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
@@ -33,19 +33,24 @@ public class DateRangeFieldTest extends
     assertU(adoc("id", "0", "dateRange", "[* TO *]"));
     assertU(adoc("id", "1", "dateRange", "2014-05-21T12:00:00.000Z"));
     assertU(adoc("id", "2", "dateRange", "[2000 TO 2014-05-21]"));
+    assertU(adoc("id", "3", "dateRange", "2020-05-21T12:00:00.000Z/DAY"));//DateMath syntax
     assertU(commit());
 
-    //ensure stored value is the same (not toString of Shape)
-    assertQ(req("q", "id:1", "fl", "dateRange"), "//result/doc/arr[@name='dateRange']/str[.='2014-05-21T12:00:00.000Z']");
+    //ensure stored value resolves datemath
+    assertQ(req("q", "id:1", "fl", "dateRange"), "//result/doc/arr[@name='dateRange']/str[.='2014-05-21T12:00:00Z']");//no 000 ms
+    assertQ(req("q", "id:2", "fl", "dateRange"), "//result/doc/arr[@name='dateRange']/str[.='[2000 TO 2014-05-21]']");//a range; same
+    assertQ(req("q", "id:3", "fl", "dateRange"), "//result/doc/arr[@name='dateRange']/str[.='2020-05-21T00:00:00Z']");//resolve datemath
 
     String[] commonParams = {"q", "{!field f=dateRange op=$op v=$qq}", "sort", "id asc"};
-    assertQ(req(commonParams, "qq", "[* TO *]"), xpathMatches(0, 1, 2));
+    assertQ(req(commonParams, "qq", "[* TO *]"), xpathMatches(0, 1, 2, 3));
     assertQ(req(commonParams, "qq", "2012"), xpathMatches(0, 2));
     assertQ(req(commonParams, "qq", "2013", "op", "Contains"), xpathMatches(0, 2));
     assertQ(req(commonParams, "qq", "2014", "op", "Contains"), xpathMatches(0));
     assertQ(req(commonParams, "qq", "[1999 TO 2001]", "op", "IsWithin"), xpathMatches());
     assertQ(req(commonParams, "qq", "2014-05", "op", "IsWithin"), xpathMatches(1));
 
+    assertQ(req("q", "dateRange:[1998 TO 2000}"), xpathMatches(0));//exclusive end, so we barely miss one doc
+
     //show without local-params
     assertQ(req("q", "dateRange:\"2014-05-21T12:00:00.000Z\""), xpathMatches(0, 1, 2));
     assertQ(req("q", "dateRange:[1999 TO 2001]"), xpathMatches(0, 2));