mirror of https://github.com/apache/lucene.git
LUCENE-732 - DateTools support for QueryParser, Resolution can be set on a per field basis
git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@494184 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
dc20634a97
commit
ceaa0c3091
|
@ -170,6 +170,11 @@ API Changes
|
|||
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
|
||||
|
|
|
@ -45,14 +45,31 @@ import org.apache.lucene.util.Parameter;
|
|||
* 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 @@ import org.apache.lucene.util.Parameter;
|
|||
* @author Peter Halacsy
|
||||
* @author Tatu Saloranta
|
||||
*/
|
||||
|
||||
public class QueryParser implements QueryParserConstants {
|
||||
|
||||
private static final int CONJ_NONE = 0;
|
||||
|
@ -92,6 +108,11 @@ public class QueryParser implements QueryParserConstants {
|
|||
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 @@ public class QueryParser implements QueryParserConstants {
|
|||
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 @@ public class QueryParser implements QueryParserConstants {
|
|||
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 @@ public class QueryParser implements QueryParserConstants {
|
|||
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;
|
||||
|
@ -1174,12 +1265,6 @@ public class QueryParser implements QueryParserConstants {
|
|||
return false;
|
||||
}
|
||||
|
||||
final private boolean jj_3R_3() {
|
||||
if (jj_scan_token(STAR)) return true;
|
||||
if (jj_scan_token(COLON)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public QueryParserTokenManager token_source;
|
||||
public Token token, jj_nt;
|
||||
private int jj_ntk;
|
||||
|
|
|
@ -69,14 +69,31 @@ import org.apache.lucene.util.Parameter;
|
|||
* 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 @@ import org.apache.lucene.util.Parameter;
|
|||
* @author Peter Halacsy
|
||||
* @author Tatu Saloranta
|
||||
*/
|
||||
|
||||
public class QueryParser {
|
||||
|
||||
private static final int CONJ_NONE = 0;
|
||||
|
@ -116,6 +132,11 @@ public class QueryParser {
|
|||
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 @@ public class QueryParser {
|
|||
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 @@ public class QueryParser {
|
|||
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) { }
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.apache.lucene.analysis.TokenStream;
|
|||
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.IOException;
|
|||
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 class TestQueryParser extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ public class TestQueryParser extends TestCase {
|
|||
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 @@ public class TestQueryParser extends TestCase {
|
|||
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 {
|
||||
|
|
Loading…
Reference in New Issue