Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
134a3e8
Unique output attribute names after optimization
alex-spies Jul 4, 2024
9d9c70f
Enforce unique row attribute names in verifier
alex-spies Jul 4, 2024
0d4e1df
Update docs/changelog/110488.yaml
alex-spies Jul 4, 2024
6f36c29
Add tests for grok, dissect, enrich
alex-spies Jul 5, 2024
cd48514
Add tests for keep
alex-spies Jul 5, 2024
3a5dab7
Make row consistent with other plans
alex-spies Jul 5, 2024
f71ef42
Update docs/changelog/110488.yaml
alex-spies Jul 5, 2024
42be4eb
Add test for drop, rename and stats
alex-spies Jul 5, 2024
2a0d630
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 5, 2024
11da00c
Add test dataset with deeper field hierarchy
alex-spies Jul 9, 2024
0d6758e
Add hierarchical shadowing test for eval
alex-spies Jul 9, 2024
0e13eaf
Add hierarchical tests for drop, dissect
alex-spies Jul 9, 2024
878eb99
Add hierarchical tests for enrich
alex-spies Jul 9, 2024
e8609f2
Add hierarchical tests for grok, keep
alex-spies Jul 9, 2024
5b4be72
Add hierarchical tests for rename, row
alex-spies Jul 9, 2024
2f1778a
Add more extreme case for stats
alex-spies Jul 9, 2024
73a9736
Add new capability for this fix
alex-spies Jul 9, 2024
e3531ff
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 9, 2024
2cb23f5
Fix EsRelation.equals, mutation in ResolveUnionTypes
alex-spies Jul 9, 2024
222698c
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 10, 2024
1a648e4
Make union types use unique attribute names
alex-spies Jul 11, 2024
658bebc
Cleanup leftover
alex-spies Jul 11, 2024
e2760a5
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 11, 2024
6ac0fa9
Revert "Cleanup leftover"
alex-spies Jul 15, 2024
0f3c274
Revert "Make union types use unique attribute names"
alex-spies Jul 15, 2024
091c099
Revert "Fix EsRelation.equals, mutation in ResolveUnionTypes"
alex-spies Jul 15, 2024
f478d59
More ENRICH tests with internal shadowing
alex-spies Jul 15, 2024
6f00534
More consistent test names
alex-spies Jul 15, 2024
535c954
More KEEP tests
alex-spies Jul 15, 2024
fb85e16
Update docs
alex-spies Jul 15, 2024
6f98cde
Add more tests
alex-spies Jul 15, 2024
fdcc40e
Improve doc wording
alex-spies Jul 15, 2024
3779900
Improve GROK docs
alex-spies Jul 15, 2024
07e9584
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 15, 2024
32f76a0
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 16, 2024
94737ff
Update RENAME docs and tests
alex-spies Jul 16, 2024
ea9b9a9
Avoid duplicate field attribs from union type res
alex-spies Jul 16, 2024
f5d9568
Fix leftovers
alex-spies Jul 16, 2024
a387165
Make tests deterministic
alex-spies Jul 16, 2024
233d68d
Fix rename shadowing docs
alex-spies Jul 16, 2024
d0723b0
Apply Liam's doc remarks
alex-spies Jul 16, 2024
fb17126
Don't describe KEEP precedence twice
alex-spies Jul 17, 2024
bc354f4
Merge remote-tracking branch 'upstream/main' into validate-unique-pla…
alex-spies Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/110488.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 110488
summary: "ESQL: Validate unique plan attribute names"
area: ES|QL
type: bug
issues:
- 110541
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ last_name:keyword | left:keyword | foo:keyword | middle:keyword | ri
Facello | left | Georgi1 Georgi2 Facello | middle | right | Georgi1 | Georgi2 | Facello
;

shadowingInternal
FROM employees
| KEEP first_name, last_name
| WHERE last_name == "Facello"
| EVAL name = concat(first_name, "1 ", last_name)
| DISSECT name "%{foo} %{foo}"
;

first_name:keyword | last_name:keyword | name:keyword | foo:keyword
Georgi | Facello | Georgi1 Facello | Facello
;


