All skills

Use when working with SQLiteData library (@Table, @FetchAll, @FetchOne macros) for SQLite persistence, queries, writes, migrations, or CloudKit private database sync.

Use this Skill: https://skilld.dev/gh/johnrogers/claude-swift-engineering/sqlite-data

This session only. Nothing lands on disk.

referencesqueries-basics.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Query Basics

Patterns for fetching data using @FetchAll, @FetchOne, and @Selection.

When to Use Which

Wrapper Use For Example
@FetchAll Inline queries returning multiple records @FetchAll(Item.order { $0.createdAt.desc() }) var items
@FetchOne Inline queries returning single record or aggregate @FetchOne(Item.where { $0.isActive }) var activeItem
@Fetch FetchKeyRequest only - when you need data transformation @Fetch(ComplexRequest()) var result

Common mistake: Using @Fetch for simple queries. It requires FetchKeyRequest conformance.

Only create a FetchKeyRequest struct when you need to:

  • Transform data with .map() after fetching
  • Perform multiple database operations in one transaction
  • Return a custom result type that differs from the raw query

For simple ordering, filtering, or joins without transformation → use @FetchAll or @FetchOne.

@FetchAll - Multiple Records

Fetch multiple records with automatic SwiftUI updates:

@Observable
class CountersListModel {
  @ObservationIgnored
  @FetchAll var counters: [Counter]
}

With Query Ordering

@FetchAll(
  Counter.order(by: \.id),
  animation: .default
)
var counters

With Filtering

@FetchAll(
  Reminder.where { !$0.isCompleted }
    .order(by: \.position)
)
var incompleteTasks

With Joins

@FetchAll(
  RemindersList
    .group(by: \.id)
    .order(by: \.position)
    .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted }
    .leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($2.id) }
    .select {
      ReminderListState.Columns(
        remindersCount: $1.id.count(),
        remindersList: $0,
        share: $2.share
      )
    },
  animation: .default
)
var remindersLists

@FetchOne - Single Value or Aggregate

Fetch a single record or aggregate value:

@FetchOne(Fact.count(), animation: .default)
var factsCount = 0

Multiple Aggregates with @Selection

@FetchOne(
  Reminder.select {
    Stats.Columns(
      allCount: $0.count(filter: !$0.isCompleted),
      flaggedCount: $0.count(filter: $0.isFlagged && !$0.isCompleted),
      scheduledCount: $0.count(filter: $0.isScheduled),
      todayCount: $0.count(filter: $0.isToday)
    )
  }
)
var stats = Stats()

@Selection
struct Stats {
  var allCount = 0
  var flaggedCount = 0
  var scheduledCount = 0
  var todayCount = 0
}

@Selection - Custom Result Types

Define custom result types for complex queries:

@Selection
struct ReminderListState: Identifiable, Hashable {
  var remindersList: RemindersList
  var remindersCount: Int
  @Column(as: CKShare?.self)
  var share: CKShare?

  var id: RemindersList.ID { remindersList.id }
}

Use with queries:

@FetchAll(
  RemindersList
    .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) }
    .select { list, reminder in
      ReminderListState.Columns(
        remindersList: list,
        remindersCount: reminder.id.count()
      )
    }
)
var remindersLists

Query Building Blocks

Filtering

// Simple equality
Reminder.where { $0.status.eq(.incomplete) }

// Negation
Reminder.where { !$0.isCompleted }

// Comparisons
Reminder.where { $0.priority.gt(.low) }

// In array
Tag.where { $0.title.in(["Work", "Personal"]) }

// Pattern matching
Fact.where { $0.body.contains(searchText) }

// Null checks
Reminder.where { $0.dueDate.isNot(nil) }

Ordering

// Single column
Counter.order(by: \.id)

// Multiple columns with direction
Reminder
  .order { $0.dueDate.desc() }
  .order { $0.position }

Grouping

RemindersList
  .group(by: \.id)
  .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) }
  .select { list, reminder in
    ListSummary.Columns(
      list: list,
      count: reminder.id.count()
    )
  }

Joining

// Left join
Tag
  .leftJoin(ReminderTag.all) { $0.primaryKey.eq($1.tagID) }
  .leftJoin(Reminder.all) { $1.reminderID.eq($2.id) }

// With conditions
RemindersList
  .leftJoin(Reminder.all) {
    $0.id.eq($1.remindersListID) && !$1.isCompleted
  }

Having

Filter grouped results:

Tag
  .withReminders
  .having { $2.count().gt(0) }  // Only tags with reminders
  .select { tag, _, _ in tag }

Count

// Total count
let total = try Reminder.fetchCount(db)

// Filtered count
let incomplete = try Reminder.where { !$0.isCompleted }.fetchCount(db)

// Conditional count in select
Reminder.select {
  Stats.Columns(
    allCount: $0.count(filter: !$0.isCompleted),
    flaggedCount: $0.count(filter: $0.isFlagged)
  )
}

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides comprehensive documentation and code templates for the SQLiteData Swift library, covering database modeling, querying, CloudKit synchronization, and testing. No security concerns were identified.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    14/14 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d62eb71. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 9 months ago

README badge

README badge for johnrogers/claude-swift-engineering/sqlite-data