fix(optimizer): expand positional GROUP BY in canonicalize_internal_names - #8005
fix(optimizer): expand positional GROUP BY in canonicalize_internal_names#8005fivetran-kwoodbeck wants to merge 3 commits into
Conversation
SQLGlot Integration Test Results✅ All tests passedComparing:
Overallmain: 192411 total, 153555 passed (pass rate: 79.8%) sqlglot:optimizer/fix-unstable-group-by-ordinal: 180217 total, 142403 passed (pass rate: 79.0%) Transitions: Dialect pair changes: 0 previous results not found, 3 current results not found ✅ All tests passed |
|
@fivetran-kwoodbeck why is it a problem that the positional reference is not expanded? Does it mess with idempotency, or is this just an "aesthetics"-related fix? |
It's non idempotent, but I figured it's a canonicalization bug too in that positional references are expanded in all other cases. |
5bdb420 to
66576d3
Compare
| # canonicalize_internal_names is dialect-agnostic: the source has already been | ||
| # renamed so dialect specific rules (e.g. PROJECTION_ALIASES_SHADOW_SOURCE_NAMES) no | ||
| # longer apply. | ||
| _DEFAULT_DIALECT = Dialect() |
There was a problem hiding this comment.
This doesn't look right. In my earlier comment, I meant we needed to pass the actual source dialect corresponding to the query being transformed, so that the corresponding logic in expand_group_by could kick in.
The assumption "source already renamed, so dialect rules no longer apply" is not generally accurate. Check this BigQuery edge case, for example:
-- input
SELECT a AS _t0, b AS x FROM x GROUP BY 1
-- qualified
SELECT x.a AS _t0, x.b AS x FROM c.db.x AS x GROUP BY 1
-- canonicalized, there's a clash between the _t0 source alias & projection name
SELECT _t0.a AS _t0, _t0.b AS x FROM c.db.x AS _t0 GROUP BY _t0.aThere was a problem hiding this comment.
Btw, it seems like the PR would still only partially solve the issue of idempotency. The qualify rule makes three decisions based on "a projection alias matches a source name":
- keep GROUP BY 1 (
SELECT a AS foo, b AS x FROM x GROUP BY 1) - demote a column to a plain identifier (
SELECT a AS foo, b AS x FROM x GROUP BY a) - don't inline a column in a
HAVINGclause (SELECT a, MAX(b) AS x FROM x GROUP BY 1 HAVING x > 1)
The canonicalization rule renames the table but keeps the projection alias, so the equality breaks and all three decisions (currently) change if we apply the rules again.
The PR fixes (1), because in that case we simply copy an expression from the projection list over to the Group node. The other two, though, need a schema to redo properly, because we need to resolve the columns again, which overcomplicates the canonicalization pass. This is why I was trying to "push" this work down to qualify when we chatted about it: I don't like how we mix concerns here.
Btw, even if we do pass the right dialect here to fix (1), it seems like there's a caching issue with Scope where expand_group_by sees the scope before the rename happens, so it still sees ambiguity and doesn't do what we want it to. It'd need a rename_source followed by a clear_cache. More complexity...
Given the above, I don't see a relatively simple way forward. I don't want to overcomplicate these rules to handle a couple of edge cases related to idempotency, which is already a "good to have property" and not immediately critical (i.e., incorrect results post-transformation).
Shall we just close the PR? Do you see any reasonable alternatives?
There was a problem hiding this comment.
Nice analysis @georgesittas :), I prefer this to be closed for now, blast radius of the change expands in various parts + I think partially solving this isn't worth.
For BigQuery,
qualifyleaves a positionalGROUP BY(e.g.GROUP BY 1) unexpanded when a projection alias shadows a source name.canonicalize_internal_namesthen renames the source to_tN, removing the collision, but never expanded the ordinal, meaning the canonical form keeps a positional reference.canonicalize_internal_namesnow calls_expand_group_byafter renaming. There's no dialect available (and not technically needed), so I made that parameter optional inqualify_columns.Input
Before
After