diff --git a/scripts/check_schema_compatibility.py b/scripts/check_schema_compatibility.py index 898c65dff2c..5e98db3c7e2 100644 --- a/scripts/check_schema_compatibility.py +++ b/scripts/check_schema_compatibility.py @@ -15,10 +15,14 @@ Schema = Mapping[str, Mapping[str, str]] MODELS: Final = re.compile(r"^[ \t]*model\s+(\w+)\s*\{(.*?)^[ \t]*\}", re.MULTILINE | re.DOTALL) +def _is_field(part: list[str]) -> bool: + return len(part) >= 2 and not part[0].startswith(("@@", "//")) + + def parse_model_fields(schema: str) -> Schema: def fields(body: str) -> Mapping[str, str]: parts: Final = tuple(line.split() for line in body.splitlines()) - return MappingProxyType({part[0]: " ".join(part[1:]) for part in parts if len(part) >= 2}) + return MappingProxyType({part[0]: " ".join(part[1:]) for part in parts if _is_field(part)}) return MappingProxyType({match[1]: fields(match[2]) for match in MODELS.finditer(schema)}) diff --git a/tests/unit/proxy/test_db_schema_changes.py b/tests/unit/proxy/test_db_schema_changes.py index 3a5d0fb1a0a..49dfaec1f16 100644 --- a/tests/unit/proxy/test_db_schema_changes.py +++ b/tests/unit/proxy/test_db_schema_changes.py @@ -16,6 +16,28 @@ def test_parse_model_fields_retains_types_and_attributes() -> None: assert parse_model_fields(ORIGINAL) == {"Item": {"id": "String @id", "label": "String?"}} +def test_block_attributes_and_comments_are_not_fields() -> None: + schema: Final = ( + "model Item {\n id String @id\n label String?\n // free text\n" + ' @@unique([label, id], map: "item_label_id_key")\n @@index([label, id])\n @@index([id])\n}\n' + ) + assert parse_model_fields(schema) == {"Item": {"id": "String @id", "label": "String?"}} + + +def test_changing_a_composite_unique_key_is_not_a_field_change() -> None: + before: Final = "model Item {\n id String @id\n label String?\n @@unique([label, id])\n}\n" + after: Final = "model Item {\n id String @id\n label String?\n @@unique([label, id, tenant])\n}\n" + assert check_breaking_changes(parse_model_fields(before), parse_model_fields(after)) == () + + +def test_a_narrowed_column_is_still_caught_next_to_block_attributes() -> None: + before: Final = "model Item {\n id String @id\n label String?\n @@index([label, id])\n}\n" + after: Final = "model Item {\n id String @id\n label String\n @@index([label, id])\n}\n" + assert check_breaking_changes(parse_model_fields(before), parse_model_fields(after)) == ( + "Breaking: Field Item.label changed from optional to required", + ) + + def test_parse_model_fields_keeps_json_defaults_and_empty_models() -> None: schema: Final = 'model Item {\n data Json @default("{}")\n}\nmodel Empty {\n}\n' assert parse_model_fields(schema) == {"Item": {"data": 'Json @default("{}")'}, "Empty": {}}