Browse Source

ES|QL Add primitive float support to the Compute Engine (#109746)

This commit adds primitive float support to the Compute Engine - a.k.a. FloatBlock.

ES|QL language and aggregation support can be built atop this engine primitive. Serialization and wire format compatibility can also be done as a follow up.
Chris Hegarty 1 year ago
parent
commit
2721b80ea0
37 changed files with 2416 additions and 7 deletions
  1. 6 0
      docs/changelog/109746.yaml
  2. 7 0
      server/src/main/java/org/elasticsearch/index/mapper/BlockLoader.java
  3. 63 1
      x-pack/plugin/esql/compute/build.gradle
  4. 97 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/ConstantFloatVector.java
  5. 205 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatArrayBlock.java
  6. 131 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatArrayVector.java
  7. 206 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBigArrayBlock.java
  8. 108 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBigArrayVector.java
  9. 234 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBlock.java
  10. 185 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBlockBuilder.java
  11. 96 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatLookup.java
  12. 157 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVector.java
  13. 103 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorBlock.java
  14. 65 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorBuilder.java
  15. 90 0
      x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorFixedBuilder.java
  16. 52 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java
  17. 2 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java
  18. 7 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java
  19. 14 1
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullVector.java
  20. 3 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java
  21. 8 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BigArrayVector.java.st
  22. 2 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-Block.java.st
  23. 2 0
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-Vector.java.st
  24. 1 1
      x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperatorFactories.java
  25. 115 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java
  26. 90 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java
  27. 21 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java
  28. 8 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockValueAsserter.java
  29. 312 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/FloatBlockEqualityTests.java
  30. 6 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java
  31. 6 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java
  32. 1 1
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/mvdedupe/MultivalueDedupeTests.java
  33. 2 0
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java
  34. 4 3
      x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java
  35. 1 0
      x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java
  36. 5 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/QueryList.java
  37. 1 0
      x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EstimatesRowSize.java

+ 6 - 0
docs/changelog/109746.yaml

@@ -0,0 +1,6 @@
+pr: 109746
+summary: ES|QL Add primitive float support to the Compute Engine
+area: ES|QL
+type: enhancement
+issues:
+ - 109178

+ 7 - 0
server/src/main/java/org/elasticsearch/index/mapper/BlockLoader.java

@@ -456,6 +456,13 @@ public interface BlockLoader {
         BytesRefBuilder appendBytesRef(BytesRef value);
     }
 
+    interface FloatBuilder extends Builder {
+        /**
+         * Appends a float to the current entry.
+         */
+        FloatBuilder appendFloat(float value);
+    }
+
     interface DoubleBuilder extends Builder {
         /**
          * Appends a double to the current entry.

+ 63 - 1
x-pack/plugin/esql/compute/build.gradle

@@ -46,6 +46,7 @@ def prop(Type, type, TYPE, BYTES, Array, Hash) {
     "Hash" : Hash,
 
     "int" : type == "int" ? "true" : "",
+    "float" : type == "float" ? "true" : "",
     "long" : type == "long" ? "true" : "",
     "double" : type == "double" ? "true" : "",
     "BytesRef" : type == "BytesRef" ? "true" : "",
@@ -55,6 +56,7 @@ def prop(Type, type, TYPE, BYTES, Array, Hash) {
 
 tasks.named('stringTemplates').configure {
   var intProperties      = prop("Int", "int", "INT", "Integer.BYTES", "IntArray", "LongHash")
+  var floatProperties    = prop("Float", "float", "FLOAT", "Float.BYTES", "FloatArray", "LongHash")
   var longProperties     = prop("Long", "long", "LONG", "Long.BYTES", "LongArray", "LongHash")
   var doubleProperties   = prop("Double", "double", "DOUBLE", "Double.BYTES", "DoubleArray", "LongHash")
   var bytesRefProperties = prop("BytesRef", "BytesRef", "BYTES_REF", "org.apache.lucene.util.RamUsageEstimator.NUM_BYTES_OBJECT_REF", "", "BytesRefHash")
@@ -66,6 +68,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  vectorInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntVector.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  vectorInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatVector.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  vectorInputFile
@@ -93,6 +100,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  arrayVectorInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntArrayVector.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  arrayVectorInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatArrayVector.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  arrayVectorInputFile
@@ -120,6 +132,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  bigArrayVectorInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntBigArrayVector.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  bigArrayVectorInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatBigArrayVector.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  bigArrayVectorInputFile
@@ -142,6 +159,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  constantVectorInputFile
     it.outputFile = "org/elasticsearch/compute/data/ConstantIntVector.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  constantVectorInputFile
+    it.outputFile = "org/elasticsearch/compute/data/ConstantFloatVector.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  constantVectorInputFile
@@ -169,6 +191,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  blockInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntBlock.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  blockInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatBlock.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  blockInputFile
@@ -196,6 +223,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  arrayBlockInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntArrayBlock.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  arrayBlockInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatArrayBlock.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  arrayBlockInputFile
@@ -223,6 +255,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  bigArrayBlockInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntBigArrayBlock.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  bigArrayBlockInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatBigArrayBlock.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  bigArrayBlockInputFile
@@ -245,6 +282,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  vectorBlockInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntVectorBlock.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  vectorBlockInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatVectorBlock.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  vectorBlockInputFile
@@ -272,6 +314,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  blockBuildersInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntBlockBuilder.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  blockBuildersInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatBlockBuilder.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  blockBuildersInputFile
@@ -299,6 +346,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  vectorBuildersInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntVectorBuilder.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  vectorBuildersInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatVectorBuilder.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  vectorBuildersInputFile
@@ -325,6 +377,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  vectorFixedBuildersInputFile
     it.outputFile = "org/elasticsearch/compute/data/IntVectorFixedBuilder.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  vectorFixedBuildersInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatVectorFixedBuilder.java"
+  }
   template {
     it.properties = longProperties
     it.inputFile =  vectorFixedBuildersInputFile
@@ -356,7 +413,7 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  stateInputFile
     it.outputFile = "org/elasticsearch/compute/aggregation/DoubleState.java"
   }
-  // block builders
+  // block lookups
   File lookupInputFile = new File("${projectDir}/src/main/java/org/elasticsearch/compute/data/X-Lookup.java.st")
   template {
     it.properties = intProperties
@@ -368,6 +425,11 @@ tasks.named('stringTemplates').configure {
     it.inputFile =  lookupInputFile
     it.outputFile = "org/elasticsearch/compute/data/LongLookup.java"
   }
+  template {
+    it.properties = floatProperties
+    it.inputFile =  lookupInputFile
+    it.outputFile = "org/elasticsearch/compute/data/FloatLookup.java"
+  }
   template {
     it.properties = doubleProperties
     it.inputFile =  lookupInputFile

+ 97 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/ConstantFloatVector.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+
+/**
+ * Vector implementation that stores a constant float value.
+ * This class is generated. Do not edit it.
+ */
+final class ConstantFloatVector extends AbstractVector implements FloatVector {
+
+    static final long RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(ConstantFloatVector.class);
+
+    private final float value;
+
+    ConstantFloatVector(float value, int positionCount, BlockFactory blockFactory) {
+        super(positionCount, blockFactory);
+        this.value = value;
+    }
+
+    @Override
+    public float getFloat(int position) {
+        return value;
+    }
+
+    @Override
+    public FloatBlock asBlock() {
+        return new FloatVectorBlock(this);
+    }
+
+    @Override
+    public FloatVector filter(int... positions) {
+        return blockFactory().newConstantFloatVector(value, positions.length);
+    }
+
+    @Override
+    public ReleasableIterator<FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        if (positions.getPositionCount() == 0) {
+            return ReleasableIterator.empty();
+        }
+        IntVector positionsVector = positions.asVector();
+        if (positionsVector == null) {
+            return new FloatLookup(asBlock(), positions, targetBlockSize);
+        }
+        int min = positionsVector.min();
+        if (min < 0) {
+            throw new IllegalArgumentException("invalid position [" + min + "]");
+        }
+        if (min > getPositionCount()) {
+            return ReleasableIterator.single((FloatBlock) positions.blockFactory().newConstantNullBlock(positions.getPositionCount()));
+        }
+        if (positionsVector.max() < getPositionCount()) {
+            return ReleasableIterator.single(positions.blockFactory().newConstantFloatBlockWith(value, positions.getPositionCount()));
+        }
+        return new FloatLookup(asBlock(), positions, targetBlockSize);
+    }
+
+    @Override
+    public ElementType elementType() {
+        return ElementType.FLOAT;
+    }
+
+    @Override
+    public boolean isConstant() {
+        return true;
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return RAM_BYTES_USED;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatVector that) {
+            return FloatVector.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatVector.hash(this);
+    }
+
+    public String toString() {
+        return getClass().getSimpleName() + "[positions=" + getPositionCount() + ", value=" + value + ']';
+    }
+}

+ 205 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatArrayBlock.java

@@ -0,0 +1,205 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+import org.elasticsearch.core.Releasables;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+/**
+ * Block implementation that stores values in a {@link FloatArrayVector}.
+ * This class is generated. Do not edit it.
+ */
+final class FloatArrayBlock extends AbstractArrayBlock implements FloatBlock {
+
+    static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(FloatArrayBlock.class);
+
+    private final FloatArrayVector vector;
+
+    FloatArrayBlock(
+        float[] values,
+        int positionCount,
+        int[] firstValueIndexes,
+        BitSet nulls,
+        MvOrdering mvOrdering,
+        BlockFactory blockFactory
+    ) {
+        this(
+            new FloatArrayVector(values, firstValueIndexes == null ? positionCount : firstValueIndexes[positionCount], blockFactory),
+            positionCount,
+            firstValueIndexes,
+            nulls,
+            mvOrdering
+        );
+    }
+
+    private FloatArrayBlock(
+        FloatArrayVector vector, // stylecheck
+        int positionCount,
+        int[] firstValueIndexes,
+        BitSet nulls,
+        MvOrdering mvOrdering
+    ) {
+        super(positionCount, firstValueIndexes, nulls, mvOrdering);
+        this.vector = vector;
+        assert firstValueIndexes == null
+            ? vector.getPositionCount() == getPositionCount()
+            : firstValueIndexes[getPositionCount()] == vector.getPositionCount();
+    }
+
+    static FloatArrayBlock readArrayBlock(BlockFactory blockFactory, BlockStreamInput in) throws IOException {
+        final SubFields sub = new SubFields(blockFactory, in);
+        FloatArrayVector vector = null;
+        boolean success = false;
+        try {
+            vector = FloatArrayVector.readArrayVector(sub.vectorPositions(), in, blockFactory);
+            var block = new FloatArrayBlock(vector, sub.positionCount, sub.firstValueIndexes, sub.nullsMask, sub.mvOrdering);
+            blockFactory.adjustBreaker(block.ramBytesUsed() - vector.ramBytesUsed() - sub.bytesReserved);
+            success = true;
+            return block;
+        } finally {
+            if (success == false) {
+                Releasables.close(vector);
+                blockFactory.adjustBreaker(-sub.bytesReserved);
+            }
+        }
+    }
+
+    void writeArrayBlock(StreamOutput out) throws IOException {
+        writeSubFields(out);
+        vector.writeArrayVector(vector.getPositionCount(), out);
+    }
+
+    @Override
+    public FloatVector asVector() {
+        return null;
+    }
+
+    @Override
+    public float getFloat(int valueIndex) {
+        return vector.getFloat(valueIndex);
+    }
+
+    @Override
+    public FloatBlock filter(int... positions) {
+        try (var builder = blockFactory().newFloatBlockBuilder(positions.length)) {
+            for (int pos : positions) {
+                if (isNull(pos)) {
+                    builder.appendNull();
+                    continue;
+                }
+                int valueCount = getValueCount(pos);
+                int first = getFirstValueIndex(pos);
+                if (valueCount == 1) {
+                    builder.appendFloat(getFloat(getFirstValueIndex(pos)));
+                } else {
+                    builder.beginPositionEntry();
+                    for (int c = 0; c < valueCount; c++) {
+                        builder.appendFloat(getFloat(first + c));
+                    }
+                    builder.endPositionEntry();
+                }
+            }
+            return builder.mvOrdering(mvOrdering()).build();
+        }
+    }
+
+    @Override
+    public ReleasableIterator<FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        return new FloatLookup(this, positions, targetBlockSize);
+    }
+
+    @Override
+    public ElementType elementType() {
+        return ElementType.FLOAT;
+    }
+
+    @Override
+    public FloatBlock expand() {
+        if (firstValueIndexes == null) {
+            incRef();
+            return this;
+        }
+        if (nullsMask == null) {
+            vector.incRef();
+            return vector.asBlock();
+        }
+
+        // The following line is correct because positions with multi-values are never null.
+        int expandedPositionCount = vector.getPositionCount();
+        long bitSetRamUsedEstimate = Math.max(nullsMask.size(), BlockRamUsageEstimator.sizeOfBitSet(expandedPositionCount));
+        blockFactory().adjustBreaker(bitSetRamUsedEstimate);
+
+        FloatArrayBlock expanded = new FloatArrayBlock(
+            vector,
+            expandedPositionCount,
+            null,
+            shiftNullsToExpandedPositions(),
+            MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING
+        );
+        blockFactory().adjustBreaker(expanded.ramBytesUsedOnlyBlock() - bitSetRamUsedEstimate);
+        // We need to incRef after adjusting any breakers, otherwise we might leak the vector if the breaker trips.
+        vector.incRef();
+        return expanded;
+    }
+
+    private long ramBytesUsedOnlyBlock() {
+        return BASE_RAM_BYTES_USED + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask);
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return ramBytesUsedOnlyBlock() + vector.ramBytesUsed();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatBlock that) {
+            return FloatBlock.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatBlock.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[positions="
+            + getPositionCount()
+            + ", mvOrdering="
+            + mvOrdering()
+            + ", vector="
+            + vector
+            + ']';
+    }
+
+    @Override
+    public void allowPassingToDifferentDriver() {
+        vector.allowPassingToDifferentDriver();
+    }
+
+    @Override
+    public BlockFactory blockFactory() {
+        return vector.blockFactory();
+    }
+
+    @Override
+    public void closeInternal() {
+        blockFactory().adjustBreaker(-ramBytesUsedOnlyBlock());
+        Releasables.closeExpectNoException(vector);
+    }
+}

