AGENT SKILL

postgres-best-practices

Postgres schema and query rules, independent of the calling language. TRIGGER — load BEFORE writing or editing a .sql file, a migration, or any schema, index, constraint or query change against Postgres: new tables, foreign keys, status columns, claim or lease queries, backfills, ON CONFLICT upserts. SKIP for MySQL, SQLite and other engines, and for code that never touches SQL.

License 许可
MIT
最近更新
2026-11-11
Stars
★ 0
Forks
0

01 前言 Front Matter

name
postgres-best-practices
description
Postgres schema and query rules, independent of the calling language. TRIGGER — load BEFORE writing or editing a .sql file, a migration, or any schema, index, constraint or query change against Postgres: new tables, foreign keys, status columns, claim or lease queries, backfills, ON CONFLICT upserts. SKIP for MySQL, SQLite and other engines, and for code that never touches SQL.

02 资源文件 5

03 SKILL.md 正文

Postgres

Rules for schema and queries on Postgres. They are language-agnostic; the Go half lives in the go-idiomatic skill (references/database.md, references/patterns.md).

Where to Look

If you're working on… Load this
Go code that runs these queries (database/sql, pgx) go-idiomatic references/database.md
A closed value set mirrored as a Go type go-idiomatic references/patterns.md § Typed Value Sets
Writing or changing a migration, any tool references/migrations.md
Converting an existing Postgres ENUM to text + CHECK references/migrations.md § ENUM Conversion
Migrations in a repo that uses goose references/goose.md (only when the repo uses goose)
Only if the repo has sqlc.yaml: queries, config, CI references/sqlc.md

Schema

  • Closed value sets are text + a named CHECK, never a Postgres ENUM. Adding a value to an ENUM is a type migration and removing one is not supported; a CHECK is one ALTER. Never mix the two styles in one schema, and never leave a status column unchecked. An existing ENUM is a conversion to schedule, not a style to extend.
  • Every foreign key column has an index. Postgres does not create one. Without it, deleting a parent scans the child table, and ON DELETE CASCADE turns that into one scan per row.
  • timestamptz for instants, date only for calendar dates. Never store times as text.
  • jsonb for JSON, never text.
  • Name every constraint and index, with one scheme (<table>_<cols>_key, _fkey, _check, <table>_<cols>_idx). Names are what errors and migrations refer to.
  • One meaning for absence per column. NULL or '', not both. Pick NULL unless the column is NOT NULL DEFAULT '' by design.
  • An append-only table gets a retention policy in the migration that creates it. Outboxes, run logs and audit trails only grow; decide how long rows live, index the column the cleanup filters on, and delete expired rows in batches (DELETE ... WHERE id IN (SELECT id ... LIMIT n)) from a scheduled job. Added later, the first cleanup is one huge delete on a huge table.

Queries

  • One clock per comparison. DEFAULT now() is fine for audit columns. Any time the application compares against, or a test asserts on, is passed in as a parameter. Mixing SQL NOW() with the app's time.Now() makes leases and expiries untestable and skewed.
  • Explicit column lists. No SELECT * and no RETURNING *; adding a column must not change what callers receive.
  • Every list query has a hard LIMIT. A list without one returns the whole table once the table grows. On growing tables page by keyset (WHERE (created_at, id) < ($1, $2) ORDER BY created_at DESC, id DESC LIMIT $3), not OFFSET, which reads and discards every skipped row.
  • No N+1 loops. Fetch with = ANY($1::uuid[]), insert with multi-row VALUES or COPY.
  • Claiming work uses FOR UPDATE SKIP LOCKED so concurrent workers take different rows instead of queueing on one. Update the claimed row in the same statement or transaction.
  • Read-then-write uses SELECT ... FOR UPDATE in a transaction. A plain read races.
  • Match unique violations (SQLSTATE 23505) by constraint name, not by message text or by assuming which constraint fired. This is why constraints are named.
  • ON CONFLICT names its target (ON CONFLICT (col) or ON CONSTRAINT name). A bare ON CONFLICT DO NOTHING silently swallows violations of constraints you did not mean.

