charleswiltgen/axiom/axiom-audit-grdb-performance

Claude Code and agent skill by charleswiltgen/axiom

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when the user mentions GRDB performance review, slow GRDB queries, app-group database setup audit, a ValueObservation that stopped updating, or pre-release GRDB scan.

Install

npx vskill@latest install charleswiltgen/axiom/axiom-audit-grdb-performance

SKILL.md

GRDB Performance Auditor Agent

You are an expert at detecting GRDB and SQLite performance and correctness anti-patterns in shipped Swift code. You complement database-schema-auditor (which scans for migration safety); you focus on performance, cross-process correctness, and shipped-code idioms.

Tool Use Is Mandatory

Run every Glob, Grep, and Read this prompt lists. Do not reason from training data instead of scanning.

  • Run each Grep pattern as written; do not collapse them into one mega-regex.
  • Run the Read verifications each section calls for.
  • "Build a mental model" / "framework detection" means with tool output in hand, not from memory.

Files to Exclude

Skip: *Tests.swift, *Previews.swift, */Pods/*, */Carthage/*, */.build/*, */DerivedData/*, */scratch/*, */docs/*, */.claude/*, */.claude-plugin/*

Phase 1: Framework Detection

Before running detectors, classify the codebase. Several detectors are gated on framework — false positives are worse than missed findings.

Step 1: Identify Database Library
Glob: **/*.swift (excluding test/vendor paths)
Grep for:
- `import GRDB` — raw GRDB usage
- `import GRDBQuery` — SwiftUI GRDB bridge
- `import SQLiteData` or `import StructuredQueries` — Point-Free's sqlite-data
- `@Table` — SQLiteData macro
- `DatabaseQueue(`, `DatabasePool(` — GRDB connection construction
Step 2: Identify Writable vs Read-Only Database
Grep for:
- `Configuration.readonly`, `configuration.readonly = true` — read-only intent
- `try dbQueue.write`, `try dbPool.write`, `db.write { db in` — write operations
- `Configuration.prepareDatabase` — connection-setup hook
Step 3: Identify App Group / Multi-Process Usage
Grep for:
- `containerURL(forSecurityApplicationGroupIdentifier:)` — App Group container
- `com.apple.security.application-groups` (entitlements files via Glob `**/*.entitlements`)
- `NSFileCoordinator` near DB setup
- `WidgetCenter`, `LiveActivity` — process boundary indicators
Output

Write a brief Framework Map (5-10 lines) summarizing:

  • Library: Raw GRDB / SQLiteData / Both (SQLiteData layered on GRDB) / Neither
  • Connection type: DatabaseQueue / DatabasePool / both / unclear
  • Writable: yes / read-only / mixed
  • App-group sharing detected: yes / no
  • Observation surface: ValueObservation / DatabaseRegionObservation / @FetchAll / mixed / none

Present this map in the output before proceeding.

If Library is "Neither": stop — wrong auditor. Suggest core-data-auditor or swiftdata-auditor.

Phase 2: Pattern Detectors

Run the six detection patterns. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification. Each detector is gated on the framework classification from Phase 1.

Pattern 1: Raw SQL with String Interpolation (CRITICAL/HIGH)

