/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.search.aggregations.support; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.index.fielddata.AbstractSortedNumericDocValues; import org.elasticsearch.index.fielddata.AbstractSortedSetDocValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import java.io.IOException; /** * Utility class that allows to return views of {@link ValuesSource}s that * replace the missing value with a configured value. */ public enum MissingValues { ; // TODO: we could specialize the single value case public static ValuesSource.Bytes replaceMissing(final ValuesSource.Bytes valuesSource, final BytesRef missing) { return new ValuesSource.Bytes() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { SortedBinaryDocValues values = valuesSource.bytesValues(context); return replaceMissing(values, missing); } }; } static SortedBinaryDocValues replaceMissing(final SortedBinaryDocValues values, final BytesRef missing) { return new SortedBinaryDocValues() { private int count; @Override public boolean advanceExact(int doc) throws IOException { if (values.advanceExact(doc)) { count = values.docValueCount(); } else { count = 0; } // always return true because we want to return a value even if // the document does not have a value return true; } @Override public int docValueCount() { return count == 0 ? 1 : count; } @Override public BytesRef nextValue() throws IOException { if (count > 0) { return values.nextValue(); } else { return missing; } } }; } public static ValuesSource.Numeric replaceMissing(final ValuesSource.Numeric valuesSource, final Number missing) { final boolean missingIsFloat = missing.doubleValue() % 1 != 0; final boolean isFloatingPoint = valuesSource.isFloatingPoint() || missingIsFloat; return new ValuesSource.Numeric() { @Override public boolean isFloatingPoint() { return isFloatingPoint; } @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { return replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString())); } @Override public SortedNumericDocValues longValues(LeafReaderContext context) throws IOException { final SortedNumericDocValues values = valuesSource.longValues(context); return replaceMissing(values, missing.longValue()); } @Override public SortedNumericDoubleValues doubleValues(LeafReaderContext context) throws IOException { final SortedNumericDoubleValues values = valuesSource.doubleValues(context); return replaceMissing(values, missing.doubleValue()); } }; } static SortedNumericDocValues replaceMissing(final SortedNumericDocValues values, final long missing) { return new AbstractSortedNumericDocValues() { private int count; @Override public long nextValue() throws IOException { if (count > 0) { return values.nextValue(); } else { return missing; } } @Override public int docValueCount() { return count == 0 ? 1 : count; } @Override public boolean advanceExact(int doc) throws IOException { if (values.advanceExact(doc)) { count = values.docValueCount(); } else { count = 0; } // always return true because we want to return a value even if // the document does not have a value return true; } }; } static SortedNumericDoubleValues replaceMissing(final SortedNumericDoubleValues values, final double missing) { return new SortedNumericDoubleValues() { private int count; @Override public boolean advanceExact(int doc) throws IOException { if (values.advanceExact(doc)) { count = values.docValueCount(); } else { count = 0; } // always return true because we want to return a value even if // the document does not have a value return true; } @Override public double nextValue() throws IOException { if (count > 0) { return values.nextValue(); } else { return missing; } } @Override public int docValueCount() { return count == 0 ? 1 : count; } }; } public static ValuesSource.Bytes replaceMissing(final ValuesSource.Bytes.WithOrdinals valuesSource, final BytesRef missing) { return new ValuesSource.Bytes.WithOrdinals() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { SortedBinaryDocValues values = valuesSource.bytesValues(context); return replaceMissing(values, missing); } @Override public SortedSetDocValues ordinalsValues(LeafReaderContext context) throws IOException { SortedSetDocValues values = valuesSource.ordinalsValues(context); return replaceMissing(values, missing); } @Override public SortedSetDocValues globalOrdinalsValues(LeafReaderContext context) throws IOException { SortedSetDocValues values = valuesSource.globalOrdinalsValues(context); return replaceMissing(values, missing); } }; } static SortedSetDocValues replaceMissing(final SortedSetDocValues values, final BytesRef missing) throws IOException { final long missingOrd = values.lookupTerm(missing); if (missingOrd >= 0) { // The value already exists return replaceMissingOrd(values, missingOrd); } else { final long insertedOrd = -1 - missingOrd; return insertOrd(values, insertedOrd, missing); } } static SortedSetDocValues replaceMissingOrd(final SortedSetDocValues values, final long missingOrd) { return new AbstractSortedSetDocValues() { private boolean hasOrds; private long nextMissingOrd; @Override public BytesRef lookupOrd(long ord) throws IOException { return values.lookupOrd(ord); } @Override public long getValueCount() { return values.getValueCount(); } @Override public long nextOrd() throws IOException { if (hasOrds) { return values.nextOrd(); } else { // we want to return the next missing ord but set this to // NO_MORE_ORDS so on the next call we indicate there are no // more values long ordToReturn = nextMissingOrd; nextMissingOrd = SortedSetDocValues.NO_MORE_ORDS; return ordToReturn; } } @Override public boolean advanceExact(int doc) throws IOException { hasOrds = values.advanceExact(doc); nextMissingOrd = missingOrd; // always return true because we want to return a value even if // the document does not have a value return true; } }; } static SortedSetDocValues insertOrd(final SortedSetDocValues values, final long insertedOrd, final BytesRef missingValue) { return new AbstractSortedSetDocValues() { private boolean hasOrds; private long nextMissingOrd; @Override public BytesRef lookupOrd(long ord) throws IOException { if (ord < insertedOrd) { return values.lookupOrd(ord); } else if (ord > insertedOrd) { return values.lookupOrd(ord - 1); } else { return missingValue; } } @Override public long getValueCount() { return 1 + values.getValueCount(); } @Override public long nextOrd() throws IOException { if (hasOrds) { final long ord = values.nextOrd(); if (ord < insertedOrd) { return ord; } else { return ord + 1; } } else { // we want to return the next missing ord but set this to // NO_MORE_ORDS so on the next call we indicate there are no // more values long ordToReturn = nextMissingOrd; nextMissingOrd = SortedSetDocValues.NO_MORE_ORDS; return ordToReturn; } } @Override public boolean advanceExact(int doc) throws IOException { hasOrds = values.advanceExact(doc); nextMissingOrd = insertedOrd; // always return true because we want to return a value even if // the document does not have a value return true; } }; } public static ValuesSource.GeoPoint replaceMissing(final ValuesSource.GeoPoint valuesSource, final GeoPoint missing) { return new ValuesSource.GeoPoint() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { return replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString())); } @Override public MultiGeoPointValues geoPointValues(LeafReaderContext context) { final MultiGeoPointValues values = valuesSource.geoPointValues(context); return replaceMissing(values, missing); } }; } static MultiGeoPointValues replaceMissing(final MultiGeoPointValues values, final GeoPoint missing) { return new MultiGeoPointValues() { private int count; @Override public boolean advanceExact(int doc) throws IOException { if (values.advanceExact(doc)) { count = values.docValueCount(); } else { count = 0; } // always return true because we want to return a value even if // the document does not have a value return true; } @Override public int docValueCount() { return count == 0 ? 1 : count; } @Override public GeoPoint nextValue() throws IOException { if (count > 0) { return values.nextValue(); } else { return missing; } } }; } }