"""ORM model for the materialised relationship graph. One row = one directed edge ``(from_type, from_id) → (to_type, to_id)`` of a given ``rel_type``. The unique constraint on the 5-tuple makes the sync handler's UPSERT trivial — bump ``last_seen_at`` on existing edges, INSERT new ones. Soft delete via ``deleted_at`` rather than hard delete so the timeline of "when did this ownership end" stays queryable. """ from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( JSON, DateTime, Float, Index, String, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.sql import func from app.core.database import Base from app.core.sql_types import GUID as Uuid class RelRelationship(Base): """One directed edge in the resource relationship graph.""" __tablename__ = "rel_relationships" __mapper_args__ = {"eager_defaults": True} id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) from_type: Mapped[str] = mapped_column(String(32), nullable=False) from_id: Mapped[str] = mapped_column(String(64), nullable=False) from_label: Mapped[str | None] = mapped_column(String(255)) to_type: Mapped[str] = mapped_column(String(32), nullable=False) to_id: Mapped[str] = mapped_column(String(64), nullable=False) to_label: Mapped[str | None] = mapped_column(String(255)) rel_type: Mapped[str] = mapped_column(String(48), nullable=False) attributes: Mapped[dict[str, Any] | None] = mapped_column(JSON) source: Mapped[str] = mapped_column( String(48), nullable=False, default="sync", ) weight: Mapped[float] = mapped_column( Float, nullable=False, default=1.0, server_default="1.0", ) first_seen_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) last_seen_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now(), ) __table_args__ = ( UniqueConstraint( "from_type", "from_id", "to_type", "to_id", "rel_type", name="uq_rel_edge", ), Index("idx_rel_from", "from_type", "from_id"), Index("idx_rel_to", "to_type", "to_id"), Index("idx_rel_type", "rel_type"), Index("idx_rel_last_seen", "last_seen_at"), ) def __repr__(self) -> str: return ( f" {self.to_type}:{self.to_id}>" )