diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 17ef07d53fa..a30f7e700ec 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -144,6 +144,10 @@ New Features to IndexReader.open (in the case you have a custom IndexReaderFactory). (simonw via rmuir) +* SOLR-2136: Boolean type added to function queries, along with + new functions exists(), if(), and(), or(), xor(), not(), def(), + and true and false constants. (yonik) + Optimizations ---------------------- diff --git a/solr/src/java/org/apache/solr/schema/BoolField.java b/solr/src/java/org/apache/solr/schema/BoolField.java index 0332b829d1c..3cd50247abc 100644 --- a/solr/src/java/org/apache/solr/schema/BoolField.java +++ b/solr/src/java/org/apache/solr/schema/BoolField.java @@ -17,12 +17,16 @@ package org.apache.solr.schema; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.FieldCache; import org.apache.lucene.search.SortField; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.CharsRef; +import org.apache.solr.search.MutableValue; +import org.apache.solr.search.MutableValueBool; +import org.apache.solr.search.MutableValueInt; import org.apache.solr.search.QParser; -import org.apache.solr.search.function.ValueSource; -import org.apache.solr.search.function.OrdFieldSource; +import org.apache.solr.search.function.*; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; @@ -50,7 +54,7 @@ public class BoolField extends FieldType { @Override public ValueSource getValueSource(SchemaField field, QParser qparser) { field.checkFieldCacheSource(qparser); - return new OrdFieldSource(field.name); + return new BoolFieldSource(field.name); } // avoid instantiating every time... @@ -121,7 +125,7 @@ public class BoolField extends FieldType { @Override public Object toObject(SchemaField sf, BytesRef term) { - return term.bytes[0] == 'T'; + return term.bytes[term.offset] == 'T'; } @Override @@ -145,6 +149,83 @@ public class BoolField extends FieldType { @Override public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException { - writer.writeBool(name, f.stringValue().charAt(0) =='T'); + writer.writeBool(name, f.stringValue().charAt(0) == 'T'); } } + +// TODO - this can be much more efficient - use OpenBitSet or Bits +class BoolFieldSource extends ValueSource { + protected String field; + + public BoolFieldSource(String field) { + this.field = field; + } + + @Override + public String description() { + return "bool(" + field + ')'; + } + + + @Override + public DocValues getValues(Map context, IndexReader.AtomicReaderContext readerContext) throws IOException { + final FieldCache.DocTermsIndex sindex = FieldCache.DEFAULT.getTermsIndex(readerContext.reader, field); + + // figure out what ord maps to true + int nord = sindex.numOrd(); + BytesRef br = new BytesRef(); + int tord = -1; + for (int i=1; i sources = fp.parseValueSourceList(); + return new MultiBoolFunction(sources) { + @Override + protected String name() { + return "and"; + } + @Override + protected boolean func(int doc, DocValues[] vals) { + for (DocValues dv : vals) + if (!dv.boolVal(doc)) return false; + return true; + } + }; + } + }); + + addParser("or", new ValueSourceParser() { + @Override + public ValueSource parse(FunctionQParser fp) throws ParseException { + List sources = fp.parseValueSourceList(); + return new MultiBoolFunction(sources) { + @Override + protected String name() { + return "or"; + } + @Override + protected boolean func(int doc, DocValues[] vals) { + for (DocValues dv : vals) + if (dv.boolVal(doc)) return true; + return false; + } + }; + } + }); + + addParser("xor", new ValueSourceParser() { + @Override + public ValueSource parse(FunctionQParser fp) throws ParseException { + List sources = fp.parseValueSourceList(); + return new MultiBoolFunction(sources) { + @Override + protected String name() { + return "xor"; + } + @Override + protected boolean func(int doc, DocValues[] vals) { + int nTrue=0, nFalse=0; + for (DocValues 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 ParseException { + ValueSource ifValueSource = fp.parseValueSource(); + ValueSource trueValueSource = fp.parseValueSource(); + ValueSource falseValueSource = fp.parseValueSource(); + + return new IfFunction(ifValueSource, trueValueSource, falseValueSource); + } + }); + + addParser("def", new ValueSourceParser() { + @Override + public ValueSource parse(FunctionQParser fp) throws ParseException { + return new DefFunction(fp.parseValueSourceList()); + } + }); + } private static TInfo parseTerm(FunctionQParser fp) throws ParseException { @@ -857,6 +985,11 @@ class LongConstValueSource extends ConstNumberSource { public Number getNumber() { return constant; } + + @Override + public boolean getBool() { + return constant != 0; + } } @@ -981,3 +1114,69 @@ abstract class Double2Parser extends NamedParser { } } + + +class BoolConstValueSource extends ConstNumberSource { + final boolean constant; + + public BoolConstValueSource(boolean constant) { + this.constant = constant; + } + + @Override + public String description() { + return "const(" + constant + ")"; + } + + @Override + public DocValues getValues(Map context, AtomicReaderContext 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; + } +} diff --git a/solr/src/java/org/apache/solr/search/function/BoolDocValues.java b/solr/src/java/org/apache/solr/search/function/BoolDocValues.java new file mode 100644 index 00000000000..443f379ab95 --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/BoolDocValues.java @@ -0,0 +1,79 @@ +package org.apache.solr.search.function; + +import org.apache.solr.search.MutableValue; +import org.apache.solr.search.MutableValueBool; +import org.apache.solr.search.MutableValueInt; + +public abstract class BoolDocValues extends DocValues { + protected final ValueSource vs; + + public BoolDocValues(ValueSource vs) { + this.vs = vs; + } + + @Override + public abstract boolean boolVal(int doc); + + @Override + public byte byteVal(int doc) { + return boolVal(doc) ? (byte)1 : (byte)0; + } + + @Override + public short shortVal(int doc) { + return boolVal(doc) ? (short)1 : (short)0; + } + + @Override + public float floatVal(int doc) { + return boolVal(doc) ? (float)1 : (float)0; + } + + @Override + public int intVal(int doc) { + return boolVal(doc) ? 1 : 0; + } + + @Override + public long longVal(int doc) { + return boolVal(doc) ? (long)1 : (long)0; + } + + @Override + public double doubleVal(int doc) { + return boolVal(doc) ? (double)1 : (double)0; + } + + @Override + public String strVal(int doc) { + return Boolean.toString(boolVal(doc)); + } + + @Override + public Object objectVal(int doc) { + return exists(doc) ? boolVal(doc) : null; + } + + @Override + public String toString(int doc) { + return vs.description() + '=' + strVal(doc); + } + + @Override + public ValueFiller getValueFiller() { + return new ValueFiller() { + private final MutableValueBool mval = new MutableValueBool(); + + @Override + public MutableValue getValue() { + return mval; + } + + @Override + public void fillValue(int doc) { + mval.value = boolVal(doc); + mval.exists = exists(doc); + } + }; + } +} diff --git a/solr/src/java/org/apache/solr/search/function/BoolFunction.java b/solr/src/java/org/apache/solr/search/function/BoolFunction.java new file mode 100644 index 00000000000..b7898d15184 --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/BoolFunction.java @@ -0,0 +1,23 @@ +/** + * 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.function; + + +public abstract class BoolFunction extends ValueSource { + // TODO: placeholder to return type, among other common future functionality +} diff --git a/solr/src/java/org/apache/solr/search/function/ConstNumberSource.java b/solr/src/java/org/apache/solr/search/function/ConstNumberSource.java index da9ef1f051a..fac0611cc71 100755 --- a/solr/src/java/org/apache/solr/search/function/ConstNumberSource.java +++ b/solr/src/java/org/apache/solr/search/function/ConstNumberSource.java @@ -26,4 +26,5 @@ public abstract class ConstNumberSource extends ValueSource { public abstract float getFloat(); public abstract double getDouble(); public abstract Number getNumber(); + public abstract boolean getBool(); } diff --git a/solr/src/java/org/apache/solr/search/function/ConstValueSource.java b/solr/src/java/org/apache/solr/search/function/ConstValueSource.java index ad495a18007..fc0b9334427 100755 --- a/solr/src/java/org/apache/solr/search/function/ConstValueSource.java +++ b/solr/src/java/org/apache/solr/search/function/ConstValueSource.java @@ -66,6 +66,10 @@ public class ConstValueSource extends ConstNumberSource { public Object objectVal(int doc) { return constant; } + @Override + public boolean boolVal(int doc) { + return constant != 0.0f; + } }; } @@ -105,4 +109,9 @@ public class ConstValueSource extends ConstNumberSource { public Number getNumber() { return constant; } + + @Override + public boolean getBool() { + return constant != 0.0f; + } } diff --git a/solr/src/java/org/apache/solr/search/function/DefFunction.java b/solr/src/java/org/apache/solr/search/function/DefFunction.java new file mode 100644 index 00000000000..b2f99a3c6fa --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/DefFunction.java @@ -0,0 +1,124 @@ +package org.apache.solr.search.function; +/** + * 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. + */ + +import org.apache.lucene.index.IndexReader.AtomicReaderContext; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.util.BytesRef; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class DefFunction extends MultiFunction { + public DefFunction(List sources) { + super(sources); + } + + @Override + protected String name() { + return "def"; + } + + + @Override + public DocValues getValues(Map fcontext, AtomicReaderContext readerContext) throws IOException { + + + return new Values(valsArr(sources, fcontext, readerContext)) { + final int upto = valsArr.length - 1; + + private DocValues get(int doc) { + for (int i=0; i { final double[] arr = vals.values; final Bits valid = vals.valid; - return new DocValues() { - @Override - public float floatVal(int doc) { - return (float) arr[doc]; - } - - @Override - public int intVal(int doc) { - return (int) arr[doc]; - } - - @Override - public long longVal(int doc) { - return (long) arr[doc]; - } - + return new DoubleDocValues(this) { @Override public double doubleVal(int doc) { return arr[doc]; } @Override - public String strVal(int doc) { - return Double.toString(arr[doc]); - } - - @Override - public Object objectVal(int doc) { - return valid.get(doc) ? arr[doc] : null; - } - - @Override - public String toString(int doc) { - return description() + '=' + doubleVal(doc); + public boolean exists(int doc) { + return valid.get(doc); } @Override @@ -147,7 +122,7 @@ public class DoubleFieldSource extends NumericFieldCacheSource { } } - @Override + @Override public ValueFiller getValueFiller() { return new ValueFiller() { private final double[] doubleArr = arr; diff --git a/solr/src/java/org/apache/solr/search/function/IfFunction.java b/solr/src/java/org/apache/solr/search/function/IfFunction.java new file mode 100644 index 00000000000..00ad2f437d6 --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/IfFunction.java @@ -0,0 +1,148 @@ +/** + * 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.function; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexReader.AtomicReaderContext; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.util.BytesRef; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + + +public class IfFunction extends BoolFunction { + private ValueSource ifSource; + private ValueSource trueSource; + private ValueSource falseSource; + + + public IfFunction(ValueSource ifSource, ValueSource trueSource, ValueSource falseSource) { + this.ifSource = ifSource; + this.trueSource = trueSource; + this.falseSource = falseSource; + } + + @Override + public DocValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { + final DocValues ifVals = ifSource.getValues(context, readerContext); + final DocValues trueVals = trueSource.getValues(context, readerContext); + final DocValues falseVals = falseSource.getValues(context, readerContext); + + return new DocValues() { + @Override + public byte byteVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.byteVal(doc) : falseVals.byteVal(doc); + } + + @Override + public short shortVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.shortVal(doc) : falseVals.shortVal(doc); + } + + @Override + public float floatVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.floatVal(doc) : falseVals.floatVal(doc); + } + + @Override + public int intVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.intVal(doc) : falseVals.intVal(doc); + } + + @Override + public long longVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.longVal(doc) : falseVals.longVal(doc); + } + + @Override + public double doubleVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.doubleVal(doc) : falseVals.doubleVal(doc); + } + + @Override + public String strVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.strVal(doc) : falseVals.strVal(doc); + } + + @Override + public boolean boolVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.boolVal(doc) : falseVals.boolVal(doc); + } + + @Override + public boolean bytesVal(int doc, BytesRef target) { + return ifVals.boolVal(doc) ? trueVals.bytesVal(doc, target) : falseVals.bytesVal(doc, target); + } + + @Override + public Object objectVal(int doc) { + return ifVals.boolVal(doc) ? trueVals.objectVal(doc) : falseVals.objectVal(doc); + } + + @Override + public boolean exists(int doc) { + return true; // TODO: flow through to any sub-sources? + } + + @Override + public ValueFiller getValueFiller() { + // TODO: we need types of trueSource / falseSource to handle this + // for now, use float. + return super.getValueFiller(); + } + + @Override + public String toString(int doc) { + return "if(" + ifVals.toString(doc) + ',' + trueVals.toString(doc) + ',' + falseVals.toString(doc) + ')'; + } + }; + + } + + @Override + public String description() { + return "if(" + ifSource.description() + ',' + trueSource.description() + ',' + falseSource + ')'; + } + + @Override + public int hashCode() { + int h = ifSource.hashCode(); + h = h * 31 + trueSource.hashCode(); + h = h * 31 + falseSource.hashCode(); + return h; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof IfFunction)) return false; + IfFunction other = (IfFunction)o; + return ifSource.equals(other.ifSource) + && trueSource.equals(other.trueSource) + && falseSource.equals(other.falseSource); + } + + @Override + public void createWeight(Map context, IndexSearcher searcher) throws IOException { + ifSource.createWeight(context, searcher); + trueSource.createWeight(context, searcher); + falseSource.createWeight(context, searcher); + } +} \ No newline at end of file diff --git a/solr/src/java/org/apache/solr/search/function/LongDocValues.java b/solr/src/java/org/apache/solr/search/function/LongDocValues.java index f5117bd0d43..f0e8f6d8ee9 100644 --- a/solr/src/java/org/apache/solr/search/function/LongDocValues.java +++ b/solr/src/java/org/apache/solr/search/function/LongDocValues.java @@ -38,6 +38,11 @@ public abstract class LongDocValues extends DocValues { return (double)longVal(doc); } + @Override + public boolean boolVal(int doc) { + return longVal(doc) != 0; + } + @Override public String strVal(int doc) { return Long.toString(longVal(doc)); diff --git a/solr/src/java/org/apache/solr/search/function/MultiBoolFunction.java b/solr/src/java/org/apache/solr/search/function/MultiBoolFunction.java new file mode 100644 index 00000000000..033ef6ebae9 --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/MultiBoolFunction.java @@ -0,0 +1,105 @@ +/** + * 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.function; + +import org.apache.lucene.index.IndexReader.AtomicReaderContext; +import org.apache.lucene.search.IndexSearcher; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + + +public abstract class MultiBoolFunction extends BoolFunction { + protected final List sources; + + public MultiBoolFunction(List sources) { + this.sources = sources; + } + + protected abstract String name(); + + protected abstract boolean func(int doc, DocValues[] vals); + + @Override + public BoolDocValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { + final DocValues[] vals = new DocValues[sources.size()]; + int i=0; + for (ValueSource source : sources) { + vals[i++] = source.getValues(context, readerContext); + } + + return new BoolDocValues(this) { + @Override + public boolean boolVal(int doc) { + return func(doc, vals); + } + + @Override + public String toString(int doc) { + StringBuilder sb = new StringBuilder(name()); + sb.append('('); + boolean first = true; + for (DocValues dv : vals) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(dv.toString(doc)); + } + return sb.toString(); + } + }; + } + + @Override + public String description() { + StringBuilder sb = new StringBuilder(name()); + sb.append('('); + boolean first = true; + for (ValueSource source : sources) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(source.description()); + } + return sb.toString(); + } + + @Override + public int hashCode() { + return sources.hashCode() + name().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this.getClass() != o.getClass()) return false; + MultiBoolFunction other = (MultiBoolFunction)o; + return this.sources.equals(other.sources); + } + + @Override + public void createWeight(Map context, IndexSearcher searcher) throws IOException { + for (ValueSource source : sources) { + source.createWeight(context, searcher); + } + } +} \ No newline at end of file diff --git a/solr/src/java/org/apache/solr/search/function/MultiFunction.java b/solr/src/java/org/apache/solr/search/function/MultiFunction.java new file mode 100644 index 00000000000..941b3415ba7 --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/MultiFunction.java @@ -0,0 +1,122 @@ +package org.apache.solr.search.function; +/** + * 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. + */ + +import org.apache.lucene.index.IndexReader.AtomicReaderContext; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.util.BytesRef; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + + +public abstract class MultiFunction extends ValueSource { + protected final List sources; + + public MultiFunction(List sources) { + this.sources = sources; + } + + abstract protected String name(); + + @Override + public String description() { + return description(name(), sources); + } + + public static String description(String name, List sources) { + StringBuilder sb = new StringBuilder(); + sb.append(name).append('('); + boolean firstTime=true; + for (ValueSource source : sources) { + if (firstTime) { + firstTime=false; + } else { + sb.append(','); + } + sb.append(source); + } + sb.append(')'); + return sb.toString(); + } + + public static DocValues[] valsArr(List sources, Map fcontext, AtomicReaderContext readerContext) throws IOException { + final DocValues[] valsArr = new DocValues[sources.size()]; + int i=0; + for (ValueSource source : sources) { + valsArr[i++] = source.getValues(fcontext, readerContext); + } + return valsArr; + } + + public class Values extends DocValues { + final DocValues[] valsArr; + + public Values(DocValues[] valsArr) { + this.valsArr = valsArr; + } + + @Override + public String toString(int doc) { + return MultiFunction.toString(name(), valsArr, doc); + } + + @Override + public ValueFiller getValueFiller() { + // TODO: need ValueSource.type() to determine correct type + return super.getValueFiller(); + } + } + + + public static String toString(String name, DocValues[] valsArr, int doc) { + StringBuilder sb = new StringBuilder(); + sb.append(name).append('('); + boolean firstTime=true; + for (DocValues vals : valsArr) { + if (firstTime) { + firstTime=false; + } else { + sb.append(','); + } + sb.append(vals.toString(doc)); + } + sb.append(')'); + return sb.toString(); + } + + @Override + public void createWeight(Map context, IndexSearcher searcher) throws IOException { + for (ValueSource source : sources) + source.createWeight(context, searcher); + } + + @Override + public int hashCode() { + return sources.hashCode() + name().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this.getClass() != o.getClass()) return false; + MultiFunction other = (MultiFunction)o; + return this.sources.equals(other.sources); + } +} + diff --git a/solr/src/java/org/apache/solr/search/function/SimpleBoolFunction.java b/solr/src/java/org/apache/solr/search/function/SimpleBoolFunction.java new file mode 100644 index 00000000000..6a4da8b229d --- /dev/null +++ b/solr/src/java/org/apache/solr/search/function/SimpleBoolFunction.java @@ -0,0 +1,74 @@ +/** + * 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.function; + +import org.apache.lucene.index.IndexReader.AtomicReaderContext; +import org.apache.lucene.search.IndexSearcher; + +import java.io.IOException; +import java.util.Map; + + +public abstract class SimpleBoolFunction extends BoolFunction { + protected final ValueSource source; + + public SimpleBoolFunction(ValueSource source) { + this.source = source; + } + + protected abstract String name(); + + protected abstract boolean func(int doc, DocValues vals); + + @Override + public BoolDocValues getValues(Map context, AtomicReaderContext readerContext) throws IOException { + final DocValues vals = source.getValues(context, readerContext); + return new BoolDocValues(this) { + @Override + public boolean boolVal(int doc) { + return func(doc, vals); + } + @Override + public String toString(int doc) { + return name() + '(' + vals.toString(doc) + ')'; + } + }; + } + + @Override + public String description() { + return name() + '(' + source.description() + ')'; + } + + @Override + public int hashCode() { + return source.hashCode() + name().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this.getClass() != o.getClass()) return false; + SingleFunction other = (SingleFunction)o; + return this.source.equals(other.source); + } + + @Override + public void createWeight(Map context, IndexSearcher searcher) throws IOException { + source.createWeight(context, searcher); + } +} \ No newline at end of file diff --git a/solr/src/java/org/apache/solr/search/function/StrDocValues.java b/solr/src/java/org/apache/solr/search/function/StrDocValues.java index e4c28da47eb..5726824388c 100644 --- a/solr/src/java/org/apache/solr/search/function/StrDocValues.java +++ b/solr/src/java/org/apache/solr/search/function/StrDocValues.java @@ -21,6 +21,11 @@ public abstract class StrDocValues extends DocValues { return exists(doc) ? strVal(doc) : null; } + @Override + public boolean boolVal(int doc) { + return exists(doc); + } + @Override public String toString(int doc) { return vs.description() + "='" + strVal(doc) + "'"; diff --git a/solr/src/java/org/apache/solr/search/function/StringIndexDocValues.java b/solr/src/java/org/apache/solr/search/function/StringIndexDocValues.java index 95d7d0cd823..71db0ab36d0 100755 --- a/solr/src/java/org/apache/solr/search/function/StringIndexDocValues.java +++ b/solr/src/java/org/apache/solr/search/function/StringIndexDocValues.java @@ -78,6 +78,10 @@ public abstract class StringIndexDocValues extends DocValues { return spareChars.toString(); } + @Override + public boolean boolVal(int doc) { + return exists(doc); + } @Override public abstract Object objectVal(int doc); // force subclasses to override diff --git a/solr/src/test/org/apache/solr/search/TestQueryTypes.java b/solr/src/test/org/apache/solr/search/TestQueryTypes.java index ca49dd72b3f..efd6c68f547 100755 --- a/solr/src/test/org/apache/solr/search/TestQueryTypes.java +++ b/solr/src/test/org/apache/solr/search/TestQueryTypes.java @@ -119,7 +119,29 @@ public class TestQueryTypes extends AbstractSolrTestCase { assertQ(req( "q", "{!frange v="+f+" l='"+v+"' u='"+v+"'}" ) ,"//result[@numFound='1']" ); - + + // exists() + assertQ(req( "fq","id:999", "q", "{!frange l=1 u=1}if(exists("+f+"),1,0)" ) + ,"//result[@numFound='1']" + ); + + // boolean value of non-zero values (just leave off the exists from the prev test) + assertQ(req( "fq","id:999", "q", "{!frange l=1 u=1}if("+f+",1,0)" ) + ,"//result[@numFound='1']" + ); + + if (!"id".equals(f)) { + assertQ(req( "fq","id:1", "q", "{!frange l=1 u=1}if(exists("+f+"),1,0)" ) + ,"//result[@numFound='0']" + ); + + // boolean value of zero/missing values (just leave off the exists from the prev test) + assertQ(req( "fq","id:1", "q", "{!frange l=1 u=1}if("+f+",1,0)" ) + ,"//result[@numFound='0']" + ); + + } + // function query... just make sure it doesn't throw an exception if ("v_s".equals(f)) continue; // in this context, functions must be able to be interpreted as a float assertQ(req( "q", "+id:999 _val_:\"" + f + "\"") diff --git a/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java b/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java index 1bf6dd8edfc..4648b424126 100755 --- a/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java +++ b/solr/src/test/org/apache/solr/search/function/TestFunctionQuery.java @@ -581,4 +581,56 @@ public class TestFunctionQuery extends SolrTestCaseJ4 { purgeFieldCache(FieldCache.DEFAULT); // avoid FC insanity } + @Test + public void testBooleanFunctions() throws Exception { + assertU(adoc("id", "1", "text", "hello", "foo_s","A", "foo_ti", "0", "foo_tl","0")); + assertU(adoc("id", "2" , "foo_ti","10", "foo_tl","11")); + assertU(commit()); + + // true and false functions and constants + assertJQ(req("q", "id:1", "fl", "t:true(),f:false(),tt:{!func}true,ff:{!func}false") + , "/response/docs/[0]=={'t':true,'f':false,'tt':true,'ff':false}"); + + // test that exists(query) depends on the query matching the document + assertJQ(req("q", "id:1", "fl", "t:exists(query($q1)),f:exists(query($q2))", "q1","text:hello", "q2","text:there") + , "/response/docs/[0]=={'t':true,'f':false}"); + + // test if() + assertJQ(req("q", "id:1", "fl", "a1:if(true,'A','B')", "fl","b1:if(false,'A','B')") + , "/response/docs/[0]=={'a1':'A', 'b1':'B'}"); + + // test boolean operators + assertJQ(req("q", "id:1", "fl", "t1:and(true,true)", "fl","f1:and(true,false)", "fl","f2:and(false,true)", "fl","f3:and(false,false)") + , "/response/docs/[0]=={'t1':true, 'f1':false, 'f2':false, 'f3':false}"); + assertJQ(req("q", "id:1", "fl", "t1:or(true,true)", "fl","t2:or(true,false)", "fl","t3:or(false,true)", "fl","f1:or(false,false)") + , "/response/docs/[0]=={'t1':true, 't2':true, 't3':true, 'f1':false}"); + assertJQ(req("q", "id:1", "fl", "f1:xor(true,true)", "fl","t1:xor(true,false)", "fl","t2:xor(false,true)", "fl","f2:xor(false,false)") + , "/response/docs/[0]=={'t1':true, 't2':true, 'f1':false, 'f2':false}"); + assertJQ(req("q", "id:1", "fl", "t:not(false),f:not(true)") + , "/response/docs/[0]=={'t':true, 'f':false}"); + + + // def(), the default function that returns the first value that exists + assertJQ(req("q", "id:1", "fl", "x:def(id,123.0), y:def(foo_f,234.0)") + , "/response/docs/[0]=={'x':1.0, 'y':234.0}"); + assertJQ(req("q", "id:1", "fl", "x:def(foo_s,'Q'), y:def(missing_s,'W')") + , "/response/docs/[0]=={'x':'A', 'y':'W'}"); + + // test constant conversion to boolean + assertJQ(req("q", "id:1", "fl", "a:not(0), b:not(1), c:not(0.0), d:not(1.1), e:not('A')") + , "/response/docs/[0]=={'a':true, 'b':false, 'c':true, 'd':false, 'e':false}"); + + } + + + @Test + public void testPseudoFieldFunctions() throws Exception { + assertU(adoc("id", "1", "text", "hello", "foo_s","A")); + assertU(adoc("id", "2")); + assertU(commit()); + + assertJQ(req("q", "id:1", "fl", "a:1,b:2.0,c:'X',d:{!func}foo_s,e:{!func}bar_s") // if exists() is false, no pseudo-field should be added + , "/response/docs/[0]=={'a':1, 'b':2.0,'c':'X','d':'A'}"); + } + }