Repository navigation
fix: coalesce the merged key of RIGHT/FULL USING/NATURAL joins - #22998
Phoenix500526 wants to merge 1 commit into
Conversation
neilconway
left a comment
There was a problem hiding this comment.
Thanks for sending this PR! Overall I agree that the current behavior is wrong and should be fixed.
I think the current approach produces an error for queries like SELECT k FROM a FULL JOIN b USING(k) ORDER BY a.k, because the sort introduces a hidden projection column.
Hi, @neilconway I dug into this one. The failure is a schema clash: I found two ways out, neither perfectly clean. One is to give the merged key a fake qualifier, e.g. SELECT __join_virtual_table__.order_id FROM (
SELECT CASE WHEN o1.order_id IS NOT NULL THEN o1.order_id ELSE o2.order_id END AS order_id, o1.order_id
FROM orders o1 FULL JOIN orders o2 USING(order_id) ORDER BY o1.order_id)
-- re-plan: qualified field name o1.order_id and unqualified field name order_id would be ambiguousThe other is to push the Sort below the merged-key projection so it reads a.k straight off the join: Clean plan, fixes the SELECT ... FROM (SELECT *, * FROM orders o1 FULL JOIN orders o2 USING(order_id) ORDER BY o1.order_id)
-- re-plan: Projections require unique expression names ... order_id ... order_idSo both fix execution but both trip up unparse — a FULL USING merged key is a COALESCE the unparser doesn't turn back into USING. Neither feels clean enough to me — do you have a better approach in mind for this case? |
|
@Phoenix500526 Thanks for revising this! The |
c1cbe60 to
a7cb6cd
Compare
@neilconway Thanks so much for offering to dig into the Quick rundown: Idea: rename the merged key to a reserved internal name, not a fake qualifier. Both of my earlier attempts died at unparse because a During sort push-down, when a qualified sort column ( The folded schema is now I've added round-trip tests ( One loose end I should flag: the internal name leaks into EXPLAIN. The result column is k and the unparsed SQL is clean, but the intermediate plan nodes still show It's purely cosmetic — no effect on results or round-trip SQL — but it's not the prettiest. Hiding it completely would mean touching the generic plan Display, which felt like overkill, so I left it for now. If you spot a cleaner way, I'm all ears. |
b5c263f to
b89ffe3
Compare
|
@Phoenix500526 thanks for the update on this! I'm on vacation until July 8; happy to look at this when I get back. |
c97faf3 to
e5db60d
Compare
da16255 to
f4c5047
Compare
|
Hi @neilconway and @nathanb9 , sorry to ping. This PR has been open for a while, and I’d appreciate another look when you have time. Thanks. 😁 |
|
Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days. |
f4c5047 to
056c596
Compare
|
Hi @neilconway and @nathanb9, thanks for your earlier feedback. I've addressed the cases you raised, rebased onto |
056c596 to
541a169
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #22998 +/- ##
==========================================
- Coverage 82.78% 82.77% -0.01%
==========================================
Files 1148 1148
Lines 451075 451240 +165
Branches 451075 451240 +165
==========================================
+ Hits 373400 373523 +123
- Misses 54927 54955 +28
- Partials 22748 22762 +14 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
541a169 to
9c08afd
Compare
|
Just a note that this fixes a case I ran into with CREATE TABLE l(k INT, a VARCHAR) AS VALUES (1, 'l1'), (2, 'l2');
CREATE TABLE r(k INT, b VARCHAR) AS VALUES (2, 'r2'), (3, 'r3');
SELECT * FROM l AS z LEFT JOIN r AS a USING (k) ORDER BY a;
-- a | k | b
-- ---+------+-----
-- l1 | NULL | NULL <- k should be 1
-- l2 | 2 | r2This case behaves correctly on this branch. Thanks for the fix @Phoenix500526 and reviews @neilconway and @nathanb9 |
There was a problem hiding this comment.
If we want the output column order to be independent of alphabetic ordering and preserve the LHS schema order, this could be something like
cols.sort_by_key(|c| plan.schema().maybe_index_of_column(c));Unqualified USING and NATURAL keys could return NULL for right-only rows or fail beside qualified side keys. Resolve merged keys by join type across SELECT, WHERE, ORDER BY and wildcards while preserving qualified side access and SQL round trips. CLOSES apache#22881 Signed-off-by: Jiawei Zhao <Phoenix500526@163.com>
9c08afd to
7059068
Compare
Which issue does this PR close?
Rationale for this change
A
USING/NATURALjoin exposes its join key as a single merged column whose value, per the SQL standard, isCOALESCE(left.key, right.key). DataFusion resolved an unqualified reference to that merged key to the left column unconditionally.For
RIGHTandFULLjoins the left key is NULL-padded on rows that exist only on the right, so the merged key came out NULL instead of the value that is actually present. The wrong NULL is silent and propagates intoGROUP BY,ORDER BY(changing row order) andSELECT *, corrupting results;WHEREon the merged key additionally failed with an "ambiguous reference" error.INNER/LEFTare unaffected, since their left key is never NULL-padded.What changes are included in this PR?
The unqualified merged key of a
RIGHT/FULL`` USING/NATURALjoin now resolves toCOALESCE(left, right)everywhere it can be referenced:SELECT,ORDER BY,GROUP BY,HAVING,QUALIFY);WHEREpredicates — previously rejected as ambiguous; they now resolve against the join's real USING columns;SELECT *).Mechanics:
LogicalPlan::outer_using_key_pairs()returns the(left, right)key pairs ofRIGHT/FULLUSING/NATURALjoins;CASE WHEN left IS NOT NULL THEN left ELSE right END— the exact form coalesce is simplified to, so it can be built indatafusion-exprwithout depending on the functions crate — aliased to the key name so the output column keeps its name.INNER/LEFTjoins and qualified access (a.k / b.k) are left unchanged.Are these changes tested?
Yes. A new sqllogictest file
join_using_merged_key.sltcovers the merged key forRIGHT/FULL/NATURALjoins acrossSELECT,SELECT *,WHEREandORDER BY, withINNER/LEFT, qualifieda.k/b.kaccess, and the explicitcoalesce(a.k, b.k) ... ONform as regression guards.The full sqllogictest suite and the
datafusion-common/datafusion-expr/datafusion-sqlunit tests pass with no regressions.Are there any user-facing changes?
Yes. For
RIGHT/FULLUSING/NATURALjoins, the merged join key now returnsCOALESCE(left, right)(the value from whichever side is present) instead ofNULLfor rows that exist only on the right, and referencing the merged key inWHEREno longer raises an ambiguous-reference error. Queries that do not useRIGHT/FULLUSING/NATURALjoins are unaffected.No breaking public API changes (the change only adds new public items).