LUCENE-1768: Committing new NumericRangeQuery support by Vinicius Barros to flexible StandardQueryParser. This first commit supports all functionality, but still needs some improvements, that will be part of 2nd half of GSoC.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1144744 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Uwe Schindler 2011-07-09 21:05:13 +00:00
parent cf6fb18337
commit c178c8eb1a
30 changed files with 1677 additions and 77 deletions

View File

@ -51,5 +51,8 @@ public class QueryParserMessages extends NLS {
public static String WILDCARD_NOT_SUPPORTED; public static String WILDCARD_NOT_SUPPORTED;
public static String TOO_MANY_BOOLEAN_CLAUSES; public static String TOO_MANY_BOOLEAN_CLAUSES;
public static String LEADING_WILDCARD_NOT_ALLOWED; public static String LEADING_WILDCARD_NOT_ALLOWED;
public static String COULD_NOT_PARSE_NUMBER;
public static String NUMBER_CLASS_NOT_SUPPORTED_BY_NUMERIC_RANGE_QUERY;
public static String UNSUPPORTED_NUMERIC_DATA_TYPE;
} }

View File

@ -25,8 +25,7 @@ import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax.Type;
/** /**
* A {@link FieldQueryNode} represents a element that contains field/text tuple * A {@link FieldQueryNode} represents a element that contains field/text tuple
*/ */
public class FieldQueryNode extends QueryNodeImpl implements TextableQueryNode, public class FieldQueryNode extends QueryNodeImpl implements FieldValuePairQueryNode<CharSequence>, TextableQueryNode {
FieldableNode {
/** /**
* The term's field * The term's field
@ -180,4 +179,12 @@ public class FieldQueryNode extends QueryNodeImpl implements TextableQueryNode,
} }
public CharSequence getValue() {
return getText();
}
public void setValue(CharSequence value) {
setText(value);
}
} }

View File

@ -0,0 +1,22 @@
package org.apache.lucene.queryParser.core.nodes;
/**
* 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.
*/
public interface FieldValuePairQueryNode<T extends Object> extends FieldableNode, ValueQueryNode<T> {
}

View File

@ -0,0 +1,26 @@
package org.apache.lucene.queryParser.core.nodes;
/**
* 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.
*/
public interface NumberQueryNode {
void setNumber(Number number);
Number getNumber();
}

View File

@ -0,0 +1,26 @@
package org.apache.lucene.queryParser.core.nodes;
/**
* 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.
*/
public interface ValueQueryNode<T extends Object> extends QueryNode {
public void setValue(T value);
public T getValue();
}

View File

@ -29,6 +29,7 @@ import org.apache.lucene.queryParser.core.QueryParserHelper;
import org.apache.lucene.queryParser.core.config.QueryConfigHandler; import org.apache.lucene.queryParser.core.config.QueryConfigHandler;
import org.apache.lucene.queryParser.standard.builders.StandardQueryTreeBuilder; import org.apache.lucene.queryParser.standard.builders.StandardQueryTreeBuilder;
import org.apache.lucene.queryParser.standard.config.FuzzyConfig; import org.apache.lucene.queryParser.standard.config.FuzzyConfig;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.Operator; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.Operator;
@ -109,7 +110,7 @@ import org.apache.lucene.search.Query;
* @see StandardQueryTreeBuilder * @see StandardQueryTreeBuilder
*/ */
public class StandardQueryParser extends QueryParserHelper { public class StandardQueryParser extends QueryParserHelper {
/** /**
* Constructs a {@link StandardQueryParser} object. * Constructs a {@link StandardQueryParser} object.
*/ */
@ -118,7 +119,7 @@ public class StandardQueryParser extends QueryParserHelper {
new StandardQueryNodeProcessorPipeline(null), new StandardQueryNodeProcessorPipeline(null),
new StandardQueryTreeBuilder()); new StandardQueryTreeBuilder());
} }
/** /**
* Constructs a {@link StandardQueryParser} object and sets an * Constructs a {@link StandardQueryParser} object and sets an
* {@link Analyzer} to it. The same as: * {@link Analyzer} to it. The same as:
@ -133,15 +134,16 @@ public class StandardQueryParser extends QueryParserHelper {
*/ */
public StandardQueryParser(Analyzer analyzer) { public StandardQueryParser(Analyzer analyzer) {
this(); this();
this.setAnalyzer(analyzer); this.setAnalyzer(analyzer);
} }
@Override @Override
public String toString(){ public String toString() {
return "<StandardQueryParser config=\"" + this.getQueryConfigHandler() + "\"/>"; return "<StandardQueryParser config=\"" + this.getQueryConfigHandler()
+ "\"/>";
} }
/** /**
* Overrides {@link QueryParserHelper#parse(String, String)} so it casts the * Overrides {@link QueryParserHelper#parse(String, String)} so it casts the
* return object to {@link Query}. For more reference about this method, check * return object to {@link Query}. For more reference about this method, check
@ -160,11 +162,11 @@ public class StandardQueryParser extends QueryParserHelper {
@Override @Override
public Query parse(String query, String defaultField) public Query parse(String query, String defaultField)
throws QueryNodeException { throws QueryNodeException {
return (Query) super.parse(query, defaultField); return (Query) super.parse(query, defaultField);
} }
/** /**
* Gets implicit operator setting, which will be either {@link Operator#AND} * Gets implicit operator setting, which will be either {@link Operator#AND}
* or {@link Operator#OR}. * or {@link Operator#OR}.
@ -172,7 +174,7 @@ public class StandardQueryParser extends QueryParserHelper {
public StandardQueryConfigHandler.Operator getDefaultOperator() { public StandardQueryConfigHandler.Operator getDefaultOperator() {
return getQueryConfigHandler().get(ConfigurationKeys.DEFAULT_OPERATOR); return getQueryConfigHandler().get(ConfigurationKeys.DEFAULT_OPERATOR);
} }
/** /**
* Sets the boolean operator of the QueryParser. In default mode ( * Sets the boolean operator of the QueryParser. In default mode (
* {@link Operator#OR}) terms without any modifiers are considered optional: * {@link Operator#OR}) terms without any modifiers are considered optional:
@ -184,7 +186,7 @@ public class StandardQueryParser extends QueryParserHelper {
public void setDefaultOperator(StandardQueryConfigHandler.Operator operator) { public void setDefaultOperator(StandardQueryConfigHandler.Operator operator) {
getQueryConfigHandler().set(ConfigurationKeys.DEFAULT_OPERATOR, operator); getQueryConfigHandler().set(ConfigurationKeys.DEFAULT_OPERATOR, operator);
} }
/** /**
* Set to <code>true</code> to allow leading wildcard characters. * Set to <code>true</code> to allow leading wildcard characters.
* <p> * <p>
@ -197,7 +199,7 @@ public class StandardQueryParser extends QueryParserHelper {
public void setLowercaseExpandedTerms(boolean lowercaseExpandedTerms) { public void setLowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
getQueryConfigHandler().set(ConfigurationKeys.LOWERCASE_EXPANDED_TERMS, lowercaseExpandedTerms); getQueryConfigHandler().set(ConfigurationKeys.LOWERCASE_EXPANDED_TERMS, lowercaseExpandedTerms);
} }
/** /**
* @see #setLowercaseExpandedTerms(boolean) * @see #setLowercaseExpandedTerms(boolean)
*/ */
@ -212,7 +214,7 @@ public class StandardQueryParser extends QueryParserHelper {
} }
} }
/** /**
* Set to <code>true</code> to allow leading wildcard characters. * Set to <code>true</code> to allow leading wildcard characters.
* <p> * <p>
@ -225,7 +227,7 @@ public class StandardQueryParser extends QueryParserHelper {
public void setAllowLeadingWildcard(boolean allowLeadingWildcard) { public void setAllowLeadingWildcard(boolean allowLeadingWildcard) {
getQueryConfigHandler().set(ConfigurationKeys.ALLOW_LEADING_WILDCARD, allowLeadingWildcard); getQueryConfigHandler().set(ConfigurationKeys.ALLOW_LEADING_WILDCARD, allowLeadingWildcard);
} }
/** /**
* Set to <code>true</code> to enable position increments in result query. * Set to <code>true</code> to enable position increments in result query.
* <p> * <p>
@ -238,7 +240,7 @@ public class StandardQueryParser extends QueryParserHelper {
public void setEnablePositionIncrements(boolean enabled) { public void setEnablePositionIncrements(boolean enabled) {
getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS, enabled); getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS, enabled);
} }
/** /**
* @see #setEnablePositionIncrements(boolean) * @see #setEnablePositionIncrements(boolean)
*/ */
@ -253,7 +255,7 @@ public class StandardQueryParser extends QueryParserHelper {
} }
} }
/** /**
* By default, it uses * By default, it uses
* {@link MultiTermQuery#CONSTANT_SCORE_AUTO_REWRITE_DEFAULT} when creating a * {@link MultiTermQuery#CONSTANT_SCORE_AUTO_REWRITE_DEFAULT} when creating a
@ -267,14 +269,14 @@ public class StandardQueryParser extends QueryParserHelper {
public void setMultiTermRewriteMethod(MultiTermQuery.RewriteMethod method) { public void setMultiTermRewriteMethod(MultiTermQuery.RewriteMethod method) {
getQueryConfigHandler().set(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD, method); getQueryConfigHandler().set(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD, method);
} }
/** /**
* @see #setMultiTermRewriteMethod(org.apache.lucene.search.MultiTermQuery.RewriteMethod) * @see #setMultiTermRewriteMethod(org.apache.lucene.search.MultiTermQuery.RewriteMethod)
*/ */
public MultiTermQuery.RewriteMethod getMultiTermRewriteMethod() { public MultiTermQuery.RewriteMethod getMultiTermRewriteMethod() {
return getQueryConfigHandler().get(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD); return getQueryConfigHandler().get(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD);
} }
/** /**
* Set the fields a query should be expanded to when the field is * Set the fields a query should be expanded to when the field is
* <code>null</code> * <code>null</code>
@ -282,15 +284,15 @@ public class StandardQueryParser extends QueryParserHelper {
* @param fields the fields used to expand the query * @param fields the fields used to expand the query
*/ */
public void setMultiFields(CharSequence[] fields) { public void setMultiFields(CharSequence[] fields) {
if (fields == null) { if (fields == null) {
fields = new CharSequence[0]; fields = new CharSequence[0];
} }
getQueryConfigHandler().set(ConfigurationKeys.MULTI_FIELDS, fields); getQueryConfigHandler().set(ConfigurationKeys.MULTI_FIELDS, fields);
} }
/** /**
* Returns the fields used to expand the query when the field for a * Returns the fields used to expand the query when the field for a
* certain query is <code>null</code> * certain query is <code>null</code>
@ -319,21 +321,29 @@ public class StandardQueryParser extends QueryParserHelper {
fuzzyConfig.setPrefixLength(fuzzyPrefixLength); fuzzyConfig.setPrefixLength(fuzzyPrefixLength);
} }
public void setNumericConfigMap(Map<String,NumericConfig> numericConfigMap) {
getQueryConfigHandler().set(ConfigurationKeys.NUMERIC_CONFIG_MAP, numericConfigMap);
}
public Map<String,NumericConfig> getNumericConfigMap() {
return getQueryConfigHandler().get(ConfigurationKeys.NUMERIC_CONFIG_MAP);
}
/** /**
* Set locale used by date range parsing. * Set locale used by date range parsing.
*/ */
public void setLocale(Locale locale) { public void setLocale(Locale locale) {
getQueryConfigHandler().set(ConfigurationKeys.LOCALE, locale); getQueryConfigHandler().set(ConfigurationKeys.LOCALE, locale);
} }
/** /**
* Returns current locale, allowing access by subclasses. * Returns current locale, allowing access by subclasses.
*/ */
public Locale getLocale() { public Locale getLocale() {
return getQueryConfigHandler().get(ConfigurationKeys.LOCALE); return getQueryConfigHandler().get(ConfigurationKeys.LOCALE);
} }
/** /**
* Sets the default slop for phrases. If zero, then exact phrase matches are * Sets the default slop for phrases. If zero, then exact phrase matches are
* required. Default value is zero. * required. Default value is zero.
@ -357,10 +367,10 @@ public class StandardQueryParser extends QueryParserHelper {
getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer); getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer);
} }
public Analyzer getAnalyzer() { public Analyzer getAnalyzer() {
return getQueryConfigHandler().get(ConfigurationKeys.ANALYZER); return getQueryConfigHandler().get(ConfigurationKeys.ANALYZER);
} }
/** /**
* @see #setAllowLeadingWildcard(boolean) * @see #setAllowLeadingWildcard(boolean)
*/ */
@ -374,7 +384,7 @@ public class StandardQueryParser extends QueryParserHelper {
return allowLeadingWildcard; return allowLeadingWildcard;
} }
} }
/** /**
* Get the minimal similarity for fuzzy queries. * Get the minimal similarity for fuzzy queries.
*/ */
@ -387,7 +397,7 @@ public class StandardQueryParser extends QueryParserHelper {
return fuzzyConfig.getMinSimilarity(); return fuzzyConfig.getMinSimilarity();
} }
} }
/** /**
* Get the prefix length for fuzzy queries. * Get the prefix length for fuzzy queries.
* *
@ -402,7 +412,7 @@ public class StandardQueryParser extends QueryParserHelper {
return fuzzyConfig.getPrefixLength(); return fuzzyConfig.getPrefixLength();
} }
} }
/** /**
* Gets the default slop for phrases. * Gets the default slop for phrases.
*/ */
@ -416,7 +426,7 @@ public class StandardQueryParser extends QueryParserHelper {
return phraseSlop; return phraseSlop;
} }
} }
/** /**
* Set the minimum similarity for fuzzy queries. Default is defined on * Set the minimum similarity for fuzzy queries. Default is defined on
* {@link FuzzyQuery#defaultMinSimilarity}. * {@link FuzzyQuery#defaultMinSimilarity}.

View File

@ -0,0 +1,38 @@
package org.apache.lucene.queryParser.standard.builders;
/**
* 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.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.nodes.FieldQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.search.TermQuery;
/**
* Builds a {@link TermQuery} object from a {@link FieldQueryNode} object.
*/
public class DummyQueryNodeBuilder implements StandardQueryBuilder {
public DummyQueryNodeBuilder() {
// empty constructor
}
public TermQuery build(QueryNode queryNode) throws QueryNodeException {
return null;
}
}

