blob: 22ad4213f0b64ac95e15b375ea14a2e9419e1f93 [file] [log] [blame]
/*
* 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.index.Term;
import org.apache.solr.legacy.LegacyNumericRangeQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CharsRefBuilder;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.parser.QueryParser;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Collection of static utilities useful for query parsing.
*
*
*/
public class QueryParsing {
public static final String OP = "q.op"; // the SolrParam used to override the QueryParser "default operator"
public static final String V = "v"; // value of this parameter
public static final String F = "f"; // field that a query or command pertains to
public static final String TYPE = "type";// parser for this query or command
public static final String DEFTYPE = "defType"; // default parser for any direct subqueries
public static final String SPLIT_ON_WHITESPACE = "sow"; // Whether to split on whitespace prior to analysis
public static final String LOCALPARAM_START = "{!";
public static final char LOCALPARAM_END = '}';
// true if the value was specified by the "v" param (i.e. v=myval, or v=$param)
public static final String VAL_EXPLICIT = "__VAL_EXPLICIT__";
/**
* @param txt Text to parse
* @param start Index into text for start of parsing
* @param target Object to inject with parsed settings
* @param params Additional existing parameters
*/
public static int parseLocalParams(String txt, int start, ModifiableSolrParams target, SolrParams params) throws SyntaxError {
return parseLocalParams(txt, start, target, params, LOCALPARAM_START, LOCALPARAM_END);
}
/**
* @param txt Text to parse
* @param start Index into text for start of parsing
* @param target Object to inject with parsed settings
* @param params Additional existing parameters
* @param startString String that indicates the start of a localParams section
* @param endChar Character that indicates the end of a localParams section
*/
public static int parseLocalParams(String txt, int start, ModifiableSolrParams target, SolrParams params, String startString, char endChar) throws SyntaxError {
int off = start;
if (!txt.startsWith(startString, off)) return start;
StrParser p = new StrParser(txt, start, txt.length());
p.pos += startString.length(); // skip over "{!"
for (; ;) {
/*
if (p.pos>=txt.length()) {
throw new SyntaxError("Missing '}' parsing local params '" + txt + '"');
}
*/
char ch = p.peek();
if (ch == endChar) {
return p.pos + 1;
}
String id = p.getId();
if (id.length() == 0) {
throw new SyntaxError("Expected ending character '" + endChar + "' parsing local params '" + txt + '"');
}
String[] val = new String[1];
ch = p.peek();
if (ch != '=') {
// single word... treat {!func} as type=func for easy lookup
val[0] = id;
id = TYPE;
} else {
// saw equals, so read value
p.pos++;
ch = p.peek();
boolean deref = false;
if (ch == '$') {
p.pos++;
ch = p.peek();
deref = true; // dereference whatever value is read by treating it as a variable name
}
if (ch == '\"' || ch == '\'') {
val[0] = p.getQuotedString();
} else {
// read unquoted literal ended by whitespace or endChar (normally '}')
// there is no escaping.
int valStart = p.pos;
for (; ;) {
if (p.pos >= p.end) {
throw new SyntaxError("Missing end to unquoted value starting at " + valStart + " str='" + txt + "'");
}
char c = p.val.charAt(p.pos);
if (c == endChar || Character.isWhitespace(c)) {
val[0] = p.val.substring(valStart, p.pos);
break;
}
p.pos++;
}
}
if (deref) { // dereference parameter
if (params != null) {
val = params.getParams(val[0]);
}
}
}
if (target != null) target.add(id, val);
}
}
/**
* "foo" returns null
* "{!prefix f=myfield}yes" returns type="prefix",f="myfield",v="yes"
* "{!prefix f=myfield v=$p}" returns type="prefix",f="myfield",v=params.get("p")
*/
public static SolrParams getLocalParams(String txt, SolrParams params) throws SyntaxError {
if (txt == null || !txt.startsWith(LOCALPARAM_START)) {
return null;
}
ModifiableSolrParams localParams = new ModifiableSolrParams();
int start = QueryParsing.parseLocalParams(txt, 0, localParams, params);
String val = localParams.get(V);
if (val == null) {
val = txt.substring(start);
localParams.set(V, val);
} else {
// localParams.put(VAL_EXPLICIT, "true");
}
return localParams;
}
///////////////////////////
///////////////////////////
///////////////////////////
static FieldType writeFieldName(String name, IndexSchema schema, Appendable out, int flags) throws IOException {
FieldType ft = null;
ft = schema.getFieldTypeNoEx(name);
out.append(name);
if (ft == null) {
out.append("(UNKNOWN FIELD ").append(name).append(String.valueOf(')'));
}
out.append(':');
return ft;
}
static void writeFieldVal(String val, FieldType ft, Appendable out, int flags) throws IOException {
if (ft != null) {
try {
out.append(ft.indexedToReadable(val));
} catch (Exception e) {
out.append("EXCEPTION(val=");
out.append(val);
out.append(")");
}
} else {
out.append(val);
}
}
static void writeFieldVal(BytesRef val, FieldType ft, Appendable out, int flags) throws IOException {
if (ft != null) {
try {
CharsRefBuilder readable = new CharsRefBuilder();
ft.indexedToReadable(val, readable);
out.append(readable.get());
} catch (Exception e) {
out.append("EXCEPTION(val=");
out.append(val.utf8ToString());
out.append(")");
}
} else {
out.append(val.utf8ToString());
}
}
private static int FLAG_BOOSTED=0x01;
private static int FLAG_IS_CLAUSE=0x02;
/**
* @see #toString(Query,IndexSchema)
*/
public static void toString(Query query, IndexSchema schema, Appendable out, int flags) throws IOException {
int subflag = flags & ~(FLAG_BOOSTED|FLAG_IS_CLAUSE); // clear the boosted / is clause flags for recursion
if (query instanceof TermQuery) {
TermQuery q = (TermQuery) query;
Term t = q.getTerm();
FieldType ft = writeFieldName(t.field(), schema, out, flags);
writeFieldVal(t.bytes(), ft, out, flags);
} else if (query instanceof TermRangeQuery) {
TermRangeQuery q = (TermRangeQuery) query;
String fname = q.getField();
FieldType ft = writeFieldName(fname, schema, out, flags);
out.append(q.includesLower() ? '[' : '{');
BytesRef lt = q.getLowerTerm();
BytesRef ut = q.getUpperTerm();
if (lt == null) {
out.append('*');
} else {
writeFieldVal(lt, ft, out, flags);
}
out.append(" TO ");
if (ut == null) {
out.append('*');
} else {
writeFieldVal(ut, ft, out, flags);
}
out.append(q.includesUpper() ? ']' : '}');
} else if (query instanceof LegacyNumericRangeQuery) {
@SuppressWarnings({"rawtypes"})
LegacyNumericRangeQuery q = (LegacyNumericRangeQuery) query;
String fname = q.getField();
FieldType ft = writeFieldName(fname, schema, out, flags);
out.append(q.includesMin() ? '[' : '{');
Number lt = q.getMin();
Number ut = q.getMax();
if (lt == null) {
out.append('*');
} else {
out.append(lt.toString());
}
out.append(" TO ");
if (ut == null) {
out.append('*');
} else {
out.append(ut.toString());
}
out.append(q.includesMax() ? ']' : '}');
} else if (query instanceof BooleanQuery) {
BooleanQuery q = (BooleanQuery) query;
boolean needParens = false;
if (q.getMinimumNumberShouldMatch() != 0 || (flags & (FLAG_IS_CLAUSE | FLAG_BOOSTED)) != 0 ) {
needParens = true;
}
if (needParens) {
out.append('(');
}
boolean first = true;
for (BooleanClause c : q.clauses()) {
if (!first) {
out.append(' ');
} else {
first = false;
}
if (c.isProhibited()) {
out.append('-');
} else if (c.isRequired()) {
out.append('+');
}
Query subQuery = c.getQuery();
toString(subQuery, schema, out, subflag | FLAG_IS_CLAUSE);
}
if (needParens) {
out.append(')');
}
if (q.getMinimumNumberShouldMatch() > 0) {
out.append('~');
out.append(Integer.toString(q.getMinimumNumberShouldMatch()));
}
} else if (query instanceof PrefixQuery) {
PrefixQuery q = (PrefixQuery) query;
Term prefix = q.getPrefix();
FieldType ft = writeFieldName(prefix.field(), schema, out, flags);
out.append(prefix.text());
out.append('*');
} else if (query instanceof WildcardQuery) {
out.append(query.toString());
} else if (query instanceof FuzzyQuery) {
out.append(query.toString());
} else if (query instanceof ConstantScoreQuery) {
out.append(query.toString());
} else if (query instanceof WrappedQuery) {
WrappedQuery q = (WrappedQuery)query;
out.append(q.getOptions());
toString(q.getWrappedQuery(), schema, out, subflag);
} else if (query instanceof BoostQuery) {
BoostQuery q = (BoostQuery)query;
toString(q.getQuery(), schema, out, subflag | FLAG_BOOSTED);
out.append('^');
out.append(Float.toString(q.getBoost()));
}
else {
out.append(query.getClass().getSimpleName()).append('(').append(query.toString()).append(')');
}
}
/**
* Formats a Query for debugging, using the IndexSchema to make
* complex field types readable.
* <p>
* The benefit of using this method instead of calling
* <code>Query.toString</code> directly is that it knows about the data
* types of each field, so any field which is encoded in a particularly
* complex way is still readable. The downside is that it only knows
* about built in Query types, and will not be able to format custom
* Query classes.
* </p>
*/
public static String toString(Query query, IndexSchema schema) {
try {
StringBuilder sb = new StringBuilder();
toString(query, schema, sb, 0);
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds a list of String which are stringified versions of a list of Queries
*/
public static List<String> toString(List<Query> queries, IndexSchema schema) {
List<String> out = new ArrayList<>(queries.size());
for (Query q : queries) {
out.add(QueryParsing.toString(q, schema));
}
return out;
}
/**
* Parses default operator string into Operator object
* @param operator the string from request
* @return Operator.AND if string equals "AND", else return Operator.OR (default)
*/
public static QueryParser.Operator parseOP(String operator) {
return "and".equalsIgnoreCase(operator) ? QueryParser.Operator.AND : QueryParser.Operator.OR;
}
}