+ 131 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatArrayVector.java

@@ -0,0 +1,131 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+
+import java.io.IOException;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Vector implementation that stores an array of float values.
+ * This class is generated. Do not edit it.
+ */
+final class FloatArrayVector extends AbstractVector implements FloatVector {
+
+    static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(FloatArrayVector.class)
+        // TODO: remove these extra bytes once `asBlock` returns a block with a separate reference to the vector.
+        + RamUsageEstimator.shallowSizeOfInstance(FloatVectorBlock.class)
+        // TODO: remove this if/when we account for memory used by Pages
+        + Block.PAGE_MEM_OVERHEAD_PER_BLOCK;
+
+    private final float[] values;
+
+    FloatArrayVector(float[] values, int positionCount, BlockFactory blockFactory) {
+        super(positionCount, blockFactory);
+        this.values = values;
+    }
+
+    static FloatArrayVector readArrayVector(int positions, StreamInput in, BlockFactory blockFactory) throws IOException {
+        final long preAdjustedBytes = RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + (long) positions * Float.BYTES;
+        blockFactory.adjustBreaker(preAdjustedBytes);
+        boolean success = false;
+        try {
+            float[] values = new float[positions];
+            for (int i = 0; i < positions; i++) {
+                values[i] = in.readFloat();
+            }
+            final var block = new FloatArrayVector(values, positions, blockFactory);
+            blockFactory.adjustBreaker(block.ramBytesUsed() - preAdjustedBytes);
+            success = true;
+            return block;
+        } finally {
+            if (success == false) {
+                blockFactory.adjustBreaker(-preAdjustedBytes);
+            }
+        }
+    }
+
+    void writeArrayVector(int positions, StreamOutput out) throws IOException {
+        for (int i = 0; i < positions; i++) {
+            out.writeFloat(values[i]);
+        }
+    }
+
+    @Override
+    public FloatBlock asBlock() {
+        return new FloatVectorBlock(this);
+    }
+
+    @Override
+    public float getFloat(int position) {
+        return values[position];
+    }
+
+    @Override
+    public ElementType elementType() {
+        return ElementType.FLOAT;
+    }
+
+    @Override
+    public boolean isConstant() {
+        return false;
+    }
+
+    @Override
+    public FloatVector filter(int... positions) {
+        try (FloatVector.Builder builder = blockFactory().newFloatVectorBuilder(positions.length)) {
+            for (int pos : positions) {
+                builder.appendFloat(values[pos]);
+            }
+            return builder.build();
+        }
+    }
+
+    @Override
+    public ReleasableIterator<FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        return new FloatLookup(asBlock(), positions, targetBlockSize);
+    }
+
+    public static long ramBytesEstimated(float[] values) {
+        return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values);
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return ramBytesEstimated(values);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatVector that) {
+            return FloatVector.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatVector.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        String valuesString = IntStream.range(0, getPositionCount())
+            .limit(10)
+            .mapToObj(n -> String.valueOf(values[n]))
+            .collect(Collectors.joining(", ", "[", getPositionCount() > 10 ? ", ...]" : "]"));
+        return getClass().getSimpleName() + "[positions=" + getPositionCount() + ", values=" + valuesString + ']';
+    }
+
+}

+ 206 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBigArrayBlock.java

@@ -0,0 +1,206 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.util.FloatArray;
+import org.elasticsearch.core.ReleasableIterator;
+import org.elasticsearch.core.Releasables;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+/**
+ * Block implementation that stores values in a {@link FloatBigArrayVector}. Does not take ownership of the given
+ * {@link FloatArray} and does not adjust circuit breakers to account for it.
+ * This class is generated. Do not edit it.
+ */
+public final class FloatBigArrayBlock extends AbstractArrayBlock implements FloatBlock {
+
+    private static final long BASE_RAM_BYTES_USED = 0; // TODO: fix this
+    private final FloatBigArrayVector vector;
+
+    public FloatBigArrayBlock(
+        FloatArray values,
+        int positionCount,
+        int[] firstValueIndexes,
+        BitSet nulls,
+        MvOrdering mvOrdering,
+        BlockFactory blockFactory
+    ) {
+        this(
+            new FloatBigArrayVector(values, firstValueIndexes == null ? positionCount : firstValueIndexes[positionCount], blockFactory),
+            positionCount,
+            firstValueIndexes,
+            nulls,
+            mvOrdering
+        );
+    }
+
+    private FloatBigArrayBlock(
+        FloatBigArrayVector vector, // stylecheck
+        int positionCount,
+        int[] firstValueIndexes,
+        BitSet nulls,
+        MvOrdering mvOrdering
+    ) {
+        super(positionCount, firstValueIndexes, nulls, mvOrdering);
+        this.vector = vector;
+        assert firstValueIndexes == null
+            ? vector.getPositionCount() == getPositionCount()
+            : firstValueIndexes[getPositionCount()] == vector.getPositionCount();
+    }
+
+    static FloatBigArrayBlock readArrayBlock(BlockFactory blockFactory, BlockStreamInput in) throws IOException {
+        final SubFields sub = new SubFields(blockFactory, in);
+        FloatBigArrayVector vector = null;
+        boolean success = false;
+        try {
+            vector = FloatBigArrayVector.readArrayVector(sub.vectorPositions(), in, blockFactory);
+            var block = new FloatBigArrayBlock(vector, sub.positionCount, sub.firstValueIndexes, sub.nullsMask, sub.mvOrdering);
+            blockFactory.adjustBreaker(block.ramBytesUsed() - vector.ramBytesUsed() - sub.bytesReserved);
+            success = true;
+            return block;
+        } finally {
+            if (success == false) {
+                Releasables.close(vector);
+                blockFactory.adjustBreaker(-sub.bytesReserved);
+            }
+        }
+    }
+
+    void writeArrayBlock(StreamOutput out) throws IOException {
+        writeSubFields(out);
+        vector.writeArrayVector(vector.getPositionCount(), out);
+    }
+
+    @Override
+    public FloatVector asVector() {
+        return null;
+    }
+
+    @Override
+    public float getFloat(int valueIndex) {
+        return vector.getFloat(valueIndex);
+    }
+
+    @Override
+    public FloatBlock filter(int... positions) {
+        try (var builder = blockFactory().newFloatBlockBuilder(positions.length)) {
+            for (int pos : positions) {
+                if (isNull(pos)) {
+                    builder.appendNull();
+                    continue;
+                }
+                int valueCount = getValueCount(pos);
+                int first = getFirstValueIndex(pos);
+                if (valueCount == 1) {
+                    builder.appendFloat(getFloat(getFirstValueIndex(pos)));
+                } else {
+                    builder.beginPositionEntry();
+                    for (int c = 0; c < valueCount; c++) {
+                        builder.appendFloat(getFloat(first + c));
+                    }
+                    builder.endPositionEntry();
+                }
+            }
+            return builder.mvOrdering(mvOrdering()).build();
+        }
+    }
+
+    @Override
+    public ReleasableIterator<FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        return new FloatLookup(this, positions, targetBlockSize);
+    }
+
+    @Override
+    public ElementType elementType() {
+        return ElementType.FLOAT;
+    }
+
+    @Override
+    public FloatBlock expand() {
+        if (firstValueIndexes == null) {
+            incRef();
+            return this;
+        }
+        if (nullsMask == null) {
+            vector.incRef();
+            return vector.asBlock();
+        }
+
+        // The following line is correct because positions with multi-values are never null.
+        int expandedPositionCount = vector.getPositionCount();
+        long bitSetRamUsedEstimate = Math.max(nullsMask.size(), BlockRamUsageEstimator.sizeOfBitSet(expandedPositionCount));
+        blockFactory().adjustBreaker(bitSetRamUsedEstimate);
+
+        FloatBigArrayBlock expanded = new FloatBigArrayBlock(
+            vector,
+            expandedPositionCount,
+            null,
+            shiftNullsToExpandedPositions(),
+            MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING
+        );
+        blockFactory().adjustBreaker(expanded.ramBytesUsedOnlyBlock() - bitSetRamUsedEstimate);
+        // We need to incRef after adjusting any breakers, otherwise we might leak the vector if the breaker trips.
+        vector.incRef();
+        return expanded;
+    }
+
+    private long ramBytesUsedOnlyBlock() {
+        return BASE_RAM_BYTES_USED + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask);
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return ramBytesUsedOnlyBlock() + RamUsageEstimator.sizeOf(vector);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatBlock that) {
+            return FloatBlock.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatBlock.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[positions="
+            + getPositionCount()
+            + ", mvOrdering="
+            + mvOrdering()
+            + ", ramBytesUsed="
+            + vector.ramBytesUsed()
+            + ']';
+    }
+
+    @Override
+    public void allowPassingToDifferentDriver() {
+        vector.allowPassingToDifferentDriver();
+    }
+
+    @Override
+    public BlockFactory blockFactory() {
+        return vector.blockFactory();
+    }
+
+    @Override
+    public void closeInternal() {
+        blockFactory().adjustBreaker(-ramBytesUsedOnlyBlock());
+        Releasables.closeExpectNoException(vector);
+    }
+}

