agentleFS
andr-caandr-ca/agentharness/.claude/skills/database-conventions/SKILL.md

database-conventions

Skill1 starsChanged 2 months agoMITView on GitHub

Use when designing a database schema, writing migrations, reviewing SQL queries, or choosing between relational and document models — covers naming, index strategy, migration safety, N+1 prevention, and transaction boundaries.

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 5 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every table has a primary key.

    line 31
  2. Very specific:

    Always store timestamps with timezone (TIMESTAMPTZ in Postgres).

    line 34
  3. Very specific:

    Avoid TEXT for constrained values — use an ENUM or a lookup table so the DB enforces the constraint.

    line 37
  4. Distinctive:

    Every foreign key column gets an index (Postgres doesn't auto-index FKs).

    line 58
  5. Very specific:

    Add an index on any column used in a WHERE, ORDER BY, or JOIN in frequently-run queries.

    line 59
  6. Distinctive:

    Don't index every column — indexes slow writes and consume storage.

    line 63
  7. Very specific:

    Every migration is reversible — provide a down migration.

    line 82
  8. Distinctive:

    Use transactions for any multi-step operation where partial completion would leave the data in an inconsistent state.

    line 122
  9. Distinctive:

    Keep transactions as short as possible — hold locks for the minimum time.

    line 137
  10. Distinctive:

    Don't make external HTTP calls inside a transaction.

    line 138
  11. Very specific:

    Read-only queries don't need transactions (but REPEATABLE READ isolation may be appropriate for consistent multi-query reads).

    line 139

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Database Conventions

Design and operational guidelines for relational databases (PostgreSQL primary dialect; MySQL/SQLite notes included where they differ). Apply these when creating schemas, reviewing migrations, or writing queries.


Naming

  • Tables: snake_case, plural (users, audit_logs, order_items)
  • Columns: snake_case, singular (user_id, created_at, is_active)
  • Primary keys: id (serial/UUID)
  • Foreign keys: <referenced_table_singular>_id (user_id, order_id)
  • Booleans: is_ or has_ prefix (is_active, has_verified_email)
  • Timestamps: created_at, updated_at, deleted_at (UTC, with timezone)

Schema design

  • Every table has a primary key. Prefer UUID (gen_random_uuid()) for distributed or externally-exposed IDs; serial/bigserial for internal join tables where IDs never leave the DB.
  • Always store timestamps with timezone (TIMESTAMPTZ in Postgres).
  • Soft-delete via deleted_at IS NULL filter, not DELETE — unless you have a clear data retention policy that requires hard deletes.
  • Avoid TEXT for constrained values — use an ENUM or a lookup table so the DB enforces the constraint.
-- Good: constrained status field
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');

CREATE TABLE orders (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id     UUID NOT NULL REFERENCES users(id),
    status      order_status NOT NULL DEFAULT 'pending',
    total_cents INTEGER NOT NULL CHECK (total_cents >= 0),
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Indexes

  • Every foreign key column gets an index (Postgres doesn't auto-index FKs).
  • Add an index on any column used in a WHERE, ORDER BY, or JOIN in frequently-run queries.
  • Composite indexes: column order matters — put equality conditions first, range/sort columns last.
  • Don't index every column — indexes slow writes and consume storage.
-- Required: FK index
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- Composite: user_id first (equality), created_at second (range/sort)
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);

Migrations

Never edit a migration that has been applied anywhere. Migrations are immutable once run — treat them like published git commits.

Rules:

  • Each migration does one logical change.
  • Every migration is reversible — provide a down migration.
  • Adding a column: always nullable or with a DEFAULT so existing rows are not blocked during migration.
  • Renaming a column: two-migration pattern (add new → backfill → drop old) to avoid downtime for live deploys.
  • Dropping a column or table: mark as NOT NULL removal / unused first, deploy, then drop in a subsequent release.
-- WRONG: adds NOT NULL column without a default → fails on existing rows
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) NOT NULL;

-- RIGHT: nullable first, add default/constraint in a follow-up migration
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);

N+1 queries

N+1 occurs when you load a list of N records and then execute 1 query per record to load related data.

# WRONG: N+1 — one query for users, then one per user for posts
users = db.query(User).all()
for user in users:
    posts = db.query(Post).filter_by(user_id=user.id).all()  # N queries

# RIGHT: eager load with a JOIN or subquery
users = db.query(User).options(joinedload(User.posts)).all()

Detect N+1 in development: log slow queries, use query count assertions in tests, or use a tool like sqlalchemy-utils's query counter.


Transactions

Use transactions for any multi-step operation where partial completion would leave the data in an inconsistent state.

# WRONG: transfer without transaction — debit can succeed while credit fails
account_a.balance -= amount
account_b.balance += amount

# RIGHT: wrap in a transaction
with db.begin():
    account_a.balance -= amount
    account_b.balance += amount

Rules:

  • Keep transactions as short as possible — hold locks for the minimum time.
  • Don't make external HTTP calls inside a transaction.
  • Read-only queries don't need transactions (but REPEATABLE READ isolation may be appropriate for consistent multi-query reads).

Query review checklist

  • All user-supplied values in parameterised queries (no string interpolation)
  • Foreign keys are indexed
  • SELECT * replaced with explicit column list in production queries
  • Pagination uses LIMIT + OFFSET or cursor-based (prefer cursor for large sets)
  • No N+1 pattern in loops
  • Multi-step writes wrapped in a transaction
  • Migrations are reversible and don't add NOT NULL columns without defaults

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.