Class PromqlAttributesTranslationContext

java.lang.Object
org.elasticsearch.xpack.esql.optimizer.rules.logical.promql.PromqlAttributesTranslationContext

public final class PromqlAttributesTranslationContext extends Object
The label-grouping fragment of the PromQL -> ESQL syntax-directed translation.

A PromQL query such as sum by(cluster) (avg without(region) (cpu_util)) is a chain of aggregates over a single leaf selector. PromQL grouping is dynamic: the concrete set of label names lives on disk and is unknown at plan time, so we cannot list it efficiently. Instead, the translation carries the aggregation shape symbolically and resolves it against real columns only at the very end.

Abstract domain: label sets

A label set is a plain List<Attribute> plus two "top" sentinels: UNIVERSE for the full label universe T ("every runtime label") - the one set we can never enumerate at plan time - and SCALAR for a subtree that exposes no series identity at all (a literal/scalar or a bare NONE). T is realised physically by the opaque _timeseries grouping key (see TranslateTimeSeriesWithout), which is why it contributes no concrete columns when resolved. The static union/intersect/minus helpers implement set algebra over these values (labels are compared by field name). T minus a finite set stays T: the removed labels are never listed, and - because every BY forces a finite scope down its subtree - that complement is never observed, so a single UNIVERSE sentinel is enough.

The two attributes

The translation is an L-attributed syntax-directed definition. Two attributes flow through the aggregate tree, modelled here as distinct immutable value types: Two passes are unavoidable: this is an L-attributed (not S-attributed) definition, and the translation can only resolve symbolic labels against the concrete columns a child subtree actually produces, which exist only after the child is translated.

The translation: from attribute to target representation

A PromqlAttributesTranslationContext.SynthesizedAttributes is symbolic; it cannot be handed to a plan node directly. PromqlAttributesTranslationContext.SynthesizedAttributes.translate(java.util.List<org.elasticsearch.xpack.esql.core.expression.Attribute>)/ PromqlAttributesTranslationContext.SynthesizedAttributes.translateLeaf(java.util.List<org.elasticsearch.xpack.esql.core.expression.Attribute>) are the code-generation semantic action that concretizes it against the target schema - collapsing the T/symbolic sets, binding labels by name to the real Attribute instances, and reporting unresolved BY labels for null-filling. Their output, PromqlAttributesTranslationContext.ResolvedAttributes, is the target representation the plan builder consumes; unlike the attributes, it does not propagate up the tree.

Exclusions: inherited vs synthesized

WITHOUT dimensions are tracked twice, on purpose, because two distinct consumers need two distinct facts:

Worked examples

Each aggregate's synthesized [grouping, output, subtreeWithouts] is shown on its closing line; the leaf's translateLeaf additionally receives the demand's accumulated exclusions.
 sum without(pod) (
   avg without(region) (
     cpu_util
   ) [G=T\{region}, O={}, X={region}]   // translateLeaf path-exclusions = {pod,region}
 ) [G=T\{region,pod}, O={}, X={region,pod}]

 sum by(cluster,region) (
   avg without(region) (
     cpu_util
   ) [G=T\{region}, O={}, X={region}]   // translateLeaf path-exclusions = {region}
 ) [G={cluster}, O={cluster,region}, X={}]   // region is null-filled

 sum without(pod) (
   avg by(cluster,pod) (
     cpu_util
   ) [G={cluster,pod}, O={cluster,pod}, X={}]   // translateLeaf path-exclusions = {pod}
 ) [G={cluster}, O={}, X={pod}]
 

The descent starts from PromqlAttributesTranslationContext.InheritedAttributes.unconstrained() above the outermost aggregate: every label is in scope (T) and nothing is excluded. Both attribute types are immutable; every transition returns a new value.