/* * 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.apache.lucene.search.grouping; import org.apache.lucene.index.DocValues; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.index.fielddata.AbstractNumericDocValues; import org.elasticsearch.index.fielddata.AbstractSortedDocValues; import java.io.IOException; import java.util.Collection; /** * Utility class that ensures that a single collapse key is extracted per document. */ abstract class CollapsingDocValuesSource<T> extends GroupSelector<T> { protected final String field; CollapsingDocValuesSource(String field) throws IOException { this.field = field; } @Override public void setGroups(Collection<SearchGroup<T>> groups) { throw new UnsupportedOperationException(); } /** * Implementation for {@link NumericDocValues} and {@link SortedNumericDocValues}. * Fails with an {@link IllegalStateException} if a document contains multiple values for the specified field. */ static class Numeric extends CollapsingDocValuesSource<Long> { private NumericDocValues values; private long value; private boolean hasValue; Numeric(String field) throws IOException { super(field); } @Override public State advanceTo(int doc) throws IOException { if (values.advanceExact(doc)) { hasValue = true; value = values.longValue(); return State.ACCEPT; } else { hasValue = false; return State.SKIP; } } @Override public Long currentValue() { return hasValue ? value : null; } @Override public Long copyValue() { return currentValue(); } @Override public void setNextReader(LeafReaderContext readerContext) throws IOException { LeafReader reader = readerContext.reader(); DocValuesType type = getDocValuesType(reader, field); if (type == null || type == DocValuesType.NONE) { values = DocValues.emptyNumeric(); return ; } switch (type) { case NUMERIC: values = DocValues.getNumeric(reader, field); break; case SORTED_NUMERIC: final SortedNumericDocValues sorted = DocValues.getSortedNumeric(reader, field); values = DocValues.unwrapSingleton(sorted); if (values == null) { values = new AbstractNumericDocValues() { private long value; @Override public boolean advanceExact(int target) throws IOException { if (sorted.advanceExact(target)) { if (sorted.docValueCount() > 1) { throw new IllegalStateException("failed to collapse " + target + ", the collapse field must be single valued"); } value = sorted.nextValue(); return true; } else { return false; } } @Override public int docID() { return sorted.docID(); } @Override public long longValue() throws IOException { return value; } }; } break; default: throw new IllegalStateException("unexpected doc values type " + type + "` for field `" + field + "`"); } } } /** * Implementation for {@link SortedDocValues} and {@link SortedSetDocValues}. * Fails with an {@link IllegalStateException} if a document contains multiple values for the specified field. */ static class Keyword extends CollapsingDocValuesSource<BytesRef> { private SortedDocValues values; private int ord; Keyword(String field) throws IOException { super(field); } @Override public org.apache.lucene.search.grouping.GroupSelector.State advanceTo(int doc) throws IOException { if (values.advanceExact(doc)) { ord = values.ordValue(); return State.ACCEPT; } else { ord = -1; return State.SKIP; } } @Override public BytesRef currentValue() { if (ord == -1) { return null; } else { try { return values.lookupOrd(ord); } catch (IOException e) { throw new RuntimeException(e); } } } @Override public BytesRef copyValue() { BytesRef value = currentValue(); if (value == null) { return null; } else { return BytesRef.deepCopyOf(value); } } @Override public void setNextReader(LeafReaderContext readerContext) throws IOException { LeafReader reader = readerContext.reader(); DocValuesType type = getDocValuesType(reader, field); if (type == null || type == DocValuesType.NONE) { values = DocValues.emptySorted(); return ; } switch (type) { case SORTED: values = DocValues.getSorted(reader, field); break; case SORTED_SET: final SortedSetDocValues sorted = DocValues.getSortedSet(reader, field); values = DocValues.unwrapSingleton(sorted); if (values == null) { values = new AbstractSortedDocValues() { private int ord; @Override public boolean advanceExact(int target) throws IOException { if (sorted.advanceExact(target)) { ord = (int) sorted.nextOrd(); if (sorted.nextOrd() != SortedSetDocValues.NO_MORE_ORDS) { throw new IllegalStateException("failed to collapse " + target + ", the collapse field must be single valued"); } return true; } else { return false; } } @Override public int docID() { return sorted.docID(); } @Override public int ordValue() { return ord; } @Override public BytesRef lookupOrd(int ord) throws IOException { return sorted.lookupOrd(ord); } @Override public int getValueCount() { return (int) sorted.getValueCount(); } }; } break; default: throw new IllegalStateException("unexpected doc values type " + type + "` for field `" + field + "`"); } } } private static DocValuesType getDocValuesType(LeafReader in, String field) { FieldInfo fi = in.getFieldInfos().fieldInfo(field); if (fi != null) { return fi.getDocValuesType(); } return null; } }