Checklist of PostgreSQL and GraphQL features pdbq supports. Checked = implemented and tested today; unchecked = planned or explicitly deferred (tier noted where one applies: v1.x = next minor releases, v2 = major-version track, stretch = nice-to-have). Use this as the reference when adding features: tick the box in the same PR that ships it.
- Tables (including partitioned tables)
- Views (read-only)
- Materialized views (read-only)
- Columns: type, nullability, defaults, identity/generated detection
- Primary keys
- Unique constraints
- Foreign keys (including multi-column)
- Indexes (name, columns, uniqueness, method) — drives the filter policy
- Enums
- Functions (args, return type, set-returning, volatility)
- Comments on tables, columns, enums, functions (exposed as GraphQL descriptions), and constraints (smart comments)
- RLS-enabled status per table
- Granted privileges per table (SELECT/INSERT/UPDATE/DELETE gate generation)
- Schema allowlist (
schema.schemas) - Composite types read into the catalog
- Composite-type columns mapped to GraphQL object types (matching
<Type>Inputon mutations; composite arrays included) - Domains surfaced as named scalars (currently resolved to base type) (v1.x)
- Function argument default values / variadic args (v1.x)
- Functions with
OUT/INOUTparameters or anonymous record returns (v1.x)
-
int2/int4→Int -
int8→BigInt(string-serialized, no precision loss) -
float4/float8→Float -
numeric/money→BigFloat(string-serialized) -
bool→Boolean -
text/varchar/char/citext/name→String -
uuid→UUID -
json/jsonb→JSON -
timestamp/timestamptz→Datetime -
date→Date,time/timetz→Time -
bytea→String(base64 output) - Arrays of any mapped type → GraphQL lists
- Enums → GraphQL enum types (labels mapped to spec-valid names, round-tripped on input and output)
-
inet/cidr/macaddr,interval, ranges,ltree,tsvector→Stringfallback -
intervalas structured scalar (v1.x) - Range types with structured bounds (v1.x; range filter operators tracked under Filtering & ordering)
-
hstore(v1.x) -
ltreeoperators (v1.x) - Full-text search:
tsvector/tsqueryfilter ops (v1.x) - PostGIS
geometry/geography→GeoJSONscalar (GeoJSON out; GeoJSON or WKT/EWKT in), with spatial filters, derived accessors, projection arguments and distance ordering — see PostGIS
- Relay connection per table (
allUsers):nodes,edges { cursor node },totalCount,pageInfo, withfirst/last/offset/before/after - Keyset (index-backed) cursors — cursor = nodeId (
base64(["Type", pk...])); PK-less tables fall back to offset cursors - Node interface / global object identification (
nodeId: ID!, Relaynode(nodeId:)) - Single-row lookup by primary key (
userById) - Single-row lookup per unique constraint (
userByEmail) - Forward relations (FK → parent object field)
- Backward relations (FK → child connection field with full connection args)
- Arbitrarily nested relation selections, compiled to one SQL statement (lateral joins, no N+1)
- Field aliases and
__typename - Named + inline fragments
- GraphQL variables with coercion and default values
- Multiple root fields per operation
- Depth limit and cost limit per operation
- Aggregate fields (count/sum/avg on lists) — stretch
- GraphQL subscriptions / live queries — non-goal for v1 (hook surfaces designed with this in mind; ship later)
-
<Type>Filterinput per table withand/or/notcombinators, arbitrarily nested - Scalar operators:
equalTo notEqualTo in notIn isNull lessThan lessThanOrEqualTo greaterThan greaterThanOrEqualTo - Text operators:
like likeInsensitive startsWith endsWith(operands escaped) - Array operators:
contains containedBy overlaps -
jsonboperators:contains containedBy containsKey pathExists pathMatch - Enum filtering with GraphQL enum values
- Indexed-only policy (default): only indexed/PK/unique columns filterable and orderable
- Per-table column allowlist override (
filters.allow_columns) - Global policy switch (
filters.indexed_only: false) -
orderByenums (EMAIL_ASC/EMAIL_DESC), multi-column, PK tiebreaker always appended - Filters/ordering on backward relation fields, not just root lists
-
jsonbpath operators (@?/@@viapathExists/pathMatch) - Range operators (
&&,@>element,<<,>>) (v1.x, alongside structured range types) - Filtering across relations (
posts: {some: {title: {equalTo: ...}}}) — built-inadvanced-filtersplugin: forward FK takes the parent's filter, reverse FK a{some|none|every}wrapper, compiled to EXISTS subqueries - Filtering on relation existence (
postsExist: true,authorExists: false) — Boolean variant per relation, compiled to a bare(NOT) EXISTS - Filtering/ordering on computed columns — built-in
advanced-filtersplugin (single-row-argument stable functions; not index-backed) -
distinctOn(column-enum arg on connections →SELECT DISTINCT ON; distinct-awaretotalCount; first/offset pagination only)
-
create<Type>(input:)per insertable table -
update<Type>ByPk(patch:)per updatable table with PK -
delete<Type>ByPkper deletable table with PK - Payload types with the mutated row selectable (relations included, compiled against the DML CTE)
- Generated/identity columns excluded from inputs; defaulted columns optional
- Privilege-gated generation (no INSERT grant → no create mutation)
- Not-found update/delete → GraphQL error, transaction rolled back
- Nested mutations via built-in plugin:
create/connectof FK parents, nestedcreateof children, multi-CTE single statement, bounded depth, forced transaction - Update/delete by unique constraints (
updateUserByEmail,deleteUserByEmail, mirroring the lookup surface) - Upsert (
ON CONFLICT) —upsert<Type>By<Unique>(input:)per non-generated key target; provided non-key columns update fromEXCLUDED - Bulk mutations (
createUsers(input: [...]),updateUsers(filter:, patch:),deleteUsers(filter:); payload = mutated rows +affectedCount) -
clientMutationIdpassthrough (Relay classic) - Nested
connecton reverse relations and nested update/delete/disconnect (v1.x)
- Stable/immutable functions → Query fields
- Volatile functions → Mutation fields
- Scalar arguments mapped from GraphQL args (named args only)
- Scalar returns (via
to_jsonb) -
SETOF <table>returns → list of the table's object type with full selection support -
SETOF <scalar>returns → scalar lists - Single-row table returns with selection support
-
voidreturns →Boolean - EXECUTE-privilege gating
- Computed columns (stable/immutable functions whose first argument is a row type → fields on that type; extra scalar args become field args; set-returning/volatile deferred)
- Set-returning computed columns (
SETOF <scalar>/SETOF <table>row-type functions → list fields with full selection support) - Functions with table-valued arguments or polymorphic types (v1.x)
- Custom mutations returning payload types with relations (v1.x)
- On by default; per-operation
SET LOCAL ROLE(never plainSETon pooled connections) - Claims via transaction-scoped
set_config('pdbq.claims.*', ...) - JWT claim source (HS256/384/512, issuer/audience checks)
- Trusted-header claim source for behind-gateway deployments
- Anonymous role for unauthenticated requests
- Role claim configurable; default role fallback
- Role name validation before interpolation into
SET ROLE -
rls.enabled: falseescape hatch with loud startup warning - JWKS / RS256 asymmetric JWT verification (
rls.auth.jwks_url+jwks_cache_ttl; RS256/384/512 + ES256/384/512, kid-matched, rotation-aware cache) - Per-request role allowlist (
rls.allowed_roles; default/anonymous roles always allowed) - Claim → GraphQL context exposure for plugins beyond
op.Claims(exec.OperationFromContext/ClaimsFromContext— the operation rides the request context into CompileHooks)
- Every mutation in a transaction by default (
transactions.mutations) - One transaction per operation; mutations abort remaining root fields on error
- Queries transaction-free unless RLS context requires one
- Plugins can force a transaction (
op.ForceTx) - Isolation level config (read_committed / repeatable_read / serializable)
- Statement timeout per connection (
database.statement_timeout) - PG error → GraphQL error mapping with
errors.detail: dev|prod(constraint violations pass through in prod, internals sanitized) - One transaction per request spanning operations (
transactions.per_request) - Automatic retry on serialization failures (
transactions.max_retries; SQLSTATE 40001/40P01, whole-operation re-run) - Savepoints for partial mutation recovery (v1.x)
-
POST /graphql(JSON body) andGET /graphql(query params) - GraphQL introspection (
__schema,__type,__typename), resolved in-process and exempt from depth/cost limits - Embedded GraphiQL playground (opt-in, off by default)
-
GET /healthz//readyz -
GET /schema.graphql(SDL export; opt-in, off by default) - Request timeout, max body size
- Atomic schema hot-swap (in-flight requests finish on the old schema)
-
/metrics(Prometheus) endpoint (v1.x) - Persisted queries / APQ (
server.apqApollo protocol,server.persisted_queries_pathallowlist file,server.persisted_onlylockdown) -
@defer/@stream(v2) - Schema contracts — named schema variants that hide tagged types/fields per audience (e.g.
public/partner/internal), each with its own SDL export and served endpoint; builds on the@omit/tag machinery in smart-comments (v2) - CORS configuration (
server.cors_origins: exact-match allowlist or*) - Response compression (gzip, opt-in via
server.compression)
-
pdbq schema dump— versioned, hashed, gzipped catalog snapshot -
serve --schema.cache_path— boot without touching pg_catalog -
pdbq schema check— CI drift gate (non-zero exit on drift) - Format-version and corruption rejection on load
- Watch mode: DDL event trigger +
LISTEN/NOTIFYre-introspection - Poll-hash fallback when event triggers can't be installed
- Watch + cache combination rejected at config validation
- Drift diff detail in
schema checkoutput (per-object added/removed/changed lines, column-level for tables)
-
pdbq serve|query|schema|config|pluginssubcommands -
pdbq querypipe-friendly: stdin queries,--var k=v(JSON-parsed),--vars-file -,--operation, distinct exit code for GraphQL errors - Config layering: flags > env (
PDBQ_*,__escapes underscores) > YAML -
pdbq config example— annotated reference generated from struct tags (committed atexamples/pdbq.example.yaml, can't drift) -
pdbq config init|validate -
pdbq plugins listwith hook surfaces -
pdbq schema print --json(introspection-format output) - Shell completions shipped (
pdbq completion bash|zsh|fish|powershell, plus fixed-vocabulary flag value completion)
- Compile-time plugins; ordered registry (priority + registration order)
-
CatalogHook— transform the introspected catalog -
InflectionHook— override any generated name, middleware-chained -
SchemaHook— mutate the schema IR before SDL generation -
CompileHook— wrap SQL generation per root field -
RequestHook— before/after operation, context + tx control - Enable/disable/configure per plugin via
plugins.*config - Library embedding:
pdbq.New(cfg, pdbq.WithPlugins(...)) - Collision detection with warnings at schema build
- Built-in:
simple-names - Built-in:
nested-mutations - Built-in:
advanced-filters(relation filters + computed-column filtering/ordering; halves toggle viaplugins.settings.advanced-filters.{relations,computed}) - Out-of-process plugins (go-plugin gRPC or wasm) — v2 track; the existing hook interfaces are the contract either way
- Built-in: smart-comments plugin (
docs/smart-comments.md) —@omit(per-action on tables, columns, FKs, unique constraints, functions),@name,@fieldName/@foreignFieldName,@deprecated,@notNull/@nullable,@filterable/@sortable, and logical@primaryKey/@unique/@foreignKeyon views;advanced-filtershonors the tags per object (@omit filter,orderon relations/computed functions) and gainsrelations_opt_in/computed_opt_infor explicit@filterableopt-in
- Multi-stage Dockerfile → static binary on distroless, nonroot
- Compose dev stack (Postgres + fixture + watch mode) and test stack
- Makefile: build/dev/test/test-e2e/bench/fuzz/lint/example-config/docker-build
- CI: vet, unit+golden with
-race, e2e against Postgres 16 service, docker build - Golden-file compiler tests (
-updateregeneration) - E2E suite incl. RLS matrix and nested-mutation rollback atomicity
- Fuzz: filter-input → SQL invariance (SQL text independent of values)
- Benchmarks: schema build, compile per query shape
- Load benchmarks (k6/vegeta) for RPS/latency against the compose stack, tracked over time (v1.x)
- Published container image / release automation (v1.x)
- golangci-lint config committed (Makefile falls back to
go vet) (v1.x)