Fix for union-types for multiple columns with the same name#110793
Fix for union-types for multiple columns with the same name#110793craigtaverner merged 22 commits intoelastic:mainfrom
Conversation
|
Pinging @elastic/es-analytical-engine (Team:Analytics) |
0745971 to
5541673
Compare
craigtaverner
left a comment
There was a problem hiding this comment.
Excellent! This is the fix we've been looking for! 👋
| /** | ||
| * Fix for union-types when sorting a type-casted field. We changed how we remove synthetic union-types fields. | ||
| */ | ||
| UNION_TYPES_REMOVE_FIELDS; |
There was a problem hiding this comment.
In related discussions we realized that adding capabilities to the end of the enum increases the risk of clashes when back-porting. Not a big issue, but requires a little extra manual step when back-porting.
There was a problem hiding this comment.
Moved up a little, under the other union type capability.
| }; | ||
| } | ||
|
|
||
| private LogicalPlan dropConvertedAttributes(LogicalPlan plan, List<FieldAttribute> unionFieldAttributes) { |
There was a problem hiding this comment.
Nice that this could finally go. It did look wrong.
| if (missing.isEmpty() == false) { | ||
| output.addAll(missing); | ||
| return new EsRelation(esr.source(), esr.index(), output, esr.indexMode(), esr.frozen()); | ||
| List<Attribute> newOutput = new ArrayList<>(esr.output()); |
There was a problem hiding this comment.
I definitely missed the subtlety of the mutating state behaviour here, and did not realize this was why my earlier edit to EsRelation could be deleted. Thanks for fixing this and bringing back EsRelation fixes.
| @Override | ||
| public String fieldName() { | ||
| // The super fieldName uses parents to compute the path; this class ignores parents, so we need to rely on the name instead. | ||
| // Using field().getName() would be wrong: for subfields like parent.subfield that would return only the last part, subfield. |
There was a problem hiding this comment.
nice observation!
| for (NamedExpression projection : projections) { | ||
| if (projection instanceof FieldAttribute f && stats.exists(f.qualifiedName()) == false) { | ||
| // Do not use the attribute name, this can deviate from the field name for union types. | ||
| if (projection instanceof FieldAttribute f && stats.exists(f.fieldName()) == false) { |
There was a problem hiding this comment.
Perhaps in the fieldName javadoc we should mention why we don't use qualified name (the fact that qualifiers are always null in ESQL, and this is an artifact of SQL code)?
There was a problem hiding this comment.
I hope we can merge #110581 soon, making the comment here very short lived. Update I mean any comment about qualified names, not the existing comment.
In any case, the qualified name might even be pretty wrong here - our indices don't know about qualifiers, only field names!
| return Objects.equals(index, other.index) | ||
| && indexMode == other.indexMode() | ||
| && frozen == other.frozen | ||
| && Objects.equals(attrs, other.attrs); |
There was a problem hiding this comment.
Excellent. I had this fix in an earlier version of union-types, but removed it when I discovered it was not needed. I did not realize that it was only not needed due to another bug. It feels good to see this code come back.
There was a problem hiding this comment.
I think it's quite important; this can mask test failures (it did), and it can also prevent optimizations from taking effect if all they do is change an EsRelation's attributes (like for union types).
There was a problem hiding this comment.
This is needed because now (sometime last year when EsRelation has been changed to work "better" in ESQL) attributes can also be provided through a new public constructor. Until the public constructor that also accepted attributes, they were built from EsIndex. There was an immutability and a link betwee EsIndex and EsRelation and I think that's why equals didn't need the attributes (the object was built once and it stayed as is from the _field_caps call until the query was executed on ES and results returned).
| ElementType elementType = PlannerUtils.toElementType(dataType, fieldExtractPreference); | ||
| String fieldName = attr.name(); | ||
| // Do not use the field attribute name, this can deviate from the field name for union types. | ||
| String fieldName = attr instanceof FieldAttribute fa ? fa.fieldName() : attr.name(); |
There was a problem hiding this comment.
I was wondering, surely all attributes in this method must be field attributes?
There was a problem hiding this comment.
I think metadata attributes, like _id or _source could also occur, at least in principle.
alex-spies
left a comment
There was a problem hiding this comment.
This should be good to go once the remaining test failures are fixed.
astefan
left a comment
There was a problem hiding this comment.
LGTM in general.
I've left some comments, kind of optional, or nice to haves.
| // On later versions, the attribute can be renamed when creating synthetic attributes. | ||
| // TODO: We should use synthetic() to check for that case. | ||
| // https://github.com/elastic/elasticsearch/issues/105821 | ||
| boolean isSynthetic = name().startsWith(SYNTHETIC_ATTRIBUTE_NAME_PREFIX); |
There was a problem hiding this comment.
I don't think this var here improves readbility. It's used only once and can be removed.
| required_capability: casting_operator | ||
| required_capability: union_types_remove_fields | ||
|
|
||
| FROM sample_data, sample_data_str |
There was a problem hiding this comment.
It's worth adding here another more advanced variant (but still trying to keep the original query that brought up this issue): FROM sample_data, sample_data_str | SORT client_ip::ip, @timestamp ASC| eval client_ip_as_ip = client_ip::ip.
| new UnresolveUnionTypes(), | ||
| new DropSyntheticUnionTypeAttributes() |
There was a problem hiding this comment.
It feels like these two rules could be only one, an union-types cleanup step kind of rule. They both do some very specific union-types operations and I don't see them not living together. I don't have an idea on how to merge them, but if you do and can do it, I kinda want to see them in only one rule.
| ) { | ||
| var unionFieldAttribute = new FieldAttribute(fa.source(), fa.name(), resolvedField); // Generates new ID for the field | ||
| // Generate new ID for the field and suffix it with the data type to maintain unique attribute names. | ||
| String unionTypedFieldName = SubstituteSurrogates.rawTemporaryName( |
There was a problem hiding this comment.
The more I see rawTemporaryName being used, they more it feels like it can belong to FieldAttribute, or somewhere else outside the SubstituteSurrogates rule.
There was a problem hiding this comment.
Agree and so I just tried that; it would make sense to move to esql.core.Attribute, but then we'd likely want to move temporaryName as well; the latter, however, relies on AggregateFunction which is outside of esql.core.
Maybe let's leave this for another time to not cause too much code movement; this PR needs backporting to 8.15, that could become harder if we also refactor outside of the PR's immediate scope.
| return Objects.equals(index, other.index) | ||
| && indexMode == other.indexMode() | ||
| && frozen == other.frozen | ||
| && Objects.equals(attrs, other.attrs); |
There was a problem hiding this comment.
This is needed because now (sometime last year when EsRelation has been changed to work "better" in ESQL) attributes can also be provided through a new public constructor. Until the public constructor that also accepted attributes, they were built from EsIndex. There was an immutability and a link betwee EsIndex and EsRelation and I think that's why equals didn't need the attributes (the object was built once and it stayed as is from the _field_caps call until the query was executed on ES and results returned).
|
Thanks for the review @astefan , I addressed your comments! |
|
Hi @craigtaverner, I've created a changelog YAML for you. |
The tests fail spordically, so safer to mute the entire suite.
…110793) * Make union types use unique attribute names * Cleanup leftover * Added failing test and final fix to EsRelation * Implement FieldAttribute.fieldName() * Fix tests * Refactor * Do not ignore union typed field's parent * Fix important typo D'oh * Mute unrelated (part of) test * Move capability to better location * Fix analyzer tests * multi-node tests with an earlier version of union-types (before this change) fail * Add capability to remaining failing tests * Remove variable * Add more complex test * Consolidate union type cleanup rules * Add 3 more required_capability's to make CI happy * Update caps for union type subfield yaml tests * Update docs/changelog/110793.yaml * Refined changelog text * Mute BWC for 8.15.0 for failing YAML tests * union_types_remove_fields for all 160_union_types tests The tests fail spordically, so safer to mute the entire suite. --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co>
* Fix bug in union-types with type-casting in grouping key of STATS (#110476) * Allow auto-generated type-cast fields in CsvTests This allows, for example, a csv-spec test result header like `client_ip::ip:ip`, which is generated with a command like `STATS count=count(*) BY client_ip::ip` It is also a small cleanup of the header parsing code, since it was using Strings.split() in an odd way. * Fix bug in union-types with type-casting in grouping key of STATS * Update docs/changelog/110476.yaml * Added casting_operator required capability Using the new `::` syntax requires disabling support for older versions in multi-cluster tests. * Added more tests for inline stats over long/datetime * Trying to fix the STATS...STATS bug This makes two changes: * Keeps the Alias in the aggs.aggregates from the grouping key, so that ReplaceStatsNestedExpressionWithEval still works * Adds explicit support for union-types conversion at grouping key loading in the ordinalGroupingOperatorFactory Neither fix the particular edge case, but do seem correct * Added EsqlCapability for this change So that mixed cluster tests don't fail these new queries. * Fix InsertFieldExtract for union types Union types require a FieldExtractExec to be performed first thing at the bottom of local physical plans. In queries like ``` from testidx* | eval x = to_string(client_ip) | stats c = count(*) by x | keep c ``` The `stats` has the grouping `x` but the aggregates get pruned to just `c`. In cases like this, we did not insert a FieldExtractExec, which this fixes. * Revert query that previously failed With Alex's fix, this query now passes. * Revert integration of union-types to ordinals aggregator This is because we have not found a test case that actually demonstrates this is necessary. * More tests that would fail without the latest fix * Correct code style * Fix failing case when aggregating on union-type with invalid grouping key * Capabilities restrictions on the new YML tests * Update docs/changelog/110476.yaml --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co> * An alternative approach to supporting union-types on stats grouping field (#110600) * Added union-types field extration to ordinals aggregation * Revert previous approach to getting union-types working in aggregations Where the grouping field is erased by later commands, like a subsequent stats. Instead we include union-type supports in the ordinals aggregation and mark the block loader as not supporting ordinals. * Fix union-types when aggregating on inline conversion function (#110652) A query like: ``` FROM sample_data, sample_data_str | STATS count=count(*) BY client_ip = TO_IP(client_ip) | SORT count DESC, client_ip ASC | KEEP count, client_ip ``` Failed due to unresolved aggregates from the union-type in the grouping key * Fix for union-types for multiple columns with the same name (#110793) * Make union types use unique attribute names * Cleanup leftover * Added failing test and final fix to EsRelation * Implement FieldAttribute.fieldName() * Fix tests * Refactor * Do not ignore union typed field's parent * Fix important typo D'oh * Mute unrelated (part of) test * Move capability to better location * Fix analyzer tests * multi-node tests with an earlier version of union-types (before this change) fail * Add capability to remaining failing tests * Remove variable * Add more complex test * Consolidate union type cleanup rules * Add 3 more required_capability's to make CI happy * Update caps for union type subfield yaml tests * Update docs/changelog/110793.yaml * Refined changelog text * Mute BWC for 8.15.0 for failing YAML tests * union_types_remove_fields for all 160_union_types tests The tests fail spordically, so safer to mute the entire suite. --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co> --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co>
* Make union types use unique attribute names * Cleanup leftover * Added failing test and final fix to EsRelation * Implement FieldAttribute.fieldName() * Fix tests * Refactor * Do not ignore union typed field's parent * Fix important typo D'oh * Mute unrelated (part of) test * Move capability to better location * Fix analyzer tests * multi-node tests with an earlier version of union-types (before this change) fail * Add capability to remaining failing tests * Remove variable * Add more complex test * Consolidate union type cleanup rules * Add 3 more required_capability's to make CI happy * Update caps for union type subfield yaml tests * Update docs/changelog/110793.yaml * Refined changelog text * Mute BWC for 8.15.0 for failing YAML tests * union_types_remove_fields for all 160_union_types tests The tests fail spordically, so safer to mute the entire suite. --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co>
* Make union types use unique attribute names * Cleanup leftover * Added failing test and final fix to EsRelation * Implement FieldAttribute.fieldName() * Fix tests * Refactor * Do not ignore union typed field's parent * Fix important typo D'oh * Mute unrelated (part of) test * Move capability to better location * Fix analyzer tests * multi-node tests with an earlier version of union-types (before this change) fail * Add capability to remaining failing tests * Remove variable * Add more complex test * Consolidate union type cleanup rules * Add 3 more required_capability's to make CI happy * Update caps for union type subfield yaml tests * Update docs/changelog/110793.yaml * Refined changelog text * Mute BWC for 8.15.0 for failing YAML tests * union_types_remove_fields for all 160_union_types tests The tests fail spordically, so safer to mute the entire suite. --------- Co-authored-by: Alexander Spies <alexander.spies@elastic.co>
There have been several issues during the union-types work that lead to multiple columns with the same name, and there have been a couple of fixes to this, however more cases have been found, so this PR takes a new approach to finding a more general fix to this problem. Instead of dropping them immediately after using them, we put a single drop at the top of the plan. This makes the plan much less brittle against plan re-ordering rules. Also, we make all synthetic union-type FieldAttributes (transient ones used internally only, not the ones published in the results) use unique internal names, to protect against rules that rely on name uniqueness, and also make debugging easier.
Fixes #109916
Fixes #110490