+ 108 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBigArrayVector.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.util.FloatArray;
+import org.elasticsearch.core.Releasable;
+import org.elasticsearch.core.ReleasableIterator;
+
+import java.io.IOException;
+
+/**
+ * Vector implementation that defers to an enclosed {@link FloatArray}.
+ * Does not take ownership of the array and does not adjust circuit breakers to account for it.
+ * This class is generated. Do not edit it.
+ */
+public final class FloatBigArrayVector extends AbstractVector implements FloatVector, Releasable {
+
+    private static final long BASE_RAM_BYTES_USED = 0; // FIXME
+
+    private final FloatArray values;
+
+    public FloatBigArrayVector(FloatArray values, int positionCount, BlockFactory blockFactory) {
+        super(positionCount, blockFactory);
+        this.values = values;
+    }
+
+    static FloatBigArrayVector readArrayVector(int positions, StreamInput in, BlockFactory blockFactory) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    void writeArrayVector(int positions, StreamOutput out) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FloatBlock asBlock() {
+        return new FloatVectorBlock(this);
+    }
+
+    @Override
+    public float getFloat(int position) {
+        return values.get(position);
+    }
+
+    @Override
+    public ElementType elementType() {
+        return ElementType.FLOAT;
+    }
+
+    @Override
+    public boolean isConstant() {
+        return false;
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values);
+    }
+
+    @Override
+    public FloatVector filter(int... positions) {
+        var blockFactory = blockFactory();
+        final FloatArray filtered = blockFactory.bigArrays().newFloatArray(positions.length);
+        for (int i = 0; i < positions.length; i++) {
+            filtered.set(i, values.get(positions[i]));
+        }
+        return new FloatBigArrayVector(filtered, positions.length, blockFactory);
+    }
+
+    @Override
+    public ReleasableIterator<FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        return new FloatLookup(asBlock(), positions, targetBlockSize);
+    }
+
+    @Override
+    public void closeInternal() {
+        // The circuit breaker that tracks the values {@link FloatArray} is adjusted outside
+        // of this class.
+        values.close();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatVector that) {
+            return FloatVector.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatVector.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[positions=" + getPositionCount() + ", values=" + values + ']';
+    }
+}

+ 234 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBlock.java

