Class ColumnStatsAccumulator

java.lang.Object
org.elasticsearch.xpack.esql.datasources.cache.ColumnStatsAccumulator

public final class ColumnStatsAccumulator extends Object
Accumulates per-column null count + min + max across the pages of a single cold scan. Tracked types: BOOLEAN, INTEGER, LONG (incl. DATETIME / DATE_NANOS), DOUBLE, KEYWORD / TEXT / IP. UNSIGNED_LONG and VERSION are deliberately untracked (their stored byte order disagrees with the type's semantic order); all other types are untracked too. Untracked types contribute null counts only — min and max stay null so the warm path bails out rather than serving an unbounded answer.

Hot-path discipline: the iterator passes blocks by their position index in the page (not by name) via acceptBlockAt(int, Block) — accumulator state is held in a flat array so each block dispatch is a single array load.

Multi-valued cells: every value in a position contributes to the running min/max, matching the Parquet stats contract.

Single-instance reuse rule: each instance is owned by exactly one batch-iterator and accumulates over that iterator's full lifetime. Concurrent invocation is not supported.

  • Method Details

    • forProjectedAttributes

      public static ColumnStatsAccumulator forProjectedAttributes(Attribute[] projectedAttrs)
      Builds an accumulator covering the page-block positions 0..projectedAttrs.length. State for each index is sized for the corresponding attribute's data type.
    • forSchema

      public static ColumnStatsAccumulator forSchema(List<Attribute> fileSchema)
      Builds an accumulator keyed to a file's FULL positional schema, for the ALL harvest scope. Identical machinery to forProjectedAttributes(org.elasticsearch.xpack.esql.core.expression.Attribute[]) — every tracked file column gets a ColumnStatsAccumulator.ColumnState indexed by its position in fileSchema — but the row-format readers feed it raw parsed/typed values via acceptValueAt(int, Object) (one per file column, regardless of projection) rather than output Blocks, because the output page only carries the query's projected columns. Feeding a Block via acceptBlockAt(int, Block) and feeding a single value via acceptValueAt(int, java.lang.Object) update the same per-column state, so a mixed call sequence is well-defined.
    • isEmpty

      public boolean isEmpty()
      True when no columns are being tracked — capture loop should skip block iteration.
    • acceptBlockAt

      public void acceptBlockAt(int blockIndex, Block block)
      Feeds the block at page position blockIndex into the accumulator. Caller's responsibility: indices must match the layout the accumulator was built for. Out-of-range indices are silently ignored.
    • acceptNullAt

      public void acceptNullAt(int blockIndex)
    • acceptBooleanAt

      public void acceptBooleanAt(int blockIndex, boolean value)
    • acceptIntAt

      public void acceptIntAt(int blockIndex, int value)
    • acceptLongAt

      public void acceptLongAt(int blockIndex, long value)
    • acceptDoubleAt

      public void acceptDoubleAt(int blockIndex, double value)
    • acceptBytesRefAt

      public void acceptBytesRefAt(int blockIndex, org.apache.lucene.util.BytesRef value)
    • acceptValueAt

      public void acceptValueAt(int columnIndex, Object value)
      Feeds a single already-typed value into the accumulator at column position columnIndex. This is the ALL-scope entry point: the row-format readers hand a boxed value parsed straight from the raw record (no output Block exists for an unprojected file column). Accepted value shapes match what the readers' type-conversion produces:
      • null — counts toward nullCount.
      • Boolean / Integer / Long / Double / BytesRef — a single value, folded into min/max for tracked types.
      • List — a multi-valued cell; every element folds individually (matching the Block path's per-value contract). An empty list counts as null.
      Out-of-range indices are silently ignored, matching acceptBlockAt(int, org.elasticsearch.compute.data.Block).
    • snapshot

      public Map<String,ExternalStats.ColumnStats> snapshot()
      Snapshots the current state into an immutable ExternalStats.ColumnStats map. Safe to call only once per accumulator instance — call it from the iterator's close-time hook.