Skip to content

Commit c0591b6

Browse files
committed
use GRDB
1 parent 472fcd0 commit c0591b6

18 files changed

Lines changed: 411 additions & 387 deletions

Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var dependencies: [Package.Dependency] = [
2424
.package(url: "https://github.com/apple/swift-collections.git", from: "1.2.0"),
2525
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0"),
2626
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"),
27+
.package(url: "https://github.com/groue/GRDB.swift.git", from: "6.29.3"),
2728
.package(url: "https://github.com/swiftlang/swift-syntax.git", exact: "603.0.0-prerelease-2025-10-30"),
2829
]
2930

@@ -82,7 +83,7 @@ var targets: [Target] = [
8283
.product(name: "Logging", package: "swift-log"),
8384
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
8485
.product(name: "Collections", package: "swift-collections"),
85-
"SQLite",
86+
.product(name: "GRDB", package: "GRDB.swift"),
8687
"ExceptionCatcher",
8788
"IMessageCore",
8889
],
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1+
import GRDB
2+
13
public extension IMDatabase {
24
func accountLogins() throws -> [String] {
3-
let statement = try cachedStatement(forEscapedSQL: """
4-
SELECT DISTINCT account_login
5-
FROM chat
6-
""")
7-
8-
try statement.reset()
9-
10-
return try statement.compactMapRowsUntilDone { row in
11-
try row[0].optional(String.self)
5+
try read { db in
6+
try Row.fetchAll(db, sql: """
7+
SELECT DISTINCT account_login
8+
FROM chat
9+
""").compactMap { $0[0] as String? }
1210
}
1311
}
1412
}

src/IMessage/Sources/IMDatabase/Database/IMDatabase+Attachments.swift

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Collections
2+
import GRDB
23
import Logging
3-
import SQLite
44
import IMessageCore
55

66
private let log = Logger(imessageLabel: "imdb.db")
@@ -14,14 +14,13 @@ LEFT JOIN attachment a ON a.ROWID = maj.attachment_id
1414

1515
extension IMDatabase {
1616
func hydrateAttachments(for message: inout Message) throws {
17-
let statement = try cachedStatement(forEscapedSQL: """
18-
\(attachmentQuerySharedPrologue)
19-
WHERE m.guid = ?
20-
""").reset()
21-
try statement.bind(message.guid)
22-
23-
let attachments = try statement.compactMapRowsUntilDone { row in
24-
try Attachment(row: row)
17+
let attachments = try read { db in
18+
try Row.fetchAll(db, sql: """
19+
\(attachmentQuerySharedPrologue)
20+
WHERE m.guid = ?
21+
""", arguments: [message.guid]).compactMap { row in
22+
try Attachment(row: row)
23+
}
2524
}
2625
message.attachments = attachments
2726
#if DEBUG
@@ -32,44 +31,45 @@ extension IMDatabase {
3231
func hydrateAttachments(for messages: inout OrderedDictionary<Message.ID, Message>) throws {
3332
let messageRowIDs = messages.keys.map(String.init)
3433

35-
let statement = try Statement.prepare(escapedSQL: """
36-
\(attachmentQuerySharedPrologue)
37-
WHERE m.ROWID IN (\(messageRowIDs.joined(separator: ",")))
38-
""", for: database)
34+
try read { db in
35+
let rows = try Row.fetchAll(db, sql: """
36+
\(attachmentQuerySharedPrologue)
37+
WHERE m.ROWID IN (\(messageRowIDs.joined(separator: ",")))
38+
""")
39+
for row in rows {
40+
let messageRowID = row.requiredInt(at: 0)
3941

40-
try statement.stepUntilDone { row in
41-
let messageRowID = try row[0].expect(Int.self)
42+
guard messages[messageRowID] != nil else {
43+
assertionFailure()
44+
continue
45+
}
4246

43-
guard messages[messageRowID] != nil else {
44-
assertionFailure()
45-
return
46-
}
47+
if messages[messageRowID]!.attachments == nil {
48+
messages[messageRowID]!.attachments = []
49+
}
4750

48-
if messages[messageRowID]!.attachments == nil {
49-
messages[messageRowID]!.attachments = []
50-
}
51+
guard let attachment = try Attachment(row: row) else {
52+
continue
53+
}
5154

52-
guard let attachment = try Attachment(row: row) else {
53-
return
55+
messages[messageRowID]!.attachments!.append(attachment)
5456
}
55-
56-
messages[messageRowID]!.attachments!.append(attachment)
5757
}
5858
}
5959
}
6060

6161
extension Attachment {
62-
init?(row: borrowing Row) throws {
62+
init?(row: Row) throws {
6363
// (skipping `m.ROWID`)
64-
guard let attachmentRowID = try row[1].optionalConverting(Int.self) else {
64+
guard let attachmentRowID = row.optionalInt(at: 1) else {
6565
return nil
6666
}
67-
let attachmentGUID = try GUID<Attachment>(row[2].expect(String.self))
68-
let fileName = try row[3].optionalConverting(String.self)
69-
let transferName = try row[4].optionalConverting(String.self)
70-
let isSticker = try row[5].looseBool()
71-
let transferState = try Attachment.IMFileTransferState(rawValue: row[6].expectConverting(Int.self))
72-
let uti = try row[7].optionalConverting(String.self)
67+
let attachmentGUID = GUID<Attachment>(row.requiredString(at: 2))
68+
let fileName = row.optionalString(at: 3)
69+
let transferName = row.optionalString(at: 4)
70+
let isSticker = row.looseBool(at: 5)
71+
let transferState = Attachment.IMFileTransferState(rawValue: row.requiredInt(at: 6))
72+
let uti = row.optionalString(at: 7)
7373

7474
self = Attachment(
7575
id: attachmentRowID,
Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import GRDB
12
import IMessageCore
23
import Logging
34

@@ -6,19 +7,16 @@ private let log = Logger(label: "imdb.chats")
67
public extension IMDatabase {
78
// TODO: replace with overload that takes `GUID`
89
func chat(withGUID chatGUID: String) throws -> Chat? {
9-
let statement = try cachedStatement(forEscapedSQL: """
10-
SELECT ROWID, display_name, service_name
11-
FROM chat
12-
WHERE guid = ?
13-
""")
14-
15-
try statement.reset()
16-
try statement.bind(chatGUID)
17-
18-
let chats = try statement.mapRowsUntilDone { row in
19-
let displayName = try row[1].optional(String.self)?.nonEmpty
20-
let serviceName = try Chat.ServiceName(rawValue: row[2].expect(String.self))
21-
return try Chat(id: row[0].expect(Int.self), guid: GUID(chatGUID), displayName: displayName, serviceName: serviceName)
10+
let chats = try read { db in
11+
try Row.fetchAll(db, sql: """
12+
SELECT ROWID, display_name, service_name
13+
FROM chat
14+
WHERE guid = ?
15+
""", arguments: [chatGUID]).map { row in
16+
let displayName = row.optionalString(at: 1)?.nonEmpty
17+
let serviceName = Chat.ServiceName(rawValue: row.requiredString(at: 2))
18+
return Chat(id: row.requiredInt(at: 0), guid: GUID(chatGUID), displayName: displayName, serviceName: serviceName)
19+
}
2220
}
2321

2422
if chats.count > 1 {
@@ -32,41 +30,36 @@ public extension IMDatabase {
3230
}
3331

3432
func chats() throws -> [Chat] {
35-
let statement = try cachedStatement(forEscapedSQL: """
36-
SELECT ROWID, guid, display_name, service_name
37-
FROM chat
38-
""")
39-
40-
try statement.reset()
41-
42-
return try statement.mapRowsUntilDone { row -> Chat? in
43-
let id = try row[0].expect(Int.self)
44-
guard let guid = try row[1].optional(String.self) else {
45-
log.error("chat \(id) has no GUID, very spooky. dropping it on the ground")
46-
return nil
33+
try read { db in
34+
try Row.fetchAll(db, sql: """
35+
SELECT ROWID, guid, display_name, service_name
36+
FROM chat
37+
""").compactMap { row -> Chat? in
38+
let id = row.requiredInt(at: 0)
39+
guard let guid = row.optionalString(at: 1) else {
40+
log.error("chat \(id) has no GUID, very spooky. dropping it on the ground")
41+
return nil
42+
}
43+
let displayName = row.optionalString(at: 2)?.nonEmpty
44+
let serviceName = Chat.ServiceName(rawValue: row.optionalString(at: 3) ?? "NONE")
45+
return Chat(id: id, guid: GUID(guid), displayName: displayName, serviceName: serviceName)
4746
}
48-
let displayName = try row[2].optional(String.self)?.nonEmpty
49-
let serviceName = try Chat.ServiceName(rawValue: row[3].optional(String.self) ?? "NONE")
50-
return Chat(id: id, guid: GUID(guid), displayName: displayName, serviceName: serviceName)
51-
}.compactMap(\.self)
47+
}
5248
}
5349

5450
// this doesn't include the user themselves, just everyone else in the group chat,
5551
// UNLESS the user went out of their way to redundantly add themselves, which is possible when initially creating the chat
5652
func handles(inChatWithGUID chatGUID: String) throws -> [Handle] {
57-
let statement = try cachedStatement(forEscapedSQL: """
58-
SELECT handle.ROWID, handle.id
59-
FROM chat
60-
INNER JOIN chat_handle_join ON chat_handle_join.chat_id = chat.ROWID
61-
INNER JOIN handle ON handle.ROWID = chat_handle_join.handle_id
62-
WHERE chat.guid = ?
63-
""")
64-
65-
try statement.reset()
66-
try statement.bind(chatGUID)
67-
68-
return try statement.mapRowsUntilDone { row in
69-
try Handle(rowid: row[0].expect(Int.self), id: row[1].expect(String.self))
53+
try read { db in
54+
try Row.fetchAll(db, sql: """
55+
SELECT handle.ROWID, handle.id
56+
FROM chat
57+
INNER JOIN chat_handle_join ON chat_handle_join.chat_id = chat.ROWID
58+
INNER JOIN handle ON handle.ROWID = chat_handle_join.handle_id
59+
WHERE chat.guid = ?
60+
""", arguments: [chatGUID]).map { row in
61+
Handle(rowid: row.requiredInt(at: 0), id: row.requiredString(at: 1))
62+
}
7063
}
7164
}
7265
}

0 commit comments

Comments
 (0)