| /* |
| * 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. |
| */ |
| package org.apache.solr.search; |
| |
| import org.apache.lucene.analysis.Analyzer; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.queryparser.simple.SimpleQueryParser; |
| import org.apache.lucene.search.BooleanClause; |
| import org.apache.lucene.search.BooleanQuery; |
| import org.apache.lucene.search.BoostQuery; |
| import org.apache.lucene.search.FuzzyQuery; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.util.BytesRef; |
| import org.apache.solr.common.params.CommonParams; |
| import org.apache.solr.common.params.SimpleParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.parser.QueryParser; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.schema.FieldType; |
| import org.apache.solr.schema.IndexSchema; |
| import org.apache.solr.schema.SchemaField; |
| import org.apache.solr.schema.TextField; |
| import org.apache.solr.util.SolrPluginUtils; |
| |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * Create a query from the input value that will be parsed by Lucene's SimpleQueryParser. |
| * See {@link org.apache.lucene.queryparser.simple.SimpleQueryParser} for details on the exact syntax allowed |
| * to be used for queries. |
| * <br> |
| * The following options may be applied for parsing the query. |
| * <ul> |
| * <li> |
| * q.operators - Used to enable specific operations for parsing. The operations that can be enabled are |
| * and, not, or, prefix, phrase, precedence, escape, and whitespace. By default all operations |
| * are enabled. All operations can be disabled by passing in an empty string to this parameter. |
| * </li> |
| * <li> |
| * q.op - Used to specify the operator to be used if whitespace is a delimiter. Either 'AND' or 'OR' |
| * can be specified for this parameter. Any other string will cause an exception to be thrown. |
| * If this parameter is not specified 'OR' will be used by default. |
| * </li> |
| * <li> |
| * qf - The list of query fields and boosts to use when building the simple query. The format is the following: |
| * <code>fieldA^1.0 fieldB^2.2</code>. A field can also be specified without a boost by simply listing the |
| * field as <code>fieldA fieldB</code>. Any field without a boost will default to use a boost of 1.0. |
| * </li> |
| * <li> |
| * df - An override for the default field specified in the schema or a default field if one is not specified |
| * in the schema. If qf is not specified the default field will be used as the field to run the query |
| * against. |
| * </li> |
| * </ul> |
| */ |
| public class SimpleQParserPlugin extends QParserPlugin { |
| /** The name that can be used to specify this plugin should be used to parse the query. */ |
| public static final String NAME = "simple"; |
| |
| /** Map of string operators to their int counterparts in SimpleQueryParser. */ |
| private static final Map<String, Integer> OPERATORS = new HashMap<>(); |
| |
| /* Setup the map of possible operators. */ |
| static { |
| OPERATORS.put(SimpleParams.AND_OPERATOR, SimpleQueryParser.AND_OPERATOR); |
| OPERATORS.put(SimpleParams.NOT_OPERATOR, SimpleQueryParser.NOT_OPERATOR); |
| OPERATORS.put(SimpleParams.OR_OPERATOR, SimpleQueryParser.OR_OPERATOR); |
| OPERATORS.put(SimpleParams.PREFIX_OPERATOR, SimpleQueryParser.PREFIX_OPERATOR); |
| OPERATORS.put(SimpleParams.PHRASE_OPERATOR, SimpleQueryParser.PHRASE_OPERATOR); |
| OPERATORS.put(SimpleParams.PRECEDENCE_OPERATORS, SimpleQueryParser.PRECEDENCE_OPERATORS); |
| OPERATORS.put(SimpleParams.ESCAPE_OPERATOR, SimpleQueryParser.ESCAPE_OPERATOR); |
| OPERATORS.put(SimpleParams.WHITESPACE_OPERATOR, SimpleQueryParser.WHITESPACE_OPERATOR); |
| OPERATORS.put(SimpleParams.FUZZY_OPERATOR, SimpleQueryParser.FUZZY_OPERATOR); |
| OPERATORS.put(SimpleParams.NEAR_OPERATOR, SimpleQueryParser.NEAR_OPERATOR); |
| } |
| |
| /** Returns a QParser that will create a query by using Lucene's SimpleQueryParser. */ |
| @Override |
| public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { |
| return new SimpleQParser(qstr, localParams, params, req); |
| } |
| |
| private static class SimpleQParser extends QParser { |
| private SimpleQueryParser parser; |
| |
| public SimpleQParser (String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { |
| |
| super(qstr, localParams, params, req); |
| // Some of the parameters may come in through localParams, so combine them with params. |
| SolrParams defaultParams = SolrParams.wrapDefaults(localParams, params); |
| |
| // This will be used to specify what fields and boosts will be used by SimpleQueryParser. |
| Map<String, Float> queryFields = SolrPluginUtils.parseFieldBoosts(defaultParams.get(SimpleParams.QF)); |
| |
| if (queryFields.isEmpty()) { |
| // It qf is not specified setup up the queryFields map to use the defaultField. |
| String defaultField = defaultParams.get(CommonParams.DF); |
| |
| if (defaultField == null) { |
| // A query cannot be run without having a field or set of fields to run against. |
| throw new IllegalStateException("Neither " + SimpleParams.QF + " nor " + CommonParams.DF |
| + " are present."); |
| } |
| |
| queryFields.put(defaultField, 1.0F); |
| } |
| else { |
| for (Map.Entry<String, Float> queryField : queryFields.entrySet()) { |
| if (queryField.getValue() == null) { |
| // Some fields may be specified without a boost, so default the boost to 1.0 since a null value |
| // will not be accepted by SimpleQueryParser. |
| queryField.setValue(1.0F); |
| } |
| } |
| } |
| |
| // Setup the operations that are enabled for the query. |
| int enabledOps = 0; |
| String opParam = defaultParams.get(SimpleParams.QO); |
| |
| if (opParam == null) { |
| // All operations will be enabled. |
| enabledOps = -1; |
| } else { |
| // Parse the specified enabled operations to be used by the query. |
| String[] operations = opParam.split(","); |
| |
| for (String operation : operations) { |
| Integer enabledOp = OPERATORS.get(operation.trim().toUpperCase(Locale.ROOT)); |
| |
| if (enabledOp != null) { |
| enabledOps |= enabledOp; |
| } |
| } |
| } |
| |
| // Create a SimpleQueryParser using the analyzer from the schema. |
| final IndexSchema schema = req.getSchema(); |
| parser = new SolrSimpleQueryParser(req.getSchema().getQueryAnalyzer(), queryFields, enabledOps, this, schema); |
| |
| // Set the default operator to be either 'AND' or 'OR' for the query. |
| QueryParser.Operator defaultOp = QueryParsing.parseOP(defaultParams.get(QueryParsing.OP)); |
| |
| if (defaultOp == QueryParser.Operator.AND) { |
| parser.setDefaultOperator(BooleanClause.Occur.MUST); |
| } |
| } |
| |
| @Override |
| public Query parse() throws SyntaxError { |
| return parser.parse(qstr); |
| } |
| |
| } |
| |
| private static class SolrSimpleQueryParser extends SimpleQueryParser { |
| QParser qParser; |
| IndexSchema schema; |
| |
| public SolrSimpleQueryParser(Analyzer analyzer, Map<String, Float> weights, int flags, |
| QParser qParser, IndexSchema schema) { |
| super(analyzer, weights, flags); |
| this.qParser = qParser; |
| this.schema = schema; |
| } |
| |
| @Override |
| protected Query newPrefixQuery(String text) { |
| BooleanQuery.Builder bq = new BooleanQuery.Builder(); |
| |
| for (Map.Entry<String, Float> entry : weights.entrySet()) { |
| String field = entry.getKey(); |
| FieldType type = schema.getFieldType(field); |
| Query prefix = null; |
| |
| if (type instanceof TextField) { |
| // If the field type is a TextField then use the multi term analyzer. |
| Analyzer analyzer = ((TextField)type).getMultiTermAnalyzer(); |
| BytesRef termBytes = TextField.analyzeMultiTerm(field, text, analyzer); |
| if (termBytes != null) { |
| String term = termBytes.utf8ToString(); |
| SchemaField sf = schema.getField(field); |
| prefix = sf.getType().getPrefixQuery(qParser, sf, term); |
| } |
| } else { |
| // If the type is *not* a TextField don't do any analysis. |
| SchemaField sf = schema.getField(field); |
| prefix = type.getPrefixQuery(qParser, sf, text); |
| } |
| if (prefix != null) { |
| float boost = entry.getValue(); |
| if (boost != 1f) { |
| prefix = new BoostQuery(prefix, boost); |
| } |
| bq.add(prefix, BooleanClause.Occur.SHOULD); |
| } |
| } |
| |
| return simplify(bq.build()); |
| } |
| |
| @Override |
| protected Query newFuzzyQuery(String text, int fuzziness) { |
| BooleanQuery.Builder bq = new BooleanQuery.Builder(); |
| |
| for (Map.Entry<String, Float> entry : weights.entrySet()) { |
| String field = entry.getKey(); |
| FieldType type = schema.getFieldType(field); |
| Query fuzzy = null; |
| |
| if (type instanceof TextField) { |
| // If the field type is a TextField then use the multi term analyzer. |
| Analyzer analyzer = ((TextField)type).getMultiTermAnalyzer(); |
| BytesRef termBytes = TextField.analyzeMultiTerm(field, text, analyzer); |
| if (termBytes != null) { |
| String term = termBytes.utf8ToString(); |
| fuzzy = new FuzzyQuery(new Term(entry.getKey(), term), fuzziness); |
| } |
| } else { |
| // If the type is *not* a TextField don't do any analysis. |
| fuzzy = new FuzzyQuery(new Term(entry.getKey(), text), fuzziness); |
| } |
| if (fuzzy != null) { |
| float boost = entry.getValue(); |
| if (boost != 1f) { |
| fuzzy = new BoostQuery(fuzzy, boost); |
| } |
| bq.add(fuzzy, BooleanClause.Occur.SHOULD); |
| } |
| } |
| |
| return simplify(bq.build()); |
| } |
| |
| |
| } |
| } |
| |