diff --git a/src/mountainash_data/backends/ibis/backend.py b/src/mountainash_data/backends/ibis/backend.py index cf3aecd..75ebea9 100644 --- a/src/mountainash_data/backends/ibis/backend.py +++ b/src/mountainash_data/backends/ibis/backend.py @@ -483,8 +483,9 @@ def table(self, name: str, *, database: str | None = None) -> t.Any: def table_exists( self, name: str, database: str | None = None ) -> bool: - tables = self.list_tables() - return name in tables + # ibis exposes no native table_exists; scope the membership check to the + # requested namespace by forwarding database= through list_tables (DEBT-9). + return name in self.list_tables(namespace=database) def run_sql( self, diff --git a/tests/test_unit/backends/ibis/test_backend.py b/tests/test_unit/backends/ibis/test_backend.py index 051a6b8..6f0896b 100644 --- a/tests/test_unit/backends/ibis/test_backend.py +++ b/tests/test_unit/backends/ibis/test_backend.py @@ -320,6 +320,45 @@ def test_table_exists_returns_bool(): assert backend.table_exists("t") is True +def test_table_exists_honors_database_namespace(): + """table_exists(database=...) must scope the check to that namespace (DEBT-9). + + A table living only in a non-default schema must be found via ``database=`` + and must NOT be found in the default namespace, and vice versa. + """ + with IbisBackend(dialect="duckdb", database=":memory:") as backend: + raw = backend.ibis_connection() + raw.raw_sql("CREATE SCHEMA tenant_a") + raw.raw_sql("CREATE TABLE tenant_a.sleep (id INTEGER)") + raw.raw_sql("CREATE TABLE main_only (id INTEGER)") + + # Table exists only in tenant_a. + assert backend.table_exists("sleep", database="tenant_a") is True + assert backend.table_exists("sleep") is False + # Table exists only in the default namespace. + assert backend.table_exists("main_only") is True + assert backend.table_exists("main_only", database="tenant_a") is False + + +def test_table_exists_forwards_database_to_introspection(monkeypatch): + """The ``database`` arg must reach the introspection call, not be dropped. + + Guards the swallowed-error path: ``IbisConnection.list_tables`` returns ``[]`` + on failure, so a test asserting only a bool return can pass on a version that + never forwards ``database``. Assert the forwarding directly. + """ + with IbisBackend(dialect="sqlite", database=":memory:") as backend: + seen: dict[str, str | None] = {} + + def fake_list_tables(namespace=None): + seen["namespace"] = namespace + return ["sleep"] + + monkeypatch.setattr(backend, "list_tables", fake_list_tables) + assert backend.table_exists("sleep", database="tenant_a") is True + assert seen == {"namespace": "tenant_a"} + + def test_fluent_chaining(): """Multiple fluent calls can be chained.""" with IbisBackend(dialect="sqlite", database=":memory:") as backend: