Claude-skill-registry data-access-patterns
Guidelines for database interactions using raw SQL and idiomatic Go patterns
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/data-access-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-data-access-patterns && rm -rf "$T"
manifest:
skills/data/data-access-patterns/SKILL.mdsource content
Data Access Patterns Skill
Use this skill when writing or modifying database access code in
internal/models/.
This project uses explicit SQL over ORMs for performance, readability, and fine-grained control.
Core Principles
- No ORM: Use
directly. Do NOT introduce or use any ORM.database/sql - PostgreSQL Driver: The project uses
(imported ingithub.com/lib/pq
).internal/db/db.go - Raw SQL: Write explicit, readable SQL queries. Use PostgreSQL-specific features (JSONB, ON CONFLICT) and
placeholders.$1, $2 - Transactional Integrity: Use
when multiple operations must complete together or fail (atomicity).*sql.Tx - Separation of Concerns: Keep database models and access logic in
. Repository functions should focus on data retrieval and persistence.internal/models/
Repository Function Naming
| Pattern | Purpose | Example Signature |
|---|---|---|
| Retrieve a single entity | |
| Filtered retrieval | |
| Insert or update one record | |
| Batch insert/update | |
| Clean up records | |
Writing Efficient Queries
JSONB for Complex Trees
When fetching an entity with nested children (e.g., a weapon with many slots), use
jsonb_agg and jsonb_build_object to minimize round-trips and simplify Go-side scanning.
// Example: Single query to fetch weapon and all its slots query := ` SELECT w.name, w.item_id, jsonb_agg(jsonb_build_object( 'slot_id', ws.slot_id, 'name', ws.name )) as slots FROM weapons w JOIN slots ws ON w.item_id = ws.item_id WHERE w.item_id = $1 GROUP BY w.name, w.item_id;`
Idempotent Writes (ON CONFLICT
)
ON CONFLICTPrefer
ON CONFLICT for upsert operations to ensure idempotency and handle existing records gracefully.
query := ` INSERT INTO weapons (item_id, name) VALUES ($1, $2) ON CONFLICT (item_id) DO UPDATE SET name = EXCLUDED.name;`
Transaction Management Checklist
When implementing writes:
- Composite Writes: If a function calls multiple other write functions (e.g.,
callingUpsertWeapon
), it MUST accept aupsertManySlot
.*sql.Tx - Top-Level Orchestration: Start the transaction at the highest possible level (usually in the importer or service layer).
- Defer Rollback: Defer a rollback immediately after starting a transaction to prevent leaks on error.
tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() // Safe: does nothing if committed if err := UpsertManyWeapon(tx, weapons); err != nil { return err } return tx.Commit()
Developer Best Practices
- ✅ Explicit Scanning: Scan rows into structs carefully; match types exactly with the DB schema.
- ✅ Resource Management:
immediately after adefer rows.Close()
call.Query - ✅ Strict Errors: Check errors after
AND after the loop withrows.Scan()
.rows.Err() - ✅ Clean Signatures: Pass
for read-only operations and*sql.DB
for multi-step write operations.*sql.Tx - ❌ **Avoid SELECT ***: Explicitly list required columns to prevent breakage from schema changes.
- ❌ No Global DB: Pass the database handle as an argument.