You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@lucene.apache.org by ho...@apache.org on 2007/01/08 21:02:52 UTC

svn commit: r494184 - in /lucene/java/trunk: CHANGES.txt src/java/org/apache/lucene/queryParser/QueryParser.java src/java/org/apache/lucene/queryParser/QueryParser.jj src/test/org/apache/lucene/queryParser/TestQueryParser.java

Author: hossman
Date: Mon Jan  8 12:02:51 2007
New Revision: 494184

URL: http://svn.apache.org/viewvc?view=rev&rev=494184
Log:
LUCENE-732 - DateTools support for QueryParser, Resolution can be set on a per field basis

Modified:
    lucene/java/trunk/CHANGES.txt
    lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.java
    lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.jj
    lucene/java/trunk/src/test/org/apache/lucene/queryParser/TestQueryParser.java

Modified: lucene/java/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/java/trunk/CHANGES.txt?view=diff&rev=494184&r1=494183&r2=494184
==============================================================================
--- lucene/java/trunk/CHANGES.txt (original)
+++ lucene/java/trunk/CHANGES.txt Mon Jan  8 12:02:51 2007
@@ -170,6 +170,11 @@
     on an older format index will create a single .nrm file for the new
     segment.  (Doron Cohen via Yonik Seeley)
 
+14. LUCENE-732: DateTools support has been added to QueryParser, with
+    setters for both the default Resolution, and per-field Resolution.
+    For backwards compatibility, DateField is still used if no Resolutions
+    are specified. (Michael Busch via Chris Hostetter)
+    
 Bug fixes
 
  1. Fixed the web application demo (built with "ant war-demo") which

