Skip to content

Commit b3a2a9d

Browse files
Niels-bclaude
andauthored
fix(redshift): detect existing DWH schema via svv_all_schemas (SCS-1193) (#2789)
The Diagnostics Warehouse existence check queried information_schema.schemata, which on Redshift only lists schemas the current user OWNS. A pre-provisioned DWH schema the service user merely has USAGE/CREATE on was therefore invisible, so Soda issued CREATE SCHEMA -- which needs CREATE-on-database and failed with 'permission denied for database' even though the schema already existed. Override the schemas metadata query on Redshift to use pg_catalog.svv_all_schemas (visible by access, not ownership), matching the existing svv_tables/svv_columns overrides. Base dialect behaviour is unchanged via a new build_schemas_metadata_from_clause() hook. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ddc39ec commit b3a2a9d

3 files changed

Lines changed: 51 additions & 3 deletions

File tree

soda-core/src/soda_core/common/sql_dialect.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,17 @@ def build_all_columns_metadata_query_str(
15761576
]
15771577
)
15781578

1579+
def build_schemas_metadata_from_clause(self, table_namespace: Optional[DataSourceNamespace] = None) -> FROM:
1580+
"""
1581+
FROM clause for the schemas metadata query. Defaults to information_schema.schemata.
1582+
1583+
Override in dialects whose schema listing must not come from information_schema.
1584+
(e.g. Redshift, where information_schema.schemata is visibility-scoped to the schema
1585+
OWNER and therefore misses schemas the current user only has USAGE/CREATE on.)
1586+
"""
1587+
information_schema_namespace_elements = self.information_schema_namespace_elements(table_namespace)
1588+
return FROM(self.table_schemata()).IN(information_schema_namespace_elements)
1589+
15791590
def build_schemas_metadata_query_str(
15801591
self, table_namespace: Optional[DataSourceNamespace] = None, filter_on_schema_name: Optional[str] = None
15811592
) -> str:
@@ -1587,11 +1598,9 @@ def build_schemas_metadata_query_str(
15871598
"""
15881599
database_name: str | None = table_namespace.get_database_for_metadata_query() if table_namespace else None
15891600

1590-
information_schema_namespace_elements = self.information_schema_namespace_elements(table_namespace)
1591-
15921601
select_elements = [
15931602
SELECT([self.column_schema_name()]),
1594-
FROM(self.table_schemata()).IN(information_schema_namespace_elements),
1603+
self.build_schemas_metadata_from_clause(table_namespace),
15951604
]
15961605

15971606
and_elements = [

soda-redshift/src/soda_redshift/common/data_sources/redshift_data_source.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,20 @@ def build_columns_metadata_from_clause(self, table_namespace: DataSourceNamespac
213213

214214
def _pg_catalog_schema(self) -> str:
215215
return "pg_catalog"
216+
217+
def table_schemata(self) -> str:
218+
# information_schema.schemata on Redshift (Postgres-8 lineage) lists only schemas the
219+
# current user OWNS, so a schema the DWH service user merely has USAGE/CREATE on is
220+
# invisible. Soda's existence check then returns false and it issues CREATE SCHEMA,
221+
# which needs CREATE-on-database and fails with "permission denied for database ..."
222+
# even though the schema already exists (SCS-1193). SVV_ALL_SCHEMAS lists schemas by
223+
# access, matching the svv_tables / svv_columns overrides above.
224+
return self.default_casify("svv_all_schemas")
225+
226+
def column_schemata_catalog_name(self) -> str:
227+
# SVV_ALL_SCHEMAS names the database column database_name (vs information_schema's catalog_name).
228+
return self.default_casify("database_name")
229+
230+
def build_schemas_metadata_from_clause(self, table_namespace: Optional[DataSourceNamespace] = None) -> FROM:
231+
# SVV_ALL_SCHEMAS is a system view in pg_catalog, mirroring the svv_columns FROM clause.
232+
return FROM(self.table_schemata()).IN(self._pg_catalog_schema())

soda-redshift/tests/unit/test_redshift_dialect.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from soda_core.common.metadata_types import DbSchemaDataSourceNamespace
12
from soda_core.common.sql_dialect import FROM, RANDOM, SELECT
23
from soda_redshift.common.data_sources.redshift_data_source import RedshiftSqlDialect
34

@@ -8,6 +9,27 @@ def test_random():
89
assert sql == 'SELECT RANDOM()\nFROM "a";'
910

1011

12+
def test_schema_existence_query_uses_svv_all_schemas_not_information_schema():
13+
# Regression for SCS-1193: information_schema.schemata on Redshift only lists schemas the
14+
# current user OWNS, so a pre-provisioned DWH schema the service user merely has USAGE on is
15+
# invisible -> Soda wrongly issues CREATE SCHEMA -> "permission denied for database". The
16+
# existence check must query SVV_ALL_SCHEMAS (access-visible), like svv_tables / svv_columns.
17+
sql_dialect: RedshiftSqlDialect = RedshiftSqlDialect()
18+
sql = sql_dialect.build_schemas_metadata_query_str(
19+
table_namespace=DbSchemaDataSourceNamespace(database="comm", schema="qe_gold"),
20+
filter_on_schema_name="qe_gold",
21+
)
22+
lowered = sql.lower()
23+
assert "svv_all_schemas" in lowered
24+
assert "information_schema" not in lowered
25+
# SVV_ALL_SCHEMAS exposes the database via database_name (not information_schema's catalog_name)
26+
assert "database_name" in lowered
27+
assert "catalog_name" not in lowered
28+
# still filters on the target database + schema so it answers "does THIS schema exist"
29+
assert "comm" in lowered
30+
assert "qe_gold" in lowered
31+
32+
1133
def test_max_sql_statement_length_respects_redshift_16mb_cap():
1234
# Redshift's documented statement cap is 16 MB; the dialect reserves
1335
# 1 MB for multi-byte characters (we count chars, the server counts bytes).

0 commit comments

Comments
 (0)