View File

@ -0,0 +1,93 @@
package org.apache.lucene.queryParser.standard.builders;
/**
* 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.document.NumericField;
import org.apache.lucene.messages.MessageImpl;
import org.apache.lucene.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.util.StringUtils;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
import org.apache.lucene.queryParser.standard.nodes.NumericQueryNode;
import org.apache.lucene.queryParser.standard.nodes.NumericRangeQueryNode;
import org.apache.lucene.search.NumericRangeQuery;
public class NumericRangeQueryNodeBuilder implements StandardQueryBuilder {
public NumericRangeQueryNodeBuilder() {
// empty constructor
}
public NumericRangeQuery<? extends Number> build(QueryNode queryNode)
throws QueryNodeException {
NumericRangeQueryNode numericRangeNode = (NumericRangeQueryNode) queryNode;
NumericQueryNode lowerNumericNode = numericRangeNode.getLowerBound();
NumericQueryNode upperNumericNode = numericRangeNode.getUpperBound();
Number lowerNumber, upperNumber;
if (lowerNumericNode != null) {
lowerNumber = lowerNumericNode.getValue();
} else {
lowerNumber = null;
}
if (upperNumericNode != null) {
upperNumber = upperNumericNode.getValue();
} else {
upperNumber = null;
}
NumericConfig numericConfig = numericRangeNode.getNumericConfig();
NumericField.DataType numberType = numericConfig.getType();
String field = StringUtils.toString(numericRangeNode.getField());
boolean minInclusive = numericRangeNode.isLowerInclusive();
boolean maxInclusive = numericRangeNode.isUpperInclusive();
int precisionStep = numericConfig.getPrecisionStep();
switch (numberType) {
case LONG:
return NumericRangeQuery.newLongRange(field, precisionStep,
(Long) lowerNumber, (Long) upperNumber, minInclusive, maxInclusive);
case INT:
return NumericRangeQuery.newIntRange(field, precisionStep,
(Integer) lowerNumber, (Integer) upperNumber, minInclusive,
maxInclusive);
case FLOAT:
return NumericRangeQuery.newFloatRange(field, precisionStep,
(Float) lowerNumber, (Float) upperNumber, minInclusive,
maxInclusive);
case DOUBLE:
return NumericRangeQuery.newDoubleRange(field, precisionStep,
(Double) lowerNumber, (Double) upperNumber, minInclusive,
maxInclusive);
default :
throw new QueryNodeException(new MessageImpl(
QueryParserMessages.UNSUPPORTED_NUMERIC_DATA_TYPE, numberType));
}
}
}

View File

@ -31,8 +31,10 @@ import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.SlopQueryNode; import org.apache.lucene.queryParser.core.nodes.SlopQueryNode;
import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode; import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode;
import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode; import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode;
import org.apache.lucene.queryParser.standard.nodes.NumericQueryNode;
import org.apache.lucene.queryParser.standard.nodes.NumericRangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.PrefixWildcardQueryNode; import org.apache.lucene.queryParser.standard.nodes.PrefixWildcardQueryNode;
import org.apache.lucene.queryParser.standard.nodes.RangeQueryNode; import org.apache.lucene.queryParser.standard.nodes.TermRangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode; import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode;
import org.apache.lucene.queryParser.standard.nodes.StandardBooleanQueryNode; import org.apache.lucene.queryParser.standard.nodes.StandardBooleanQueryNode;
import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode; import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode;
@ -50,12 +52,14 @@ import org.apache.lucene.search.Query;
*/ */
public class StandardQueryTreeBuilder extends QueryTreeBuilder implements public class StandardQueryTreeBuilder extends QueryTreeBuilder implements
StandardQueryBuilder { StandardQueryBuilder {
public StandardQueryTreeBuilder() { public StandardQueryTreeBuilder() {
setBuilder(GroupQueryNode.class, new GroupQueryNodeBuilder()); setBuilder(GroupQueryNode.class, new GroupQueryNodeBuilder());
setBuilder(FieldQueryNode.class, new FieldQueryNodeBuilder()); setBuilder(FieldQueryNode.class, new FieldQueryNodeBuilder());
setBuilder(BooleanQueryNode.class, new BooleanQueryNodeBuilder()); setBuilder(BooleanQueryNode.class, new BooleanQueryNodeBuilder());
setBuilder(FuzzyQueryNode.class, new FuzzyQueryNodeBuilder()); setBuilder(FuzzyQueryNode.class, new FuzzyQueryNodeBuilder());
setBuilder(NumericQueryNode.class, new DummyQueryNodeBuilder());
setBuilder(NumericRangeQueryNode.class, new NumericRangeQueryNodeBuilder());
setBuilder(BoostQueryNode.class, new BoostQueryNodeBuilder()); setBuilder(BoostQueryNode.class, new BoostQueryNodeBuilder());
setBuilder(ModifierQueryNode.class, new ModifierQueryNodeBuilder()); setBuilder(ModifierQueryNode.class, new ModifierQueryNodeBuilder());
setBuilder(WildcardQueryNode.class, new WildcardQueryNodeBuilder()); setBuilder(WildcardQueryNode.class, new WildcardQueryNodeBuilder());
@ -63,19 +67,19 @@ public class StandardQueryTreeBuilder extends QueryTreeBuilder implements
setBuilder(MatchNoDocsQueryNode.class, new MatchNoDocsQueryNodeBuilder()); setBuilder(MatchNoDocsQueryNode.class, new MatchNoDocsQueryNodeBuilder());
setBuilder(PrefixWildcardQueryNode.class, setBuilder(PrefixWildcardQueryNode.class,
new PrefixWildcardQueryNodeBuilder()); new PrefixWildcardQueryNodeBuilder());
setBuilder(RangeQueryNode.class, new RangeQueryNodeBuilder()); setBuilder(TermRangeQueryNode.class, new TermRangeQueryNodeBuilder());
setBuilder(RegexpQueryNode.class, new RegexpQueryNodeBuilder()); setBuilder(RegexpQueryNode.class, new RegexpQueryNodeBuilder());
setBuilder(SlopQueryNode.class, new SlopQueryNodeBuilder()); setBuilder(SlopQueryNode.class, new SlopQueryNodeBuilder());
setBuilder(StandardBooleanQueryNode.class, setBuilder(StandardBooleanQueryNode.class,
new StandardBooleanQueryNodeBuilder()); new StandardBooleanQueryNodeBuilder());
setBuilder(MultiPhraseQueryNode.class, new MultiPhraseQueryNodeBuilder()); setBuilder(MultiPhraseQueryNode.class, new MultiPhraseQueryNodeBuilder());
setBuilder(MatchAllDocsQueryNode.class, new MatchAllDocsQueryNodeBuilder()); setBuilder(MatchAllDocsQueryNode.class, new MatchAllDocsQueryNodeBuilder());
} }
@Override @Override
public Query build(QueryNode queryNode) throws QueryNodeException { public Query build(QueryNode queryNode) throws QueryNodeException {
return (Query) super.build(queryNode); return (Query) super.build(queryNode);
} }
} }

