Skip to content

Commit f109bab

Browse files
authored
Use an immediate schedule in FetchKey. (#75)
1 parent 06cd9e3 commit f109bab

5 files changed

Lines changed: 96 additions & 26 deletions

File tree

Sources/SharingGRDBCore/FetchKey.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,7 @@ public struct FetchKey<Value: Sendable>: SharedReaderKey {
310310
continuation.resumeReturningInitialValue()
311311
return
312312
}
313-
guard !isTesting else {
314-
continuation.resume(with: Result { try database.read(request.fetch) })
315-
return
316-
}
317-
let scheduler: any ValueObservationScheduler = scheduler ?? .async(onQueue: .main)
313+
let scheduler: any ValueObservationScheduler = scheduler ?? ImmediateScheduler()
318314
database.asyncRead { dbResult in
319315
let result = dbResult.flatMap { db in
320316
Result {
@@ -345,7 +341,7 @@ public struct FetchKey<Value: Sendable>: SharedReaderKey {
345341
let observation = ValueObservation.tracking { db in
346342
Result { try request.fetch(db) }
347343
}
348-
let scheduler: any ValueObservationScheduler = scheduler ?? .async(onQueue: .main)
344+
let scheduler: any ValueObservationScheduler = scheduler ?? ImmediateScheduler()
349345
#if canImport(Combine)
350346
let dropFirst =
351347
switch context {
@@ -434,3 +430,10 @@ private struct FetchOneRequest<Value: DatabaseValueConvertible>: FetchKeyRequest
434430
public struct NotFound: Error {
435431
public init() {}
436432
}
433+
434+
private struct ImmediateScheduler: ValueObservationScheduler, Hashable {
435+
func immediateInitialValue() -> Bool { true }
436+
func schedule(_ action: @escaping @Sendable () -> Void) {
437+
action()
438+
}
439+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Dependencies
2+
import DependenciesTestSupport
3+
import Foundation
4+
import SharingGRDB
5+
import Testing
6+
7+
@Suite(.dependency(\.defaultDatabase, try .database()))
8+
struct FetchAllTests {
9+
@Dependency(\.defaultDatabase) var database
10+
11+
@MainActor
12+
@Test func concurrency() async throws {
13+
let count = 1_000
14+
try await database.write { db in
15+
try Record.delete().execute(db)
16+
}
17+
18+
@FetchAll var records: [Record]
19+
20+
await withThrowingTaskGroup { group in
21+
for index in 1...count {
22+
group.addTask {
23+
try await database.write { db in
24+
try Record.insert(Record(id: index)).execute(db)
25+
}
26+
}
27+
}
28+
}
29+
30+
try await $records.load()
31+
#expect(records == (1...count).map { Record(id: $0) })
32+
33+
await withThrowingTaskGroup { group in
34+
for index in 1...(count/2) {
35+
group.addTask {
36+
try await database.write { db in
37+
try Record.find(index * 2).delete().execute(db)
38+
}
39+
}
40+
}
41+
}
42+
43+
try await $records.load()
44+
#expect(records == (0...(count/2-1)).map { Record(id: $0 * 2 + 1) })
45+
}
46+
}
47+
48+
@Table
49+
private struct Record: Equatable {
50+
let id: Int
51+
@Column(as: Date.UnixTimeRepresentation.self)
52+
var date = Date(timeIntervalSince1970: 42)
53+
@Column(as: Date?.UnixTimeRepresentation.self)
54+
var optionalDate: Date?
55+
}
56+
extension DatabaseWriter where Self == DatabaseQueue {
57+
fileprivate static func database() throws -> DatabaseQueue {
58+
let database = try DatabaseQueue()
59+
try database.write { db in
60+
try #sql(
61+
"""
62+
CREATE TABLE "records" (
63+
"id" INTEGER PRIMARY KEY AUTOINCREMENT
64+
, "date" INTEGER NOT NULL DEFAULT 42
65+
, "optionalDate" INTEGER
66+
)
67+
"""
68+
)
69+
.execute(db)
70+
for _ in 1...3 {
71+
_ = try Record.insert(Record.Draft()).execute(db)
72+
}
73+
}
74+
return database
75+
}
76+
}

Tests/SharingGRDBTests/FetchTests.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,26 @@ import Testing
1010
struct FetchTests {
1111
@Test func bareFetchAll() async throws {
1212
@FetchAll var records: [Record]
13-
try await Task.sleep(nanoseconds: 100_000_000)
1413
#expect(records == [Record(id: 1), Record(id: 2), Record(id: 3)])
1514
}
1615

1716
@Test func fetchAllWithQuery() async throws {
1817
@FetchAll(Record.where { $0.id > 1 }) var records: [Record]
19-
try await Task.sleep(nanoseconds: 100_000_000)
2018
#expect(records == [Record(id: 2), Record(id: 3)])
2119
}
2220

2321
@Test func fetchOneCountWithQuery() async throws {
2422
@FetchOne(Record.where { $0.id > 1 }.count()) var recordsCount = 0
25-
try await Task.sleep(nanoseconds: 100_000_000)
2623
#expect(recordsCount == 2)
2724
}
2825

2926
@Test func bareFetchOneOptional() async throws {
3027
@FetchOne var record: Record?
31-
try await Task.sleep(nanoseconds: 100_000_000)
3228
#expect(record != nil)
3329
}
3430

3531
@Test func fetchOneOptionalWithQuery() async throws {
3632
@FetchOne(#sql("SELECT * FROM records LIMIT 1")) var record: Record?
37-
try await Task.sleep(nanoseconds: 100_000_000)
3833
#expect(record != nil)
3934
}
4035
}

Tests/SharingGRDBTests/IntegrationTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ struct IntegrationTests {
1818
_ = try SyncUp.insert(SyncUp.Draft(isActive: true, title: "Engineering"))
1919
.execute(db)
2020
}
21-
try await Task.sleep(nanoseconds: 100_000_000)
21+
try await $syncUps.load()
2222
#expect(syncUps == [SyncUp(id: 1, isActive: true, title: "Engineering")])
2323
try await database.write { db in
2424
_ = try SyncUp.upsert(SyncUp.Draft(id: 1, isActive: false, title: "Engineering"))
2525
.execute(db)
2626
}
27-
try await Task.sleep(nanoseconds: 100_000_000)
27+
try await $syncUps.load()
2828
#expect(syncUps == [])
2929
try await database.write { db in
3030
_ = try SyncUp.upsert(SyncUp.Draft(id: 1, isActive: true, title: "Engineering"))
3131
.execute(db)
3232
}
33-
try await Task.sleep(nanoseconds: 100_000_000)
33+
try await $syncUps.load()
3434
#expect(syncUps == [SyncUp(id: 1, isActive: true, title: "Engineering")])
3535
}
3636

@@ -43,19 +43,19 @@ struct IntegrationTests {
4343
_ = try SyncUp.insert(SyncUp.Draft(isActive: true, title: "Engineering"))
4444
.execute(db)
4545
}
46-
try await Task.sleep(nanoseconds: 100_000_000)
46+
try await $syncUps.load()
4747
#expect(syncUps == [SyncUp(id: 1, isActive: true, title: "Engineering")])
4848
try await database.write { db in
4949
_ = try SyncUp.upsert(SyncUp.Draft(id: 1, isActive: false, title: "Engineering"))
5050
.execute(db)
5151
}
52-
try await Task.sleep(nanoseconds: 100_000_000)
52+
try await $syncUps.load()
5353
#expect(syncUps == [])
5454
try await database.write { db in
5555
_ = try SyncUp.upsert(SyncUp.Draft(id: 1, isActive: true, title: "Engineering"))
5656
.execute(db)
5757
}
58-
try await Task.sleep(nanoseconds: 100_000_000)
58+
try await $syncUps.load()
5959
#expect(syncUps == [SyncUp(id: 1, isActive: true, title: "Engineering")])
6060
}
6161
}

Tests/SharingGRDBTests/SharingGRDBTests.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import Testing
99

1010
@Suite struct GRDBSharingTests {
1111
@Test
12-
func fetchOne() async throws {
13-
try await withDependencies {
12+
func fetchOne() throws {
13+
try withDependencies {
1414
$0.defaultDatabase = try DatabaseQueue()
1515
} operation: {
1616
@FetchOne(#sql("SELECT 1")) var bool = false
17-
try await Task.sleep(nanoseconds: 100_000_000)
1817
#expect(bool)
1918
#expect($bool.loadError == nil)
2019
}
@@ -30,13 +29,12 @@ import Testing
3029
}
3130
}
3231

33-
@Test func fetchSyntaxError() async throws {
34-
try await withDependencies {
32+
@Test func fetchSyntaxError() throws {
33+
try withDependencies {
3534
$0.defaultDatabase = try DatabaseQueue()
3635
} operation: {
3736
@FetchOne(#sql("SELEC 1")) var bool = false
3837
#expect(bool == false)
39-
try await Task.sleep(nanoseconds: 100_000_000)
4038
#expect($bool.loadError is DatabaseError?)
4139
let error = try #require($bool.loadError as? DatabaseError)
4240
#expect(error.message == #"near "SELEC": syntax error"#)
@@ -49,20 +47,18 @@ import Testing
4947
$0.defaultDatabase = try .database(named: name)
5048
} operation: {
5149
@SharedReader(.fetchAll(sql: "SELECT * FROM records")) var records1: [Record] = []
52-
try await Task.sleep(nanoseconds: 100_000_000)
5350
#expect(records1.map(\.id) == [1, 2, 3])
5451

5552
try await withDependencies {
5653
$0.defaultDatabase = try .database(named: name)
5754
} operation: {
5855
@Dependency(\.defaultDatabase) var database2
5956
@SharedReader(.fetchAll(sql: "SELECT * FROM records")) var records2: [Record] = []
60-
try await Task.sleep(nanoseconds: 100_000_000)
6157
#expect(records2.map(\.id) == [1, 2, 3])
6258
try await database2.write { db in
6359
_ = try Record.deleteOne(db, key: 1)
6460
}
65-
try await Task.sleep(nanoseconds: 100_000_000)
61+
try await $records2.load()
6662
#expect(records1.map(\.id) == [1, 2, 3])
6763
#expect(records2.map(\.id) == [2, 3])
6864
}

0 commit comments

Comments
 (0)