Source code for operators.redshift.get_database
from airflow.exceptions import AirflowException
from typing import Any, Sequence
from operators.redshift.base_redshift import BaseRedshiftOperator
[docs]
class RedshiftGetDatabaseOperator(BaseRedshiftOperator):
"""
Operator to retrieve a list of Redshift database names matching a LIKE pattern.
Queries svv_redshift_databases via the Redshift Data API. Useful for
fanning out a CTAS across all databases matching a naming convention
(e.g. all '*_datamart' databases for ctasifier all_tenants mode with
tenant_discovery='databases_matching').
Unlike RedshiftGetTenantsOperator (which discovers schemas inside a
single database), this operator discovers DATABASES across the cluster.
:param redshift_workgroup: Redshift workgroup name (e.g. Config.REDSHIFT_WORKGROUP).
:type redshift_workgroup: str
:param database: Redshift database to issue the discovery query against
(just a routing target for the Data API; the result set covers all
databases visible to svv_redshift_databases).
:type database: str
:param database_pattern: SQL LIKE pattern for the database_name column,
e.g. '%_datamart'. Required.
:type database_pattern: str
:param region: AWS region for the Redshift Data API client.
:type region: str
:param additional_exclude_databases: Optional collection or string of
database names to exclude from the result set. Accepts list/tuple/set,
a JSON string (e.g. '["dev","staging"]'), a Python-literal string
(e.g. "['dev']"), or a single plain string (e.g. "dev"). This is
CaSe SeNsItIvE.
:type additional_exclude_databases: Any
"""
template_fields: Sequence[str] = (
*BaseRedshiftOperator.template_fields,
"database_pattern",
"additional_exclude_databases",
)
def __init__(
self,
redshift_workgroup: str,
database: str,
database_pattern: str,
region: str = "us-east-1",
additional_exclude_databases: Any = None,
*args,
**kwargs,
):
super().__init__(
redshift_workgroup=redshift_workgroup,
database=database,
region=region,
*args,
**kwargs,
)
self.database_pattern = database_pattern
# Store raw; coerce at execution time after templating has rendered.
self.additional_exclude_databases = additional_exclude_databases
[docs]
def execute(self, context):
"""Retrieve the list of database names from Redshift."""
if not self.database_pattern or not str(self.database_pattern).strip():
raise AirflowException(
"RedshiftGetDatabaseOperator requires database_pattern "
"(SQL LIKE pattern, e.g. '%_datamart')."
)
user_excludes = self._coerce_excludes(self.additional_exclude_databases)
# Redshift databases are user-named, so there's no baseline exclude
# set (unlike schemas, which include information_schema/aws_*/etc).
exclude_databases = sorted(set(user_excludes))
self.log.info(
"RedshiftGetDatabaseOperator pattern=%r excludes=%s (raw=%r)",
self.database_pattern,
exclude_databases,
self.additional_exclude_databases,
)
where_excl = ""
if exclude_databases:
excl_clause = ", ".join(
self.sql_literal(d) for d in exclude_databases
)
where_excl = f" AND database_name NOT IN ({excl_clause})\n"
sql = (
"SELECT database_name\n"
"FROM svv_redshift_databases\n"
f"WHERE database_name LIKE {self.sql_literal(self.database_pattern)}\n"
f"{where_excl}"
"ORDER BY database_name ASC;"
)
try:
rows = self._rs_fetch(sql)
databases = [r["database_name"] for r in rows]
except Exception as e:
self.log.exception(
"Error retrieving database names from Redshift "
"(workgroup=%s database=%s pattern=%r): %s",
self.redshift_workgroup,
self.database,
self.database_pattern,
e,
)
raise AirflowException(
"Failed to retrieve Redshift database names "
f"(workgroup='{self.redshift_workgroup}', "
f"database='{self.database}', "
f"pattern={self.database_pattern!r})."
) from e
if not databases:
raise AirflowException(
"No Redshift databases matched pattern "
f"{self.database_pattern!r} "
f"(workgroup='{self.redshift_workgroup}')."
)
self.log.info("Found %d database(s): %s", len(databases), databases)
return databases