blob: 8c07c439cbccc82b82b896aee838b9ad09735021 [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 java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.FunctionScoreQuery;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.BoolDocValues;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.queries.function.docvalues.LongDocValues;
import org.apache.lucene.queries.function.valuesource.*;
import org.apache.lucene.queries.payloads.PayloadDecoder;
import org.apache.lucene.queries.payloads.PayloadFunction;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.spell.JaroWinklerDistance;
import org.apache.lucene.search.spell.LevenshteinDistance;
import org.apache.lucene.search.spell.NGramDistance;
import org.apache.lucene.search.spell.StringDistance;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.schema.CurrencyFieldType;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.StrField;
import org.apache.solr.schema.TextField;
import org.apache.solr.search.facet.AggValueSource;
import org.apache.solr.search.facet.AvgAgg;
import org.apache.solr.search.facet.CountAgg;
import org.apache.solr.search.facet.CountValsAgg;
import org.apache.solr.search.facet.HLLAgg;
import org.apache.solr.search.facet.MinMaxAgg;
import org.apache.solr.search.facet.MissingAgg;
import org.apache.solr.search.facet.PercentileAgg;
import org.apache.solr.search.facet.RelatednessAgg;
import org.apache.solr.search.facet.StddevAgg;
import org.apache.solr.search.facet.SumAgg;
import org.apache.solr.search.facet.SumsqAgg;
import org.apache.solr.search.facet.UniqueAgg;
import org.apache.solr.search.facet.UniqueBlockFieldAgg;
import org.apache.solr.search.facet.UniqueBlockQueryAgg;
import org.apache.solr.search.facet.VarianceAgg;
import org.apache.solr.search.function.CollapseScoreFunction;
import org.apache.solr.search.function.ConcatStringFunction;
import org.apache.solr.search.function.EqualFunction;
import org.apache.solr.search.function.OrdFieldSource;
import org.apache.solr.search.function.ReverseOrdFieldSource;
import org.apache.solr.search.function.SolrComparisonBoolFunction;
import org.apache.solr.search.function.distance.GeoDistValueSourceParser;
import org.apache.solr.search.function.distance.GeohashFunction;
import org.apache.solr.search.function.distance.GeohashHaversineFunction;
import org.apache.solr.search.function.distance.HaversineFunction;
import org.apache.solr.search.function.distance.SquaredEuclideanFunction;
import org.apache.solr.search.function.distance.StringDistanceFunction;
import org.apache.solr.search.function.distance.VectorDistanceFunction;
import org.apache.solr.search.join.ChildFieldValueSourceParser;
import org.apache.solr.util.DateMathParser;
import org.apache.solr.util.PayloadUtils;
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.
*/
public abstract class ValueSourceParser implements NamedListInitializedPlugin {
/**
* Initialize the plugin.
*/
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {}
/**
* Parse the user input into a ValueSource.
*/
public abstract ValueSource parse(FunctionQParser fp) throws SyntaxError;
/** standard functions supported by default, filled in static class initialization */
private static final Map<String, ValueSourceParser> standardVSParsers = new HashMap<>();
/** standard functions supported by default */
public static final Map<String, ValueSourceParser> standardValueSourceParsers
= Collections.unmodifiableMap(standardVSParsers);
/** Adds a new parser for the name and returns any existing one that was overridden.
* This is not thread safe.
*/
private static ValueSourceParser addParser(String name, ValueSourceParser p) {
return standardVSParsers.put(name, p);
}
/** Adds a new parser for the name and returns any existing one that was overridden.
* This is not thread safe.
*/
private static ValueSourceParser addParser(NamedParser p) {
return standardVSParsers.put(p.name(), p);
}
private static void alias(String source, String dest) {
standardVSParsers.put(dest, standardVSParsers.get(source));
}
static {
addParser("testfunc", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
final ValueSource source = fp.parseValueSource();
return new TestValueSource(source);
}
});
addParser("ord", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String field = fp.parseId();
return new OrdFieldSource(field);
}
});
addParser("literal", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new LiteralValueSource(fp.parseArg());
}
});
addParser("threadid", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new LongConstValueSource(Thread.currentThread().getId());
}
});
addParser("sleep", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
int ms = fp.parseInt();
ValueSource source = fp.parseValueSource();
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return source;
}
});
addParser("rord", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String field = fp.parseId();
return new ReverseOrdFieldSource(field);
}
});
addParser("top", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
// top(vs) is now a no-op
ValueSource source = fp.parseValueSource();
return source;
}
});
addParser("linear", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource source = fp.parseValueSource();
float slope = fp.parseFloat();
float intercept = fp.parseFloat();
return new LinearFloatFunction(source, slope, intercept);
}
});
addParser("recip", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource source = fp.parseValueSource();
float m = fp.parseFloat();
float a = fp.parseFloat();
float b = fp.parseFloat();
return new ReciprocalFloatFunction(source, m, a, b);
}
});
addParser("scale", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource source = fp.parseValueSource();
float min = fp.parseFloat();
float max = fp.parseFloat();
return new ScaleFloatFunction(source, min, max);
}
});
addParser("div", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new DivFloatFunction(a, b);
}
});
addParser("mod", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new DualFloatFunction(a, b) {
@Override
protected String name() {
return "mod";
}
@Override
protected float func(int doc, FunctionValues aVals, FunctionValues bVals) throws IOException {
return aVals.floatVal(doc) % bVals.floatVal(doc);
}
};
}
});
addParser("map", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource source = fp.parseValueSource();
float min = fp.parseFloat();
float max = fp.parseFloat();
ValueSource target = fp.parseValueSource();
ValueSource def = fp.hasMoreArguments() ? fp.parseValueSource() : null;
return new RangeMapFloatFunction(source, min, max, target, def);
}
});
addParser("abs", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource source = fp.parseValueSource();
return new SimpleFloatFunction(source) {
@Override
protected String name() {
return "abs";
}
@Override
protected float func(int doc, FunctionValues vals) throws IOException {
return Math.abs(vals.floatVal(doc));
}
};
}
});
addParser("cscore", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new CollapseScoreFunction();
}
});
addParser("sum", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new SumFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
alias("sum","add");
addParser("product", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new ProductFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
alias("product","mul");
addParser("sub", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new DualFloatFunction(a, b) {
@Override
protected String name() {
return "sub";
}
@Override
protected float func(int doc, FunctionValues aVals, FunctionValues bVals) throws IOException {
return aVals.floatVal(doc) - bVals.floatVal(doc);
}
};
}
});
addParser("vector", new ValueSourceParser(){
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new VectorValueSource(fp.parseValueSourceList());
}
});
addParser("query", new ValueSourceParser() {
// boost(query($q),rating)
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
Query q = fp.parseNestedQuery();
float defVal = 0.0f;
if (fp.hasMoreArguments()) {
defVal = fp.parseFloat();
}
return new QueryValueSource(q, defVal);
}
});
addParser("boost", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
Query q = fp.parseNestedQuery();
ValueSource vs = fp.parseValueSource();
return new QueryValueSource(FunctionScoreQuery.boostByValue(q, vs.asDoubleValuesSource()), 0.0f);
}
});
addParser("joindf", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String f0 = fp.parseArg();
String qf = fp.parseArg();
return new JoinDocFreqValueSource( f0, qf );
}
});
addParser("geodist", new GeoDistValueSourceParser());
addParser("hsin", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
double radius = fp.parseDouble();
//SOLR-2114, make the convert flag required, since the parser doesn't support much in the way of lookahead or the ability to convert a String into a ValueSource
boolean convert = Boolean.parseBoolean(fp.parseArg());
MultiValueSource pv1;
MultiValueSource pv2;
ValueSource one = fp.parseValueSource();
ValueSource two = fp.parseValueSource();
if (fp.hasMoreArguments()) {
pv1 = new VectorValueSource(Arrays.asList(one, two));//x1, y1
pv2 = new VectorValueSource(Arrays.asList(fp.parseValueSource(), fp.parseValueSource()));//x2, y2
} else {
//check to see if we have multiValue source
if (one instanceof MultiValueSource && two instanceof MultiValueSource){
pv1 = (MultiValueSource) one;
pv2 = (MultiValueSource) two;
} else {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Input must either be 2 MultiValueSources, or there must be 4 ValueSources");
}
}
return new HaversineFunction(pv1, pv2, radius, convert);
}
});
addParser("ghhsin", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
double radius = fp.parseDouble();
ValueSource gh1 = fp.parseValueSource();
ValueSource gh2 = fp.parseValueSource();
return new GeohashHaversineFunction(gh1, gh2, radius);
}
});
addParser("geohash", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lat = fp.parseValueSource();
ValueSource lon = fp.parseValueSource();
return new GeohashFunction(lat, lon);
}
});
addParser("strdist", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource str1 = fp.parseValueSource();
ValueSource str2 = fp.parseValueSource();
String distClass = fp.parseArg();
StringDistance dist = null;
if (distClass.equalsIgnoreCase("jw")) {
dist = new JaroWinklerDistance();
} else if (distClass.equalsIgnoreCase("edit")) {
dist = new LevenshteinDistance();
} else if (distClass.equalsIgnoreCase("ngram")) {
int ngram = 2;
if (fp.hasMoreArguments()) {
ngram = fp.parseInt();
}
dist = new NGramDistance(ngram);
} else {
dist = fp.req.getCore().getResourceLoader().newInstance(distClass, StringDistance.class);
}
return new StringDistanceFunction(str1, str2, dist);
}
});
addParser("field", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String fieldName = fp.parseArg();
SchemaField f = fp.getReq().getSchema().getField(fieldName);
if (fp.hasMoreArguments()) {
// multivalued selector option
String s = fp.parseArg();
FieldType.MultiValueSelector selector = FieldType.MultiValueSelector.lookup(s);
if (null == selector) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Multi-Valued field selector '"+s+"' not supported");
}
return f.getType().getSingleValueSource(selector, f, fp);
}
// simple field ValueSource
return f.getType().getValueSource(f, fp);
}
});
addParser("currency", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String fieldName = fp.parseArg();
SchemaField f = fp.getReq().getSchema().getField(fieldName);
if (! (f.getType() instanceof CurrencyFieldType)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Currency function input must be the name of a CurrencyFieldType: " + fieldName);
}
CurrencyFieldType ft = (CurrencyFieldType) f.getType();
String code = fp.hasMoreArguments() ? fp.parseArg() : null;
return ft.getConvertedValueSource(code, ft.getValueSource(f, fp));
}
});
addParser(new DoubleParser("rad") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return vals.doubleVal(doc) * DistanceUtils.DEGREES_TO_RADIANS;
}
});
addParser(new DoubleParser("deg") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return vals.doubleVal(doc) * DistanceUtils.RADIANS_TO_DEGREES;
}
});
addParser(new DoubleParser("sqrt") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.sqrt(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("cbrt") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.cbrt(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("log") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.log10(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("ln") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.log(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("exp") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.exp(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("sin") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.sin(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("cos") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.cos(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("tan") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.tan(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("asin") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.asin(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("acos") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.acos(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("atan") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.atan(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("sinh") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.sinh(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("cosh") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.cosh(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("tanh") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.tanh(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("ceil") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.ceil(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("floor") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.floor(vals.doubleVal(doc));
}
});
addParser(new DoubleParser("rint") {
@Override
public double func(int doc, FunctionValues vals) throws IOException {
return Math.rint(vals.doubleVal(doc));
}
});
addParser(new Double2Parser("pow") {
@Override
public double func(int doc, FunctionValues a, FunctionValues b) throws IOException {
return Math.pow(a.doubleVal(doc), b.doubleVal(doc));
}
});
addParser(new Double2Parser("hypot") {
@Override
public double func(int doc, FunctionValues a, FunctionValues b) throws IOException {
return Math.hypot(a.doubleVal(doc), b.doubleVal(doc));
}
});
addParser(new Double2Parser("atan2") {
@Override
public double func(int doc, FunctionValues a, FunctionValues b) throws IOException {
return Math.atan2(a.doubleVal(doc), b.doubleVal(doc));
}
});
addParser("max", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new MaxFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
addParser("min", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new MinFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
addParser("sqedist", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
MVResult mvr = getMultiValueSources(sources);
return new SquaredEuclideanFunction(mvr.mv1, mvr.mv2);
}
});
addParser("dist", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
float power = fp.parseFloat();
List<ValueSource> sources = fp.parseValueSourceList();
MVResult mvr = getMultiValueSources(sources);
return new VectorDistanceFunction(power, mvr.mv1, mvr.mv2);
}
});
addParser("ms", new DateValueSourceParser());
addParser("pi", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new DoubleConstValueSource(Math.PI);
}
});
addParser("e", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new DoubleConstValueSource(Math.E);
}
});
addParser("docfreq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = parseTerm(fp);
return new DocFreqValueSource(tinfo.field, tinfo.val, tinfo.indexedField, tinfo.indexedBytes.get());
}
});
addParser("totaltermfreq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = parseTerm(fp);
return new TotalTermFreqValueSource(tinfo.field, tinfo.val, tinfo.indexedField, tinfo.indexedBytes.get());
}
});
alias("totaltermfreq","ttf");
addParser("sumtotaltermfreq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String field = fp.parseArg();
return new SumTotalTermFreqValueSource(field);
}
});
alias("sumtotaltermfreq","sttf");
addParser("idf", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = parseTerm(fp);
return new IDFValueSource(tinfo.field, tinfo.val, tinfo.indexedField, tinfo.indexedBytes.get());
}
});
addParser("termfreq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = parseTerm(fp);
return new TermFreqValueSource(tinfo.field, tinfo.val, tinfo.indexedField, tinfo.indexedBytes.get());
}
});
addParser("tf", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = parseTerm(fp);
return new TFValueSource(tinfo.field, tinfo.val, tinfo.indexedField, tinfo.indexedBytes.get());
}
});
addParser("norm", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String field = fp.parseArg();
return new NormValueSource(field);
}
});
addParser("maxdoc", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new MaxDocValueSource();
}
});
addParser("numdocs", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new NumDocsValueSource();
}
});
addParser("payload", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
// payload(field,value[,default, ['min|max|average|first']])
// defaults to "average" and 0.0 default value
TInfo tinfo = parseTerm(fp); // would have made this parser a new separate class and registered it, but this handy method is private :/
ValueSource defaultValueSource;
if (fp.hasMoreArguments()) {
defaultValueSource = fp.parseValueSource();
} else {
defaultValueSource = new ConstValueSource(0.0f);
}
PayloadFunction payloadFunction = null;
String func = "average";
if (fp.hasMoreArguments()) {
func = fp.parseArg();
}
payloadFunction = PayloadUtils.getPayloadFunction(func);
// Support func="first" by payloadFunction=null
if(payloadFunction == null && !"first".equals(func)) {
// not "first" (or average, min, or max)
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid payload function: " + func);
}
IndexSchema schema = fp.getReq().getCore().getLatestSchema();
PayloadDecoder decoder = schema.getPayloadDecoder(tinfo.field);
if (decoder==null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No payload decoder found for field: " + tinfo.field);
}
return new FloatPayloadValueSource(
tinfo.field,
tinfo.val,
tinfo.indexedField,
tinfo.indexedBytes.get(),
decoder,
payloadFunction,
defaultValueSource);
}
});
addParser("true", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new BoolConstValueSource(true);
}
});
addParser("false", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) {
return new BoolConstValueSource(false);
}
});
addParser("exists", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource vs = fp.parseValueSource();
return new SimpleBoolFunction(vs) {
@Override
protected String name() {
return "exists";
}
@Override
protected boolean func(int doc, FunctionValues vals) throws IOException {
return vals.exists(doc);
}
};
}
});
addParser("not", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource vs = fp.parseValueSource();
return new SimpleBoolFunction(vs) {
@Override
protected boolean func(int doc, FunctionValues vals) throws IOException {
return !vals.boolVal(doc);
}
@Override
protected String name() {
return "not";
}
};
}
});
addParser("and", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new MultiBoolFunction(sources) {
@Override
protected String name() {
return "and";
}
@Override
protected boolean func(int doc, FunctionValues[] vals) throws IOException {
for (FunctionValues dv : vals)
if (!dv.boolVal(doc)) return false;
return true;
}
};
}
});
addParser("or", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new MultiBoolFunction(sources) {
@Override
protected String name() {
return "or";
}
@Override
protected boolean func(int doc, FunctionValues[] vals) throws IOException {
for (FunctionValues dv : vals)
if (dv.boolVal(doc)) return true;
return false;
}
};
}
});
addParser("xor", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new MultiBoolFunction(sources) {
@Override
protected String name() {
return "xor";
}
@Override
protected boolean func(int doc, FunctionValues[] vals) throws IOException {
int nTrue=0, nFalse=0;
for (FunctionValues dv : vals) {
if (dv.boolVal(doc)) nTrue++;
else nFalse++;
}
return nTrue != 0 && nFalse != 0;
}
};
}
});
addParser("if", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource ifValueSource = fp.parseValueSource();
ValueSource trueValueSource = fp.parseValueSource();
ValueSource falseValueSource = fp.parseValueSource();
return new IfFunction(ifValueSource, trueValueSource, falseValueSource);
}
});
addParser("gt", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lhsValSource = fp.parseValueSource();
ValueSource rhsValSource = fp.parseValueSource();
return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "gt", (cmp) -> cmp > 0);
}
});
addParser("lt", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lhsValSource = fp.parseValueSource();
ValueSource rhsValSource = fp.parseValueSource();
return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "lt", (cmp) -> cmp < 0);
}
});
addParser("gte", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lhsValSource = fp.parseValueSource();
ValueSource rhsValSource = fp.parseValueSource();
return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "gte", (cmp) -> cmp >= 0);
}
});
addParser("lte", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lhsValSource = fp.parseValueSource();
ValueSource rhsValSource = fp.parseValueSource();
return new SolrComparisonBoolFunction(lhsValSource, rhsValSource, "lte", (cmp) -> cmp <= 0);
}
});
addParser("eq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
ValueSource lhsValSource = fp.parseValueSource();
ValueSource rhsValSource = fp.parseValueSource();
return new EqualFunction(lhsValSource, rhsValSource, "eq");
}
});
addParser("def", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new DefFunction(fp.parseValueSourceList());
}
});
addParser("concat", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<ValueSource> sources = fp.parseValueSourceList();
return new ConcatStringFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
addParser("agg", new ValueSourceParser() {
@Override
public AggValueSource parse(FunctionQParser fp) throws SyntaxError {
return fp.parseAgg(FunctionQParser.FLAG_DEFAULT);
}
});
addParser("agg_count", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new CountAgg();
}
});
addParser("agg_unique", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new UniqueAgg(fp.parseArg());
}
});
addParser("agg_uniqueBlock", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
if (fp.sp.peek() == QueryParsing.LOCALPARAM_START.charAt(0) ) {
return new UniqueBlockQueryAgg(fp.parseNestedQuery());
}
return new UniqueBlockFieldAgg(fp.parseArg());
}
});
addParser("agg_hll", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new HLLAgg(fp.parseArg());
}
});
addParser("agg_sum", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new SumAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_avg", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new AvgAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_sumsq", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new SumsqAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_variance", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new VarianceAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_stddev", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new StddevAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_missing", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new MissingAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_countvals", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new CountValsAgg(fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
/***
addParser("agg_multistat", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return null;
}
});
***/
addParser("agg_min", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new MinMaxAgg("min", fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_max", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new MinMaxAgg("max", fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE));
}
});
addParser("agg_percentile", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
List<Double> percentiles = new ArrayList<>();
ValueSource vs = fp.parseValueSource(FunctionQParser.FLAG_DEFAULT | FunctionQParser.FLAG_USE_FIELDNAME_SOURCE);
while (fp.hasMoreArguments()) {
double val = fp.parseDouble();
if (val<0 || val>100) {
throw new SyntaxError("requested percentile must be between 0 and 100. got " + val);
}
percentiles.add(val);
}
if (percentiles.isEmpty()) {
throw new SyntaxError("expected percentile(valsource,percent1[,percent2]*) EXAMPLE:percentile(myfield,50)");
}
return new PercentileAgg(vs, percentiles);
}
});
addParser("agg_" + RelatednessAgg.NAME, new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
// TODO: (fore & back)-ground should be optional -- use hasMoreArguments
// if only one arg, assume it's the foreground
// (background is the one that will most commonly just be "*:*")
// see notes in RelatednessAgg constructor about why we don't do this yet
RelatednessAgg agg = new RelatednessAgg(fp.parseNestedQuery(), fp.parseNestedQuery());
agg.setOpts(fp);
return agg;
}
});
addParser("childfield", new ChildFieldValueSourceParser());
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
private static TInfo parseTerm(FunctionQParser fp) throws SyntaxError {
TInfo tinfo = new TInfo();
tinfo.indexedField = tinfo.field = fp.parseArg();
tinfo.val = fp.parseArg();
tinfo.indexedBytes = new BytesRefBuilder();
FieldType ft = fp.getReq().getSchema().getFieldTypeNoEx(tinfo.field);
if (ft == null) ft = new StrField();
if (ft instanceof TextField) {
// need to do analysis on the term
String indexedVal = tinfo.val;
Query q = ft.getFieldQuery(fp, fp.getReq().getSchema().getFieldOrNull(tinfo.field), tinfo.val);
if (q instanceof TermQuery) {
Term term = ((TermQuery)q).getTerm();
tinfo.indexedField = term.field();
indexedVal = term.text();
}
tinfo.indexedBytes.copyChars(indexedVal);
} else {
ft.readableToIndexed(tinfo.val, tinfo.indexedBytes);
}
return tinfo;
}
private static void splitSources(int dim, List<ValueSource> sources, List<ValueSource> dest1, List<ValueSource> dest2) {
//Get dim value sources for the first vector
for (int i = 0; i < dim; i++) {
dest1.add(sources.get(i));
}
//Get dim value sources for the second vector
for (int i = dim; i < sources.size(); i++) {
dest2.add(sources.get(i));
}
}
private static MVResult getMultiValueSources(List<ValueSource> sources) {
MVResult mvr = new MVResult();
if (sources.size() % 2 != 0) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources. There must be an even number of sources");
}
if (sources.size() == 2) {
//check to see if these are MultiValueSource
boolean s1MV = sources.get(0) instanceof MultiValueSource;
boolean s2MV = sources.get(1) instanceof MultiValueSource;
if (s1MV && s2MV) {
mvr.mv1 = (MultiValueSource) sources.get(0);
mvr.mv2 = (MultiValueSource) sources.get(1);
} else if (s1MV ||
s2MV) {
//if one is a MultiValueSource, than the other one needs to be too.
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources. There must be an even number of sources");
} else {
mvr.mv1 = new VectorValueSource(Collections.singletonList(sources.get(0)));
mvr.mv2 = new VectorValueSource(Collections.singletonList(sources.get(1)));
}
} else {
int dim = sources.size() / 2;
List<ValueSource> sources1 = new ArrayList<>(dim);
List<ValueSource> sources2 = new ArrayList<>(dim);
//Get dim value sources for the first vector
splitSources(dim, sources, sources1, sources2);
mvr.mv1 = new VectorValueSource(sources1);
mvr.mv2 = new VectorValueSource(sources2);
}
return mvr;
}
private static class MVResult {
MultiValueSource mv1;
MultiValueSource mv2;
}
private static class TInfo {
String field;
String val;
String indexedField;
BytesRefBuilder indexedBytes;
}
}
class DateValueSourceParser extends ValueSourceParser {
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
}
public Date getDate(FunctionQParser fp, String arg) {
if (arg == null) return null;
// check character index 1 to be a digit. Index 0 might be a +/-.
if (arg.startsWith("NOW") || (arg.length() > 1 && Character.isDigit(arg.charAt(1)))) {
Date now = null;//TODO pull from params?
return DateMathParser.parseMath(now, arg);
}
return null;
}
public ValueSource getValueSource(FunctionQParser fp, String arg) {
if (arg == null) return null;
SchemaField f = fp.req.getSchema().getField(arg);
return f.getType().getValueSource(f, fp);
}
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
String first = fp.parseArg();
String second = fp.parseArg();
if (first == null) first = "NOW";
Date d1 = getDate(fp, first);
ValueSource v1 = d1 == null ? getValueSource(fp, first) : null;
Date d2 = getDate(fp, second);
ValueSource v2 = d2 == null ? getValueSource(fp, second) : null;
// d constant
// v field
// dd constant
// dv subtract field from constant
// vd subtract constant from field
// vv subtract fields
final long ms1 = (d1 == null) ? 0 : d1.getTime();
final long ms2 = (d2 == null) ? 0 : d2.getTime();
// "d,dd" handle both constant cases
if (d1 != null && v2 == null) {
return new LongConstValueSource(ms1 - ms2);
}
// "v" just the date field
if (v1 != null && v2 == null && d2 == null) {
return v1;
}
// "dv"
if (d1 != null && v2 != null)
return new DualFloatFunction(new LongConstValueSource(ms1), v2) {
@Override
protected String name() {
return "ms";
}
@Override
protected float func(int doc, FunctionValues aVals, FunctionValues bVals) throws IOException {
return ms1 - bVals.longVal(doc);
}
};
// "vd"
if (v1 != null && d2 != null)
return new DualFloatFunction(v1, new LongConstValueSource(ms2)) {
@Override
protected String name() {
return "ms";
}
@Override
protected float func(int doc, FunctionValues aVals, FunctionValues bVals) throws IOException {
return aVals.longVal(doc) - ms2;
}
};
// "vv"
if (v1 != null && v2 != null)
return new DualFloatFunction(v1, v2) {
@Override
protected String name() {
return "ms";
}
@Override
protected float func(int doc, FunctionValues aVals, FunctionValues bVals) throws IOException {
return aVals.longVal(doc) - bVals.longVal(doc);
}
};
return null; // shouldn't happen
}
}
// Private for now - we need to revisit how to handle typing in function queries
class LongConstValueSource extends ConstNumberSource {
final long constant;
final double dv;
final float fv;
public LongConstValueSource(long constant) {
this.constant = constant;
this.dv = constant;
this.fv = constant;
}
@Override
public String description() {
return "const(" + constant + ")";
}
@Override
public FunctionValues getValues(@SuppressWarnings({"rawtypes"})Map context
, LeafReaderContext readerContext) throws IOException {
return new LongDocValues(this) {
@Override
public float floatVal(int doc) {
return fv;
}
@Override
public int intVal(int doc) {
return (int) constant;
}
@Override
public long longVal(int doc) {
return constant;
}
@Override
public double doubleVal(int doc) {
return dv;
}
@Override
public String toString(int doc) {
return description();
}
};
}
@Override
public int hashCode() {
return (int) constant + (int) (constant >>> 32);
}
@Override
public boolean equals(Object o) {
if (LongConstValueSource.class != o.getClass()) return false;
LongConstValueSource other = (LongConstValueSource) o;
return this.constant == other.constant;
}
@Override
public int getInt() {
return (int)constant;
}
@Override
public long getLong() {
return constant;
}
@Override
public float getFloat() {
return fv;
}
@Override
public double getDouble() {
return dv;
}
@Override
public Number getNumber() {
return constant;
}
@Override
public boolean getBool() {
return constant != 0;
}
}
abstract class NamedParser extends ValueSourceParser {
private final String name;
public NamedParser(String name) {
this.name = name;
}
public String name() {
return name;
}
}
abstract class DoubleParser extends NamedParser {
public DoubleParser(String name) {
super(name);
}
public abstract double func(int doc, FunctionValues vals) throws IOException;
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new Function(fp.parseValueSource());
}
class Function extends SingleFunction {
public Function(ValueSource source) {
super(source);
}
@Override
public String name() {
return DoubleParser.this.name();
}
@Override
public FunctionValues getValues(@SuppressWarnings({"rawtypes"})Map context, LeafReaderContext readerContext) throws IOException {
@SuppressWarnings({"unchecked"})
final FunctionValues vals = source.getValues(context, readerContext);
return new DoubleDocValues(this) {
@Override
public double doubleVal(int doc) throws IOException {
return func(doc, vals);
}
@Override
public String toString(int doc) throws IOException {
return name() + '(' + vals.toString(doc) + ')';
}
};
}
}
}
abstract class Double2Parser extends NamedParser {
public Double2Parser(String name) {
super(name);
}
public abstract double func(int doc, FunctionValues a, FunctionValues b) throws IOException;
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {
return new Function(fp.parseValueSource(), fp.parseValueSource());
}
class Function extends ValueSource {
private final ValueSource a;
private final ValueSource b;
/**
* @param a the base.
* @param b the exponent.
*/
public Function(ValueSource a, ValueSource b) {
this.a = a;
this.b = b;
}
@Override
public String description() {
return name() + "(" + a.description() + "," + b.description() + ")";
}
@Override
public FunctionValues getValues(@SuppressWarnings({"rawtypes"})Map context, LeafReaderContext readerContext) throws IOException {
@SuppressWarnings({"unchecked"})
final FunctionValues aVals = a.getValues(context, readerContext);
@SuppressWarnings({"unchecked"})
final FunctionValues bVals = b.getValues(context, readerContext);
return new DoubleDocValues(this) {
@Override
public double doubleVal(int doc) throws IOException {
return func(doc, aVals, bVals);
}
@Override
public String toString(int doc) throws IOException {
return name() + '(' + aVals.toString(doc) + ',' + bVals.toString(doc) + ')';
}
};
}
@Override
public void createWeight(@SuppressWarnings({"rawtypes"})Map context, IndexSearcher searcher) throws IOException {
}
@Override
public int hashCode() {
int h = a.hashCode();
h ^= (h << 13) | (h >>> 20);
h += b.hashCode();
h ^= (h << 23) | (h >>> 10);
h += name().hashCode();
return h;
}
@Override
public boolean equals(Object o) {
if (this.getClass() != o.getClass()) return false;
Function other = (Function)o;
return this.a.equals(other.a)
&& this.b.equals(other.b);
}
}
}
class BoolConstValueSource extends ConstNumberSource {
final boolean constant;
public BoolConstValueSource(boolean constant) {
this.constant = constant;
}
@Override
public String description() {
return "const(" + constant + ")";
}
@Override
public FunctionValues getValues(@SuppressWarnings({"rawtypes"})Map context,
LeafReaderContext readerContext) throws IOException {
return new BoolDocValues(this) {
@Override
public boolean boolVal(int doc) {
return constant;
}
};
}
@Override
public int hashCode() {
return constant ? 0x12345678 : 0x87654321;
}
@Override
public boolean equals(Object o) {
if (BoolConstValueSource.class != o.getClass()) return false;
BoolConstValueSource other = (BoolConstValueSource) o;
return this.constant == other.constant;
}
@Override
public int getInt() {
return constant ? 1 : 0;
}
@Override
public long getLong() {
return constant ? 1 : 0;
}
@Override
public float getFloat() {
return constant ? 1 : 0;
}
@Override
public double getDouble() {
return constant ? 1 : 0;
}
@Override
public Number getNumber() {
return constant ? 1 : 0;
}
@Override
public boolean getBool() {
return constant;
}
}
class TestValueSource extends ValueSource {
ValueSource source;
public TestValueSource(ValueSource source) {
this.source = source;
}
@Override
@SuppressWarnings({"unchecked"})
public FunctionValues getValues(@SuppressWarnings({"rawtypes"})Map context
, LeafReaderContext readerContext) throws IOException {
if (context.get(this) == null) {
SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "testfunc: unweighted value source detected. delegate="+source + " request=" + (requestInfo==null ? "null" : requestInfo.getReq()));
}
return source.getValues(context, readerContext);
}
@Override
public boolean equals(Object o) {
return o instanceof TestValueSource && source.equals(((TestValueSource)o).source);
}
@Override
public int hashCode() {
return source.hashCode() + TestValueSource.class.hashCode();
}
@Override
public String description() {
return "testfunc(" + source.description() + ')';
}
@Override
@SuppressWarnings({"unchecked"})
public void createWeight(@SuppressWarnings({"rawtypes"})Map context, IndexSearcher searcher) throws IOException {
context.put(this, this);
}
@Override
public SortField getSortField(boolean reverse) {
return super.getSortField(reverse);
}
}