complexPattern
ROW a = "1953-01-23T12:15:00Z - some text - 127.0.0.1;"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,28 @@ FROM employees | STATS COUNT(*), MIN(salary * 10), MAX(languages)| DROP `COUNT(
MIN(salary * 10):i | MAX(languages):i
253240 | 5
;

// Not really shadowing, but let's keep the name consistent with the other command's tests
shadowingInternal
FROM employees
| KEEP emp_no, first_name, last_name
| DROP last_name, last_name
| LIMIT 2
;

emp_no:integer | first_name:keyword
10001 | Georgi
10002 | Bezalel
;

shadowingInternalWildcard
FROM employees
| KEEP emp_no, first_name, last_name
| DROP last*name, last*name, last*, last_name
| LIMIT 2
;

emp_no:integer | first_name:keyword
10001 | Georgi
10002 | Bezalel
;
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ ROW left = "left", airport = "Zurich Airport ZRH", city = "Zürich", middle = "m
left:keyword | city:keyword | middle:keyword | right:keyword | airport:text | region:text | city_boundary:geo_shape
;

shadowingInternal
required_capability: enrich_load
ROW city = "Zürich"
| ENRICH city_names ON city WITH x = airport, x = region
;

city:keyword | x:text
Zürich | Bezirk Zürich
;


simple
required_capability: enrich_load

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ left:keyword | middle:keyword | right:keyword | x:integer | y:integer
left | middle | right | 9 | 10
;

shadowingInternal
ROW x = 10000
| EVAL x = x + 1, x = x - 2
;

x:integer
9999
;


withMath
row a = 1 | eval b = 2 + 3;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ last_name:keyword | left:keyword | foo:keyword | middle:keyword | ri
Facello | left | Georgi1 Georgi2 Facello | middle | right | Georgi1 | Georgi2 | Facello
;

shadowingInternal
FROM employees
| KEEP last_name
| WHERE last_name == "Facello"
| EVAL name = concat("1 ", last_name)
| GROK name "%{WORD:foo} %{WORD:foo}"
;

last_name:keyword | name:keyword | foo:keyword
Facello | 1 Facello | [1, Facello]
;

complexPattern
ROW a = "1953-01-23T12:15:00Z 127.0.0.1 some.email@foo.com 42"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,28 @@ c:i
1
1
;

shadowingInternal
FROM employees
| SORT emp_no ASC
| KEEP last_name, last_name
| LIMIT 2
;

last_name:keyword
Facello
Simmel
;


shadowingInternalWildcard
FROM employees
| SORT emp_no ASC
| KEEP last*name, last*name, last*, last_name
| LIMIT 2
;

last_name:keyword
Facello
Simmel
;
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,29 @@ avg_worked_seconds:l | birth_date:date | emp_no:i | first_n
341158890 | 1961-10-15T00:00:00.000Z | 10060 | Breannda | M | 1.42 | 1.4199999570846558 | 1.419921875 | 1.42 | 1987-11-02T00:00:00.000Z | [false, false, false, true]| [Business Analyst, Data Scientist, Senior Team Lead] | 2 | 2 | 2 | 2 | Billingsley | 29175 | [-1.76, -0.85] | [-1, 0] | [-0.85, -1.76] | [-1, 0] | true | 29175
246355863 | null | 10042 | Magy | F | 1.44 | 1.440000057220459 | 1.4404296875 | 1.44 | 1993-03-21T00:00:00.000Z | null | [Architect, Business Analyst, Internship, Junior Developer] | 3 | 3 | 3 | 3 | Stamatiou | 30404 | [-9.28, 9.42] | [-9, 9] | [-9.28, 9.42] | [-9, 9] | true | 30404
;

shadowing
FROM employees
| SORT emp_no ASC
| KEEP emp_no, first_name, last_name
| RENAME emp_no AS last_name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an interesting edge case.
Maybe I am reading the docs update wrong (thank you for updating the docs as well):

If it conflicts with an existing column name,
the existing column is replaced by the renamed column. If multiple columns
are renamed to the same name, all but the rightmost column with the same new
name are dropped.

but there is a contradiction between what we say in docs and what actually happens. According to docs, in the results below, last_name should contain info from emp_no but it should be the second column from left to right, not the first one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch! The column is not replaced, it's dropped. I'll update the doc.

| LIMIT 2
;

last_name:integer | first_name:keyword
10001 | Georgi
10002 | Bezalel
;

shadowingInternal
FROM employees
| SORT emp_no ASC
| KEEP emp_no, last_name
| RENAME emp_no AS x, last_name AS x
| LIMIT 2
;

x:keyword
Facello
Simmel
;
13 changes: 11 additions & 2 deletions x-pack/plugin/esql/qa/testFixtures/src/main/resources/row.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ a:integer
// end::multivalue-result[]
;

shadowingInternal
//TODO: needs cap to skip on bwc tests
row a = 1, a = 2;

a:integer
2
;

unsignedLongLiteral
ROW long_max = 9223372036854775807, ul_start = 9223372036854775808, ul_end = 18446744073709551615, double=18446744073709551616;

Expand Down Expand Up @@ -70,10 +78,11 @@ a:integer | b:integer | c:null | z:integer
;

evalRowWithNull2
//TODO: needs cap to skip on bwc tests
row a = 1, null, b = 2, c = null, null | eval z = a+b;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one had ambiguous attribute names.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider this a bug fix or a breaking change? (IMHO it's a bug, but I see it's questionable)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, this is a bug, since there's no way to refer to the attributes named null in following commands.


a:integer | null:null | b:integer | c:null | null:null | z:integer
1 | null | 2 | null | null | 3
a:integer | b:integer | c:null | null:null | z:integer
1 | 2 | null | null | 3
;

evalRowWithNull3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1819,3 +1819,12 @@ warning:Line 3:17: java.lang.ArithmeticException: / by zero
w_avg:double
null
;

shadowingInternal
FROM employees
| STATS x = MAX(emp_no), x = MIN(emp_no)
Comment thread
alex-spies marked this conversation as resolved.
;

x:integer
10001
;
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import java.util.function.Predicate;
import java.util.function.Supplier;

import static java.util.Collections.singletonList;

public final class AnalyzerRules {

public abstract static class AnalyzerRule<SubPlan extends LogicalPlan> extends Rule<SubPlan, LogicalPlan> {
Expand Down Expand Up @@ -138,14 +136,6 @@ public static List<Attribute> maybeResolveAgainstList(
)
.toList();

return singletonList(
ua.withUnresolvedMessage(
"Reference ["
+ ua.qualifiedName()
+ "] is ambiguous (to disambiguate use quotes or qualifiers); "
+ "matches any of "
+ refs
)
);
throw new IllegalStateException("Reference [" + ua.qualifiedName() + "] is ambiguous; " + "matches any of " + refs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
package org.elasticsearch.xpack.esql.optimizer;

import org.elasticsearch.xpack.esql.core.common.Failures;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.AttributeSet;
import org.elasticsearch.xpack.esql.core.expression.Expressions;
import org.elasticsearch.xpack.esql.core.expression.NameId;
import org.elasticsearch.xpack.esql.core.plan.QueryPlan;
import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
Expand All @@ -36,6 +38,9 @@
import org.elasticsearch.xpack.esql.plan.physical.RowExec;
import org.elasticsearch.xpack.esql.plan.physical.ShowExec;

import java.util.HashSet;
import java.util.Set;

import static org.elasticsearch.xpack.esql.core.common.Failure.fail;

class OptimizerRules {
Expand All @@ -49,9 +54,24 @@ void checkPlan(P p, Failures failures) {
AttributeSet input = p.inputSet();
AttributeSet generated = generates(p);
AttributeSet missing = refs.subtract(input).subtract(generated);
if (missing.size() > 0) {
if (missing.isEmpty() == false) {
failures.add(fail(p, "Plan [{}] optimized incorrectly due to missing references {}", p.nodeString(), missing));
}

Set<String> outputAttributeNames = new HashSet<>();
Set<NameId> outputAttributeIds = new HashSet<>();
for (Attribute outputAttr : p.output()) {
if (outputAttributeNames.add(outputAttr.name()) == false || outputAttributeIds.add(outputAttr.id()) == false) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.name() -> .qualifiedName()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I changed my mind on this:

It is the actual name that needs to be unique; otherwise, bugs could slip in because we somehow end up using qualifiers on accident; qualifiers are not respected by our optimization rules, e.g. mergeOutputAttributes; this PR demonstrates that qualifiers are entirely unused, and the validation for the current state should reflect current assumptions.

If we end up using qualifiers after all (I think that's really for the future and we should really remove them until then), we can easily update the validation.

failures.add(
fail(
p,
"Plan [{}] optimized incorrectly due to duplicate output attribute {}",
p.nodeString(),
outputAttr.toString()
)
);
}
}
}

protected AttributeSet references(P p) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import static org.elasticsearch.xpack.esql.core.parser.ParserUtils.source;
import static org.elasticsearch.xpack.esql.core.parser.ParserUtils.typedParsing;
import static org.elasticsearch.xpack.esql.core.parser.ParserUtils.visitList;
import static org.elasticsearch.xpack.esql.expression.NamedExpressions.mergeOutputExpressions;
import static org.elasticsearch.xpack.esql.plan.logical.Enrich.Mode;
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.stringToInt;

Expand Down Expand Up @@ -217,8 +218,9 @@ public Map<String, Object> visitCommandOptions(EsqlBaseParser.CommandOptionsCont
}

@Override
@SuppressWarnings("unchecked")
public LogicalPlan visitRowCommand(EsqlBaseParser.RowCommandContext ctx) {
return new Row(source(ctx), visitFields(ctx.fields()));
return new Row(source(ctx), (List<Alias>) (List) mergeOutputExpressions(visitFields(ctx.fields()), List.of()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package org.elasticsearch.xpack.esql.plan.logical;

import org.elasticsearch.xpack.esql.core.expression.Alias;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
Expand All @@ -28,6 +29,12 @@ public List<Alias> renamings() {
return renamings;
}

@Override
public List<Attribute> output() {
// Rename is mapped to a Project during analysis; we do not compute the output here.
throw new IllegalStateException("Should never reach here.");
}

@Override
public boolean expressionsResolved() {
for (var alias : renamings) {
Expand Down