View File

@ -0,0 +1,60 @@
package org.apache.lucene.queryParser.standard.builders;
/**
* 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.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.nodes.FieldQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.util.StringUtils;
import org.apache.lucene.queryParser.standard.nodes.TermRangeQueryNode;
import org.apache.lucene.queryParser.standard.processors.MultiTermRewriteMethodProcessor;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.TermRangeQuery;
/**
* Builds a {@link TermRangeQuery} object from a {@link TermRangeQueryNode}
* object.
*/
public class TermRangeQueryNodeBuilder implements StandardQueryBuilder {
public TermRangeQueryNodeBuilder() {
// empty constructor
}
public TermRangeQuery build(QueryNode queryNode) throws QueryNodeException {
TermRangeQueryNode rangeNode = (TermRangeQueryNode) queryNode;
FieldQueryNode upper = rangeNode.getUpperBound();
FieldQueryNode lower = rangeNode.getLowerBound();
String field = StringUtils.toString(rangeNode.getField());
TermRangeQuery rangeQuery = TermRangeQuery.newStringRange(field, lower
.getTextAsString(), upper.getTextAsString(), rangeNode
.isLowerInclusive(), rangeNode.isUpperInclusive());
MultiTermQuery.RewriteMethod method = (MultiTermQuery.RewriteMethod) queryNode
.getTag(MultiTermRewriteMethodProcessor.TAG_ID);
if (method != null) {
rangeQuery.setRewriteMethod(method);
}
return rangeQuery;
}
}

View File

