(Re)qualify the sides of a join if needed, i.e. if the columns from one side would otherwise conflict with the columns from the other. This is especially useful for queries that come as Substrait, since Substrait doesn't currently allow specifying aliases, neither for columns nor for tables. DataFusion requires columns to be uniquely identifiable, in some places (see e.g. DFSchema::check_names).
(
left: LogicalPlanBuilder,
right: LogicalPlanBuilder,
)
| 1773 | /// - The requalified or original right logical plan |
| 1774 | /// - If a requalification was needed or not |
| 1775 | pub fn requalify_sides_if_needed( |
| 1776 | left: LogicalPlanBuilder, |
| 1777 | right: LogicalPlanBuilder, |
| 1778 | ) -> Result<(LogicalPlanBuilder, LogicalPlanBuilder, bool)> { |
| 1779 | let left_cols = left.schema().columns(); |
| 1780 | let right_cols = right.schema().columns(); |
| 1781 | |
| 1782 | // Requalify if merging the schemas would cause an error during join. |
| 1783 | // This can happen in several cases: |
| 1784 | // 1. Duplicate qualified fields: both sides have same relation.name |
| 1785 | // 2. Duplicate unqualified fields: both sides have same unqualified name |
| 1786 | // 3. Ambiguous reference: one side qualified, other unqualified, same name |
| 1787 | // |
| 1788 | // Implementation note: This uses a simple O(n*m) nested loop rather than |
| 1789 | // a HashMap-based O(n+m) approach. The nested loop is preferred because: |
| 1790 | // - Schemas are typically small (in TPCH benchmark, max is 16 columns), |
| 1791 | // so n*m is negligible |
| 1792 | // - Early return on first conflict makes common case very fast |
| 1793 | // - Code is simpler and easier to reason about |
| 1794 | // - Called only during plan construction, not in execution hot path |
| 1795 | for l in &left_cols { |
| 1796 | for r in &right_cols { |
| 1797 | if l.name != r.name { |
| 1798 | continue; |
| 1799 | } |
| 1800 | |
| 1801 | // Same name - check if this would cause a conflict |
| 1802 | match (&l.relation, &r.relation) { |
| 1803 | // Both qualified with same relation - duplicate qualified field |
| 1804 | (Some(l_rel), Some(r_rel)) if l_rel == r_rel => { |
| 1805 | return Ok(( |
| 1806 | left.alias(TableReference::bare("left"))?, |
| 1807 | right.alias(TableReference::bare("right"))?, |
| 1808 | true, |
| 1809 | )); |
| 1810 | } |
| 1811 | // Both unqualified - duplicate unqualified field |
| 1812 | (None, None) => { |
| 1813 | return Ok(( |
| 1814 | left.alias(TableReference::bare("left"))?, |
| 1815 | right.alias(TableReference::bare("right"))?, |
| 1816 | true, |
| 1817 | )); |
| 1818 | } |
| 1819 | // One qualified, one not - ambiguous reference |
| 1820 | (Some(_), None) | (None, Some(_)) => { |
| 1821 | return Ok(( |
| 1822 | left.alias(TableReference::bare("left"))?, |
| 1823 | right.alias(TableReference::bare("right"))?, |
| 1824 | true, |
| 1825 | )); |
| 1826 | } |
| 1827 | // Different qualifiers - OK, no conflict |
| 1828 | _ => {} |
| 1829 | } |
| 1830 | } |
| 1831 | } |
| 1832 |
no test coverage detected
searching dependent graphs…