Sqd diag
Agent Skills for SQLiteData (Point-Free's GRDB-based SwiftData replacement with CloudKit sync). Covers @Table, fetch wrappers, queries, migrations, and SyncEngine.
npx -y skills add sitapix/sqlitedata-swift-skills --skill sqd-diagAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when a SQLiteData error message or unexpected behavior occurs — symptom-to-fix lookup for build errors, runtime crashes, migration failures, and query problems. NOT for implementing features (use core) or learning CloudKit sync patterns (use router)
SKILL.md
8.6 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it
SQLiteData Diagnostics
Symptom-based troubleshooting for SQLiteData issues.
1. Build/Compilation Errors
"Cannot find type 'Table' / '@Table'"
- Missing
import SQLiteData(it re-exports StructuredQueriesSQLite) - Package.swift missing
swift-structured-queriesdependency (should come transitively via SQLiteData)
"Cannot convert value of type 'X' to expected argument type..."
- Check that your
@Tablestruct fields match the SQL schema types TEXT→StringorUUID,INTEGER→IntorBool,REAL→Double,BLOB→Data- Enums must conform to
QueryBindable
"Referencing static method 'buildExpression' requires..."
- Usually means a type mismatch in
.select { }closure - Check that
@Selectionstruct field types match query expressions
Concurrency warnings on @Table structs
- Add
nonisolatedbefore the struct:@Table nonisolated struct Item { ... }
2. Runtime Database Errors
"no such table: X"
- Migration hasn't run → check
migrator.migrate(database)is called - Table name mismatch →
@Table("customName")must match SQLCREATE TABLE "customName" - In previews/tests:
prepareDependenciesnot called
"no such column: X"
- Column name in Swift doesn't match SQL schema
- Migration adding the column hasn't been registered
- Swift property names are auto-converted to camelCase column names
"FOREIGN KEY constraint failed"
- Inserting child before parent exists
- Deleting parent without
ON DELETE CASCADE - SyncEngine handles this automatically during sync — if in app code, insert parent first
"NOT NULL constraint failed"
- Inserting NULL into a NOT NULL column without a default
- For CloudKit: use
NOT NULL ON CONFLICT REPLACE DEFAULT <value>
"UNIQUE constraint failed"
- Duplicate primary key or unique column value
- For CloudKit-synced tables: unique constraints (except PK) are not allowed
"database is locked"
- Long-running read blocking writes (or vice versa)
- Consider using
DatabasePoolinstead ofDatabaseQueue - Check for nested
database.writecalls
3. SyncEngine Errors
Quick symptom→fix map. For full CloudKit sync patterns, use /skill sqd-cloudkit.
| Symptom | Fix |
|---|---|
| SyncEngine init throws about UNIQUE | Remove all UNIQUE constraints from synced tables (except PK). Workaround: make the unique column the primary key via @Column(primaryKey: true) — see /skill sqd-cloudkit §3 |
| SyncEngine init throws about RESTRICT/NO ACTION | Change to ON DELETE CASCADE, SET NULL, or SET DEFAULT |
write-permission-error | User lacks write permission on shared record — catch via SyncEngine.writePermissionError |
invalid-record-name-error | Primary key has non-ASCII chars, >255 chars, or starts with underscore |
| Data not syncing | Check: isRunning == true, iCloud signed in, capabilities enabled, table listed in SyncEngine(tables:) |
| Records sync but data wrong | Missing NOT NULL ON CONFLICT REPLACE DEFAULT on non-nullable columns |
limitExceeded / batchRequestFailed | Transient — SyncEngine retries automatically |
| Share not working | Check: CKSharingSupported in Info.plist, table in tables: (not privateTables:), root record with UUID PK, acceptShare(metadata:) in SceneDelegate |
4. @FetchAll / @FetchOne / @Fetch Issues
Data not updating in UI
- In
@Observableclass: add@ObservationIgnoredto the fetch property wrappers - Check database is the same instance:
@Dependency(\.defaultDatabase)must be the prepared one prepareDependenciesonly called once?
"A blank, in-memory database is being used"
prepareDependenciesnot called, or called after first access- In previews:
let _ = prepareDependencies { ... }at top of#Preview - In tests:
.dependency(\.defaultDatabase, ...)trait
Query returns empty but data exists
- Check query conditions:
.where { }filter may be wrong - Table name mismatch between SQL and
@Table - Database not migrated (tables don't exist)
Load error on @Fetch property
- Check
$property.loadErrorfor details - Common: SQL syntax error in
#sql(...)macro - Common: Type mismatch between query output and expected type
@Fetch with FetchKeyRequest not loading
FetchKeyRequestrequires akeyvalue before it fetches — check the key is set- If using
@Fetch(ItemRequest())with a default key, ensure the key matches an actual row @Fetchdoes not auto-load on init — callload()or set a key to trigger the fetch- Check
$property.loadError— a nil key produces no error but also no data
ValueObservation / SharedReader stale data
SharedReadercaches the last emitted value — if the underlying query changes shape (e.g. table renamed), the reader stays stale@FetchAllusesValueObservationinternally — if the observed tables haven't changed, no update fires- Writes via raw SQL (
db.execute(sql:)) bypass StructuredQueries change tracking — use@Tablequery builders instead - If using
DatabaseQueue: only one connection, reads wait on writes — observation may appear delayed
"Cannot subscribe to observation" / subscription issues
FetchSubscription.cancel()called too early- Database connection closed
- Using
DatabaseQueuein multithreaded context (useDatabasePool)
5. Migration Issues
eraseDatabaseOnSchemaChange causes data loss in dev
- This is expected behavior — it's a DEBUG-only convenience
- Wrap in
#if DEBUG:migrator.eraseDatabaseOnSchemaChange = true - For production: always add new migrations, never edit existing ones
migratePrimaryKeys throws
- Schema it doesn't know how to handle — fall back to manual migration
- See
ManuallyMigratingPrimaryKeysdocs
ALTER TABLE fails
- SQLite has limited ALTER TABLE support
- Can only: add columns, rename table, rename column
- Cannot: drop columns (SQLite 3.35+), add constraints to existing columns
- For complex changes: create new table, copy data, drop old, rename new
Migration ordering / dependencies
- Migrations run in registration order — if migration B references a table from migration A, register A first
- Never reorder existing migrations — new devices replay from the start
- If two migrations touch the same table, combine them or use explicit ordering keys
- CloudKit-synced schemas must be backwards-compatible — never remove or rename columns in migrations
6. Preview Issues
Previews crash or show empty data
#Preview {
let _ = try! prepareDependencies {
try $0.bootstrapDatabase()
try? $0.defaultDatabase.seedSampleData()
}
ContentView()
}
CloudKit previews crash
- Mock cloud container is used automatically in preview context
- If still crashing: check
SyncEngineinit isn't hitting network - Use
startImmediately: falseand don't call.start()in previews
7. Testing Issues
Tests interfere with each other
- Each test gets its own temporary database via
defaultDatabase() - Use
.dependency(\.defaultDatabase, ...)trait per suite - Don't share database instances across test suites
Test database has no tables
- Run migrations in test setup:
try migrator.migrate(database) - Or use
bootstrapDatabase()helper
Preview works but tests fail (or vice versa)
- Preview and tests use different
prepareDependenciescalls — ensure both set up the same migrations - Preview may use
seedSampleData()which inserts rows — tests should start clean @MainActorisolation in tests can cause issues with database access — usenonisolatedtest methods orwithDependenciesfor explicit setup
Device vs simulator differences
- Simulator uses different file paths —
NSHomeDirectory()changes per boot - CloudKit sync requires a real iCloud account — simulator with no account silently skips sync
- File protection levels behave differently in simulator —
.completeUnlessOpenmay not block reads as expected
Quick Diagnostic Checklist
import SQLiteDatapresent?prepareDependenciescalled once at app start?migrator.migrate(database)called?@ObservationIgnoredon fetch property wrappers in@Observable?- Table names match between
@Tableand SQL? - Column types match between Swift and SQL?
- For CloudKit: UUID primary keys with
ON CONFLICT REPLACE? - For CloudKit: no UNIQUE constraints, no RESTRICT/NO ACTION?
- Foreign key indexes created?
configuration.foreignKeysEnabled = trueif using foreign keys?
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.