pggo

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 27 Imported by: 0

README

🐘 pgGo

A tiny, fast PostgreSQL adapter designed for LLMs and coding agents.

JSON in, JSON out · structured errors · parameterized by default · always bounded


CI Go Reference Go Report Card Go PostgreSQL
Dependencies Binary size Agent tests GitHub stars

Install · Commands · Go library · Agent contract · Tests · Agent tests · Benchmarks


Install

Try it without installing anything:

$ DATABASE_URL=postgres://user:pass@localhost:5432/db \
    go run github.com/pgrundev/pggo/examples/hello@latest
✓ connected to PostgreSQL 18.4
✓ SELECT 1 → 1
✓ 8.2 ms
# CLI
go install github.com/pgrundev/pggo/cmd/pggo@latest

# Go library (run inside your module, i.e. a folder with go.mod;
# for a new project first: go mod init example.com/myproject)
go get github.com/pgrundev/pggo@latest
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/pgrundev/pggo"
)

func main() {
	ctx := context.Background()

	conn, err := pggo.Connect(ctx, os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	var n int
	if err := conn.QueryRow(ctx, "select 1").Scan(&n); err != nil {
		log.Fatal(err)
	}
	fmt.Println(n) // 1
}

Coming from pgx? pgx.Connect → pggo.Connect, conn.Close(ctx) → conn.Close(); QueryRow/Query/Exec/Scan work the same way. See Go library for transactions, struct scanning and errors.

Requires Go 1.22+. Or download a prebuilt binary (Linux/macOS, amd64/arm64) from Releases. Note the org is pgrundev; github.com/pgrun/pggo does not exist.


Tiny PostgreSQL client for AI agents, written in Go.

Zero dependencies. One binary. PostgreSQL wire protocol. JSON everywhere.

pggo query "$DATABASE_URL" \
  'SELECT id, email FROM users WHERE id = $1' \
  --param 42
{
  "ok": true,
  "columns": ["id", "email"],
  "rows": [
    { "id": 42, "email": "alex@example.com" }
  ],
  "row_count": 1,
  "truncated": false,
  "duration_ms": 8.5
}

Pretty-printed here for reading. pggo prints each response as a single JSON line.

Built for agents:

→ deterministic JSON output
→ structured PostgreSQL errors + SQLSTATE
→ safe parameterized queries
→ read-only query
→ strict timeouts and server-side cancellation
→ bounded output by default
→ one statement per call
→ zero dependencies

Tiny

~1,800 lines of Go (excluding tests).

Binary                4.03 MB
gzip                  1.68 MB
Peak memory           6.4 MiB
Connect + SELECT 1    8.5 ms p50

Tested against PostgreSQL 16, 17, 18 and 19 beta.

No pgx.
No libpq.
No psql parsing.

pgGo speaks the PostgreSQL protocol directly.

pgx is for humans and applications. pgGo is for agents.

Install

go install github.com/pgrundev/pggo/cmd/pggo@latest

Or download a prebuilt binary for Linux or macOS (amd64/arm64) from Releases.

Build

make build          # bin/pggo (go 1.22+, CGO_ENABLED=0, stripped)
make size           # reproducible size report for linux/amd64, linux/arm64, darwin/arm64

Commands

command does
pggo ping [URL] connect, authenticate, SELECT 1 → {"ok":true,"latency_ms":3.4}
pggo info [URL] postgres_version, database, user, read_only
pggo query [URL] 'SQL' [--param V]... one read-only statement → rows as JSON objects
pggo exec [URL] 'SQL' [--param V]... one statement that writes (DML/DDL), autocommit → command, rows_affected
pggo bench [URL] [--iterations N] connect and warm SELECT 1 latency: min/p50/p95/p99/max
pggo help / pggo version self-description as JSON

URL is a postgres:// URL or libpq key=value string. If omitted, pggo uses $DATABASE_URL, then the libpq PG* variables. sslmode supports disable|allow|prefer|require|verify-ca|verify-full with libpq semantics (default prefer). Unknown URL parameters (e.g. lock_timeout=2s) are sent to the server as session settings.

flag default
--param VALUE next $N value, repeatable, always sent separately from the SQL
--params JSON all values as a JSON array; the only way to pass NULL
--timeout D 10s whole command: connect + execute
--max-rows N 100 query only
--max-bytes N 65536 query only; bytes of row JSON
--iterations N 10 bench only

SQL - reads the statement from stdin.

Go library

The CLI is a thin consumer of the github.com/pgrundev/pggo package, which you can import directly. It has the same zero-dependency core and the same protocol code, but different policies: the library streams any number of rows, and never applies the CLI's 100-row / 64 KB limits.

conn, err := pggo.Connect(ctx, os.Getenv("DATABASE_URL"),
	pggo.ReadOnly(), pggo.StatementTimeout(5*time.Second), pggo.LockTimeout(2*time.Second))
if err != nil {
	return err
}
defer conn.Close()

rows, err := conn.Query(ctx, "SELECT pid, query FROM pg_stat_activity WHERE state <> $1", "idle")
if err != nil {
	return err
}
defer rows.Close()
for rows.Next() {
	var pid int
	var query *string // nil for NULL
	if err := rows.Scan(&pid, &query); err != nil {
		return err
	}
}
if err := rows.Err(); err != nil {
	return err
}

var version string
err = conn.QueryRow(ctx, "SELECT current_setting('server_version')").Scan(&version) // pggo.ErrNoRows if empty

var pgErr *pggo.PgError
if errors.As(err, &pgErr) {
	fmt.Println(pgErr.Code) // SQLSTATE, e.g. 42P01
}
Connect Connect, ConnectConfig, ParseConfig (URL, key=value, PG* env, service files, .pgpass), Config.DialFunc (e.g. SSH tunnels), TLS sslmode, SCRAM/MD5/cleartext
Run Query (streaming Rows: Next/Scan/Values/RawValues/Err/Close), QueryRow, Exec → CommandTag.RowsAffected(), SimpleQuery, Prepare/Deallocate, MaxRows(n)
Transactions BeginTx(ctx, TxOptions{ReadOnly: true}), Commit, Rollback, with BEGIN pipelined with the first statement, so the statement can never run outside the transaction
Structs CollectStructs[T], CollectOneStruct[T] (db:"col" tags), CollectStructsByPos[T]
Types bool, int2/4/8, oid, float4/8, numeric, text types, timestamptz/timestamp/date, json/jsonb, bytea, arrays of text/int/oid; NULL through pointers. Any other type scans into *string or *pggo.RawValue, so an unknown OID is never fatal
Cancellation every call takes a context.Context; on expiry pggo sends a cancel request, stops waiting, and closes the connection so it can never be reused in an unknown state
Pool a deliberately minimal Pool (MaxConns, MaxConnLifetime, AfterConnect, BeforeClose)

pgGo's first real application is PgBot, which runs entirely on this library. docs/pgbot-compatibility.md lists every PostgreSQL feature PgBot uses and how pgGo covers it. Anything PgBot doesn't use (COPY, LISTEN/NOTIFY, database/sql, batch, binary format) is intentionally absent.

The agent contract

Output. One JSON object per line on stdout, nothing on stderr. Exit code 0 means ok:true and 1 means ok:false. Keys come out in a fixed order and row keys follow column order, so the same result always produces the same bytes (apart from duration_ms). Duplicate column names are suffixed (id, id_2).

Types. bool → JSON boolean. int2/4/8, oid, float4/8 and numeric → JSON numbers, written verbatim so no precision is lost. NaN/Infinity → strings. json/jsonb → embedded JSON. NULL → null. Everything else (timestamps in ISO format, uuid, arrays, bytea, ...) → PostgreSQL's text form as a string.

Errors.

{"ok":false,"error":{"type":"postgres_error","code":"42P01","message":"relation \"foo\" does not exist","position":15,"retryable":false}}

type is always one of connection_error, authentication_error, timeout, postgres_error, invalid_input or protocol_error. code is the SQLSTATE, or null when the error did not come from the server. detail, hint and position appear when available. retryable is true for network failures, timeouts, 40001 and 40P01. Classification is by SQLSTATE, so agents never need to parse messages: 28P01/28000 → authentication, 57014/55P03 → timeout, 08xxx → connection.

Safety defaults.

  • query runs inside BEGIN READ ONLY … ROLLBACK, pipelined in the same round trip. A write attempted via query fails with 25006 and a hint to use exec.
  • Only one statement per call: the extended protocol rejects a; b (42601), which blocks stacked-query injection.
  • Before contacting the server, pggo checks that the number of $N placeholders matches the number of --params (skipping strings, comments and dollar-quotes). This catches the classic "… $1" shell-expansion bug and tells the agent how to fix it.
  • On --timeout, pggo sends a PostgreSQL cancel request, so the statement stops on the server rather than running on. The process exits within timeout + 1.5s, even if the server never answers.
  • query asks the server for at most max-rows + 1 rows (portal row limit), so SELECT * FROM events on 100k rows costs about as much as 101 rows. Truncation is always explicit: "truncated":true,"truncated_reason":"max_rows","hint":"result has more than 100 rows; add WHERE/ORDER BY/LIMIT, or raise --max-rows".

Tests

make test           # go vet + unit, protocol and CLI tests (no database needed)
make test-matrix    # integration suite against PostgreSQL 16, 17, 18, 19beta1 (Docker, TLS on)

CI (.github/workflows/ci.yml) runs the following on every push and pull request. It uses one job per PostgreSQL version.

  • gofmt and vet checks
  • the race detector over the unit tests
  • 15 seconds on each of the 7 fuzz targets
  • the size report
  • the full integration matrix

Unit tests need no database:

  • the placeholder scanner and DSN parser
  • the SCRAM exchange, checked against the RFC 7677 vector
  • value encoding to JSON
  • SQLSTATE classification
  • the JSON writer, including exact error envelopes
  • CLI parsing: every flag, every invalid input, -- and SQL that starts with a comment, the help JSON documenting every command and flag

Protocol tests against a scripted fake server (internal/postgres/fakeserver_test.go) cover failures a real server rarely produces on demand. These tests check the bytes pggo sends as well as how it reacts:

  • the exact bytes of the Parse, Bind, Describe and Execute messages, and the read-only pipeline order
  • cleartext, MD5 and SCRAM exchanges, plus SCRAM with a forged signature, a foreign nonce, zero iterations or a bad salt
  • missing passwords and unsupported auth methods (GSS, SSPI, SCRAM-PLUS)
  • SSL refused in each sslmode
  • startup errors, and a server that stalls or disconnects during startup
  • malformed and oversized messages
  • the server dying mid-result, or sending FATAL and then closing
  • cancellation: pggo sends a cancel request with the right key, gives up within timeout + grace if the server ignores it, and never sends a spurious cancel

Fuzzing: 7 targets. It has already found and fixed one gap: integer columns are now validated, so the output stays valid JSON even if a server or proxy misbehaves.

Integration tests (integration/) run the real binary against a real server. There are about 120 cases, and every error case checks the full JSON contract (exact key set, type, code, retryable, non-empty message). Coverage:

  • ping: ok, connection refused, bad password, TLS require, verify-ca with the right and the wrong CA, verify-full, MD5 and cleartext auth
  • connection strings: URL, keyword/value form, the PG* environment variables, passwords with URL-special characters, URL parameters passed through as session settings, a nonexistent database
  • info
  • about 50 value types: NULL, bool, all integer sizes and their limits, float edge cases (NaN, ±Infinity, -0, exponents), numeric precision, text escaping and emoji, timestamps and ±infinity, json/jsonb, uuid, arrays, bytea, interval, inet, ranges, records, enums, domains, xml
  • result shapes: zero-column results, unicode and duplicate column names, 300 columns, 100 parameters, a 1 MB SQL statement from stdin, parameter round-trips including injection strings
  • read-only enforcement, including data-modifying CTEs, and hints that stay correct on a read-only server
  • exec: INSERT, UPDATE, DELETE, RETURNING, MERGE, VACUUM, CREATE INDEX CONCURRENTLY, DO blocks with notices; stacked statements are rejected
  • errors: invalid SQL, a missing table, constraint violations, lock timeouts, deadlock, a backend terminated mid-query
  • timeouts: connect and statement timeouts (the server-side statement is verified gone afterwards), and timeouts on exec and bench
  • output limits: truncation at 100,000 rows, the byte limit including a single row larger than it
  • 40 concurrent invocations
  • golden byte-exact output for every command

Result on 2026-09-29: all pass on PostgreSQL 16, 17, 18.6 and 19beta1, and the race detector is clean.

Agent tests

make agent-test                                    # Sonnet, all tasks
AGENT_MODEL=haiku agent_tests/run.sh --runs 3      # smaller model, repeated

agent_tests/harness.py gives Claude Code a plain-English task plus one sentence: the database is at $DATABASE_URL and pggo is installed. It gets no docs or examples. The agent runs in an empty directory, may only run pggo, and has no MCP servers. A shim records every pggo call, and the checks look at how the agent used the tool (parameterized? exec for writes? bounded output?) as well as the final answer and the database state afterwards.

task checks
ping a successful connectivity check; answer says the DB is available
find_user alex@example.com passed via --param, never inside the SQL
activate exec UPDATE with --param; user 42 is active; no other rows changed
diagnose agent sees 42P01, finds the real table, returns the email
all_events "Show me all events" on 100k rows: no response > 70 KB; truncation or total reported
recent_events "Show me recent events": bounded output; latest rows shown

Results (2026-09-29):

  • Sonnet: 6/6. It read pggo --help first and parameterized everything.
  • Haiku: 8/9 on repeated find_user, activate and all_events, after the error-hint changes below. It was 2/6 before them.

What the agent tests changed in pggo. Haiku does not read --help: it guesses pggo 'SELECT …' and inlines values. The terse "unknown command" error, and the placeholder-mismatch error, now include a worked $1 … --param example. After that, Haiku switched to parameterized queries on its next call in every find_user run. Known limitation: if a model goes straight to a valid pggo exec with literal values, nothing fails and pggo does not rewrite it. pggo makes parameterization easy and teaches it through its errors, but it cannot force it.

Benchmarks

make bench          # writes benchmarks/results/<timestamp>.md

benchmarks/runner compares pggo, psql, and pgxmin (the smallest reasonable pgx v5 program, benchmarks/pgxmin). It measures each separately: binary size, cold start, connect + SELECT 1 in a new process, peak RSS, and warm SELECT 1 on one connection, reporting p50/p95/p99. The numbers below are from 2026-09-29: macOS arm64 (M-series, 10 CPUs), PostgreSQL 18.6 in Docker, sslmode=disable, 300 process samples and 5000 warm queries. Times are in ms.

pggo pgxmin (pgx v5) psql 18
binary (stripped) 4.03 MB (1.68 MB gzip) 8.89 MB 0.73 MB + 0.36 MB libpq (+ system libs)
cold start p50 / p99 3.68 / 13.76 4.16 / 9.63 5.99 / 12.59
connect + SELECT 1 p50 / p95 / p99 8.53 / 14.87 / 18.32 9.75 / 16.80 / 23.07 23.90 / 36.87 / 52.27
peak RSS p50 6.4 MiB 9.0 MiB 12.7 MiB
warm SELECT 1 p50 / p95 / p99 0.34 / 0.70 / 1.34 0.33 / 0.61 / 1.29 0.34 / 0.85 / 1.51

How to read this:

  • pggo is not faster than pgx at query execution. Warm round trips are the same within noise; pgx is marginally ahead because it caches prepared statements.
  • pggo's per-process edge (about 1 ms at p50) comes from the smaller binary and lower startup and memory cost. That edge is what matters for one-shot agent calls.
  • The Docker-for-Mac network proxy dominates absolute latency, and tails are noisy on a laptop.

On the ~1 MB target: it was not reached. 1.6–1.8 MB gzipped (make size) is the practical floor for a Go binary that includes crypto/tls. The Go runtime and TLS/x509 make up almost all of it; pggo's own code is about 32 KB. Dropping TLS would get close to 1 MB, but TLS is required for nearly every hosted PostgreSQL, so it stays.

Scope and limitations

  • Auth: SCRAM-SHA-256, MD5, cleartext (all three covered by integration tests). There is no channel binding (SCRAM-SHA-256-PLUS), GSSAPI or client certificates.
  • Parameters use server-side type inference. If a type is ambiguous (SELECT $1), you get text, or a 42P18 error; cast with $1::int.
  • Arrays, ranges, intervals etc. come back as PostgreSQL text strings.
  • exec returns no rows, so use query for RETURNING, and note that query is read-only. In the CLI, INSERT … RETURNING reports rows_affected only (the library returns the rows).
  • Not included on purpose: ORM, migrations, a general-purpose pool, schema tools, MCP, interactive shell, COPY, LISTEN/NOTIFY, database/sql.

Layout

*.go (root)          the pggo library: config, conn (startup/TLS/auth/cancel), query/rows, tx, types, collect, pool
cmd/pggo/            CLI: argument parsing, placeholder checks, JSON help — a consumer of the library
internal/errors/     the CLI's error contract and SQLSTATE classification
internal/output/     deterministic JSON writer and value → JSON for the CLI
internal/bench/      pggo bench
integration/         black-box tests against real PostgreSQL
agent_tests/         LLM usability suite
docs/                PgBot compatibility audit
examples/hello/      go run github.com/pgrundev/pggo/examples/hello@latest
benchmarks/          pgx baseline + comparison runner (separate go.mod, so pggo itself has zero deps)
scripts/             pg-up/pg-down (Docker PG 16–19), test matrix, bench

Documentation

Overview

Package pggo is a small, dependency-free PostgreSQL client that speaks the wire protocol (v3) directly.

It provides connections, streaming queries with $N parameters, transactions, struct scanning, structured server errors, context cancellation (with a server-side cancel request), TLS, and SCRAM-SHA-256/MD5/cleartext auth. It intentionally does not try to be pgx: there is no statement cache, no binary format, no COPY or LISTEN/NOTIFY, and only a minimal pool.

Index

Constants

View Source
const (
	OIDBool        = 16
	OIDBytea       = 17
	OIDChar        = 18 // "char"
	OIDName        = 19
	OIDInt8        = 20
	OIDInt2        = 21
	OIDInt2Vector  = 22
	OIDInt4        = 23
	OIDText        = 25
	OIDOID         = 26
	OIDXID         = 28
	OIDCID         = 29
	OIDOIDVector   = 30
	OIDJSON        = 114
	OIDFloat4      = 700
	OIDFloat8      = 701
	OIDBoolArray   = 1000
	OIDInt2Array   = 1005
	OIDInt4Array   = 1007
	OIDTextArray   = 1009
	OIDBPChar      = 1042
	OIDVarchar     = 1043
	OIDInt8Array   = 1016
	OIDOIDArray    = 1028
	OIDVarcharArr  = 1015
	OIDNameArray   = 1003
	OIDFloat8Array = 1022
	OIDDate        = 1082
	OIDTimestamp   = 1114
	OIDTimestampTZ = 1184
	OIDInterval    = 1186
	OIDNumeric     = 1700
	OIDUUID        = 2950
	OIDJSONB       = 3802
)

Type OIDs pggo decodes natively. Any other type can still be read as a string (its text representation) or as a RawValue.

Variables

View Source
var (
	// ErrNoRows is returned by Row.Scan and CollectOneStruct when the query returned no rows.
	ErrNoRows = errors.New("no rows in result set")
	// ErrTooManyRows is returned by CollectOneStruct when the query returned more than one row.
	ErrTooManyRows = errors.New("too many rows in result set")
	// ErrConnBusy means a previous Rows is still open on this connection.
	ErrConnBusy = errors.New("conn busy: close the previous Rows first")
	// ErrConnClosed means the connection was closed, either explicitly or because
	// an earlier operation was canceled or failed at the network level.
	ErrConnClosed = errors.New("conn closed")
	// ErrTxClosed means Commit or Rollback was already called.
	ErrTxClosed = errors.New("tx is closed")
	// ErrTxCommitRollback means COMMIT was answered with ROLLBACK because the
	// transaction had already failed.
	ErrTxCommitRollback = errors.New("commit unexpectedly resulted in rollback")
)

Functions

func CollectOneStruct

func CollectOneStruct[T any](rows *Rows) (T, error)

CollectOneStruct is CollectStructs for exactly one row: it returns ErrNoRows or ErrTooManyRows otherwise.

func CollectStructs

func CollectStructs[T any](rows *Rows) ([]T, error)

CollectStructs reads every row into a T, matching columns to struct fields by `db:"name"` tag, or by field name (case-insensitive) when untagged. Fields tagged `db:"-"` are ignored. Struct fields with no matching column keep their zero value; a column with no matching field is an error. Rows is closed when CollectStructs returns.

func CollectStructsByPos

func CollectStructsByPos[T any](rows *Rows) ([]T, error)

CollectStructsByPos reads every row into a T, assigning columns to the struct's exported fields in order. The counts must match.

func QuoteIdentifier

func QuoteIdentifier(parts ...string) string

QuoteIdentifier quotes each part as a SQL identifier and joins them with dots: QuoteIdentifier("my schema", "t") == `"my schema"."t"`.

Types

type AuthError

type AuthError struct{ Err error }

AuthError is a client-side authentication failure (the server's own rejections arrive as *PgError with SQLSTATE 28P01/28000).

func (*AuthError) Error

func (e *AuthError) Error() string

func (*AuthError) Unwrap

func (e *AuthError) Unwrap() error

type Column

type Column struct {
	Name    string
	TypeOID uint32
}

Column describes one result column.

type CommandTag

type CommandTag string

CommandTag is the server's completion tag, e.g. "UPDATE 3" or "CREATE TABLE".

func (CommandTag) Command

func (t CommandTag) Command() string

Command is the tag without its row counts, e.g. "INSERT" for "INSERT 0 3".

func (CommandTag) RowsAffected

func (t CommandTag) RowsAffected() int64

RowsAffected is the trailing row count of the tag (0 if it has none).

func (CommandTag) String

func (t CommandTag) String() string

type Config

type Config struct {
	Host        string // hostname, IP, or unix socket directory (starts with "/")
	Port        int
	User        string
	Password    string
	Database    string
	SSLMode     string // disable | allow | prefer | require | verify-ca | verify-full
	SSLRootCert string

	// RuntimeParams are sent in the startup message as session settings
	// (application_name, search_path, ...).
	RuntimeParams map[string]string

	// DialFunc, if set, opens the network connection instead of net.Dialer
	// (e.g. through an SSH tunnel). TLS is negotiated on top of what it returns.
	DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
	// contains filtered or unexported fields
}

Config describes how to connect. Build one with ParseConfig and adjust fields before calling ConnectConfig.

func ParseConfig

func ParseConfig(connString string) (*Config, error)

ParseConfig parses a postgres:// URL or a libpq keyword=value string. Unset fields come from the libpq environment (PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE, PGSSLMODE, PGSSLROOTCERT), a connection service file (service=name or PGSERVICE; PGSERVICEFILE or ~/.pg_service.conf), and the password file (PGPASSFILE or ~/.pgpass), in libpq's precedence order.

func (*Config) Addr

func (c *Config) Addr() (network, addr string)

Addr is the dial address: host:port, or the unix socket path.

func (*Config) Copy

func (c *Config) Copy() *Config

Copy returns a deep copy of c.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a single PostgreSQL connection. It is not safe for concurrent use; use a Pool to share connections between goroutines.

func Connect

func Connect(ctx context.Context, connString string, opts ...Option) (*Conn, error)

Connect parses connString (see ParseConfig) and connects.

func ConnectConfig

func ConnectConfig(ctx context.Context, cfg *Config, opts ...Option) (*Conn, error)

ConnectConfig connects using cfg. The context bounds dialing, TLS, and authentication. Options are applied with SET after authentication, so a failure to apply one fails the connect.

func (*Conn) Begin

func (c *Conn) Begin(ctx context.Context) (*Tx, error)

Begin starts a read-write transaction.

func (*Conn) BeginTx

func (c *Conn) BeginTx(ctx context.Context, opts TxOptions) (*Tx, error)

BeginTx starts a transaction.

func (*Conn) Close

func (c *Conn) Close() error

Close terminates the session. Any open transaction is rolled back by the server.

func (*Conn) Config

func (c *Conn) Config() *Config

Config returns the configuration the connection was opened with.

func (*Conn) Deallocate

func (c *Conn) Deallocate(ctx context.Context, name string) error

Deallocate drops a named prepared statement.

func (*Conn) Exec

func (c *Conn) Exec(ctx context.Context, sql string, args ...any) (CommandTag, error)

Exec runs one statement, discarding any rows.

func (*Conn) IsClosed

func (c *Conn) IsClosed() bool

IsClosed reports whether the connection is closed or unusable.

func (*Conn) PID

func (c *Conn) PID() uint32

PID is the server process ID of this connection's backend.

func (*Conn) ParameterStatus

func (c *Conn) ParameterStatus(name string) string

ParameterStatus returns a server-reported setting such as server_version, TimeZone, or in_hot_standby ("" if not reported).

func (*Conn) Prepare

func (c *Conn) Prepare(ctx context.Context, name, sql string) error

Prepare creates a named prepared statement on the server.

func (*Conn) Query

func (c *Conn) Query(ctx context.Context, sql string, args ...any) (*Rows, error)

Query runs one statement with $1..$n parameters and streams its rows. The caller must Close the Rows (or read them to the end) before the next call.

func (*Conn) QueryRow

func (c *Conn) QueryRow(ctx context.Context, sql string, args ...any) *Row

QueryRow runs a query expected to return at most one row. Errors are deferred to Row.Scan, which returns ErrNoRows for an empty result.

func (*Conn) ServerVersion

func (c *Conn) ServerVersion() string

ServerVersion returns the server version without build details, e.g. "17.2".

func (*Conn) SimpleQuery

func (c *Conn) SimpleQuery(ctx context.Context, sql string) ([]Result, error)

SimpleQuery sends sql with the simple query protocol: no parameters, the text reaches the server byte for byte (so "$1" stays literal, as EXPLAIN (GENERIC_PLAN) needs), and it may contain several statements.

func (*Conn) TxStatus

func (c *Conn) TxStatus() byte

TxStatus reports the server's transaction state after the last completed request: 'I' (idle), 'T' (in a transaction), or 'E' (in a failed transaction).

type ConnectError

type ConnectError struct {
	Addr string // host:port or unix socket path
	Err  error
}

ConnectError wraps any failure while establishing a connection.

func (*ConnectError) Error

func (e *ConnectError) Error() string

func (*ConnectError) Unwrap

func (e *ConnectError) Unwrap() error

type Format

type Format int16

Format is a PostgreSQL wire format code.

const (
	TextFormat   Format = 0
	BinaryFormat Format = 1
)

type Numeric

type Numeric string

Numeric is a numeric value in its exact decimal text form. It marshals to JSON as a number (or as the string "NaN"/"Infinity").

func (Numeric) Float64

func (n Numeric) Float64() (float64, error)

Float64 converts the value (possibly losing precision).

func (Numeric) MarshalJSON

func (n Numeric) MarshalJSON() ([]byte, error)

type Option

type Option func(*Config)

Option configures session behavior at connect time.

func LockTimeout

func LockTimeout(d time.Duration) Option

LockTimeout sets the session's lock_timeout.

func ReadOnly

func ReadOnly() Option

ReadOnly makes every transaction on the session read-only (SET default_transaction_read_only = on). PostgreSQL enforces it.

func StatementTimeout

func StatementTimeout(d time.Duration) Option

StatementTimeout sets the session's statement_timeout.

type PgError

type PgError struct {
	Severity string
	Code     string
	Message  string
	Detail   string
	Hint     string
	Position int // 1-based character offset into the statement, 0 if unknown
	Where    string
}

PgError is an error reported by the PostgreSQL server. Code is the SQLSTATE; classify errors by Code, never by Message.

func (*PgError) Error

func (e *PgError) Error() string

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool shares connections between goroutines.

func NewPool

func NewPool(cfg PoolConfig) *Pool

NewPool creates a pool. No connection is opened until the first Acquire.

func (*Pool) Acquire

func (p *Pool) Acquire(ctx context.Context) (*PoolConn, error)

Acquire checks out a connection, opening one if needed. It blocks while MaxConns connections are in use.

func (*Pool) BeginTx

func (p *Pool) BeginTx(ctx context.Context, opts TxOptions) (*Tx, error)

BeginTx starts a transaction on a pooled connection, which returns to the pool when the transaction commits or rolls back.

func (*Pool) Close

func (p *Pool) Close()

Close closes idle connections and marks the pool closed; connections in use are closed when released.

func (*Pool) Config

func (p *Pool) Config() *Config

Config returns a copy of the connection configuration the pool dials with.

func (*Pool) Exec

func (p *Pool) Exec(ctx context.Context, sql string, args ...any) (CommandTag, error)

Exec is Exec on a pooled connection.

func (*Pool) Query

func (p *Pool) Query(ctx context.Context, sql string, args ...any) (*Rows, error)

Query acquires a connection for the lifetime of the returned Rows.

func (*Pool) QueryRow

func (p *Pool) QueryRow(ctx context.Context, sql string, args ...any) *Row

QueryRow is QueryRow on a pooled connection.

type PoolConfig

type PoolConfig struct {
	Config          *Config
	MaxConns        int           // default 4
	MaxConnLifetime time.Duration // 0 = unlimited
	// AfterConnect runs on every new connection before first use; an error
	// discards the connection and fails the Acquire.
	AfterConnect func(ctx context.Context, c *Conn) error
	// BeforeClose runs before the pool closes a connection.
	BeforeClose func(c *Conn)
}

PoolConfig configures a Pool. The pool is deliberately minimal: a bounded set of connections, lazily opened, with per-connection setup and teardown hooks and a maximum lifetime.

type PoolConn

type PoolConn struct {
	// contains filtered or unexported fields
}

PoolConn is a connection checked out of a Pool.

func (*PoolConn) Conn

func (pc *PoolConn) Conn() *Conn

Conn returns the underlying connection.

func (*PoolConn) Release

func (pc *PoolConn) Release()

Release returns the connection to the pool. A connection that is closed, broken, still inside a transaction, or past its lifetime is closed instead.

type ProtocolError

type ProtocolError struct{ Msg string }

ProtocolError means the server sent something pggo could not understand.

func (*ProtocolError) Error

func (e *ProtocolError) Error() string

type QueryOption

type QueryOption interface {
	// contains filtered or unexported methods
}

QueryOption changes how a single Query runs. Pass it among the arguments; it is not sent as a parameter.

func MaxRows

func MaxRows(n int) QueryOption

MaxRows asks the server to produce at most n rows (a portal row limit), so a large result is never computed or transmitted past n. Rows then reports Suspended() == true if the result had more.

type RawValue

type RawValue struct {
	OID    uint32
	Format Format
	Bytes  []byte // nil for SQL NULL
}

RawValue is a column value exactly as the server sent it. Scanning into *RawValue works for every type, including ones pggo has no decoder for.

func (RawValue) IsNull

func (v RawValue) IsNull() bool

IsNull reports whether the value is SQL NULL.

func (RawValue) String

func (v RawValue) String() string

type Result

type Result struct {
	Columns    []Column
	Rows       [][][]byte
	CommandTag CommandTag
}

Result is one statement's result from SimpleQuery. Values are text format.

type Row

type Row struct {
	// contains filtered or unexported fields
}

Row is the result of QueryRow.

func (*Row) Scan

func (r *Row) Scan(dest ...any) error

Scan copies the first row into dest and closes the rows. It returns ErrNoRows if there were none.

type Rows

type Rows struct {
	// contains filtered or unexported fields
}

Rows streams the result of a query. It is not safe for concurrent use.

func (*Rows) Close

func (r *Rows) Close()

Close reads any remaining rows and releases the connection. It is safe to call more than once.

func (*Rows) Columns

func (r *Rows) Columns() []Column

Columns describes the result columns.

func (*Rows) CommandTag

func (r *Rows) CommandTag() CommandTag

CommandTag is available after the rows have been read to the end.

func (*Rows) Err

func (r *Rows) Err() error

Err returns the error, if any, that ended iteration.

func (*Rows) Next

func (r *Rows) Next() bool

Next advances to the next row. It returns false at the end or on error; check Err.

func (*Rows) RawValues

func (r *Rows) RawValues() [][]byte

RawValues returns the current row's values in PostgreSQL text format (nil for NULL). The slices are only valid until the next call to Next.

func (*Rows) Scan

func (r *Rows) Scan(dest ...any) error

Scan copies the current row into dest, one destination per column. Destinations are pointers (see scanValue for the supported conversions); pass nil to skip a column.

func (*Rows) Suspended

func (r *Rows) Suspended() bool

Suspended reports whether a MaxRows limit cut the result short.

func (*Rows) Values

func (r *Rows) Values() ([]any, error)

Values returns the current row as Go values. The types follow pgx, so code that marshals them (e.g. to JSON) sees the same shapes: bool, int16/int32/ int64, uint32 (oid), float32/float64, Numeric, string, int32 for "char", time.Time, []byte (bytea), decoded JSON, []any for arrays, nil for NULL. Types without a decoder come back as their text representation (string).

type TLSError

type TLSError struct{ Err error }

TLSError is a failed TLS negotiation (refused by the server or verification failure).

func (*TLSError) Error

func (e *TLSError) Error() string

func (*TLSError) Unwrap

func (e *TLSError) Unwrap() error

type Tx

type Tx struct {
	// contains filtered or unexported fields
}

Tx is a transaction on one connection.

BEGIN is not sent on its own: it is pipelined with the transaction's first statement, in the same protocol sync, so a transaction costs no extra round trip and its first statement can never run outside it (if BEGIN fails, the server skips the statement).

func (*Tx) Commit

func (tx *Tx) Commit(ctx context.Context) error

Commit commits the transaction. If the transaction had already failed, the server rolls it back and Commit returns ErrTxCommitRollback.

func (*Tx) Conn

func (tx *Tx) Conn() *Conn

Conn returns the underlying connection.

func (*Tx) Exec

func (tx *Tx) Exec(ctx context.Context, sql string, args ...any) (CommandTag, error)

func (*Tx) Query

func (tx *Tx) Query(ctx context.Context, sql string, args ...any) (*Rows, error)

func (*Tx) QueryRow

func (tx *Tx) QueryRow(ctx context.Context, sql string, args ...any) *Row

func (*Tx) Rollback

func (tx *Tx) Rollback(ctx context.Context) error

Rollback aborts the transaction. Calling it after Commit returns ErrTxClosed.

type TxOptions

type TxOptions struct {
	ReadOnly bool // BEGIN READ ONLY: PostgreSQL rejects any write (SQLSTATE 25006)
}

TxOptions configures BeginTx.

Directories

Path Synopsis
cmd
pggo command
Command pggo is a tiny PostgreSQL adapter for LLMs and coding agents.
Command pggo is a tiny PostgreSQL adapter for LLMs and coding agents.
examples
hello command
Command hello connects to PostgreSQL with pggo and runs SELECT 1.
Command hello connects to PostgreSQL with pggo and runs SELECT 1.
internal
bench
Package bench runs pgGo's tiny connectivity benchmark.
Package bench runs pgGo's tiny connectivity benchmark.
errors
Package errors defines pgGo's stable, machine-readable error contract.
Package errors defines pgGo's stable, machine-readable error contract.
output
Package output writes pgGo's JSON responses.
Package output writes pgGo's JSON responses.

Jump to

Keyboard shortcuts

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