pdbq

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 24 Imported by: 0

README

pdbq

CI Release Go Reference

Zero-boilerplate GraphQL API for PostgreSQL, in the spirit of PostGraphile: point it at a live database and it introspects pg_catalog, generates a GraphQL schema (types, relations, filters, pagination, CRUD mutations), and compiles every GraphQL operation into one parameterized SQL statement — no N+1, no dataloaders.

$ pdbq serve --database.url postgres://localhost/mydb --server.graphiql
time=... msg=introspected tables=12 enums=3 functions=2 took=38ms
time=... msg=listening addr=:8080 graphiql=true

Highlights

  • Single-statement compilation — nested selections become jsonb_build_object trees, relations become LEFT JOIN LATERAL, lists become jsonb_agg; PostgreSQL returns the response JSON directly.
  • RLS-aware by default — each request runs in a transaction with SET LOCAL ROLE and claims exposed via set_config('pdbq.claims.*', ...), from a verified JWT or trusted gateway headers.
  • Filters with an indexed-only policy — only indexed columns are filterable out of the box (filters.indexed_only: false opens it up), with per-type operator sets (likeInsensitive for text, @> for arrays/jsonb, ...).
  • Relay connections everywhere — every collection (allUsers, backward relations) is a cursor connection with first/last/offset/before/ after, keyset-backed cursors, totalCount and pageInfo; every row type with a primary key implements Node with a global nodeId resolvable via node(nodeId:).
  • Schema cachepdbq schema dump / serve --schema.cache_path boots without touching pg_catalog; pdbq schema check is a CI drift gate.
  • Watch mode — a DDL event trigger (or poll fallback) re-introspects and atomically swaps the schema during development.
  • Plugins — five small hook interfaces (catalog, inflection, schema, compile, request) with four built-ins proving the design: smart-comments (tune the schema with PostGraphile-style @omit/@name/ @primaryKey/... tags in database COMMENTs, see docs/smart-comments.md), simple-names (users instead of allUsers), advanced-filters (filter across relations and filter/order by computed columns), and nested-mutations (nested create/connect inputs compiled to multi-CTE statements in a forced transaction).
  • Pipe-friendly CLIecho '{users {email}}' | pdbq query - prints JSON and exits non-zero on GraphQL errors.

Installation

Prebuilt binaries for Linux, macOS and Windows (amd64/arm64) are on the releases page, with a checksums.txt for verification:

$ curl -fsSL https://github.com/suprbdev/pdbq/releases/latest/download/pdbq_0.1.0_linux_amd64.tar.gz | tar xz
$ ./pdbq --version

Or pull the (multi-arch, distroless) Docker image:

$ docker run --rm ghcr.io/suprbdev/pdbq:latest --version

Or build from source:

$ go install github.com/suprbdev/pdbq/cmd/pdbq@latest

Quickstart

$ docker compose up          # Postgres + fixture + pdbq
$ open http://localhost:8080 # GraphiQL (port mapping via compose.override.yaml, see docs/quickstart.md)

Or against your own database:

$ pdbq config init          # writes a starter pdbq.yaml
$ pdbq serve

See docs/quickstart.md for the full tour.

Documentation

