| /* |
| * 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.Collections; |
| import java.util.List; |
| |
| import org.apache.lucene.queries.function.FunctionQuery; |
| import org.apache.lucene.queries.function.valuesource.QueryValueSource; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.Sort; |
| import org.apache.lucene.search.SortField; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.schema.IndexSchema; |
| import org.apache.solr.schema.SchemaField; |
| |
| public class SortSpecParsing { |
| |
| public static final String DOCID = "_docid_"; |
| public static final String SCORE = "score"; |
| |
| /** |
| * <p> |
| * The form of the sort specification string currently parsed is: |
| * </p> |
| * <pre> |
| * SortSpec ::= SingleSort [, SingleSort]* |
| * SingleSort ::= <fieldname|function> SortDirection |
| * SortDirection ::= top | desc | bottom | asc |
| * </pre> |
| * Examples: |
| * <pre> |
| * score desc #normal sort by score (will return null) |
| * weight bottom #sort by weight ascending |
| * weight desc #sort by weight descending |
| * height desc,weight desc #sort by height descending, and use weight descending to break any ties |
| * height desc,weight asc #sort by height descending, using weight ascending as a tiebreaker |
| * </pre> |
| * @return a SortSpec object populated with the appropriate Sort (which may be null if |
| * default score sort is used) and SchemaFields (where applicable) using |
| * hardcoded default count & offset values. |
| */ |
| public static SortSpec parseSortSpec(String sortSpec, SolrQueryRequest req) { |
| return parseSortSpecImpl(sortSpec, req.getSchema(), req); |
| } |
| |
| /** |
| * <p> |
| * The form of the (function free) sort specification string currently parsed is: |
| * </p> |
| * <pre> |
| * SortSpec ::= SingleSort [, SingleSort]* |
| * SingleSort ::= <fieldname> SortDirection |
| * SortDirection ::= top | desc | bottom | asc |
| * </pre> |
| * Examples: |
| * <pre> |
| * score desc #normal sort by score (will return null) |
| * weight bottom #sort by weight ascending |
| * weight desc #sort by weight descending |
| * height desc,weight desc #sort by height descending, and use weight descending to break any ties |
| * height desc,weight asc #sort by height descending, using weight ascending as a tiebreaker |
| * </pre> |
| * @return a SortSpec object populated with the appropriate Sort (which may be null if |
| * default score sort is used) and SchemaFields (where applicable) using |
| * hardcoded default count & offset values. |
| */ |
| public static SortSpec parseSortSpec(String sortSpec, IndexSchema schema) { |
| return parseSortSpecImpl(sortSpec, schema, null); |
| } |
| |
| private static SortSpec parseSortSpecImpl(String sortSpec, IndexSchema schema, |
| SolrQueryRequest optionalReq) { |
| if (sortSpec == null || sortSpec.length() == 0) return newEmptySortSpec(); |
| |
| List<SortField> sorts = new ArrayList<>(4); |
| List<SchemaField> fields = new ArrayList<>(4); |
| |
| try { |
| |
| StrParser sp = new StrParser(sortSpec); |
| while (sp.pos < sp.end) { |
| sp.eatws(); |
| |
| final int start = sp.pos; |
| |
| // short circuit test for a really simple field name |
| String field = sp.getId(null); |
| Exception qParserException = null; |
| |
| if ((field == null || !Character.isWhitespace(sp.peekChar())) && (optionalReq != null)) { |
| // let's try it as a function instead |
| field = null; |
| String funcStr = sp.val.substring(start); |
| |
| QParser parser = QParser.getParser(funcStr, FunctionQParserPlugin.NAME, optionalReq); |
| Query q = null; |
| try { |
| if (parser instanceof FunctionQParser) { |
| FunctionQParser fparser = (FunctionQParser)parser; |
| fparser.setParseMultipleSources(false); |
| fparser.setParseToEnd(false); |
| |
| q = fparser.getQuery(); |
| |
| if (fparser.localParams != null) { |
| if (fparser.valFollowedParams) { |
| // need to find the end of the function query via the string parser |
| int leftOver = fparser.sp.end - fparser.sp.pos; |
| sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover |
| } else { |
| // the value was via the "v" param in localParams, so we need to find |
| // the end of the local params themselves to pick up where we left off |
| sp.pos = start + fparser.localParamsEnd; |
| } |
| } else { |
| // need to find the end of the function query via the string parser |
| int leftOver = fparser.sp.end - fparser.sp.pos; |
| sp.pos = sp.end - leftOver; // reset our parser to the same amount of leftover |
| } |
| } else { |
| // A QParser that's not for function queries. |
| // It must have been specified via local params. |
| q = parser.getQuery(); |
| |
| assert parser.getLocalParams() != null; |
| sp.pos = start + parser.localParamsEnd; |
| } |
| |
| Boolean top = sp.getSortDirection(); |
| if (null != top) { |
| // we have a Query and a valid direction |
| if (q instanceof FunctionQuery) { |
| sorts.add(((FunctionQuery)q).getValueSource().getSortField(top)); |
| } else { |
| sorts.add((new QueryValueSource(q, 0.0f)).getSortField(top)); |
| } |
| fields.add(null); |
| continue; |
| } |
| } catch (Exception e) { |
| // hang onto this in case the string isn't a full field name either |
| qParserException = e; |
| } |
| } |
| |
| // if we made it here, we either have a "simple" field name, |
| // or there was a problem parsing the string as a complex func/quer |
| |
| if (field == null) { |
| // try again, simple rules for a field name with no whitespace |
| sp.pos = start; |
| field = sp.getSimpleString(); |
| } |
| Boolean top = sp.getSortDirection(); |
| if (null == top) { |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, |
| "Can't determine a Sort Order (asc or desc) in sort spec " + sp); |
| } |
| |
| if (SCORE.equals(field)) { |
| if (top) { |
| sorts.add(SortField.FIELD_SCORE); |
| } else { |
| sorts.add(new SortField(null, SortField.Type.SCORE, true)); |
| } |
| fields.add(null); |
| } else if (DOCID.equals(field)) { |
| sorts.add(new SortField(null, SortField.Type.DOC, top)); |
| fields.add(null); |
| } else { |
| // try to find the field |
| SchemaField sf = schema.getFieldOrNull(field); |
| if (null == sf) { |
| if (null != qParserException) { |
| throw new SolrException |
| (SolrException.ErrorCode.BAD_REQUEST, |
| "sort param could not be parsed as a query, and is not a "+ |
| "field that exists in the index: " + field, |
| qParserException); |
| } |
| throw new SolrException |
| (SolrException.ErrorCode.BAD_REQUEST, |
| "sort param field can't be found: " + field); |
| } |
| sorts.add(sf.getSortField(top)); |
| fields.add(sf); |
| } |
| } |
| |
| } catch (SyntaxError e) { |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "error in sort: " + sortSpec, e); |
| } |
| |
| |
| // normalize a sort on score desc to null |
| if (sorts.size()==1 && sorts.get(0) == SortField.FIELD_SCORE) { |
| return newEmptySortSpec(); |
| } |
| |
| Sort s = new Sort(sorts.toArray(new SortField[sorts.size()])); |
| return new SortSpec(s, fields); |
| } |
| |
| private static SortSpec newEmptySortSpec() { |
| return new SortSpec(null, Collections.<SchemaField>emptyList()); |
| } |
| |
| } |