/* * 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.queries.function; import java.io.IOException; import java.util.Objects; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.PostingsEnum; import org.apache.lucene.index.Term; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; import org.apache.lucene.search.DoubleValues; import org.apache.lucene.search.DoubleValuesSource; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.LongValues; import org.apache.lucene.search.LongValuesSource; /** * Class exposing static helper methods for generating DoubleValuesSource instances * over some IndexReader statistics */ public final class IndexReaderFunctions { // non-instantiable class private IndexReaderFunctions() {} /** * Creates a constant value source returning the docFreq of a given term * * @see IndexReader#docFreq(Term) */ public static DoubleValuesSource docFreq(Term term) { return new IndexReaderDoubleValuesSource(r -> (double) r.docFreq(term), "docFreq(" + term.toString() + ")"); } /** * Creates a constant value source returning the index's maxDoc * * @see IndexReader#maxDoc() */ public static DoubleValuesSource maxDoc() { return new IndexReaderDoubleValuesSource(IndexReader::maxDoc, "maxDoc()"); } /** * Creates a constant value source returning the index's numDocs * * @see IndexReader#numDocs() */ public static DoubleValuesSource numDocs() { return new IndexReaderDoubleValuesSource(IndexReader::numDocs, "numDocs()"); } /** * Creates a constant value source returning the number of deleted docs in the index * * @see IndexReader#numDeletedDocs() */ public static DoubleValuesSource numDeletedDocs() { return new IndexReaderDoubleValuesSource(IndexReader::numDeletedDocs, "numDeletedDocs()"); } /** * Creates a constant value source returning the sumTotalTermFreq for a field * * @see IndexReader#getSumTotalTermFreq(String) */ public static LongValuesSource sumTotalTermFreq(String field) { return new SumTotalTermFreqValuesSource(field); } private static class SumTotalTermFreqValuesSource extends LongValuesSource { private final String field; private SumTotalTermFreqValuesSource(String field) { this.field = field; } @Override public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { throw new UnsupportedOperationException("IndexReaderFunction must be rewritten before use"); } @Override public boolean needsScores() { return false; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SumTotalTermFreqValuesSource that = (SumTotalTermFreqValuesSource) o; return Objects.equals(field, that.field); } @Override public int hashCode() { return Objects.hash(field); } @Override public LongValuesSource rewrite(IndexSearcher searcher) throws IOException { return new NoCacheConstantLongValuesSource(searcher.getIndexReader().getSumTotalTermFreq(field), this); } @Override public String toString() { return "sumTotalTermFreq(" + field + ")"; } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } private static class NoCacheConstantLongValuesSource extends LongValuesSource { final long value; final LongValuesSource parent; private NoCacheConstantLongValuesSource(long value, LongValuesSource parent) { this.value = value; this.parent = parent; } @Override public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { return new LongValues() { @Override public long longValue() throws IOException { return value; } @Override public boolean advanceExact(int doc) throws IOException { return true; } }; } @Override public boolean needsScores() { return false; } @Override public LongValuesSource rewrite(IndexSearcher reader) throws IOException { return this; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof NoCacheConstantLongValuesSource)) return false; NoCacheConstantLongValuesSource that = (NoCacheConstantLongValuesSource) o; return value == that.value && Objects.equals(parent, that.parent); } @Override public int hashCode() { return Objects.hash(value, parent); } @Override public String toString() { return parent.toString(); } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } /** * Creates a value source that returns the term freq of a given term for each document * * @see PostingsEnum#freq() */ public static DoubleValuesSource termFreq(Term term) { return new TermFreqDoubleValuesSource(term); } private static class TermFreqDoubleValuesSource extends DoubleValuesSource { private final Term term; private TermFreqDoubleValuesSource(Term term) { this.term = term; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Terms terms = ctx.reader().terms(term.field()); TermsEnum te = terms == null ? null : terms.iterator(); if (te == null || te.seekExact(term.bytes()) == false) { return DoubleValues.EMPTY; } final PostingsEnum pe = te.postings(null); assert pe != null; return new DoubleValues() { @Override public double doubleValue() throws IOException { return pe.freq(); } @Override public boolean advanceExact(int doc) throws IOException { if (pe.docID() > doc) return false; return pe.docID() == doc || pe.advance(doc) == doc; } }; } @Override public boolean needsScores() { return false; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return this; } @Override public String toString() { return "termFreq(" + term.toString() + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TermFreqDoubleValuesSource that = (TermFreqDoubleValuesSource) o; return Objects.equals(term, that.term); } @Override public int hashCode() { return Objects.hash(term); } @Override public boolean isCacheable(LeafReaderContext ctx) { return true; } } /** * Creates a constant value source returning the totalTermFreq for a given term * * @see IndexReader#totalTermFreq(Term) */ public static DoubleValuesSource totalTermFreq(Term term) { return new IndexReaderDoubleValuesSource(r -> r.totalTermFreq(term), "totalTermFreq(" + term.toString() + ")"); } /** * Creates a constant value source returning the sumDocFreq for a given field * * @see IndexReader#getSumDocFreq(String) */ public static DoubleValuesSource sumDocFreq(String field) { return new IndexReaderDoubleValuesSource(r -> r.getSumDocFreq(field), "sumDocFreq(" + field + ")"); } /** * Creates a constant value source returning the docCount for a given field * * @see IndexReader#getDocCount(String) */ public static DoubleValuesSource docCount(String field) { return new IndexReaderDoubleValuesSource(r -> r.getDocCount(field), "docCount(" + field + ")"); } @FunctionalInterface private interface ReaderFunction { double apply(IndexReader reader) throws IOException; } private static class IndexReaderDoubleValuesSource extends DoubleValuesSource { private final ReaderFunction func; private final String description; private IndexReaderDoubleValuesSource(ReaderFunction func, String description) { this.func = func; this.description = description; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { throw new UnsupportedOperationException("IndexReaderFunction must be rewritten before use"); } @Override public boolean needsScores() { return false; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return new NoCacheConstantDoubleValuesSource(func.apply(searcher.getIndexReader()), this); } @Override public String toString() { return description; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IndexReaderDoubleValuesSource that = (IndexReaderDoubleValuesSource) o; return Objects.equals(description, that.description) && Objects.equals(func, that.func); } @Override public int hashCode() { return Objects.hash(description, func); } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } private static class NoCacheConstantDoubleValuesSource extends DoubleValuesSource { final double value; final DoubleValuesSource parent; private NoCacheConstantDoubleValuesSource(double value, DoubleValuesSource parent) { this.value = value; this.parent = parent; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { return new DoubleValues() { @Override public double doubleValue() throws IOException { return value; } @Override public boolean advanceExact(int doc) throws IOException { return true; } }; } @Override public boolean needsScores() { return false; } @Override public DoubleValuesSource rewrite(IndexSearcher reader) throws IOException { return this; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof NoCacheConstantDoubleValuesSource)) return false; NoCacheConstantDoubleValuesSource that = (NoCacheConstantDoubleValuesSource) o; return Double.compare(that.value, value) == 0 && Objects.equals(parent, that.parent); } @Override public int hashCode() { return Objects.hash(value, parent); } @Override public String toString() { return parent.toString(); } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } }