Documentation
¶
Overview ¶
Package peng is a sqlc-powered database migration and seeding tool.
Peng provides SQL migrations with separate up/down files plus two shapes of seeds: top-level SQL files that the CLI runs inline, and Go packages compiled into a seed-runner binary by `peng build`. It also exposes its operations over MCP so language-model agents can drive migrations and seeds.
All peng-managed files live under a self-contained .peng/ directory in a user project:
.peng/peng.yaml config (dialect, DSN) .peng/go.mod module peng-cli .peng/migrations/<version>_<name>.up.sql .peng/migrations/<version>_<name>.down.sql .peng/seeds/<version>_<name>.sql SQL seed .peng/seeds/<version>_<name>/seed.go Go seed entry point .peng/seeds/<version>_<name>/query.sql sqlc input .peng/seeds/<version>_<name>/query.sql.go sqlc-generated .peng/bin/seed-runner built by `peng build`
.peng/ is a separate Go module from the parent project — the parent never imports from it, so nothing breaks if you rename the parent's module path.
Index ¶
- Constants
- Variables
- type AdoptScan
- type ApplySeedOption
- type ApplySeedOpts
- type Column
- type Config
- type CreateMigrationOptions
- type DriftIssue
- type DriftKind
- type DriftReport
- type Enum
- type FnArg
- type ForeignKey
- type Function
- type Index
- type Migration
- type MigrationRecord
- type MigrationStatus
- type OutOfOrderPolicy
- type QueryDef
- type QueryParam
- type QueryResult
- type Schema
- type SeedRecord
- type SeedRunResult
- type SeedStatus
- type SkippedFile
- type Snapshot
- type SourceFormat
- type Table
- type View
Constants ¶
const ( DefaultMigrationsDir = ".peng/migrations" DefaultSeedsDir = ".peng/seeds" DefaultQueriesDir = ".peng/queries" // DefaultBinDir is where `peng build` writes the compiled // seed-runner binary that `peng seed apply` exec()s. DefaultBinDir = ".peng/bin" )
Default paths used when Config fields are zero. The bookkeeping table names (peng_migrations, peng_seeds) are fixed in v1.
The .peng/ directory is dot-prefixed because it is a self-contained Go module (own go.mod) consumed by the peng CLI, not by the parent project. Nothing in the parent module needs to import packages from inside .peng/, so Go's "skip dot-prefixed dirs" rule does not bite.
const ( // CreateMigrationUpTemplate is the default body written to // <version>_<name>.up.sql when no UpSQL override is provided. CreateMigrationUpTemplate = "-- Migration: %s (up)\n\n\n" // CreateMigrationDownTemplate is the default body written to // <version>_<name>.down.sql when no DownSQL override is provided. CreateMigrationDownTemplate = "-- Migration: %s (down)\n\n\n" )
const (
DialectPostgres = "postgres"
)
Supported dialects.
const SchemaWireVersion = 1
SchemaWireVersion is the current Schema.Version emitted by peng. peng-tui checks this and errors if it's higher than what it knows.
const SeedRunnerBinName = "seed-runner"
SeedRunnerBinName is the unsuffixed seed-runner binary name written by `peng build`. Cross-compile targets append `-<os>-<arch>` so multiple arches can coexist under .peng/bin/.
const SeedRunnerModule = "peng-cli"
SeedRunnerModule is the `module` line written to .peng/go.mod by `peng init`. The name is intentionally generic — .peng/ is its own world, decoupled from the parent project's module path.
Variables ¶
var ErrInvalidMigrationName = errors.New("invalid migration name")
ErrInvalidMigrationName is returned when CreateMigration is called with a name that doesn't match MigrationNamePattern.
var MigrationNamePattern = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)
MigrationNamePattern is the allowed shape of migration names. Names must start with a lowercase letter and contain only lowercase letters, digits, and underscores. Mirrors goose / dbmate conventions and lets the generated filename be safe across filesystems.
Functions ¶
This section is empty.
Types ¶
type AdoptScan ¶
type AdoptScan struct {
Format SourceFormat
Migrations []Migration
Skipped []SkippedFile
}
AdoptScan is the result of scanning another tool's migration folder. Migrations is sorted by version ascending and ready to hand to WriteMigrationPair; Skipped explains anything that didn't import.
func ScanSource ¶
ScanSource reads a source migration directory, detects which tool produced it, and parses the migrations into peng's Migration shape. It never writes anything. A non-existent dir is an error (unlike CollectMigrations) because adopting a missing folder is always a user mistake worth surfacing.
func (*AdoptScan) Importable ¶
Importable reports whether the scan can be imported wholesale: the format was recognized, at least one migration parsed, and no fatal incompatibility was found. When false, callers fall back to a baseline snapshot.
type ApplySeedOption ¶
type ApplySeedOption func(*ApplySeedOpts)
ApplySeedOption configures one or more seed applications.
func WithForce ¶
func WithForce() ApplySeedOption
WithForce makes ApplySeed / ApplyAllSeeds run a seed even when it has already been recorded as applied.
type ApplySeedOpts ¶
type ApplySeedOpts struct {
Force bool
}
ApplySeedOpts is the resolved state of zero or more ApplySeedOption values. Dialect packages call ResolveApplySeedOpts to read the resolved options rather than re-implementing the option pattern for each dialect.
func ResolveApplySeedOpts ¶
func ResolveApplySeedOpts(opts []ApplySeedOption) ApplySeedOpts
ResolveApplySeedOpts collapses option functions into a struct. Dialect packages call this on their public ApplySeed methods.
type Column ¶
type Column struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
Default string `json:"default,omitempty"`
IsPK bool `json:"is_pk,omitempty"`
Comment string `json:"comment,omitempty"`
}
Column is one column of a table or view.
type Config ¶
Config holds the static settings for a Provider. It excludes runtime concerns like the *sql.DB connection, which is passed separately to NewProvider.
func (*Config) ApplyDefaults ¶
func (c *Config) ApplyDefaults()
ApplyDefaults fills in default values for unset Config fields. Exported so dialect packages can call it from their constructors.
type CreateMigrationOptions ¶
type CreateMigrationOptions struct {
// UpSQL overrides the empty up.sql template body. Used by the REST
// API surface to create+populate a migration in one call.
UpSQL string
// DownSQL overrides the empty down.sql template body.
DownSQL string
// Now lets tests inject a deterministic clock. When nil,
// time.Now().UTC() is used.
Now func() time.Time
}
CreateMigrationOptions tunes CreateMigration's behavior.
type DriftIssue ¶
DriftIssue is one detected drift condition.
type DriftKind ¶
type DriftKind int
DriftKind discriminates the three drift conditions peng detects.
const ( // DriftChecksumChanged: an applied migration's file has been // modified since it was applied. DriftChecksumChanged DriftKind = iota // DriftOrphaned: an applied row in peng_migrations has no // matching .up.sql on disk. DriftOrphaned // DriftOutOfOrder: a pending migration's version is older than // the current head. DriftOutOfOrder )
type DriftReport ¶
type DriftReport struct {
Issues []DriftIssue
}
DriftReport collects every drift issue found in a single check. Issues is empty when no drift is detected.
type Enum ¶
type Enum struct {
Schema string `json:"schema"`
Name string `json:"name"`
Values []string `json:"values"`
}
Enum is an enum type.
type ForeignKey ¶
type ForeignKey struct {
Name string `json:"name"`
FromTable string `json:"from_table"` // qualified: "schema.name"
FromColumns []string `json:"from_columns"`
ToTable string `json:"to_table"` // qualified
ToColumns []string `json:"to_columns"`
OnDelete string `json:"on_delete,omitempty"` // CASCADE / SET NULL / NO ACTION / RESTRICT
OnUpdate string `json:"on_update,omitempty"`
}
ForeignKey is one referential constraint.
type Function ¶
type Function struct {
Schema string `json:"schema"`
Name string `json:"name"`
Args []FnArg `json:"args,omitempty"`
// Signature is the raw argument list as the engine displays it
// (e.g. "order_id bigint, currency text DEFAULT 'USD'") — used
// when Args isn't populated structurally. Display-only.
Signature string `json:"signature,omitempty"`
Returns string `json:"returns,omitempty"` // empty for procedures
Body string `json:"body,omitempty"`
}
Function is a stored function or procedure.
type Index ¶
type Index struct {
Name string `json:"name"`
Columns []string `json:"columns"`
Unique bool `json:"unique,omitempty"`
Method string `json:"method,omitempty"` // e.g. "btree", "gin"
}
Index is a non-PK index. PRIMARY KEY constraints are reported via Table.PrimaryKey, not duplicated here.
type Migration ¶
type Migration struct {
Version string
Name string
UpSQL string
DownSQL string
UpPath string
DownPath string
}
Migration is a single pairing of an up.sql and down.sql file under the migrations directory. SQL bodies are loaded lazily by the collector; the paths are retained for diagnostic messages.
func CollectMigrations ¶
CollectMigrations walks dir for files matching <version>_<name>.(up|down).sql, pairs them by stem, errors if either half of a pair is missing, and returns the loaded migrations sorted by version ascending. A non-existent dir returns an empty slice with no error so callers can treat it as "no migrations yet" without a filesystem precheck.
func CreateMigration ¶
func CreateMigration(dir, name string, opts *CreateMigrationOptions) (Migration, error)
CreateMigration writes a timestamped migration pair under dir and returns the resulting Migration (with paths + bodies populated).
The version is the current UTC time formatted as "20060102150405". If the down.sql write fails after up.sql succeeded, up.sql is removed best-effort so the caller doesn't see a partial pair on disk.
Hoisted from cli/create.go so the REST/IPC server can expose `POST /migrations` without shelling out to the CLI binary.
func WriteMigrationPair ¶
WriteMigrationPair writes m as <version>_<name>.up.sql and .down.sql under dir, preserving m.Version (unlike CreateMigration, which stamps a fresh timestamp). It returns m with UpPath/DownPath populated. If the down write fails after the up succeeded, the up file is removed best-effort so no half-pair is left on disk.
type MigrationRecord ¶
type MigrationRecord struct {
Version string
Name string
AppliedAt time.Time
DurationMs int64
// Checksum is the SHA-256 (hex) of the up.sql content at the
// time the migration was applied. Empty for migrations applied
// by versions of peng before checksums were tracked.
Checksum string
}
MigrationRecord is one row in a dialect's peng_migrations table. Dialect Store implementations construct and return these.
type MigrationStatus ¶
type MigrationStatus struct {
Migration Migration
Applied bool
AppliedAt time.Time
DurationMs int64
// AppliedChecksum is the SHA-256 (hex) recorded at apply time.
// Empty when Applied is false or when the migration predates
// checksum tracking.
AppliedChecksum string
// CurrentChecksum is the SHA-256 (hex) of the current up.sql
// content on disk.
CurrentChecksum string
// ChecksumChanged is true when the migration is applied and the
// recorded checksum differs from the current file's checksum.
// Indicates someone edited the migration after it was applied.
ChecksumChanged bool
// OutOfOrder is true when the migration is pending and its
// version is older than the current head (the largest applied
// version). Indicates a rebase or branch-merge-out-of-order
// situation.
OutOfOrder bool
}
MigrationStatus combines a Migration with its applied state. The fields are dialect-agnostic; each dialect's Provider returns the same struct shape from its Status method.
type OutOfOrderPolicy ¶
type OutOfOrderPolicy int
OutOfOrderPolicy controls what Provider.Up does when it discovers a pending migration whose version is older than the current head.
const ( // OutOfOrderError (default): Up errors before applying any // out-of-order pendings. OutOfOrderError OutOfOrderPolicy = iota // OutOfOrderWarn: Up logs a warning and applies the migration // anyway. The peng_migrations row records applied_at = now() // so the audit trail captures the apply. OutOfOrderWarn )
type QueryDef ¶
type QueryDef struct {
Name string
Description string
Params []QueryParam
SQL string // raw SQL with @param references
}
QueryDef is a parsed .peng/queries/<name>.sql file.
func DiscoverQueries ¶
DiscoverQueries walks dir for *.sql files and parses each one. Returns them sorted alphabetically by name. A non-existent dir returns an empty slice with no error.
func ParseQueryFile ¶
ParseQueryFile reads a .peng/queries/<name>.sql file and returns the parsed QueryDef.
File format:
-- description: one-line summary -- @param_name type Human-readable description -- @other_param text Another param SELECT ... WHERE col = @param_name AND other = @other_param;
The header block is any leading run of comment lines. The first blank line ends the header; everything after it is the SQL body.
type QueryParam ¶
type QueryParam struct {
Name string
Type string // optional type hint (e.g. "uuid", "text")
Description string
}
QueryParam is one @name entry documented in a query file's header.
type QueryResult ¶
QueryResult holds the output rows from RunQuery.
type Schema ¶
type Schema struct {
// Version of the wire format. Bumped on breaking shape changes;
// additive fields keep the same version.
Version int `json:"version"`
// Dialect identifies the engine (postgres, sqlite, ...).
Dialect string `json:"dialect"`
Tables []Table `json:"tables"`
Views []View `json:"views,omitempty"`
Functions []Function `json:"functions,omitempty"`
Enums []Enum `json:"enums,omitempty"`
ForeignKeys []ForeignKey `json:"foreign_keys"`
}
Schema is the dialect-agnostic structural model of a database used by `peng tui` and by peng-tui over the JSON wire protocol. Each dialect implements its own SchemaModel(ctx) method returning this shape.
Phase 0 populates Tables, ForeignKeys. Views, Functions, Enums, and per-Index detail can be filled in later — peng-tui tolerates empty slices.
type SeedRecord ¶
SeedRecord is one row in a dialect's peng_seeds table.
type SeedRunResult ¶
SeedRunResult describes the outcome of an ApplySeed call. Applied is false when a previously-applied seed was skipped (no WithForce).
type SeedStatus ¶
type SeedStatus struct {
Version string
Name string
Applied bool
AppliedAt time.Time
DurationMs int64
}
SeedStatus combines a registered seed's identity (version + name) with its applied state. The seed's Run function is dialect-specific and lives in the dialect package, so it's not exposed here.
type SkippedFile ¶
SkippedFile records a source file ScanSource could not import cleanly. Fatal marks an incompatibility that makes the whole folder un-importable (the caller should fall back to a schema-dump baseline); non-fatal entries are advisory — e.g. a synthesized empty down for a golang-migrate migration that shipped without one.
type Snapshot ¶
type Snapshot struct {
// Dialect identifies which database engine produced this snapshot.
// SchemaApply refuses cross-dialect application.
Dialect string
// HeadVersion is the largest version in peng_migrations at
// dump time. SchemaApply backfills bookkeeping rows for every
// migration with version <= HeadVersion.
HeadVersion string
// DumpedAt is the wall-clock time the snapshot was produced.
DumpedAt time.Time
// DBVersion is the source server version string, captured for
// diagnostic value.
DBVersion string
// SQL is the schema-only DDL body. peng bookkeeping tables
// (peng_migrations, peng_seeds) are NOT included — the
// snapshot is the user schema only.
SQL string
}
Snapshot is a point-in-time schema dump produced by `peng schema dump`. Applying a snapshot fast-forwards a fresh database to the state it had when the snapshot was taken, then backfills peng_migrations rows for every migration up through HeadVersion so subsequent `peng up` runs only apply post-snapshot migrations.
Snapshots are NOT migrations. They are a separate concept that preserves the immutability invariant migrations rely on (see #5 drift detection — migrations are write-once because their checksums are tracked). Snapshots may be regenerated at any time.
func ReadSnapshot ¶
ReadSnapshot loads a snapshot from path, parsing the peng: header. Returns an error if the required headers (dialect, head_version) are missing.
type SourceFormat ¶
type SourceFormat int
SourceFormat identifies the migration tool that produced a folder scanned by ScanSource.
const ( // FormatUnknown means ScanSource could not recognize the folder as // any supported tool's migration layout. FormatUnknown SourceFormat = iota // FormatGolangMigrate is golang-migrate's layout: // <version>_<name>.up.sql + <version>_<name>.down.sql. Identical to // peng's own format, so import is a near-verbatim copy. FormatGolangMigrate // FormatGoose is goose's layout: a single <version>_<name>.sql file // per migration with `-- +goose Up` / `-- +goose Down` sections (or // a Go-based <version>_<name>.go file, which can't be translated). FormatGoose )
func (SourceFormat) String ¶
func (f SourceFormat) String() string
String returns the tool name, suitable for CLI output.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cli implements the peng command-line interface.
|
Package cli implements the peng command-line interface. |
|
cmd
|
|
|
peng
command
Command peng is the peng CLI entry point.
|
Command peng is the peng CLI entry point. |
|
Package eventbus is a minimal in-process publish/subscribe channel for server-side change events (migration_added, schema_changed, etc.).
|
Package eventbus is a minimal in-process publish/subscribe channel for server-side change events (migration_added, schema_changed, etc.). |
|
Package generator handles code generation for peng-managed artifacts: the per-seed sqlc.yaml that drives query.sql.go generation, the sqlc subprocess invocation, and (in a later milestone) the auto-generated seed registry.go.
|
Package generator handles code generation for peng-managed artifacts: the per-seed sqlc.yaml that drives query.sql.go generation, the sqlc subprocess invocation, and (in a later milestone) the auto-generated seed registry.go. |
|
internal
|
|
|
modpath
Package modpath locates the nearest go.mod above a directory and extracts its module declaration.
|
Package modpath locates the nearest go.mod above a directory and extracts its module declaration. |
|
queries/postgres
Package postgres implements peng's store.Store interface for PostgreSQL, backed by sqlc-generated query code.
|
Package postgres implements peng's store.Store interface for PostgreSQL, backed by sqlc-generated query code. |
|
Package pg provides peng's PostgreSQL dialect.
|
Package pg provides peng's PostgreSQL dialect. |
|
Package server is peng's transport-agnostic request mux.
|
Package server is peng's transport-agnostic request mux. |
|
handlers
Package handlers wires up route registrations for the peng server.
|
Package handlers wires up route registrations for the peng server. |
|
transport
|
|
|
httpx
Package httpx wraps a server.Mux as an http.Handler.
|
Package httpx wraps a server.Mux as an http.Handler. |
|
ipc
Package ipc implements peng's framed JSON-RPC transport for embedding hosts like peng-tui.
|
Package ipc implements peng's framed JSON-RPC transport for embedding hosts like peng-tui. |