fix(optimizer): dedupe colliding star expanded aliases#7872
fix(optimizer): dedupe colliding star expanded aliases#7872fivetran-kwoodbeck wants to merge 7 commits into
Conversation
16ec962 to
7c53f32
Compare
There was a problem hiding this comment.
@fivetran-kwoodbeck I don't think we should bother dealing with this. These queries seem pathological and the amount of complexity and overhead I'm seeing in this PR makes me feel like this is definitely not worth it.
Postgres' behavior for example demonstrates, imo, why something like this in any normal system / pipeline would be pretty much unusable:
georgesittas=# SELECT * FROM t1 CROSS JOIN (SELECT * FROM t1 AS a CROSS JOIN t2 AS b) AS w;
c | c | c
---+---+---
1 | 1 | 2
(1 row)
georgesittas=# SELECT c FROM t1 CROSS JOIN (SELECT * FROM t1 AS a CROSS JOIN t2 AS b) AS w;
ERROR: column reference "c" is ambiguous
LINE 1: SELECT c FROM t1 CROSS JOIN (SELECT * FROM t1 AS a CROSS JOI...
^
SQLGlot Integration Test Results❌ 5 regressions — see details belowComparing:
By Dialect
Overallmain: 192414 total, 153544 passed (pass rate: 79.8%) sqlglot:optimizer/bug-query-star-collision: 180220 total, 142392 passed (pass rate: 79.0%) Transitions: Dialect pair changes: 0 previous results not found, 3 current results not found ❌ 5 regressions (view logs) |
@georgesittas I hear you on the complexity, but this bug was detected by analyzing actual queries from Fivetran's production BigQuery history. It's a legit bug that will trigger in production. Just think of how often columns named You're right about the explicit column case ( DuckDB: Silently alters the output (returning If the canonical output is hashed, it can also cause different queries to collide. One idea is we could look for colliding output names in a single pass after the stars are expanded. I think that would simplify things. |
7c53f32 to
842ccde
Compare
842ccde to
79e4f04
Compare
3d198c0 to
2eccfcc
Compare
| source.expression.set("expressions", [exp.Star()]) | ||
| del new_selections[star_start:] | ||
| new_selections.append(expression) | ||
| break |
There was a problem hiding this comment.
Why are we overwriting the whole expressions here with a single star? What happens when a projection list contains both stars and actual projections?
| if isinstance(source, Scope) and isinstance(source.expression, exp.Select): | ||
| # This source is being re-exposed via a star, check for duplicate output | ||
| # and restore the star if found. | ||
| if _has_duplicate_output_names(source.expression): |
There was a problem hiding this comment.
This can also clobber duplicate aliases authored by the user explicitly. For example:
-- input, produces (k, k)
SELECT * FROM (SELECT a AS k, a AS k FROM x) AS s
-- output, produces (a, b)
WITH "s" AS (SELECT * FROM "x" AS "x") SELECT * FROM "s"| # title: outer star over derived table with duplicate output names is left unexpanded | ||
| # execute: false | ||
| SELECT * FROM (SELECT * FROM x CROSS JOIN y) AS s; | ||
| SELECT * FROM (SELECT * FROM x AS x CROSS JOIN y AS y) AS s; |
There was a problem hiding this comment.
The testing coverage here is insufficient.
There was a problem hiding this comment.
Isn't the solution as simple as extending this with or len(columns) != len(set(columns))?
There was a problem hiding this comment.
That could work. I was aiming to not expand when there's duplicates. So, for example:
SELECT * FROM (SELECT * FROM x CROSS JOIN y) AS s
would now turn into this:
SELECT *
FROM (
SELECT x.id AS id, x.name AS name, y.id AS id, y.name AS name
FROM x CROSS JOIN y
) AS s;
It has (incorrectly named) duplicates, but it'll run and be idempotent which are the main points that matter.
There was a problem hiding this comment.
I see, makes sense. My approach doesn't expand when there are duplicates within one source, but doesn't handle cross-source projection duplication.
I'm not sure what the best approach is, but I would check if it makes raising a qualify error if any duplicates are detected at any scope level, to avoid ambiguity from creeping up on us.
Up to you, I'm also happy to investigate if you have other things on your plate.
There was a problem hiding this comment.
at any scope level
May be fine to not do this at the top-level scope. I think most engines will run such queries. The issue appears when you try to project from or expand a scope that contains duplicate projection names.
2eccfcc to
75954c5
Compare
When
qualify_columnsexpands aSELECT *over sources with overlapping column names (e.g. two joined tables that both have anidcolumn), it causes problems:This is a problem when that expanded scope is itself re-exposed to an outer scope via another
table.*oralias.*, since duplicate aliases at that point collapse distinct columns under the same name.Sample Query (wrong canonicalization)
Output (before fix), this is a different query (vs input):
Expected output (after fix), same query as input:
Sample Queries (for query collision):
These are different queries that return different data, but they both collide into:
Expected output: