Skip to content

Commit d5e1e65

Browse files
authored
Merge pull request #205 from r3dbars/codex/performance-audit-stats-query
[codex] Optimize recent stats queries
2 parents 7a93432 + 5083e69 commit d5e1e65

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

Sources/TranscriptedCore/Stats/StatsDatabase.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ public final class StatsDatabase {
164164

165165
// Create indexes for common queries
166166
let createDateIndex = "CREATE INDEX IF NOT EXISTS idx_recordings_date ON recordings(date);"
167+
let createDateTimeIndex = "CREATE INDEX IF NOT EXISTS idx_recordings_date_time ON recordings(date DESC, time DESC);"
167168
executeSQL(createRecordingsTable)
168169
executeSQL(createDailyActivityTable)
169170
executeSQL(createDateIndex)
171+
executeSQL(createDateTimeIndex)
170172
}
171173

172174
func executeSQL(_ sql: String) {
@@ -269,6 +271,41 @@ public final class StatsDatabase {
269271
return recordings
270272
}
271273

274+
/// Get the most recent recordings without materializing the full table.
275+
public func getRecentRecordings(limit: Int) -> [RecordingMetadata] {
276+
guard limit > 0 else { return [] }
277+
return queue.sync {
278+
getRecentRecordingsImpl(limit: limit)
279+
}
280+
}
281+
282+
private func getRecentRecordingsImpl(limit: Int) -> [RecordingMetadata] {
283+
var recordings: [RecordingMetadata] = []
284+
285+
let sql = """
286+
SELECT id, date, time, duration_seconds, word_count, speaker_count, processing_time_ms, transcript_path, title
287+
FROM recordings
288+
ORDER BY date DESC, time DESC
289+
LIMIT ?;
290+
"""
291+
var statement: OpaquePointer?
292+
293+
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
294+
sqlite3_bind_int(statement, 1, Int32(limit))
295+
296+
while sqlite3_step(statement) == SQLITE_ROW {
297+
if let recording = recordingMetadataFromRow(statement) {
298+
recordings.append(recording)
299+
}
300+
}
301+
} else {
302+
AppLogger.stats.error("Failed to prepare getRecentRecordings", ["sqlite_error": dbErrorMessage()])
303+
}
304+
305+
sqlite3_finalize(statement)
306+
return recordings
307+
}
308+
272309
/// Parses a RecordingMetadata from the current row of a prepared SELECT statement.
273310
/// Expected column order: id(0), date(1), time(2), duration_seconds(3), word_count(4),
274311
/// speaker_count(5), processing_time_ms(6), transcript_path(7), title(8)

Sources/TranscriptedCore/Stats/StatsService.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ public final class StatsService: ObservableObject {
9797
calculateStreaks()
9898

9999
// Get recent transcripts
100-
let allRecordings = database.getAllRecordings()
101-
recentTranscripts = Array(allRecordings.prefix(3))
100+
recentTranscripts = database.getRecentRecordings(limit: 3)
102101

103102
// Update motivational message
104103
updateMotivationalMessage()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import XCTest
2+
@testable import TranscriptedCore
3+
4+
@available(macOS 14.0, *)
5+
final class StatsDatabaseTests: XCTestCase {
6+
7+
func testGetRecentRecordingsHonorsLimitAndSortOrder() {
8+
let databaseURL = FileManager.default.temporaryDirectory
9+
.appendingPathComponent("TranscriptedCoreStatsTests-\(UUID().uuidString).sqlite")
10+
11+
let calendar = Calendar(identifier: .gregorian)
12+
let rows = [
13+
RecordingMetadata(id: "oldest", date: calendar.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 9, minute: 0))!, durationSeconds: 60),
14+
RecordingMetadata(id: "middle", date: calendar.date(from: DateComponents(year: 2026, month: 4, day: 6, hour: 10, minute: 30))!, durationSeconds: 120),
15+
RecordingMetadata(id: "newer", date: calendar.date(from: DateComponents(year: 2026, month: 4, day: 7, hour: 8, minute: 15))!, durationSeconds: 180),
16+
RecordingMetadata(id: "newest", date: calendar.date(from: DateComponents(year: 2026, month: 4, day: 7, hour: 18, minute: 45))!, durationSeconds: 240),
17+
]
18+
19+
do {
20+
let database = StatsDatabase(path: databaseURL.path)
21+
22+
for row in rows {
23+
database.recordSession(row)
24+
}
25+
database.queue.sync {}
26+
27+
XCTAssertEqual(database.getRecentRecordings(limit: 2).map(\.id), ["newest", "newer"])
28+
XCTAssertEqual(database.getRecentRecordings(limit: 10).map(\.id), ["newest", "newer", "middle", "oldest"])
29+
XCTAssertTrue(database.getRecentRecordings(limit: 0).isEmpty)
30+
}
31+
32+
for suffix in ["", "-shm", "-wal"] {
33+
try? FileManager.default.removeItem(at: URL(fileURLWithPath: databaseURL.path + suffix))
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)