[ { "id": 1, "name": "orm-vs-sqlx-pgx-recommendation", "description": "Tests whether the model recommends sqlx/pgx over ORMs and explains why ORMs are harmful", "prompt": "I'm starting a new Go web application with PostgreSQL. I need to set up database access. What library should I use? I've heard GORM is popular and has good docs. Can you set up the database layer for me?", "trap": "Without the skill, the model may recommend GORM since it's popular and widely known. The skill explicitly says to avoid ORMs and use sqlx or pgx.", "assertions": [ {"id": "1.1", "text": "Recommends sqlx or pgx instead of GORM"}, {"id": "1.2", "text": "Explains why ORMs are problematic (N+1 queries, unpredictable SQL generation, magic hooks, or debugging difficulty)"}, {"id": "1.3", "text": "Recommends pgx specifically for PostgreSQL-only projects due to performance advantage (30-50% faster)"}, {"id": "1.4", "text": "Does NOT set up GORM or ent as the primary database library"}, {"id": "1.5", "text": "Mentions that learning the ORM API is harder than learning SQL or that ORMs hide SQL"} ] }, { "id": 2, "name": "exec-vs-query-for-non-select", "description": "Tests the subtle rule that db.Query must NOT be used for statements that don't return rows", "prompt": "Write a Go function that deletes expired sessions from a PostgreSQL database. Use sqlx. The function should delete all sessions where expires_at < now() and return the number of deleted rows.", "trap": "The model may use db.QueryContext or db.Query for the DELETE statement, which returns *Rows that must be closed. The skill says to use db.Exec for statements that don't return rows.", "assertions": [ {"id": "2.1", "text": "Uses ExecContext (not QueryContext or Query) for the DELETE statement"}, {"id": "2.2", "text": "Uses the *Context variant (ExecContext, not Exec)"}, {"id": "2.3", "text": "Passes ctx to the database call"}, {"id": "2.4", "text": "Retrieves RowsAffected() from the result to return the count"}, {"id": "2.5", "text": "Uses parameterized query (not string concatenation)"} ] }, { "id": 3, "name": "nullable-column-handling", "description": "Tests whether pointer fields are preferred for NULLable columns over sql.NullXxx types", "prompt": "I have a PostgreSQL users table with columns: id (int), name (text NOT NULL), bio (text, nullable), deleted_at (timestamptz, nullable). Write a Go struct that I can use with sqlx for scanning and also marshal to JSON properly. The bio should be omitted from JSON when NULL, and deleted_at should appear as null in JSON.", "trap": "Without the skill, the model may use sql.NullString and sql.NullTime which require custom JSON marshaling. The skill recommends pointer fields as the cleanest approach.", "assertions": [ {"id": "3.1", "text": "Uses pointer types (*string for bio, *time.Time for deleted_at) rather than sql.NullString/sql.NullTime"}, {"id": "3.2", "text": "Includes db struct tags for sqlx (db:\"column_name\")"}, {"id": "3.3", "text": "Includes json struct tags"}, {"id": "3.4", "text": "Uses json:\"bio,omitempty\" for bio (omitted when NULL)"}, {"id": "3.5", "text": "Uses json:\"deleted_at\" without omitempty for deleted_at (appears as null)"} ] }, { "id": 4, "name": "connection-pool-configuration", "description": "Tests that connection pool settings are configured with all four parameters and reasonable values", "prompt": "Write a Go function that creates a new sqlx database connection to PostgreSQL and returns *sqlx.DB. Just the basic setup, nothing fancy.", "trap": "Without the skill, the model often returns the *sqlx.DB without configuring the connection pool. The skill requires all four pool settings.", "assertions": [ {"id": "4.1", "text": "Calls SetMaxOpenConns on the database connection"}, {"id": "4.2", "text": "Calls SetMaxIdleConns on the database connection"}, {"id": "4.3", "text": "Calls SetConnMaxLifetime on the database connection"}, {"id": "4.4", "text": "Calls SetConnMaxIdleTime on the database connection"}, {"id": "4.5", "text": "MaxIdleConns is less than or equal to MaxOpenConns"} ] }, { "id": 5, "name": "rows-close-and-err-check", "description": "Tests proper handling of rows iteration: defer Close and rows.Err() check after loop", "prompt": "Write a Go function using sqlx that lists all active users (active = true) from a users table. Return a slice of User structs. Use db.QueryContext, not db.SelectContext — I want to handle scanning manually for learning purposes.", "trap": "Without the skill, the model may forget to defer rows.Close() or skip the rows.Err() check after iteration, both of which are required.", "assertions": [ {"id": "5.1", "text": "Calls defer rows.Close() immediately after the QueryContext call (before the loop)"}, {"id": "5.2", "text": "Checks rows.Err() after the for rows.Next() loop completes"}, {"id": "5.3", "text": "Returns the error from rows.Err() if non-nil"}, {"id": "5.4", "text": "Uses QueryContext (not Query) with a context parameter"}, {"id": "5.5", "text": "Checks the error returned by QueryContext before proceeding"} ] }, { "id": 6, "name": "errnorows-handling-pattern", "description": "Tests proper sql.ErrNoRows handling with domain error translation", "prompt": "Write a Go function GetUserByEmail(ctx context.Context, db *sqlx.DB, email string) that returns the user or an appropriate error when not found. Use sqlx.GetContext.", "trap": "Without the skill, the model may return the raw sql.ErrNoRows error directly or not distinguish it from other errors. The skill requires translating to a domain error.", "assertions": [ {"id": "6.1", "text": "Uses errors.Is(err, sql.ErrNoRows) to check for not-found"}, {"id": "6.2", "text": "Returns a domain-specific error (e.g. ErrUserNotFound) when no rows, NOT the raw sql.ErrNoRows"}, {"id": "6.3", "text": "Wraps non-ErrNoRows errors with context using fmt.Errorf and %w"}, {"id": "6.4", "text": "Uses GetContext (not Get) with the ctx parameter"}, {"id": "6.5", "text": "Uses parameterized query placeholder ($1 or ?) not string concatenation"} ] }, { "id": 7, "name": "transaction-with-defer-rollback", "description": "Tests the correct transaction pattern: BeginTxx, defer Rollback, Commit", "prompt": "Write a Go function TransferFunds(ctx, db, fromAccountID, toAccountID string, amount int) that transfers money between two accounts atomically. Use sqlx.", "trap": "Without the skill, the model may forget defer tx.Rollback(), use wrong isolation level for financial operations, or miss SELECT FOR UPDATE.", "assertions": [ {"id": "7.1", "text": "Uses BeginTxx (or BeginTx) to start a transaction"}, {"id": "7.2", "text": "Calls defer tx.Rollback() immediately after BeginTxx"}, {"id": "7.3", "text": "Uses SELECT ... FOR UPDATE when reading balances to prevent race conditions"}, {"id": "7.4", "text": "Sets serializable or repeatable-read isolation level (financial operation)"}, {"id": "7.5", "text": "Calls tx.Commit() at the end of the successful path"} ] }, { "id": 8, "name": "dynamic-in-clause-with-rebind", "description": "Tests proper handling of dynamic IN clauses with sqlx.In and Rebind", "prompt": "Write a Go function that fetches users by a list of IDs from PostgreSQL using sqlx. The function receives a []int64 of user IDs.", "trap": "Without the skill, the model may try to use a raw IN ($1) placeholder or manually build the query string. The skill shows sqlx.In + Rebind pattern.", "assertions": [ {"id": "8.1", "text": "Uses sqlx.In() to expand the IN clause placeholders"}, {"id": "8.2", "text": "Calls db.Rebind() on the query after sqlx.In to adjust placeholders for the driver"}, {"id": "8.3", "text": "Passes the expanded args from sqlx.In to the query execution"}, {"id": "8.4", "text": "Uses a *Context method variant (SelectContext, QueryContext, etc.)"}, {"id": "8.5", "text": "Handles the error from sqlx.In"} ] }, { "id": 9, "name": "dynamic-column-name-allowlist", "description": "Tests that dynamic column names use allowlists, not direct interpolation", "prompt": "Write a Go function that lists users with a sortable column parameter. The caller specifies which column to sort by as a string. Use sqlx.", "trap": "Without the skill, the model may directly interpolate the sort column into the SQL string without validation, creating a SQL injection vector.", "assertions": [ {"id": "9.1", "text": "Validates the sort column against an explicit allowlist (map or slice of allowed column names)"}, {"id": "9.2", "text": "Returns an error if the column is not in the allowlist"}, {"id": "9.3", "text": "Does NOT directly pass the column name as a parameterized placeholder ($1) — column names cannot be parameterized"}, {"id": "9.4", "text": "Uses fmt.Sprintf or string concatenation ONLY after validation against the allowlist"}, {"id": "9.5", "text": "Uses a *Context method variant for the actual query"} ] }, { "id": 10, "name": "schema-creation-refusal", "description": "Tests that the model refuses to generate database schemas", "prompt": "Create a PostgreSQL schema for an e-commerce application with users, products, orders, and order_items tables. Include proper indexes, foreign keys, and constraints.", "trap": "Without the skill, the model will happily generate the full schema. The skill explicitly states AI must NOT generate database schemas.", "assertions": [ {"id": "10.1", "text": "Does NOT generate a complete CREATE TABLE schema"}, {"id": "10.2", "text": "Explains why AI-generated schemas are problematic (missing indexes, incorrect types, bad normalization, or need for production context)"}, {"id": "10.3", "text": "Recommends human review or dedicated database tooling for schema design"}, {"id": "10.4", "text": "Mentions that schema design requires understanding data volumes, access patterns, or production constraints"} ] }, { "id": 11, "name": "batch-processing-sweet-spot", "description": "Tests that batch operations use reasonable batch sizes, not row-by-row or one giant batch", "prompt": "Write a Go function that inserts 50,000 user records into PostgreSQL. Use sqlx. Optimize for speed.", "trap": "Without the skill, the model may insert all 50k in one statement or insert one-by-one. The skill recommends 100-1000 rows per batch.", "assertions": [ {"id": "11.1", "text": "Uses batching with a batch size between 100 and 1000 rows"}, {"id": "11.2", "text": "Does NOT insert all 50,000 rows in a single statement"}, {"id": "11.3", "text": "Does NOT insert one row at a time in a loop"}, {"id": "11.4", "text": "Uses NamedExecContext or a multi-row INSERT pattern"}, {"id": "11.5", "text": "Handles errors per batch with context about which batch failed"} ] }, { "id": 12, "name": "cursor-pagination-over-offset", "description": "Tests that cursor-based pagination is recommended over OFFSET for large datasets", "prompt": "Write a Go function that paginates through a large events table (millions of rows) ordered by created_at. The function should support page navigation.", "trap": "Without the skill, the model commonly uses OFFSET/LIMIT which degrades performance on deep pages. The skill requires cursor-based pagination.", "assertions": [ {"id": "12.1", "text": "Uses cursor-based pagination (WHERE created_at > $1) instead of OFFSET"}, {"id": "12.2", "text": "Explains why OFFSET is problematic (re-scans skipped rows, O(offset+limit))"}, {"id": "12.3", "text": "Uses LIMIT with ORDER BY for the page size"}, {"id": "12.4", "text": "Returns a cursor value (e.g. the last created_at) for the next page"}, {"id": "12.5", "text": "Uses parameterized queries for the cursor value"} ] }, { "id": 13, "name": "integration-test-with-build-tags", "description": "Tests proper database integration test setup with build tags and transaction rollback", "prompt": "Write integration tests for a Go repository layer that interacts with PostgreSQL. I want to test that Create and GetByID work correctly together.", "trap": "Without the skill, the model may not use build tags or may not wrap tests in transactions for cleanup. The skill requires both.", "assertions": [ {"id": "13.1", "text": "Uses //go:build integration build tag to separate from unit tests"}, {"id": "13.2", "text": "Uses transaction-based test isolation (begin tx in setup, rollback in teardown)"}, {"id": "13.3", "text": "Does NOT test against a production database — uses a test DSN or testcontainers"}, {"id": "13.4", "text": "Uses testify/suite or a similar setup/teardown pattern"}, {"id": "13.5", "text": "Tests actual SQL correctness (not mocked — this is integration)"} ] }, { "id": 14, "name": "avoid-hidden-sql-features", "description": "Tests that the model avoids triggers, views, stored procedures in application code", "prompt": "I want to automatically update an 'updated_at' column every time a row is modified in my users table. Should I create a PostgreSQL trigger for this? Also, I have a complex query that joins 5 tables — should I create a database view?", "trap": "Without the skill, the model will likely recommend triggers and views as standard PostgreSQL features. The skill says to avoid hidden SQL features.", "assertions": [ {"id": "14.1", "text": "Advises against using triggers for updated_at in application code"}, {"id": "14.2", "text": "Recommends setting updated_at explicitly in Go code instead"}, {"id": "14.3", "text": "Advises against using views for the complex query"}, {"id": "14.4", "text": "Explains that hidden SQL features create invisible side effects or make debugging harder"}, {"id": "14.5", "text": "Recommends keeping SQL explicit and visible in Go code"} ] }, { "id": 15, "name": "pgx-copy-for-bulk-postgres", "description": "Tests knowledge of pgx COPY protocol for maximum PostgreSQL bulk insert throughput", "prompt": "I need to insert 100,000 rows into PostgreSQL as fast as possible. I'm already using pgx. What's the fastest approach?", "trap": "Without the skill, the model may suggest multi-row INSERT statements. The skill teaches pgx.CopyFrom using the binary COPY protocol for maximum throughput.", "assertions": [ {"id": "15.1", "text": "Recommends pgx.CopyFrom using the COPY protocol"}, {"id": "15.2", "text": "Shows pgx.CopyFromRows or pgx.CopyFromSlice usage"}, {"id": "15.3", "text": "Mentions that COPY is significantly faster than multi-row INSERT"}, {"id": "15.4", "text": "Uses pgx.Identifier for the table name"}, {"id": "15.5", "text": "Still recommends batching if the dataset is extremely large (to avoid memory issues)"} ] } ]