Skip to content

Commit dd98aa9

Browse files
committed
cleanup
1 parent 8c0609d commit dd98aa9

File tree

3 files changed

+295
-254
lines changed

3 files changed

+295
-254
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.optimizer.rules.logical.promql;
9+
10+
import org.elasticsearch.xpack.esql.core.expression.Alias;
11+
import org.elasticsearch.xpack.esql.core.expression.Attribute;
12+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
13+
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
14+
import org.elasticsearch.xpack.esql.core.expression.NameId;
15+
import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
16+
import org.elasticsearch.xpack.esql.core.tree.Source;
17+
import org.elasticsearch.xpack.esql.expression.function.grouping.TimeSeriesWithout;
18+
19+
import java.util.ArrayList;
20+
import java.util.HashSet;
21+
import java.util.List;
22+
import java.util.Set;
23+
24+
sealed public interface LabelSet permits LabelSet.FixedLabelSet, LabelSet.ExclusionLabelSet {
25+
/**
26+
* Create a label set with known declared.
27+
*/
28+
static LabelSet of(List<Attribute> labels) {
29+
return new LabelSet.FixedLabelSet(labels);
30+
}
31+
32+
/**
33+
* Project to intersection with input's visible declared. Kills uncertainty.
34+
* Carries declared for null synthesis (PromQL requires all BY declared in output).
35+
*/
36+
static LabelSet by(LabelSet input, List<Attribute> labels) {
37+
return new LabelSet.FixedLabelSet(intersection(input.declaredLabels(), labels), labels);
38+
}
39+
40+
/**
41+
* Remove declared. Introduces uncertainty. Accumulates exclusions.
42+
*/
43+
static LabelSet without(LabelSet input, List<Attribute> excluded) {
44+
List<Attribute> visible = difference(input.declaredLabels(), excluded);
45+
if (input instanceof LabelSet.ExclusionLabelSet w) {
46+
var accumulated = new ArrayList<>(w.excludedLabels());
47+
accumulated.addAll(excluded);
48+
return new LabelSet.ExclusionLabelSet(visible, accumulated);
49+
}
50+
return new LabelSet.ExclusionLabelSet(visible, excluded);
51+
}
52+
53+
/**
54+
* Intersect with available declared. If available is empty, trust the input.
55+
*/
56+
static LabelSet intersectWithLabels(LabelSet input, List<Attribute> available) {
57+
if (available.isEmpty()) {
58+
return input;
59+
}
60+
return new LabelSet.FixedLabelSet(intersection(input.declaredLabels(), available));
61+
}
62+
63+
/**
64+
* Empty label set.
65+
*/
66+
static LabelSet none() {
67+
return new LabelSet.FixedLabelSet(List.of());
68+
}
69+
70+
/**
71+
* Set difference by field name.
72+
*/
73+
static List<Attribute> difference(List<Attribute> from, List<Attribute> toRemove) {
74+
Set<String> removeNames = new HashSet<>();
75+
for (Attribute attr : toRemove) {
76+
removeNames.add(fieldName(attr));
77+
}
78+
return difference(from, removeNames);
79+
}
80+
81+
/**
82+
* Set difference by pre-computed field name set.
83+
*/
84+
static List<Attribute> difference(List<Attribute> from, Set<String> toRemove) {
85+
List<Attribute> result = new ArrayList<>();
86+
for (Attribute attr : from) {
87+
if (toRemove.contains(fieldName(attr)) == false) {
88+
result.add(attr);
89+
}
90+
}
91+
return result;
92+
}
93+
94+
/**
95+
* Set intersection by field name.
96+
*/
97+
static List<Attribute> intersection(List<Attribute> requested, List<Attribute> available) {
98+
Set<String> availableNames = new HashSet<>();
99+
for (Attribute attr : available) {
100+
availableNames.add(fieldName(attr));
101+
}
102+
List<Attribute> result = new ArrayList<>();
103+
for (Attribute attr : requested) {
104+
if (availableNames.contains(fieldName(attr))) {
105+
result.add(attr);
106+
}
107+
}
108+
return result;
109+
}
110+
111+
/**
112+
* Resolves requested labels against a plan output, matching first by identity then by field name.
113+
*/
114+
static List<Attribute> resolveLabels(List<Attribute> requested, List<Attribute> visibleOutput) {
115+
List<Attribute> resolved = new ArrayList<>();
116+
for (Attribute attribute : requested) {
117+
if (visibleOutput.contains(attribute)) {
118+
resolved.add(attribute);
119+
continue;
120+
}
121+
Attribute byName = findAttributeByFieldName(visibleOutput, fieldName(attribute));
122+
if (byName != null) {
123+
resolved.add(byName);
124+
}
125+
}
126+
return resolved;
127+
}
128+
129+
static Attribute findAttributeByFieldName(List<Attribute> attributes, String fieldNameToFind) {
130+
for (Attribute attribute : attributes) {
131+
if (fieldName(attribute).equals(fieldNameToFind)) {
132+
return attribute;
133+
}
134+
}
135+
return null;
136+
}
137+
138+
static Attribute findAttributeById(List<Attribute> attributes, NameId id) {
139+
for (Attribute attribute : attributes) {
140+
if (attribute.id().equals(id)) {
141+
return attribute;
142+
}
143+
}
144+
return null;
145+
}
146+
147+
static String fieldName(Attribute attr) {
148+
if (attr instanceof FieldAttribute fieldAttr) {
149+
return fieldAttr.fieldName().string();
150+
}
151+
return attr.name();
152+
}
153+
154+
/**
155+
* Labels known to be present.
156+
*/
157+
List<Attribute> declaredLabels();
158+
159+
/**
160+
* Labels known to be excluded. Empty for exact (Fixed) label sets.
161+
*/
162+
List<Attribute> excludedLabels();
163+
164+
/**
165+
* Declared output labels (for BY null synthesis). Defaults to declared().
166+
*/
167+
default List<Attribute> byDeclaredLabels() {
168+
return declaredLabels();
169+
}
170+
171+
/**
172+
* True if the label set is exact (no unknowns).
173+
*/
174+
boolean stationary();
175+
176+
/**
177+
* Resolve into concrete grouping and aggregate output expressions.
178+
* parentDemand carries the parent's label requirements -- if the parent is WITHOUT,
179+
* a _timeseries grouping is added even for Fixed aggregates.
180+
*/
181+
LabelSet.GroupingShape toGroupingShape(Source source, LabelSet parentDemand);
182+
183+
/**
184+
* Match visible declared against plan output. Returns resolved and missing declared.
185+
*/
186+
default LabelSet.Matched match(List<Attribute> planOutput) {
187+
List<Attribute> resolved = resolveLabels(declaredLabels(), planOutput);
188+
List<Attribute> missing = difference(declaredLabels(), resolved);
189+
return new LabelSet.Matched(resolved, missing);
190+
}
191+
192+
record GroupingShape(List<? extends NamedExpression> groupings, List<? extends NamedExpression> outputs) {}
193+
194+
record Matched(List<Attribute> resolved, List<Attribute> missing) {}
195+
196+
record FixedLabelSet(List<Attribute> declaredLabels, List<Attribute> byDeclaredLabels) implements LabelSet {
197+
FixedLabelSet(List<Attribute> declared) {
198+
this(declared, declared);
199+
}
200+
201+
@Override
202+
public List<Attribute> excludedLabels() {
203+
return List.of();
204+
}
205+
206+
@Override
207+
public List<Attribute> byDeclaredLabels() {
208+
return byDeclaredLabels;
209+
}
210+
211+
@Override
212+
public boolean stationary() {
213+
return true;
214+
}
215+
216+
@Override
217+
public LabelSet.GroupingShape toGroupingShape(Source source, LabelSet parentDemand) {
218+
var groupings = new ArrayList<NamedExpression>(declaredLabels);
219+
var outputs = new ArrayList<NamedExpression>();
220+
for (Attribute a : declaredLabels) {
221+
if (MetadataAttribute.isTimeSeriesAttributeName(a.name()) == false) {
222+
outputs.add(a);
223+
}
224+
}
225+
// If parent demands _timeseries (WITHOUT flowing downward), add it
226+
if (parentDemand.stationary() == false) {
227+
Alias tw = new TimeSeriesWithout(source, List.copyOf(parentDemand.excludedLabels())).toAttribute();
228+
groupings.add(tw);
229+
outputs.add(tw.toAttribute());
230+
}
231+
return new LabelSet.GroupingShape(groupings, outputs);
232+
}
233+
}
234+
235+
record ExclusionLabelSet(List<Attribute> declaredLabels, List<Attribute> excludedLabels) implements LabelSet {
236+
@Override
237+
public boolean stationary() {
238+
return false;
239+
}
240+
241+
@Override
242+
public LabelSet.GroupingShape toGroupingShape(Source source, LabelSet parentDemand) {
243+
Alias tw = new TimeSeriesWithout(source, List.copyOf(excludedLabels)).toAttribute();
244+
var groupings = new ArrayList<NamedExpression>();
245+
groupings.add(tw);
246+
groupings.addAll(declaredLabels);
247+
var outputs = new ArrayList<NamedExpression>();
248+
outputs.add(tw.toAttribute());
249+
outputs.addAll(declaredLabels);
250+
return new LabelSet.GroupingShape(groupings, outputs);
251+
}
252+
}
253+
254+
}

0 commit comments

Comments
 (0)