"""Config for the class-based sql2sf flow.""" from os import environ from random import shuffle from snowflake_etl.conf import config as cfg feed_name = 'sql2sf' DYNAMODB_STATUS_TABLE = environ.get( 'SQL2SF_STATUS_TABLE', 'sql2sf_table_sync_status') class SourcesConf: """A class to store attributes of the source tables and other configs.""" def __init__(self): """Create a SourceConf entity.""" self.conf = cfg.getconf('sql2sf_sources') def get_tables_to_sync(self): """Get tables which we want to sync. Returns: tables_to_sync (list): List of tuples: (source_db_host, source_schema, source_table, file_format). """ tables_to_sync = [] for db_host_name in self.conf['db_hosts']: for schema_name in self.conf['db_hosts'][db_host_name]['schemas']: for table_name in self.conf[ 'db_hosts'][db_host_name]['schemas'][schema_name][ 'tables']: table_props = self.conf[ 'db_hosts'][db_host_name]['schemas'][schema_name][ 'tables'][table_name] file_format = table_props.get('file_format') if not table_props.get('exclude_from_daily_sync'): tables_to_sync.append( (db_host_name, schema_name, table_name, file_format)) # to make tables from different dbs go to sync faster shuffle(tables_to_sync) return tables_to_sync def get_table_props_by_name(self, source_table): """Get props of the source table by name. This is required for sync_big_mysql_table script. Args: source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: tuple: The props of the table. """ for db_host_name in self.conf['db_hosts']: for schema_name in self.conf['db_hosts'][db_host_name]['schemas']: for table_name in self.conf[ 'db_hosts'][db_host_name]['schemas'][schema_name][ 'tables']: if table_name == source_table: table_props = self.conf[ 'db_hosts'][db_host_name]['schemas'][schema_name][ 'tables'][table_name] file_format = table_props.get('file_format') return ( db_host_name, schema_name, table_name, file_format) raise KeyError('Table {} not found in sql2sf.yml'.format(table_name)) def get_db_type(self, source_db_host): """Get db type. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. Raises: KeyError: If host isn't supported. Returns: str: Db type (e.g, mysql, or redshift). """ try: return self.conf[ 'db_hosts'][source_db_host]['db_type'] except KeyError: raise KeyError( 'Db host {host} is not supported, please check ' 'sql2sf_sources.yml'.format( host=source_db_host)) from None def get_db_credentials(self, source_db_host, source_schema): """Get db credentials. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. Raises: KeyError: If schema or host isn't supported. Returns: tuple: Host, port, user and password for a db host. """ try: db_config = self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'db_config'] except KeyError: raise KeyError( 'Schema {schema} or db host {host} is not supported, please ' 'check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host)) from None return ( db_config.get('host'), int(db_config.get('port')), db_config.get('user'), db_config.get('password')) def get_table_params(self, source_db_host, source_schema, source_table): """Get params of the source table (how to unload it). Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: dict: The params of the table. """ try: return self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table] except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_query(self, db_type, query_type): """Get unload query. Args: db_type (str): The type of the db. E.g. 'redshift', or 'mysql'. query_type (str): The type of the query. E.g. 'snapshot'. Raises: KeyError: If db_type or query_type isn't supported. Returns: str: An SQL query. """ try: return self.conf['queries'][db_type][query_type] except KeyError: raise KeyError( 'No {query_type} query type exists for {db_type}'.format( query_type=query_type, db_type=db_type)) from None def is_unload_in_chunks(self, source_db_host, source_schema, source_table): """Check if we need to unload table in chunks. This is useful for big MySQL (and other non-DW) tables. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: bool: True if we need to unload table in chunks, False otherwise. """ try: return bool(self.conf[ 'db_hosts'][source_db_host]['schemas'][ source_schema][ 'tables'][source_table].get('unload_in_chunks')) except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_chunk_size(self, source_db_host, source_schema, source_table): """Check if we need to unload table in chunks. This is useful for big MySQL (and other non-DW) tables. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: int: Chunk size (it's not a real number of rows in a future chunk, but alleged number of rows, which will be calculated in generator based on MAX(primary_key), and MIN(primary_key), and chunk size as range. If primary key isn't sparsed, then chunk_size equals to row count in chunk). """ try: return self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table].get( 'chunk_size') except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_primary_key(self, source_db_host, source_schema, source_table): """Get a primary key name for unloading non-DW tables in chunks. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: str: The name of the primary key. """ try: return self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table].get('primary_key') except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_s3_path_data(self, db_type): """Get S3 path template for unloading data. Args: db_type (str): The type of the db. E.g. 'redshift', or 'mysql'. Raises: KeyError: If db_type isn't supported. Returns: str: A template of a S3 path to parametrize later. """ try: return self.conf['s3_destinations'][db_type]['data'] except KeyError: raise KeyError('No S3 path specified for {db_type}'.format( db_type=db_type)) from None def get_s3_path_schema(self, db_type): """Get S3 path template to place extracted JSON schema of a table. Args: db_type (str): The type of the db. E.g. 'redshift', or 'mysql'. Raises: KeyError: If db_type isn't supported. Returns: str: A template of a S3 path to parametrize later. """ try: return self.conf['s3_destinations'][db_type]['schema'] except KeyError: raise KeyError('No S3 path specified for {db_type}'.format( db_type=db_type)) from None def is_table_supports_incremental_sync( self, source_db_host, source_schema, source_table): """Check if table supports incremental syncs. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: bool: True if yes, False otherwise. """ try: return bool(self.conf[ 'db_hosts'][source_db_host]['schemas'][ source_schema][ 'tables'][source_table].get( 'sync_incremental_support')) except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_sync_sanity_threshold( self, source_db_host, source_schema, source_table): """Get row count threshold for syncing tables. If 0, then there is a strict row count check. If sanity_threshold not set in sql2sf_sources, returns 100000. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: int: Value from config, or default one (100000). """ try: threshold = self.conf[ 'db_hosts'][source_db_host]['schemas'][ source_schema][ 'tables'][source_table].get( 'sanity_threshold') if threshold is None: return 100000 else: return threshold except KeyError: raise KeyError( 'Schema {schema}, or db host {host}, or table {table} is not ' 'supported, please check sql2sf_sources.yml'.format( schema=source_schema, host=source_db_host, table=source_table)) from None def get_stripspecialchars_func_name( self, source_db_host, source_schema, source_table): """Get UDF name to wrap column name in SQL query. Some tables are OK, so we don't have to wrap with stripSpecialChars or stripSpecialCharsLong. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Returns: str: Name of the function, or empty string. """ wrap_to_stripspecialchars = self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table].get('wrap_to_stripspecialchars') wrap_to_stripspecialchars_long = self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table].get('wrap_to_stripspecialchars_long') if wrap_to_stripspecialchars_long: return 'stripSpecialCharsLong' elif wrap_to_stripspecialchars: return 'stripSpecialChars' else: return '' def is_schema_supported(self, source_db_host, source_schema): """Check if schema supported by the sql2sf workflow. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. Raises: KeyError: If schema, or host, or table isn't supported. Returns: bool: True if yes, False otherwise. """ try: self.conf['db_hosts'][source_db_host]['schemas'][source_schema] except KeyError: return False else: return True def is_table_supported(self, source_db_host, source_schema, source_table): """Check if table supported by the sql2sf workflow. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Raises: KeyError: If schema, or host, or table isn't supported. Returns: bool: True if yes, False otherwise. """ try: self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema][ 'tables'][source_table] except KeyError: return False else: return True def is_table_transient(self, source_db_host, source_schema, source_table): """Check if we need to create stg and dest tables as transient. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Returns: str: Table param to format a SQL statement. '' or 'TRANSIENT'. Raises: ValueError: If table wasn't found in the sql2sf_sources.yml within a specified schema. """ table_params = self.conf[ 'db_hosts'][source_db_host]['schemas'][source_schema]['tables'][ source_table] if table_params: if table_params.get('transient'): return 'TRANSIENT' else: return '' else: raise KeyError( 'No table {table} in {schema} schema, please check ' 'sources.yml'.format( table=source_table, schema=source_schema)) from None def is_schema_validation_needed( self, source_db_host, source_schema, source_table): """Check if we want to compare source and destination schemas. If true abort in the case of mismatch otherwise just report. Args: source_db_host (str): The name of host under db_hosts key in sql2sf_sources.yml. E.g. 'reportsar02'. source_schema (str): The name of db schema. E.g. 'art_relations'. source_table (str): The name of source table. Returns: bool: True if needed False otherwise. Default is True. """ try: is_needed = self.conf[ 'db_hosts'][source_db_host]['schemas'][ source_schema][ 'tables'][source_table]['validate_schema'] return bool(is_needed) except KeyError: return True