SQLStreams

the messaging platform that is just Postgres

You last visited on 9999-99-99 Show what's new since then

0631 — Every SQL literal names its schema

Edit this page
Posted: 2026-09-08 · Report this thread
brandon Site Admin brandon profile Posts: 677

Context. [0630] made a Postgres schema one Vulkan installation and rejected qualifying the SQL: “207 SQL literals would each have become a Sprintf, which contradicts the ## SQL rule that a literal is a constant raw string.” Both halves of that read too pessimistically. 114 of the 208 literals were already fmt.Sprintf — every per-topic table name is interpolated — so 94 changed shape, not 208. And the ## SQL rule’s concern is constant text per query, for statement caching; the schema is constant per pool, so the rendered text is constant per query exactly as before, and the raw-string shape rule is untouched.

What the record did not weigh is that its own search_path leaves every statement a fall-through path. The path is "<schema>, public", and public cannot be dropped — [0630] explains why: InTransaction runs the caller’s own SQL on Vulkan’s pool, so INSERT INTO orders must keep finding public.orders. So any Vulkan table missing from the installation’s schema but present in public resolves to public’s copy. Two installations in one database is the arrangement [0630] blessed, and topic ids are per-installation serials, so the names collide by construction. Reads: every 42P01-means-absence mapping — implicit RegisterSystem, the catalog reads behind VK0005 — finds a neighbour’s catalog instead of seeing absence. Writes: DROP TABLE IF EXISTS message_log_1 can name another installation’s table.

Decision. Every table a literal names is written %[1]s.<name>. The schema is Sprintf verb [1], filled from PostgresDatastore.Schema; tables follow as [2], [3]. Indexed verbs rather than positional because the schema repeats at every reference — 23 of them in deliveryconsumer.fanOut — and positional would pass it 23 times. No helper: plain fmt.Sprintf at the call site, which keeps go vet checking verbs against args, and as a side effect makes a stray % in future SQL a vet error rather than a runtime one. A CREATE INDEX qualifies only its ON target, since an index cannot carry a schema and lands in its table’s. A name that reaches Postgres as a bind parameter or inside a quoted string has no literal to qualify and is built qualified in Go at its call site — four to_regclass($1) reads and one ::regclass. Five literals live in free funcs with no receiver and take a trailing schema string. search_path keeps exactly one job: resolving the caller’s own statements inside InTransaction — superseded by [0632], which closes the migration-step gap below and then removes the path entirely.

Consequences. Supersedes [0630]‘s “no SQL changes” clause and its rejection of qualifying; the rest of [0630] stands. tools/conventions/sql_schema_test.go walks 298 references across 208 literals and requires exactly %[1]s.<name> — split on the dot, not a prefix test, because a prefix test passes %[1]s.%[2]s.%[3]s, which is how four double-qualified literals survived the first check. Qualifying broke table_ddl_test.go silently: it skipped any name holding a %, so the 13 shared tables fell out of the kind check and it passed while walking nothing; it now trims the qualifier first. The schema lab’s absence section stands a whole installation in public and asserts the reads are still absences — the fall-through demonstrated rather than argued. The sandbox mirror re-synced 45 templates and its interpolate fills [1] with PGlite’s own public. Left open here and closed by [0632]: migrate.Migration’s step funcs took (ctx, q, topicId) and could reach no schema, so a post-v1 step could not qualify its SQL — free to change only while both registries are empty. Rejected: a Fill/Sql helper on the datastore wrapping Sprintf — it hides fmt behind a house verb and leans on vet’s wrapper detection, which only holds while the format argument is forwarded unmodified; and a {schema} token filled at query build, which adds a second placeholder grammar beside Sprintf’s own.