mirror of https://github.com/apache/lucene.git
LUCENE-2039: Added extensible query parser which enables arbitrary parser extensions based on field naming scheme
git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@887533 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ab447b6af0
commit
5556599fad
|
@ -16,6 +16,11 @@ API Changes
|
||||||
reader. (Eirik Bjørsnøs via Mike McCandless)
|
reader. (Eirik Bjørsnøs via Mike McCandless)
|
||||||
|
|
||||||
New features
|
New features
|
||||||
|
|
||||||
|
* LUCENE-2039: Add a extensible query parser to contrib/misc.
|
||||||
|
ExtendableQueryParser enables arbitrary parser extensions based on a
|
||||||
|
customizable field naming scheme.
|
||||||
|
(Simon Willnauer)
|
||||||
|
|
||||||
* LUCENE-2108: Spellchecker now safely supports concurrent modifications to
|
* LUCENE-2108: Spellchecker now safely supports concurrent modifications to
|
||||||
the spell-index. Threads can safely obtain term suggestions while the spell-
|
the spell-index. Threads can safely obtain term suggestions while the spell-
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.analysis.Analyzer;
|
||||||
|
import org.apache.lucene.queryParser.ParseException;
|
||||||
|
import org.apache.lucene.queryParser.QueryParser;
|
||||||
|
import org.apache.lucene.queryParser.ext.Extensions.Pair;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.util.Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ExtendableQueryParser} enables arbitrary query parser extension
|
||||||
|
* based on a customizable field naming scheme. The lucene query syntax allows
|
||||||
|
* implicit and explicit field definitions as query prefix followed by a colon
|
||||||
|
* (':') character. The {@link ExtendableQueryParser} allows to encode extension
|
||||||
|
* keys into the field symbol associated with a registered instance of
|
||||||
|
* {@link ParserExtension}. A customizable separation character separates the
|
||||||
|
* extension key from the actual field symbol. The {@link ExtendableQueryParser}
|
||||||
|
* splits (@see {@link Extensions#splitExtensionField(String, String)}) the
|
||||||
|
* extension key from the field symbol and tries to resolve the associated
|
||||||
|
* {@link ParserExtension}. If the parser can't resolve the key or the field
|
||||||
|
* token does not contain a separation character, {@link ExtendableQueryParser}
|
||||||
|
* yields the same behavior as its super class {@link QueryParser}. Otherwise,
|
||||||
|
* if the key is associated with a {@link ParserExtension} instance, the parser
|
||||||
|
* builds an instance of {@link ExtensionQuery} to be processed by
|
||||||
|
* {@link ParserExtension#parse(ExtensionQuery)}.If a extension field does not
|
||||||
|
* contain a field part the default field for the query will be used.
|
||||||
|
* <p>
|
||||||
|
* To guarantee that an extension field is processed with its associated
|
||||||
|
* extension, the extension query part must escape any special characters like
|
||||||
|
* '*' or '['. If the extension query contains any whitespace characters, the
|
||||||
|
* extension query part must be enclosed in quotes.
|
||||||
|
* Example ('_' used as separation character):
|
||||||
|
* <pre>
|
||||||
|
* title_customExt:"Apache Lucene\?" OR content_customExt:prefix\*
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Search on the default field:
|
||||||
|
* <pre>
|
||||||
|
* _customExt:"Apache Lucene\?" OR _customExt:prefix\*
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The {@link ExtendableQueryParser} itself does not implement the logic how
|
||||||
|
* field and extension key are separated or ordered. All logic regarding the
|
||||||
|
* extension key and field symbol parsing is located in {@link Extensions}.
|
||||||
|
* Customized extension schemes should be implemented by sub-classing
|
||||||
|
* {@link Extensions}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For details about the default encoding scheme see {@link Extensions}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see Extensions
|
||||||
|
* @see ParserExtension
|
||||||
|
* @see ExtensionQuery
|
||||||
|
*/
|
||||||
|
public class ExtendableQueryParser extends QueryParser {
|
||||||
|
|
||||||
|
private final String defaultField;
|
||||||
|
private final Extensions extensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default empty extensions instance
|
||||||
|
*/
|
||||||
|
private static final Extensions DEFAULT_EXTENSION = new Extensions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ExtendableQueryParser} instance
|
||||||
|
*
|
||||||
|
* @param matchVersion
|
||||||
|
* the lucene version to use.
|
||||||
|
* @param f
|
||||||
|
* the default query field
|
||||||
|
* @param a
|
||||||
|
* the analyzer used to find terms in a query string
|
||||||
|
*/
|
||||||
|
public ExtendableQueryParser(final Version matchVersion, final String f,
|
||||||
|
final Analyzer a) {
|
||||||
|
this(matchVersion, f, a, DEFAULT_EXTENSION);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ExtendableQueryParser} instance
|
||||||
|
*
|
||||||
|
* @param matchVersion
|
||||||
|
* the lucene version to use.
|
||||||
|
* @param f
|
||||||
|
* the default query field
|
||||||
|
* @param a
|
||||||
|
* the analyzer used to find terms in a query string
|
||||||
|
* @param ext
|
||||||
|
* the query parser extensions
|
||||||
|
*/
|
||||||
|
public ExtendableQueryParser(final Version matchVersion, final String f,
|
||||||
|
final Analyzer a, final Extensions ext) {
|
||||||
|
super(matchVersion, f, a);
|
||||||
|
this.defaultField = f;
|
||||||
|
this.extensions = ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension field delimiter character.
|
||||||
|
*
|
||||||
|
* @return the extension field delimiter character.
|
||||||
|
*/
|
||||||
|
public char getExtensionFieldDelimiter() {
|
||||||
|
return extensions.getExtensionFieldDelimiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Query getFieldQuery(final String field, final String queryText)
|
||||||
|
throws ParseException {
|
||||||
|
final Pair<String,String> splitExtensionField = this.extensions
|
||||||
|
.splitExtensionField(defaultField, field);
|
||||||
|
final ParserExtension extension = this.extensions
|
||||||
|
.getExtension(splitExtensionField.cud);
|
||||||
|
if (extension != null) {
|
||||||
|
return extension.parse(new ExtensionQuery(splitExtensionField.cur,
|
||||||
|
queryText));
|
||||||
|
}
|
||||||
|
return super.getFieldQuery(field, queryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ExtensionQuery} holds all query components extracted from the original
|
||||||
|
* query string like the query field and the extension query string.
|
||||||
|
*
|
||||||
|
* @see Extensions
|
||||||
|
* @see ExtendableQueryParser
|
||||||
|
* @see ParserExtension
|
||||||
|
*/
|
||||||
|
public class ExtensionQuery {
|
||||||
|
|
||||||
|
private final String field;
|
||||||
|
private final String rawQueryString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ExtensionQuery}
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* the query field
|
||||||
|
* @param rawQueryString
|
||||||
|
* the raw extension query string
|
||||||
|
*/
|
||||||
|
public ExtensionQuery(String field, String rawQueryString) {
|
||||||
|
this.field = field;
|
||||||
|
this.rawQueryString = rawQueryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the query field
|
||||||
|
*
|
||||||
|
* @return the query field
|
||||||
|
*/
|
||||||
|
public String getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw extension query string
|
||||||
|
*
|
||||||
|
* @return the raw extension query string
|
||||||
|
*/
|
||||||
|
public String getRawQueryString() {
|
||||||
|
return rawQueryString;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.lucene.queryParser.QueryParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Extensions} class represents an extension mapping to associate
|
||||||
|
* {@link ParserExtension} instances with extension keys. An extension key is a
|
||||||
|
* string encoded into a Lucene standard query parser field symbol recognized by
|
||||||
|
* {@link ExtendableQueryParser}. The query parser passes each extension field
|
||||||
|
* token to {@link #splitExtensionField(String, String)} to separate the
|
||||||
|
* extension key from the field identifier.
|
||||||
|
* <p>
|
||||||
|
* In addition to the key to extension mapping this class also defines the field
|
||||||
|
* name overloading scheme. {@link ExtendableQueryParser} uses the given
|
||||||
|
* extension to split the actual field name and extension key by calling
|
||||||
|
* {@link #splitExtensionField(String, String)}. To change the order or the key
|
||||||
|
* / field name encoding scheme users can subclass {@link Extensions} to
|
||||||
|
* implement their own.
|
||||||
|
*
|
||||||
|
* @see ExtendableQueryParser
|
||||||
|
* @see ParserExtension
|
||||||
|
*/
|
||||||
|
public class Extensions {
|
||||||
|
private final Map<String,ParserExtension> extensions = new HashMap<String,ParserExtension>();
|
||||||
|
private final char extensionFieldDelimiter;
|
||||||
|
/**
|
||||||
|
* The default extension field delimiter character. This constant is set to
|
||||||
|
* ':'
|
||||||
|
*/
|
||||||
|
public static final char DEFAULT_EXTENSION_FIELD_DELIMITER = ':';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Extensions} instance with the
|
||||||
|
* {@link #DEFAULT_EXTENSION_FIELD_DELIMITER} as a delimiter character.
|
||||||
|
*/
|
||||||
|
public Extensions() {
|
||||||
|
this(DEFAULT_EXTENSION_FIELD_DELIMITER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Extensions} instance
|
||||||
|
*
|
||||||
|
* @param extensionFieldDelimiter
|
||||||
|
* the extensions field delimiter character
|
||||||
|
*/
|
||||||
|
public Extensions(char extensionFieldDelimiter) {
|
||||||
|
this.extensionFieldDelimiter = extensionFieldDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new {@link ParserExtension} instance associated with the given key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the parser extension key
|
||||||
|
* @param extension
|
||||||
|
* the parser extension
|
||||||
|
*/
|
||||||
|
public void add(String key, ParserExtension extension) {
|
||||||
|
this.extensions.put(key, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link ParserExtension} instance for the given key or
|
||||||
|
* <code>null</code> if no extension can be found for the key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the extension key
|
||||||
|
* @return the {@link ParserExtension} instance for the given key or
|
||||||
|
* <code>null</code> if no extension can be found for the key.
|
||||||
|
*/
|
||||||
|
public final ParserExtension getExtension(String key) {
|
||||||
|
return this.extensions.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extension field delimiter
|
||||||
|
*
|
||||||
|
* @return the extension field delimiter
|
||||||
|
*/
|
||||||
|
public char getExtensionFieldDelimiter() {
|
||||||
|
return extensionFieldDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a extension field and returns the field / extension part as a
|
||||||
|
* {@link Pair}. This method tries to split on the first occurrence of the
|
||||||
|
* extension field delimiter, if the delimiter is not present in the string
|
||||||
|
* the result will contain a <code>null</code> value for the extension key and
|
||||||
|
* the given field string as the field value. If the given extension field
|
||||||
|
* string contains no field identifier the result pair will carry the given
|
||||||
|
* default field as the field value.
|
||||||
|
*
|
||||||
|
* @param defaultField
|
||||||
|
* the default query field
|
||||||
|
* @param field
|
||||||
|
* the extension field string
|
||||||
|
* @return a {@link Pair} with the field name as the {@link Pair#cur} and the
|
||||||
|
* extension key as the {@link Pair#cud}
|
||||||
|
*/
|
||||||
|
public Pair<String,String> splitExtensionField(String defaultField,
|
||||||
|
String field) {
|
||||||
|
int indexOf = field.indexOf(this.extensionFieldDelimiter);
|
||||||
|
if (indexOf < 0)
|
||||||
|
return new Pair<String,String>(field, null);
|
||||||
|
final String indexField = indexOf == 0 ? defaultField : field.substring(0,
|
||||||
|
indexOf);
|
||||||
|
final String extensionKey = field.substring(indexOf + 1);
|
||||||
|
return new Pair<String,String>(indexField, extensionKey);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes an extension field. The default implementation is equivalent to
|
||||||
|
* {@link QueryParser#escape(String)}.
|
||||||
|
*
|
||||||
|
* @param extfield
|
||||||
|
* the extension field identifier
|
||||||
|
* @return the extension field identifier with all special chars escaped with
|
||||||
|
* a backslash character.
|
||||||
|
*/
|
||||||
|
public String escapeExtensionField(String extfield) {
|
||||||
|
return QueryParser.escape(extfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an extension field string from a given extension key and the default
|
||||||
|
* query field. The default field and the key are delimited with the extension
|
||||||
|
* field delimiter character. This method makes no assumption about the order
|
||||||
|
* of the extension key and the field. By default the extension key is
|
||||||
|
* appended to the end of the returned string while the field is added to the
|
||||||
|
* beginning. Special Query characters are escaped in the result.
|
||||||
|
* <p>
|
||||||
|
* Note: {@link Extensions} subclasses must maintain the contract between
|
||||||
|
* {@link #buildExtensionField(String)} and
|
||||||
|
* {@link #splitExtensionField(String, String)} where the latter inverts the
|
||||||
|
* former.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public String buildExtensionField(String extensionKey) {
|
||||||
|
return buildExtensionField(extensionKey, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an extension field string from a given extension key and the
|
||||||
|
* extensions field. The field and the key are delimited with the extension
|
||||||
|
* field delimiter character. This method makes no assumption about the order
|
||||||
|
* of the extension key and the field. By default the extension key is
|
||||||
|
* appended to the end of the returned string while the field is added to the
|
||||||
|
* beginning. Special Query characters are escaped in the result.
|
||||||
|
* <p>
|
||||||
|
* Note: {@link Extensions} subclasses must maintain the contract between
|
||||||
|
* {@link #buildExtensionField(String, String)} and
|
||||||
|
* {@link #splitExtensionField(String, String)} where the latter inverts the
|
||||||
|
* former.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param extensionKey
|
||||||
|
* the extension key
|
||||||
|
* @param field
|
||||||
|
* the field to apply the extension on.
|
||||||
|
* @return escaped extension field identifier
|
||||||
|
* @see #buildExtensionField(String) to use the default query field
|
||||||
|
*/
|
||||||
|
public String buildExtensionField(String extensionKey, String field) {
|
||||||
|
StringBuilder builder = new StringBuilder(field);
|
||||||
|
builder.append(this.extensionFieldDelimiter);
|
||||||
|
builder.append(extensionKey);
|
||||||
|
return escapeExtensionField(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a generic pair.
|
||||||
|
*
|
||||||
|
* @param <Cur>
|
||||||
|
* the pairs first element
|
||||||
|
* @param <Cud>
|
||||||
|
* the pairs last element of the pair.
|
||||||
|
*/
|
||||||
|
public static class Pair<Cur,Cud> {
|
||||||
|
|
||||||
|
public final Cur cur;
|
||||||
|
public final Cud cud;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Pair
|
||||||
|
*
|
||||||
|
* @param cur
|
||||||
|
* the pairs first element
|
||||||
|
* @param cud
|
||||||
|
* the pairs last element
|
||||||
|
*/
|
||||||
|
public Pair(Cur cur, Cud cud) {
|
||||||
|
this.cur = cur;
|
||||||
|
this.cud = cud;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.ParseException;
|
||||||
|
import org.apache.lucene.queryParser.QueryParser;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an extension base class to the Lucene standard
|
||||||
|
* {@link QueryParser}. The {@link QueryParser} is generated by the JavaCC
|
||||||
|
* parser generator. Changing or adding functionality or syntax in the standard
|
||||||
|
* query parser requires changes to the JavaCC source file. To enable extending
|
||||||
|
* the standard query parser without changing the JavaCC sources and re-generate
|
||||||
|
* the parser the {@link ParserExtension} can be customized and plugged into an
|
||||||
|
* instance of {@link ExtendableQueryParser}, a direct subclass of
|
||||||
|
* {@link QueryParser}.
|
||||||
|
*
|
||||||
|
* @see Extensions
|
||||||
|
* @see ExtendableQueryParser
|
||||||
|
*/
|
||||||
|
public abstract class ParserExtension {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the given {@link ExtensionQuery} and returns a corresponding
|
||||||
|
* {@link Query} instance. Subclasses must either return a {@link Query}
|
||||||
|
* instance or raise a {@link ParseException}. This method must not return
|
||||||
|
* <code>null</code>.
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* the extension query
|
||||||
|
* @return a new query instance
|
||||||
|
* @throws ParseException
|
||||||
|
* if the query can not be parsed.
|
||||||
|
*/
|
||||||
|
public abstract Query parse(final ExtensionQuery query) throws ParseException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<html><head></head>
|
||||||
|
<body>
|
||||||
|
Extendable QueryParser provides a simple and flexible extension mechanism by overloading query field names.
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
import org.apache.lucene.index.Term;
|
||||||
|
import org.apache.lucene.queryParser.ParseException;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.TermQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ExtensionStub extends ParserExtension {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query parse(ExtensionQuery components) throws ParseException {
|
||||||
|
return new TermQuery(new Term(components.getField(), components
|
||||||
|
.getRawQueryString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.analysis.Analyzer;
|
||||||
|
import org.apache.lucene.analysis.SimpleAnalyzer;
|
||||||
|
import org.apache.lucene.queryParser.ParseException;
|
||||||
|
import org.apache.lucene.queryParser.QueryParser;
|
||||||
|
import org.apache.lucene.queryParser.TestQueryParser;
|
||||||
|
import org.apache.lucene.search.BooleanClause;
|
||||||
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.TermQuery;
|
||||||
|
import org.apache.lucene.util.Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testcase for the class {@link ExtendableQueryParser}
|
||||||
|
*/
|
||||||
|
public class TestExtendableQueryParser extends TestQueryParser {
|
||||||
|
private static char[] DELIMITERS = new char[] {
|
||||||
|
Extensions.DEFAULT_EXTENSION_FIELD_DELIMITER, '-', '|' };
|
||||||
|
|
||||||
|
public TestExtendableQueryParser(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParser getParser(Analyzer a) throws Exception {
|
||||||
|
return getParser(a, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParser getParser(Analyzer a, Extensions extensions)
|
||||||
|
throws Exception {
|
||||||
|
if (a == null)
|
||||||
|
a = new SimpleAnalyzer();
|
||||||
|
QueryParser qp = extensions == null ? new ExtendableQueryParser(
|
||||||
|
Version.LUCENE_CURRENT, "field", a) : new ExtendableQueryParser(
|
||||||
|
Version.LUCENE_CURRENT, "field", a, extensions);
|
||||||
|
qp.setDefaultOperator(QueryParser.OR_OPERATOR);
|
||||||
|
return qp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUnescapedExtDelimiter() throws Exception {
|
||||||
|
Extensions ext = newExtensions(':');
|
||||||
|
ext.add("testExt", new ExtensionStub());
|
||||||
|
ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null, ext);
|
||||||
|
try {
|
||||||
|
parser.parse("aField:testExt:\"foo \\& bar\"");
|
||||||
|
fail("extension field delimiter is not escaped");
|
||||||
|
} catch (ParseException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtFieldUnqoted() throws Exception {
|
||||||
|
for (int i = 0; i < DELIMITERS.length; i++) {
|
||||||
|
Extensions ext = newExtensions(DELIMITERS[i]);
|
||||||
|
ext.add("testExt", new ExtensionStub());
|
||||||
|
ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null,
|
||||||
|
ext);
|
||||||
|
String field = ext.buildExtensionField("testExt", "aField");
|
||||||
|
Query query = parser.parse(String.format("%s:foo bar", field));
|
||||||
|
assertTrue("expected instance of BooleanQuery but was "
|
||||||
|
+ query.getClass(), query instanceof BooleanQuery);
|
||||||
|
BooleanQuery bquery = (BooleanQuery) query;
|
||||||
|
BooleanClause[] clauses = bquery.getClauses();
|
||||||
|
assertEquals(2, clauses.length);
|
||||||
|
BooleanClause booleanClause = clauses[0];
|
||||||
|
query = booleanClause.getQuery();
|
||||||
|
assertTrue("expected instance of TermQuery but was " + query.getClass(),
|
||||||
|
query instanceof TermQuery);
|
||||||
|
TermQuery tquery = (TermQuery) query;
|
||||||
|
assertEquals("aField", tquery.getTerm()
|
||||||
|
.field());
|
||||||
|
assertEquals("foo", tquery.getTerm().text());
|
||||||
|
|
||||||
|
booleanClause = clauses[1];
|
||||||
|
query = booleanClause.getQuery();
|
||||||
|
assertTrue("expected instance of TermQuery but was " + query.getClass(),
|
||||||
|
query instanceof TermQuery);
|
||||||
|
tquery = (TermQuery) query;
|
||||||
|
assertEquals("field", tquery.getTerm().field());
|
||||||
|
assertEquals("bar", tquery.getTerm().text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtDefaultField() throws Exception {
|
||||||
|
for (int i = 0; i < DELIMITERS.length; i++) {
|
||||||
|
Extensions ext = newExtensions(DELIMITERS[i]);
|
||||||
|
ext.add("testExt", new ExtensionStub());
|
||||||
|
ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null,
|
||||||
|
ext);
|
||||||
|
String field = ext.buildExtensionField("testExt");
|
||||||
|
Query parse = parser.parse(String.format("%s:\"foo \\& bar\"", field));
|
||||||
|
assertTrue("expected instance of TermQuery but was " + parse.getClass(),
|
||||||
|
parse instanceof TermQuery);
|
||||||
|
TermQuery tquery = (TermQuery) parse;
|
||||||
|
assertEquals("field", tquery.getTerm().field());
|
||||||
|
assertEquals("foo & bar", tquery.getTerm().text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extensions newExtensions(char delimiter) {
|
||||||
|
return new Extensions(delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtField() throws Exception {
|
||||||
|
for (int i = 0; i < DELIMITERS.length; i++) {
|
||||||
|
Extensions ext = newExtensions(DELIMITERS[i]);
|
||||||
|
ext.add("testExt", new ExtensionStub());
|
||||||
|
ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null,
|
||||||
|
ext);
|
||||||
|
String field = ext.buildExtensionField("testExt", "afield");
|
||||||
|
Query parse = parser.parse(String.format("%s:\"foo \\& bar\"", field));
|
||||||
|
assertTrue("expected instance of TermQuery but was " + parse.getClass(),
|
||||||
|
parse instanceof TermQuery);
|
||||||
|
TermQuery tquery = (TermQuery) parse;
|
||||||
|
assertEquals("afield", tquery.getTerm().field());
|
||||||
|
assertEquals("foo & bar", tquery.getTerm().text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.apache.lucene.queryParser.ext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.util.LuceneTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testcase for the {@link Extensions} class
|
||||||
|
*/
|
||||||
|
public class TestExtensions extends LuceneTestCase {
|
||||||
|
|
||||||
|
private Extensions ext;
|
||||||
|
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
this.ext = new Extensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBuildExtensionField() {
|
||||||
|
assertEquals("field\\:key", ext.buildExtensionField("key", "field"));
|
||||||
|
assertEquals("\\:key", ext.buildExtensionField("key"));
|
||||||
|
|
||||||
|
ext = new Extensions('.');
|
||||||
|
assertEquals("field.key", ext.buildExtensionField("key", "field"));
|
||||||
|
assertEquals(".key", ext.buildExtensionField("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSplitExtensionField() {
|
||||||
|
assertEquals("field\\:key", ext.buildExtensionField("key", "field"));
|
||||||
|
assertEquals("\\:key", ext.buildExtensionField("key"));
|
||||||
|
|
||||||
|
ext = new Extensions('.');
|
||||||
|
assertEquals("field.key", ext.buildExtensionField("key", "field"));
|
||||||
|
assertEquals(".key", ext.buildExtensionField("key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAddGetExtension() {
|
||||||
|
ParserExtension extension = new ExtensionStub();
|
||||||
|
assertNull(ext.getExtension("foo"));
|
||||||
|
ext.add("foo", extension);
|
||||||
|
assertSame(extension, ext.getExtension("foo"));
|
||||||
|
ext.add("foo", null);
|
||||||
|
assertNull(ext.getExtension("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetExtDelimiter() {
|
||||||
|
assertEquals(Extensions.DEFAULT_EXTENSION_FIELD_DELIMITER, this.ext
|
||||||
|
.getExtensionFieldDelimiter());
|
||||||
|
ext = new Extensions('?');
|
||||||
|
assertEquals('?', this.ext.getExtensionFieldDelimiter());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEscapeExtension() {
|
||||||
|
assertEquals("abc\\:\\?\\{\\}\\[\\]\\\\\\(\\)\\+\\-\\!\\~", ext
|
||||||
|
.escapeExtensionField("abc:?{}[]\\()+-!~"));
|
||||||
|
try {
|
||||||
|
ext.escapeExtensionField(null);
|
||||||
|
fail("should throw NPE - escape string is null");
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue