diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c16aa48..4d5d0a96f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Validate relation identifier length at creation time and raise a clear error when it exceeds Databricks' 255-character limit ([#1309](https://github.com/databricks/dbt-databricks/issues/1309)) - Fix spurious `MicrobatchConcurrency` behavior-change warning firing on every run regardless of whether the project contained microbatch models ([#1406](https://github.com/databricks/dbt-databricks/issues/1406)) - Fix DBR capability cache being permanently poisoned by a transient version-query failure ([#1398](https://github.com/databricks/dbt-databricks/issues/1398)) +- Fix spurious `use_managed_iceberg` behavior-change warning firing on every run for projects that don't use Iceberg ([#1266](https://github.com/databricks/dbt-databricks/issues/1266)) ## dbt-databricks 1.11.7 (Apr 17, 2026) diff --git a/dbt/adapters/databricks/impl.py b/dbt/adapters/databricks/impl.py index 4d6964495..c9f39a203 100644 --- a/dbt/adapters/databricks/impl.py +++ b/dbt/adapters/databricks/impl.py @@ -248,7 +248,9 @@ def __init__(self, config: Any, mp_context: SpawnContext) -> None: self.add_catalog_integration(constants.DEFAULT_HIVE_METASTORE_CATALOG) # Store the use_managed_iceberg flag in GlobalState for the session - GlobalState.set_use_managed_iceberg(bool(self.behavior.use_managed_iceberg)) + GlobalState.set_use_managed_iceberg( + self.get_behavior_flag_no_warn(USE_MANAGED_ICEBERG["name"]) + ) @property def _behavior_flags(self) -> list[BehaviorFlag]: @@ -262,8 +264,7 @@ def _behavior_flags(self) -> list[BehaviorFlag]: def supports(self, capability: Capability) -> bool: if capability == Capability.MicrobatchConcurrency: - # `.no_warn` avoids a BehaviorChangeEvent on dbt-core's per-parse probe. - return self.behavior.use_concurrent_microbatch.no_warn + return self.get_behavior_flag_no_warn(USE_CONCURRENT_MICROBATCH["name"]) return super().supports(capability) def quote(self, identifier): # type: ignore[override,no-untyped-def] @@ -281,7 +282,7 @@ def is_uniform(self, config: BaseConfig) -> bool: ", 'table', or 'snapshot'." ) if ( - self.behavior.use_managed_iceberg + self.get_behavior_flag_no_warn(USE_MANAGED_ICEBERG["name"]) and catalog_relation.catalog_type != constants.UNITY_CATALOG_TYPE ): raise DbtConfigError( @@ -290,7 +291,7 @@ def is_uniform(self, config: BaseConfig) -> bool: ) # UniForm refers to Delta tables with Iceberg compatibility. # Native managed Iceberg tables don't need Delta properties. - return not self.behavior.use_managed_iceberg + return not self.get_behavior_flag_no_warn(USE_MANAGED_ICEBERG["name"]) else: return False @@ -770,6 +771,16 @@ def _get_columns_for_catalog( # type: ignore[override] as_dict["column_type"] = as_dict.pop("dtype") yield as_dict + def get_behavior_flag_no_warn(self, behavior_flag_name: str) -> bool: + """Get the value of a behavior flag without triggering a warning. + + dbt logs a warning the first time a behavior flag with a False value is accessed. Use + this method to access the value of a behavior flag without triggering a warning. + """ + # As intended - This method will error out if the behavior flag is missing. + behavior_flag = getattr(self.behavior, behavior_flag_name) + return behavior_flag.no_warn + def add_query( self, sql: str, diff --git a/dbt/include/databricks/macros/relations/file_format.sql b/dbt/include/databricks/macros/relations/file_format.sql index 4f938e6c7..bbb4abfae 100644 --- a/dbt/include/databricks/macros/relations/file_format.sql +++ b/dbt/include/databricks/macros/relations/file_format.sql @@ -13,7 +13,7 @@ {% endif %} {#-- Use managed Iceberg if behavior flag is enabled and table_format is iceberg --#} - {% if adapter.behavior.use_managed_iceberg and table_format == 'iceberg' %} + {% if table_format == 'iceberg' and adapter.behavior.use_managed_iceberg %} using iceberg {% else %} using {{ file_format }} diff --git a/tests/unit/macros/relations/test_table_macros.py b/tests/unit/macros/relations/test_table_macros.py index 9b3730962..fefde871d 100644 --- a/tests/unit/macros/relations/test_table_macros.py +++ b/tests/unit/macros/relations/test_table_macros.py @@ -339,3 +339,39 @@ def test_macros_create_table_as_uniform_iceberg(self, config, template_bundle): "'delta.universalFormat.enabledFormats' = 'iceberg') as select 1" ) assert sql == expected + + +class TestFileFormatClause(MacroTestBase): + """Regression tests for `file_format_clause` short-circuit behavior. + + For non-Iceberg models, the macro must not read `adapter.behavior.use_managed_iceberg`, + since that access fires a BehaviorChangeEvent deprecation warning on every run. + """ + + @pytest.fixture(scope="class") + def template_name(self) -> str: + return "file_format.sql" + + @pytest.fixture(scope="class") + def macro_folders_to_load(self) -> list: + return ["macros/relations", "macros"] + + def test_file_format_clause_does_not_access_flag_for_non_iceberg(self, template_bundle): + """Non-Iceberg relations must short-circuit before reading the behavior flag.""" + + class ErrorOnUseManagedIcebergAccess: + @property + def use_managed_iceberg(self): + raise AssertionError( + "use_managed_iceberg must not be accessed when table_format != 'iceberg'" + ) + + template_bundle.context["adapter"].behavior = ErrorOnUseManagedIcebergAccess() + catalog_relation = unity_relation( + table_format=constants.DEFAULT_TABLE_FORMAT, + file_format=constants.DELTA_FILE_FORMAT, + ) + + sql = self.run_macro(template_bundle.template, "file_format_clause", catalog_relation) + + assert sql == "using delta" diff --git a/tests/unit/test_adapter.py b/tests/unit/test_adapter.py index 23ce774d8..f028e486e 100644 --- a/tests/unit/test_adapter.py +++ b/tests/unit/test_adapter.py @@ -1282,7 +1282,7 @@ def test_is_uniform_with_managed_iceberg_returns_false( Native managed Iceberg tables don't use UniForm (Delta with Iceberg compatibility), so they shouldn't get Delta table properties added.""" - adapter.behavior.use_managed_iceberg = True + adapter.behavior.use_managed_iceberg.setting = True adapter.build_catalog_relation = Mock( return_value=unity_catalog_relation_managed_iceberg_relation ) @@ -1295,7 +1295,7 @@ def test_is_uniform_with_uniform_iceberg_returns_true( self, adapter, mock_config, unity_catalog_relation ): """Test that is_uniform returns True for UniForm Iceberg tables""" - adapter.behavior.use_managed_iceberg = False # Default + adapter.behavior.use_managed_iceberg.setting = False # Default adapter.build_catalog_relation = Mock(return_value=unity_catalog_relation) adapter.has_capability = Mock(return_value=True) # Has iceberg capability @@ -1318,7 +1318,7 @@ def test_is_uniform_with_managed_iceberg_hive_metastore_error( self, adapter, mock_config, hive_catalog_relation ): """Test that is_uniform raises error for managed Iceberg with Hive Metastore""" - adapter.behavior.use_managed_iceberg = True + adapter.behavior.use_managed_iceberg.setting = True adapter.build_catalog_relation = Mock(return_value=hive_catalog_relation) adapter.has_capability = Mock(return_value=True) # Has iceberg capability @@ -1331,7 +1331,7 @@ def test_is_uniform_with_old_dbr_version_error( self, adapter, mock_config, unity_catalog_relation ): """Test that is_uniform raises error for insufficient DBR version""" - adapter.behavior.use_managed_iceberg = False + adapter.behavior.use_managed_iceberg.setting = False adapter.build_catalog_relation = Mock(return_value=unity_catalog_relation) adapter.has_capability = Mock(return_value=False) # No ICEBERG capability @@ -1342,7 +1342,7 @@ def test_is_uniform_with_invalid_materialization_error( self, adapter, mock_config, unity_catalog_relation ): """Test that is_uniform raises error for invalid materialization""" - adapter.behavior.use_managed_iceberg = False + adapter.behavior.use_managed_iceberg.setting = False adapter.build_catalog_relation = Mock(return_value=unity_catalog_relation) adapter.has_capability = Mock(return_value=True) # Has iceberg capability mock_config.get.side_effect = lambda key: "view" if key == "materialized" else None