4 changes: 0 additions & 4 deletions ibis/backends/polars/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ def do_connect(
def version(self) -> str:
return pl.__version__

@property
def current_database(self) -> str | None:
raise NotImplementedError('polars backend does not support databases')

def list_tables(self, like=None, database=None):
return self._filter_with_like(list(self._tables.keys()), like)

Expand Down
16 changes: 11 additions & 5 deletions ibis/backends/postgres/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ def do_connect(
database=database,
driver=f'postgresql+{driver}',
)
self.database_name = alchemy_url.database

connect_args = {}
if schema is not None:
Expand All @@ -132,15 +131,22 @@ def connect(dbapi_connection, connection_record):

super().do_connect(engine)

def list_databases(self, like=None):
def list_databases(self, like=None) -> list[str]:
query = "SELECT datname FROM pg_catalog.pg_database WHERE NOT datistemplate"
with self.begin() as con:
# http://dba.stackexchange.com/a/1304/58517
databases = con.exec_driver_sql(
"SELECT datname FROM pg_catalog.pg_database WHERE NOT datistemplate"
).scalars()
databases = list(con.exec_driver_sql(query).scalars())

return self._filter_with_like(databases, like)

@property
def current_database(self) -> str:
return self._scalar_query(sa.select(sa.func.current_database()))

@property
def current_schema(self) -> str:
return self._scalar_query(sa.select(sa.func.current_schema()))

def function(self, name: str, *, schema: str | None = None) -> Callable:
query = sa.text(
"""
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/pyspark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def version(self):
return pyspark.__version__

@property
def current_database(self) -> str | None:
def current_database(self) -> str:
return self._catalog.currentDatabase()

def list_databases(self, like=None):
Expand Down
36 changes: 17 additions & 19 deletions ibis/backends/snowflake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,21 @@ class Backend(BaseAlchemyBackend, CanCreateDatabase, AlchemyCanCreateSchema):

_latest_udf_python_version = (3, 10)

@property
def _current_schema(self) -> str:
query = sa.select(sa.func.current_schema())
with self.begin() as con:
return con.execute(query).scalar()

def _convert_kwargs(self, kwargs):
with contextlib.suppress(KeyError):
kwargs["account"] = kwargs.pop("host")

@property
def version(self) -> str:
with self.begin() as con:
return con.execute(sa.select(sa.func.current_version())).scalar()
return self._scalar_query(sa.select(sa.func.current_version()))

@property
def current_schema(self) -> str:
return self._scalar_query(sa.select(sa.func.current_schema()))

@property
def current_database(self) -> str:
return self._scalar_query(sa.select(sa.func.current_database()))

def _compile_sqla_type(self, typ) -> str:
return sa.types.to_instance(typ).compile(dialect=self.con.dialect)
Expand Down Expand Up @@ -189,7 +190,6 @@ def do_connect(
url = URL(
account=account, user=user, password=password or "", **dbparams, **kwargs
)
self.database_name = dbparams["database"]
if connect_args is None:
connect_args = {}

Expand Down Expand Up @@ -226,7 +226,7 @@ def connect(dbapi_connection, connection_record):
dialect = engine.dialect
quote = dialect.preparer(dialect).quote_identifier
with dbapi_connection.cursor() as cur:
(database, schema) = cur.execute(
database, schema = cur.execute(
"SELECT CURRENT_DATABASE(), CURRENT_SCHEMA()"
).fetchone()
try:
Expand Down Expand Up @@ -424,18 +424,16 @@ def _metadata(self, query: str) -> Iterable[tuple[str, dt.DataType]]:
yield name, typ

def list_databases(self, like=None) -> list[str]:
d = sa.table(
"databases",
sa.column("database_name", sa.TEXT()),
schema="information_schema",
)
query = sa.select(d.c.database_name).order_by(d.c.database_name)
with self.begin() as con:
databases = con.exec_driver_sql(
"SELECT database_name FROM information_schema.databases"
).scalars()
databases = list(con.execute(query).scalars())
return self._filter_with_like(databases, like)

@property
def current_database(self) -> str:
query = sa.select(sa.func.current_database())
with self.begin() as con:
return con.execute(query).scalar()

def _register_in_memory_table(self, op: ops.InMemoryTable) -> None:
import pyarrow.parquet as pq

Expand Down
15 changes: 14 additions & 1 deletion ibis/backends/sqlite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import ibis.expr.datatypes as dt
import ibis.expr.schema as sch
from ibis import util
from ibis.backends.base import CanListDatabases
from ibis.backends.base.sql.alchemy import BaseAlchemyBackend
from ibis.backends.sqlite import udf
from ibis.backends.sqlite.compiler import SQLiteCompiler
Expand All @@ -38,7 +39,7 @@
import ibis.expr.types as ir


class Backend(BaseAlchemyBackend):
class Backend(BaseAlchemyBackend, CanListDatabases):
name = 'sqlite'
compiler = SQLiteCompiler
supports_create_or_replace = False
Expand All @@ -56,6 +57,18 @@ def __getstate__(self) -> dict:
)
return r

@property
def current_database(self) -> str:
# AFAICT there is no notion of a schema in SQLite
return "main"

def list_databases(self, like: str | None = None) -> list[str]:
with self.begin() as con:
mappings = con.exec_driver_sql("PRAGMA database_list").mappings()
results = list(toolz.pluck("name", mappings))

return sorted(self._filter_with_like(results, like))

def do_connect(
self,
database: str | Path | None = None,
Expand Down
9 changes: 3 additions & 6 deletions ibis/backends/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@ def test_version(backend):
# 1. `current_database` returns '.', but isn't listed in list_databases()
@pytest.mark.never(
["polars", "dask", "pandas"],
reason="backends does not support databases",
reason="backend does not support databases",
raises=AttributeError,
)
@pytest.mark.notimpl(
["duckdb", "mssql", "trino", "druid", "oracle"],
raises=AssertionError,
)
@pytest.mark.notimpl(["oracle"], raises=AssertionError)
@pytest.mark.notimpl(
["datafusion"],
raises=NotImplementedError,
reason="CURRENT_DATABASE() isn't implemented",
reason="current_database isn't implemented",
)
@pytest.mark.never(
["bigquery"], raises=FutureWarning, reason="list_databases is deprecated"
Expand Down
6 changes: 3 additions & 3 deletions ibis/backends/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,11 +508,11 @@ def test_list_databases(alchemy_con):
test_databases = {
"sqlite": {"main"},
"postgres": {"postgres", "ibis_testing"},
"mssql": {"INFORMATION_SCHEMA"},
"mssql": {"ibis_testing"},
"mysql": {"ibis_testing", "information_schema"},
"duckdb": {"information_schema", "main"},
"duckdb": {"memory"},
"snowflake": {"IBIS_TESTING"},
"trino": {"default"},
"trino": {"memory"},
"oracle": set(),
}
assert test_databases[alchemy_con.name] <= set(alchemy_con.list_databases())
Expand Down
26 changes: 20 additions & 6 deletions ibis/backends/trino/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import ibis.expr.datatypes as dt
import ibis.expr.types as ir
from ibis import util
from ibis.backends.base import CanListDatabases
from ibis.backends.base.sql.alchemy import AlchemyCanCreateSchema, BaseAlchemyBackend
from ibis.backends.base.sql.alchemy.datatypes import ArrayType
from ibis.backends.trino.compiler import TrinoSQLCompiler
Expand All @@ -29,22 +30,35 @@
import ibis.expr.schema as sch


class Backend(BaseAlchemyBackend, AlchemyCanCreateSchema):
class Backend(BaseAlchemyBackend, AlchemyCanCreateSchema, CanListDatabases):
name = "trino"
compiler = TrinoSQLCompiler
supports_create_or_replace = False
supports_temporary_tables = False

@cached_property
def version(self) -> str:
with self.begin() as con:
return con.execute(sa.select(sa.func.version())).scalar()
return self._scalar_query(sa.select(sa.func.version()))

@property
def current_database(self) -> str | None:
query = sa.select(sa.literal_column("current_catalog"))
def current_database(self) -> str:
return self._scalar_query(sa.select(sa.literal_column("current_catalog")))

def list_databases(self, like: str | None = None) -> list[str]:
s = sa.table(
"schemata",
sa.column("catalog_name", sa.VARCHAR()),
schema="information_schema",
)

query = sa.select(sa.distinct(s.c.catalog_name)).order_by(s.c.catalog_name)
with self.begin() as con:
return con.execute(query).scalar()
results = list(con.execute(query).scalars())
return self._filter_with_like(results, like=like)

@property
def current_schema(self) -> str:
return self._scalar_query(sa.select(sa.literal_column("current_schema")))

def do_connect(
self,
Expand Down