prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  🪲 Column Names not Aliased
  
  **Sample Code/Statement** _If you can, please submit the SQL statement or Python code snippet, or a representative example using the sample datasets._
  
  Example from user
  ~~~sql
  SELECT *
    FROM $planets AS P
   INNER JOIN $satellites AS S
        ON P.id = S.id
  ~~~
  
  Simplified example
  ~~~sql
  SELECT *
    FROM $planets
   INNER JOIN $satellites
        ON $planets.id = $satellites.id
  ~~~
  
  **Additional context** _Add any other context about the problem here, for example what you have done to try to diagnose or workaround the problem._
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `opteryx/operators/exit_node.py`
  Content:
  ```
  1 # Licensed under the Apache License, Version 2.0 (the "License");
  2 # you may not use this file except in compliance with the License.
  3 # You may obtain a copy of the License at
  4 #
  5 #     http://www.apache.org/licenses/LICENSE-2.0
  6 #
  7 # Unless required by applicable law or agreed to in writing, software
  8 # distributed under the License is distributed on an "AS IS" BASIS,
  9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 # See the License for the specific language governing permissions and
  11 # limitations under the License.
  12 
  13 """
  14 Exit Node
  15 
  16 This is a SQL Query Execution Plan Node.
  17 
  18 This does the final preparation before returning results to users.
  19 
  20 This does two things that the projection node doesn't do:
  21     - renames columns from the internal names
  22     - removes all columns not being returned to the user
  23 
  24 This node doesn't do any calculations, it is a pure Projection.
  25 """
  26 import time
  27 from typing import Generator
  28 
  29 from opteryx.exceptions import AmbiguousIdentifierError
  30 from opteryx.exceptions import InvalidInternalStateError
  31 from opteryx.models import QueryProperties
  32 from opteryx.operators import BasePlanNode
  33 
  34 
  35 class ExitNode(BasePlanNode):
  36     def __init__(self, properties: QueryProperties, **config):
  37         super().__init__(properties=properties)
  38         self.columns = config.get("columns", [])
  39 
  40     @property
  41     def config(self):  # pragma: no cover
  42         return None
  43 
  44     @property
  45     def name(self):  # pragma: no cover
  46         return "Exit"
  47 
  48     def execute(self) -> Generator:
  49         start = time.monotonic_ns()
  50         morsels = self._producers[0]  # type:ignore
  51 
  52         final_columns = []
  53         final_names = []
  54         for column in self.columns:
  55             final_columns.append(column.schema_column.identity)
  56             final_names.append(column.current_name)
  57 
  58         if len(final_columns) != len(set(final_columns)):  # pragma: no cover
  59             from collections import Counter
  60 
  61             duplicates = [column for column, count in Counter(final_columns).items() if count > 1]
  62             matches = {a for a, b in zip(final_names, final_columns) if b in duplicates}
  63             raise AmbiguousIdentifierError(
  64                 message=f"Query result contains multiple instances of the same column(s) - `{'`, `'.join(matches)}`"
  65             )
  66 
  67         self.statistics.time_exiting += time.monotonic_ns() - start
  68         for morsel in morsels.execute():
  69             start = time.monotonic_ns()
  70             if not set(final_columns).issubset(morsel.column_names):  # pragma: no cover
  71                 mapping = {name: int_name for name, int_name in zip(final_columns, final_names)}
  72                 missing_references = {
  73                     mapping.get(ref): ref for ref in final_columns if ref not in morsel.column_names
  74                 }
  75 
  76                 raise InvalidInternalStateError(
  77                     f"The following fields were not in the resultset - {', '.join(missing_references.keys())}"
  78                 )
  79 
  80             morsel = morsel.select(final_columns)
  81             morsel = morsel.rename_columns(final_names)
  82 
  83             self.statistics.time_exiting += time.monotonic_ns() - start
  84             yield morsel
  85             start = time.monotonic_ns()
  86
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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
  @@ -64,6 +64,14 @@
                   message=f"Query result contains multiple instances of the same column(s) - `{'`, `'.join(matches)}`"
               )
   
  +        if len(set(final_names)) != len(final_names):  # we have duplicate names
  +            final_names = []
  +            for column in self.columns:
  +                if column.schema_column.origin:
  +                    final_names.append(f"{column.schema_column.origin[0]}.{column.current_name}")
  +                else:
  +                    final_names.append(column.qualified_name)
  +
           self.statistics.time_exiting += time.monotonic_ns() - start
           for morsel in morsels.execute():
               start = time.monotonic_ns()
  
verification_info: {"golden_diff": "diff --git a/opteryx/operators/exit_node.py b/opteryx/operators/exit_node.py\n--- a/opteryx/operators/exit_node.py\n+++ b/opteryx/operators/exit_node.py\n@@ -64,6 +64,14 @@\n                 message=f\"Query result contains multiple instances of the same column(s) - `{'`, `'.join(matches)}`\"\n             )\n \n+        if len(set(final_names)) != len(final_names):  # we have duplicate names\n+            final_names = []\n+            for column in self.columns:\n+                if column.schema_column.origin:\n+                    final_names.append(f\"{column.schema_column.origin[0]}.{column.current_name}\")\n+                else:\n+                    final_names.append(column.qualified_name)\n+\n         self.statistics.time_exiting += time.monotonic_ns() - start\n         for morsel in morsels.execute():\n             start = time.monotonic_ns()\n", "issue": "\ud83e\udeb2 Column Names not Aliased\n\r\n**Sample Code/Statement** _If you can, please submit the SQL statement or Python code snippet, or a representative example using the sample datasets._\r\n\r\nExample from user\r\n~~~sql\r\nSELECT *\r\n  FROM $planets AS P\r\n INNER JOIN $satellites AS S\r\n      ON P.id = S.id\r\n~~~\r\n\r\nSimplified example\r\n~~~sql\r\nSELECT *\r\n  FROM $planets\r\n INNER JOIN $satellites\r\n      ON $planets.id = $satellites.id\r\n~~~\r\n\r\n**Additional context** _Add any other context about the problem here, for example what you have done to try to diagnose or workaround the problem._\r\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nExit Node\n\nThis is a SQL Query Execution Plan Node.\n\nThis does the final preparation before returning results to users.\n\nThis does two things that the projection node doesn't do:\n    - renames columns from the internal names\n    - removes all columns not being returned to the user\n\nThis node doesn't do any calculations, it is a pure Projection.\n\"\"\"\nimport time\nfrom typing import Generator\n\nfrom opteryx.exceptions import AmbiguousIdentifierError\nfrom opteryx.exceptions import InvalidInternalStateError\nfrom opteryx.models import QueryProperties\nfrom opteryx.operators import BasePlanNode\n\n\nclass ExitNode(BasePlanNode):\n    def __init__(self, properties: QueryProperties, **config):\n        super().__init__(properties=properties)\n        self.columns = config.get(\"columns\", [])\n\n    @property\n    def config(self):  # pragma: no cover\n        return None\n\n    @property\n    def name(self):  # pragma: no cover\n        return \"Exit\"\n\n    def execute(self) -> Generator:\n        start = time.monotonic_ns()\n        morsels = self._producers[0]  # type:ignore\n\n        final_columns = []\n        final_names = []\n        for column in self.columns:\n            final_columns.append(column.schema_column.identity)\n            final_names.append(column.current_name)\n\n        if len(final_columns) != len(set(final_columns)):  # pragma: no cover\n            from collections import Counter\n\n            duplicates = [column for column, count in Counter(final_columns).items() if count > 1]\n            matches = {a for a, b in zip(final_names, final_columns) if b in duplicates}\n            raise AmbiguousIdentifierError(\n                message=f\"Query result contains multiple instances of the same column(s) - `{'`, `'.join(matches)}`\"\n            )\n\n        self.statistics.time_exiting += time.monotonic_ns() - start\n        for morsel in morsels.execute():\n            start = time.monotonic_ns()\n            if not set(final_columns).issubset(morsel.column_names):  # pragma: no cover\n                mapping = {name: int_name for name, int_name in zip(final_columns, final_names)}\n                missing_references = {\n                    mapping.get(ref): ref for ref in final_columns if ref not in morsel.column_names\n                }\n\n                raise InvalidInternalStateError(\n                    f\"The following fields were not in the resultset - {', '.join(missing_references.keys())}\"\n                )\n\n            morsel = morsel.select(final_columns)\n            morsel = morsel.rename_columns(final_names)\n\n            self.statistics.time_exiting += time.monotonic_ns() - start\n            yield morsel\n            start = time.monotonic_ns()\n", "path": "opteryx/operators/exit_node.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nExit Node\n\nThis is a SQL Query Execution Plan Node.\n\nThis does the final preparation before returning results to users.\n\nThis does two things that the projection node doesn't do:\n    - renames columns from the internal names\n    - removes all columns not being returned to the user\n\nThis node doesn't do any calculations, it is a pure Projection.\n\"\"\"\nimport time\nfrom typing import Generator\n\nfrom opteryx.exceptions import AmbiguousIdentifierError\nfrom opteryx.exceptions import InvalidInternalStateError\nfrom opteryx.models import QueryProperties\nfrom opteryx.operators import BasePlanNode\n\n\nclass ExitNode(BasePlanNode):\n    def __init__(self, properties: QueryProperties, **config):\n        super().__init__(properties=properties)\n        self.columns = config.get(\"columns\", [])\n\n    @property\n    def config(self):  # pragma: no cover\n        return None\n\n    @property\n    def name(self):  # pragma: no cover\n        return \"Exit\"\n\n    def execute(self) -> Generator:\n        start = time.monotonic_ns()\n        morsels = self._producers[0]  # type:ignore\n\n        final_columns = []\n        final_names = []\n        for column in self.columns:\n            final_columns.append(column.schema_column.identity)\n            final_names.append(column.current_name)\n\n        if len(final_columns) != len(set(final_columns)):  # pragma: no cover\n            from collections import Counter\n\n            duplicates = [column for column, count in Counter(final_columns).items() if count > 1]\n            matches = {a for a, b in zip(final_names, final_columns) if b in duplicates}\n            raise AmbiguousIdentifierError(\n                message=f\"Query result contains multiple instances of the same column(s) - `{'`, `'.join(matches)}`\"\n            )\n\n        if len(set(final_names)) != len(final_names):  # we have duplicate names\n            final_names = []\n            for column in self.columns:\n                if column.schema_column.origin:\n                    final_names.append(f\"{column.schema_column.origin[0]}.{column.current_name}\")\n                else:\n                    final_names.append(column.qualified_name)\n\n        self.statistics.time_exiting += time.monotonic_ns() - start\n        for morsel in morsels.execute():\n            start = time.monotonic_ns()\n            if not set(final_columns).issubset(morsel.column_names):  # pragma: no cover\n                mapping = {name: int_name for name, int_name in zip(final_columns, final_names)}\n                missing_references = {\n                    mapping.get(ref): ref for ref in final_columns if ref not in morsel.column_names\n                }\n\n                raise InvalidInternalStateError(\n                    f\"The following fields were not in the resultset - {', '.join(missing_references.keys())}\"\n                )\n\n            morsel = morsel.select(final_columns)\n            morsel = morsel.rename_columns(final_names)\n\n            self.statistics.time_exiting += time.monotonic_ns() - start\n            yield morsel\n            start = time.monotonic_ns()\n", "path": "opteryx/operators/exit_node.py"}]}