| /* |
| * 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 java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.lucene.search.Query; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.params.CommonParams; |
| import org.apache.solr.common.params.ModifiableSolrParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.common.util.StrUtils; |
| import org.apache.solr.request.SolrQueryRequest; |
| |
| /** |
| * <b>Note: This API is experimental and may change in non backward-compatible ways in the future</b> |
| * |
| * |
| */ |
| public abstract class QParser { |
| /** @lucene.experimental */ |
| public static final int FLAG_FILTER = 0x01; |
| |
| protected String qstr; |
| protected SolrParams params; |
| protected SolrParams localParams; |
| protected SolrQueryRequest req; |
| protected int recurseCount; |
| |
| /** @lucene.experimental */ |
| protected int flags; |
| |
| protected Query query; |
| |
| protected String stringIncludingLocalParams; // the original query string including any local params |
| protected boolean valFollowedParams; // true if the value "qstr" followed the localParams |
| protected int localParamsEnd; // the position one past where the localParams ended |
| |
| /** |
| * Constructor for the QParser |
| * @param qstr The part of the query string specific to this parser |
| * @param localParams The set of parameters that are specific to this QParser. See http://wiki.apache.org/solr/LocalParams |
| * @param params The rest of the {@link org.apache.solr.common.params.SolrParams} |
| * @param req The original {@link org.apache.solr.request.SolrQueryRequest}. |
| */ |
| public QParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { |
| this.qstr = qstr; |
| this.localParams = localParams; |
| |
| // insert tags into tagmap. |
| // WARNING: the internal representation of tagged objects in the request context is |
| // experimental and subject to change! |
| if (localParams != null) { |
| String tagStr = localParams.get(CommonParams.TAG); |
| if (tagStr != null) { |
| Map<Object,Object> context = req.getContext(); |
| @SuppressWarnings("unchecked") |
| Map<Object,Collection<Object>> tagMap = (Map<Object, Collection<Object>>)req.getContext().get("tags"); |
| if (tagMap == null) { |
| tagMap = new HashMap<>(); |
| context.put("tags", tagMap); |
| } |
| if (tagStr.indexOf(',') >= 0) { |
| List<String> tags = StrUtils.splitSmart(tagStr, ','); |
| for (String tag : tags) { |
| addTag(tagMap, tag, this); |
| } |
| } else { |
| addTag(tagMap, tagStr, this); |
| } |
| } |
| } |
| |
| this.params = params; |
| this.req = req; |
| } |
| |
| /** @lucene.experimental */ |
| public void setFlags(int flags) { |
| this.flags = flags; |
| } |
| |
| /** @lucene.experimental */ |
| public int getFlags() { |
| return flags; |
| } |
| |
| /** @lucene.experimental Query is in the context of a filter, where scores don't matter */ |
| public boolean isFilter() { |
| return (flags & FLAG_FILTER) != 0; |
| } |
| |
| /** @lucene.experimental */ |
| public void setIsFilter(boolean isFilter) { |
| if (isFilter) |
| flags |= FLAG_FILTER; |
| else |
| flags &= ~FLAG_FILTER; |
| } |
| |
| private static void addTag(Map<Object,Collection<Object>> tagMap, Object key, Object val) { |
| Collection<Object> lst = tagMap.get(key); |
| if (lst == null) { |
| lst = new ArrayList<>(2); |
| tagMap.put(key, lst); |
| } |
| lst.add(val); |
| } |
| |
| /** Create and return the <code>Query</code> object represented by <code>qstr</code>. Null MAY be returned to signify |
| * there was no input (e.g. no query string) to parse. |
| * @see #getQuery() |
| **/ |
| public abstract Query parse() throws SyntaxError; |
| |
| public SolrParams getLocalParams() { |
| return localParams; |
| } |
| |
| public void setLocalParams(SolrParams localParams) { |
| this.localParams = localParams; |
| } |
| |
| public SolrParams getParams() { |
| return params; |
| } |
| |
| public void setParams(SolrParams params) { |
| this.params = params; |
| } |
| |
| public SolrQueryRequest getReq() { |
| return req; |
| } |
| |
| public void setReq(SolrQueryRequest req) { |
| this.req = req; |
| } |
| |
| public String getString() { |
| return qstr; |
| } |
| |
| public void setString(String s) { |
| this.qstr = s; |
| } |
| |
| /** |
| * Returns the resulting query from this QParser, calling parse() only the |
| * first time and caching the Query result. <em>A null return is possible!</em> |
| */ |
| //TODO never return null; standardize the semantics |
| public Query getQuery() throws SyntaxError { |
| if (query==null) { |
| query=parse(); |
| |
| if (localParams != null) { |
| String cacheStr = localParams.get(CommonParams.CACHE); |
| if (cacheStr != null) { |
| if (CommonParams.FALSE.equals(cacheStr)) { |
| extendedQuery().setCache(false); |
| } else if (CommonParams.TRUE.equals(cacheStr)) { |
| extendedQuery().setCache(true); |
| } else if ("sep".equals(cacheStr)) { |
| extendedQuery().setCacheSep(true); |
| } |
| } |
| |
| int cost = localParams.getInt(CommonParams.COST, Integer.MIN_VALUE); |
| if (cost != Integer.MIN_VALUE) { |
| extendedQuery().setCost(cost); |
| } |
| } |
| } |
| return query; |
| } |
| |
| // returns an extended query (and sets "query" to a new wrapped query if necessary) |
| private ExtendedQuery extendedQuery() { |
| if (query instanceof ExtendedQuery) { |
| return (ExtendedQuery)query; |
| } else { |
| WrappedQuery wq = new WrappedQuery(query); |
| query = wq; |
| return wq; |
| } |
| } |
| |
| private void checkRecurse() throws SyntaxError { |
| if (recurseCount++ >= 100) { |
| throw new SyntaxError("Infinite Recursion detected parsing query '" + qstr + "'"); |
| } |
| } |
| |
| // TODO: replace with a SolrParams that defaults to checking localParams first? |
| // ideas.. |
| // create params that satisfy field-specific overrides |
| // overrideable syntax $x=foo (set global for limited scope) (invariants & security?) |
| // $x+=foo (append to global for limited scope) |
| |
| /** check both local and global params */ |
| public String getParam(String name) { |
| String val; |
| if (localParams != null) { |
| val = localParams.get(name); |
| if (val != null) return val; |
| } |
| return params.get(name); |
| } |
| |
| /** Create a new QParser for parsing an embedded sub-query */ |
| public QParser subQuery(String q, String defaultType) throws SyntaxError { |
| checkRecurse(); |
| if (defaultType == null && localParams != null) { |
| // if not passed, try and get the defaultType from local params |
| defaultType = localParams.get(QueryParsing.DEFTYPE); |
| } |
| QParser nestedParser = getParser(q, defaultType, getReq()); |
| nestedParser.flags = this.flags; // TODO: this would be better passed in to the constructor... change to a ParserContext object? |
| nestedParser.recurseCount = recurseCount; |
| recurseCount--; |
| return nestedParser; |
| } |
| |
| /** |
| * @param useGlobalParams look up sort, start, rows in global params if not in local params |
| * @return the sort specification |
| */ |
| public SortSpec getSortSpec(boolean useGlobalParams) throws SyntaxError { |
| getQuery(); // ensure query is parsed first |
| |
| String sortStr = null; |
| Integer start = null; |
| Integer rows = null; |
| |
| if (localParams != null) { |
| sortStr = localParams.get(CommonParams.SORT); |
| start = localParams.getInt(CommonParams.START); |
| rows = localParams.getInt(CommonParams.ROWS); |
| |
| // if any of these parameters are present, don't go back to the global params |
| if (sortStr != null || start != null || rows != null) { |
| useGlobalParams = false; |
| } |
| } |
| |
| if (useGlobalParams) { |
| if (sortStr ==null) { |
| sortStr = params.get(CommonParams.SORT); |
| } |
| if (start == null) { |
| start = params.getInt(CommonParams.START); |
| } |
| if (rows == null) { |
| rows = params.getInt(CommonParams.ROWS); |
| } |
| } |
| |
| start = start != null ? start : CommonParams.START_DEFAULT; |
| rows = rows != null ? rows : CommonParams.ROWS_DEFAULT; |
| |
| SortSpec sort = SortSpecParsing.parseSortSpec(sortStr, req); |
| |
| sort.setOffset(start); |
| sort.setCount(rows); |
| return sort; |
| } |
| |
| public String[] getDefaultHighlightFields() { |
| return new String[]{}; |
| } |
| |
| public Query getHighlightQuery() throws SyntaxError { |
| Query query = getQuery(); |
| return query instanceof WrappedQuery ? ((WrappedQuery)query).getWrappedQuery() : query; |
| } |
| |
| public void addDebugInfo(NamedList<Object> debugInfo) { |
| debugInfo.add("QParser", this.getClass().getSimpleName()); |
| } |
| |
| /** |
| * Create a {@link QParser} to parse <code>qstr</code>, |
| * using the "lucene" (QParserPlugin.DEFAULT_QTYPE) query parser. |
| * The query parser may be overridden by local-params in the query |
| * string itself. For example if |
| * qstr=<code>{!prefix f=myfield}foo</code> |
| * then the prefix query parser will be used. |
| */ |
| public static QParser getParser(String qstr, SolrQueryRequest req) throws SyntaxError { |
| return getParser(qstr, QParserPlugin.DEFAULT_QTYPE, req); |
| } |
| |
| /** |
| * Create a {@link QParser} to parse <code>qstr</code> using the <code>defaultParser</code>. |
| * Note that local-params is only parsed when the defaultParser is "lucene" or "func". |
| */ |
| public static QParser getParser(String qstr, String defaultParser, SolrQueryRequest req) throws SyntaxError { |
| boolean allowLocalParams = defaultParser == null || defaultParser.equals(QParserPlugin.DEFAULT_QTYPE) |
| || defaultParser.equals(FunctionQParserPlugin.NAME); |
| return getParser(qstr, defaultParser, allowLocalParams, req); |
| } |
| |
| /** |
| * Expert: Create a {@link QParser} to parse {@code qstr} using the {@code parserName} parser, while allowing a |
| * toggle for whether local-params may be parsed. |
| * The query parser may be overridden by local parameters in the query string itself |
| * (assuming {@code allowLocalParams}. |
| * For example if parserName=<code>dismax</code> and qstr=<code>foo</code>, |
| * then the dismax query parser will be used to parse and construct the query object. |
| * However if qstr=<code>{!prefix f=myfield}foo</code> then the prefix query parser will be used. |
| * |
| * @param allowLocalParams Whether this query parser should parse local-params syntax. |
| * Note that the "lucene" query parser natively parses local-params regardless. |
| * @lucene.internal |
| */ |
| public static QParser getParser(String qstr, String parserName, boolean allowLocalParams, SolrQueryRequest req) throws SyntaxError { |
| // SolrParams localParams = QueryParsing.getLocalParams(qstr, req.getParams()); |
| if (parserName == null) { |
| parserName = QParserPlugin.DEFAULT_QTYPE;//"lucene" |
| } |
| String stringIncludingLocalParams = qstr; |
| ModifiableSolrParams localParams = null; |
| SolrParams globalParams = req.getParams(); |
| boolean valFollowedParams = true; |
| int localParamsEnd = -1; |
| |
| if (allowLocalParams && qstr != null && qstr.startsWith(QueryParsing.LOCALPARAM_START)) { |
| localParams = new ModifiableSolrParams(); |
| localParamsEnd = QueryParsing.parseLocalParams(qstr, 0, localParams, globalParams); |
| |
| String val = localParams.get(QueryParsing.V); |
| if (val != null) { |
| // val was directly specified in localParams via v=<something> or v=$arg |
| valFollowedParams = false; |
| //TODO if remainder of query string after '}' is non-empty, then what? Throw error? Fall back to lucene QParser? |
| } else { |
| // use the remainder of the string as the value |
| valFollowedParams = true; |
| val = qstr.substring(localParamsEnd); |
| localParams.set(QueryParsing.V, val); |
| } |
| |
| parserName = localParams.get(QueryParsing.TYPE,parserName); |
| qstr = localParams.get(QueryParsing.V); |
| } |
| |
| QParserPlugin qplug = req.getCore().getQueryPlugin(parserName); |
| if (qplug == null) { |
| // there should a way to include parameter for which parsing failed |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, |
| "invalid query parser '" + parserName + (stringIncludingLocalParams == null? |
| "'": "' for query '" + stringIncludingLocalParams + "'")); |
| } |
| QParser parser = qplug.createParser(qstr, localParams, req.getParams(), req); |
| |
| parser.stringIncludingLocalParams = stringIncludingLocalParams; |
| parser.valFollowedParams = valFollowedParams; |
| parser.localParamsEnd = localParamsEnd; |
| return parser; |
| } |
| |
| } |