|
1 | 1 | from copy import deepcopy |
2 | 2 | from dataclasses import dataclass |
3 | | -from typing import Mapping, Any, Optional, List, Union, Dict, FrozenSet, Tuple, TYPE_CHECKING |
| 3 | +from typing import Mapping, Any, Optional, List, Union, Dict, FrozenSet, Tuple |
4 | 4 |
|
5 | 5 | from dbt.adapters.base.impl import AdapterConfig, ConstraintSupport |
6 | 6 | from dbt.adapters.base.meta import available |
|
22 | 22 | CatalogTable, |
23 | 23 | ColumnMetadata, |
24 | 24 | ) |
| 25 | +from dbt_common.behavior_flags import BehaviorFlag |
25 | 26 | from dbt_common.events.functions import fire_event |
26 | 27 | from dbt_common.exceptions import CompilationError, DbtDatabaseError, DbtRuntimeError |
27 | 28 | from dbt_common.utils import filter_null_values |
|
38 | 39 | from dbt.adapters.snowflake import SnowflakeConnectionManager |
39 | 40 | from dbt.adapters.snowflake import SnowflakeRelation |
40 | 41 |
|
41 | | -if TYPE_CHECKING: |
42 | | - import agate |
| 42 | +import agate |
43 | 43 |
|
44 | 44 | SHOW_OBJECT_METADATA_MACRO_NAME = "snowflake__show_object_metadata" |
45 | 45 |
|
| 46 | +SNOWFLAKE_DEFAULT_TRANSIENT_DYNAMIC_TABLES = BehaviorFlag( |
| 47 | + name="snowflake_default_transient_dynamic_tables", |
| 48 | + default=False, |
| 49 | + description=( |
| 50 | + "When enabled, dynamic tables default to transient (matching regular table behavior). " |
| 51 | + "This is a breaking change from previous behavior where dynamic tables were non-transient." |
| 52 | + ), |
| 53 | +) |
| 54 | + |
46 | 55 |
|
47 | 56 | @dataclass |
48 | 57 | class SnowflakeConfig(AdapterConfig): |
@@ -99,6 +108,10 @@ class SnowflakeAdapter(SQLAdapter): |
99 | 108 | } |
100 | 109 | ) |
101 | 110 |
|
| 111 | + @property |
| 112 | + def _behavior_flags(self) -> list[BehaviorFlag]: |
| 113 | + return [SNOWFLAKE_DEFAULT_TRANSIENT_DYNAMIC_TABLES] |
| 114 | + |
102 | 115 | def __init__(self, config, mp_context) -> None: |
103 | 116 | super().__init__(config, mp_context) |
104 | 117 | self.add_catalog_integration(constants.DEFAULT_INFO_SCHEMA_CATALOG) |
@@ -512,7 +525,9 @@ def build_catalog_relation(self, model: RelationConfig) -> Optional[CatalogRelat |
512 | 525 | return None |
513 | 526 |
|
514 | 527 | @available |
515 | | - def describe_dynamic_table(self, relation: SnowflakeRelation) -> Dict[str, Any]: |
| 528 | + def describe_dynamic_table( |
| 529 | + self, relation: SnowflakeRelation, include_transient: bool = False |
| 530 | + ) -> Dict[str, Any]: |
516 | 531 | """ |
517 | 532 | Get all relevant metadata about a dynamic table to return as a dict to Agate Table row |
518 | 533 |
|
@@ -548,7 +563,35 @@ def describe_dynamic_table(self, relation: SnowflakeRelation) -> Dict[str, Any]: |
548 | 563 | if "initialization_warehouse" in available_columns: |
549 | 564 | base_columns.insert(base_columns.index("warehouse") + 1, "initialization_warehouse") |
550 | 565 |
|
551 | | - return {"dynamic_table": dt_table.select(base_columns)} |
| 566 | + selected = dt_table.select(base_columns) |
| 567 | + |
| 568 | + if include_transient: |
| 569 | + is_transient = self._query_dynamic_table_transient_status(relation) |
| 570 | + # choosing a future proof column name |
| 571 | + selected = selected.compute( |
| 572 | + [("transient", agate.Formula(agate.Boolean(), lambda row: is_transient))] |
| 573 | + ) |
| 574 | + |
| 575 | + return {"dynamic_table": selected} |
| 576 | + |
| 577 | + def _query_dynamic_table_transient_status(self, relation: SnowflakeRelation) -> bool: |
| 578 | + """ |
| 579 | + Query SHOW TABLES to determine if a dynamic table is transient. |
| 580 | +
|
| 581 | + SHOW DYNAMIC TABLES does not expose transient status, so we fall back to |
| 582 | + SHOW TABLES where the "kind" column contains "TRANSIENT" for transient tables. |
| 583 | + """ |
| 584 | + quoting = relation.quote_policy |
| 585 | + schema = f'"{relation.schema}"' if quoting.schema else relation.schema |
| 586 | + database = f'"{relation.database}"' if quoting.database else relation.database |
| 587 | + show_tables_sql = f"show tables like '{relation.identifier}' in schema {database}.{schema}" |
| 588 | + _, tables_table = self.execute(show_tables_sql, fetch=True) |
| 589 | + if len(tables_table.rows) > 0: |
| 590 | + tables_table = tables_table.rename( |
| 591 | + column_names=[name.lower() for name in tables_table.column_names] |
| 592 | + ) |
| 593 | + return tables_table.rows[0].get("kind") == "TRANSIENT" |
| 594 | + return False |
552 | 595 |
|
553 | 596 | def expand_column_types(self, goal, current): |
554 | 597 | reference_columns = {c.name: c for c in self.get_columns_in_relation(goal)} |
|
0 commit comments