Repository: ibis-project/ibis

Issue:
bug: 'No database selected' error when attempting to infer column type
### What happened?

I'm using a mysql database with multiple schemas (separate 'databases' all on the same mysql server). I can connect to the various tables with the table method, e.g. `conn.table(name=table_name, schema=db_name)`,where the original connection does not specify a specific db, e.g. `mysql://DB_USER':PASSWORD@HOST')}:PORT`

This works fine generally, however when I have a table that includes a datatype that ibis has to infer, the type inference logic ends up referencing the table incorrectly and so results in the error log provided. 

I've put in a quick fix which resolves the problem for me, but is not tested properly and might just be specific to my use case.

https://github.com/ibis-project/ibis/blob/a7e845ac9a2c2069135c38a923babc1f1d6de65c/ibis/backends/base/sql/alchemy/__init__.py#L471

After this line I added the following to add the table.schema name to the quoted_name following dot notation:

`quoted_name = ".".join([table.schema, quoted_name])`


### What version of ibis are you using?

5.1.0

### What backend(s) are you using, if any?

mysql

### Relevant log output

```sh
File "/.venv/lib/python3.10/site-packages/ibis/backends/base/sql/alchemy/__init__.py", line 519, in table
    sqla_table = self._get_sqla_table(name, database=database, schema=schema)
  File "/.venv/lib/python3.10/site-packages/ibis/backends/base/sql/alchemy/__init__.py", line 435, in _get_sqla_table
    return self._handle_failed_column_type_inference(table, nulltype_cols)
  File "/.venv/lib/python3.10/site-packages/ibis/backends/base/sql/alchemy/__init__.py", line 446, in _handle_failed_column_type_inference
    for colname, type in self._metadata(quoted_name):
  File "/.venv/lib/python3.10/site-packages/ibis/backends/mysql/__init__.py", line 119, in _metadata
    result = con.exec_driver_sql(f"SELECT * FROM {query} _ LIMIT 0")
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1772, in exec_driver_sql
    ret = self._execute_context(
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1842, in _execute_context
    return self._exec_single_context(
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1983, in _exec_single_context
    self._handle_dbapi_exception(
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2326, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1964, in _exec_single_context
    self.dialect.do_execute(
  File "/.venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 748, in do_execute
    cursor.execute(statement, parameters)
  File "/.venv/lib/python3.10/site-packages/pymysql/cursors.py", line 158, in execute
    result = self._query(query)
  File "/.venv/lib/python3.10/site-packages/pymysql/cursors.py", line 325, in _query
    conn.query(q)
  File "/.venv/lib/python3.10/site-packages/pymysql/connections.py", line 549, in query
    self._affected_rows = self._read_query_result(unbuffered=unbuffered)
  File "/.venv/lib/python3.10/site-packages/pymysql/connections.py", line 779, in _read_query_result
    result.read()
  File "/.venv/lib/python3.10/site-packages/pymysql/connections.py", line 1157, in read
    first_packet = self.connection._read_packet()
  File "/.venv/lib/python3.10/site-packages/pymysql/connections.py", line 729, in _read_packet
    packet.raise_for_error()
  File "/.venv/lib/python3.10/site-packages/pymysql/protocol.py", line 221, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "/.venv/lib/python3.10/site-packages/pymysql/err.py", line 143, in raise_mysql_exception
    raise errorclass(errno, errval)
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1046, 'No database selected')
[SQL: SELECT * FROM findings _ LIMIT 0]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
@heshamdar Welcome 👋🏻!

Thanks for the thorough report, description and traceback. This looks like a bug.

We'll investigate and should get a fix in soon!
@heshamdar Do you have a table schema (e.g., `CREATE TABLE t (x SOME_TYPE)`) that triggers this bug? I don't know off the top of my head what column type might trigger this.

Resolving patch:
diff --git a/ibis/backends/base/sql/alchemy/__init__.py b/ibis/backends/base/sql/alchemy/__init__.py
--- a/ibis/backends/base/sql/alchemy/__init__.py
+++ b/ibis/backends/base/sql/alchemy/__init__.py
@@ -467,8 +467,15 @@ def _handle_failed_column_type_inference(
         """Handle cases where SQLAlchemy cannot infer the column types of `table`."""
 
         self.inspector.reflect_table(table, table.columns)
+
         dialect = self.con.dialect
-        quoted_name = dialect.identifier_preparer.quote(table.name)
+
+        quoted_name = ".".join(
+            map(
+                dialect.identifier_preparer.quote,
+                filter(None, [table.schema, table.name]),
+            )
+        )
 
         for colname, type in self._metadata(quoted_name):
             if colname in nulltype_cols:


Test patch:
diff --git a/ibis/backends/mysql/tests/conftest.py b/ibis/backends/mysql/tests/conftest.py
--- a/ibis/backends/mysql/tests/conftest.py
+++ b/ibis/backends/mysql/tests/conftest.py
@@ -106,6 +106,20 @@ def connect(_: Path):
         )
 
 
+@pytest.fixture(scope='session')
+def setup_privs():
+    engine = sa.create_engine(f"mysql+pymysql://root:@{MYSQL_HOST}:{MYSQL_PORT:d}")
+    with engine.begin() as con:
+        # allow the ibis user to use any database
+        con.exec_driver_sql("CREATE SCHEMA IF NOT EXISTS `test_schema`")
+        con.exec_driver_sql(
+            f"GRANT CREATE,SELECT,DROP ON `test_schema`.* TO `{MYSQL_USER}`@`%%`"
+        )
+    yield
+    with engine.begin() as con:
+        con.exec_driver_sql("DROP SCHEMA IF EXISTS `test_schema`")
+
+
 @pytest.fixture(scope='session')
 def con():
     return ibis.mysql.connect(
@@ -115,3 +129,10 @@ def con():
         database=IBIS_TEST_MYSQL_DB,
         port=MYSQL_PORT,
     )
+
+
+@pytest.fixture(scope='session')
+def con_nodb():
+    return ibis.mysql.connect(
+        host=MYSQL_HOST, user=MYSQL_USER, password=MYSQL_PASS, port=MYSQL_PORT
+    )
diff --git a/ibis/backends/mysql/tests/test_client.py b/ibis/backends/mysql/tests/test_client.py
--- a/ibis/backends/mysql/tests/test_client.py
+++ b/ibis/backends/mysql/tests/test_client.py
@@ -86,3 +86,18 @@ def test_blob_type(con, coltype):
     finally:
         with con.begin() as c:
             c.exec_driver_sql(f"DROP TABLE {tmp}")
+
+
+@pytest.fixture(scope="session")
+def tmp_t(con_nodb):
+    with con_nodb.begin() as c:
+        c.exec_driver_sql("CREATE TABLE IF NOT EXISTS test_schema.t (x INET6)")
+    yield
+    with con_nodb.begin() as c:
+        c.exec_driver_sql("DROP TABLE IF EXISTS test_schema.t")
+
+
+@pytest.mark.usefixtures("setup_privs", "tmp_t")
+def test_get_schema_from_query_other_schema(con_nodb):
+    t = con_nodb.table("t", schema="test_schema")
+    assert t.schema() == ibis.schema({"x": dt.string})