|  | @@ -27,7 +27,6 @@ import org.elasticsearch.compute.data.DocVector;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.IntBlock;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.IntVector;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.Page;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.lucene.BlockOrdinalsReader;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.lucene.ValueSourceInfo;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.lucene.ValuesSourceReaderOperator;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.operator.HashAggregationOperator.GroupSpec;
 | 
	
	
		
			
				|  | @@ -234,18 +233,31 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |          final List<GroupingAggregator> aggregators = createGroupingAggregators();
 | 
	
		
			
				|  |  |          try {
 | 
	
		
			
				|  |  | +            boolean seenNulls = false;
 | 
	
		
			
				|  |  | +            for (OrdinalSegmentAggregator agg : ordinalAggregators.values()) {
 | 
	
		
			
				|  |  | +                if (agg.seenNulls()) {
 | 
	
		
			
				|  |  | +                    seenNulls = true;
 | 
	
		
			
				|  |  | +                    for (int i = 0; i < aggregators.size(); i++) {
 | 
	
		
			
				|  |  | +                        aggregators.get(i).addIntermediateRow(0, agg.aggregators.get(i), 0);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              for (OrdinalSegmentAggregator agg : ordinalAggregators.values()) {
 | 
	
		
			
				|  |  |                  final AggregatedResultIterator it = agg.getResultIterator();
 | 
	
		
			
				|  |  |                  if (it.next()) {
 | 
	
		
			
				|  |  |                      pq.add(it);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            int position = -1;
 | 
	
		
			
				|  |  | +            final int startPosition = seenNulls ? 0 : -1;
 | 
	
		
			
				|  |  | +            int position = startPosition;
 | 
	
		
			
				|  |  |              final BytesRefBuilder lastTerm = new BytesRefBuilder();
 | 
	
		
			
				|  |  |              var blockBuilder = BytesRefBlock.newBlockBuilder(1);
 | 
	
		
			
				|  |  | +            if (seenNulls) {
 | 
	
		
			
				|  |  | +                blockBuilder.appendNull();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              while (pq.size() > 0) {
 | 
	
		
			
				|  |  |                  final AggregatedResultIterator top = pq.top();
 | 
	
		
			
				|  |  | -                if (position == -1 || lastTerm.get().equals(top.currentTerm) == false) {
 | 
	
		
			
				|  |  | +                if (position == startPosition || lastTerm.get().equals(top.currentTerm) == false) {
 | 
	
		
			
				|  |  |                      position++;
 | 
	
		
			
				|  |  |                      lastTerm.copyBytes(top.currentTerm);
 | 
	
		
			
				|  |  |                      blockBuilder.appendBytesRef(top.currentTerm);
 | 
	
	
		
			
				|  | @@ -338,11 +350,8 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |                  if (BlockOrdinalsReader.canReuse(currentReader, docs.getInt(0)) == false) {
 | 
	
		
			
				|  |  |                      currentReader = new BlockOrdinalsReader(withOrdinals.ordinalsValues(leafReaderContext));
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                final IntBlock ordinals = currentReader.readOrdinals(docs);
 | 
	
		
			
				|  |  | +                final IntBlock ordinals = currentReader.readOrdinalsAdded1(docs);
 | 
	
		
			
				|  |  |                  for (int p = 0; p < ordinals.getPositionCount(); p++) {
 | 
	
		
			
				|  |  | -                    if (ordinals.isNull(p)) {
 | 
	
		
			
				|  |  | -                        continue;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  |                      int start = ordinals.getFirstValueIndex(p);
 | 
	
		
			
				|  |  |                      int end = start + ordinals.getValueCount(p);
 | 
	
		
			
				|  |  |                      for (int i = start; i < end; i++) {
 | 
	
	
		
			
				|  | @@ -350,8 +359,8 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |                          visitedOrds.set(ord);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                for (GroupingAggregator aggregator : aggregators) {
 | 
	
		
			
				|  |  | -                    aggregator.prepareProcessPage(this, page).add(0, ordinals);
 | 
	
		
			
				|  |  | +                for (GroupingAggregatorFunction.AddInput addInput : prepared) {
 | 
	
		
			
				|  |  | +                    addInput.add(0, ordinals);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              } catch (IOException e) {
 | 
	
		
			
				|  |  |                  throw new UncheckedIOException(e);
 | 
	
	
		
			
				|  | @@ -362,6 +371,10 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |              return new AggregatedResultIterator(aggregators, visitedOrds, withOrdinals.ordinalsValues(leafReaderContext));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        boolean seenNulls() {
 | 
	
		
			
				|  |  | +            return visitedOrds.get(0);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          @Override
 | 
	
		
			
				|  |  |          public BitArray seenGroupIds(BigArrays bigArrays) {
 | 
	
		
			
				|  |  |              BitArray seen = new BitArray(0, bigArrays);
 | 
	
	
		
			
				|  | @@ -377,7 +390,7 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private static class AggregatedResultIterator {
 | 
	
		
			
				|  |  |          private BytesRef currentTerm;
 | 
	
		
			
				|  |  | -        private long currentOrd = -1;
 | 
	
		
			
				|  |  | +        private long currentOrd = 0;
 | 
	
		
			
				|  |  |          private final List<GroupingAggregator> aggregators;
 | 
	
		
			
				|  |  |          private final BitArray ords;
 | 
	
		
			
				|  |  |          private final SortedSetDocValues dv;
 | 
	
	
		
			
				|  | @@ -395,8 +408,9 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          boolean next() throws IOException {
 | 
	
		
			
				|  |  |              currentOrd = ords.nextSetBit(currentOrd + 1);
 | 
	
		
			
				|  |  | +            assert currentOrd > 0 : currentOrd;
 | 
	
		
			
				|  |  |              if (currentOrd < Long.MAX_VALUE) {
 | 
	
		
			
				|  |  | -                currentTerm = dv.lookupOrd(currentOrd);
 | 
	
		
			
				|  |  | +                currentTerm = dv.lookupOrd(currentOrd - 1);
 | 
	
		
			
				|  |  |                  return true;
 | 
	
		
			
				|  |  |              } else {
 | 
	
		
			
				|  |  |                  currentTerm = null;
 | 
	
	
		
			
				|  | @@ -448,4 +462,49 @@ public class OrdinalsGroupingOperator implements Operator {
 | 
	
		
			
				|  |  |              Releasables.close(extractor, aggregator);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    static final class BlockOrdinalsReader {
 | 
	
		
			
				|  |  | +        private final SortedSetDocValues sortedSetDocValues;
 | 
	
		
			
				|  |  | +        private final Thread creationThread;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        BlockOrdinalsReader(SortedSetDocValues sortedSetDocValues) {
 | 
	
		
			
				|  |  | +            this.sortedSetDocValues = sortedSetDocValues;
 | 
	
		
			
				|  |  | +            this.creationThread = Thread.currentThread();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        IntBlock readOrdinalsAdded1(IntVector docs) throws IOException {
 | 
	
		
			
				|  |  | +            final int positionCount = docs.getPositionCount();
 | 
	
		
			
				|  |  | +            IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount);
 | 
	
		
			
				|  |  | +            for (int p = 0; p < positionCount; p++) {
 | 
	
		
			
				|  |  | +                int doc = docs.getInt(p);
 | 
	
		
			
				|  |  | +                if (false == sortedSetDocValues.advanceExact(doc)) {
 | 
	
		
			
				|  |  | +                    builder.appendInt(0);
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                int count = sortedSetDocValues.docValueCount();
 | 
	
		
			
				|  |  | +                // TODO don't come this way if there are a zillion ords on the field
 | 
	
		
			
				|  |  | +                if (count == 1) {
 | 
	
		
			
				|  |  | +                    builder.appendInt(Math.toIntExact(sortedSetDocValues.nextOrd() + 1));
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                builder.beginPositionEntry();
 | 
	
		
			
				|  |  | +                for (int i = 0; i < count; i++) {
 | 
	
		
			
				|  |  | +                    builder.appendInt(Math.toIntExact(sortedSetDocValues.nextOrd() + 1));
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                builder.endPositionEntry();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return builder.build();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        int docID() {
 | 
	
		
			
				|  |  | +            return sortedSetDocValues.docID();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /**
 | 
	
		
			
				|  |  | +         * Checks if the reader can be used to read a range documents starting with the given docID by the current thread.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        static boolean canReuse(BlockOrdinalsReader reader, int startingDocID) {
 | 
	
		
			
				|  |  | +            return reader != null && reader.creationThread == Thread.currentThread() && reader.docID() <= startingDocID;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |