Skip to content

Commit 4015d02

Browse files
committed
wip
1 parent d523ab2 commit 4015d02

File tree

5 files changed

+104
-10
lines changed

5 files changed

+104
-10
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ let ordersWithDetails = Order
177177
.prefetchRelationship(\.items)
178178
```
179179

180+
#### Fetching specific properties
181+
182+
To reduce memory usage, you can fetch only specific properties instead of full objects:
183+
184+
```swift
185+
// Fetch only specific properties for better performance
186+
let lightweightPeople = Person.fetchKeyPaths(\.name, \.age)
187+
```
188+
180189
### Executing queries
181190

182191
Queries are just descriptions of how to fetch objects from a context. To make them

Sources/SwiftQuery/PersistentModel+Query.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,9 @@ public extension PersistentModel {
7272
static func prefetchRelationship(_ keyPath: PartialKeyPath<Self>) -> Query<Self> {
7373
query().prefetchRelationship(keyPath)
7474
}
75+
76+
/// Constructs an empty query over this model type and invokes ``Query/fetchKeyPaths(_:)`` on that query.
77+
static func fetchKeyPaths(_ keyPaths: PartialKeyPath<Self>...) -> Query<Self> {
78+
query().fetchKeyPaths(keyPaths)
79+
}
7580
}

Sources/SwiftQuery/Query.swift

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public struct Query<T: PersistentModel> {
4040

4141
public var relationshipKeyPaths: [PartialKeyPath<T>] = []
4242

43+
public var propertiesToFetch: [PartialKeyPath<T>] = []
4344

4445
/// SwiftData compatible sort descriptors generated from the query's sort configuration.
4546
public var sortDescriptors: [SortDescriptor<T>] {
@@ -53,6 +54,7 @@ public struct Query<T: PersistentModel> {
5354
descriptor.fetchLimit = range.upperBound - range.lowerBound
5455
}
5556
descriptor.relationshipKeyPathsForPrefetching = relationshipKeyPaths
57+
descriptor.propertiesToFetch = propertiesToFetch
5658
return descriptor
5759
}
5860

@@ -66,12 +68,14 @@ public struct Query<T: PersistentModel> {
6668
predicate: Predicate<T>? = nil,
6769
sortBy: [AnySortDescriptor<T>] = [],
6870
range: Range<Int>? = nil,
69-
prefetchRelationships: [PartialKeyPath<T>] = []
71+
prefetchRelationships: [PartialKeyPath<T>] = [],
72+
propertiesToFetch: [PartialKeyPath<T>] = []
7073
) {
7174
self.predicate = predicate
7275
self.sortBy = sortBy
7376
self.range = range
7477
self.relationshipKeyPaths = prefetchRelationships
78+
self.propertiesToFetch = propertiesToFetch
7579
}
7680

7781
/// Returns a new query that includes only objects matching the given predicate.
@@ -103,7 +107,8 @@ public struct Query<T: PersistentModel> {
103107
predicate: compoundPredicate,
104108
sortBy: sortBy,
105109
range: range,
106-
prefetchRelationships: relationshipKeyPaths
110+
prefetchRelationships: relationshipKeyPaths,
111+
propertiesToFetch: propertiesToFetch
107112
)
108113
}
109114

@@ -150,7 +155,8 @@ public struct Query<T: PersistentModel> {
150155
predicate: predicate,
151156
sortBy: sortBy.map { $0.reversed() },
152157
range: range,
153-
prefetchRelationships: relationshipKeyPaths
158+
prefetchRelationships: relationshipKeyPaths,
159+
propertiesToFetch: propertiesToFetch
154160
)
155161
}
156162

@@ -174,6 +180,7 @@ public struct Query<T: PersistentModel> {
174180
sortBy: sortBy,
175181
range: range,
176182
prefetchRelationships: relationshipKeyPaths,
183+
propertiesToFetch: propertiesToFetch
177184
)
178185
}
179186
}
@@ -206,7 +213,8 @@ public extension Query {
206213
predicate: predicate,
207214
sortBy: sortBy + [.init(keyPath, order: order)],
208215
range: range,
209-
prefetchRelationships: relationshipKeyPaths
216+
prefetchRelationships: relationshipKeyPaths,
217+
propertiesToFetch: propertiesToFetch
210218
)
211219
}
212220

@@ -231,7 +239,8 @@ public extension Query {
231239
predicate: predicate,
232240
sortBy: sortBy + [.init(keyPath, order: order)],
233241
range: range,
234-
prefetchRelationships: relationshipKeyPaths
242+
prefetchRelationships: relationshipKeyPaths,
243+
propertiesToFetch: propertiesToFetch
235244
)
236245
}
237246

@@ -256,7 +265,8 @@ public extension Query {
256265
predicate: predicate,
257266
sortBy: sortBy + [.init(keyPath, comparator: comparator, order: order)],
258267
range: range,
259-
prefetchRelationships: relationshipKeyPaths
268+
prefetchRelationships: relationshipKeyPaths,
269+
propertiesToFetch: propertiesToFetch
260270
)
261271
}
262272

@@ -281,13 +291,12 @@ public extension Query {
281291
predicate: predicate,
282292
sortBy: sortBy + [.init(keyPath, comparator: comparator, order: order)],
283293
range: range,
284-
prefetchRelationships: relationshipKeyPaths
294+
prefetchRelationships: relationshipKeyPaths,
295+
propertiesToFetch: propertiesToFetch
285296
)
286297
}
287298
}
288299

289-
290-
291300
public extension Query {
292301
/// Returns a new query that prefetches the specified relationship when executing the fetch.
293302
///
@@ -315,7 +324,41 @@ public extension Query {
315324
predicate: predicate,
316325
sortBy: sortBy,
317326
range: range,
318-
prefetchRelationships: relationshipKeyPaths + [keyPath]
327+
prefetchRelationships: relationshipKeyPaths + [keyPath],
328+
propertiesToFetch: propertiesToFetch
319329
)
320330
}
331+
332+
/// Returns a new query that fetches only the specified key paths when executing the fetch.
333+
///
334+
/// This can improve performance by reducing memory usage when you only need specific properties.
335+
/// Properties not included will be faulted and loaded on demand.
336+
///
337+
/// - Parameter keyPaths: Array of key paths to the properties to fetch
338+
/// - Returns: A new query with the additional properties marked for fetching
339+
func fetchKeyPaths(_ keyPaths: [PartialKeyPath<T>]) -> Self {
340+
Query(
341+
predicate: predicate,
342+
sortBy: sortBy,
343+
range: range,
344+
prefetchRelationships: relationshipKeyPaths,
345+
propertiesToFetch: propertiesToFetch + keyPaths
346+
)
347+
}
348+
349+
/// Returns a new query that fetches only the specified key paths when executing the fetch.
350+
///
351+
/// This can improve performance by reducing memory usage when you only need specific properties.
352+
/// Properties not included will be faulted and loaded on demand.
353+
///
354+
/// - Parameter keyPaths: Key paths to the properties to fetch
355+
/// - Returns: A new query with the additional properties marked for fetching
356+
///
357+
/// ## Example
358+
/// ```swift
359+
/// let lightweightPeople = Person.fetchKeyPaths(\.name, \.age)
360+
/// ```
361+
func fetchKeyPaths(_ keyPaths: PartialKeyPath<T>...) -> Self {
362+
fetchKeyPaths(keyPaths)
363+
}
321364
}

Tests/SwiftQueryTests/PersistentModelQueryTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,12 @@ struct PersistentModelQueryTests {
6868
#expect(modelQuery.relationshipKeyPaths.count == directQuery.relationshipKeyPaths.count)
6969
#expect(modelQuery.relationshipKeyPaths.contains(\Person.name) == directQuery.relationshipKeyPaths.contains(\Person.name))
7070
}
71+
72+
@Test func fetchKeyPaths() async throws {
73+
let modelQuery = Person.fetchKeyPaths(\.name)
74+
let directQuery = Query<Person>().fetchKeyPaths(\.name)
75+
76+
#expect(modelQuery.propertiesToFetch.count == directQuery.propertiesToFetch.count)
77+
#expect(modelQuery.propertiesToFetch.contains(\Person.name) == directQuery.propertiesToFetch.contains(\Person.name))
78+
}
7179
}

Tests/SwiftQueryTests/QueryTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,33 @@ struct QueryTests {
299299
#expect(query.relationshipKeyPaths.contains(\Person.age))
300300
}
301301

302+
@Test func fetchKeyPaths_single() async throws {
303+
let query = Query<Person>()
304+
.fetchKeyPaths(\.name)
305+
306+
#expect(query.propertiesToFetch.count == 1)
307+
#expect(query.propertiesToFetch.contains(\Person.name))
308+
}
309+
310+
@Test func fetchKeyPaths_multiple() async throws {
311+
let query = Query<Person>()
312+
.fetchKeyPaths(\.name, \.age)
313+
314+
#expect(query.propertiesToFetch.count == 2)
315+
#expect(query.propertiesToFetch.contains(\Person.name))
316+
#expect(query.propertiesToFetch.contains(\Person.age))
317+
}
318+
319+
@Test func fetchKeyPaths_withOtherModifiers() async throws {
320+
let predicate = #Predicate<Person> { $0.age >= 18 }
321+
let query = Person.include(predicate)
322+
.sortBy(\.name)
323+
.fetchKeyPaths(\.age)
324+
325+
#expect(query.predicate != nil)
326+
#expect(query.sortBy.count == 1)
327+
#expect(query.propertiesToFetch.count == 1)
328+
#expect(query.propertiesToFetch.contains(\Person.age))
329+
}
330+
302331
}

0 commit comments

Comments
 (0)