### 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/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java
assertTrue(AggregationInspectionHelper.hasValue(range));
        }, new NumberFieldMapper.NumberFieldType(NUMBER_FIELD_NAME, NumberFieldMapper.NumberType.INTEGER));
    }
    /**
     * If the top level query is a runtime field we use the standard aggregator
     * because it's marginally faster. You'd expect it to be a *ton* faster but
     * usually the ranges drive the iteration and they are still fairly fast.
     * But the union operation overhead that comes with combining the range with
     * the top level query tends to slow us down more than the standard aggregator.
     */
    public void testRuntimeFieldTopLevelQueryNotOptimized() throws IOException {
        long totalDocs = (long) RangeAggregator.DOCS_PER_RANGE_TO_USE_FILTERS * 4;
        SearchLookup lookup = new SearchLookup(s -> null, (ft, l) -> null);
        StringFieldScript.LeafFactory scriptFactory = ctx -> new StringFieldScript("dummy", Map.of(), lookup, ctx) {
            @Override
            public void execute() {
                emit("cat");
            }
        };
        Query query = new StringScriptFieldTermQuery(new Script("dummy"), scriptFactory, "dummy", "cat", false);
        debugTestCase(new RangeAggregationBuilder("r").field(NUMBER_FIELD_NAME).addRange(0, 1).addRange(1, 2).addRange(2, 3), query, iw -> {
            for (int d = 0; d < totalDocs; d++) {
                iw.addDocument(List.of(new IntPoint(NUMBER_FIELD_NAME, 0), new SortedNumericDocValuesField(NUMBER_FIELD_NAME, 0)));
            }
        }, (InternalRange<?, ?> r, Class<? extends Aggregator> impl, Map<String, Map<String, Object>> debug) -> {
            assertThat(
                r.getBuckets().stream().map(InternalRange.Bucket::getKeyAsString).collect(toList()),
                equalTo(List.of("0.0-1.0", "1.0-2.0", "2.0-3.0"))
            );
            assertThat(r.getBuckets().stream().map(InternalRange.Bucket::getFrom).collect(toList()), equalTo(List.of(0.0, 1.0, 2.0)));