Doc Contents
docs/features.md Feature support checklist (what works, what's planned)
docs/quickstart.md First run, first queries
docs/configuration.md Config layering; full reference in examples/pdbq.example.yaml
docs/cli.md All subcommands, scripting patterns
docs/rls.md Roles, JWT/header claims, policies
docs/filtering.md Filter operators, indexed-only policy
docs/plugins.md Hook surfaces, writing a plugin, built-ins as worked examples
docs/caching.md Schema cache and CI/CD gates
docs/deployment.md Docker image, health checks, production checklist

Development

$ make test        # unit + golden tests (no database needed)
$ make test-e2e    # end-to-end suite against a disposable Postgres
$ make bench       # schema-build and compile benchmarks
$ make fuzz        # fuzz the filter-input -> SQL path
$ make dev         # live stack with watch mode

Golden tests are the compiler's regression net: each .graphql document in internal/compile/testdata/ has its compiled SQL snapshotted next to it (regenerate with go test ./internal/compile/ -update).

Documentation

Overview

Public embedding surface: aliases that let external modules construct a Config, implement plugin hooks, and mount pdbq inside their own HTTP mux. The implementation stays under internal/; aliases are the contract.

Package pdbq wires the subsystems into a runnable application and is the embedding surface for third-party plugins: import pdbq as a library and run pdbq.New(cfg, pdbq.WithPlugins(myPlugin)).Serve(ctx).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App struct {
	Config   config.Config
	Registry *plugin.Registry
	Log      *slog.Logger
	// contains filtered or unexported fields
}

App is a configured pdbq instance.

func New

func New(cfg config.Config, opts ...Option) *App

New creates an App with the built-in plugins registered.

func (*App) BuildSchema

func (a *App) BuildSchema(ctx context.Context, cat *introspect.Catalog) (*schema.Built, error)

BuildSchema runs the full pipeline: catalog hooks -> inflection -> default generation -> schema hooks -> SDL validation.

func (*App) Close

func (a *App) Close()

Close releases resources.

func (*App) Connect

func (a *App) Connect(ctx context.Context) error

Connect opens the pool.

func (*App) Executor

func (a *App) Executor(built *schema.Built) *exec.Executor

Executor assembles the request executor for a built schema.

func (*App) Handler

func (a *App) Handler(ctx context.Context) (http.Handler, error)

Handler runs the boot pipeline (introspect -> plugins -> schema -> executor) and returns the GraphQL http.Handler for embedding in a larger mux. Watch mode, when enabled, hot-swaps the schema behind the returned handler exactly as Serve does. The caller owns the listener; server.addr and request timeouts still apply to the handler's internals (http.TimeoutHandler), but read/idle timeouts are the embedder's job.

func (*App) LoadCatalog

func (a *App) LoadCatalog(ctx context.Context) (*introspect.Catalog, error)

LoadCatalog introspects the database, or loads the schema cache when configured.

func (*App) Plugins

func (a *App) Plugins() []plugin.Plugin

BuiltinPluginNames lists built-ins for `pdbq plugins list`.

func (*App) Pool

func (a *App) Pool() *pgxpool.Pool

Pool exposes the connection pool (nil before Connect).

func (*App) Query

func (a *App) Query(ctx context.Context, query string, vars map[string]any, operation string) (*exec.Result, error)

Query executes a one-off GraphQL request (the `pdbq query` path).

func (*App) Serve

func (a *App) Serve(ctx context.Context) error

Serve runs the HTTP server (and watch mode when enabled) until ctx ends.

type AuthConfig

type AuthConfig = config.Auth

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type Built

type Built = schema.Built

Types referenced by hook signatures and the executor.

type Catalog

type Catalog = introspect.Catalog

Types referenced by hook signatures and the executor.

type CatalogHook

type CatalogHook = plugin.CatalogHook

Plugin hook surface.

type CompileFunc

type CompileFunc = compile.Func

Types referenced by hook signatures and the executor.

type CompileHook

type CompileHook = plugin.CompileHook

Plugin hook surface.

type CompileRequest

type CompileRequest = compile.Request

Types referenced by hook signatures and the executor.

type Config

type Config = config.Config

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the documented defaults.

func LoadConfig

func LoadConfig(path string, flags *flag.FlagSet) (Config, error)

LoadConfig builds the effective config: defaults < YAML < PDBQ_* env < flags (nil flags is fine).

type DatabaseConfig

type DatabaseConfig = config.Database

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type ErrorsConfig

type ErrorsConfig = config.Errors

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type ExecRequest

type ExecRequest = exec.Request

Types referenced by hook signatures and the executor.

type Executor

type Executor = exec.Executor

Types referenced by hook signatures and the executor.

type FiltersConfig

type FiltersConfig = config.Filters

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type InflectInput

type InflectInput = inflect.Input

Types referenced by hook signatures and the executor.

type InflectKind

type InflectKind = inflect.Kind

Types referenced by hook signatures and the executor.

type InflectNext

type InflectNext = inflect.Next

Types referenced by hook signatures and the executor.

type InflectionHook

type InflectionHook = plugin.InflectionHook

Plugin hook surface.

type LogConfig

type LogConfig = config.Log

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type Operation

type Operation = exec.Operation

Types referenced by hook signatures and the executor.

type Option

type Option func(*App)

Option customizes an App.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger overrides the default logger.

func WithPlugins

func WithPlugins(plugins ...plugin.Plugin) Option

WithPlugins registers additional plugins (the library embedding pattern).

type Plugin

type Plugin = plugin.Plugin

Plugin hook surface.

type PluginRegistry

type PluginRegistry = plugin.Registry

Plugin hook surface.

type PluginsConfig

type PluginsConfig = config.Plugins

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type RLSConfig

type RLSConfig = config.RLS

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type RequestHook

type RequestHook = plugin.RequestHook

Plugin hook surface.

type Result

type Result = exec.Result

Types referenced by hook signatures and the executor.

type SchemaBuilder

type SchemaBuilder = schema.Builder

Types referenced by hook signatures and the executor.

type SchemaConfig

type SchemaConfig = config.Schema

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type SchemaHook

type SchemaHook = plugin.SchemaHook

Plugin hook surface.

type ServerConfig

type ServerConfig = config.Server

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type Statement

type Statement = compile.Statement

Types referenced by hook signatures and the executor.

type TXConfig

type TXConfig = config.TX

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

type WatchConfig

type WatchConfig = config.Watch

Configuration. Section types carry a Config suffix to avoid clashing with unrelated root-package names.

Directories

Path Synopsis
cmd
pdbq command
Command pdbq serves an automatically mapped GraphQL API for a PostgreSQL database, and provides schema-cache, config, and one-off query tooling.
Command pdbq serves an automatically mapped GraphQL API for a PostgreSQL database, and provides schema-cache, config, and one-off query tooling.
internal
cache
Package cache (de)serializes the introspected Catalog so pdbq can boot without touching pg_catalog: gzip-compressed JSON with a format version and a content hash for drift detection (`pdbq schema dump|check`).
Package cache (de)serializes the introspected Catalog so pdbq can boot without touching pg_catalog: gzip-compressed JSON with a format version and a content hash for drift detection (`pdbq schema dump|check`).
compile
Package compile turns one validated GraphQL root field into exactly one parameterized SQL statement, PostGraphile-style: nested selections become jsonb_build_object trees, relations become LEFT JOIN LATERAL subqueries, lists become jsonb_agg — so the database returns the response JSON directly and there is no N+1 problem to paper over.
Package compile turns one validated GraphQL root field into exactly one parameterized SQL statement, PostGraphile-style: nested selections become jsonb_build_object trees, relations become LEFT JOIN LATERAL subqueries, lists become jsonb_agg — so the database returns the response JSON directly and there is no N+1 problem to paper over.
config
Package config defines pdbq's configuration model and its loading rules: YAML file < environment (PDBQ_*) < command-line flags.
Package config defines pdbq's configuration model and its loading rules: YAML file < environment (PDBQ_*) < command-line flags.
exec
Package exec owns the request lifecycle: parse/validate, depth and cost limits, per-operation transactions, RLS role switching + claims via set_config, execution of compiled statements, and PG->GraphQL error mapping.
Package exec owns the request lifecycle: parse/validate, depth and cost limits, per-operation transactions, RLS role switching + claims via set_config, execution of compiled statements, and PG->GraphQL error mapping.
inflect
Package inflect defines the naming pipeline for every generated GraphQL identifier.
Package inflect defines the naming pipeline for every generated GraphQL identifier.
introspect
Package introspect reads pg_catalog and produces a Catalog — the single serializable model that drives schema building, caching, and plugins.
Package introspect reads pg_catalog and produces a Catalog — the single serializable model that drives schema building, caching, and plugins.
plugin
Package plugin defines the hook surfaces and the ordered registry.
Package plugin defines the hook surfaces and the ordered registry.
plugins/advancedfilters
Package advancedfilters is a built-in plugin extending the generated filter/orderBy surface with two features default generation leaves out:
Package advancedfilters is a built-in plugin extending the generated filter/orderBy surface with two features default generation leaves out:
plugins/nestedmutations
Package nestedmutations is a built-in plugin exercising every hook surface: it adds nested `create`/`connect` input fields on create-mutation inputs following foreign keys (SchemaHook), compiles them into a single multi-CTE SQL statement respecting FK direction and insertion order (CompileHook), and forces a transaction for nested mutations even when transactions are globally disabled (RequestHook), unless plugins.nested-mutations.force_transactions is false.
Package nestedmutations is a built-in plugin exercising every hook surface: it adds nested `create`/`connect` input fields on create-mutation inputs following foreign keys (SchemaHook), compiles them into a single multi-CTE SQL statement respecting FK direction and insertion order (CompileHook), and forces a transaction for nested mutations even when transactions are globally disabled (RequestHook), unless plugins.nested-mutations.force_transactions is false.
plugins/simplenames
Package simplenames is a built-in plugin proving that naming is fully hook-driven: `users` instead of `allUsers`, `user(id:)` instead of `userById`, `updateUser` instead of `updateUserById`, and shortened relation names where unambiguous.
Package simplenames is a built-in plugin proving that naming is fully hook-driven: `users` instead of `allUsers`, `user(id:)` instead of `userById`, `updateUser` instead of `updateUserById`, and shortened relation names where unambiguous.
plugins/smartcomments
Package smartcomments is a built-in plugin that turns PostGraphile-style "smart comments" — database COMMENTs whose leading lines start with '@' — into schema customization, so the generated API can be tuned with plain DDL and no pdbq configuration:
Package smartcomments is a built-in plugin that turns PostGraphile-style "smart comments" — database COMMENTs whose leading lines start with '@' — into schema customization, so the generated API can be tuned with plain DDL and no pdbq configuration:
schema
Package schema turns an introspect.Catalog into a GraphQL schema.
Package schema turns an introspect.Catalog into a GraphQL schema.
server
Package server exposes the GraphQL API over HTTP: POST /graphql, an embedded GraphiQL page, and health endpoints.
Package server exposes the GraphQL API over HTTP: POST /graphql, an embedded GraphiQL page, and health endpoints.
smarttags
Package smarttags parses PostGraphile-style "smart comments": lines at the start of a database COMMENT beginning with '@' are machine-readable tags, everything after the first non-tag line is the human description.
Package smarttags parses PostGraphile-style "smart comments": lines at the start of a database COMMENT beginning with '@' are machine-readable tags, everything after the first non-tag line is the human description.
testutil
Package testutil provides a hand-built fixture Catalog mirroring the docker fixture schema, so schema-builder and compiler tests run without a database.
Package testutil provides a hand-built fixture Catalog mirroring the docker fixture schema, so schema-builder and compiler tests run without a database.
watch
Package watch re-introspects on DDL changes (dev mode).
Package watch re-introspects on DDL changes (dev mode).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL