From c178c8eb1a81012dbac333615412476b337685e8 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Sat, 9 Jul 2011 21:05:13 +0000 Subject: [PATCH] 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 --- .../core/messages/QueryParserMessages.java | 3 + .../core/nodes/FieldQueryNode.java | 11 +- .../core/nodes/FieldValuePairQueryNode.java | 22 + .../core/nodes/NumberQueryNode.java | 26 ++ .../core/nodes/ValueQueryNode.java | 26 ++ .../standard/StandardQueryParser.java | 70 +-- .../builders/DummyQueryNodeBuilder.java | 38 ++ .../NumericRangeQueryNodeBuilder.java | 93 ++++ .../builders/StandardQueryTreeBuilder.java | 16 +- .../builders/TermRangeQueryNodeBuilder.java | 60 +++ .../standard/config/NumberDateFormat.java | 59 +++ .../standard/config/NumericConfig.java | 94 ++++ .../config/NumericFieldConfigListener.java | 57 +++ .../config/StandardQueryConfigHandler.java | 17 + .../nodes/AbstractRangeQueryNode.java | 153 +++++++ .../standard/nodes/NumericQueryNode.java | 91 ++++ .../standard/nodes/NumericRangeQueryNode.java | 116 +++++ .../standard/nodes/TermRangeQueryNode.java | 35 ++ .../AnalyzerQueryNodeProcessor.java | 1 - .../DefaultPhraseSlopQueryNodeProcessor.java | 1 - .../processors/FuzzyQueryNodeProcessor.java | 1 - ...ercaseExpandedTermsQueryNodeProcessor.java | 1 - .../MultiFieldQueryNodeProcessor.java | 1 - .../MultiTermRewriteMethodProcessor.java | 4 +- .../processors/NumericQueryNodeProcessor.java | 122 +++++ .../NumericRangeQueryNodeProcessor.java | 143 ++++++ .../ParametricRangeQueryNodeProcessor.java | 65 +-- .../StandardQueryNodeProcessorPipeline.java | 2 + .../messages/QueryParserMessages.properties | 9 + .../standard/TestNumericQueryParser.java | 417 ++++++++++++++++++ 30 files changed, 1677 insertions(+), 77 deletions(-) create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/FieldValuePairQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/NumberQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/nodes/ValueQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/DummyQueryNodeBuilder.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/NumericRangeQueryNodeBuilder.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/builders/TermRangeQueryNodeBuilder.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumberDateFormat.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericConfig.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/config/NumericFieldConfigListener.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/AbstractRangeQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/NumericRangeQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/nodes/TermRangeQueryNode.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericQueryNodeProcessor.java create mode 100644 lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java create mode 100644 lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java 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; + } + +}