Predicates

Predicates define filter conditions for queries. SwiftDataServer provides the #Predicate macro for 100% API compatibility with Apple's SwiftData.

The #Predicate Macro

Create type-safe predicates using a closure syntax:

// Using $0 shorthand
let adults = #Predicate<User> { $0.age >= 18 }

// Using named parameter
let adults = #Predicate<User> { user in
    user.age >= 18
}

Comparison Operators

// Equality
let byName = #Predicate<User> { $0.name == "Alice" }

// Inequality
let notAdmin = #Predicate<User> { $0.role != "admin" }

// Less than
let young = #Predicate<User> { $0.age < 18 }

// Less than or equal
let youngOrExact = #Predicate<User> { $0.age <= 17 }

// Greater than
let senior = #Predicate<User> { $0.age > 65 }

// Greater than or equal
let adults = #Predicate<User> { $0.age >= 18 }

Logical Operators

// AND
let activeAdults = #Predicate<User> { user in
    user.age >= 18 && user.isActive == true
}

// OR
let specialUsers = #Predicate<User> { user in
    user.role == "admin" || user.role == "moderator"
}

// NOT
let notActive = #Predicate<User> { !($0.isActive == true) }

// Complex combinations
let complex = #Predicate<User> { user in
    (user.age >= 18 && user.isActive) || user.role == "admin"
}

String Operations

// Contains
let searchName = #Predicate<User> { $0.name.contains("John") }

// Has prefix
let doctorsPrefix = #Predicate<User> { $0.name.hasPrefix("Dr.") }

// Has suffix
let gmailUsers = #Predicate<User> { $0.email.hasSuffix("@gmail.com") }

// Case-insensitive contains (uses LOWER() in SQL)
let searchInsensitive = #Predicate<User> {
    $0.name.localizedStandardContains("john")
}

Nil Checks

// Is nil
let noEmail = #Predicate<User> { $0.email == nil }

// Is not nil
let hasEmail = #Predicate<User> { $0.email != nil }

Using Variables

Reference external variables in your predicates:

let searchTerm = "Alice"
let minAge = 21

let predicate = #Predicate<User> { user in
    user.name.contains(searchTerm) && user.age >= minAge
}

Using Predicates

// With FetchDescriptor
let adults = #Predicate<User> { $0.age >= 18 }
let descriptor = FetchDescriptor<User>(predicate: adults)
let users = try context.fetch(descriptor)

// Using builder pattern
let users = try context.fetch(
    FetchDescriptor<User>()
        .filter(adults)
        .sorted(by: SortDescriptor(propertyName: "name"))
)

Combining Predicates

Combine existing predicates programmatically:

let adults = #Predicate<User> { $0.age >= 18 }
let active = #Predicate<User> { $0.isActive == true }

// Combine with AND
let activeAdults = adults.and(active)

// Combine with OR
let eitherCondition = adults.or(active)

// Negate
let minors = adults.negated

Struct-Based Construction

For dynamic predicate building, construct predicates programmatically:

// Simple comparison
let adults = Predicate<User>(expression: .comparison(
    keyPath: "age",
    op: .greaterThanOrEqual,
    value: .int(18)
))

// Between range
let middleAged = Predicate<User>(expression: .between(
    keyPath: "age",
    low: .int(30),
    high: .int(50)
))

// In list
let specificRoles = Predicate<User>(expression: .in(
    keyPath: "role",
    values: [.string("admin"), .string("moderator")]
))

Supported Operations Summary

Operation SQL
===
!=!=
<<
<=<=
>>
>=>=
&&AND
||OR
!NOT
.contains()LIKE '%...%'
.hasPrefix()LIKE '...%'
.hasSuffix()LIKE '%...'
== nilIS NULL
!= nilIS NOT NULL

Next Steps