|
1 | 1 | """All generic rules.""" |
2 | 2 |
|
3 | 3 | from dbt_score import Model, RuleViolation, Severity, rule |
| 4 | +from dbt_score.rules.filters import is_table |
4 | 5 |
|
5 | 6 |
|
6 | 7 | @rule |
@@ -47,7 +48,65 @@ def sql_has_reasonable_number_of_lines( |
47 | 48 | def has_example_sql(model: Model) -> RuleViolation | None: |
48 | 49 | """The documentation of a model should have an example query.""" |
49 | 50 | if model.language == "sql": |
50 | | - if "```sql" not in model.description: |
| 51 | + if "```sql" not in (model.description or ""): |
51 | 52 | return RuleViolation( |
52 | 53 | "The model description does not include an example SQL query." |
53 | 54 | ) |
| 55 | + |
| 56 | + |
| 57 | +@rule(rule_filters={is_table()}) |
| 58 | +def single_pk_defined_at_column_level(model: Model) -> RuleViolation | None: |
| 59 | + """Single-column PK must be defined as a column constraint.""" |
| 60 | + for constraint in model.constraints: |
| 61 | + if constraint.type == "primary_key": |
| 62 | + if constraint.columns is not None and len(constraint.columns) == 1: |
| 63 | + return RuleViolation( |
| 64 | + f"Single-column PK {constraint.columns[0]} must be defined as a " |
| 65 | + f"column constraint." |
| 66 | + ) |
| 67 | + |
| 68 | + |
| 69 | +@rule(rule_filters={is_table()}) |
| 70 | +def single_column_uniqueness_at_column_level(model: Model) -> RuleViolation | None: |
| 71 | + """Single-column uniqueness test must be defined as a column test.""" |
| 72 | + for data_test in model.tests: |
| 73 | + if data_test.type == "unique_combination_of_columns": |
| 74 | + if len(data_test.kwargs.get("combination_of_columns", [])) == 1: |
| 75 | + return RuleViolation( |
| 76 | + "Single-column uniqueness test must be defined as a column test." |
| 77 | + ) |
| 78 | + |
| 79 | + |
| 80 | +@rule(rule_filters={is_table()}) |
| 81 | +def has_uniqueness_test(model: Model) -> RuleViolation | None: |
| 82 | + """Model has uniqueness test for primary key.""" |
| 83 | + # ruff: noqa: C901 [too-complex] |
| 84 | + |
| 85 | + # Single-column PK |
| 86 | + for column in model.columns: |
| 87 | + for column_constraint in column.constraints: |
| 88 | + if column_constraint.type == "primary_key": |
| 89 | + for data_test in column.tests: |
| 90 | + if data_test.type == "unique": |
| 91 | + return None |
| 92 | + return RuleViolation( |
| 93 | + f"No unique constraint defined on PK column {column.name}." |
| 94 | + ) |
| 95 | + |
| 96 | + # Composite PK |
| 97 | + pk_columns: list[str] = [] |
| 98 | + for model_constraint in model.constraints: |
| 99 | + if model_constraint.type == "primary_key": |
| 100 | + pk_columns = model_constraint.columns or [] |
| 101 | + break |
| 102 | + |
| 103 | + if not pk_columns: # No PK, no need for uniqueness test |
| 104 | + return None |
| 105 | + |
| 106 | + for data_test in model.tests: |
| 107 | + if data_test.type == "unique_combination_of_columns": |
| 108 | + if set(data_test.kwargs.get("combination_of_columns")) == set(pk_columns): # type: ignore |
| 109 | + return None |
| 110 | + return RuleViolation( |
| 111 | + f"No uniqueness test defined and matching PK {','.join(pk_columns)}." |
| 112 | + ) |
0 commit comments