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 TOO_MANY_BOOLEAN_CLAUSES;
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
*/
public class FieldQueryNode extends QueryNodeImpl implements TextableQueryNode,
FieldableNode {
public class FieldQueryNode extends QueryNodeImpl implements FieldValuePairQueryNode<CharSequence>, TextableQueryNode {
/**
* 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.standard.builders.StandardQueryTreeBuilder;
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.ConfigurationKeys;
import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.Operator;
@ -138,8 +139,9 @@ public class StandardQueryParser extends QueryParserHelper {
}
@Override
public String toString(){
return "<StandardQueryParser config=\"" + this.getQueryConfigHandler() + "\"/>";
public String toString() {
return "<StandardQueryParser config=\"" + this.getQueryConfigHandler()
+ "\"/>";
}
/**
@ -320,6 +322,14 @@ public class StandardQueryParser extends QueryParserHelper {
}
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.
*/

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.TokenizedPhraseQueryNode;
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.RangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.TermRangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.RegexpQueryNode;
import org.apache.lucene.queryParser.standard.nodes.StandardBooleanQueryNode;
import org.apache.lucene.queryParser.standard.nodes.WildcardQueryNode;
@ -56,6 +58,8 @@ public class StandardQueryTreeBuilder extends QueryTreeBuilder implements
setBuilder(FieldQueryNode.class, new FieldQueryNodeBuilder());
setBuilder(BooleanQueryNode.class, new BooleanQueryNodeBuilder());
setBuilder(FuzzyQueryNode.class, new FuzzyQueryNodeBuilder());
setBuilder(NumericQueryNode.class, new DummyQueryNodeBuilder());
setBuilder(NumericRangeQueryNode.class, new NumericRangeQueryNodeBuilder());
setBuilder(BoostQueryNode.class, new BoostQueryNodeBuilder());
setBuilder(ModifierQueryNode.class, new ModifierQueryNodeBuilder());
setBuilder(WildcardQueryNode.class, new WildcardQueryNodeBuilder());
@ -63,7 +67,7 @@ public class StandardQueryTreeBuilder extends QueryTreeBuilder implements
setBuilder(MatchNoDocsQueryNode.class, new MatchNoDocsQueryNodeBuilder());
setBuilder(PrefixWildcardQueryNode.class,
new PrefixWildcardQueryNodeBuilder());
setBuilder(RangeQueryNode.class, new RangeQueryNodeBuilder());
setBuilder(TermRangeQueryNode.class, new TermRangeQueryNodeBuilder());
setBuilder(RegexpQueryNode.class, new RegexpQueryNodeBuilder());
setBuilder(SlopQueryNode.class, new SlopQueryNodeBuilder());
setBuilder(StandardBooleanQueryNode.class,

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();
/**
* 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 {
@ -171,6 +187,7 @@ public class StandardQueryConfigHandler extends QueryConfigHandler {
// Add listener that will build the FieldConfig.
addFieldConfigListener(new FieldBoostMapFCListener(this));
addFieldConfigListener(new FieldDateResolutionFCListener(this));
addFieldConfigListener(new NumericFieldConfigListener(this));
// Default Values
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.TokenizedPhraseQueryNode;
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.nodes.MultiPhraseQueryNode;
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.TokenizedPhraseQueryNode;
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.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.processors.QueryNodeProcessorImpl;
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.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.processors.QueryNodeProcessorImpl;
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.nodes.RegexpQueryNode;
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.QueryNode;
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;
/**

View File

@ -19,10 +19,10 @@ package org.apache.lucene.queryParser.standard.processors;
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.processors.QueryNodeProcessorImpl;
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.WildcardQueryNode;
import org.apache.lucene.search.MultiTermQuery;
@ -43,7 +43,7 @@ public class MultiTermRewriteMethodProcessor extends QueryNodeProcessorImpl {
// set setMultiTermRewriteMethod for WildcardQueryNode and
// PrefixWildcardQueryNode
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);

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.ParametricQueryNode.CompareOperator;
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.nodes.RangeQueryNode;
import org.apache.lucene.queryParser.standard.nodes.TermRangeQueryNode;
/**
* 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
* 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
* values. <br/>
* value, it will only create the {@link TermRangeQueryNode} using the
* non-parsed values. <br/>
* <br/>
* 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
@ -55,7 +54,7 @@ import org.apache.lucene.queryParser.standard.nodes.RangeQueryNode;
*
* @see ConfigurationKeys#DATE_RESOLUTION
* @see ConfigurationKeys#LOCALE
* @see RangeQueryNode
* @see TermRangeQueryNode
* @see ParametricRangeQueryNode
*/
public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
@ -131,7 +130,9 @@ public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
lower.setText(part1);
upper.setText(part2);
return new RangeQueryNode(lower, upper);
return new TermRangeQueryNode(lower, upper,
lower.getOperator() == CompareOperator.GE,
upper.getOperator() == CompareOperator.LE);
}

View File

@ -53,6 +53,8 @@ public class StandardQueryNodeProcessorPipeline extends
add(new FuzzyQueryNodeProcessor());
add(new MatchAllDocsQueryNodeProcessor());
add(new LowercaseExpandedTermsQueryNodeProcessor());
add(new NumericQueryNodeProcessor());
add(new NumericRangeQueryNodeProcessor());
add(new ParametricRangeQueryNodeProcessor());
add(new AllowLeadingWildcardProcessor());
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>
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;
}
}