/**
* 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.*;
import java.io.IOException;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.lucene.index.IndexReader;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.SolrException;
import org.apache.solr.search.function.*;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
import org.apache.solr.schema.TrieDateField;
import org.apache.solr.schema.DateField;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.LegacyDateField;
/**
* A factory that parses user queries to generate ValueSource instances.
* Intented usage is to create pluggable, named functions for use in function queries.
*/
public abstract class ValueSourceParser implements NamedListInitializedPlugin
{
/**
* Initialize the plugin.
*/
public abstract void init( NamedList args );
/**
* Parse the user input into a ValueSource.
*
* @param fp
* @throws ParseException
*/
public abstract ValueSource parse(FunctionQParser fp) throws ParseException;
/* standard functions */
public static Map<String, ValueSourceParser> standardValueSourceParsers = new HashMap<String, ValueSourceParser>();
static {
standardValueSourceParsers.put("ord", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
String field = fp.parseId();
return new TopValueSource(new OrdFieldSource(field));
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("rord", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
String field = fp.parseId();
return new TopValueSource(new ReverseOrdFieldSource(field));
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("top", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
// nested top is redundant, and ord and rord get automatically wrapped
if (source instanceof TopValueSource) return source;
return new TopValueSource(source);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("linear", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
float slope = fp.parseFloat();
float intercept = fp.parseFloat();
return new LinearFloatFunction(source,slope,intercept);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("max", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
float val = fp.parseFloat();
return new MaxFloatFunction(source,val);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("recip", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
float m = fp.parseFloat();
float a = fp.parseFloat();
float b = fp.parseFloat();
return new ReciprocalFloatFunction(source,m,a,b);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("scale", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
float min = fp.parseFloat();
float max = fp.parseFloat();
return new TopValueSource(new ScaleFloatFunction(source,min,max));
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("pow", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new PowFloatFunction(a,b);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("div", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new DivFloatFunction(a,b);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("map", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
float min = fp.parseFloat();
float max = fp.parseFloat();
float target = fp.parseFloat();
Float def = fp.hasMoreArguments() ? fp.parseFloat() : null;
return new RangeMapFloatFunction(source,min,max,target,def);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("sqrt", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
return new SimpleFloatFunction(source) {
protected String name() {
return "sqrt";
}
protected float func(int doc, DocValues vals) {
return (float)Math.sqrt(vals.floatVal(doc));
}
};
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("log", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
return new SimpleFloatFunction(source) {
protected String name() {
return "log";
}
protected float func(int doc, DocValues vals) {
return (float)Math.log10(vals.floatVal(doc));
}
};
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("abs", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource source = fp.parseValueSource();
return new SimpleFloatFunction(source) {
protected String name() {
return "abs";
}
protected float func(int doc, DocValues vals) {
return (float)Math.abs(vals.floatVal(doc));
}
};
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("sum", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
List<ValueSource> sources = fp.parseValueSourceList();
return new SumFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("product", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
List<ValueSource> sources = fp.parseValueSourceList();
return new ProductFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("sub", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
ValueSource a = fp.parseValueSource();
ValueSource b = fp.parseValueSource();
return new DualFloatFunction(a,b) {
protected String name() {
return "sub";
}
protected float func(int doc, DocValues aVals, DocValues bVals) {
return aVals.floatVal(doc) - bVals.floatVal(doc);
}
};
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("query", new ValueSourceParser() {
// boost(query($q),rating)
public ValueSource parse(FunctionQParser fp) throws ParseException {
Query q = fp.parseNestedQuery();
float defVal = 0.0f;
if (fp.hasMoreArguments()) {
defVal = fp.parseFloat();
}
return new QueryValueSource(q, defVal);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("boost", new ValueSourceParser() {
public ValueSource parse(FunctionQParser fp) throws ParseException {
Query q = fp.parseNestedQuery();
ValueSource vs = fp.parseValueSource();
BoostedQuery bq = new BoostedQuery(q, vs);
return new QueryValueSource(bq, 0.0f);
}
public void init(NamedList args) {
}
});
standardValueSourceParsers.put("ms", new DateValueSourceParser());
}
}
class DateValueSourceParser extends ValueSourceParser {
DateField df = new TrieDateField();
public void init(NamedList args) {}
public Date getDate(FunctionQParser fp, String arg) {
if (arg==null) return null;
if (arg.startsWith("NOW") || (arg.length()>0 && Character.isDigit(arg.charAt(0)))) {
return df.parseMathLenient(null, arg, fp.req);
}
return null;
}
public ValueSource getValueSource(FunctionQParser fp, String arg) {
if (arg==null) return null;
SchemaField f = fp.req.getSchema().getField(arg);
if (f.getType().getClass() == DateField.class || f.getType().getClass() == LegacyDateField.class) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can't use ms() function on non-numeric legacy date field " + arg);
}
return f.getType().getValueSource(f, fp);
}
public ValueSource parse(FunctionQParser fp) throws ParseException {
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) {
protected String name() {
return "ms";
}
protected float func(int doc, DocValues aVals, DocValues bVals) {
return ms1 - bVals.longVal(doc);
}
};
// "vd"
if (v1!=null && d2!=null)
return new DualFloatFunction(v1, new LongConstValueSource(ms2)) {
protected String name() {
return "ms";
}
protected float func(int doc, DocValues aVals, DocValues bVals) {
return aVals.longVal(doc) - ms2;
}
};
// "vv"
if (v1!=null && v2!=null)
return new DualFloatFunction(v1,v2) {
protected String name() {
return "ms";
}
protected float func(int doc, DocValues aVals, DocValues bVals) {
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 ValueSource {
final long constant;
public LongConstValueSource(long constant) {
this.constant = constant;
}
public String description() {
return "const(" + constant + ")";
}
public DocValues getValues(Map context, IndexReader reader) throws IOException {
return new DocValues() {
public float floatVal(int doc) {
return constant;
}
public int intVal(int doc) {
return (int)constant;
}
public long longVal(int doc) {
return constant;
}
public double doubleVal(int doc) {
return constant;
}
public String strVal(int doc) {
return Long.toString(constant);
}
public String toString(int doc) {
return description();
}
};
}
public int hashCode() {
return (int)constant + (int)(constant>>>32);
}
public boolean equals(Object o) {
if (LongConstValueSource.class != o.getClass()) return false;
LongConstValueSource other = (LongConstValueSource)o;
return this.constant == other.constant;
}
}