@ -0,0 +1,59 @@
package org.apache.lucene.queryParser.standard.config;
/**
* 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 java.text.DateFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Date;
public class NumberDateFormat extends NumberFormat {
private static final long serialVersionUID = 964823936071308283L;
final private DateFormat dateFormat;
public NumberDateFormat(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public StringBuffer format(double number, StringBuffer toAppendTo,
FieldPosition pos) {
return dateFormat.format(new Date((long) number), toAppendTo, pos);
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo,
FieldPosition pos) {
return dateFormat.format(new Date(number), toAppendTo, pos);
}
@Override
public Number parse(String source, ParsePosition parsePosition) {
return dateFormat.parse(source, parsePosition).getTime();
}
@Override
public StringBuffer format(Object number, StringBuffer toAppendTo,
FieldPosition pos) {
return dateFormat.format(number, toAppendTo, pos);
}
}

View File

@ -0,0 +1,94 @@
package org.apache.lucene.queryParser.standard.config;
/**
* 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 java.text.NumberFormat;
import org.apache.lucene.document.NumericField;
public class NumericConfig {
private int precisionStep;
private NumberFormat format;
private NumericField.DataType type;
public NumericConfig(int precisionStep, NumberFormat format, NumericField.DataType type) {
setPrecisionStep(precisionStep);
setNumberFormat(format);
setType(type);
}
public int getPrecisionStep() {
return precisionStep;
}
public void setPrecisionStep(int precisionStep) {
this.precisionStep = precisionStep;
}
public NumberFormat getNumberFormat() {
return format;
}
public NumericField.DataType getType() {
return type;
}
public void setType(NumericField.DataType type) {
if (type == null) {
throw new IllegalArgumentException("type cannot be null!");
}
this.type = type;
}
public void setNumberFormat(NumberFormat format) {
if (format == null) {
throw new IllegalArgumentException("format cannot be null!");
}
this.format = format;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof NumericConfig) {
NumericConfig other = (NumericConfig) obj;
if (this.precisionStep == other.precisionStep
&& this.format == other.format) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,57 @@
package org.apache.lucene.queryParser.standard.config;
/**
* 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 java.util.Map;
import org.apache.lucene.queryParser.core.config.FieldConfig;
import org.apache.lucene.queryParser.core.config.FieldConfigListener;
import org.apache.lucene.queryParser.core.config.QueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
public class NumericFieldConfigListener implements FieldConfigListener {
final private QueryConfigHandler config;
public NumericFieldConfigListener(QueryConfigHandler config) {
if (config == null) {
throw new IllegalArgumentException("config cannot be null!");
}
this.config = config;
}
public void buildFieldConfig(FieldConfig fieldConfig) {
Map<String,NumericConfig> numericConfigMap = config
.get(ConfigurationKeys.NUMERIC_CONFIG_MAP);
if (numericConfigMap != null) {
NumericConfig numericConfig = numericConfigMap
.get(fieldConfig.getField());
if (numericConfig != null) {
fieldConfig.set(ConfigurationKeys.NUMERIC_CONFIG, numericConfig);
}
}
}
}

View File

@ -161,6 +161,22 @@ public class StandardQueryConfigHandler extends QueryConfigHandler {
*/ */
final public static ConfigurationKey<Float> BOOST = ConfigurationKey.newInstance(); final public static ConfigurationKey<Float> BOOST = ConfigurationKey.newInstance();
/**
* Key used to set a field to its {@link NumericConfig}.
*
* @see StandardQueryParser#setNumericConfigMap(Map)
* @see StandardQueryParser#getNumericConfigMap()
*/
final public static ConfigurationKey<NumericConfig> NUMERIC_CONFIG = ConfigurationKey.newInstance();
/**
* Key used to set the {@link NumericConfig} in {@link FieldConfig} for numeric fields.
*
* @see StandardQueryParser#setNumericConfigMap(Map)
* @see StandardQueryParser#getNumericConfigMap()
*/
final public static ConfigurationKey<Map<String,NumericConfig>> NUMERIC_CONFIG_MAP = ConfigurationKey.newInstance();
} }
public static enum Operator { public static enum Operator {
@ -171,6 +187,7 @@ public class StandardQueryConfigHandler extends QueryConfigHandler {
// Add listener that will build the FieldConfig. // Add listener that will build the FieldConfig.
addFieldConfigListener(new FieldBoostMapFCListener(this)); addFieldConfigListener(new FieldBoostMapFCListener(this));
addFieldConfigListener(new FieldDateResolutionFCListener(this)); addFieldConfigListener(new FieldDateResolutionFCListener(this));
addFieldConfigListener(new NumericFieldConfigListener(this));
// Default Values // Default Values
set(ConfigurationKeys.ALLOW_LEADING_WILDCARD, false); // default in 2.9 set(ConfigurationKeys.ALLOW_LEADING_WILDCARD, false); // default in 2.9

View File

@ -0,0 +1,153 @@
package org.apache.lucene.queryParser.standard.nodes;
/**
* 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 java.util.ArrayList;
import org.apache.lucene.queryParser.core.nodes.FieldValuePairQueryNode;
import org.apache.lucene.queryParser.core.nodes.FieldableNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNodeImpl;
import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryParser.core.util.StringUtils;
public abstract class AbstractRangeQueryNode<T extends FieldValuePairQueryNode<?>>
extends QueryNodeImpl implements FieldableNode {
private boolean lowerInclusive, upperInclusive;
protected AbstractRangeQueryNode() {
setLeaf(false);
allocate();
}
public CharSequence getField() {
CharSequence field = null;
T lower = getLowerBound();
T upper = getUpperBound();
if (lower != null) {
field = lower.getField();
} else if (upper != null) {
field = upper.getField();
}
return field;
}
public void setField(CharSequence fieldName) {
T lower = getLowerBound();
T upper = getUpperBound();
if (lower != null) {
lower.setField(fieldName);
}
if (upper != null) {
upper.setField(fieldName);
}
}
@SuppressWarnings("unchecked")
public T getLowerBound() {
return (T) getChildren().get(0);
}
@SuppressWarnings("unchecked")
public T getUpperBound() {
return (T) getChildren().get(1);
}
public boolean isLowerInclusive() {
return lowerInclusive;
}
public boolean isUpperInclusive() {
return upperInclusive;
}
public void setBounds(T lower, T upper, boolean lowerInclusive,
boolean upperInclusive) {
if (lower != null && upper != null) {
String lowerField = StringUtils.toString(lower.getField());
String upperField = StringUtils.toString(upper.getField());
if ((upperField == null && lowerField == null)
|| (upperField != null && !upperField.equals(lowerField))) {
throw new IllegalArgumentException(
"lower and upper bounds should have the same field name!");
}
this.lowerInclusive = lowerInclusive;
this.upperInclusive = upperInclusive;
ArrayList<QueryNode> children = new ArrayList<QueryNode>(2);
children.add(lower);
children.add(upper);
set(children);
}
}
public CharSequence toQueryString(EscapeQuerySyntax escapeSyntaxParser) {
StringBuilder sb = new StringBuilder();
T lower = getLowerBound();
T upper = getUpperBound();
if (lowerInclusive) {
sb.append('[');
} else {
sb.append('{');
}
if (lower != null) {
sb.append(lower.toQueryString(escapeSyntaxParser));
} else {
sb.append("...");
}
sb.append(' ');
if (upper != null) {
sb.append(upper.toQueryString(escapeSyntaxParser));
} else {
sb.append("...");
}
if (upperInclusive) {
sb.append(']');
} else {
sb.append('}');
}
return sb.toString();
}
}

View File

@ -0,0 +1,91 @@
package org.apache.lucene.queryParser.standard.nodes;
/**
* 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 java.text.NumberFormat;
import java.util.Locale;
import org.apache.lucene.queryParser.core.nodes.FieldValuePairQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNodeImpl;
import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax.Type;
public class NumericQueryNode extends QueryNodeImpl implements
FieldValuePairQueryNode<Number> {
private NumberFormat numberFormat;
private CharSequence field;
private Number value;
public NumericQueryNode(CharSequence field, Number value,
NumberFormat numberFormat) {
super();
setNumberFormat(numberFormat);
setField(field);
setValue(value);
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence fieldName) {
this.field = fieldName;
}
protected CharSequence getTermEscaped(EscapeQuerySyntax escaper) {
return escaper.escape(NumberFormat.getNumberInstance().format(this.value),
Locale.ENGLISH, Type.NORMAL);
}
public CharSequence toQueryString(EscapeQuerySyntax escapeSyntaxParser) {
if (isDefaultField(this.field)) {
return getTermEscaped(escapeSyntaxParser);
} else {
return this.field + ":" + getTermEscaped(escapeSyntaxParser);
}
}
public void setNumberFormat(NumberFormat format) {
this.numberFormat = format;
}
public NumberFormat getNumberFormat() {
return this.numberFormat;
}
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
@Override
public String toString() {
return "<numeric field='" + this.field + "' number='"
+ numberFormat.format(value) + "'/>";
}
}

View File

@ -0,0 +1,116 @@
package org.apache.lucene.queryParser.standard.nodes;
import org.apache.lucene.document.NumericField;
import org.apache.lucene.messages.MessageImpl;
import org.apache.lucene.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
/**
* 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.
*/
public class NumericRangeQueryNode extends
AbstractRangeQueryNode<NumericQueryNode> {
public NumericConfig numericConfig;
public NumericRangeQueryNode(NumericQueryNode lower, NumericQueryNode upper,
boolean lowerInclusive, boolean upperInclusive, NumericConfig numericConfig) throws QueryNodeException {
setBounds(lower, upper, lowerInclusive, upperInclusive, numericConfig);
}
private static NumericField.DataType getNumericDataType(Number number) throws QueryNodeException {
if (number instanceof Long) {
return NumericField.DataType.LONG;
} else if (number instanceof Integer) {
return NumericField.DataType.INT;
} else if (number instanceof Double) {
return NumericField.DataType.DOUBLE;
} else if (number instanceof Float) {
return NumericField.DataType.FLOAT;
} else {
throw new QueryNodeException(
new MessageImpl(
QueryParserMessages.NUMBER_CLASS_NOT_SUPPORTED_BY_NUMERIC_RANGE_QUERY,
number.getClass()));
}
}
public void setBounds(NumericQueryNode lower, NumericQueryNode upper,
boolean lowerInclusive, boolean upperInclusive, NumericConfig numericConfig) throws QueryNodeException {
if (numericConfig == null) {
throw new IllegalArgumentException("numericConfig cannot be null!");
}
NumericField.DataType lowerNumberType, upperNumberType;
if (lower != null && lower.getValue() != null) {
lowerNumberType = getNumericDataType(lower.getValue());
} else {
lowerNumberType = null;
}
if (upper != null && upper.getValue() != null) {
upperNumberType = getNumericDataType(upper.getValue());
} else {
upperNumberType = null;
}
if (lowerNumberType != null
&& !lowerNumberType.equals(numericConfig.getType())) {
throw new IllegalArgumentException(
"lower value's type should be the same as numericConfig type: "
+ lowerNumberType + " != " + numericConfig.getType());
}
if (upperNumberType != null
&& !upperNumberType.equals(numericConfig.getType())) {
throw new IllegalArgumentException(
"upper value's type should be the same as numericConfig type: "
+ upperNumberType + " != " + numericConfig.getType());
}
super.setBounds(lower, upper, lowerInclusive, upperInclusive);
this.numericConfig = numericConfig;
}
public NumericConfig getNumericConfig() {
return this.numericConfig;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("<numericRange lowerInclusive='");
sb.append(isLowerInclusive()).append("' upperInclusive='").append(
isUpperInclusive()).append(
"' precisionStep='" + numericConfig.getPrecisionStep()).append(
"' type='" + numericConfig.getType()).append("'>\n");
sb.append(getLowerBound()).append('\n');
sb.append(getUpperBound()).append('\n');
sb.append("</numericRange>");
return sb.toString();
}
}

View File

@ -0,0 +1,35 @@
package org.apache.lucene.queryParser.standard.nodes;
/**
* 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.queryParser.core.nodes.FieldQueryNode;
/**
* This query node represents a range query.
*
* @see ParametricRangeQueryNodeProcessor
* @see org.apache.lucene.search.TermRangeQuery
*/
public class TermRangeQueryNode extends AbstractRangeQueryNode<FieldQueryNode> {
public TermRangeQueryNode(FieldQueryNode lower, FieldQueryNode upper,
boolean lowerInclusive, boolean upperInclusive) {
setBounds(lower, upper, lowerInclusive, upperInclusive);
}
}

View File

@ -40,7 +40,6 @@ import org.apache.lucene.queryParser.core.nodes.QuotedFieldQueryNode;
import org.apache.lucene.queryParser.core.nodes.TextableQueryNode; import org.apache.lucene.queryParser.core.nodes.TextableQueryNode;
import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode; import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode; import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode;
import org.apache.lucene.queryParser.standard.nodes.StandardBooleanQueryNode; import org.apache.lucene.queryParser.standard.nodes.StandardBooleanQueryNode;

View File

@ -25,7 +25,6 @@ import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.SlopQueryNode; import org.apache.lucene.queryParser.core.nodes.SlopQueryNode;
import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode; import org.apache.lucene.queryParser.core.nodes.TokenizedPhraseQueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode; import org.apache.lucene.queryParser.standard.nodes.MultiPhraseQueryNode;

View File

@ -25,7 +25,6 @@ import org.apache.lucene.queryParser.core.nodes.FuzzyQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode; import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.FuzzyConfig; import org.apache.lucene.queryParser.standard.config.FuzzyConfig;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.FuzzyQuery;

View File

@ -27,7 +27,6 @@ import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.TextableQueryNode; import org.apache.lucene.queryParser.core.nodes.TextableQueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.core.util.UnescapedCharSequence; import org.apache.lucene.queryParser.core.util.UnescapedCharSequence;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode; import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode;
import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode; import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode;

View File

@ -27,7 +27,6 @@ import org.apache.lucene.queryParser.core.nodes.FieldableNode;
import org.apache.lucene.queryParser.core.nodes.GroupQueryNode; import org.apache.lucene.queryParser.core.nodes.GroupQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode; import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
/** /**

View File

@ -19,10 +19,10 @@ package org.apache.lucene.queryParser.standard.processors;
import java.util.List; import java.util.List;
import org.apache.lucene.queryParser.core.nodes.ParametricRangeQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode; import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.AbstractRangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode; import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode;
import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode; import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
@ -43,7 +43,7 @@ public class MultiTermRewriteMethodProcessor extends QueryNodeProcessorImpl {
// set setMultiTermRewriteMethod for WildcardQueryNode and // set setMultiTermRewriteMethod for WildcardQueryNode and
// PrefixWildcardQueryNode // PrefixWildcardQueryNode
if (node instanceof WildcardQueryNode if (node instanceof WildcardQueryNode
|| node instanceof ParametricRangeQueryNode || node instanceof RegexpQueryNode) { || node instanceof AbstractRangeQueryNode || node instanceof RegexpQueryNode) {
MultiTermQuery.RewriteMethod rewriteMethod = getQueryConfigHandler().get(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD); MultiTermQuery.RewriteMethod rewriteMethod = getQueryConfigHandler().get(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD);

View File

@ -0,0 +1,122 @@
package org.apache.lucene.queryParser.standard.processors;
/**
* 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 java.text.NumberFormat;
import java.text.ParseException;
import java.util.List;
import org.apache.lucene.messages.MessageImpl;
import org.apache.lucene.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.QueryNodeParseException;
import org.apache.lucene.queryParser.core.config.FieldConfig;
import org.apache.lucene.queryParser.core.config.QueryConfigHandler;
import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
import org.apache.lucene.queryParser.core.nodes.FieldQueryNode;
import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.NumericQueryNode;
import org.apache.lucene.queryParser.standard.nodes.NumericRangeQueryNode;
public class NumericQueryNodeProcessor extends QueryNodeProcessorImpl {
public NumericQueryNodeProcessor() {
// empty constructor
}
@Override
protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {
if (node instanceof FieldQueryNode
&& !(node instanceof ParametricQueryNode)) {
QueryConfigHandler config = getQueryConfigHandler();
if (config != null) {
FieldQueryNode fieldNode = (FieldQueryNode) node;
FieldConfig fieldConfig = config.getFieldConfig(fieldNode
.getFieldAsString());
if (fieldConfig != null) {
NumericConfig numericConfig = fieldConfig
.get(ConfigurationKeys.NUMERIC_CONFIG);
if (numericConfig != null) {
NumberFormat numberFormat = numericConfig.getNumberFormat();
Number number;
try {
number = numberFormat.parse(fieldNode.getTextAsString());
} catch (ParseException e) {
throw new QueryNodeParseException(new MessageImpl(
QueryParserMessages.COULD_NOT_PARSE_NUMBER, fieldNode
.getTextAsString(), numberFormat.getClass()
.getCanonicalName()), e);
}
switch (numericConfig.getType()) {
case LONG:
number = number.longValue();
break;
case INT:
number = number.intValue();
break;
case DOUBLE:
number = number.doubleValue();
break;
case FLOAT:
number = number.floatValue();
}
NumericQueryNode lowerNode = new NumericQueryNode(fieldNode
.getField(), number, numberFormat);
NumericQueryNode upperNode = new NumericQueryNode(fieldNode
.getField(), number, numberFormat);
return new NumericRangeQueryNode(lowerNode, upperNode, true, true,
numericConfig);
}
}
}
}
return node;
}
@Override
protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException {
return node;
}
@Override
protected List<QueryNode> setChildrenOrder(List<QueryNode> children)
throws QueryNodeException {
return children;
}
}

View File

@ -0,0 +1,143 @@
package org.apache.lucene.queryParser.standard.processors;
/**
* 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 java.text.NumberFormat;
import java.text.ParseException;
import java.util.List;
import org.apache.lucene.messages.MessageImpl;
import org.apache.lucene.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.QueryNodeParseException;
import org.apache.lucene.queryParser.core.config.FieldConfig;
import org.apache.lucene.queryParser.core.config.QueryConfigHandler;
import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode;
import org.apache.lucene.queryParser.core.nodes.ParametricRangeQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode.CompareOperator;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.core.util.StringUtils;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.NumericQueryNode;
import org.apache.lucene.queryParser.standard.nodes.NumericRangeQueryNode;
public class NumericRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
public NumericRangeQueryNodeProcessor() {
// empty constructor
}
@Override
protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {
if (node instanceof ParametricRangeQueryNode) {
QueryConfigHandler config = getQueryConfigHandler();
if (config != null) {
ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node;
FieldConfig fieldConfig = config.getFieldConfig(StringUtils
.toString(parametricRangeNode.getField()));
if (fieldConfig != null) {
NumericConfig numericConfig = fieldConfig
.get(ConfigurationKeys.NUMERIC_CONFIG);
if (numericConfig != null) {
ParametricQueryNode lower = parametricRangeNode.getLowerBound();
ParametricQueryNode upper = parametricRangeNode.getUpperBound();
NumberFormat numberFormat = numericConfig.getNumberFormat();
Number lowerNumber, upperNumber;
try {
lowerNumber = numberFormat.parse(lower.getTextAsString());
} catch (ParseException e) {
throw new QueryNodeParseException(new MessageImpl(
QueryParserMessages.COULD_NOT_PARSE_NUMBER, lower
.getTextAsString(), numberFormat.getClass()
.getCanonicalName()), e);
}
try {
upperNumber = numberFormat.parse(upper.getTextAsString());
} catch (ParseException e) {
throw new QueryNodeParseException(new MessageImpl(
QueryParserMessages.COULD_NOT_PARSE_NUMBER, upper
.getTextAsString(), numberFormat.getClass()
.getCanonicalName()), e);
}
switch (numericConfig.getType()) {
case LONG:
upperNumber = upperNumber.longValue();
lowerNumber = lowerNumber.longValue();
break;
case INT:
upperNumber = upperNumber.intValue();
lowerNumber = lowerNumber.intValue();
break;
case DOUBLE:
upperNumber = upperNumber.doubleValue();
lowerNumber = lowerNumber.doubleValue();
break;
case FLOAT:
upperNumber = upperNumber.floatValue();
lowerNumber = lowerNumber.floatValue();
}
NumericQueryNode lowerNode = new NumericQueryNode(
parametricRangeNode.getField(), lowerNumber, numberFormat);
NumericQueryNode upperNode = new NumericQueryNode(
parametricRangeNode.getField(), upperNumber, numberFormat);
boolean upperInclusive = upper.getOperator() == CompareOperator.LE;
boolean lowerInclusive = lower.getOperator() == CompareOperator.GE;
return new NumericRangeQueryNode(lowerNode, upperNode,
lowerInclusive, upperInclusive, numericConfig);
}
}
}
}
return node;
}
@Override
protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException {
return node;
}
@Override
protected List<QueryNode> setChildrenOrder(List<QueryNode> children)
throws QueryNodeException {
return children;
}
}

View File

@ -33,17 +33,16 @@ import org.apache.lucene.queryParser.core.nodes.ParametricRangeQueryNode;
import org.apache.lucene.queryParser.core.nodes.QueryNode; import org.apache.lucene.queryParser.core.nodes.QueryNode;
import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode.CompareOperator; import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode.CompareOperator;
import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl; import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys; import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.nodes.RangeQueryNode; import org.apache.lucene.queryParser.standard.nodes.TermRangeQueryNode;
/** /**
* This processor converts {@link ParametricRangeQueryNode} objects to * This processor converts {@link ParametricRangeQueryNode} objects to
* {@link RangeQueryNode} objects. It reads the lower and upper bounds value * {@link TermRangeQueryNode} objects. It reads the lower and upper bounds value
* from the {@link ParametricRangeQueryNode} object and try to parse their * from the {@link ParametricRangeQueryNode} object and try to parse their
* values using a {@link DateFormat}. If the values cannot be parsed to a date * values using a {@link DateFormat}. If the values cannot be parsed to a date
* value, it will only create the {@link RangeQueryNode} using the non-parsed * value, it will only create the {@link TermRangeQueryNode} using the
* values. <br/> * non-parsed values. <br/>
* <br/> * <br/>
* If a {@link ConfigurationKeys#LOCALE} is defined in the {@link QueryConfigHandler} it * If a {@link ConfigurationKeys#LOCALE} is defined in the {@link QueryConfigHandler} it
* will be used to parse the date, otherwise {@link Locale#getDefault()} will be * will be used to parse the date, otherwise {@link Locale#getDefault()} will be
@ -55,18 +54,18 @@ import org.apache.lucene.queryParser.standard.nodes.RangeQueryNode;
* *
* @see ConfigurationKeys#DATE_RESOLUTION * @see ConfigurationKeys#DATE_RESOLUTION
* @see ConfigurationKeys#LOCALE * @see ConfigurationKeys#LOCALE
* @see RangeQueryNode * @see TermRangeQueryNode
* @see ParametricRangeQueryNode * @see ParametricRangeQueryNode
*/ */
public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl { public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
public ParametricRangeQueryNodeProcessor() { public ParametricRangeQueryNodeProcessor() {
// empty constructor // empty constructor
} }
@Override @Override
protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException { protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {
if (node instanceof ParametricRangeQueryNode) { if (node instanceof ParametricRangeQueryNode) {
ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node; ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node;
ParametricQueryNode upper = parametricRangeNode.getUpperBound(); ParametricQueryNode upper = parametricRangeNode.getUpperBound();
@ -79,31 +78,31 @@ public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
if (locale == null) { if (locale == null) {
locale = Locale.getDefault(); locale = Locale.getDefault();
} }
CharSequence field = parametricRangeNode.getField(); CharSequence field = parametricRangeNode.getField();
String fieldStr = null; String fieldStr = null;
if (field != null) { if (field != null) {
fieldStr = field.toString(); fieldStr = field.toString();
} }
FieldConfig fieldConfig = getQueryConfigHandler() FieldConfig fieldConfig = getQueryConfigHandler()
.getFieldConfig(fieldStr); .getFieldConfig(fieldStr);
if (fieldConfig != null) { if (fieldConfig != null) {
dateRes = fieldConfig.get(ConfigurationKeys.DATE_RESOLUTION); dateRes = fieldConfig.get(ConfigurationKeys.DATE_RESOLUTION);
} }
if (upper.getOperator() == CompareOperator.LE) { if (upper.getOperator() == CompareOperator.LE) {
inclusive = true; inclusive = true;
} else if (lower.getOperator() == CompareOperator.GE) { } else if (lower.getOperator() == CompareOperator.GE) {
inclusive = true; inclusive = true;
} }
String part1 = lower.getTextAsString(); String part1 = lower.getTextAsString();
String part2 = upper.getTextAsString(); String part2 = upper.getTextAsString();
try { try {
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
df.setLenient(true); df.setLenient(true);
@ -121,37 +120,39 @@ public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
cal.set(Calendar.MILLISECOND, 999); cal.set(Calendar.MILLISECOND, 999);
d2 = cal.getTime(); d2 = cal.getTime();
} }
part1 = DateTools.dateToString(d1, dateRes); part1 = DateTools.dateToString(d1, dateRes);
part2 = DateTools.dateToString(d2, dateRes); part2 = DateTools.dateToString(d2, dateRes);
} catch (Exception e) { } catch (Exception e) {
// do nothing // do nothing
} }
lower.setText(part1); lower.setText(part1);
upper.setText(part2); upper.setText(part2);
return new RangeQueryNode(lower, upper); return new TermRangeQueryNode(lower, upper,
lower.getOperator() == CompareOperator.GE,
upper.getOperator() == CompareOperator.LE);
} }
return node; return node;
} }
@Override @Override
protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException { protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException {
return node; return node;
} }
@Override @Override
protected List<QueryNode> setChildrenOrder(List<QueryNode> children) protected List<QueryNode> setChildrenOrder(List<QueryNode> children)
throws QueryNodeException { throws QueryNodeException {
return children; return children;
} }
} }

