Skip to content

Commit 1fa7467

Browse files
committed
Improved concurrency interfaces, fixed issues with fetchedResults functions, and documented source.
1 parent 400f0dd commit 1fa7467

File tree

10 files changed

+1249
-130
lines changed

10 files changed

+1249
-130
lines changed

README.md

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
# swift-query
2-
A simple query language for Swift Data with automatic support for Swift concurrency.
2+
A simple query language for SwiftData with automatic support for Swift concurrency.
33

44
## Usage
55

66
```swift
7-
// Main context
7+
// Query from the main context
88
let people = Query<Person>()
99
.include(#Predicate { $0.age >= 18 } )
1010
.sortBy(\.age)
1111
.results(in: modelContainer)
12+
for person in people {
13+
print("Adult: \(person.name), age \(person.age)")
14+
}
15+
1216

1317
// Or a background context
1418
Task.detached {
15-
let people = await modelContainer.perform {
16-
Query<Person>()
19+
await modelContainer.queryActor().perform { _ in
20+
let people = Query<Person>()
1721
.include(#Predicate { $0.age >= 18 } )
1822
.sortBy(\.age)
1923
.results()
24+
25+
for person in people {
26+
person.age += 1
27+
}
2028
}
2129
}
2230
```
2331

2432
### Building Queries
2533

26-
Queries are an expressive layer on top of Swift Data that allow us to quickly build
34+
Queries are an expressive layer on top of SwiftData that allow us to quickly build
2735
complex fetch decriptors by successively applying refinements. The resulting query can
2836
be saved for reuse or performed immediately.
2937

@@ -52,6 +60,24 @@ Person.include(#Predicate { $0.name == "Jack" })
5260
Person.exclude(#Predicate { $0.age > 25 })
5361
```
5462

63+
#### Creating compound refinements
64+
65+
Multiple `include()` and `exclude()` calls create compound predicates using AND logic, allowing you to build complex filters:
66+
67+
```swift
68+
// Find adult Jacks who are active
69+
Person.include(#Predicate { $0.age >= 18 })
70+
.include(#Predicate { $0.name == "Jack" })
71+
.exclude(#Predicate { $0.isInactive })
72+
```
73+
74+
This creates a compound predicate equivalent to:
75+
```swift
76+
#Predicate<Person> { person in
77+
person.age >= 18 && person.name == "Jack" && !person.isInactive
78+
}
79+
```
80+
5581
#### Ordering results
5682

5783
Queries allow their results to be ordered:
@@ -122,7 +148,7 @@ we pass in our model container and SwiftQuery will use the container's main cont
122148

123149
#### Fetching one result
124150

125-
Often we just want
151+
Often we just want to fetch a single result.
126152

127153
```swift
128154
let jillQuery = Person.include(#Predicate { $0.name == "Jill" })
@@ -153,7 +179,7 @@ let lazyAdults = Person
153179

154180
#### Fetching or creating objects matching a query
155181

156-
A common pattern in Core Data (and so in Swift Data), is to want to fetch an object
182+
A common pattern in Core Data (and so in SwiftData), is to want to fetch an object
157183
based on a set of filters, or create a new one by default in the case that object
158184
does not yet exist. This is easy with SwiftQuery using `findOrCreate`:
159185

@@ -163,7 +189,6 @@ let jill = Person
163189
.findOrCreate(in: container) {
164190
Person(name: "Jill")
165191
}
166-
}
167192
```
168193

169194
### Performing queries in a concurrent context
@@ -188,31 +213,36 @@ actor MyActor {
188213
}
189214
```
190215

191-
We also expose an async `perform` function on `ModelContainer` that allow you to
192-
implicitly use SwiftQuery's `QueryActor` to run queries:
216+
We also expose an async `perform` functions on a SwiftQuery's default actor that allow you to
217+
implicitly use `QueryActor` to run queries:
193218

194219
```swift
195-
let allJills = try await modelContainer.perform {
196-
Person
220+
await modelContainer.queryActor().perform { _ in
221+
let allJills = Person
197222
.include(#Predicate { $0.name == "Jill" })
198223
.results()
224+
225+
// Process Jills within the actor context
226+
for jill in allJills {
227+
print("Found Jill: \(jill.name)")
228+
}
199229
}
200230
```
201231

202-
You can of course pass this (or any) model actor explicitly as the isolation context:
232+
Or, to return a value:
203233

204234
```swift
205-
Task {
206-
let actor = QueryActor(modelContainer: myModelContainer)
207-
let allJills = try await Person
208-
.include(#Predicate { $0.name == "Jill" })
209-
.results(isolation: actor)
210-
}
235+
let count = await modelContainer.queryActor().perform { _ in
236+
Query<Person>()
237+
.include(#Predicate { $0.age >= 18 } )
238+
.count()
239+
}
211240
```
212241

213-
214-
## TODO
215-
- Tests
242+
Note that models cannot be returned out of the actor's isolation context using this function;
243+
only `Sendable` values can be transported across the boundary. This means the compiler
244+
effectively makes it impossible to use the models incorrectly in a multi-threaded context,
245+
thus guaranteeing the SwiftData concurrency contract at compile time.
216246

217247
## Installation
218248

@@ -232,7 +262,7 @@ dependencies: [
232262
And then adding the product to any target that needs access to the library:
233263

234264
```swift
235-
.product(name: "SwiftQuery", package: "swift-squery"),
265+
.product(name: "SwiftQuery", package: "swift-query"),
236266
```
237267

238268
## License

0 commit comments

Comments
 (0)