Skip to content

symfonic.memory.backends.postgres_erase_binding

postgres_erase_binding

Bind the eraser to the exact graph the runtime routed from (CORE-199 S2).

The runtime decides that a forget needs the eraser on its own connection, against the memory_nodes its search_path resolves. The eraser logs in elsewhere, and the SQL it shares with the runtime names tables unqualified. Unless both reach the very same relation, the eraser would erase another schema's or another database's subtree and report the forget in progress or complete while the runtime's subtree survives.

So the runtime's signal carries a :class:GraphBinding of the relation it resolved: server, database, memory_nodes oid and schema. Each eraser transaction first pins its search_path to the eraser's configured schema, then refuses unless the memory_nodes it resolves is that relation. pg_catalog leads the pinned path, so no user relation shadows a catalog, and pg_temp trails it, so no temporary table shadows a graph table: every unqualified table in the eraser's SQL then names the bound schema's.

The server is the system identifier together with the postmaster start, as an epoch rather than text so the session time zone cannot change it. A clone restored from a physical backup shares identifier and oids, never both with the same start time.

GraphBinding dataclass

GraphBinding(server: str, database: str, nodes: int, schema: str)

The one memory_nodes relation a forget was routed against.

pin async

pin(conn: Any, schema: str) -> GraphBinding

Transaction-local: resolve every unqualified table in schema only.

Source code in src/symfonic/memory/backends/postgres_erase_binding.py
async def pin(conn: Any, schema: str) -> GraphBinding:
    """Transaction-local: resolve every unqualified table in ``schema`` only."""
    await conn.fetchval(_PIN, schema)
    binding = await resolve(conn)
    if binding is None or binding.schema != schema:
        raise MemoryContractError(UNBOUND)
    return binding

pin_to async

pin_to(conn: Any, schema: str, routed: GraphBinding) -> None

Pin to schema and refuse unless it is the relation routed names.

Source code in src/symfonic/memory/backends/postgres_erase_binding.py
async def pin_to(conn: Any, schema: str, routed: GraphBinding) -> None:
    """Pin to ``schema`` and refuse unless it is the relation ``routed`` names."""
    if await pin(conn, schema) != routed:
        raise MemoryContractError(MISBOUND)

resolve async

resolve(conn: Any) -> GraphBinding | None

The memory_nodes this session resolves now, or None.

Source code in src/symfonic/memory/backends/postgres_erase_binding.py
async def resolve(conn: Any) -> GraphBinding | None:
    """The ``memory_nodes`` this session resolves now, or None."""
    row = await conn.fetchval(_IDENTITY)
    return None if row is None else GraphBinding(*row)