symfonic.core.prompt.blocks.sources.database_sql¶
database_sql ¶
Append-only guards, scope keys and dialect rewriting for the block schema.
The executable half of
:mod:symfonic.core.prompt.blocks.sources.database_schema: that module
declares the table and the statements, this one holds the rules those
statements must satisfy and the small helpers a caller needs to use them
safely. Splitting them keeps the SQL surface readable as a surface, and
keeps these checks importable without pulling the statement list along.
Nothing here reads or writes a database. :func:assert_append_only is
run at import by the schema module against its own ALL_STATEMENTS, so
a statement added later is either append-only or the package fails to
import.
assert_append_only ¶
Raise unless every statement leaves existing revisions untouched.
Run against :data:ALL_STATEMENTS at import, so a mutating statement
added to this module fails the import that introduced it rather than
the first production write.
Source code in src/symfonic/core/prompt/blocks/sources/database_sql.py
make_revision_id ¶
Return a revision identifier unique within one block and scope.
Uniqueness rides on sequence, which the primary key already
enforces per (scope_path, block_id). The digest tail is carried
for legibility only -- it is what makes r7-9f2c... recognisable
in a log -- and must never be the sole identity: restoring an earlier
revision appends the same content, so the digest repeats by design.
Source code in src/symfonic/core/prompt/blocks/sources/database_sql.py
mutating_statements ¶
Return every statement that would change or remove an existing row.
DROP TABLE is not counted: dropping the table is the declared
reverse migration (:data:MIGRATION_DOWN), and it removes the schema
rather than editing a revision inside it.
That exemption is unqualified rather than scoped to
:data:MIGRATION_DOWN's own statement -- a DROP TABLE reached
from anywhere else in the package (a reset() helper, say) would
pass this check too. It is not scoped narrower because this checker
also runs, unparametrised, against every SQL-looking string literal in
the package's source (including partial fragments an f-string splits
across ast.Constant nodes, which never equal a complete
MIGRATION_DOWN statement); a membership test against
:data:MIGRATION_DOWN would silently stop matching those fragments
and reintroduce the exact gap ALTER TABLE was added above to
close. Narrowing this safely needs the fragment-scanning test to
cooperate, not just this function.
Source code in src/symfonic/core/prompt/blocks/sources/database_sql.py
qmark ¶
Translate $1-style placeholders into the qmark paramstyle.
The statements are written once, in the style asyncpg speaks. A
driver using qmark (sqlite3, and the schema's own conformance
tests) runs the same text through this rather than keeping a second,
silently divergent copy.
Positional rewriting is only sound when the placeholders appear in ascending order and each is used once, so that is checked rather than assumed.
Source code in src/symfonic/core/prompt/blocks/sources/database_sql.py
revision_key ¶
Return the (scope_path, block_id) values to bind into a statement.
Delegates to
:func:~symfonic.core.prompt.blocks.protocol.block_isolation_key so
the value stored in the scope_path column is exactly
:attr:TenantScope.scope_path -- the same string the block layer
keys its caches on. Deriving it here independently would be one more
place for the canonical representation to drift.