View File

@ -53,6 +53,8 @@ public class StandardQueryNodeProcessorPipeline extends
add(new FuzzyQueryNodeProcessor()); add(new FuzzyQueryNodeProcessor());
add(new MatchAllDocsQueryNodeProcessor()); add(new MatchAllDocsQueryNodeProcessor());
add(new LowercaseExpandedTermsQueryNodeProcessor()); add(new LowercaseExpandedTermsQueryNodeProcessor());
add(new NumericQueryNodeProcessor());
add(new NumericRangeQueryNodeProcessor());
add(new ParametricRangeQueryNodeProcessor()); add(new ParametricRangeQueryNodeProcessor());
add(new AllowLeadingWildcardProcessor()); add(new AllowLeadingWildcardProcessor());
add(new AnalyzerQueryNodeProcessor()); add(new AnalyzerQueryNodeProcessor());

View File

@ -46,3 +46,12 @@ TOO_MANY_BOOLEAN_CLAUSES = Too many boolean clauses, the maximum supported is {0
#<CREATEDBY>Apache Lucene Community</CREATEDBY> #<CREATEDBY>Apache Lucene Community</CREATEDBY>
LEADING_WILDCARD_NOT_ALLOWED = Leading wildcard is not allowed: {0} LEADING_WILDCARD_NOT_ALLOWED = Leading wildcard is not allowed: {0}
#<CREATEDBY>Apache Lucene Community</CREATEDBY>
COULD_NOT_PARSE_NUMBER = Could not parse text "{0}" using {1}
#<CREATEDBY>Apache Lucene Community</CREATEDBY>
NUMBER_CLASS_NOT_SUPPORTED_BY_NUMERIC_RANGE_QUERY = Number class not supported by NumericRangeQueryNode: {0}
#<CREATEDBY>Apache Lucene Community</CREATEDBY>
UNSUPPORTED_NUMERIC_DATA_TYPE = Unsupported NumericField.DataType: {0}

View File

@ -0,0 +1,417 @@
package org.apache.lucene.queryParser.standard;
/**
* 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 java.io.IOException;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.queryParser.core.QueryNodeException;
import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryParser.standard.config.NumberDateFormat;
import org.apache.lucene.queryParser.standard.config.NumericConfig;
import org.apache.lucene.queryParser.standard.parser.EscapeQuerySyntaxImpl;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util._TestUtil;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestNumericQueryParser extends LuceneTestCase {
private static enum NumberType {
NEGATIVE, ZERO, POSITIVE;
}
final private static int[] DATE_STYLES = {DateFormat.FULL, DateFormat.LONG,
DateFormat.MEDIUM, DateFormat.SHORT};
final private static int PRECISION_STEP = 8;
final private static String FIELD_NAME = "field";
final private static Locale LOCALE = randomLocale(random);
final private static TimeZone TIMEZONE = randomTimeZone(random);
final private static Map<String,Number> RANDOM_NUMBER_MAP;
final private static EscapeQuerySyntax ESCAPER = new EscapeQuerySyntaxImpl();
final private static String DATE_FIELD_NAME = "date";
final private static int DATE_STYLE = randomDateStyle(random);
final private static int TIME_STYLE = randomDateStyle(random);
final private static Analyzer ANALYZER = new MockAnalyzer(random);
final private static NumberFormat NUMBER_FORMAT = NumberFormat
.getNumberInstance(LOCALE);
final private static StandardQueryParser qp = new StandardQueryParser(
ANALYZER);
final private static NumberDateFormat DATE_FORMAT;
static {
try {
NUMBER_FORMAT.setMaximumFractionDigits((random.nextInt() & 20) + 1);
NUMBER_FORMAT.setMinimumFractionDigits((random.nextInt() & 20) + 1);
NUMBER_FORMAT.setMaximumIntegerDigits((random.nextInt() & 20) + 1);
NUMBER_FORMAT.setMinimumIntegerDigits((random.nextInt() & 20) + 1);
// assumes localized date pattern will have at least year, month, day, hour, minute
SimpleDateFormat dateFormat = (SimpleDateFormat) DateFormat.getDateTimeInstance(
DATE_STYLE, TIME_STYLE, LOCALE);
// not all date patterns includes era, full year, timezone and second, so we add them here
dateFormat.applyPattern(dateFormat.toPattern() + " G s Z yyyy");
dateFormat.setTimeZone(TIMEZONE);
DATE_FORMAT = new NumberDateFormat(dateFormat);
HashMap<String,Number> randomNumberMap = new HashMap<String,Number>();
double randomDouble;
long randomLong;
int randomInt;
float randomFloat;
long randomDate;
while ((randomLong = normalizeNumber(Math.abs(random.nextLong()))
.longValue()) == 0)
;
while ((randomDouble = normalizeNumber(Math.abs(random.nextDouble()))
.doubleValue()) == 0)
;
while ((randomFloat = normalizeNumber(Math.abs(random.nextFloat()))
.floatValue()) == 0)
;
while ((randomInt = normalizeNumber(Math.abs(random.nextInt()))
.intValue()) == 0)
;
// make sure random date is at least one second from 0
while ((randomDate = normalizeNumber(Math.abs(random.nextLong()))
.longValue()) < 1000)
;
// truncate to second
randomDate = (randomDate / 1000) * 1000;
randomNumberMap.put(NumericField.DataType.LONG.name(), randomLong);
randomNumberMap.put(NumericField.DataType.INT.name(), randomInt);
randomNumberMap.put(NumericField.DataType.FLOAT.name(), randomFloat);
randomNumberMap.put(NumericField.DataType.DOUBLE.name(), randomDouble);
randomNumberMap.put(DATE_FIELD_NAME, randomDate);
RANDOM_NUMBER_MAP = Collections.unmodifiableMap(randomNumberMap);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
private static Directory directory = null;
private static IndexReader reader = null;
private static IndexSearcher searcher = null;
@BeforeClass
public static void beforeClass() throws Exception {
directory = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random, directory,
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))
.setMaxBufferedDocs(_TestUtil.nextInt(random, 50, 1000))
.setMergePolicy(newLogMergePolicy()));
Document doc = new Document();
HashMap<String,NumericConfig> numericConfigMap = new HashMap<String,NumericConfig>();
HashMap<String,NumericField> numericFieldMap = new HashMap<String,NumericField>();
qp.setNumericConfigMap(numericConfigMap);
for (NumericField.DataType type : NumericField.DataType.values()) {
numericConfigMap.put(type.name(), new NumericConfig(PRECISION_STEP,
NUMBER_FORMAT, type));
NumericField field = new NumericField(type.name(), PRECISION_STEP,
Field.Store.YES, true);
numericFieldMap.put(type.name(), field);
doc.add(field);
}
numericConfigMap.put(DATE_FIELD_NAME, new NumericConfig(PRECISION_STEP,
DATE_FORMAT, NumericField.DataType.LONG));
NumericField dateField = new NumericField(DATE_FIELD_NAME, PRECISION_STEP,
Field.Store.YES, true);
numericFieldMap.put(DATE_FIELD_NAME, dateField);
doc.add(dateField);
for (NumberType numberType : NumberType.values()) {
setFieldValues(numberType, numericFieldMap);
if (VERBOSE) System.out.println("Indexing document: " + doc);
writer.addDocument(doc);
}
reader = writer.getReader();
searcher = newSearcher(reader);
writer.close();
// SimpleDateFormat df = new SimpleDateFormat(
// "yyyy.MM.dd G 'at' HH:mm:ss z", LOCALE.ENGLISH);
// assumes localized date pattern will have at least year, month, day, hour, minute
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(
randomDateStyle(random), randomDateStyle(random), LOCALE.ENGLISH);
System.out.println(df.toPattern());
// most of date pattern do not include era, so we add it here. Also,
// sometimes second is not available, we make sure it's present too
df.applyPattern(df.toPattern() + " G s Z yyyy");
df.setTimeZone(TIMEZONE);
System.out.println(TIMEZONE);
System.out.println(TIMEZONE);
System.out.println(TIMEZONE);
long l1 = 0;
long l2 = -30000;
String d1 = df.format(new Date(l1));
String d2 = df.format(new Date(l2));
long newL1 = df.parse(d1).getTime();
long newL2 = df.parse(d2).getTime();
System.out.println(l1 + " => " + d1 + " => " + newL1);
System.out.println(l2 + " => " + d2 + " => " + newL2);
}
private static Number getNumberType(NumberType numberType, String fieldName) {
switch (numberType) {
case POSITIVE:
return RANDOM_NUMBER_MAP.get(fieldName);
case NEGATIVE:
Number number = RANDOM_NUMBER_MAP.get(fieldName);
if (NumericField.DataType.LONG.name().equals(fieldName)
|| DATE_FIELD_NAME.equals(fieldName)) {
number = -number.longValue();
} else if (NumericField.DataType.DOUBLE.name().equals(fieldName)) {
number = -number.doubleValue();
} else if (NumericField.DataType.FLOAT.name().equals(fieldName)) {
number = -number.floatValue();
} else if (NumericField.DataType.INT.name().equals(fieldName)) {
number = -number.intValue();
} else {
throw new IllegalArgumentException("field name not found: "
+ fieldName);
}
return number;
default:
return 0;
}
}
private static void setFieldValues(NumberType numberType,
HashMap<String,NumericField> numericFieldMap) {
Number number = getNumberType(numberType, NumericField.DataType.DOUBLE
.name());
numericFieldMap.get(NumericField.DataType.DOUBLE.name()).setDoubleValue(
number.doubleValue());
number = getNumberType(numberType, NumericField.DataType.INT.name());
numericFieldMap.get(NumericField.DataType.INT.name()).setIntValue(
number.intValue());
number = getNumberType(numberType, NumericField.DataType.LONG.name());
numericFieldMap.get(NumericField.DataType.LONG.name()).setLongValue(
number.longValue());
number = getNumberType(numberType, NumericField.DataType.FLOAT.name());
numericFieldMap.get(NumericField.DataType.FLOAT.name()).setFloatValue(
number.floatValue());
number = getNumberType(numberType, DATE_FIELD_NAME);
numericFieldMap.get(DATE_FIELD_NAME).setLongValue(number.longValue());
}
private static int randomDateStyle(Random random) {
return DATE_STYLES[random.nextInt(DATE_STYLES.length)];
}
@Test
public void testInclusiveNumericRange() throws Exception {
assertRangeQuery(NumberType.ZERO, NumberType.ZERO, true, true, 1);
assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, true, true, 2);
assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, true, true, 2);
assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, true, true, 3);
assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, true, true, 1);
}
// @Test
// test disabled since standard syntax parser does not work with inclusive and
// exclusive at the same time
// public void testInclusiveLowerNumericRange() throws Exception {
// assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, true, false, 1);
// assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, true, false, 1);
// assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, true, false, 2);
// assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, true, false, 1);
// }
// @Test
// test disabled since standard syntax parser does not work with inclusive and
// exclusive at the same time
// public void testInclusiveUpperNumericRange() throws Exception {
// assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, false, true, 1);
// assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, false, true, 1);
// assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, false, true, 2);
// assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, false, true, 1);
// }
@Test
public void testExclusiveNumericRange() throws Exception {
assertRangeQuery(NumberType.ZERO, NumberType.ZERO, false, false, 0);
assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, false, false, 0);
assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, false, false, 0);
assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, false, false, 1);
assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, false, false, 0);
}
@Test
public void testSimpleNumericQuery() throws Exception {
assertSimpleQuery(NumberType.ZERO, 1);
assertSimpleQuery(NumberType.POSITIVE, 1);
assertSimpleQuery(NumberType.NEGATIVE, 1);
}
public void assertRangeQuery(NumberType lowerType, NumberType upperType,
boolean upperInclusive, boolean lowerInclusive, int expectedDocCount)
throws QueryNodeException, IOException {
StringBuilder sb = new StringBuilder();
String lowerInclusiveStr = (lowerInclusive ? "[" : "{");
String upperInclusiveStr = (upperInclusive ? "]" : "}");
for (NumericField.DataType type : NumericField.DataType.values()) {
String lowerStr = numberToString(getNumberType(lowerType, type.name()));
String upperStr = numberToString(getNumberType(upperType, type.name()));
sb.append("+").append(type.name()).append(':').append(lowerInclusiveStr)
.append('"').append(lowerStr).append("\" TO \"").append(upperStr)
.append('"').append(upperInclusiveStr).append(' ');
}
String lowerDateStr = ESCAPER.escape(
DATE_FORMAT.format(new Date(getNumberType(lowerType, DATE_FIELD_NAME)
.longValue())), LOCALE, EscapeQuerySyntax.Type.STRING).toString();
String upperDateStr = ESCAPER.escape(
DATE_FORMAT.format(new Date(getNumberType(upperType, DATE_FIELD_NAME)
.longValue())), LOCALE, EscapeQuerySyntax.Type.STRING).toString();
sb.append("+").append(DATE_FIELD_NAME).append(':')
.append(lowerInclusiveStr).append('"').append(lowerDateStr).append(
"\" TO \"").append(upperDateStr).append('"').append(
upperInclusiveStr);
testQuery(sb.toString(), expectedDocCount);
}
public void assertSimpleQuery(NumberType numberType, int expectedDocCount)
throws QueryNodeException, IOException {
StringBuilder sb = new StringBuilder();
for (NumericField.DataType type : NumericField.DataType.values()) {
String numberStr = numberToString(getNumberType(numberType, type.name()));
sb.append('+').append(type.name()).append(":\"").append(numberStr)
.append("\" ");
}
String dateStr = ESCAPER.escape(
DATE_FORMAT.format(new Date(getNumberType(numberType, DATE_FIELD_NAME)
.longValue())), LOCALE, EscapeQuerySyntax.Type.STRING).toString();
sb.append('+').append(DATE_FIELD_NAME).append(":\"").append(dateStr)
.append('"');
testQuery(sb.toString(), expectedDocCount);
}
private void testQuery(String queryStr, int expectedDocCount)
throws QueryNodeException, IOException {
if (VERBOSE) System.out.println("Parsing: " + queryStr);
Query query = qp.parse(queryStr, FIELD_NAME);
if (VERBOSE) System.out.println("Querying: " + query);
TopDocs topDocs = searcher.search(query, 1000);
String msg = "Query <" + queryStr + "> retrieved " + topDocs.totalHits
+ " document(s), " + expectedDocCount + " document(s) expected.";
if (VERBOSE) System.out.println(msg);
assertEquals(msg, expectedDocCount, topDocs.totalHits);
}
private static String numberToString(Number number) {
return ESCAPER.escape(NUMBER_FORMAT.format(number), LOCALE,
EscapeQuerySyntax.Type.STRING).toString();
}
private static Number normalizeNumber(Number number) throws ParseException {
return NUMBER_FORMAT.parse(NUMBER_FORMAT.format(number));
}
@AfterClass
public static void afterClass() throws Exception {
searcher.close();
searcher = null;
reader.close();
reader = null;
directory.close();
directory = null;
}
}