Building SQL

  • Never build SQL, or shell that carries SQL, with fmt.Sprintf or string concatenation. Pass values as bind parameters ($1) or, for a script, as environment variables the script reads. Pass identifiers through pgx.Identifier{"schema", "table"}.Sanitize(). Interpolating even an int starts the habit that becomes injection.

A container entrypoint is a static script that reads its value from the environment (IDLE_TIMEOUT set via the container's Env):

//go:embed entrypoint.sh
var entrypoint string
psql -v t="$IDLE_TIMEOUT" -c "ALTER SYSTEM SET idle_in_transaction_session_timeout = :'t'"

Altering tables

  • Add columns nullable or with a constant default, then backfill, then add NOT NULL. Never rewrite a large table inside the migration that adds the column.
  • Backfill in batches, keyed on the primary key, committing between batches. One giant UPDATE holds locks and bloats the table.
  • Create indexes on live tables with CREATE INDEX CONCURRENTLY, outside a transaction.
  • Add constraints in two steps on big tables: ADD CONSTRAINT ... NOT VALID, then VALIDATE CONSTRAINT, which does not block writes.
  • Migrations are forward-only once merged. Fix mistakes with a new migration.

04 同仓库更多 Skills

O-Marsters-1997
O-Marsters-1997/my-claude-code

vault-recall

Answer the user's conceptual or technical question using their Obsidian vault as well as general knowledge. Use when the user asks a question (not a task) whose topic appears in the vault MOC digest from session start. Not for task instructions, not mid-task, and not for writing to the vault (that is to-vault).

★ 0
O-Marsters-1997
O-Marsters-1997/my-claude-code

ingest

Ingest one external source (an article, course lesson, doc, PDF or URL) into the user's Obsidian vault: save the original to raw/, then build wiki/ pages per the vault's § Ingest. Use when the user types /ingest, or says "ingest this", "add this source to my wiki", "put this article/lesson in the vault as a wiki page". Works from any repo. Routing rule: someone else's words go to wiki/ (this skill); the user's own learnings and decisions go to notes/ (/to-vault).

★ 0
O-Marsters-1997
O-Marsters-1997/my-claude-code

to-tickets

Breaks a source into independently-grabbable GitHub tickets. For a code feature the source is the plan (`docs/plans/<feature>.md`, from /to-plan) and every new or changed symbol lands in exactly one ticket, its signatures copied into the ticket body; a non-code feature is cut from the PRD's slices. Cuts tickets for one feature per run. If a code feature has no approved plan, offers /to-plan instead of cutting from prose. In a repo that runs command-centre, also emits `[[task]]` blocks into its config; in a plain treepad repo, emits a Batch Manifest instead so the tickets materialise as stacked worktrees.

★ 0
O-Marsters-1997
O-Marsters-1997/my-claude-code

to-seams

Records an agreed-but-unlanded cross-repo interface as a seam: the file a consumer's agent gets pasted into its prompt as ground truth, plus the `[[seam]]` block command-centre reads for the retirement pointer. Use when two repos need to build against an interface neither has landed yet — "record a seam for X", "we agreed on this schema, seam it", "add a seam so Y can build against Z's new field". Halts for approval before writing either file.

Skill与智能体开发
★ 0
O-Marsters-1997
O-Marsters-1997/my-claude-code

to-roadmap

Prioritises settled features into a Now / Next / Later kanban board, rendered as a self-contained ./ideas/roadmap.html. Each card is a feature, not a ticket — one card later becomes several tickets via /to-prd -> /to-plan -> /to-tickets. Boards the features in ./docs/approach.md, or the "## Accepted ideas" in ./ideas/CONTEXT.md when there is no approach doc. The approach doc is settled truth: this assigns tiers and reasons about ordering, it never re-litigates what was decided.

产品经理
★ 0
O-Marsters-1997
O-Marsters-1997/my-claude-code

to-prd

Turns one feature idea into a PRD built on journeys: a one-line problem, journeys (each ending in an observable state) and delivery slices that say what is usable after each. Interviews, explores the codebase, and saves to docs/prds/<feature>.md. Covers exactly one feature: given a multi-feature source, ask which one. Reads any approach doc or ideate report as priors so it doesn't re-ask what's settled, but needs none. Starts the per-feature spine (/to-prd -> /to-plan -> /to-tickets).

产品经理
★ 0