### Description

Our most common aggregations should be optimized for single-valued fields. In general, our aggregations look like this:

```java
    @Override
    public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
        if (valuesSource == null) {
            return LeafBucketCollector.NO_OP_COLLECTOR;
        }
        SortedNumericDocValues values = valuesSource.longValues(ctx);
        return new LeafBucketCollectorBase(sub, values) {
            @Override
            public void collect(int doc, long owningBucketOrd) throws IOException {
                if (values.advanceExact(doc)) {
                    int valuesCount = values.docValueCount();
                    for (int i = 0; i < valuesCount; ++i) {
                        long value = values.nextValue();
                        // do something with value
                    }
                }
            }
        };
    }
```

The idea consists of optimizing for single-valued fields by unwrapping the single-valued doc-value iterator:

```java
    @Override
    public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
        if (valuesSource == null) {
            return LeafBucketCollector.NO_OP_COLLECTOR;
        }
        SortedNumericDocValues values = valuesSource.longValues(ctx);
        NumericDocValues singleValues = DocValues.unwrapSingleton(values); // or FieldData.unwrapSingleton for doubles
        if (singleValues != null) {
            return new LeafBucketCollectorBase(sub, values) {
                @Override
                public void collect(int doc, long owningBucketOrd) throws IOException {
                    if (singleValues.advanceExact(doc)) {
                        long value = singleValues.longValue();
                        // do something with value
                    }
                }
            };
        } else {
            return new LeafBucketCollectorBase(sub, values) {
                @Override
                public void collect(int doc, long owningBucketOrd) throws IOException {
                    if (values.advanceExact(doc)) {
                        int valuesCount = values.docValueCount();
                        for (int i = 0; i < valuesCount; ++i) {
                            long value = values.nextValue();
                            // do something with value
                        }
                    }
                }
            };
        }
    }
```

The `terms` aggregation already does this, and e.g. `min` and `max` aggregations use similar tricks that also help optimize the single-valued case. But `date_histogram`, which is one of our most common aggregations, doesn't. Let's specialize the single-value case for date histograms too?
server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java
/**
     * Build an {@link Aggregator} for a {@code range} aggregation. If the
     * {@code ranges} can be converted into filters then it builds a
     * {@link FiltersAggregator} and uses that to collect the results
     * <strong>if</strong> that aggregator can run in "filter by filter"
     * collection mode. If it can't then we'll collect the ranges using
     * a native {@link RangeAggregator} which is significantly faster
     * than the "compatible" collection mechanism for the filters agg.
     */
    public static Aggregator build(
        String name,
        AggregatorFactories factories,
        ValuesSourceConfig valuesSourceConfig,
        InternalRange.Factory<?, ?> rangeFactory,
        Range[] ranges,
        boolean keyed,
        AggregationContext context,
        Aggregator parent,
        CardinalityUpperBound cardinality,
        Map<String, Object> metadata
    ) throws IOException {
        /*
         * Estimate the average number of docs per range so we can disable the
         * filter collection mechanism if very few docs would match. This estimate
         * doesn't take the top level query or deleted documents into account so it
         * is often an overestimate. But that is ok because at worst we end up
         * wasting a couple dozen milliseconds on the filters. If the index is
         * small-ish and there are many filters then we avoid the optimization
         * which is good enough because that is the most embarrassing scenario.
         */