diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/messages/QueryParserMessages.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/messages/QueryParserMessages.java index 50428b0b36f..4e069caf374 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/messages/QueryParserMessages.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/messages/QueryParserMessages.java @@ -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; } diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldQueryNode.java index 33614977285..b1d3b7b5bdf 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldQueryNode.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldQueryNode.java @@ -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, 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); + } + } diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldValuePairQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldValuePairQueryNode.java new file mode 100644 index 00000000000..f73b42b193e --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldValuePairQueryNode.java @@ -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 extends FieldableNode, ValueQueryNode { + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/NumberQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/NumberQueryNode.java new file mode 100644 index 00000000000..847d78f1652 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/NumberQueryNode.java @@ -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(); + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/ValueQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/ValueQueryNode.java new file mode 100644 index 00000000000..f219f604142 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/ValueQueryNode.java @@ -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 extends QueryNode { + + public void setValue(T value); + + public T getValue(); + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/StandardQueryParser.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/StandardQueryParser.java index 9b7167efb5b..014addfda30 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/StandardQueryParser.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/StandardQueryParser.java @@ -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; @@ -109,7 +110,7 @@ import org.apache.lucene.search.Query; * @see StandardQueryTreeBuilder */ public class StandardQueryParser extends QueryParserHelper { - + /** * Constructs a {@link StandardQueryParser} object. */ @@ -118,7 +119,7 @@ public class StandardQueryParser extends QueryParserHelper { new StandardQueryNodeProcessorPipeline(null), new StandardQueryTreeBuilder()); } - + /** * Constructs a {@link StandardQueryParser} object and sets an * {@link Analyzer} to it. The same as: @@ -133,15 +134,16 @@ public class StandardQueryParser extends QueryParserHelper { */ public StandardQueryParser(Analyzer analyzer) { this(); - + this.setAnalyzer(analyzer); } @Override - public String toString(){ - return ""; + public String toString() { + return ""; } - + /** * Overrides {@link QueryParserHelper#parse(String, String)} so it casts the * return object to {@link Query}. For more reference about this method, check @@ -160,11 +162,11 @@ public class StandardQueryParser extends QueryParserHelper { @Override public Query parse(String query, String defaultField) throws QueryNodeException { - + return (Query) super.parse(query, defaultField); - + } - + /** * Gets implicit operator setting, which will be either {@link Operator#AND} * or {@link Operator#OR}. @@ -172,7 +174,7 @@ public class StandardQueryParser extends QueryParserHelper { public StandardQueryConfigHandler.Operator getDefaultOperator() { return getQueryConfigHandler().get(ConfigurationKeys.DEFAULT_OPERATOR); } - + /** * Sets the boolean operator of the QueryParser. In default mode ( * {@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) { getQueryConfigHandler().set(ConfigurationKeys.DEFAULT_OPERATOR, operator); } - + /** * Set to true to allow leading wildcard characters. *

@@ -197,7 +199,7 @@ public class StandardQueryParser extends QueryParserHelper { public void setLowercaseExpandedTerms(boolean lowercaseExpandedTerms) { getQueryConfigHandler().set(ConfigurationKeys.LOWERCASE_EXPANDED_TERMS, lowercaseExpandedTerms); } - + /** * @see #setLowercaseExpandedTerms(boolean) */ @@ -212,7 +214,7 @@ public class StandardQueryParser extends QueryParserHelper { } } - + /** * Set to true to allow leading wildcard characters. *

@@ -225,7 +227,7 @@ public class StandardQueryParser extends QueryParserHelper { public void setAllowLeadingWildcard(boolean allowLeadingWildcard) { getQueryConfigHandler().set(ConfigurationKeys.ALLOW_LEADING_WILDCARD, allowLeadingWildcard); } - + /** * Set to true to enable position increments in result query. *

@@ -238,7 +240,7 @@ public class StandardQueryParser extends QueryParserHelper { public void setEnablePositionIncrements(boolean enabled) { getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS, enabled); } - + /** * @see #setEnablePositionIncrements(boolean) */ @@ -253,7 +255,7 @@ public class StandardQueryParser extends QueryParserHelper { } } - + /** * By default, it uses * {@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) { getQueryConfigHandler().set(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD, method); } - + /** * @see #setMultiTermRewriteMethod(org.apache.lucene.search.MultiTermQuery.RewriteMethod) */ public MultiTermQuery.RewriteMethod getMultiTermRewriteMethod() { return getQueryConfigHandler().get(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD); } - + /** * Set the fields a query should be expanded to when the field is * null @@ -282,15 +284,15 @@ public class StandardQueryParser extends QueryParserHelper { * @param fields the fields used to expand the query */ public void setMultiFields(CharSequence[] fields) { - + if (fields == null) { fields = new CharSequence[0]; } getQueryConfigHandler().set(ConfigurationKeys.MULTI_FIELDS, fields); - + } - + /** * Returns the fields used to expand the query when the field for a * certain query is null @@ -319,21 +321,29 @@ public class StandardQueryParser extends QueryParserHelper { fuzzyConfig.setPrefixLength(fuzzyPrefixLength); } - + + public void setNumericConfigMap(Map numericConfigMap) { + getQueryConfigHandler().set(ConfigurationKeys.NUMERIC_CONFIG_MAP, numericConfigMap); + } + + public Map getNumericConfigMap() { + return getQueryConfigHandler().get(ConfigurationKeys.NUMERIC_CONFIG_MAP); + } + /** * Set locale used by date range parsing. */ public void setLocale(Locale locale) { getQueryConfigHandler().set(ConfigurationKeys.LOCALE, locale); } - + /** * Returns current locale, allowing access by subclasses. */ public Locale getLocale() { return getQueryConfigHandler().get(ConfigurationKeys.LOCALE); } - + /** * Sets the default slop for phrases. If zero, then exact phrase matches are * required. Default value is zero. @@ -357,10 +367,10 @@ public class StandardQueryParser extends QueryParserHelper { getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer); } - public Analyzer getAnalyzer() { + public Analyzer getAnalyzer() { return getQueryConfigHandler().get(ConfigurationKeys.ANALYZER); } - + /** * @see #setAllowLeadingWildcard(boolean) */ @@ -374,7 +384,7 @@ public class StandardQueryParser extends QueryParserHelper { return allowLeadingWildcard; } } - + /** * Get the minimal similarity for fuzzy queries. */ @@ -387,7 +397,7 @@ public class StandardQueryParser extends QueryParserHelper { return fuzzyConfig.getMinSimilarity(); } } - + /** * Get the prefix length for fuzzy queries. * @@ -402,7 +412,7 @@ public class StandardQueryParser extends QueryParserHelper { return fuzzyConfig.getPrefixLength(); } } - + /** * Gets the default slop for phrases. */ @@ -416,7 +426,7 @@ public class StandardQueryParser extends QueryParserHelper { return phraseSlop; } } - + /** * Set the minimum similarity for fuzzy queries. Default is defined on * {@link FuzzyQuery#defaultMinSimilarity}. diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/DummyQueryNodeBuilder.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/DummyQueryNodeBuilder.java new file mode 100644 index 00000000000..76284bafd13 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/DummyQueryNodeBuilder.java @@ -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; + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/NumericRangeQueryNodeBuilder.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/NumericRangeQueryNodeBuilder.java new file mode 100644 index 00000000000..10cf888a723 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/NumericRangeQueryNodeBuilder.java @@ -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 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)); + + } + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/StandardQueryTreeBuilder.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/StandardQueryTreeBuilder.java index 0db0ae2a947..e2860c6e635 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/StandardQueryTreeBuilder.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/StandardQueryTreeBuilder.java @@ -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; @@ -50,12 +52,14 @@ import org.apache.lucene.search.Query; */ public class StandardQueryTreeBuilder extends QueryTreeBuilder implements StandardQueryBuilder { - + public StandardQueryTreeBuilder() { setBuilder(GroupQueryNode.class, new GroupQueryNodeBuilder()); 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,19 +67,19 @@ 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, new StandardBooleanQueryNodeBuilder()); setBuilder(MultiPhraseQueryNode.class, new MultiPhraseQueryNodeBuilder()); setBuilder(MatchAllDocsQueryNode.class, new MatchAllDocsQueryNodeBuilder()); - + } - + @Override public Query build(QueryNode queryNode) throws QueryNodeException { return (Query) super.build(queryNode); } - + } diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/TermRangeQueryNodeBuilder.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/TermRangeQueryNodeBuilder.java new file mode 100644 index 00000000000..f9186114d5d --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/TermRangeQueryNodeBuilder.java @@ -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; + + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumberDateFormat.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumberDateFormat.java new file mode 100644 index 00000000000..b1e1272ad61 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumberDateFormat.java @@ -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); + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericConfig.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericConfig.java new file mode 100644 index 00000000000..df2570a2ab2 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericConfig.java @@ -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; + + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericFieldConfigListener.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericFieldConfigListener.java new file mode 100644 index 00000000000..95457810fef --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericFieldConfigListener.java @@ -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 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); + } + + } + + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/StandardQueryConfigHandler.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/StandardQueryConfigHandler.java index c1e2007ce2d..7bc23493a9b 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/StandardQueryConfigHandler.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/StandardQueryConfigHandler.java @@ -161,6 +161,22 @@ public class StandardQueryConfigHandler extends QueryConfigHandler { */ final public static ConfigurationKey BOOST = ConfigurationKey.newInstance(); + /** + * Key used to set a field to its {@link NumericConfig}. + * + * @see StandardQueryParser#setNumericConfigMap(Map) + * @see StandardQueryParser#getNumericConfigMap() + */ + final public static ConfigurationKey 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> 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 diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/AbstractRangeQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/AbstractRangeQueryNode.java new file mode 100644 index 00000000000..ea9d8ea7424 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/AbstractRangeQueryNode.java @@ -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> + 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 children = new ArrayList(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(); + + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericQueryNode.java new file mode 100644 index 00000000000..abf0af8c4a1 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericQueryNode.java @@ -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 { + + 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 ""; + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericRangeQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericRangeQueryNode.java new file mode 100644 index 00000000000..d718733c8e9 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericRangeQueryNode.java @@ -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 { + + 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("\n"); + + sb.append(getLowerBound()).append('\n'); + sb.append(getUpperBound()).append('\n'); + sb.append(""); + + return sb.toString(); + + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/TermRangeQueryNode.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/TermRangeQueryNode.java new file mode 100644 index 00000000000..d02cb0ccc9e --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/TermRangeQueryNode.java @@ -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 { + + public TermRangeQueryNode(FieldQueryNode lower, FieldQueryNode upper, + boolean lowerInclusive, boolean upperInclusive) { + setBounds(lower, upper, lowerInclusive, upperInclusive); + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/AnalyzerQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/AnalyzerQueryNodeProcessor.java index 396faa456b8..e4302c7bb56 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/AnalyzerQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/AnalyzerQueryNodeProcessor.java @@ -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; diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/DefaultPhraseSlopQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/DefaultPhraseSlopQueryNodeProcessor.java index 35ffeba0d7d..1cf1c1c846b 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/DefaultPhraseSlopQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/DefaultPhraseSlopQueryNodeProcessor.java @@ -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; diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/FuzzyQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/FuzzyQueryNodeProcessor.java index 3d54f961156..16db38b3733 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/FuzzyQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/FuzzyQueryNodeProcessor.java @@ -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; diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/LowercaseExpandedTermsQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/LowercaseExpandedTermsQueryNodeProcessor.java index c17286500c4..98deba99f34 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/LowercaseExpandedTermsQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/LowercaseExpandedTermsQueryNodeProcessor.java @@ -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; diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiFieldQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiFieldQueryNodeProcessor.java index 750c2769495..4351cea402c 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiFieldQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiFieldQueryNodeProcessor.java @@ -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; /** diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiTermRewriteMethodProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiTermRewriteMethodProcessor.java index e7401a45d55..bea7ac5cd24 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiTermRewriteMethodProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/MultiTermRewriteMethodProcessor.java @@ -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); diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericQueryNodeProcessor.java new file mode 100644 index 00000000000..1b09056e5c1 --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericQueryNodeProcessor.java @@ -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 setChildrenOrder(List children) + throws QueryNodeException { + return children; + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java new file mode 100644 index 00000000000..c7c95a6307f --- /dev/null +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java @@ -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 setChildrenOrder(List children) + throws QueryNodeException { + return children; + } + +} diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java index 56bd3e3305e..decb18a4073 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java @@ -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.
+ * value, it will only create the {@link TermRangeQueryNode} using the + * non-parsed values.
*
* 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,18 +54,18 @@ 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 { - + public ParametricRangeQueryNodeProcessor() { - // empty constructor + // empty constructor } - + @Override protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException { - + if (node instanceof ParametricRangeQueryNode) { ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node; ParametricQueryNode upper = parametricRangeNode.getUpperBound(); @@ -79,31 +78,31 @@ public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl { if (locale == null) { locale = Locale.getDefault(); } - + CharSequence field = parametricRangeNode.getField(); String fieldStr = null; - + if (field != null) { fieldStr = field.toString(); } - + FieldConfig fieldConfig = getQueryConfigHandler() .getFieldConfig(fieldStr); - + if (fieldConfig != null) { dateRes = fieldConfig.get(ConfigurationKeys.DATE_RESOLUTION); } - + if (upper.getOperator() == CompareOperator.LE) { inclusive = true; - + } else if (lower.getOperator() == CompareOperator.GE) { inclusive = true; } - + String part1 = lower.getTextAsString(); String part2 = upper.getTextAsString(); - + try { DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); df.setLenient(true); @@ -121,37 +120,39 @@ public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl { cal.set(Calendar.MILLISECOND, 999); d2 = cal.getTime(); } - + part1 = DateTools.dateToString(d1, dateRes); part2 = DateTools.dateToString(d2, dateRes); } catch (Exception e) { // do nothing } - + lower.setText(part1); upper.setText(part2); - - return new RangeQueryNode(lower, upper); - + + return new TermRangeQueryNode(lower, upper, + lower.getOperator() == CompareOperator.GE, + upper.getOperator() == CompareOperator.LE); + } - + return node; - + } - + @Override protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException { - + return node; - + } - + @Override protected List setChildrenOrder(List children) throws QueryNodeException { - + return children; - + } - + } diff --git a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java index 029eef9a0f8..a381084d92d 100644 --- a/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java +++ b/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java @@ -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()); diff --git a/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties b/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties index f732dde9d64..fa7aa0d4821 100644 --- a/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties +++ b/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties @@ -46,3 +46,12 @@ TOO_MANY_BOOLEAN_CLAUSES = Too many boolean clauses, the maximum supported is {0 #Apache Lucene Community LEADING_WILDCARD_NOT_ALLOWED = Leading wildcard is not allowed: {0} + +#Apache Lucene Community +COULD_NOT_PARSE_NUMBER = Could not parse text "{0}" using {1} + +#Apache Lucene Community +NUMBER_CLASS_NOT_SUPPORTED_BY_NUMERIC_RANGE_QUERY = Number class not supported by NumericRangeQueryNode: {0} + +#Apache Lucene Community +UNSUPPORTED_NUMERIC_DATA_TYPE = Unsupported NumericField.DataType: {0} diff --git a/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java b/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java new file mode 100644 index 00000000000..51f19557f37 --- /dev/null +++ b/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java @@ -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 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 randomNumberMap = new HashMap(); + + 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 numericConfigMap = new HashMap(); + HashMap numericFieldMap = new HashMap(); + 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 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; + } + +}