Praneshrajan15's picture
Deploy DataForge playground API
13fe504 verified
Raw
History Blame Contribute Delete
5.57 kB
"""ConstraintIR models bridging schemas, dbt tests, SMT, and SQL proof queries."""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from dataforge.verifier.schema import Schema
ConstraintIRKind = Literal[
"column_type",
"not_null",
"domain_bound",
"regex",
"unique",
"accepted_values",
"functional_dependency",
"referential",
"dbt_generic_test",
]
class ConstraintIR(BaseModel):
"""Backend-neutral constraint representation used by patch plans."""
constraint_id: str = Field(min_length=1)
kind: ConstraintIRKind
columns: tuple[str, ...] = Field(default_factory=tuple)
expression: str | None = None
verifier: Literal["smt", "sql", "dbt"] = "smt"
repair_supported: bool = False
model_config = ConfigDict(strict=True, extra="forbid", frozen=True)
def constraint_ir_from_schema(schema: Schema | None) -> tuple[ConstraintIR, ...]:
"""Map DataForge's current schema model into the v1 ConstraintIR."""
if schema is None:
return ()
constraints: list[ConstraintIR] = []
for column, column_type in sorted(schema.columns.items()):
constraints.append(
ConstraintIR(
constraint_id=f"column_type::{column}",
kind="column_type",
columns=(column,),
expression=column_type,
verifier="smt",
repair_supported=True,
)
)
for bound in schema.domain_bounds:
parts: list[str] = []
if bound.min_value is not None:
operator = ">=" if bound.inclusive_min else ">"
parts.append(f"{bound.column} {operator} {bound.min_value}")
if bound.max_value is not None:
operator = "<=" if bound.inclusive_max else "<"
parts.append(f"{bound.column} {operator} {bound.max_value}")
constraints.append(
ConstraintIR(
constraint_id=f"domain_bound::{bound.column}",
kind="domain_bound",
columns=(bound.column,),
expression=" AND ".join(parts) if parts else None,
verifier="smt",
repair_supported=True,
)
)
for column in sorted(schema.not_null_columns):
constraints.append(
ConstraintIR(
constraint_id=f"not_null::{column}",
kind="not_null",
columns=(column,),
expression=f"{column} IS NOT NULL",
verifier="smt",
repair_supported=True,
)
)
for column in sorted(schema.unique_columns):
constraints.append(
ConstraintIR(
constraint_id=f"unique::{column}",
kind="unique",
columns=(column,),
expression=f"{column} must be unique",
verifier="smt",
repair_supported=True,
)
)
for column in sorted(schema.primary_key_columns):
constraints.append(
ConstraintIR(
constraint_id=f"primary_key::{column}",
kind="unique",
columns=(column,),
expression=f"{column} must be not null and unique",
verifier="smt",
repair_supported=False,
)
)
for accepted_rule in schema.accepted_values:
constraints.append(
ConstraintIR(
constraint_id=f"accepted_values::{accepted_rule.column}",
kind="accepted_values",
columns=(accepted_rule.column,),
expression=", ".join(accepted_rule.values),
verifier="smt",
repair_supported=True,
)
)
for regex_rule in schema.regex_constraints:
constraints.append(
ConstraintIR(
constraint_id=f"regex::{regex_rule.column}",
kind="regex",
columns=(regex_rule.column,),
expression=regex_rule.pattern,
verifier="smt",
repair_supported=True,
)
)
for relationship_rule in schema.relationships:
constraints.append(
ConstraintIR(
constraint_id=(
f"relationship::{relationship_rule.column}->"
f"{relationship_rule.reference}.{relationship_rule.reference_column}"
),
kind="referential",
columns=(relationship_rule.column,),
expression=(
f"{relationship_rule.column} references "
f"{relationship_rule.reference}({relationship_rule.reference_column})"
),
verifier="sql",
repair_supported=False,
)
)
for fd in schema.functional_dependencies:
determinant = "+".join(fd.determinant)
constraints.append(
ConstraintIR(
constraint_id=f"fd::{determinant}->{fd.dependent}",
kind="functional_dependency",
columns=(*fd.determinant, fd.dependent),
expression=f"{determinant} -> {fd.dependent}",
verifier="smt",
repair_supported=True,
)
)
return tuple(constraints)