@@ -0,0 +1,234 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.elasticsearch.TransportVersions;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+import org.elasticsearch.index.mapper.BlockLoader;
+
+import java.io.IOException;
+
+/**
+ * Block that stores float values.
+ * This class is generated. Do not edit it.
+ */
+public sealed interface FloatBlock extends Block permits FloatArrayBlock, FloatVectorBlock, ConstantNullBlock, FloatBigArrayBlock {
+
+    /**
+     * Retrieves the float value stored at the given value index.
+     *
+     * <p> Values for a given position are between getFirstValueIndex(position) (inclusive) and
+     * getFirstValueIndex(position) + getValueCount(position) (exclusive).
+     *
+     * @param valueIndex the value index
+     * @return the data value (as a float)
+     */
+    float getFloat(int valueIndex);
+
+    @Override
+    FloatVector asVector();
+
+    @Override
+    FloatBlock filter(int... positions);
+
+    @Override
+    ReleasableIterator<? extends FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize);
+
+    @Override
+    FloatBlock expand();
+
+    @Override
+    default String getWriteableName() {
+        return "FloatBlock";
+    }
+
+    NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Block.class, "FloatBlock", FloatBlock::readFrom);
+
+    private static FloatBlock readFrom(StreamInput in) throws IOException {
+        return readFrom((BlockStreamInput) in);
+    }
+
+    static FloatBlock readFrom(BlockStreamInput in) throws IOException {
+        final byte serializationType = in.readByte();
+        return switch (serializationType) {
+            case SERIALIZE_BLOCK_VALUES -> FloatBlock.readValues(in);
+            case SERIALIZE_BLOCK_VECTOR -> FloatVector.readFrom(in.blockFactory(), in).asBlock();
+            case SERIALIZE_BLOCK_ARRAY -> FloatArrayBlock.readArrayBlock(in.blockFactory(), in);
+            case SERIALIZE_BLOCK_BIG_ARRAY -> FloatBigArrayBlock.readArrayBlock(in.blockFactory(), in);
+            default -> {
+                assert false : "invalid block serialization type " + serializationType;
+                throw new IllegalStateException("invalid serialization type " + serializationType);
+            }
+        };
+    }
+
+    private static FloatBlock readValues(BlockStreamInput in) throws IOException {
+        final int positions = in.readVInt();
+        try (FloatBlock.Builder builder = in.blockFactory().newFloatBlockBuilder(positions)) {
+            for (int i = 0; i < positions; i++) {
+                if (in.readBoolean()) {
+                    builder.appendNull();
+                } else {
+                    final int valueCount = in.readVInt();
+                    builder.beginPositionEntry();
+                    for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
+                        builder.appendFloat(in.readFloat());
+                    }
+                    builder.endPositionEntry();
+                }
+            }
+            return builder.build();
+        }
+    }
+
+    @Override
+    default void writeTo(StreamOutput out) throws IOException {
+        FloatVector vector = asVector();
+        final var version = out.getTransportVersion();
+        if (vector != null) {
+            out.writeByte(SERIALIZE_BLOCK_VECTOR);
+            vector.writeTo(out);
+        } else if (version.onOrAfter(TransportVersions.ESQL_SERIALIZE_ARRAY_BLOCK) && this instanceof FloatArrayBlock b) {
+            out.writeByte(SERIALIZE_BLOCK_ARRAY);
+            b.writeArrayBlock(out);
+        } else if (version.onOrAfter(TransportVersions.ESQL_SERIALIZE_BIG_ARRAY) && this instanceof FloatBigArrayBlock b) {
+            out.writeByte(SERIALIZE_BLOCK_BIG_ARRAY);
+            b.writeArrayBlock(out);
+        } else {
+            out.writeByte(SERIALIZE_BLOCK_VALUES);
+            FloatBlock.writeValues(this, out);
+        }
+    }
+
+    private static void writeValues(FloatBlock block, StreamOutput out) throws IOException {
+        final int positions = block.getPositionCount();
+        out.writeVInt(positions);
+        for (int pos = 0; pos < positions; pos++) {
+            if (block.isNull(pos)) {
+                out.writeBoolean(true);
+            } else {
+                out.writeBoolean(false);
+                final int valueCount = block.getValueCount(pos);
+                out.writeVInt(valueCount);
+                for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
+                    out.writeFloat(block.getFloat(block.getFirstValueIndex(pos) + valueIndex));
+                }
+            }
+        }
+    }
+
+    /**
+     * Compares the given object with this block for equality. Returns {@code true} if and only if the
+     * given object is a FloatBlock, and both blocks are {@link #equals(FloatBlock, FloatBlock) equal}.
+     */
+    @Override
+    boolean equals(Object obj);
+
+    /** Returns the hash code of this block, as defined by {@link #hash(FloatBlock)}. */
+    @Override
+    int hashCode();
+
+    /**
+     * Returns {@code true} if the given blocks are equal to each other, otherwise {@code false}.
+     * Two blocks are considered equal if they have the same position count, and contain the same
+     * values (including absent null values) in the same order. This definition ensures that the
+     * equals method works properly across different implementations of the FloatBlock interface.
+     */
+    static boolean equals(FloatBlock block1, FloatBlock block2) {
+        if (block1 == block2) {
+            return true;
+        }
+        final int positions = block1.getPositionCount();
+        if (positions != block2.getPositionCount()) {
+            return false;
+        }
+        for (int pos = 0; pos < positions; pos++) {
+            if (block1.isNull(pos) || block2.isNull(pos)) {
+                if (block1.isNull(pos) != block2.isNull(pos)) {
+                    return false;
+                }
+            } else {
+                final int valueCount = block1.getValueCount(pos);
+                if (valueCount != block2.getValueCount(pos)) {
+                    return false;
+                }
+                final int b1ValueIdx = block1.getFirstValueIndex(pos);
+                final int b2ValueIdx = block2.getFirstValueIndex(pos);
+                for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
+                    if (block1.getFloat(b1ValueIdx + valueIndex) != block2.getFloat(b2ValueIdx + valueIndex)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Generates the hash code for the given block. The hash code is computed from the block's values.
+     * This ensures that {@code block1.equals(block2)} implies that {@code block1.hashCode()==block2.hashCode()}
+     * for any two blocks, {@code block1} and {@code block2}, as required by the general contract of
+     * {@link Object#hashCode}.
+     */
+    static int hash(FloatBlock block) {
+        final int positions = block.getPositionCount();
+        int result = 1;
+        for (int pos = 0; pos < positions; pos++) {
+            if (block.isNull(pos)) {
+                result = 31 * result - 1;
+            } else {
+                final int valueCount = block.getValueCount(pos);
+                result = 31 * result + valueCount;
+                final int firstValueIdx = block.getFirstValueIndex(pos);
+                for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
+                    result = 31 * result + Float.floatToIntBits(block.getFloat(pos));
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Builder for {@link FloatBlock}
+     */
+    sealed interface Builder extends Block.Builder, BlockLoader.FloatBuilder permits FloatBlockBuilder {
+        /**
+         * Appends a float to the current entry.
+         */
+        @Override
+        Builder appendFloat(float value);
+
+        /**
+         * Copy the values in {@code block} from {@code beginInclusive} to
+         * {@code endExclusive} into this builder.
+         */
+        Builder copyFrom(FloatBlock block, int beginInclusive, int endExclusive);
+
+        @Override
+        Builder appendNull();
+
+        @Override
+        Builder beginPositionEntry();
+
+        @Override
+        Builder endPositionEntry();
+
+        @Override
+        Builder copyFrom(Block block, int beginInclusive, int endExclusive);
+
+        @Override
+        Builder mvOrdering(Block.MvOrdering mvOrdering);
+
+        @Override
+        FloatBlock build();
+    }
+}

+ 185 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatBlockBuilder.java

@@ -0,0 +1,185 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+import org.elasticsearch.common.breaker.CircuitBreakingException;
+import org.elasticsearch.common.util.FloatArray;
+
+import java.util.Arrays;
+
+/**
+ * Block build of FloatBlocks.
+ * This class is generated. Do not edit it.
+ */
+final class FloatBlockBuilder extends AbstractBlockBuilder implements FloatBlock.Builder {
+
+    private float[] values;
+
+    FloatBlockBuilder(int estimatedSize, BlockFactory blockFactory) {
+        super(blockFactory);
+        int initialSize = Math.max(estimatedSize, 2);
+        adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize());
+        values = new float[initialSize];
+    }
+
+    @Override
+    public FloatBlockBuilder appendFloat(float value) {
+        ensureCapacity();
+        values[valueCount] = value;
+        hasNonNullValue = true;
+        valueCount++;
+        updatePosition();
+        return this;
+    }
+
+    @Override
+    protected int elementSize() {
+        return Float.BYTES;
+    }
+
+    @Override
+    protected int valuesLength() {
+        return values.length;
+    }
+
+    @Override
+    protected void growValuesArray(int newSize) {
+        values = Arrays.copyOf(values, newSize);
+    }
+
+    @Override
+    public FloatBlockBuilder appendNull() {
+        super.appendNull();
+        return this;
+    }
+
+    @Override
+    public FloatBlockBuilder beginPositionEntry() {
+        super.beginPositionEntry();
+        return this;
+    }
+
+    @Override
+    public FloatBlockBuilder endPositionEntry() {
+        super.endPositionEntry();
+        return this;
+    }
+
+    @Override
+    public FloatBlockBuilder copyFrom(Block block, int beginInclusive, int endExclusive) {
+        if (block.areAllValuesNull()) {
+            for (int p = beginInclusive; p < endExclusive; p++) {
+                appendNull();
+            }
+            return this;
+        }
+        return copyFrom((FloatBlock) block, beginInclusive, endExclusive);
+    }
+
+    /**
+     * Copy the values in {@code block} from {@code beginInclusive} to
+     * {@code endExclusive} into this builder.
+     */
+    public FloatBlockBuilder copyFrom(FloatBlock block, int beginInclusive, int endExclusive) {
+        if (endExclusive > block.getPositionCount()) {
+            throw new IllegalArgumentException("can't copy past the end [" + endExclusive + " > " + block.getPositionCount() + "]");
+        }
+        FloatVector vector = block.asVector();
+        if (vector != null) {
+            copyFromVector(vector, beginInclusive, endExclusive);
+        } else {
+            copyFromBlock(block, beginInclusive, endExclusive);
+        }
+        return this;
+    }
+
+    private void copyFromBlock(FloatBlock block, int beginInclusive, int endExclusive) {
+        for (int p = beginInclusive; p < endExclusive; p++) {
+            if (block.isNull(p)) {
+                appendNull();
+                continue;
+            }
+            int count = block.getValueCount(p);
+            if (count > 1) {
+                beginPositionEntry();
+            }
+            int i = block.getFirstValueIndex(p);
+            for (int v = 0; v < count; v++) {
+                appendFloat(block.getFloat(i++));
+            }
+            if (count > 1) {
+                endPositionEntry();
+            }
+        }
+    }
+
+    private void copyFromVector(FloatVector vector, int beginInclusive, int endExclusive) {
+        for (int p = beginInclusive; p < endExclusive; p++) {
+            appendFloat(vector.getFloat(p));
+        }
+    }
+
+    @Override
+    public FloatBlockBuilder mvOrdering(Block.MvOrdering mvOrdering) {
+        this.mvOrdering = mvOrdering;
+        return this;
+    }
+
+    private FloatBlock buildBigArraysBlock() {
+        final FloatBlock theBlock;
+        final FloatArray array = blockFactory.bigArrays().newFloatArray(valueCount, false);
+        for (int i = 0; i < valueCount; i++) {
+            array.set(i, values[i]);
+        }
+        if (isDense() && singleValued()) {
+            theBlock = new FloatBigArrayVector(array, positionCount, blockFactory).asBlock();
+        } else {
+            theBlock = new FloatBigArrayBlock(array, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory);
+        }
+        /*
+        * Update the breaker with the actual bytes used.
+        * We pass false below even though we've used the bytes. That's weird,
+        * but if we break here we will throw away the used memory, letting
+        * it be deallocated. The exception will bubble up and the builder will
+        * still technically be open, meaning the calling code should close it
+        * which will return all used memory to the breaker.
+        */
+        blockFactory.adjustBreaker(theBlock.ramBytesUsed() - estimatedBytes - array.ramBytesUsed());
+        return theBlock;
+    }
+
+    @Override
+    public FloatBlock build() {
+        try {
+            finish();
+            FloatBlock theBlock;
+            if (hasNonNullValue && positionCount == 1 && valueCount == 1) {
+                theBlock = blockFactory.newConstantFloatBlockWith(values[0], 1, estimatedBytes);
+            } else if (estimatedBytes > blockFactory.maxPrimitiveArrayBytes()) {
+                theBlock = buildBigArraysBlock();
+            } else if (isDense() && singleValued()) {
+                theBlock = blockFactory.newFloatArrayVector(values, positionCount, estimatedBytes).asBlock();
+            } else {
+                theBlock = blockFactory.newFloatArrayBlock(
+                    values, // stylecheck
+                    positionCount,
+                    firstValueIndexes,
+                    nullsMask,
+                    mvOrdering,
+                    estimatedBytes
+                );
+            }
+            built();
+            return theBlock;
+        } catch (CircuitBreakingException e) {
+            close();
+            throw e;
+        }
+    }
+}

+ 96 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatLookup.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.compute.operator.Operator;
+import org.elasticsearch.core.ReleasableIterator;
+import org.elasticsearch.core.Releasables;
+
+/**
+ * Generic {@link Block#lookup} implementation {@link FloatBlock}s.
+ * This class is generated. Do not edit it.
+ */
+final class FloatLookup implements ReleasableIterator<FloatBlock> {
+    private final FloatBlock values;
+    private final IntBlock positions;
+    private final long targetByteSize;
+    private int position;
+
+    private float first;
+    private int valuesInPosition;
+
+    FloatLookup(FloatBlock values, IntBlock positions, ByteSizeValue targetBlockSize) {
+        values.incRef();
+        positions.incRef();
+        this.values = values;
+        this.positions = positions;
+        this.targetByteSize = targetBlockSize.getBytes();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return position < positions.getPositionCount();
+    }
+
+    @Override
+    public FloatBlock next() {
+        try (FloatBlock.Builder builder = positions.blockFactory().newFloatBlockBuilder(positions.getTotalValueCount())) {
+            int count = 0;
+            while (position < positions.getPositionCount()) {
+                int start = positions.getFirstValueIndex(position);
+                int end = start + positions.getValueCount(position);
+                valuesInPosition = 0;
+                for (int i = start; i < end; i++) {
+                    copy(builder, positions.getInt(i));
+                }
+                switch (valuesInPosition) {
+                    case 0 -> builder.appendNull();
+                    case 1 -> builder.appendFloat(first);
+                    default -> builder.endPositionEntry();
+                }
+                position++;
+                // TOOD what if the estimate is super huge? should we break even with less than MIN_TARGET?
+                if (++count > Operator.MIN_TARGET_PAGE_SIZE && builder.estimatedBytes() < targetByteSize) {
+                    break;
+                }
+            }
+            return builder.build();
+        }
+    }
+
+    private void copy(FloatBlock.Builder builder, int valuePosition) {
+        if (valuePosition >= values.getPositionCount()) {
+            return;
+        }
+        int start = values.getFirstValueIndex(valuePosition);
+        int end = start + values.getValueCount(valuePosition);
+        for (int i = start; i < end; i++) {
+            if (valuesInPosition == 0) {
+                first = values.getFloat(i);
+                valuesInPosition++;
+                continue;
+            }
+            if (valuesInPosition == 1) {
+                builder.beginPositionEntry();
+                builder.appendFloat(first);
+            }
+            if (valuesInPosition > Block.MAX_LOOKUP) {
+                // TODO replace this with a warning and break
+                throw new IllegalArgumentException("Found a single entry with " + valuesInPosition + " entries");
+            }
+            builder.appendFloat(values.getFloat(i));
+            valuesInPosition++;
+        }
+    }
+
+    @Override
+    public void close() {
+        Releasables.close(values, positions);
+    }
+}

+ 157 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVector.java

@@ -0,0 +1,157 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.elasticsearch.TransportVersions;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+
+import java.io.IOException;
+
+/**
+ * Vector that stores float values.
+ * This class is generated. Do not edit it.
+ */
+public sealed interface FloatVector extends Vector permits ConstantFloatVector, FloatArrayVector, FloatBigArrayVector, ConstantNullVector {
+
+    float getFloat(int position);
+
+    @Override
+    FloatBlock asBlock();
+
+    @Override
+    FloatVector filter(int... positions);
+
+    @Override
+    ReleasableIterator<? extends FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize);
+
+    /**
+     * Compares the given object with this vector for equality. Returns {@code true} if and only if the
+     * given object is a FloatVector, and both vectors are {@link #equals(FloatVector, FloatVector) equal}.
+     */
+    @Override
+    boolean equals(Object obj);
+
+    /** Returns the hash code of this vector, as defined by {@link #hash(FloatVector)}. */
+    @Override
+    int hashCode();
+
+    /**
+     * Returns {@code true} if the given vectors are equal to each other, otherwise {@code false}.
+     * Two vectors are considered equal if they have the same position count, and contain the same
+     * values in the same order. This definition ensures that the equals method works properly
+     * across different implementations of the FloatVector interface.
+     */
+    static boolean equals(FloatVector vector1, FloatVector vector2) {
+        final int positions = vector1.getPositionCount();
+        if (positions != vector2.getPositionCount()) {
+            return false;
+        }
+        for (int pos = 0; pos < positions; pos++) {
+            if (vector1.getFloat(pos) != vector2.getFloat(pos)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Generates the hash code for the given vector. The hash code is computed from the vector's values.
+     * This ensures that {@code vector1.equals(vector2)} implies that {@code vector1.hashCode()==vector2.hashCode()}
+     * for any two vectors, {@code vector1} and {@code vector2}, as required by the general contract of
+     * {@link Object#hashCode}.
+     */
+    static int hash(FloatVector vector) {
+        final int len = vector.getPositionCount();
+        int result = 1;
+        for (int pos = 0; pos < len; pos++) {
+            result = 31 * result + Float.floatToIntBits(vector.getFloat(pos));
+        }
+        return result;
+    }
+
+    /** Deserializes a Vector from the given stream input. */
+    static FloatVector readFrom(BlockFactory blockFactory, StreamInput in) throws IOException {
+        final int positions = in.readVInt();
+        final byte serializationType = in.readByte();
+        return switch (serializationType) {
+            case SERIALIZE_VECTOR_VALUES -> readValues(positions, in, blockFactory);
+            case SERIALIZE_VECTOR_CONSTANT -> blockFactory.newConstantFloatVector(in.readFloat(), positions);
+            case SERIALIZE_VECTOR_ARRAY -> FloatArrayVector.readArrayVector(positions, in, blockFactory);
+            case SERIALIZE_VECTOR_BIG_ARRAY -> FloatBigArrayVector.readArrayVector(positions, in, blockFactory);
+            default -> {
+                assert false : "invalid vector serialization type [" + serializationType + "]";
+                throw new IllegalStateException("invalid vector serialization type [" + serializationType + "]");
+            }
+        };
+    }
+
+    /** Serializes this Vector to the given stream output. */
+    default void writeTo(StreamOutput out) throws IOException {
+        final int positions = getPositionCount();
+        final var version = out.getTransportVersion();
+        out.writeVInt(positions);
+        if (isConstant() && positions > 0) {
+            out.writeByte(SERIALIZE_VECTOR_CONSTANT);
+            out.writeFloat(getFloat(0));
+        } else if (version.onOrAfter(TransportVersions.ESQL_SERIALIZE_ARRAY_VECTOR) && this instanceof FloatArrayVector v) {
+            out.writeByte(SERIALIZE_VECTOR_ARRAY);
+            v.writeArrayVector(positions, out);
+        } else if (version.onOrAfter(TransportVersions.ESQL_SERIALIZE_BIG_VECTOR) && this instanceof FloatBigArrayVector v) {
+            out.writeByte(SERIALIZE_VECTOR_BIG_ARRAY);
+            v.writeArrayVector(positions, out);
+        } else {
+            out.writeByte(SERIALIZE_VECTOR_VALUES);
+            writeValues(this, positions, out);
+        }
+    }
+
+    private static FloatVector readValues(int positions, StreamInput in, BlockFactory blockFactory) throws IOException {
+        try (var builder = blockFactory.newFloatVectorFixedBuilder(positions)) {
+            for (int i = 0; i < positions; i++) {
+                builder.appendFloat(i, in.readFloat());
+            }
+            return builder.build();
+        }
+    }
+
+    private static void writeValues(FloatVector v, int positions, StreamOutput out) throws IOException {
+        for (int i = 0; i < positions; i++) {
+            out.writeFloat(v.getFloat(i));
+        }
+    }
+
+    /**
+     * A builder that grows as needed.
+     */
+    sealed interface Builder extends Vector.Builder permits FloatVectorBuilder, FixedBuilder {
+        /**
+         * Appends a float to the current entry.
+         */
+        Builder appendFloat(float value);
+
+        @Override
+        FloatVector build();
+    }
+
+    /**
+     * A builder that never grows.
+     */
+    sealed interface FixedBuilder extends Builder permits FloatVectorFixedBuilder {
+        /**
+         * Appends a float to the current entry.
+         */
+        @Override
+        FixedBuilder appendFloat(float value);
+
+        FixedBuilder appendFloat(int index, float value);
+
+    }
+}

+ 103 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorBlock.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.ReleasableIterator;
+import org.elasticsearch.core.Releasables;
+
+/**
+ * Block view of a {@link FloatVector}. Cannot represent multi-values or nulls.
+ * This class is generated. Do not edit it.
+ */
+public final class FloatVectorBlock extends AbstractVectorBlock implements FloatBlock {
+
+    private final FloatVector vector;
+
+    /**
+     * @param vector considered owned by the current block; must not be used in any other {@code Block}
+     */
+    FloatVectorBlock(FloatVector vector) {
+        this.vector = vector;
+    }
+
+    @Override
+    public FloatVector asVector() {
+        return vector;
+    }
+
+    @Override
+    public float getFloat(int valueIndex) {
+        return vector.getFloat(valueIndex);
+    }
+
+    @Override
+    public int getPositionCount() {
+        return vector.getPositionCount();
+    }
+
+    @Override
+    public ElementType elementType() {
+        return vector.elementType();
+    }
+
+    @Override
+    public FloatBlock filter(int... positions) {
+        return vector.filter(positions).asBlock();
+    }
+
+    @Override
+    public ReleasableIterator<? extends FloatBlock> lookup(IntBlock positions, ByteSizeValue targetBlockSize) {
+        return vector.lookup(positions, targetBlockSize);
+    }
+
+    @Override
+    public FloatBlock expand() {
+        incRef();
+        return this;
+    }
+
+    @Override
+    public long ramBytesUsed() {
+        return vector.ramBytesUsed();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof FloatBlock that) {
+            return FloatBlock.equals(this, that);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return FloatBlock.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[vector=" + vector + "]";
+    }
+
+    @Override
+    public void closeInternal() {
+        assert (vector.isReleased() == false) : "can't release block [" + this + "] containing already released vector";
+        Releasables.closeExpectNoException(vector);
+    }
+
+    @Override
+    public void allowPassingToDifferentDriver() {
+        vector.allowPassingToDifferentDriver();
+    }
+
+    @Override
+    public BlockFactory blockFactory() {
+        return vector.blockFactory();
+    }
+}

+ 65 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorBuilder.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import java.util.Arrays;
+
+/**
+ * Builder for {@link FloatVector}s that grows as needed.
+ * This class is generated. Do not edit it.
+ */
+final class FloatVectorBuilder extends AbstractVectorBuilder implements FloatVector.Builder {
+
+    private float[] values;
+
+    FloatVectorBuilder(int estimatedSize, BlockFactory blockFactory) {
+        super(blockFactory);
+        int initialSize = Math.max(estimatedSize, 2);
+        adjustBreaker(initialSize);
+        values = new float[Math.max(estimatedSize, 2)];
+    }
+
+    @Override
+    public FloatVectorBuilder appendFloat(float value) {
+        ensureCapacity();
+        values[valueCount] = value;
+        valueCount++;
+        return this;
+    }
+
+    @Override
+    protected int elementSize() {
+        return Float.BYTES;
+    }
+
+    @Override
+    protected int valuesLength() {
+        return values.length;
+    }
+
+    @Override
+    protected void growValuesArray(int newSize) {
+        values = Arrays.copyOf(values, newSize);
+    }
+
+    @Override
+    public FloatVector build() {
+        finish();
+        FloatVector vector;
+        if (valueCount == 1) {
+            vector = blockFactory.newConstantFloatBlockWith(values[0], 1, estimatedBytes).asVector();
+        } else {
+            if (values.length - valueCount > 1024 || valueCount < (values.length / 2)) {
+                values = Arrays.copyOf(values, valueCount);
+            }
+            vector = blockFactory.newFloatArrayVector(values, valueCount, estimatedBytes);
+        }
+        built();
+        return vector;
+    }
+}

+ 90 - 0
x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/FloatVectorFixedBuilder.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.apache.lucene.util.RamUsageEstimator;
+
+/**
+ * Builder for {@link FloatVector}s that never grows. Prefer this to
+ * {@link FloatVectorBuilder} if you know the precise size up front because
+ * it's faster.
+ * This class is generated. Do not edit it.
+ */
+final class FloatVectorFixedBuilder implements FloatVector.FixedBuilder {
+    private final BlockFactory blockFactory;
+    private final float[] values;
+    private final long preAdjustedBytes;
+    /**
+     * The next value to write into. {@code -1} means the vector has already
+     * been built.
+     */
+    private int nextIndex;
+
+    private boolean closed;
+
+    FloatVectorFixedBuilder(int size, BlockFactory blockFactory) {
+        preAdjustedBytes = ramBytesUsed(size);
+        blockFactory.adjustBreaker(preAdjustedBytes);
+        this.blockFactory = blockFactory;
+        this.values = new float[size];
+    }
+
+    @Override
+    public FloatVectorFixedBuilder appendFloat(float value) {
+        values[nextIndex++] = value;
+        return this;
+    }
+
+    @Override
+    public FloatVectorFixedBuilder appendFloat(int idx, float value) {
+        values[idx] = value;
+        return this;
+    }
+
+    private static long ramBytesUsed(int size) {
+        return size == 1
+            ? ConstantFloatVector.RAM_BYTES_USED
+            : FloatArrayVector.BASE_RAM_BYTES_USED + RamUsageEstimator.alignObjectSize(
+                (long) RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + size * Float.BYTES
+            );
+    }
+
+    @Override
+    public long estimatedBytes() {
+        return ramBytesUsed(values.length);
+    }
+
+    @Override
+    public FloatVector build() {
+        if (closed) {
+            throw new IllegalStateException("already closed");
+        }
+        closed = true;
+        FloatVector vector;
+        if (values.length == 1) {
+            vector = blockFactory.newConstantFloatBlockWith(values[0], 1, preAdjustedBytes).asVector();
+        } else {
+            vector = blockFactory.newFloatArrayVector(values, values.length, preAdjustedBytes);
+        }
+        assert vector.ramBytesUsed() == preAdjustedBytes : "fixed Builders should estimate the exact ram bytes used";
+        return vector;
+    }
+
+    @Override
+    public void close() {
+        if (closed == false) {
+            // If nextIndex < 0 we've already built the vector
+            closed = true;
+            blockFactory.adjustBreaker(-preAdjustedBytes);
+        }
+    }
+
+    boolean isReleased() {
+        return closed;
+    }
+}

+ 52 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java

@@ -235,6 +235,58 @@ public class BlockFactory {
         return v;
     }
 
+    public FloatBlock.Builder newFloatBlockBuilder(int estimatedSize) {
+        return new FloatBlockBuilder(estimatedSize, this);
+    }
+
+    public final FloatBlock newFloatArrayBlock(float[] values, int pc, int[] firstValueIndexes, BitSet nulls, MvOrdering mvOrdering) {
+        return newFloatArrayBlock(values, pc, firstValueIndexes, nulls, mvOrdering, 0L);
+    }
+
+    public FloatBlock newFloatArrayBlock(float[] values, int pc, int[] fvi, BitSet nulls, MvOrdering mvOrdering, long preAdjustedBytes) {
+        var b = new FloatArrayBlock(values, pc, fvi, nulls, mvOrdering, this);
+        adjustBreaker(b.ramBytesUsed() - preAdjustedBytes);
+        return b;
+    }
+
+    public FloatVector.Builder newFloatVectorBuilder(int estimatedSize) {
+        return new FloatVectorBuilder(estimatedSize, this);
+    }
+
+    /**
+     * Build a {@link FloatVector.FixedBuilder} that never grows.
+     */
+    public FloatVector.FixedBuilder newFloatVectorFixedBuilder(int size) {
+        return new FloatVectorFixedBuilder(size, this);
+    }
+
+    public final FloatVector newFloatArrayVector(float[] values, int positionCount) {
+        return newFloatArrayVector(values, positionCount, 0L);
+    }
+
+    public FloatVector newFloatArrayVector(float[] values, int positionCount, long preAdjustedBytes) {
+        var b = new FloatArrayVector(values, positionCount, this);
+        adjustBreaker(b.ramBytesUsed() - preAdjustedBytes);
+        return b;
+    }
+
+    public final FloatBlock newConstantFloatBlockWith(float value, int positions) {
+        return newConstantFloatBlockWith(value, positions, 0L);
+    }
+
+    public FloatBlock newConstantFloatBlockWith(float value, int positions, long preAdjustedBytes) {
+        var b = new ConstantFloatVector(value, positions, this).asBlock();
+        adjustBreaker(b.ramBytesUsed() - preAdjustedBytes);
+        return b;
+    }
+
+    public FloatVector newConstantFloatVector(float value, int positions) {
+        adjustBreaker(ConstantFloatVector.RAM_BYTES_USED);
+        var v = new ConstantFloatVector(value, positions, this);
+        assert v.ramBytesUsed() == ConstantFloatVector.RAM_BYTES_USED;
+        return v;
+    }
+
     public LongBlock.Builder newLongBlockBuilder(int estimatedSize) {
         return new LongBlockBuilder(estimatedSize, this);
     }

+ 2 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java

@@ -208,6 +208,7 @@ public final class BlockUtils {
             case LONG -> ((LongBlock.Builder) builder).appendLong((Long) val);
             case INT -> ((IntBlock.Builder) builder).appendInt((Integer) val);
             case BYTES_REF -> ((BytesRefBlock.Builder) builder).appendBytesRef(toBytesRef(val));
+            case FLOAT -> ((FloatBlock.Builder) builder).appendFloat((Float) val);
             case DOUBLE -> ((DoubleBlock.Builder) builder).appendDouble((Double) val);
             case BOOLEAN -> ((BooleanBlock.Builder) builder).appendBoolean((Boolean) val);
             default -> throw new UnsupportedOperationException("unsupported element type [" + type + "]");
@@ -265,6 +266,7 @@ public final class BlockUtils {
             case BOOLEAN -> ((BooleanBlock) block).getBoolean(offset);
             case BYTES_REF -> BytesRef.deepCopyOf(((BytesRefBlock) block).getBytesRef(offset, new BytesRef()));
             case DOUBLE -> ((DoubleBlock) block).getDouble(offset);
+            case FLOAT -> ((FloatBlock) block).getFloat(offset);
             case INT -> ((IntBlock) block).getInt(offset);
             case LONG -> ((LongBlock) block).getLong(offset);
             case NULL -> null;

+ 7 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java

@@ -25,6 +25,7 @@ final class ConstantNullBlock extends AbstractNonThreadSafeRefCounted
         BooleanBlock,
         IntBlock,
         LongBlock,
+        FloatBlock,
         DoubleBlock,
         BytesRefBlock {
 
@@ -221,6 +222,12 @@ final class ConstantNullBlock extends AbstractNonThreadSafeRefCounted
         throw new UnsupportedOperationException("null block");
     }
 
+    @Override
+    public float getFloat(int valueIndex) {
+        assert false : "null block";
+        throw new UnsupportedOperationException("null block");
+    }
+
     @Override
     public double getDouble(int valueIndex) {
         assert false : "null block";

+ 14 - 1
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullVector.java

@@ -17,7 +17,14 @@ import java.io.IOException;
 /**
  * This vector is never instantiated. This class serves as a type holder for {@link ConstantNullBlock#asVector()}.
  */
-public final class ConstantNullVector extends AbstractVector implements BooleanVector, IntVector, LongVector, DoubleVector, BytesRefVector {
+public final class ConstantNullVector extends AbstractVector
+    implements
+        BooleanVector,
+        BytesRefVector,
+        DoubleVector,
+        FloatVector,
+        IntVector,
+        LongVector {
 
     private ConstantNullVector(int positionCount, BlockFactory blockFactory) {
         super(positionCount, blockFactory);
@@ -65,6 +72,12 @@ public final class ConstantNullVector extends AbstractVector implements BooleanV
         throw new UnsupportedOperationException("null vector");
     }
 
+    @Override
+    public float getFloat(int position) {
+        assert false : "null vector";
+        throw new UnsupportedOperationException("null vector");
+    }
+
     @Override
     public double getDouble(int position) {
         assert false : "null vector";

+ 3 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java

@@ -16,6 +16,7 @@ public enum ElementType {
     BOOLEAN(BlockFactory::newBooleanBlockBuilder),
     INT(BlockFactory::newIntBlockBuilder),
     LONG(BlockFactory::newLongBlockBuilder),
+    FLOAT(BlockFactory::newFloatBlockBuilder),
     DOUBLE(BlockFactory::newDoubleBlockBuilder),
     /**
      * Blocks containing only null values.
@@ -62,6 +63,8 @@ public enum ElementType {
             elementType = INT;
         } else if (type == Long.class) {
             elementType = LONG;
+        } else if (type == Float.class) {
+            elementType = FLOAT;
         } else if (type == Double.class) {
             elementType = DOUBLE;
         } else if (type == String.class || type == BytesRef.class) {

+ 8 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BigArrayVector.java.st

@@ -46,6 +46,9 @@ $endif$
     }
 
     static $Type$BigArrayVector readArrayVector(int positions, StreamInput in, BlockFactory blockFactory) throws IOException {
+$if(float)$
+        throw new UnsupportedOperationException();
+$else$
 $if(boolean)$
         $Array$ values = new BitArray(blockFactory.bigArrays(), true, in);
 $else$
@@ -65,10 +68,15 @@ $endif$
                 values.close();
             }
         }
+$endif$
     }
 
     void writeArrayVector(int positions, StreamOutput out) throws IOException {
+$if(float)$
+        throw new UnsupportedOperationException();
+$else$
         values.writeTo(out);
+$endif$
     }
 
     @Override

+ 2 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-Block.java.st

@@ -246,6 +246,8 @@ $elseif(boolean)$
                     result = 31 * result + Boolean.hashCode(block.getBoolean(firstValueIdx + valueIndex));
 $elseif(int)$
                     result = 31 * result + block.getInt(firstValueIdx + valueIndex);
+$elseif(float)$
+                    result = 31 * result + Float.floatToIntBits(block.getFloat(pos));
 $elseif(long)$
                     long element = block.getLong(firstValueIdx + valueIndex);
                     result = 31 * result + (int) (element ^ (element >>> 32));

+ 2 - 0
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-Vector.java.st

@@ -121,6 +121,8 @@ $elseif(boolean)$
             result = 31 * result + Boolean.hashCode(vector.getBoolean(pos));
 $elseif(int)$
             result = 31 * result + vector.getInt(pos);
+$elseif(float)$
+            result = 31 * result + Float.floatToIntBits(vector.getFloat(pos));
 $elseif(long)$
             long element = vector.getLong(pos);
             result = 31 * result + (int) (element ^ (element >>> 32));

+ 1 - 1
x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperatorFactories.java

@@ -147,7 +147,7 @@ public final class TimeSeriesAggregationOperatorFactories {
                     case INT -> new org.elasticsearch.compute.aggregation.ValuesIntAggregatorFunctionSupplier(channels);
                     case LONG -> new org.elasticsearch.compute.aggregation.ValuesLongAggregatorFunctionSupplier(channels);
                     case BOOLEAN -> new org.elasticsearch.compute.aggregation.ValuesBooleanAggregatorFunctionSupplier(channels);
-                    case NULL, DOC, COMPOSITE, UNKNOWN -> throw new IllegalArgumentException("unsupported grouping type");
+                    case FLOAT, NULL, DOC, COMPOSITE, UNKNOWN -> throw new IllegalArgumentException("unsupported grouping type");
                 });
                 aggregators.add(aggregatorSupplier.groupingAggregatorFactory(AggregatorMode.SINGLE));
             }

+ 115 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java

@@ -78,6 +78,10 @@ public class BasicBlockTests extends ESTestCase {
         assertZeroPositionsAndRelease(bf.newLongBlockBuilder(0).build());
         assertZeroPositionsAndRelease(bf.newLongArrayVector(new long[] {}, 0));
         assertZeroPositionsAndRelease(bf.newLongVectorBuilder(0).build());
+        assertZeroPositionsAndRelease(bf.newFloatArrayBlock(new float[] {}, 0, new int[] { 0 }, new BitSet(), randomOrdering()));
+        assertZeroPositionsAndRelease(bf.newFloatBlockBuilder(0).build());
+        assertZeroPositionsAndRelease(bf.newFloatArrayVector(new float[] {}, 0));
+        assertZeroPositionsAndRelease(bf.newFloatVectorBuilder(0).build());
         assertZeroPositionsAndRelease(bf.newDoubleArrayBlock(new double[] {}, 0, new int[] { 0 }, new BitSet(), randomOrdering()));
         assertZeroPositionsAndRelease(bf.newDoubleBlockBuilder(0).build());
         assertZeroPositionsAndRelease(bf.newDoubleArrayVector(new double[] {}, 0));
@@ -116,6 +120,17 @@ public class BasicBlockTests extends ESTestCase {
         }
     }
 
+    public void testSmallSingleValueDenseGrowthFloat() {
+        for (int initialSize : List.of(0, 1, 2, 3, 4, 5)) {
+            try (var blockBuilder = blockFactory.newFloatBlockBuilder(initialSize)) {
+                IntStream.range(0, 10).forEach(blockBuilder::appendFloat);
+                FloatBlock block = blockBuilder.build();
+                assertSingleValueDenseBlock(block);
+                block.close();
+            }
+        }
+    }
+
     public void testSmallSingleValueDenseGrowthDouble() {
         for (int initialSize : List.of(0, 1, 2, 3, 4, 5)) {
             try (var blockBuilder = blockFactory.newDoubleBlockBuilder(initialSize)) {
@@ -487,6 +502,100 @@ public class BasicBlockTests extends ESTestCase {
         }
     }
 
+    public void testFloatBlock() {
+        for (int i = 0; i < 1000; i++) {
+            assertThat(breaker.getUsed(), is(0L));
+            int positionCount = randomIntBetween(1, 16 * 1024);
+            FloatBlock block;
+            if (randomBoolean()) {
+                final int builderEstimateSize = randomBoolean() ? randomIntBetween(1, positionCount) : positionCount;
+                var blockBuilder = blockFactory.newFloatBlockBuilder(builderEstimateSize);
+                IntStream.range(0, positionCount).forEach(blockBuilder::appendFloat);
+                block = blockBuilder.build();
+            } else {
+                float[] fa = new float[positionCount];
+                IntStream.range(0, positionCount).forEach(v -> fa[v] = (float) v);
+                block = blockFactory.newFloatArrayVector(fa, positionCount).asBlock();
+            }
+
+            assertThat(positionCount, is(block.getPositionCount()));
+            assertThat(0f, is(block.getFloat(0)));
+            assertThat((float) positionCount - 1, is(block.getFloat(positionCount - 1)));
+            int pos = (int) block.getFloat(randomPosition(positionCount));
+            assertThat((float) pos, is(block.getFloat(pos)));
+            assertSingleValueDenseBlock(block);
+            if (positionCount > 2) {
+                assertLookup(block, positions(blockFactory, 1, 2, new int[] { 1, 2 }), List.of(List.of(1f), List.of(2f), List.of(1f, 2f)));
+            }
+            assertLookup(block, positions(blockFactory, positionCount + 1000), singletonList(null));
+            assertEmptyLookup(blockFactory, block);
+
+            try (FloatBlock.Builder blockBuilder = blockFactory.newFloatBlockBuilder(1)) {
+                FloatBlock copy = blockBuilder.copyFrom(block, 0, block.getPositionCount()).build();
+                assertThat(copy, equalTo(block));
+                releaseAndAssertBreaker(block, copy);
+            }
+
+            if (positionCount > 1) {
+                assertNullValues(
+                    positionCount,
+                    blockFactory::newFloatBlockBuilder,
+                    FloatBlock.Builder::appendFloat,
+                    position -> (float) position,
+                    FloatBlock.Builder::build,
+                    (randomNonNullPosition, b) -> {
+                        assertThat((float) randomNonNullPosition, is(b.getFloat(randomNonNullPosition.intValue())));
+                    }
+                );
+            }
+
+            try (
+                DoubleVector.Builder vectorBuilder = blockFactory.newDoubleVectorBuilder(
+                    randomBoolean() ? randomIntBetween(1, positionCount) : positionCount
+                )
+            ) {
+                IntStream.range(0, positionCount).mapToDouble(ii -> 1.0 / ii).forEach(vectorBuilder::appendDouble);
+                DoubleVector vector = vectorBuilder.build();
+                assertSingleValueDenseBlock(vector.asBlock());
+                releaseAndAssertBreaker(vector.asBlock());
+            }
+        }
+    }
+
+    public void testConstantFloatBlock() {
+        for (int i = 0; i < 1000; i++) {
+            int positionCount = randomIntBetween(1, 16 * 1024);
+            float value = randomFloat();
+            FloatBlock block = blockFactory.newConstantFloatBlockWith(value, positionCount);
+            assertThat(positionCount, is(block.getPositionCount()));
+            assertThat(value, is(block.getFloat(0)));
+            assertThat(value, is(block.getFloat(positionCount - 1)));
+            assertThat(value, is(block.getFloat(randomPosition(positionCount))));
+            assertSingleValueDenseBlock(block);
+            if (positionCount > 2) {
+                assertLookup(
+                    block,
+                    positions(blockFactory, 1, 2, new int[] { 1, 2 }),
+                    List.of(List.of(value), List.of(value), List.of(value, value))
+                );
+                assertLookup(
+                    block,
+                    positions(blockFactory, 1, 2),
+                    List.of(List.of(value), List.of(value)),
+                    b -> assertThat(b.asVector(), instanceOf(ConstantFloatVector.class))
+                );
+            }
+            assertLookup(
+                block,
+                positions(blockFactory, positionCount + 1000),
+                singletonList(null),
+                b -> assertThat(b, instanceOf(ConstantNullBlock.class))
+            );
+            assertEmptyLookup(blockFactory, block);
+            releaseAndAssertBreaker(block);
+        }
+    }
+
     private void testBytesRefBlock(Supplier<BytesRef> byteArraySupplier, boolean chomp, org.mockito.ThrowingConsumer<BytesRef> assertions) {
         int positionCount = randomIntBetween(1, 16 * 1024);
         BytesRef[] values = new BytesRef[positionCount];
@@ -1030,6 +1139,7 @@ public class BasicBlockTests extends ESTestCase {
                 positionValues.add(switch (block.elementType()) {
                     case INT -> ((IntBlock) block).getInt(i++);
                     case LONG -> ((LongBlock) block).getLong(i++);
+                    case FLOAT -> ((FloatBlock) block).getFloat(i++);
                     case DOUBLE -> ((DoubleBlock) block).getDouble(i++);
                     case BYTES_REF -> ((BytesRefBlock) block).getBytesRef(i++, new BytesRef());
                     case BOOLEAN -> ((BooleanBlock) block).getBoolean(i++);
@@ -1149,6 +1259,11 @@ public class BasicBlockTests extends ESTestCase {
                             valuesAtPosition.add(l);
                             ((LongBlock.Builder) builder).appendLong(l);
                         }
+                        case FLOAT -> {
+                            float f = randomFloat();
+                            valuesAtPosition.add(f);
+                            ((FloatBlock.Builder) builder).appendFloat(f);
+                        }
                         case DOUBLE -> {
                             double d = randomDouble();
                             valuesAtPosition.add(d);

+ 90 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java

@@ -373,6 +373,96 @@ public class BlockFactoryTests extends ESTestCase {
         }
     }
 
+    public void testFloatBlockBuilderWithPossiblyLargeEstimateEmpty() {
+        var builder = blockFactory.newFloatBlockBuilder(randomIntBetween(0, 2048));
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        var block = builder.build();
+        releaseAndAssertBreaker(block);
+
+        block = blockFactory.newFloatArrayBlock(new float[] {}, 0, new int[] { 0 }, new BitSet(), randomOrdering());
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(block);
+    }
+
+    public void testFloatBlockBuilderWithPossiblyLargeEstimateSingle() {
+        var builder = blockFactory.newFloatBlockBuilder(randomIntBetween(0, 2048));
+        builder.appendFloat(randomFloat());
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        var block = builder.build();
+        releaseAndAssertBreaker(block);
+
+        block = blockFactory.newFloatArrayBlock(new float[] { randomFloat() }, 1, new int[] { 0, 1 }, new BitSet(), randomOrdering());
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(block);
+
+        block = blockFactory.newConstantFloatBlockWith(randomFloat(), randomIntBetween(1, 2048));
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(block);
+    }
+
+    public void testFloatBlockBuilderWithPossiblyLargeEstimateRandom() {
+        for (int i = 0; i < 1000; i++) {
+            assertThat(breaker.getUsed(), is(0L));
+            var builder = blockFactory.newFloatBlockBuilder(randomIntBetween(0, 2048));
+
+            builder.appendFloat(randomFloat());
+            if (randomBoolean()) {  // null-ness
+                builder.appendNull();
+            }
+            if (randomBoolean()) { // mv-ness
+                builder.beginPositionEntry();
+                builder.appendFloat(randomFloat());
+                builder.appendFloat(randomFloat());
+                builder.endPositionEntry();
+            }
+            builder.appendFloat(randomFloat());
+            assertThat(breaker.getUsed(), greaterThan(0L));
+            var block = builder.build();
+            releaseAndAssertBreaker(block);
+        }
+    }
+
+    public void testFloatVectorBuilderWithPossiblyLargeEstimateEmpty() {
+        var builder = blockFactory.newFloatVectorBuilder(randomIntBetween(0, 2048));
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        var vector = builder.build();
+        releaseAndAssertBreaker(vector);
+
+        vector = blockFactory.newFloatArrayVector(new float[] {}, 0);
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(vector);
+    }
+
+    public void testFloatVectorBuilderWithPossiblyLargeEstimateSingle() {
+        var builder = blockFactory.newFloatVectorBuilder(randomIntBetween(0, 2048));
+        builder.appendFloat(randomFloat());
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        var vector = builder.build();
+        releaseAndAssertBreaker(vector);
+
+        vector = blockFactory.newFloatArrayVector(new float[] { randomFloat() }, 1);
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(vector);
+
+        vector = blockFactory.newConstantFloatBlockWith(randomFloat(), randomIntBetween(1, 2048)).asVector();
+        assertThat(breaker.getUsed(), greaterThan(0L));
+        releaseAndAssertBreaker(vector);
+    }
+
+    public void testFloatVectorBuilderWithPossiblyLargeEstimateRandom() {
+        for (int i = 0; i < 1000; i++) {
+            assertThat(breaker.getUsed(), is(0L));
+            var builder = blockFactory.newFloatVectorBuilder(randomIntBetween(0, 2048));
+            builder.appendFloat(randomFloat());
+            if (randomBoolean()) {  // constant-ness or not
+                builder.appendFloat(randomFloat());
+            }
+            assertThat(breaker.getUsed(), greaterThan(0L));
+            var vector = builder.build();
+            releaseAndAssertBreaker(vector);
+        }
+    }
+
     public void testBooleanBlockBuilderWithPossiblyLargeEstimateEmpty() {
         var builder = blockFactory.newBooleanBlockBuilder(randomIntBetween(0, 2048));
         assertThat(breaker.getUsed(), greaterThan(0L));

+ 21 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java

@@ -31,6 +31,7 @@ public class BlockTestUtils {
         return switch (e) {
             case INT -> randomInt();
             case LONG -> randomLong();
+            case FLOAT -> Float.intBitsToFloat(randomInt());
             case DOUBLE -> randomDouble();
             case BYTES_REF -> new BytesRef(randomRealisticUnicodeOfCodepointLengthBetween(0, 5));   // TODO: also test spatial WKB
             case BOOLEAN -> randomBoolean();
@@ -90,6 +91,26 @@ public class BlockTestUtils {
                 return;
             }
         }
+        if (builder instanceof FloatBlock.Builder b) {
+            if (value instanceof Float v) {
+                b.appendFloat(v);
+                return;
+            }
+            if (value instanceof List<?> l) {
+                switch (l.size()) {
+                    case 0 -> b.appendNull();
+                    case 1 -> b.appendFloat((Float) l.get(0));
+                    default -> {
+                        b.beginPositionEntry();
+                        for (Object o : l) {
+                            b.appendFloat((Float) o);
+                        }
+                        b.endPositionEntry();
+                    }
+                }
+                return;
+            }
+        }
         if (builder instanceof DoubleBlock.Builder b) {
             if (value instanceof Double v) {
                 b.appendDouble(v);

+ 8 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockValueAsserter.java

@@ -32,6 +32,7 @@ public class BlockValueAsserter {
                 switch (block.elementType()) {
                     case INT -> assertIntRowValues((IntBlock) block, firstValueIndex, valueCount, expectedRowValues);
                     case LONG -> assertLongRowValues((LongBlock) block, firstValueIndex, valueCount, expectedRowValues);
+                    case FLOAT -> assertFloatRowValues((FloatBlock) block, firstValueIndex, valueCount, expectedRowValues);
                     case DOUBLE -> assertDoubleRowValues((DoubleBlock) block, firstValueIndex, valueCount, expectedRowValues);
                     case BYTES_REF -> assertBytesRefRowValues((BytesRefBlock) block, firstValueIndex, valueCount, expectedRowValues);
                     case BOOLEAN -> assertBooleanRowValues((BooleanBlock) block, firstValueIndex, valueCount, expectedRowValues);
@@ -55,6 +56,13 @@ public class BlockValueAsserter {
         }
     }
 
+    private static void assertFloatRowValues(FloatBlock block, int firstValueIndex, int valueCount, List<Object> expectedRowValues) {
+        for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
+            float expectedValue = ((Number) expectedRowValues.get(valueIndex)).floatValue();
+            assertThat(block.getFloat(firstValueIndex + valueIndex), is(equalTo(expectedValue)));
+        }
+    }
+
     private static void assertDoubleRowValues(DoubleBlock block, int firstValueIndex, int valueCount, List<Object> expectedRowValues) {
         for (int valueIndex = 0; valueIndex < valueCount; valueIndex++) {
             double expectedValue = ((Number) expectedRowValues.get(valueIndex)).doubleValue();

+ 312 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/FloatBlockEqualityTests.java

@@ -0,0 +1,312 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.compute.data;
+
+import org.elasticsearch.compute.operator.ComputeTestCase;
+import org.elasticsearch.core.Releasables;
+
+import java.util.BitSet;
+import java.util.List;
+
+public class FloatBlockEqualityTests extends ComputeTestCase {
+
+    static final BlockFactory blockFactory = TestBlockFactory.getNonBreakingInstance();
+
+    public void testEmptyVector() {
+        // all these "empty" vectors should be equivalent
+        List<FloatVector> vectors = List.of(
+            blockFactory.newFloatArrayVector(new float[] {}, 0),
+            blockFactory.newFloatArrayVector(new float[] { 0 }, 0),
+            blockFactory.newConstantFloatVector(0, 0),
+            blockFactory.newConstantFloatBlockWith(0, 0).filter().asVector(),
+            blockFactory.newFloatBlockBuilder(0).build().asVector(),
+            blockFactory.newFloatBlockBuilder(0).appendFloat(1).build().asVector().filter()
+        );
+        assertAllEquals(vectors);
+    }
+
+    public void testEmptyBlock() {
+        // all these "empty" vectors should be equivalent
+        List<FloatBlock> blocks = List.of(
+            blockFactory.newFloatArrayBlock(
+                new float[] {},
+                0,
+                new int[] { 0 },
+                BitSet.valueOf(new byte[] { 0b00 }),
+                randomFrom(Block.MvOrdering.values())
+            ),
+            blockFactory.newFloatArrayBlock(
+                new float[] { 0 },
+                0,
+                new int[] { 0 },
+                BitSet.valueOf(new byte[] { 0b00 }),
+                randomFrom(Block.MvOrdering.values())
+            ),
+            blockFactory.newConstantFloatBlockWith(0, 0),
+            blockFactory.newFloatBlockBuilder(0).build(),
+            blockFactory.newFloatBlockBuilder(0).appendFloat(1).build().filter(),
+            blockFactory.newFloatBlockBuilder(0).appendNull().build().filter()
+        );
+        assertAllEquals(blocks);
+        Releasables.close(blocks);
+    }
+
+    public void testVectorEquality() {
+        // all these vectors should be equivalent
+        List<FloatVector> vectors = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3).asBlock().asVector(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3, 4 }, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3).filter(0, 1, 2),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3, 4 }, 4).filter(0, 1, 2),
+            blockFactory.newFloatArrayVector(new float[] { 0, 1, 2, 3 }, 4).filter(1, 2, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 4, 2, 3 }, 4).filter(0, 2, 3),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(3).build().asVector(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(3).build().asVector().filter(0, 1, 2),
+            blockFactory.newFloatBlockBuilder(3)
+                .appendFloat(1)
+                .appendFloat(4)
+                .appendFloat(2)
+                .appendFloat(3)
+                .build()
+                .filter(0, 2, 3)
+                .asVector(),
+            blockFactory.newFloatBlockBuilder(3)
+                .appendFloat(1)
+                .appendFloat(4)
+                .appendFloat(2)
+                .appendFloat(3)
+                .build()
+                .asVector()
+                .filter(0, 2, 3)
+        );
+        assertAllEquals(vectors);
+
+        // all these constant-like vectors should be equivalent
+        List<FloatVector> moreVectors = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 1, 1, 1 }, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 1, 1 }, 3).asBlock().asVector(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 1, 1, 1 }, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 1, 1 }, 3).filter(0, 1, 2),
+            blockFactory.newFloatArrayVector(new float[] { 1, 1, 1, 4 }, 4).filter(0, 1, 2),
+            blockFactory.newFloatArrayVector(new float[] { 3, 1, 1, 1 }, 4).filter(1, 2, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 4, 1, 1 }, 4).filter(0, 2, 3),
+            blockFactory.newConstantFloatBlockWith(1, 3).asVector(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(1).appendFloat(1).build().asVector(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(1).appendFloat(1).build().asVector().filter(0, 1, 2),
+            blockFactory.newFloatBlockBuilder(3)
+                .appendFloat(1)
+                .appendFloat(4)
+                .appendFloat(1)
+                .appendFloat(1)
+                .build()
+                .filter(0, 2, 3)
+                .asVector(),
+            blockFactory.newFloatBlockBuilder(3)
+                .appendFloat(1)
+                .appendFloat(4)
+                .appendFloat(1)
+                .appendFloat(1)
+                .build()
+                .asVector()
+                .filter(0, 2, 3)
+        );
+        assertAllEquals(moreVectors);
+    }
+
+    public void testBlockEquality() {
+        // all these blocks should be equivalent
+        List<FloatBlock> blocks = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3).asBlock(),
+            new FloatArrayBlock(
+                new float[] { 1, 2, 3 },
+                3,
+                new int[] { 0, 1, 2, 3 },
+                BitSet.valueOf(new byte[] { 0b000 }),
+                randomFrom(Block.MvOrdering.values()),
+                blockFactory
+            ),
+            new FloatArrayBlock(
+                new float[] { 1, 2, 3, 4 },
+                3,
+                new int[] { 0, 1, 2, 3 },
+                BitSet.valueOf(new byte[] { 0b1000 }),
+                randomFrom(Block.MvOrdering.values()),
+                blockFactory
+            ),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3).filter(0, 1, 2).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3, 4 }, 3).filter(0, 1, 2).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3, 4 }, 4).filter(0, 1, 2).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 4, 3 }, 4).filter(0, 1, 3).asBlock(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(3).build(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(3).build().filter(0, 1, 2),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(4).appendFloat(2).appendFloat(3).build().filter(0, 2, 3),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendNull().appendFloat(2).appendFloat(3).build().filter(0, 2, 3)
+        );
+        assertAllEquals(blocks);
+
+        // all these constant-like blocks should be equivalent
+        List<FloatBlock> moreBlocks = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 9, 9 }, 2).asBlock(),
+            new FloatArrayBlock(
+                new float[] { 9, 9 },
+                2,
+                new int[] { 0, 1, 2 },
+                BitSet.valueOf(new byte[] { 0b000 }),
+                randomFrom(Block.MvOrdering.values()),
+                blockFactory
+            ),
+            new FloatArrayBlock(
+                new float[] { 9, 9, 4 },
+                2,
+                new int[] { 0, 1, 2 },
+                BitSet.valueOf(new byte[] { 0b100 }),
+                randomFrom(Block.MvOrdering.values()),
+                blockFactory
+            ),
+            blockFactory.newFloatArrayVector(new float[] { 9, 9 }, 2).filter(0, 1).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 9, 9, 4 }, 2).filter(0, 1).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 9, 9, 4 }, 3).filter(0, 1).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 9, 4, 9 }, 3).filter(0, 2).asBlock(),
+            blockFactory.newConstantFloatBlockWith(9, 2),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(9).appendFloat(9).build(),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(9).appendFloat(9).build().filter(0, 1),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(9).appendFloat(4).appendFloat(9).build().filter(0, 2),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(9).appendNull().appendFloat(9).build().filter(0, 2)
+        );
+        assertAllEquals(moreBlocks);
+    }
+
+    public void testVectorInequality() {
+        // all these vectors should NOT be equivalent
+        List<FloatVector> notEqualVectors = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 1 }, 1),
+            blockFactory.newFloatArrayVector(new float[] { 9 }, 1),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2 }, 2),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 4 }, 3),
+            blockFactory.newConstantFloatBlockWith(9, 2).asVector(),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(1).appendFloat(2).build().asVector().filter(1),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(5).build().asVector(),
+            blockFactory.newFloatBlockBuilder(1).appendFloat(1).appendFloat(2).appendFloat(3).appendFloat(4).build().asVector()
+        );
+        assertAllNotEquals(notEqualVectors);
+    }
+
+    public void testBlockInequality() {
+        // all these blocks should NOT be equivalent
+        List<FloatBlock> notEqualBlocks = List.of(
+            blockFactory.newFloatArrayVector(new float[] { 1 }, 1).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 9 }, 1).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2 }, 2).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 3 }, 3).asBlock(),
+            blockFactory.newFloatArrayVector(new float[] { 1, 2, 4 }, 3).asBlock(),
+            blockFactory.newConstantFloatBlockWith(9, 2),
+            blockFactory.newFloatBlockBuilder(2).appendFloat(1).appendFloat(2).build().filter(1),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).appendFloat(2).appendFloat(5).build(),
+            blockFactory.newFloatBlockBuilder(1).appendFloat(1).appendFloat(2).appendFloat(3).appendFloat(4).build(),
+            blockFactory.newFloatBlockBuilder(1).appendFloat(1).appendNull().build(),
+            blockFactory.newFloatBlockBuilder(1).appendFloat(1).appendNull().appendFloat(3).build(),
+            blockFactory.newFloatBlockBuilder(1).appendFloat(1).appendFloat(3).build(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1).beginPositionEntry().appendFloat(2).appendFloat(3).build()
+        );
+        assertAllNotEquals(notEqualBlocks);
+    }
+
+    public void testSimpleBlockWithSingleNull() {
+        List<FloatBlock> blocks = List.of(
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1.1f).appendNull().appendFloat(3.1f).build(),
+            blockFactory.newFloatBlockBuilder(3).appendFloat(1.1f).appendNull().appendFloat(3.1f).build()
+        );
+        assertEquals(3, blocks.get(0).getPositionCount());
+        assertTrue(blocks.get(0).isNull(1));
+        assertAllEquals(blocks);
+    }
+
+    public void testSimpleBlockWithManyNulls() {
+        int positions = randomIntBetween(1, 256);
+        boolean grow = randomBoolean();
+        FloatBlock.Builder builder1 = blockFactory.newFloatBlockBuilder(grow ? 0 : positions);
+        FloatBlock.Builder builder2 = blockFactory.newFloatBlockBuilder(grow ? 0 : positions);
+        for (int p = 0; p < positions; p++) {
+            builder1.appendNull();
+            builder2.appendNull();
+        }
+        FloatBlock block1 = builder1.build();
+        FloatBlock block2 = builder2.build();
+        assertEquals(positions, block1.getPositionCount());
+        assertTrue(block1.mayHaveNulls());
+        assertTrue(block1.isNull(0));
+
+        List<FloatBlock> blocks = List.of(block1, block2);
+        assertAllEquals(blocks);
+    }
+
+    public void testSimpleBlockWithSingleMultiValue() {
+        List<FloatBlock> blocks = List.of(
+            blockFactory.newFloatBlockBuilder(1).beginPositionEntry().appendFloat(1.1f).appendFloat(2.2f).build(),
+            blockFactory.newFloatBlockBuilder(1).beginPositionEntry().appendFloat(1.1f).appendFloat(2.2f).build()
+        );
+        assert blocks.get(0).getPositionCount() == 1 && blocks.get(0).getValueCount(0) == 2;
+        assertAllEquals(blocks);
+    }
+
+    public void testSimpleBlockWithManyMultiValues() {
+        int positions = randomIntBetween(1, 256);
+        boolean grow = randomBoolean();
+        FloatBlock.Builder builder1 = blockFactory.newFloatBlockBuilder(grow ? 0 : positions);
+        FloatBlock.Builder builder2 = blockFactory.newFloatBlockBuilder(grow ? 0 : positions);
+        FloatBlock.Builder builder3 = blockFactory.newFloatBlockBuilder(grow ? 0 : positions);
+        for (int pos = 0; pos < positions; pos++) {
+            builder1.beginPositionEntry();
+            builder2.beginPositionEntry();
+            builder3.beginPositionEntry();
+            int values = randomIntBetween(1, 16);
+            for (int i = 0; i < values; i++) {
+                float value = randomFloat();
+                builder1.appendFloat(value);
+                builder2.appendFloat(value);
+                builder3.appendFloat(value);
+            }
+            builder1.endPositionEntry();
+            builder2.endPositionEntry();
+            builder3.endPositionEntry();
+        }
+        FloatBlock block1 = builder1.build();
+        FloatBlock block2 = builder2.build();
+        FloatBlock block3 = builder3.build();
+
+        assertEquals(positions, block1.getPositionCount());
+        assertAllEquals(List.of(block1, block2, block3));
+    }
+
+    static void assertAllEquals(List<?> objs) {
+        for (Object obj1 : objs) {
+            for (Object obj2 : objs) {
+                assertEquals(obj1, obj2);
+                assertEquals(obj2, obj1);
+                // equal objects must generate the same hash code
+                assertEquals(obj1.hashCode(), obj2.hashCode());
+            }
+        }
+    }
+
+    static void assertAllNotEquals(List<?> objs) {
+        for (Object obj1 : objs) {
+            for (Object obj2 : objs) {
+                if (obj1 == obj2) {
+                    continue; // skip self
+                }
+                assertNotEquals(obj1, obj2);
+                assertNotEquals(obj2, obj1);
+                // unequal objects SHOULD generate the different hash code
+                assertNotEquals(obj1.hashCode(), obj2.hashCode());
+            }
+        }
+    }
+}

+ 6 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java

@@ -116,6 +116,7 @@ public class VectorBuilderTests extends ESTestCase {
             case NULL, DOC, COMPOSITE, UNKNOWN -> throw new UnsupportedOperationException();
             case BOOLEAN -> blockFactory.newBooleanVectorBuilder(estimatedSize);
             case BYTES_REF -> blockFactory.newBytesRefVectorBuilder(estimatedSize);
+            case FLOAT -> blockFactory.newFloatVectorBuilder(estimatedSize);
             case DOUBLE -> blockFactory.newDoubleVectorBuilder(estimatedSize);
             case INT -> blockFactory.newIntVectorBuilder(estimatedSize);
             case LONG -> blockFactory.newLongVectorBuilder(estimatedSize);
@@ -135,6 +136,11 @@ public class VectorBuilderTests extends ESTestCase {
                     ((BytesRefVector.Builder) builder).appendBytesRef(((BytesRefVector) from).getBytesRef(p, new BytesRef()));
                 }
             }
+            case FLOAT -> {
+                for (int p = 0; p < from.getPositionCount(); p++) {
+                    ((FloatVector.Builder) builder).appendFloat(((FloatVector) from).getFloat(p));
+                }
+            }
             case DOUBLE -> {
                 for (int p = 0; p < from.getPositionCount(); p++) {
                     ((DoubleVector.Builder) builder).appendDouble(((DoubleVector) from).getDouble(p));

+ 6 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java

@@ -119,6 +119,7 @@ public class VectorFixedBuilderTests extends ESTestCase {
             case NULL, BYTES_REF, DOC, COMPOSITE, UNKNOWN -> throw new UnsupportedOperationException();
             case BOOLEAN -> blockFactory.newBooleanVectorFixedBuilder(size);
             case DOUBLE -> blockFactory.newDoubleVectorFixedBuilder(size);
+            case FLOAT -> blockFactory.newFloatVectorFixedBuilder(size);
             case INT -> blockFactory.newIntVectorFixedBuilder(size);
             case LONG -> blockFactory.newLongVectorFixedBuilder(size);
         };
@@ -132,6 +133,11 @@ public class VectorFixedBuilderTests extends ESTestCase {
                     ((BooleanVector.FixedBuilder) builder).appendBoolean(((BooleanVector) from).getBoolean(p));
                 }
             }
+            case FLOAT -> {
+                for (int p = 0; p < from.getPositionCount(); p++) {
+                    ((FloatVector.Builder) builder).appendFloat(((FloatVector) from).getFloat(p));
+                }
+            }
             case DOUBLE -> {
                 for (int p = 0; p < from.getPositionCount(); p++) {
                     ((DoubleVector.FixedBuilder) builder).appendDouble(((DoubleVector) from).getDouble(p));

+ 1 - 1
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/mvdedupe/MultivalueDedupeTests.java

@@ -57,7 +57,7 @@ public class MultivalueDedupeTests extends ESTestCase {
     public static List<ElementType> supportedTypes() {
         List<ElementType> supported = new ArrayList<>();
         for (ElementType elementType : ElementType.values()) {
-            if (oneOf(elementType, ElementType.UNKNOWN, ElementType.DOC, ElementType.COMPOSITE)) {
+            if (oneOf(elementType, ElementType.UNKNOWN, ElementType.DOC, ElementType.COMPOSITE, ElementType.FLOAT)) {
                 continue;
             }
             supported.add(elementType);

+ 2 - 0
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java

@@ -43,6 +43,8 @@ public class ExtractorTests extends ESTestCase {
                 case COMPOSITE -> {
                     // TODO: add later
                 }
+                case FLOAT -> {
+                }
                 case BYTES_REF -> {
                     cases.add(valueTestCase("single alpha", e, TopNEncoder.UTF8, () -> randomAlphaOfLength(5)));
                     cases.add(valueTestCase("many alpha", e, TopNEncoder.UTF8, () -> randomList(2, 10, () -> randomAlphaOfLength(5))));

+ 4 - 3
x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java

@@ -67,6 +67,7 @@ import static org.elasticsearch.compute.data.ElementType.BOOLEAN;
 import static org.elasticsearch.compute.data.ElementType.BYTES_REF;
 import static org.elasticsearch.compute.data.ElementType.COMPOSITE;
 import static org.elasticsearch.compute.data.ElementType.DOUBLE;
+import static org.elasticsearch.compute.data.ElementType.FLOAT;
 import static org.elasticsearch.compute.data.ElementType.INT;
 import static org.elasticsearch.compute.data.ElementType.LONG;
 import static org.elasticsearch.compute.operator.topn.TopNEncoder.DEFAULT_SORTABLE;
@@ -504,7 +505,7 @@ public class TopNOperatorTests extends OperatorTestCase {
         encoders.add(DEFAULT_SORTABLE);
 
         for (ElementType e : ElementType.values()) {
-            if (e == ElementType.UNKNOWN || e == COMPOSITE) {
+            if (e == ElementType.UNKNOWN || e == COMPOSITE || e == FLOAT) {
                 continue;
             }
             elementTypes.add(e);
@@ -576,7 +577,7 @@ public class TopNOperatorTests extends OperatorTestCase {
 
         for (int type = 0; type < blocksCount; type++) {
             ElementType e = randomFrom(ElementType.values());
-            if (e == ElementType.UNKNOWN || e == COMPOSITE) {
+            if (e == ElementType.UNKNOWN || e == COMPOSITE || e == FLOAT) {
                 continue;
             }
             elementTypes.add(e);
@@ -964,7 +965,7 @@ public class TopNOperatorTests extends OperatorTestCase {
 
         for (int type = 0; type < blocksCount; type++) {
             ElementType e = randomValueOtherThanMany(
-                t -> t == ElementType.UNKNOWN || t == ElementType.DOC || t == COMPOSITE,
+                t -> t == ElementType.UNKNOWN || t == ElementType.DOC || t == COMPOSITE || t == FLOAT,
                 () -> randomFrom(ElementType.values())
             );
             elementTypes.add(e);

+ 1 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java

@@ -499,6 +499,7 @@ public final class CsvTestUtils {
             return switch (elementType) {
                 case INT -> INTEGER;
                 case LONG -> LONG;
+                case FLOAT -> FLOAT;
                 case DOUBLE -> DOUBLE;
                 case NULL -> NULL;
                 case BYTES_REF -> bytesRefBlockType(actualType);

+ 5 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/QueryList.java

@@ -15,6 +15,7 @@ import org.elasticsearch.compute.data.Block;
 import org.elasticsearch.compute.data.BooleanBlock;
 import org.elasticsearch.compute.data.BytesRefBlock;
 import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.compute.data.FloatBlock;
 import org.elasticsearch.compute.data.IntBlock;
 import org.elasticsearch.compute.data.LongBlock;
 import org.elasticsearch.core.Nullable;
@@ -143,6 +144,10 @@ abstract class QueryList {
                     DoubleBlock doubleBlock = ((DoubleBlock) block);
                     yield doubleBlock::getDouble;
                 }
+                case FLOAT -> {
+                    FloatBlock floatBlock = ((FloatBlock) block);
+                    yield floatBlock::getFloat;
+                }
                 case INT -> {
                     IntBlock intBlock = (IntBlock) block;
                     yield intBlock::getInt;

+ 1 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/EstimatesRowSize.java

@@ -113,6 +113,7 @@ public interface EstimatesRowSize {
                 default -> 50; // wild estimate for the size of a string.
             };
             case DOC -> throw new EsqlIllegalArgumentException("can't load a [doc] with field extraction");
+            case FLOAT -> Float.BYTES;
             case DOUBLE -> Double.BYTES;
             case INT -> Integer.BYTES;
             case LONG -> Long.BYTES;