Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions pkg-py/src/commons/_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,13 @@ def list_tables(self) -> list[str]:
return list(sqlalchemy.inspect(self._engine).get_table_names())

def quote(self, table_id: TableId) -> str:
# quote_identifier() always quotes. quote() leaves an all-lowercase
# name bare, because SQLAlchemy reads it as case-insensitive, and a
# warehouse that folds to upper case then looks up a different name.
# Each component is quoted on its own: one call on "ANALYTICS.PUBLIC"
# names a schema with a dot in it.
preparer = self._engine.dialect.identifier_preparer
quoted = preparer.quote(table_id.table)
if table_id.schema is None:
return quoted
# quote_schema() is given one component at a time: handed
# "ANALYTICS.PUBLIC" it produces one identifier containing a dot.
outer = ".".join(
preparer.quote_schema(part)
for part in (table_id.catalog, table_id.schema)
if part is not None
)
return f"{outer}.{quoted}"
return ".".join(preparer.quote_identifier(part) for part in table_id.parts)

def columns(self, table_id: TableId) -> list[dict[str, Any]]:
inspector = sqlalchemy.inspect(self._engine)
Expand Down
14 changes: 12 additions & 2 deletions pkg-py/tests/test_data_source_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,23 @@ def test_the_engine_backend_quotes_each_component_separately() -> None:
assert quoted == '"ANALYTICS"."PUBLIC"."ORDERS"'


def test_the_engine_backend_still_quotes_a_two_part_name() -> None:
def test_the_engine_backend_quotes_a_lowercase_name_too() -> None:
backend = EngineBackend(sqlalchemy.create_engine("sqlite://"))
assert backend.quote(TableId(table="sales", schema="analytics")) == (
"analytics.sales"
'"analytics"."sales"'
)


def test_a_lowercase_snowflake_table_keeps_its_case() -> None:
# SQLAlchemy treats an all-lowercase name as case-insensitive and leaves
# it bare, which Snowflake folds to upper case: a table created as
# "survey" would be looked up as SURVEY.
pytest.importorskip("snowflake.sqlalchemy")
backend = EngineBackend(sqlalchemy.create_engine("snowflake://u@account/db"))
quoted = backend.quote(TableId(table="survey", schema="LIVE", catalog="COMMONS_CI"))
assert quoted == '"COMMONS_CI"."LIVE"."survey"'


def test_an_engine_backend_reports_columns_from_its_inspector(
engine: sqlalchemy.Engine,
) -> None:
Expand Down
Loading