Repository: mabel-dev/opteryx

Issue:
🪲 Column aliasing flaws
### Thank you for taking the time to report a problem with Opteryx.
_To help us to respond to your request we ask that you try to provide the below detail about the bug._

**Sample Code/Statement** _If you can, please submit the SQL statement or Python code snippet, or a representative example using the sample datasets._

~~~sql
SELECT * FROM $planets LEFT JOIN(SELECT planetId AS id FROM $satellites) AS S USING(id)
~~~

The column in the child query appears to remain being called `planetId` rather than `id`



Discussion:

Resolving patch:
diff --git a/opteryx/components/binder/binder_visitor.py b/opteryx/components/binder/binder_visitor.py
--- a/opteryx/components/binder/binder_visitor.py
+++ b/opteryx/components/binder/binder_visitor.py
@@ -324,7 +324,7 @@ def visit_exit(self, node: Node, context: BindingContext) -> Tuple[Node, Binding
         # LOG: Exit
 
         # clear the derived schema
-        context.schemas["$derived"] = derived.schema()
+        context.schemas.pop("$derived", None)
 
         seen = set()
         needs_qualifier = any(
@@ -363,17 +363,20 @@ def keep_column(column, identities):
         for qualifier, schema in context.schemas.items():
             for column in schema.columns:
                 if keep_column(column, identities):
-                    column_reference = Node(
+                    column_name = name_column(qualifier=qualifier, column=column)
+                    column_reference = LogicalColumn(
                         node_type=NodeType.IDENTIFIER,
-                        name=column.name,
+                        source_column=column_name,
+                        source=None,
+                        alias=None,
                         schema_column=column,
-                        type=column.type,
-                        query_column=name_column(qualifier, column),
                     )
                     columns.append(column_reference)
 
         node.columns = columns
 
+        context.schemas["$derived"] = derived.schema()
+
         return node, context
 
     def visit_function_dataset(
@@ -738,11 +741,29 @@ def visit_show_columns(
         return node, context
 
     def visit_subquery(self, node: Node, context: BindingContext) -> Tuple[Node, BindingContext]:
+        node, context = self.visit_exit(node, context)
+
         # we sack all the tables we previously knew and create a new set of schemas here
         columns = []
         for name, schema in context.schemas.items():
             for schema_column in schema.columns:
+                projection_column = [
+                    column
+                    for column in node.columns
+                    if column.schema_column.identity == schema_column.identity
+                ]
+                if len(projection_column) > 1:
+                    from opteryx.exceptions import AmbiguousIdentifierError
+
+                    raise AmbiguousIdentifierError(
+                        message=f"Sub Query contains multiple instances of the same column."
+                    )
+
                 schema_column.origin = [node.alias]
+                schema_column.name = (
+                    projection_column[0].current_name if projection_column else schema_column.name
+                )
+                schema_column.aliases = []
                 columns.append(schema_column)
             if name[0] != "$" and name in context.relations:
                 context.relations.remove(name)
diff --git a/opteryx/components/logical_planner.py b/opteryx/components/logical_planner.py
--- a/opteryx/components/logical_planner.py
+++ b/opteryx/components/logical_planner.py
@@ -547,6 +547,7 @@ def create_node_relation(relation):
 
                 subquery_plan = plan_query(subquery["subquery"])
                 exit_node = subquery_plan.get_exit_points()[0]
+                subquery_step.columns = subquery_plan[exit_node].columns
                 subquery_plan.remove_node(exit_node, heal=True)
 
                 sub_plan += subquery_plan
diff --git a/opteryx/operators/exit_node.py b/opteryx/operators/exit_node.py
--- a/opteryx/operators/exit_node.py
+++ b/opteryx/operators/exit_node.py
@@ -51,7 +51,7 @@ def execute(self) -> Iterable:
         final_names = []
         for column in self.columns:
             final_columns.append(column.schema_column.identity)
-            final_names.append(column.query_column)
+            final_names.append(column.current_name)
 
         if len(final_columns) != len(set(final_columns)):
             from collections import Counter


Test patch:
diff --git a/tests/sql_battery/test_shapes_and_errors_battery.py b/tests/sql_battery/test_shapes_and_errors_battery.py
--- a/tests/sql_battery/test_shapes_and_errors_battery.py
+++ b/tests/sql_battery/test_shapes_and_errors_battery.py
@@ -272,7 +272,7 @@
         ("SELECT name as Name, name as NAME FROM $satellites", 177, 2, AmbiguousIdentifierError),
         ("SELECT COUNT(id) as countID, MIN(id) as minID FROM $satellites", 1, 2, None),
         ("SELECT s.id as satelliteID, p.id as planetID FROM $satellites s JOIN $planets p ON s.planetId = p.id", 177, 2, None),
-        ("SELECT x.id FROM (SELECT id as ID FROM $satellites WHERE id < 10) x", 9, 1, None),
+        ("SELECT x.ID FROM (SELECT id as ID FROM $satellites WHERE id < 10) x", 9, 1, None),
         ("SELECT name as n as m FROM $satellites", None, 1, SqlError),
         ("SELECT id*2 as doubleID FROM $satellites", 177, 1, None),
         ("SELECT id as Identifier FROM $satellites ORDER BY Identifier", 177, 1, None),
@@ -1175,10 +1175,12 @@
         ("SELECT is_reply_to FROM testdata.flat.formats.parquet WITH(NO_PARTITION) WHERE COALESCE(is_reply_to, -1) < 0", 74765, 1, None),
         # Names not found / clashes [#471]
         ("SELECT P.* FROM (SELECT * FROM $planets) AS P", 9, 20, None),
-        ("SELECT P0.id, P1.ID, P2.ID FROM $planets AS P0 JOIN (SELECT id AS ID, name FROM $planets AS ppp) AS P1 ON P0.name = P1.name JOIN (SELECT id, name AS ID FROM $planets AS pppp) AS P2 ON P0.name = P2.name", 9, 3, None),
-        ("SELECT P0.id, P1.ID FROM $planets AS P0 INNER JOIN (SELECT id, name AS ID FROM $planets) AS P1 ON P0.name = P1.name", 9, 2, None),
+        ("SELECT P0.id, P1.ID, P2.ID FROM $planets AS P0 JOIN (SELECT id AS ID, name FROM $planets AS ppp) AS P1 ON P0.name = P1.name JOIN (SELECT id, name AS ID FROM $planets AS pppp) AS P2 ON P0.name = P2.name", 9, 3, ColumnNotFoundError),
+        ("SELECT P0.id, P1.ID, P2.ID FROM $planets AS P0 JOIN (SELECT id AS ID, name FROM $planets AS ppp) AS P1 ON P0.name = P1.name JOIN (SELECT id, name AS ID FROM $planets AS pppp) AS P2 ON P0.name = P2.ID", 9, 3, ColumnNotFoundError),
+        ("SELECT P0.id, P1.ID FROM $planets AS P0 INNER JOIN (SELECT id, name AS ID FROM $planets) AS P1 ON P0.name = P1.name", 9, 2, ColumnNotFoundError),
+        ("SELECT P0.id, P1.ID FROM $planets AS P0 INNER JOIN (SELECT id, name AS ID FROM $planets) AS P1 ON P0.name = P1.ID", 9, 2, ColumnNotFoundError),
         ("SELECT P0.id, P1.ID FROM $planets AS P0 INNER JOIN (SELECT name, id AS ID FROM $planets) AS P1 USING (name)", 9, 2, UnsupportedSyntaxError),
-        ("SELECT P0.id, P1.ID FROM $planets AS P0 LEFT JOIN (SELECT id, name AS ID FROM $planets) AS P1 ON P0.name = P1.name", 9, 2, None),
+        ("SELECT P0.id, P1.ID FROM $planets AS P0 LEFT JOIN (SELECT id, name AS ID FROM $planets) AS P1 ON P0.name = P1.ID", 9, 2, None),
         # [#475] a variation of #471
         ("SELECT P0.id, P1.ID, P2.ID FROM $planets AS P0 JOIN (SELECT CONCAT_WS(' ', list(id)) AS ID, MAX(name) AS n FROM $planets AS Q1 GROUP BY gravity) AS P1 ON P0.name = P1.n JOIN (SELECT CONCAT_WS(' ', list(id)) AS ID, MAX(name) AS n FROM $planets AS Q2 GROUP BY gravity) AS P2 ON P0.name = P2.n", 8, 3, None),
         # no issue number - but these two caused a headache