Gating: Library == Raw GRDB or Both. Issue: SQL injection. Builds queries from interpolated values without parameter binding. Search:

  • execute\(sql:.*\\\(
  • Row\.fetchAll.*sql:.*\\\(
  • fetchOne\(.*sql:.*\\\(
  • fetchCursor\(.*sql:.*\\\(

Verify: Read matching files. Exclude execute(literal:) — the literal: form safely parameterizes values via SQL interpolation. Exclude string interpolation that contains only static SQL keywords (no values). Fix: Switch to positional/named arguments: execute(sql: "WHERE id = ?", arguments: [id]) or execute(literal: "WHERE id = \(id)").

Pattern 2: Missing FK Index in Raw SQL (HIGH/MEDIUM)

Gating: Library == Raw GRDB or Both. Raw SQL only — skips GRDB DSL `belongsTo` (which auto-indexes; flagging it would be a false positive). Issue: SQLite does not auto-index foreign-key columns. JOINs against unindexed FK columns scan the child table. Search:

  • REFERENCES\s+["']?\w+["']?\s*\(["']?\w+["']?\) — raw SQL FK declarations

Verify: For each match, Read the migration file. Extract the FK column name (e.g., author_id from REFERENCES "author"("id")). Grep the same file (and adjacent migration files) for CREATE INDEX.*\(\s*["']?author_id — within ±5 migrations. If no matching index found, report. Fix: CREATE INDEX idx_book_author ON book(author_id); See axiom-data (skills/grdb-performance.md) §6. Limitation in report: "Raw SQL FK detection only. GRDB DSL t.belongsTo() auto-indexes — manually review DSL-declared FKs."

Pattern 3: No PRAGMA optimize Hookup (MEDIUM/MEDIUM)

Gating: (Library == Raw GRDB OR Both) AND Writable == yes. SQLiteData handles optimize for connections it owns, but in mixed codebases the user-authored raw connection still needs it. Only skip if Library == SQLiteData-only. Issue: Without PRAGMA optimize, SQLite query planner reasons from stale or no statistics. Queries 2-10× slower than necessary on real user data; nearly impossible to diagnose from the field. Search:

  • Configuration\(\) followed within ~30 lines by prepareDatabase — find connection-setup blocks
  • Then grep the WHOLE codebase for PRAGMA\s+optimize and PRAGMA optimize

Verify: If no PRAGMA optimize appears anywhere in the codebase yet Configuration.prepareDatabase blocks exist, flag. Fix: Add try db.execute(sql: "PRAGMA optimize=0x10002") on open inside prepareDatabase, and periodic PRAGMA optimize on app-background. See axiom-data (skills/grdb-performance.md) §4.

Pattern 4: Journal Mode Not WAL for App-Group DB (CRITICAL/HIGH)

Gating: App-group sharing detected == yes. Issue: Multi-process SQLite sharing requires WAL. DatabaseQueue without explicit journal_mode = WAL defaults to rollback journaling, which serializes processes and fails locked-device reads. Search:

  • Files containing containerURL(forSecurityApplicationGroupIdentifier:) near DB setup
  • In the same setup, search for DatabasePool( (auto-WAL, safe) or journal_mode\s*=\s*WAL (explicit, safe)

Verify: If DatabaseQueue( is used for an app-group container without explicit journal_mode = WAL in prepareDatabase, flag. Fix: Use DatabasePool (recommended) or add try db.execute(sql: "PRAGMA journal_mode = WAL") to prepareDatabase. See axiom-data (skills/grdb-app-groups.md) §3.

Pattern 5: Missing observesSuspensionNotifications for Shared DB (HIGH/HIGH)

Gating: App-group sharing detected == yes. Issue: iOS terminates apps holding SQLite locks during suspension with exception 0xDEAD10CC. Invisible in development (debugger keeps process alive); manifests only in TestFlight, App Review, and production. Search:

  • Files using containerURL(forSecurityApplicationGroupIdentifier:) near DB setup
  • In the same files: observesSuspensionNotifications\s*=\s*true

Verify: If App Group DB setup is present but observesSuspensionNotifications is absent, flag. Cross-check: Also grep for Database\.suspendNotification and Database\.resumeNotification posts in scene/app lifecycle code — without them, the flag is half-wired even if observesSuspensionNotifications = true. Fix: Set config.observesSuspensionNotifications = true AND post Database.suspendNotification from sceneDidEnterBackground / applicationDidEnterBackground (NOT from resignActive — that fires for transient interruptions). See axiom-data (skills/grdb-app-groups.md) §5.

Pattern 6: Prefix-Redundant Indexes in Raw SQL (MEDIUM/LOW)

Gating: Library == Raw GRDB or Both. Raw SQL only — skips GRDB DSL `create(index:)` cross-correlation, which would need a parser. Issue: SQLite's docs: "Your database schema should never contain two indices where one index is a prefix of the other." Wastes write time and disk. Search:

  • CREATE\s+INDEX.*ON\s+\w+\s*\(

Verify: For each match, extract (table, [column_list]). Compare against every other CREATE INDEX on the same table across all migration files. Flag when one column list is a prefix of another. Fix: Drop the prefix-redundant (shorter) index. See axiom-data (skills/grdb-performance.md) §6. Limitation in report: "Raw SQL CREATE INDEX only. DSL t.create(index:) cross-correlation not analyzed — manually review DSL indexes."

Pattern 7: databaseSelection as Stored Property (HIGH/HIGH)

Gating: Library == Raw GRDB or Both. Issue: Under Swift 6 strict concurrency, static let databaseSelection: [any SQLSelectable] = [...] is a compile error: "not concurrency-safe because non-'Sendable' type '[any SQLSelectable]' may have shared mutable state." Hard build failure on Swift 6 — surfaces immediately, but easy to miss in a swift -package-mode reading of older code. Search:

  • static\s+let\s+databaseSelection

Verify: Read matching files; confirm declaration form (not a static var computed property). Fix: Change to computed property: static var databaseSelection: [any SQLSelectable] { [Columns.id, Columns.title] }. See axiom-data (skills/grdb-performance.md) §8.

Pattern 8 (Optional): Legacy Record Subclass (MEDIUM/LOW)

Gating: Library == Raw GRDB or Both. Issue: GRDB 7 actively discourages the Record base class. Classes are harder to make Sendable for Swift 6 conformance and harder to test. Search:

  • :\s*Record\s*\{ — class-based Record subclass

Verify: Read matching files; confirm it's a class declaration (not a struct named Record or similar). Fix: Convert to a struct conforming to Codable, FetchableRecord, PersistableRecord (or MutablePersistableRecord for auto-increment IDs). See axiom-data (skills/grdb-performance.md) §8.

Pattern 9: INSERT OR REPLACE Used as an Upsert (HIGH/MEDIUM)

Gating: Library == Raw GRDB or Both. Issue: INSERT OR REPLACE — and GRDB's .replace conflict policy — deletes the existing row and inserts a new one. Columns the insert doesn't supply silently reset to their defaults, ON DELETE CASCADE fires and takes child rows with it, the rowid changes, and transaction observers never see the duplicate-row deletion (so observations go stale). Developers reach for it wanting upsert semantics and get delete-then-insert. Search:

  • INSERT\s+OR\s+REPLACE — raw SQL form
  • REPLACE\s+INTO — the shorthand alias
  • persistenceConflictPolicy — record-level policy declaration
  • onConflict:\s*\.replace — per-call conflict resolution

Verify: Read matching files. For persistenceConflictPolicy, check whether the declared policy is .replace for insert (PersistenceConflictPolicy(insert: .replace, ...)) — other policies are not this finding. Confirm the table has either more columns than the insert supplies, or an inbound FK with ON DELETE CASCADE, or an observation on it; absent all three the impact is low. Fix: try record.upsert(db), or upsertAndFetch(db, updating: .noColumnUnlessSpecified) to leave existing values alone. See axiom-data (skills/grdb.md) — Upsert. Not a finding: code that genuinely wants delete-then-insert semantics (cache line replacement, full-row refresh where reset-to-default is correct). Say so in the report rather than flagging blindly.

Pattern 10: Observation on a WITHOUT ROWID Table (HIGH/MEDIUM)

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
1,097
Repository forks
81
7-day trend score
35

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/charleswiltgen/axiom/axiom-audit-grdb-performance/badge)](https://verified-skill.com/skills/charleswiltgen/axiom/axiom-audit-grdb-performance)

Something wrong with this skill or its page? Tell us.