This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit c13a35b9a99bf2999927f0f2ab9dfcfddc25dea1
Author: David Smiley <[email protected]>
AuthorDate: Sat May 24 10:35:52 2025 -0400

    Refactoring: add QParser.parseAsValueSource (#3343)
    
    It's common to need to parse a ValueSource from a QParser.  Callers were 
typically doing the same thing including unfortunate instanceof checks.
    
    * Disable FunctionQParser.parseMultipleSources by default and deprecate it.
    Test the exception.  Doesn't break any existing tests.  This aspect is very 
exotic and could go away once we remove some old things relying on it.
    
    (cherry picked from commit 759df4aacd76226984532fd6bbf8d594680ba361)
---
 .../apache/solr/handler/component/StatsField.java  | 21 +-----
 .../org/apache/solr/search/BoostQParserPlugin.java |  9 +--
 .../apache/solr/search/ExtendedDismaxQParser.java  | 13 ++--
 .../org/apache/solr/search/FunctionQParser.java    | 60 ++++++++++++-----
 .../solr/search/FunctionRangeQParserPlugin.java    |  9 +--
 .../src/java/org/apache/solr/search/Grouping.java  | 24 ++-----
 .../src/java/org/apache/solr/search/QParser.java   | 22 ++++++
 .../org/apache/solr/search/SolrReturnFields.java   | 43 +-----------
 .../org/apache/solr/search/SortSpecParsing.java    | 78 +++++++++++-----------
 .../org/apache/solr/search/ValueSourceParser.java  |  7 +-
 .../function/distance/DistanceFunctionTest.java    | 39 ++++++-----
 11 files changed, 145 insertions(+), 180 deletions(-)

diff --git 
a/solr/core/src/java/org/apache/solr/handler/component/StatsField.java 
b/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
index e74e00f015b..3f7df67da8d 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsField.java
@@ -30,10 +30,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
 import org.apache.lucene.queries.function.valuesource.FieldCacheSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -276,7 +274,7 @@ public class StatsField {
             qplug.createParser(localParams.get(QueryParsing.V), localParams, 
params, rb.req);
 
         // figure out what type of query we are dealing, get the most direct 
ValueSource
-        vs = extractValueSource(qp.parse());
+        vs = qp.parseAsValueSource();
 
         // if this ValueSource directly corresponds to a SchemaField, act as if
         // we were asked to compute stats on it directly
@@ -338,23 +336,6 @@ public class StatsField {
         : "exactly one of valueSource & schemaField must be null";
   }
 
-  /**
-   * Inspects a {@link Query} to see if it directly maps to a {@link 
ValueSource}, and if so returns
-   * it -- otherwise wraps it as needed.
-   *
-   * @param q Query whose scores we have been asked to compute stats of
-   * @returns a ValueSource to use for computing the stats
-   */
-  private static ValueSource extractValueSource(Query q) {
-    return (q instanceof FunctionQuery)
-        ?
-        // Common case: we're wrapping a func, so we can directly pull out 
ValueSource
-        ((FunctionQuery) q).getValueSource()
-        :
-        // asked to compute stats over a query, wrap it up as a ValueSource
-        new QueryValueSource(q, 0.0F);
-  }
-
   /**
    * Inspects a {@link ValueSource} to see if it directly maps to a {@link 
SchemaField}, and if so
    * returns it.
diff --git a/solr/core/src/java/org/apache/solr/search/BoostQParserPlugin.java 
b/solr/core/src/java/org/apache/solr/search/BoostQParserPlugin.java
index 96c9d566221..ef1d457b2f4 100644
--- a/solr/core/src/java/org/apache/solr/search/BoostQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/BoostQParserPlugin.java
@@ -16,10 +16,8 @@
  */
 package org.apache.solr.search;
 
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.FunctionScoreQuery;
 import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
@@ -56,12 +54,7 @@ public class BoostQParserPlugin extends QParserPlugin {
         Query q = baseParser.getQuery();
 
         if (b == null) return q;
-        Query bq = subQuery(b, FunctionQParserPlugin.NAME).getQuery();
-        if (bq instanceof FunctionQuery) {
-          vs = ((FunctionQuery) bq).getValueSource();
-        } else {
-          vs = new QueryValueSource(bq, 0.0f);
-        }
+        vs = subQuery(b, FunctionQParserPlugin.NAME).parseAsValueSource();
         return FunctionScoreQuery.boostByValue(q, vs.asDoubleValuesSource());
       }
 
diff --git 
a/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java 
b/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
index 3d593669ed5..139d0d53eff 100644
--- a/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
@@ -29,7 +29,6 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenFilterFactory;
 import org.apache.lucene.analysis.core.StopFilterFactory;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.FunctionScoreQuery;
 import org.apache.lucene.queries.function.ValueSource;
 import org.apache.lucene.queries.function.valuesource.ProductFloatFunction;
@@ -532,13 +531,11 @@ public class ExtendedDismaxQParser extends QParser {
     List<ValueSource> boosts = new ArrayList<>();
     if (config.hasMultiplicativeBoosts()) {
       for (String boostStr : config.multBoosts) {
-        if (boostStr == null || boostStr.length() == 0) continue;
-        Query boost = subQuery(boostStr, 
FunctionQParserPlugin.NAME).getQuery();
-        ValueSource vs;
-        if (boost instanceof FunctionQuery) {
-          vs = ((FunctionQuery) boost).getValueSource();
-        } else {
-          vs = new QueryValueSource(boost, 1.0f);
+        if (boostStr == null || boostStr.isEmpty()) continue;
+        ValueSource vs = subQuery(boostStr, 
FunctionQParserPlugin.NAME).parseAsValueSource();
+        // the default score should be 1, not 0
+        if (vs instanceof QueryValueSource && ((QueryValueSource) 
vs).getDefaultValue() == 0.0f) {
+          vs = new QueryValueSource(((QueryValueSource) vs).getQuery(), 1.0f);
         }
         boosts.add(vs);
       }
diff --git a/solr/core/src/java/org/apache/solr/search/FunctionQParser.java 
b/solr/core/src/java/org/apache/solr/search/FunctionQParser.java
index 2a3dcd9dc68..a88dfe52316 100644
--- a/solr/core/src/java/org/apache/solr/search/FunctionQParser.java
+++ b/solr/core/src/java/org/apache/solr/search/FunctionQParser.java
@@ -26,7 +26,6 @@ import 
org.apache.lucene.queries.function.valuesource.ConstKnnFloatValueSource;
 import org.apache.lucene.queries.function.valuesource.ConstValueSource;
 import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource;
 import org.apache.lucene.queries.function.valuesource.LiteralValueSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.queries.function.valuesource.VectorValueSource;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -36,6 +35,13 @@ import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.facet.AggValueSource;
 import org.apache.solr.search.function.FieldNameValueSource;
 
+/**
+ * Does "function query" parsing of function-call like strings, producing a 
{@link ValueSource}. As
+ * this implements {@link QParser}, we produce a {@link Query}, but more often 
{@link
+ * #parseAsValueSource()} is called instead.
+ *
+ * @see ValueSourceParser
+ */
 public class FunctionQParser extends QParser {
 
   public static final int FLAG_CONSUME_DELIMITER = 0x01; // consume delimiter 
after parsing arg
@@ -53,14 +59,27 @@ public class FunctionQParser extends QParser {
    */
   public StrParser sp;
 
-  boolean parseMultipleSources = true;
-  boolean parseToEnd = true;
+  @Deprecated private boolean parseMultipleSources = false;
+  private boolean parseToEnd = true;
 
   public FunctionQParser(
       String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest 
req) {
     super(qstr, localParams, params, req);
     setFlags(FLAG_DEFAULT);
     setString(qstr);
+    if (localParams != null && localParams.getPrimitiveBool("multiple")) {
+      setParseMultipleSources(true);
+    }
+  }
+
+  /**
+   * Parses the string to a {@link ValueSource}. Typically, this is not used, 
however.
+   *
+   * @see QParser#parseAsValueSource()
+   */
+  public static ValueSource parseAsValueSource(String string, SolrQueryRequest 
request)
+      throws SyntaxError {
+    return getParser(string, FunctionQParserPlugin.NAME, 
request).parseAsValueSource();
   }
 
   @Override
@@ -71,11 +90,18 @@ public class FunctionQParser extends QParser {
     }
   }
 
+  @Deprecated
   public void setParseMultipleSources(boolean parseMultipleSources) {
     this.parseMultipleSources = parseMultipleSources;
   }
 
-  /** parse multiple comma separated value sources */
+  /**
+   * Parse multiple comma separated value sources encapsulated into a {@link 
VectorValueSource} when
+   * {@link #getQuery()} or {@link #parseAsValueSource()} is called.
+   *
+   * @deprecated this is only needed for an unusual use-case and seems hard to 
support
+   */
+  @Deprecated
   public boolean getParseMultipleSources() {
     return parseMultipleSources;
   }
@@ -86,11 +112,23 @@ public class FunctionQParser extends QParser {
 
   /** throw exception if there is extra stuff at the end of the parsed 
valuesource(s). */
   public boolean getParseToEnd() {
-    return parseMultipleSources;
+    return parseToEnd;
   }
 
   @Override
   public Query parse() throws SyntaxError {
+    return new FunctionQuery(parseAsValueSource());
+  }
+
+  /**
+   * Parses as a ValueSource, not a Query. <em>NOT</em> intended to be called 
by {@link
+   * ValueSourceParser#parse(FunctionQParser)}; it's intended for general code 
that has a {@link
+   * QParser} but actually wants to parse a ValueSource.
+   *
+   * @return A {@link VectorValueSource} for multiple VS, otherwise just the 
single VS.
+   */
+  @Override
+  public ValueSource parseAsValueSource() throws SyntaxError {
     ValueSource vs = null;
     List<ValueSource> lst = null;
 
@@ -126,8 +164,7 @@ public class FunctionQParser extends QParser {
     if (lst != null) {
       vs = new VectorValueSource(lst);
     }
-
-    return new FunctionQuery(vs);
+    return vs;
   }
 
   /**
@@ -429,14 +466,7 @@ public class FunctionQParser extends QParser {
           subFunc.setParseMultipleSources(true);
           subFunc.setFlags(flags);
         }
-        Query subQuery = subParser.getQuery();
-        if (subQuery == null) {
-          valueSource = new ConstValueSource(0.0f);
-        } else if (subQuery instanceof FunctionQuery) {
-          valueSource = ((FunctionQuery) subQuery).getValueSource();
-        } else {
-          valueSource = new QueryValueSource(subQuery, 0.0f);
-        }
+        valueSource = subParser.parseAsValueSource();
       }
 
       /*
diff --git 
a/solr/core/src/java/org/apache/solr/search/FunctionRangeQParserPlugin.java 
b/solr/core/src/java/org/apache/solr/search/FunctionRangeQParserPlugin.java
index ad7fee8a556..0c15b2d33d7 100644
--- a/solr/core/src/java/org/apache/solr/search/FunctionRangeQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/FunctionRangeQParserPlugin.java
@@ -16,9 +16,7 @@
  */
 package org.apache.solr.search;
 
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.request.SolrQueryRequest;
@@ -50,12 +48,7 @@ public class FunctionRangeQParserPlugin extends 
QParserPlugin {
         QParser subParser = subQuery(funcStr, FunctionQParserPlugin.NAME);
         subParser.setIsFilter(
             false); // the range can be based on the relevancy score of 
embedded queries.
-        Query funcQ = subParser.getQuery();
-        if (funcQ instanceof FunctionQuery) {
-          vs = ((FunctionQuery) funcQ).getValueSource();
-        } else {
-          vs = new QueryValueSource(funcQ, 0.0f);
-        }
+        vs = subParser.parseAsValueSource();
 
         String l = localParams.get("l");
         String u = localParams.get("u");
diff --git a/solr/core/src/java/org/apache/solr/search/Grouping.java 
b/solr/core/src/java/org/apache/solr/search/Grouping.java
index 606be2874fe..f6ad2931d9e 100644
--- a/solr/core/src/java/org/apache/solr/search/Grouping.java
+++ b/solr/core/src/java/org/apache/solr/search/Grouping.java
@@ -28,9 +28,7 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.lucene.index.ExitableDirectoryReader;
 import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.search.CachingCollector;
 import org.apache.lucene.search.Collector;
 import org.apache.lucene.search.MatchNoDocsQuery;
@@ -179,24 +177,16 @@ public class Grouping {
   }
 
   public void addFunctionCommand(String groupByStr, SolrQueryRequest request) 
throws SyntaxError {
-    QParser parser = QParser.getParser(groupByStr, FunctionQParserPlugin.NAME, 
request);
-    Query q = parser.getQuery();
+    ValueSource valueSource = FunctionQParser.parseAsValueSource(groupByStr, 
request);
     final Grouping.Command<?> gc;
-    if (q instanceof FunctionQuery) {
-      ValueSource valueSource = ((FunctionQuery) q).getValueSource();
-      if (valueSource instanceof StrFieldSource) {
-        String field = ((StrFieldSource) valueSource).getField();
-        CommandField commandField = new CommandField();
-        commandField.groupBy = field;
-        gc = commandField;
-      } else {
-        CommandFunc commandFunc = new CommandFunc();
-        commandFunc.groupBy = valueSource;
-        gc = commandFunc;
-      }
+    if (valueSource instanceof StrFieldSource) {
+      String field = ((StrFieldSource) valueSource).getField();
+      CommandField commandField = new CommandField();
+      commandField.groupBy = field;
+      gc = commandField;
     } else {
       CommandFunc commandFunc = new CommandFunc();
-      commandFunc.groupBy = new QueryValueSource(q, 0.0f);
+      commandFunc.groupBy = valueSource;
       gc = commandFunc;
     }
     gc.withinGroupSort = withinGroupSort;
diff --git a/solr/core/src/java/org/apache/solr/search/QParser.java 
b/solr/core/src/java/org/apache/solr/search/QParser.java
index e6723cff3bf..1a292186542 100644
--- a/solr/core/src/java/org/apache/solr/search/QParser.java
+++ b/solr/core/src/java/org/apache/solr/search/QParser.java
@@ -21,6 +21,10 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.lucene.queries.function.FunctionQuery;
+import org.apache.lucene.queries.function.FunctionScoreQuery;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.queries.function.valuesource.QueryValueSource;
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
@@ -30,6 +34,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.ValueSourceParser.LongConstValueSource;
 
 /**
  * <b>Note: This API is experimental and may change in non backward-compatible 
ways in the
@@ -324,6 +329,23 @@ public abstract class QParser {
     return getReq().getCore().getSolrConfig().prefixQueryMinPrefixLength;
   }
 
+  /**
+   * Parse the string into a {@link ValueSource} <em>instead of a {@link 
Query}</em>. Solr calls
+   * this in most places that "function queries" go. Overridden by {@link 
FunctionQParser}.
+   */
+  public ValueSource parseAsValueSource() throws SyntaxError {
+    Query q = getQuery();
+    if (q == null) {
+      return new LongConstValueSource(0);
+    } else if (q instanceof FunctionQuery) {
+      return ((FunctionQuery) q).getValueSource();
+    } else if (q instanceof FunctionScoreQuery) {
+      return ValueSource.fromDoubleValuesSource(((FunctionScoreQuery) 
q).getSource());
+    } else {
+      return new QueryValueSource(q, 0.0f);
+    }
+  }
+
   /**
    * 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
diff --git a/solr/core/src/java/org/apache/solr/search/SolrReturnFields.java 
b/solr/core/src/java/org/apache/solr/search/SolrReturnFields.java
index f0b9cbb1f42..16a2f6781af 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrReturnFields.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrReturnFields.java
@@ -28,10 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Supplier;
-import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.queries.function.valuesource.QueryValueSource;
-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;
@@ -382,48 +379,10 @@ public class SolrReturnFields extends ReturnFields {
 
         // let's try it as a function instead
         QParser parser = QParser.getParser(funcStr, 
FunctionQParserPlugin.NAME, req);
-        Query q = null;
-        ValueSource vs = 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;
-          }
+          ValueSource vs = SortSpecParsing.parseValueSource(parser, sp, start);
           funcStr = sp.val.substring(start, sp.pos);
 
-          if (q instanceof FunctionQuery) {
-            vs = ((FunctionQuery) q).getValueSource();
-          } else {
-            vs = new QueryValueSource(q, 0.0f);
-          }
-
           if (key == null) {
             SolrParams localParams = parser.getLocalParams();
             if (localParams != null) {
diff --git a/solr/core/src/java/org/apache/solr/search/SortSpecParsing.java 
b/solr/core/src/java/org/apache/solr/search/SortSpecParsing.java
index 6b71f139133..22430a19afe 100644
--- a/solr/core/src/java/org/apache/solr/search/SortSpecParsing.java
+++ b/solr/core/src/java/org/apache/solr/search/SortSpecParsing.java
@@ -19,9 +19,7 @@ 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.queries.function.ValueSource;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
 import org.apache.solr.common.SolrException;
@@ -113,47 +111,13 @@ public class SortSpecParsing {
           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;
-            }
+            ValueSource vs = parseValueSource(parser, sp, start);
 
             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));
-              }
+              // we have a value source and a valid direction
+              sorts.add(vs.getSortField(top));
               fields.add(null);
               continue;
             }
@@ -221,6 +185,40 @@ public class SortSpecParsing {
     return new SortSpec(s, fields);
   }
 
+  static ValueSource parseValueSource(QParser parser, StrParser sp, int start) 
throws SyntaxError {
+    ValueSource vs;
+    if (parser instanceof FunctionQParser) {
+      FunctionQParser fparser = (FunctionQParser) parser;
+      fparser.setParseToEnd(false);
+
+      vs = fparser.parseAsValueSource();
+
+      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.
+      vs = parser.parseAsValueSource();
+
+      assert parser.getLocalParams() != null;
+      sp.pos = start + parser.localParamsEnd;
+    }
+    return vs;
+  }
+
   private static SortSpec newEmptySortSpec() {
     return new SortSpec(null, Collections.<SchemaField>emptyList());
   }
diff --git a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java 
b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
index a6bd40f8f49..c5c0d06dc7d 100644
--- a/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
+++ b/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
@@ -121,8 +121,11 @@ import 
org.apache.solr.util.plugin.NamedListInitializedPlugin;
 import org.locationtech.spatial4j.distance.DistanceUtils;
 
 /**
- * A factory that parses user queries to generate ValueSource instances. 
Intended usage is to create
- * pluggable, named functions for use in function queries.
+ * A factory parsing arguments (from {@link FunctionQParser}) into a real 
function whose results are
+ * emitted from a {@link ValueSource}. Custom ones can be registered by name 
and configured in
+ * {@code solrconfig.xml}.
+ *
+ * @see FunctionQParser
  */
 public abstract class ValueSourceParser implements NamedListInitializedPlugin {
   /** Parse the user input into a ValueSource. */
diff --git 
a/solr/core/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
 
b/solr/core/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
index 1a6c85e5894..aad2052ec81 100644
--- 
a/solr/core/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
+++ 
b/solr/core/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
@@ -17,7 +17,7 @@
 package org.apache.solr.search.function.distance;
 
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -282,25 +282,24 @@ public class DistanceFunctionTest extends SolrTestCaseJ4 {
             "fq",
             "id:5"),
         "//float[@name='score']='" + (float) (2.3 * 2.3 + 5.5 * 5.5 + 7.9 * 
7.9 + 2.4 * 2.4) + "'");
-    // Pass in imbalanced list, throw exception
-    try {
-      ignoreException("Illegal number of sources");
-      assertQ(
-          req(
-              "fl",
-              "*,score",
-              "q",
-              "{!func}sqedist(x_td, y_td, z_td, w_td, 0, 0, 0)",
-              "fq",
-              "id:1"),
-          "//float[@name='score']='0.0'");
-      fail("should throw an exception");
-    } catch (Exception e) {
-      Throwable cause = e.getCause();
-      assertNotNull(cause);
-      assertTrue(cause instanceof SolrException);
-    }
-    resetExceptionIgnores();
+
+    assertQEx(
+        "(should fail)",
+        "Illegal number of sources",
+        req("q", "{!func}sqedist(x_td, y_td, z_td, w_td, 0, 0, 0)"), // odd 
args
+        ErrorCode.BAD_REQUEST);
+
+    assertQEx(
+        "(should fail)",
+        "Unexpected text after function",
+        req("q", "{!func}sqedist(x_td, y_td), 0"), // text following the func 
call
+        ErrorCode.BAD_REQUEST);
+
+    assertQEx(
+        "(should fail)",
+        "Unexpected text after function",
+        req("q", "{!func}x_td, y_td, z_td, w_td, 0, 0, 0"),
+        ErrorCode.BAD_REQUEST);
 
     // do one test of Euclidean
     // two dimensions, notice how we only pass in 4 value sources

Reply via email to