dbt-labs / dbt-core

dbt enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.

Home Page:https://getdbt.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug] Renaming or removing a contracted model should raise a BreakingChange warning/error

jtcohen6 opened this issue · comments

Is this a new bug in dbt-core?

  • I believe this is a new bug in dbt-core
  • I have searched the existing issues, and I could not find an existing issue for this bug

Current Behavior

When a contracted model changes or disables its contract, dbt raises a warning (UnversionedBreakingChange) or error (ContractBreakingChangeError) within the same_contract check triggered by state:modified selection/comparison.

However, the same warning/error does not occur if the user simply deletes or renames a contracted model. I believe it should — until/unless unless that contracted model has passed a defined deprecation_date, at which point deletion is allowed.

Expected Behavior

If a contracted model is present in the comparison manifest, and missing in the current manifest, raise a warning (if unversioned) or error (if versioned) accordingly. Add a new type of "breaking change" for "renamed or removed."

Naïve implementation for illustrative purposes only:

class StateSelectorMethod(SelectorMethod):
    ...
    def check_for_deleted_contracted_models(self) -> None:
        old_contracted_nodes = set(
            k for k, v in self.previous_state.manifest.nodes.items() if v.config.contract.enforced
        )
        new_contracted_nodes = set(
            k for k, v in self.manifest.nodes.items() if v.config.contract.enforced
        )
        for unique_id in old_contracted_nodes - new_contracted_nodes:
            node = self.previous_state.manifest.nodes[unique_id]
            if (
                node.deprecation_date
                and node.deprecation_date < datetime.datetime.now().astimezone()
            ):
                # Passed its deprecation_date, so deletion is allowed
                continue
            if node.version is None:
                print("WARNING! Unversioned contracted model renamed or removed")
                # This should be warn_or_error(UnversionedBreakingChange), and we probably want a new "breaking_change" reason
            else:
                print("ERROR! Versioned contracted model renamed or removed")
                # This should raise ContractBreakingChangeError
    ...
    def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
        if self.previous_state is None or self.previous_state.manifest is None:
            raise DbtRuntimeError("Got a state selector method, but no comparison manifest")
        self.check_for_deleted_contracted_models()
        ...

Steps To Reproduce

  1. Create a model configured with contract: {enforced: true}
  2. Parse the project / run the model
  3. Move the manifest to a folder named state/
  4. dbt list -s state:modified --state

Relevant log output

No response

Environment

- OS: macOS
- Python: 3.10.11
- dbt: v1.8 / main

Which database adapter are you using with dbt?

No response

Additional Context

No response

@MichelleArk link an example of the current implementation for similar errors in the contracted model.