gopgql
One annotated GraphQL SDL document → PostgreSQL 19 SQL/PGQ. Each
tab below is a complete, editable scenario: put a
schema and a query in on the left,
press Generate, and read the generated database
schema and the compiled query on the right. It runs entirely in your
browser as compiled Go (WebAssembly) — the real
sdl+generator+migrate+compiler
packages, no server and no JavaScript re-implementation. Nothing is
hardcoded: every output is generated from your input.
loading WebAssembly…
Nesting extends one MATCH chain rather than spawning a
second query, so a three-hop traversal is still a single
GRAPH_TABLE. Argument values travel as bind parameters
($1, …) and are never interpolated, and every pair of
vertex positions that could bind the same row is guarded with
<> — PostgreSQL does not enforce edge isomorphism.
SQL/PGQ has no variable-length paths, so gopgql refuses a selection
nested past MaxDepth instead of silently truncating the
pattern. The rejection is a typed
*compiler.DepthExceededError raised at compile time, so
no SQL exists to send. The ceiling is per-Compiler
configuration (compiler.WithMaxDepth), not a constant —
it defaults to 3. Raise it below to
compile this four-hop query, or lower it until a shorter one is
refused.
@column(name:) renames the physical column — and with it
the property the graph exposes, which is why the compiled query below
projects name while the GraphQL field is still
title. @column(type:) overrides the default
scalar mapping, @unique makes the database itself reject
a duplicate, and @index adds a secondary index with an
optional access method — bare, the name is derived from the table and
column.
Selecting two relationships at one level would need comma-separated
path patterns in a single MATCH — PG19 parses those but
will not execute them. gopgql splits instead: the chain up to the
branching level stays one GRAPH_TABLE, each branch
becomes its own, and the outer query LEFT JOINs them on
the projected ids. Guards that would have spanned the split move to
the ON clause, so a branch still cannot walk back to a
vertex an ancestor bound.
A GraphQL interface makes several tables one queryable position. An
interface carrying @node(label:) becomes a label its
implementors' tables all expose — (v0 IS actor); an
interface without it is matched by alternation over their own labels
— (v0 IS bot|person). Edit the query to
{ profiles { name } } to see the second form.
The initial 0001_init.sql goose migration generated from
a schema: the full CREATE DDL, with a
-- +goose Down section that is its exact inverse.
Revise that schema and gopgql folds the initial migration back into a
model, diffs it, and emits the delta — ALTER TABLE plus a
recreated property graph. The revision below is diffed against the
schema in the scenario above, so editing either one regenerates it.