diff --git a/core/dbt/parser/schemas.py b/core/dbt/parser/schemas.py index 8cbbbbaf194..303631e3618 100644 --- a/core/dbt/parser/schemas.py +++ b/core/dbt/parser/schemas.py @@ -992,8 +992,48 @@ def parse_patch(self, block: TargetBlock[NodeTarget], refs: ParserRef) -> None: self.patch_node_config(node, patch) node.patch(patch) + # TODO: We want to do all the actual patching either in the above node.patch() call + # or here, but it will require some thought to the details. For now the patching is + # awkwardly split. + self.patch_constraints(node, block.target.constraints) node.build_contract_checksum() + def patch_constraints(self, node, constraints): + contract_config = node.config.get("contract") + if isinstance(node, ModelNode) and contract_config.enforced is True: + self._validate_constraint_prerequisites(node) + + if any( + c for c in constraints if "type" not in c or not ConstraintType.is_valid(c["type"]) + ): + raise ParsingError( + f"Invalid constraint type on model {node.name}: " + f"Type must be one of {[ct.value for ct in ConstraintType]}" + ) + + node.constraints = [ModelLevelConstraint.from_dict(c) for c in constraints] + + def _validate_constraint_prerequisites(self, model_node: ModelNode): + errors = [] + if not model_node.columns: + errors.append( + "Constraints must be defined in a `yml` schema configuration file like `schema.yml`." + ) + + if model_node.config.materialized not in ["table", "view", "incremental"]: + errors.append( + f"Only table, view, and incremental materializations are supported for constraints, but found '{model_node.config.materialized}'" + ) + + if str(model_node.language) != "sql": + errors.append(f"Language Error: Expected 'sql' but found '{model_node.language}'") + + if errors: + raise ParsingError( + f"Constraint validation failed for: ({model_node.original_file_path})\n" + + "\n".join(errors) + ) + # TestablePatchParser = seeds, snapshots class TestablePatchParser(NodePatchParser[UnparsedNodeUpdate]): @@ -1131,43 +1171,6 @@ def parse_patch(self, block: TargetBlock[UnparsedModelUpdate], refs: ParserRef) self.manifest.rebuild_ref_lookup() self.manifest.rebuild_disabled_lookup() - def patch_constraints(self, node, constraints): - contract_config = node.config.get("contract") - assert isinstance(node, ModelNode) - if contract_config.enforced is True: - self._validate_constraint_prerequisites(node) - - if any( - c for c in constraints if "type" not in c or not ConstraintType.is_valid(c["type"]) - ): - raise ParsingError( - f"Invalid constraint type on model {node.name}: " - f"Type must be one of {[ct.value for ct in ConstraintType]}" - ) - - node.constraints = [ModelLevelConstraint.from_dict(c) for c in constraints] - - def _validate_constraint_prerequisites(self, model_node: ModelNode): - errors = [] - if not model_node.columns: - errors.append( - "Constraints must be defined in a `yml` schema configuration file like `schema.yml`." - ) - - if model_node.config.materialized not in ["table", "view", "incremental"]: - errors.append( - f"Only table, view, and incremental materializations are supported for constraints, but found '{model_node.config.materialized}'" - ) - - if str(model_node.language) != "sql": - errors.append(f"Language Error: Expected 'sql' but found '{model_node.language}'") - - if errors: - raise ParsingError( - f"Constraint validation failed for: ({model_node.original_file_path})\n" - + "\n".join(errors) - ) - def _target_type(self) -> Type[UnparsedModelUpdate]: return UnparsedModelUpdate