Modified: lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.java?view=diff&rev=494184&r1=494183&r2=494184
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.java (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.java Mon Jan  8 12:02:51 2007
@@ -45,14 +45,31 @@
  * documentation</a>.
  * </p>
  *
- * <p>In {@link RangeQuery}s, QueryParser tries to detect date values, e.g. <tt>date:[6/1/2005 TO 6/4/2005]</tt>
- * produces a range query that searches for "date" fields between 2005-06-01 and 2005-06-04. Note
- * that the format of the accpeted input depends on {@link #setLocale(Locale) the locale}. This
- * feature also assumes that your index uses the {@link DateField} class to store dates.
- * If you use a different format (e.g. {@link DateTools}) and you still want QueryParser
- * to turn local dates in range queries into valid queries you need to create your own
+ * <p>
+ * In {@link RangeQuery}s, QueryParser tries to detect date values, e.g.
+ * <tt>date:[6/1/2005 TO 6/4/2005]</tt> produces a range query that searches
+ * for "date" fields between 2005-06-01 and 2005-06-04. Note that the format
+ * of the accepted input depends on {@link #setLocale(Locale) the locale}.
+ * By default a date is converted into a search term using the deprecated
+ * {@link DateField} for compatibility reasons.
+ * To use the new {@link DateTools} to convert dates, a
+ * {@link DateTools.Resolution} has to be set.
+ * </p>
+ * <p>
+ * The date resolution that shall be used for RangeQueries can be set
+ * using {@link #setDateResolution(DateTools.Resolution)}
+ * or {@link #setDateResolution(String, DateTools.Resolution)}. The former
+ * sets the default date resolution for all fields, whereas the latter can
+ * be used to set field specific date resolutions. Field specific date
+ * resolutions take, if set, precedence over the default date resolution.
+ * </p>
+ * <p>
+ * If you use neither {@link DateField} nor {@link DateTools} in your
+ * index, you can create your own
  * query parser that inherits QueryParser and overwrites
- * {@link #getRangeQuery(String, String, String, boolean)}.</p>
+ * {@link #getRangeQuery(String, String, String, boolean)} to
+ * use a different method for date conversion.
+ * </p>
  *
  * <p>Note that QueryParser is <em>not</em> thread-safe.</p>
  *
@@ -60,7 +77,6 @@
  * @author Peter Halacsy
  * @author Tatu Saloranta
  */
-
 public class QueryParser implements QueryParserConstants {
 
   private static final int CONJ_NONE   = 0;
@@ -92,6 +108,11 @@
   int fuzzyPrefixLength = FuzzyQuery.defaultPrefixLength;
   Locale locale = Locale.getDefault();
 
+  // the default date resolution
+  DateTools.Resolution dateResolution = null;
+  // maps field names to date resolutions
+  Map fieldToDateResolution = null;
+
   /** The default operator for parsing queries. 
    * Use {@link QueryParser#setDefaultOperator} to change it.
    */
@@ -286,6 +307,61 @@
     return locale;
   }
 
+  /**
+   * Sets the default date resolution used by RangeQueries for fields for which no
+   * specific date resolutions has been set. Field specific resolutions can be set
+   * with {@link #setDateResolution(String, DateTools.Resolution)}.
+   *  
+   * @param dateResolution the default date resolution to set
+   */
+  public void setDateResolution(DateTools.Resolution dateResolution) {
+    this.dateResolution = dateResolution;
+  }
+
+  /**
+   * Sets the date resolution used by RangeQueries for a specific field.
+   *  
+   * @param field field for which the date resolution is to be set 
+   * @param dateResolution date resolution to set
+   */
+  public void setDateResolution(String fieldName, DateTools.Resolution dateResolution) {
+    if (fieldName == null) {
+      throw new IllegalArgumentException("Field cannot be null.");
+    }
+
+    if (fieldToDateResolution == null) {
+      // lazily initialize HashMap
+      fieldToDateResolution = new HashMap();
+    }
+
+    fieldToDateResolution.put(fieldName, dateResolution);
+  }
+
+  /**
+   * Returns the date resolution that is used by RangeQueries for the given field. 
+   * Returns null, if no default or field specific date resolution has been set
+   * for the given field.
+   *
+   */
+  public DateTools.Resolution getDateResolution(String fieldName) {
+    if (fieldName == null) {
+      throw new IllegalArgumentException("Field cannot be null.");
+    }
+
+    if (fieldToDateResolution == null) {
+      // no field specific date resolutions set; return default date resolution instead
+      return this.dateResolution;
+    }
+
+    DateTools.Resolution resolution = (DateTools.Resolution) fieldToDateResolution.get(fieldName);
+    if (resolution == null) {
+      // no date resolutions set for the given field; return default date resolution instead
+      resolution = this.dateResolution;
+    }
+
+    return resolution;
+  }
+
   protected void addClause(Vector clauses, int conj, int mods, Query q) {
     boolean required, prohibited;
 
@@ -472,8 +548,17 @@
         cal.set(Calendar.MILLISECOND, 999);
         d2 = cal.getTime();
       }
-      part1 = DateField.dateToString(d1);
-      part2 = DateField.dateToString(d2);
+      DateTools.Resolution resolution = getDateResolution(field);
+      if (resolution == null) {
+        // no default or field specific date resolution has been set,
+        // use deprecated DateField to maintain compatibilty with
+        // pre-1.9 Lucene versions.
+        part1 = DateField.dateToString(d1);
+        part2 = DateField.dateToString(d2);
+      } else {
+        part1 = DateTools.dateToString(d1, resolution);
+        part2 = DateTools.dateToString(d2, resolution);
+      }
     }
     catch (Exception e) { }
 
@@ -1158,6 +1243,12 @@
     finally { jj_save(0, xla); }
   }
 
+  final private boolean jj_3R_3() {
+    if (jj_scan_token(STAR)) return true;
+    if (jj_scan_token(COLON)) return true;
+    return false;
+  }
+
   final private boolean jj_3R_2() {
     if (jj_scan_token(TERM)) return true;
     if (jj_scan_token(COLON)) return true;
@@ -1171,12 +1262,6 @@
     jj_scanpos = xsp;
     if (jj_3R_3()) return true;
     }
-    return false;
-  }
-
-  final private boolean jj_3R_3() {
-    if (jj_scan_token(STAR)) return true;
-    if (jj_scan_token(COLON)) return true;
     return false;
   }
 

Modified: lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.jj
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.jj?view=diff&rev=494184&r1=494183&r2=494184
==============================================================================
--- lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.jj (original)
+++ lucene/java/trunk/src/java/org/apache/lucene/queryParser/QueryParser.jj Mon Jan  8 12:02:51 2007
@@ -69,14 +69,31 @@
  * documentation</a>.
  * </p>
  *
- * <p>In {@link RangeQuery}s, QueryParser tries to detect date values, e.g. <tt>date:[6/1/2005 TO 6/4/2005]</tt>
- * produces a range query that searches for "date" fields between 2005-06-01 and 2005-06-04. Note
- * that the format of the accpeted input depends on {@link #setLocale(Locale) the locale}. This
- * feature also assumes that your index uses the {@link DateField} class to store dates.
- * If you use a different format (e.g. {@link DateTools}) and you still want QueryParser
- * to turn local dates in range queries into valid queries you need to create your own
+ * <p>
+ * In {@link RangeQuery}s, QueryParser tries to detect date values, e.g.
+ * <tt>date:[6/1/2005 TO 6/4/2005]</tt> produces a range query that searches
+ * for "date" fields between 2005-06-01 and 2005-06-04. Note that the format
+ * of the accepted input depends on {@link #setLocale(Locale) the locale}.
+ * By default a date is converted into a search term using the deprecated
+ * {@link DateField} for compatibility reasons.
+ * To use the new {@link DateTools} to convert dates, a
+ * {@link DateTools.Resolution} has to be set.
+ * </p>
+ * <p>
+ * The date resolution that shall be used for RangeQueries can be set
+ * using {@link #setDateResolution(DateTools.Resolution)}
+ * or {@link #setDateResolution(String, DateTools.Resolution)}. The former
+ * sets the default date resolution for all fields, whereas the latter can
+ * be used to set field specific date resolutions. Field specific date
+ * resolutions take, if set, precedence over the default date resolution.
+ * </p>
+ * <p>
+ * If you use neither {@link DateField} nor {@link DateTools} in your
+ * index, you can create your own
  * query parser that inherits QueryParser and overwrites
- * {@link #getRangeQuery(String, String, String, boolean)}.</p>
+ * {@link #getRangeQuery(String, String, String, boolean)} to
+ * use a different method for date conversion.
+ * </p>
  *
  * <p>Note that QueryParser is <em>not</em> thread-safe.</p>
  *
@@ -84,7 +101,6 @@
  * @author Peter Halacsy
  * @author Tatu Saloranta
  */
-
 public class QueryParser {
 
   private static final int CONJ_NONE   = 0;
@@ -116,6 +132,11 @@
   int fuzzyPrefixLength = FuzzyQuery.defaultPrefixLength;
   Locale locale = Locale.getDefault();
 
+  // the default date resolution
+  DateTools.Resolution dateResolution = null;
+  // maps field names to date resolutions
+  Map fieldToDateResolution = null;
+
   /** The default operator for parsing queries. 
    * Use {@link QueryParser#setDefaultOperator} to change it.
    */
@@ -310,6 +331,61 @@
     return locale;
   }
 
+  /**
+   * Sets the default date resolution used by RangeQueries for fields for which no
+   * specific date resolutions has been set. Field specific resolutions can be set
+   * with {@link #setDateResolution(String, DateTools.Resolution)}.
+   *  
+   * @param dateResolution the default date resolution to set
+   */
+  public void setDateResolution(DateTools.Resolution dateResolution) {
+    this.dateResolution = dateResolution;
+  }
+  
+  /**
+   * Sets the date resolution used by RangeQueries for a specific field.
+   *  
+   * @param field field for which the date resolution is to be set 
+   * @param dateResolution date resolution to set
+   */
+  public void setDateResolution(String fieldName, DateTools.Resolution dateResolution) {
+    if (fieldName == null) {
+      throw new IllegalArgumentException("Field cannot be null.");
+    }
+    
+    if (fieldToDateResolution == null) {
+      // lazily initialize HashMap
+      fieldToDateResolution = new HashMap();
+    }
+    
+    fieldToDateResolution.put(fieldName, dateResolution);
+  }
+
+  /**
+   * Returns the date resolution that is used by RangeQueries for the given field. 
+   * Returns null, if no default or field specific date resolution has been set
+   * for the given field.
+   *
+   */
+  public DateTools.Resolution getDateResolution(String fieldName) {
+    if (fieldName == null) {
+      throw new IllegalArgumentException("Field cannot be null.");
+    }
+    
+    if (fieldToDateResolution == null) {
+      // no field specific date resolutions set; return default date resolution instead
+      return this.dateResolution;
+    }
+    
+    DateTools.Resolution resolution = (DateTools.Resolution) fieldToDateResolution.get(fieldName);
+    if (resolution == null) {
+      // no date resolutions set for the given field; return default date resolution instead
+      resolution = this.dateResolution;
+    }
+    
+    return resolution;
+  }
+
   protected void addClause(Vector clauses, int conj, int mods, Query q) {
     boolean required, prohibited;
 
@@ -496,8 +572,17 @@
         cal.set(Calendar.MILLISECOND, 999);
         d2 = cal.getTime();
       }
-      part1 = DateField.dateToString(d1);
-      part2 = DateField.dateToString(d2);
+      DateTools.Resolution resolution = getDateResolution(field);
+      if (resolution == null) {
+        // no default or field specific date resolution has been set,
+        // use deprecated DateField to maintain compatibilty with
+        // pre-1.9 Lucene versions.
+        part1 = DateField.dateToString(d1);
+        part2 = DateField.dateToString(d2);
+      } else {
+        part1 = DateTools.dateToString(d1, resolution);
+        part2 = DateTools.dateToString(d2, resolution);
+      }
     }
     catch (Exception e) { }
 

Modified: lucene/java/trunk/src/test/org/apache/lucene/queryParser/TestQueryParser.java
URL: http://svn.apache.org/viewvc/lucene/java/trunk/src/test/org/apache/lucene/queryParser/TestQueryParser.java?view=diff&rev=494184&r1=494183&r2=494184
==============================================================================
--- lucene/java/trunk/src/test/org/apache/lucene/queryParser/TestQueryParser.java (original)
+++ lucene/java/trunk/src/test/org/apache/lucene/queryParser/TestQueryParser.java Mon Jan  8 12:02:51 2007
@@ -27,6 +27,7 @@
 import org.apache.lucene.analysis.WhitespaceAnalyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.document.DateField;
+import org.apache.lucene.document.DateTools;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.index.IndexWriter;
@@ -38,6 +39,7 @@
 import java.io.Reader;
 import java.text.DateFormat;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.Locale;
 
 /**
@@ -127,6 +129,16 @@
     }
   }
 
+  public void assertQueryEquals(QueryParser qp, String field, String query, String result) 
+    throws Exception {
+    Query q = qp.parse(query);
+    String s = q.toString(field);
+    if (!s.equals(result)) {
+      fail("Query /" + query + "/ yielded /" + s
+           + "/, expecting /" + result + "/");
+    }
+  }
+  
   public void assertEscapedQueryEquals(String query, Analyzer a, String result)
     throws Exception {
     String escapedQuery = QueryParser.escape(query);
@@ -378,12 +390,28 @@
     assertQueryEquals("( bar blar { a TO z}) ", null, "bar blar {a TO z}");
     assertQueryEquals("gack ( bar blar { a TO z}) ", null, "gack (bar blar {a TO z})");
   }
-
-  private String getDate(String s) throws Exception {
+  
+  /** for testing legacy DateField support */
+  private String getLegacyDate(String s) throws Exception {
     DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
     return DateField.dateToString(df.parse(s));
   }
 
+  /** for testing DateTools support */
+  private String getDate(String s, DateTools.Resolution resolution) throws Exception {
+    DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
+    return getDate(df.parse(s), resolution);      
+  }
+  
+  /** for testing DateTools support */
+  private String getDate(Date d, DateTools.Resolution resolution) throws Exception {
+      if (resolution == null) {
+        return DateField.dateToString(d);      
+      } else {
+        return DateTools.dateToString(d, resolution);
+      }
+    }
+  
   private String getLocalizedDate(int year, int month, int day, boolean extendLastDate) {
     DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
     Calendar calendar = Calendar.getInstance();
@@ -397,16 +425,66 @@
     return df.format(calendar.getTime());
   }
 
-  public void testDateRange() throws Exception {
+  /** for testing legacy DateField support */
+  public void testLegacyDateRange() throws Exception {
     String startDate = getLocalizedDate(2002, 1, 1, false);
     String endDate = getLocalizedDate(2002, 1, 4, false);
     Calendar endDateExpected = Calendar.getInstance();
     endDateExpected.set(2002, 1, 4, 23, 59, 59);
     endDateExpected.set(Calendar.MILLISECOND, 999);
     assertQueryEquals("[ " + startDate + " TO " + endDate + "]", null,
-                      "[" + getDate(startDate) + " TO " + DateField.dateToString(endDateExpected.getTime()) + "]");
+                      "[" + getLegacyDate(startDate) + " TO " + DateField.dateToString(endDateExpected.getTime()) + "]");
     assertQueryEquals("{  " + startDate + "    " + endDate + "   }", null,
-                      "{" + getDate(startDate) + " TO " + getDate(endDate) + "}");
+                      "{" + getLegacyDate(startDate) + " TO " + getLegacyDate(endDate) + "}");
+  }
+  
+  public void testDateRange() throws Exception {
+    String startDate = getLocalizedDate(2002, 1, 1, false);
+    String endDate = getLocalizedDate(2002, 1, 4, false);
+    Calendar endDateExpected = Calendar.getInstance();
+    endDateExpected.set(2002, 1, 4, 23, 59, 59);
+    endDateExpected.set(Calendar.MILLISECOND, 999);
+    final String defaultField = "default";
+    final String monthField = "month";
+    final String hourField = "hour";
+    QueryParser qp = new QueryParser("field", new SimpleAnalyzer());
+    
+    // Don't set any date resolution and verify if DateField is used
+    assertDateRangeQueryEquals(qp, defaultField, startDate, endDate, 
+                               endDateExpected.getTime(), null);
+    
+    // set a field specific date resolution
+    qp.setDateResolution(monthField, DateTools.Resolution.MONTH);
+    
+    // DateField should still be used for defaultField
+    assertDateRangeQueryEquals(qp, defaultField, startDate, endDate, 
+                               endDateExpected.getTime(), null);
+    
+    // set default date resolution to MILLISECOND 
+    qp.setDateResolution(DateTools.Resolution.MILLISECOND);
+    
+    // set second field specific date resolution    
+    qp.setDateResolution(hourField, DateTools.Resolution.HOUR);
+
+    // for this field no field specific date resolution has been set,
+    // so verify if the default resolution is used
+    assertDateRangeQueryEquals(qp, defaultField, startDate, endDate, 
+            endDateExpected.getTime(), DateTools.Resolution.MILLISECOND);
+
+    // verify if field specific date resolutions are used for these two fields
+    assertDateRangeQueryEquals(qp, monthField, startDate, endDate, 
+            endDateExpected.getTime(), DateTools.Resolution.MONTH);
+
+    assertDateRangeQueryEquals(qp, hourField, startDate, endDate, 
+            endDateExpected.getTime(), DateTools.Resolution.HOUR);  
+  }
+  
+  public void assertDateRangeQueryEquals(QueryParser qp, String field, String startDate, String endDate, 
+                                         Date endDateInclusive, DateTools.Resolution resolution) throws Exception {
+    assertQueryEquals(qp, field, field + ":[" + startDate + " TO " + endDate + "]",
+               "[" + getDate(startDate, resolution) + " TO " + getDate(endDateInclusive, resolution) + "]");
+    assertQueryEquals(qp, field, field + ":{" + startDate + " TO " + endDate + "}",
+               "{" + getDate(startDate, resolution) + " TO " + getDate(endDate, resolution) + "}");
   }
 
   public void testEscaped() throws Exception {