All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.search.FunctionQParser Maven / Gradle / Ivy

/**
 * 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.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.function.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class FunctionQParser extends QParser {

  protected QueryParsing.StrParser sp;

  public FunctionQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
    super(qstr, localParams, params, req);
  }

  public Query parse() throws ParseException {
    sp = new QueryParsing.StrParser(getString());
    ValueSource vs = parseValueSource();

    /***  boost promoted to top-level query type to avoid this hack 

    // HACK - if this is a boosted query wrapped in a value-source, return
    // that boosted query instead of a FunctionQuery
    if (vs instanceof QueryValueSource) {
      Query q = ((QueryValueSource)vs).getQuery();
      if (q instanceof BoostedQuery) return q;
    }
    ***/

    return new FunctionQuery(vs);
  }

  /**
   * Are there more arguments in the argument list being parsed?
   * 
   * @return whether more args exist
   * @throws ParseException
   */
  public boolean hasMoreArguments() throws ParseException {
    int ch = sp.peek();
    /* determine whether the function is ending with a paren or end of str */
    return (! (ch == 0 || ch == ')') );
  }
  
  /**
   * TODO: Doc
   * 
   * @throws ParseException
   */
  public String parseId() throws ParseException {
    String value = sp.getId();
    consumeArgumentDelimiter();
    return value;
  }
  
  /**
   * Parse a float.
   * 
   * @return Float
   * @throws ParseException
   */
  public Float parseFloat() throws ParseException {
    float value = sp.getFloat();
    consumeArgumentDelimiter();
    return value;
  }

  public String parseArg() throws ParseException {
    sp.eatws();
    char ch = sp.peek();
    String val = null;
    switch (ch) {
      case ')': return null;
      case '$':
        sp.pos++;
        String param = sp.getId();
        val = getParam(param);
        break;
      case '\'':
      case '"':
        val = sp.getQuotedString();
        break;
      default:
        // read unquoted literal ended by whitespace ',' or ')'
        // there is no escaping.
        int valStart = sp.pos;
        for (;;) {
          if (sp.pos >= sp.end) {
            throw new ParseException("Missing end to unquoted value starting at " + valStart + " str='" + sp.val +"'");
          }
          char c = sp.val.charAt(sp.pos);
          if (c==')' || c==',' || Character.isWhitespace(c)) {
            val = sp.val.substring(valStart, sp.pos);
            break;
          }
          sp.pos++;
        }
    }

    sp.eatws();
    consumeArgumentDelimiter();
    return val;
  }

  
  /**
   * Parse a list of ValueSource.  Must be the final set of arguments
   * to a ValueSource.
   * 
   * @return List
   * @throws ParseException
   */
  public List parseValueSourceList() throws ParseException {
    List sources = new ArrayList(3);
    for (;;) {
      sources.add(parseValueSource(false));
      if (! consumeArgumentDelimiter()) break;
    }
    return sources;
  }

  /**
   * Parse an individual ValueSource.
   * 
   * @throws ParseException
   */
  public ValueSource parseValueSource() throws ParseException {
    /* consume the delimiter afterward for an external call to parseValueSource */
    return parseValueSource(true);
  }
  
  /**
   * TODO: Doc
   * 
   * @throws ParseException
   */
  public Query parseNestedQuery() throws ParseException {
    Query nestedQuery;
    
    if (sp.opt("$")) {
      String param = sp.getId();
      String qstr = getParam(param);
      qstr = qstr==null ? "" : qstr;
      nestedQuery = subQuery(qstr, null).getQuery();
    }
    else {
      int start = sp.pos;
      String v = sp.val;
  
      String qs = v;
      HashMap nestedLocalParams = new HashMap();
      int end = QueryParsing.parseLocalParams(qs, start, nestedLocalParams, getParams());
  
      QParser sub;
  
      if (end>start) {
        if (nestedLocalParams.get(QueryParsing.V) != null) {
          // value specified directly in local params... so the end of the
          // query should be the end of the local params.
          sub = subQuery(qs.substring(start, end), null);
        } else {
          // value here is *after* the local params... ask the parser.
          sub = subQuery(qs, null);
          // int subEnd = sub.findEnd(')');
          // TODO.. implement functions to find the end of a nested query
          throw new ParseException("Nested local params must have value in v parameter.  got '" + qs + "'");
        }
      } else {
        throw new ParseException("Nested function query must use $param or {!v=value} forms. got '" + qs + "'");
      }
  
      sp.pos += end-start;  // advance past nested query
      nestedQuery = sub.getQuery();
    }
    consumeArgumentDelimiter();
    
    return nestedQuery;
  }

  /**
   * Parse an individual value source.
   * 
   * @param doConsumeDelimiter whether to consume a delimiter following the ValueSource  
   * @throws ParseException
   */
  protected ValueSource parseValueSource(boolean doConsumeDelimiter) throws ParseException {
    ValueSource valueSource;
    
    int ch = sp.peek();
    if (ch>='0' && ch<='9'  || ch=='.' || ch=='+' || ch=='-') {
      valueSource = new ConstValueSource(sp.getFloat());
    }
    else {
      String id = sp.getId();
      if (sp.opt("(")) {
        // a function... look it up.
        ValueSourceParser argParser = req.getCore().getValueSourceParser(id);
        if (argParser==null) {
          throw new ParseException("Unknown function " + id + " in FunctionQuery(" + sp + ")");
        }
        valueSource = argParser.parse(this);
        sp.expect(")");
      }
      else {
        SchemaField f = req.getSchema().getField(id);
        valueSource = f.getType().getValueSource(f, this);
      }
    }
    
    if (doConsumeDelimiter)
      consumeArgumentDelimiter();
    
    return valueSource;
  }

  /**
   * Consume an argument delimiter (a comma) from the token stream.
   * Only consumes if more arguments should exist (no ending parens or end of string).
   * 
   * @return whether a delimiter was consumed
   * @throws ParseException
   */
  protected boolean consumeArgumentDelimiter() throws ParseException {
    /* if a list of args is ending, don't expect the comma */
    if (hasMoreArguments()) {
      sp.expect(",");
      return true;
    }
   
    return false;
  }
    

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy