diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java new file mode 100644 index 0000000..42855f0 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValues.java @@ -0,0 +1,30 @@ +/* + * 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.lucene.search; + +/** + * Per-segment, per-document double values, which can be calculated at search-time + */ +public abstract class DoubleValues { + + /** + * Get the double value for a document + */ + public abstract double get(int doc); + +} diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java new file mode 100644 index 0000000..3ca76b8 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java @@ -0,0 +1,206 @@ +/* + * 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.lucene.search; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.LongToDoubleFunction; + +import org.apache.lucene.index.DocValues; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.NumericDocValues; + +/** + * Base class for producing {@link DoubleValues} + */ +public abstract class DoubleValuesSource { + + /** + * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext + */ + public abstract DoubleValues getValues(LeafReaderContext ctx, Scorer scorer) throws IOException; + + /** + * Create a sort field based on the value of this producer + * @param reverse true if the sort should be decreasing + */ + public SortField getSortField(boolean reverse) { + return new DoubleValuesSortField(this, reverse); + } + + /** + * @return true if the {@link DoubleValues} created by this producer needs a Scorer + */ + public boolean needsScores() { + return false; + } + + /** + * Creates a DoubleValuesSource that wraps a double-valued field + */ + public static DoubleValuesSource fromDoubleField(String field) { + return fromDoubleField(field, Double::longBitsToDouble); + } + + /** + * A DoubleValuesSource that returns the score for each document + */ + public static final DoubleValuesSource SCORES = new DoubleValuesSource() { + + @Override + public DoubleValues getValues(LeafReaderContext ctx, Scorer scorer) throws IOException { + return new DoubleValues() { + @Override + public double get(int docID) { + assert scorer != null; + assert scorer.docID() == docID; + try { + return scorer.score(); + } catch (IOException e) { + throw new RuntimeException(e); // nocommit + } + } + }; + } + + @Override + public boolean needsScores() { + return true; + } + + }; + + public static DoubleValuesSource fromFloatField(String field) { + return fromDoubleField(field, (v) -> (double)Float.intBitsToFloat((int)v)); + } + + /** + * Expose a NumericDocValuesField as doubles + */ + public static DoubleValuesSource fromDoubleField(String field, LongToDoubleFunction decoder) { + return new FieldDoubleValuesSource(field, decoder); + } + + private static class FieldDoubleValuesSource extends DoubleValuesSource { + + final String field; + final LongToDoubleFunction decoder; + + private FieldDoubleValuesSource(String field, LongToDoubleFunction decoder) { + this.field = field; + this.decoder = decoder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldDoubleValuesSource that = (FieldDoubleValuesSource) o; + return Objects.equals(field, that.field) && + Objects.equals(decoder, that.decoder); + } + + @Override + public int hashCode() { + return Objects.hash(field, decoder); + } + + @Override + public DoubleValues getValues(LeafReaderContext ctx, Scorer scorer) throws IOException { + final NumericDocValues values = DocValues.getNumeric(ctx.reader(), field); + return new DoubleValues() { + @Override + public double get(int docID) { + long input = values.get(docID); + double output = decoder.applyAsDouble(input); + //System.out.println("Long val: " + input + " Double val: " + output); + return output; + } + }; + } + } + + public static DoubleValuesSource fromLongField(String field) { + return fromDoubleField(field, (v) -> (double)(long)v); + } + + private static class DoubleValuesSortField extends SortField { + + final DoubleValuesSource producer; + + public DoubleValuesSortField(DoubleValuesSource producer, boolean reverse) { + super(producer.toString(), new DoubleValuesComparatorSource(producer), reverse); + this.producer = producer; + } + + @Override + public boolean needsScores() { + return producer.needsScores(); + } + + } + + private static class ValuesHolder { + DoubleValues values; + } + + private static class DoubleValuesComparatorSource extends FieldComparatorSource { + private final DoubleValuesSource producer; + + public DoubleValuesComparatorSource(DoubleValuesSource producer) { + this.producer = producer; + } + + @Override + public String toString() { + return "double"; + } + + @Override + public FieldComparator newComparator(String fieldname, int numHits, + int sortPos, boolean reversed) throws IOException { + return new FieldComparator.DoubleComparator(numHits, fieldname, 0.0){ + + ValuesHolder holder = new ValuesHolder(); + LeafReaderContext ctx; + + @Override + protected NumericDocValues getNumericDocValues(LeafReaderContext context, String field) throws IOException { + this.ctx = context; + return new NumericDocValues() { + @Override + public long get(int docID) { + return Double.doubleToLongBits(holder.values.get(docID)); + } + }; + } + + @Override + public void setScorer(Scorer scorer) { + try { + holder.values = producer.getValues(ctx, scorer); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + } + } + +} diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDoubleDocValues.java b/lucene/core/src/test/org/apache/lucene/search/TestDoubleDocValues.java new file mode 100644 index 0000000..9532580 --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/search/TestDoubleDocValues.java @@ -0,0 +1,145 @@ +package org.apache.lucene.search; + +/* + * 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 java.util.Arrays; +import java.util.Collections; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.DoubleDocValuesField; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FloatDocValuesField; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.English; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.TestUtil; +import org.junit.Test; + +public class TestDoubleDocValues extends LuceneTestCase { + + private Directory dir; + private IndexReader reader; + private IndexSearcher searcher; + + @Override + public void setUp() throws Exception { + super.setUp(); + dir = newDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir); + int numDocs = TestUtil.nextInt(random(), 2049, 4000); + for (int i = 0; i < numDocs; i++) { + Document document = new Document(); + document.add(newTextField("english", English.intToEnglish(i), Field.Store.NO)); + document.add(newTextField("oddeven", (i % 2 == 0) ? "even" : "odd", Field.Store.NO)); + document.add(new NumericDocValuesField("int", random().nextInt())); + document.add(new NumericDocValuesField("long", random().nextLong())); + document.add(new FloatDocValuesField("float", random().nextFloat())); + document.add(new DoubleDocValuesField("double", random().nextDouble())); + iw.addDocument(document); + } + reader = iw.getReader(); + iw.close(); + searcher = newSearcher(reader); + } + + @Override + public void tearDown() throws Exception { + reader.close(); + dir.close(); + super.tearDown(); + } + + @Test + public void testSimpleFieldSortables() throws Exception { + int n = atLeast(4); + for (int i = 0; i < n; i++) { + Sort sort = randomSort(); + checkSorts(new MatchAllDocsQuery(), sort); + checkSorts(new TermQuery(new Term("english", "one")), sort); + BooleanQuery.Builder bq = new BooleanQuery.Builder(); + bq.add(new TermQuery(new Term("english", "one")), BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("oddeven", "even")), BooleanClause.Occur.SHOULD); + checkSorts(bq.build(), sort); + // force in order + bq.add(new TermQuery(new Term("english", "two")), BooleanClause.Occur.SHOULD); + bq.setMinimumNumberShouldMatch(2); + checkSorts(bq.build(), sort); + } + } + + Sort randomSort() throws Exception { + boolean reversed = random().nextBoolean(); + SortField fields[] = new SortField[] { + new SortField("int", SortField.Type.INT, reversed), + new SortField("long", SortField.Type.LONG, reversed), + new SortField("float", SortField.Type.FLOAT, reversed), + new SortField("double", SortField.Type.DOUBLE, reversed), + new SortField("score", SortField.Type.SCORE) + }; + Collections.shuffle(Arrays.asList(fields), random()); + int numSorts = TestUtil.nextInt(random(), 1, fields.length); + return new Sort(Arrays.copyOfRange(fields, 0, numSorts)); + } + + // Take a Sort, and replace any field sorts with Sortables + Sort convertSortToSortable(Sort sort) { + SortField original[] = sort.getSort(); + SortField mutated[] = new SortField[original.length]; + for (int i = 0; i < mutated.length; i++) { + if (random().nextInt(3) > 0) { + SortField s = original[i]; + boolean reverse = s.getType() == SortField.Type.SCORE || s.getReverse(); + switch (s.getType()) { + case INT: + case LONG: + mutated[i] = DoubleValuesSource.fromLongField(s.getField()).getSortField(reverse); + break; + case FLOAT: + case DOUBLE: + mutated[i] = DoubleValuesSource.fromDoubleField(s.getField()).getSortField(reverse); + break; + default: + mutated[i] = original[i]; + } + } else { + mutated[i] = original[i]; + } + } + + return new Sort(mutated); + } + + void checkSorts(Query query, Sort sort) throws Exception { + int size = TestUtil.nextInt(random(), 1, searcher.getIndexReader().maxDoc() / 5); + TopDocs expected = searcher.search(query, size, sort, random().nextBoolean(), random().nextBoolean()); + Sort mutatedSort = convertSortToSortable(sort); + TopDocs actual = searcher.search(query, size, mutatedSort, random().nextBoolean(), random().nextBoolean()); + + CheckHits.checkEqual(query, expected.scoreDocs, actual.scoreDocs); + + if (size < actual.totalHits) { + expected = searcher.searchAfter(expected.scoreDocs[size-1], query, size, sort); + actual = searcher.searchAfter(actual.scoreDocs[size-1], query, size, mutatedSort); + CheckHits.checkEqual(query, expected.scoreDocs, actual.scoreDocs); + } + } +} diff --git a/lucene/expressions/build.xml b/lucene/expressions/build.xml index 1dddc6d..61ae64f 100644 --- a/lucene/expressions/build.xml +++ b/lucene/expressions/build.xml @@ -26,7 +26,6 @@ - @@ -35,16 +34,6 @@ - - - - - - - - - - diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java b/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java index 5ec2edb..809e6ba 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/Bindings.java @@ -16,7 +16,7 @@ */ package org.apache.lucene.expressions; -import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.search.DoubleValuesSource; /** * Binds variable names in expressions to actual data. @@ -35,10 +35,6 @@ public abstract class Bindings { /** * Returns a ValueSource bound to the variable name. */ - public abstract ValueSource getValueSource(String name); - - /** Returns a {@code ValueSource} over relevance scores */ - protected final ValueSource getScoreValueSource() { - return new ScoreValueSource(); - } + public abstract DoubleValuesSource getValueSource(String name); + } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java b/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java index 02be23b..04aafd7 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/Expression.java @@ -16,9 +16,9 @@ */ package org.apache.lucene.expressions; -import org.apache.lucene.expressions.js.JavascriptCompiler; // javadocs -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.expressions.js.JavascriptCompiler; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.Rescorer; import org.apache.lucene.search.SortField; @@ -66,17 +66,17 @@ public abstract class Expression { * Evaluates the expression for the given document. * * @param document docId of the document to compute a value for - * @param functionValues {@link FunctionValues} for each element of {@link #variables}. + * @param functionValues {@link DoubleValues} for each element of {@link #variables}. * @return The computed value of the expression for the given document. */ - public abstract double evaluate(int document, FunctionValues[] functionValues); + public abstract double evaluate(int document, DoubleValues[] functionValues); /** * Get a value source which can compute the value of this expression in the context of the given bindings. * @param bindings Bindings to use for external values in this expression * @return A value source which will evaluate this expression when used */ - public ValueSource getValueSource(Bindings bindings) { + public DoubleValuesSource getValueSource(Bindings bindings) { return new ExpressionValueSource(bindings, this); } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java deleted file mode 100644 index eabf6dd..0000000 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionComparator.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.lucene.expressions; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.search.FieldComparator; -import org.apache.lucene.search.LeafFieldComparator; -import org.apache.lucene.search.Scorer; - -/** A custom comparator for sorting documents by an expression */ -class ExpressionComparator extends FieldComparator implements LeafFieldComparator { - private final double[] values; - private double bottom; - private double topValue; - - private ValueSource source; - private FunctionValues scores; - private LeafReaderContext readerContext; - - public ExpressionComparator(ValueSource source, int numHits) { - values = new double[numHits]; - this.source = source; - } - - // TODO: change FieldComparator.setScorer to throw IOException and remove this try-catch - @Override - public void setScorer(Scorer scorer) { - // TODO: might be cleaner to lazy-init 'source' and set scorer after? - assert readerContext != null; - try { - Map context = new HashMap<>(); - assert scorer != null; - context.put("scorer", scorer); - scores = source.getValues(context, readerContext); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public int compare(int slot1, int slot2) { - return Double.compare(values[slot1], values[slot2]); - } - - @Override - public void setBottom(int slot) { - bottom = values[slot]; - } - - @Override - public void setTopValue(Double value) { - topValue = value.doubleValue(); - } - - @Override - public int compareBottom(int doc) throws IOException { - return Double.compare(bottom, scores.doubleVal(doc)); - } - - @Override - public void copy(int slot, int doc) throws IOException { - values[slot] = scores.doubleVal(doc); - } - - @Override - public LeafFieldComparator getLeafComparator(LeafReaderContext context) throws IOException { - this.readerContext = context; - return this; - } - - @Override - public Double value(int slot) { - return Double.valueOf(values[slot]); - } - - @Override - public int compareTop(int doc) throws IOException { - return Double.compare(topValue, scores.doubleVal(doc)); - } -} diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java index ad195cd..3cfbe22 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionFunctionValues.java @@ -16,20 +16,17 @@ */ package org.apache.lucene.expressions; -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.queries.function.docvalues.DoubleDocValues; +import org.apache.lucene.search.DoubleValues; -/** A {@link FunctionValues} which evaluates an expression */ -class ExpressionFunctionValues extends DoubleDocValues { +/** A {@link DoubleValues} which evaluates an expression */ +class ExpressionFunctionValues extends DoubleValues { final Expression expression; - final FunctionValues[] functionValues; + final DoubleValues[] functionValues; int currentDocument = -1; double currentValue; - ExpressionFunctionValues(ValueSource parent, Expression expression, FunctionValues[] functionValues) { - super(parent); + ExpressionFunctionValues(Expression expression, DoubleValues[] functionValues) { if (expression == null) { throw new NullPointerException(); } @@ -39,9 +36,9 @@ class ExpressionFunctionValues extends DoubleDocValues { this.expression = expression; this.functionValues = functionValues; } - + @Override - public double doubleVal(int document) { + public double get(int document) { if (currentDocument != document) { currentDocument = document; currentValue = expression.evaluate(document, functionValues); @@ -49,4 +46,5 @@ class ExpressionFunctionValues extends DoubleDocValues { return currentValue; } + } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionRescorer.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionRescorer.java index 33e8428..ee81a59 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionRescorer.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionRescorer.java @@ -20,13 +20,12 @@ package org.apache.lucene.expressions; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.ReaderUtil; -import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Rescorer; @@ -49,7 +48,7 @@ class ExpressionRescorer extends SortRescorer { private final Expression expression; private final Bindings bindings; - /** Uses the provided {@link ValueSource} to assign second + /** Uses the provided {@link DoubleValuesSource} to assign second * pass scores. */ public ExpressionRescorer(Expression expression, Bindings bindings) { super(new Sort(expression.getSortField(bindings, true))); @@ -65,17 +64,15 @@ class ExpressionRescorer extends SortRescorer { int subReader = ReaderUtil.subIndex(docID, leaves); LeafReaderContext readerContext = leaves.get(subReader); int docIDInSegment = docID - readerContext.docBase; - Map context = new HashMap<>(); FakeScorer fakeScorer = new FakeScorer(); fakeScorer.score = firstPassExplanation.getValue(); fakeScorer.doc = docIDInSegment; - context.put("scorer", fakeScorer); - List subs = new ArrayList<>(Arrays.asList(superExpl.getDetails())); for(String variable : expression.variables) { - subs.add(Explanation.match((float) bindings.getValueSource(variable).getValues(context, readerContext).doubleVal(docIDInSegment), + DoubleValues values = bindings.getValueSource(variable).getValues(readerContext, fakeScorer); + subs.add(Explanation.match((float) values.get(docIDInSegment), "variable \"" + variable + "\"")); } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java deleted file mode 100644 index 2b39834..0000000 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionSortField.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.lucene.expressions; - -import java.io.IOException; - -import org.apache.lucene.search.FieldComparator; -import org.apache.lucene.search.SortField; - -/** A {@link SortField} which sorts documents by the evaluated value of an expression for each document */ -class ExpressionSortField extends SortField { - private final ExpressionValueSource source; - - ExpressionSortField(String name, ExpressionValueSource source, boolean reverse) { - super(name, Type.CUSTOM, reverse); - this.source = source; - } - - @Override - public FieldComparator getComparator(final int numHits, final int sortPos) throws IOException { - return new ExpressionComparator(source, numHits); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((source == null) ? 0 : source.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - ExpressionSortField other = (ExpressionSortField) obj; - if (source == null) { - if (other.source != null) return false; - } else if (!source.equals(other.source)) return false; - return true; - } - - @Override - public String toString() { - StringBuilder buffer = new StringBuilder(); - - buffer.append(""); - - if (getReverse()) { - buffer.append('!'); - } - - return buffer.toString(); - } - - @Override - public boolean needsScores() { - return source.needsScores(); - } -} \ No newline at end of file diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java index fcba455..8bf6498 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java @@ -20,18 +20,19 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.search.SortField; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; +import org.apache.lucene.search.Scorer; /** - * A {@link ValueSource} which evaluates a {@link Expression} given the context of an {@link Bindings}. + * A {@link DoubleValuesSource} which evaluates a {@link Expression} given the context of an {@link Bindings}. */ -@SuppressWarnings({"rawtypes", "unchecked"}) -final class ExpressionValueSource extends ValueSource { - final ValueSource variables[]; + +final class ExpressionValueSource extends DoubleValuesSource { + final DoubleValuesSource variables[]; final Expression expression; final boolean needsScores; @@ -39,39 +40,29 @@ final class ExpressionValueSource extends ValueSource { if (bindings == null) throw new NullPointerException(); if (expression == null) throw new NullPointerException(); this.expression = expression; - variables = new ValueSource[expression.variables.length]; + variables = new DoubleValuesSource[expression.variables.length]; boolean needsScores = false; for (int i = 0; i < variables.length; i++) { - ValueSource source = bindings.getValueSource(expression.variables[i]); - if (source instanceof ScoreValueSource) { - needsScores = true; - } else if (source instanceof ExpressionValueSource) { - if (((ExpressionValueSource)source).needsScores()) { - needsScores = true; - } - } else if (source == null) { + DoubleValuesSource source = bindings.getValueSource(expression.variables[i]); + if (source == null) { throw new RuntimeException("Internal error. Variable (" + expression.variables[i] + ") does not exist."); } variables[i] = source; + needsScores |= source.needsScores(); } this.needsScores = needsScores; } @Override - public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { - Map valuesCache = (Map)context.get("valuesCache"); - if (valuesCache == null) { - valuesCache = new HashMap<>(); - context = new HashMap(context); - context.put("valuesCache", valuesCache); - } - FunctionValues[] externalValues = new FunctionValues[expression.variables.length]; + public DoubleValues getValues(LeafReaderContext readerContext, Scorer scorer) throws IOException { + Map valuesCache = new HashMap<>(); + DoubleValues[] externalValues = new DoubleValues[expression.variables.length]; for (int i = 0; i < variables.length; ++i) { String externalName = expression.variables[i]; - FunctionValues values = valuesCache.get(externalName); + DoubleValues values = valuesCache.get(externalName); if (values == null) { - values = variables[i].getValues(context, readerContext); + values = variables[i].getValues(readerContext, scorer); if (values == null) { throw new RuntimeException("Internal error. External (" + externalName + ") does not exist."); } @@ -80,28 +71,18 @@ final class ExpressionValueSource extends ValueSource { externalValues[i] = values; } - return new ExpressionFunctionValues(this, expression, externalValues); + return new ExpressionFunctionValues(expression, externalValues); } @Override - public SortField getSortField(boolean reverse) { - return new ExpressionSortField(expression.sourceText, this, reverse); - } - - @Override - public String description() { + public String toString() { return "expr(" + expression.sourceText + ")"; } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((expression == null) ? 0 : expression.hashCode()); - result = prime * result + (needsScores ? 1231 : 1237); - result = prime * result + Arrays.hashCode(variables); - return result; + int varHash = Objects.hash(variables); + return Objects.hash(expression.sourceText, needsScores, varHash); } @Override @@ -132,7 +113,8 @@ final class ExpressionValueSource extends ValueSource { return true; } - boolean needsScores() { + @Override + public boolean needsScores() { return needsScores; } } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java deleted file mode 100644 index e310c06..0000000 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreFunctionValues.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.lucene.expressions; - -import java.io.IOException; - -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.queries.function.docvalues.DoubleDocValues; -import org.apache.lucene.search.Scorer; - -/** - * A utility class to allow expressions to access the score as a {@link FunctionValues}. - */ -class ScoreFunctionValues extends DoubleDocValues { - final Scorer scorer; - - ScoreFunctionValues(ValueSource parent, Scorer scorer) { - super(parent); - this.scorer = scorer; - } - - @Override - public double doubleVal(int document) { - try { - assert document == scorer.docID(); - return scorer.score(); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } -} diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java deleted file mode 100644 index ea1669c..0000000 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/ScoreValueSource.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.lucene.expressions; - - -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.search.Scorer; - -import java.io.IOException; -import java.util.Map; - -/** - * A {@link ValueSource} which uses the {@link Scorer} passed through - * the context map by {@link ExpressionComparator}. - */ -@SuppressWarnings({"rawtypes"}) -class ScoreValueSource extends ValueSource { - - /** - * context must contain a key "scorer" which is a {@link Scorer}. - */ - @Override - public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { - Scorer v = (Scorer) context.get("scorer"); - if (v == null) { - throw new IllegalStateException("Expressions referencing the score can only be used for sorting"); - } - return new ScoreFunctionValues(this, v); - } - - @Override - public boolean equals(Object o) { - return o == this; - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public String description() { - return "score()"; - } -} diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java b/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java index e64249e..53a4770 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/SimpleBindings.java @@ -20,11 +20,7 @@ package org.apache.lucene.expressions; import java.util.HashMap; import java.util.Map; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.queries.function.valuesource.DoubleFieldSource; -import org.apache.lucene.queries.function.valuesource.FloatFieldSource; -import org.apache.lucene.queries.function.valuesource.IntFieldSource; -import org.apache.lucene.queries.function.valuesource.LongFieldSource; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.SortField; /** @@ -64,9 +60,9 @@ public final class SimpleBindings extends Bindings { } /** - * Bind a {@link ValueSource} directly to the given name. + * Bind a {@link DoubleValuesSource} directly to the given name. */ - public void add(String name, ValueSource source) { map.put(name, source); } + public void add(String name, DoubleValuesSource source) { map.put(name, source); } /** * Adds an Expression to the bindings. @@ -78,27 +74,25 @@ public final class SimpleBindings extends Bindings { } @Override - public ValueSource getValueSource(String name) { + public DoubleValuesSource getValueSource(String name) { Object o = map.get(name); if (o == null) { throw new IllegalArgumentException("Invalid reference '" + name + "'"); } else if (o instanceof Expression) { return ((Expression)o).getValueSource(this); - } else if (o instanceof ValueSource) { - return ((ValueSource)o); + } else if (o instanceof DoubleValuesSource) { + return ((DoubleValuesSource) o); } SortField field = (SortField) o; switch(field.getType()) { case INT: - return new IntFieldSource(field.getField()); case LONG: - return new LongFieldSource(field.getField()); + return DoubleValuesSource.fromLongField(field.getField()); case FLOAT: - return new FloatFieldSource(field.getField()); case DOUBLE: - return new DoubleFieldSource(field.getField()); + return DoubleValuesSource.fromDoubleField(field.getField()); case SCORE: - return getScoreValueSource(); + return DoubleValuesSource.SCORES; default: throw new UnsupportedOperationException(); } diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java index c78f6a97..a4ab037 100644 --- a/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java +++ b/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java @@ -39,7 +39,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.apache.lucene.expressions.Expression; import org.apache.lucene.expressions.js.JavascriptParser.ExpressionContext; -import org.apache.lucene.queries.function.FunctionValues; +import org.apache.lucene.search.DoubleValues; import org.apache.lucene.util.IOUtils; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; @@ -93,13 +93,13 @@ public final class JavascriptCompiler { private static final String COMPILED_EXPRESSION_INTERNAL = COMPILED_EXPRESSION_CLASS.replace('.', '/'); static final Type EXPRESSION_TYPE = Type.getType(Expression.class); - static final Type FUNCTION_VALUES_TYPE = Type.getType(FunctionValues.class); + static final Type FUNCTION_VALUES_TYPE = Type.getType(DoubleValues.class); private static final org.objectweb.asm.commons.Method EXPRESSION_CTOR = getAsmMethod(void.class, "", String.class, String[].class), - EVALUATE_METHOD = getAsmMethod(double.class, "evaluate", int.class, FunctionValues[].class); + EVALUATE_METHOD = getAsmMethod(double.class, "evaluate", int.class, DoubleValues[].class); - static final org.objectweb.asm.commons.Method DOUBLE_VAL_METHOD = getAsmMethod(double.class, "doubleVal", int.class); + static final org.objectweb.asm.commons.Method DOUBLE_VAL_METHOD = getAsmMethod(double.class, "get", int.class); /** create an ASM Method object from return type, method name, and parameters. */ private static org.objectweb.asm.commons.Method getAsmMethod(Class rtype, String name, Class... ptypes) { @@ -155,8 +155,8 @@ public final class JavascriptCompiler { */ @SuppressWarnings({"unused", "null"}) private static void unusedTestCompile() { - FunctionValues f = null; - double ret = f.doubleVal(2); + DoubleValues f = null; + double ret = f.get(2); } /** diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java index 01b3394..0463fdb 100644 --- a/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java +++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestDemoExpressions.java @@ -16,22 +16,25 @@ */ package org.apache.lucene.expressions; +import java.io.IOException; + import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.expressions.js.JavascriptCompiler; import org.apache.lucene.expressions.js.VariableContext; import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.Term; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource; -import org.apache.lucene.queries.function.valuesource.IntFieldSource; import org.apache.lucene.search.CheckHits; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.FieldDoc; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TermQuery; @@ -39,9 +42,9 @@ import org.apache.lucene.search.TopFieldDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.util.LuceneTestCase; +import static org.apache.lucene.expressions.js.VariableContext.Type.INT_INDEX; import static org.apache.lucene.expressions.js.VariableContext.Type.MEMBER; import static org.apache.lucene.expressions.js.VariableContext.Type.STR_INDEX; -import static org.apache.lucene.expressions.js.VariableContext.Type.INT_INDEX; /** simple demo of using expressions */ @@ -236,7 +239,7 @@ public class TestDemoExpressions extends LuceneTestCase { public void testStaticExtendedVariableExample() throws Exception { Expression popularity = JavascriptCompiler.compile("doc[\"popularity\"].value"); SimpleBindings bindings = new SimpleBindings(); - bindings.add("doc['popularity'].value", new IntFieldSource("popularity")); + bindings.add("doc['popularity'].value", DoubleValuesSource.fromLongField("popularity")); Sort sort = new Sort(popularity.getSortField(bindings, true)); TopFieldDocs td = searcher.search(new MatchAllDocsQuery(), 3, sort); @@ -250,6 +253,25 @@ public class TestDemoExpressions extends LuceneTestCase { assertEquals(2D, (Double)d.fields[0], 1E-4); } + static class ConstantValuesSource extends DoubleValuesSource { + + final double value; + + ConstantValuesSource(double value) { + this.value = value; + } + + @Override + public DoubleValues getValues(LeafReaderContext ctx, Scorer scorer) throws IOException { + return new DoubleValues() { + @Override + public double get(int doc) { + return value; + } + }; + } + } + public void testDynamicExtendedVariableExample() throws Exception { Expression popularity = JavascriptCompiler.compile("doc['popularity'].value + magicarray[0] + fourtytwo"); @@ -258,7 +280,7 @@ public class TestDemoExpressions extends LuceneTestCase { // filled in with proper error messages for a real use case. Bindings bindings = new Bindings() { @Override - public ValueSource getValueSource(String name) { + public DoubleValuesSource getValueSource(String name) { VariableContext[] var = VariableContext.parse(name); assert var[0].type == MEMBER; String base = var[0].text; @@ -266,7 +288,7 @@ public class TestDemoExpressions extends LuceneTestCase { if (var.length > 1 && var[1].type == STR_INDEX) { String field = var[1].text; if (var.length > 2 && var[2].type == MEMBER && var[2].text.equals("value")) { - return new IntFieldSource(field); + return DoubleValuesSource.fromLongField(field); } else { fail("member: " + var[2].text);// error case, non/missing "value" member access } @@ -275,12 +297,12 @@ public class TestDemoExpressions extends LuceneTestCase { } } else if (base.equals("magicarray")) { if (var.length > 1 && var[1].type == INT_INDEX) { - return new DoubleConstValueSource(2048); + return new ConstantValuesSource(2048); } else { fail();// error case, magic array isn't an array } } else if (base.equals("fourtytwo")) { - return new DoubleConstValueSource(42); + return new ConstantValuesSource(42); } else { fail();// error case (variable doesn't exist) } diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSortField.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSortField.java index ec6ea11..d39bcf0 100644 --- a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSortField.java +++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionSortField.java @@ -31,7 +31,7 @@ public class TestExpressionSortField extends LuceneTestCase { bindings.add(new SortField("popularity", SortField.Type.INT)); SortField sf = expr.getSortField(bindings, true); - assertEquals("!", sf.toString()); + assertEquals("!", sf.toString()); } public void testEquals() throws Exception { diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java index 3129d8c..5074df0 100644 --- a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java +++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java @@ -17,21 +17,17 @@ package org.apache.lucene.expressions; -import java.util.HashMap; - import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.expressions.js.JavascriptCompiler; -import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.RandomIndexWriter; -import org.apache.lucene.queries.function.FunctionValues; -import org.apache.lucene.queries.function.ValueSource; -import org.apache.lucene.queries.function.ValueSourceScorer; -import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.SortField; import org.apache.lucene.store.Directory; import org.apache.lucene.util.LuceneTestCase; @@ -82,65 +78,15 @@ public class TestExpressionValueSource extends LuceneTestCase { Expression expr = JavascriptCompiler.compile("2*popularity"); SimpleBindings bindings = new SimpleBindings(); bindings.add(new SortField("popularity", SortField.Type.LONG)); - ValueSource vs = expr.getValueSource(bindings); + DoubleValuesSource vs = expr.getValueSource(bindings); assertEquals(1, reader.leaves().size()); LeafReaderContext leaf = reader.leaves().get(0); - FunctionValues values = vs.getValues(new HashMap(), leaf); - - assertEquals(10, values.doubleVal(0), 0); - assertEquals(10, values.floatVal(0), 0); - assertEquals(10, values.longVal(0)); - assertEquals(10, values.intVal(0)); - assertEquals(10, values.shortVal(0)); - assertEquals(10, values.byteVal(0)); - assertEquals("10.0", values.strVal(0)); - assertEquals(new Double(10), values.objectVal(0)); + DoubleValues values = vs.getValues(leaf, null); - assertEquals(40, values.doubleVal(1), 0); - assertEquals(40, values.floatVal(1), 0); - assertEquals(40, values.longVal(1)); - assertEquals(40, values.intVal(1)); - assertEquals(40, values.shortVal(1)); - assertEquals(40, values.byteVal(1)); - assertEquals("40.0", values.strVal(1)); - assertEquals(new Double(40), values.objectVal(1)); - - assertEquals(4, values.doubleVal(2), 0); - assertEquals(4, values.floatVal(2), 0); - assertEquals(4, values.longVal(2)); - assertEquals(4, values.intVal(2)); - assertEquals(4, values.shortVal(2)); - assertEquals(4, values.byteVal(2)); - assertEquals("4.0", values.strVal(2)); - assertEquals(new Double(4), values.objectVal(2)); - } - - public void testRangeScorer() throws Exception { - Expression expr = JavascriptCompiler.compile("2*popularity"); - SimpleBindings bindings = new SimpleBindings(); - bindings.add(new SortField("popularity", SortField.Type.LONG)); - ValueSource vs = expr.getValueSource(bindings); - - assertEquals(1, reader.leaves().size()); - LeafReaderContext leaf = reader.leaves().get(0); - FunctionValues values = vs.getValues(new HashMap(), leaf); - - // everything - ValueSourceScorer scorer = values.getRangeScorer(leaf, "4", "40", true, true); - DocIdSetIterator iter = scorer.iterator(); - assertEquals(-1, iter.docID()); - assertEquals(0, iter.nextDoc()); - assertEquals(1, iter.nextDoc()); - assertEquals(2, iter.nextDoc()); - assertEquals(DocIdSetIterator.NO_MORE_DOCS, iter.nextDoc()); - - // just the first doc - scorer = values.getRangeScorer(leaf, "4", "40", false, false); - iter = scorer.iterator(); - assertEquals(-1, scorer.docID()); - assertEquals(0, iter.nextDoc()); - assertEquals(DocIdSetIterator.NO_MORE_DOCS, iter.nextDoc()); + assertEquals(10, values.get(0), 0); + assertEquals(40, values.get(1), 0); + assertEquals(4, values.get(2), 0); } public void testEquals() throws Exception { @@ -150,7 +96,7 @@ public class TestExpressionValueSource extends LuceneTestCase { bindings.add(new SortField("a", SortField.Type.INT)); bindings.add(new SortField("b", SortField.Type.INT)); - ValueSource vs1 = expr.getValueSource(bindings); + DoubleValuesSource vs1 = expr.getValueSource(bindings); // same instance assertEquals(vs1, vs1); // null @@ -158,20 +104,22 @@ public class TestExpressionValueSource extends LuceneTestCase { // other object assertFalse(vs1.equals("foobar")); // same bindings and expression instances - ValueSource vs2 = expr.getValueSource(bindings); - assertEquals(vs1.hashCode(), vs2.hashCode()); + DoubleValuesSource vs2 = expr.getValueSource(bindings); + int vs1Hash = vs1.hashCode(); + int vs2Hash = vs2.hashCode(); + assertEquals(vs1Hash, vs2Hash); assertEquals(vs1, vs2); // equiv bindings (different instance) SimpleBindings bindings2 = new SimpleBindings(); bindings2.add(new SortField("a", SortField.Type.INT)); bindings2.add(new SortField("b", SortField.Type.INT)); - ValueSource vs3 = expr.getValueSource(bindings2); + DoubleValuesSource vs3 = expr.getValueSource(bindings2); assertEquals(vs1, vs3); // different bindings (same names, different types) SimpleBindings bindings3 = new SimpleBindings(); bindings3.add(new SortField("a", SortField.Type.LONG)); - bindings3.add(new SortField("b", SortField.Type.INT)); - ValueSource vs4 = expr.getValueSource(bindings3); + bindings3.add(new SortField("b", SortField.Type.FLOAT)); + DoubleValuesSource vs4 = expr.getValueSource(bindings3); assertFalse(vs1.equals(vs4)); } } diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java index 49d4b77..8b76e00 100644 --- a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java +++ b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java @@ -19,11 +19,16 @@ package org.apache.lucene.queries.function; import java.io.IOException; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Objects; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.queries.function.docvalues.DoubleDocValues; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparatorSource; import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Scorer; import org.apache.lucene.search.SimpleFieldComparator; import org.apache.lucene.search.SortField; @@ -36,6 +41,53 @@ import org.apache.lucene.search.SortField; */ public abstract class ValueSource { + private static class WrappedDoubleValuesSource extends ValueSource { + + final DoubleValuesSource in; + + public WrappedDoubleValuesSource(DoubleValuesSource in) { + this.in = in; + } + + @Override + public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { + DoubleValues values = in.getValues(readerContext, (Scorer) context.get("scorer")); + return new DoubleDocValues(this) { + + @Override + public double doubleVal(int doc) { + return values.get(doc); + } + + }; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WrappedDoubleValuesSource that = (WrappedDoubleValuesSource) o; + return Objects.equals(in, that.in); + } + + @Override + public int hashCode() { + return Objects.hash(in); + } + + @Override + public String description() { + return in.toString(); + } + } + + /** + * Convert a {@link DoubleValuesSource} to a ValueSource + */ + public static ValueSource wrapDoubleValuesSource(DoubleValuesSource in) { + return new WrappedDoubleValuesSource(in); + } + /** * Gets the values for this reader and the context that was previously * passed to createWeight() diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java index 509e0ab..75b9de4 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestValueSources.java @@ -16,10 +16,10 @@ */ package org.apache.lucene.queries.function; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.io.IOException; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.MockAnalyzer; @@ -29,52 +29,24 @@ import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.document.SortedDocValuesField; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; -import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.Term; import org.apache.lucene.queries.function.docvalues.FloatDocValues; -import org.apache.lucene.queries.function.valuesource.BytesRefFieldSource; -import org.apache.lucene.queries.function.valuesource.ConstValueSource; -import org.apache.lucene.queries.function.valuesource.DivFloatFunction; -import org.apache.lucene.queries.function.valuesource.DocFreqValueSource; -import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource; -import org.apache.lucene.queries.function.valuesource.DoubleFieldSource; -import org.apache.lucene.queries.function.valuesource.FloatFieldSource; -import org.apache.lucene.queries.function.valuesource.IDFValueSource; -import org.apache.lucene.queries.function.valuesource.IfFunction; -import org.apache.lucene.queries.function.valuesource.IntFieldSource; -import org.apache.lucene.queries.function.valuesource.JoinDocFreqValueSource; -import org.apache.lucene.queries.function.valuesource.LinearFloatFunction; -import org.apache.lucene.queries.function.valuesource.LiteralValueSource; -import org.apache.lucene.queries.function.valuesource.LongFieldSource; -import org.apache.lucene.queries.function.valuesource.MaxDocValueSource; -import org.apache.lucene.queries.function.valuesource.MaxFloatFunction; -import org.apache.lucene.queries.function.valuesource.MinFloatFunction; -import org.apache.lucene.queries.function.valuesource.MultiFloatFunction; -import org.apache.lucene.queries.function.valuesource.MultiFunction; -import org.apache.lucene.queries.function.valuesource.NormValueSource; -import org.apache.lucene.queries.function.valuesource.NumDocsValueSource; -import org.apache.lucene.queries.function.valuesource.PowFloatFunction; -import org.apache.lucene.queries.function.valuesource.ProductFloatFunction; -import org.apache.lucene.queries.function.valuesource.QueryValueSource; -import org.apache.lucene.queries.function.valuesource.RangeMapFloatFunction; -import org.apache.lucene.queries.function.valuesource.ReciprocalFloatFunction; -import org.apache.lucene.queries.function.valuesource.ScaleFloatFunction; -import org.apache.lucene.queries.function.valuesource.SumFloatFunction; -import org.apache.lucene.queries.function.valuesource.SumTotalTermFreqValueSource; -import org.apache.lucene.queries.function.valuesource.TFValueSource; -import org.apache.lucene.queries.function.valuesource.TermFreqValueSource; -import org.apache.lucene.queries.function.valuesource.TotalTermFreqValueSource; +import org.apache.lucene.queries.function.valuesource.*; import org.apache.lucene.search.CheckHits; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; -import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.similarities.ClassicSimilarity; import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.store.Directory; @@ -82,6 +54,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.LuceneTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Test; // TODO: add separate docvalues test /** @@ -663,5 +636,30 @@ public class TestValueSources extends LuceneTestCase { /** @see ExistsValueSource */ private static final ValueSource ALL_EXIST_VS = new ExistsValueSource(true); /** @see ExistsValueSource */ - private static final ValueSource NONE_EXIST_VS = new ExistsValueSource(false); + private static final ValueSource NONE_EXIST_VS = new ExistsValueSource(false); + + @Test + public void testDoubleValuesSourceConversion() throws IOException { + + final double value = 15.0; + + DoubleValuesSource source = new DoubleValuesSource() { + @Override + public DoubleValues getValues(LeafReaderContext ctx, Scorer scorer) throws IOException { + return new DoubleValues() { + @Override + public double get(int doc) { + return value; + } + }; + } + }; + + ValueSource vs = ValueSource.wrapDoubleValuesSource(source); + FunctionValues fv = vs.getValues(null, null); + assertEquals(fv.longVal(0), 15); + assertEquals(fv.intVal(0), 15); + assertEquals(fv.doubleVal(0), 15.0, 0.00000001); + + } }