Repository: ibis-project/ibis

Issue:
bug(sqlalchemy): incorrect CROSS JOIN with multiple joins
From @gvwilson's comments in #2087, using this SQL schema:

```
CREATE TABLE Person (
    id text,
    personal text,
    family text
);
CREATE TABLE Visited (
    id integer,
    site text,
    dated text
);
CREATE TABLE Survey (
    taken integer,
    person text,
    quant text,
    reading real,
    FOREIGN KEY(taken) REFERENCES Visited(id),
    FOREIGN KEY(person) REFERENCES Person(id)
);
```

with this Ibis expression:
```
survey = con.table("Survey")
person = con.table("Person")
visited = con.table("Visited")
q = person.join(survey, person.id==survey.person).join(visited, visited.id==survey.taken)
print(str(q.compile()))
```

it results in this SQL:
```
SELECT anon_1.id AS id_x,
       anon_1.personal,       anon_1.family,       anon_1.taken,
       anon_1.person,       anon_1.quant,       anon_1.reading,
       t2.id AS id_y,       t2.site,       t2.dated
FROM  (SELECT t0.id AS id,          t0.personal AS personal,
          t0.family AS family,
          t1.taken AS taken,
          t1.person AS person,
          t1.quant AS quant,
          t1.reading AS reading
   FROM main."Person" AS t0
   JOIN main."Survey" AS t1 ON t0.id = t1.person) AS anon_1,
     main."Person" AS t0
JOIN main."Survey" AS t1 ON t0.id = t1.person
JOIN main."Visited" AS t2 ON t2.id = t1.taken
```

Note the extra implicit CROSS JOIN with "Person" as well as the extra JOIN with "Survey", which is incorrect.


Discussion:
The shared columns named `id` are the problem here; if each column (not just join keys!) is named uniquely, then it works as expected.
xref: #3526 maybe?

Resolving patch:
diff --git a/ibis/backends/base/sql/alchemy/registry.py b/ibis/backends/base/sql/alchemy/registry.py
--- a/ibis/backends/base/sql/alchemy/registry.py
+++ b/ibis/backends/base/sql/alchemy/registry.py
@@ -117,9 +117,11 @@ def get_col_or_deferred_col(sa_table, colname):
     where `table_set` is `sa_table` above.
     """
     try:
-        out_expr = sa_table.c[colname]
+        col = sa_table.c[colname]
     except KeyError:
         out_expr = sa.column(colname)
+    else:
+        out_expr = sa_table.corresponding_column(col)
 
     return out_expr
 


Test patch:
diff --git a/ibis/tests/sql/test_sqlalchemy.py b/ibis/tests/sql/test_sqlalchemy.py
--- a/ibis/tests/sql/test_sqlalchemy.py
+++ b/ibis/tests/sql/test_sqlalchemy.py
@@ -841,3 +841,63 @@ def test_filter_group_by_agg_with_same_name():
     )
     ex = sa.select([t0]).where(t0.c.bigint_col == 60)
     _check(expr, ex)
+
+
+@pytest.fixture
+def person():
+    return ibis.table(
+        dict(id="string", personal="string", family="string"),
+        name="person",
+    )
+
+
+@pytest.fixture
+def visited():
+    return ibis.table(
+        dict(id="int32", site="string", dated="string"),
+        name="visited",
+    )
+
+
+@pytest.fixture
+def survey():
+    return ibis.table(
+        dict(
+            taken="int32",
+            person="string",
+            quant="string",
+            reading="float32",
+        ),
+        name="survey",
+    )
+
+
+def test_no_cross_join(person, visited, survey):
+    expr = person.join(survey, person.id == survey.person).join(
+        visited,
+        visited.id == survey.taken,
+    )
+
+    context = AlchemyContext(compiler=AlchemyCompiler)
+    _ = AlchemyCompiler.to_sql(expr, context)
+
+    t0 = context.get_ref(person)
+    t1 = context.get_ref(survey)
+    t2 = context.get_ref(visited)
+
+    from_ = t0.join(t1, t0.c.id == t1.c.person).join(t2, t2.c.id == t1.c.taken)
+    ex = sa.select(
+        [
+            t0.c.id.label("id_x"),
+            t0.c.personal,
+            t0.c.family,
+            t1.c.taken,
+            t1.c.person,
+            t1.c.quant,
+            t1.c.reading,
+            t2.c.id.label("id_y"),
+            t2.c.site,
+            t2.c.dated,
+        ]
+    ).select_from(from_)
+    _check(expr, ex)