Skip to content

Commit 8fe9ed9

Browse files
authored
Merge pull request #1 from datlechin/feat/history
feat: Query history
2 parents d8ac3d0 + f544f39 commit 8fe9ed9

16 files changed

Lines changed: 3570 additions & 8 deletions

OpenTable/ContentView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct ContentView: View {
2323

2424
@Environment(\.openWindow) private var openWindow
2525
@Environment(\.dismissWindow) private var dismissWindow
26+
@EnvironmentObject private var appState: AppState
2627

2728
private let storage = ConnectionStorage.shared
2829

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//
2+
// QueryHistoryManager.swift
3+
// OpenTable
4+
//
5+
// Thread-safe coordinator for query history and bookmarks
6+
// Communicates via NotificationCenter (NOT ObservableObject)
7+
//
8+
9+
import Foundation
10+
11+
/// Notification names for query history updates
12+
extension Notification.Name {
13+
static let queryHistoryDidUpdate = Notification.Name("queryHistoryDidUpdate")
14+
static let queryBookmarksDidUpdate = Notification.Name("queryBookmarksDidUpdate")
15+
static let loadQueryIntoEditor = Notification.Name("loadQueryIntoEditor")
16+
}
17+
18+
/// Thread-safe manager for query history and bookmarks
19+
/// NOT an ObservableObject - uses NotificationCenter for UI communication
20+
final class QueryHistoryManager {
21+
static let shared = QueryHistoryManager()
22+
23+
private let storage = QueryHistoryStorage.shared
24+
25+
private init() {
26+
// Perform cleanup on initialization (app launch)
27+
storage.cleanup()
28+
}
29+
30+
// MARK: - History Capture
31+
32+
/// Record a query execution (non-blocking background write)
33+
func recordQuery(
34+
query: String,
35+
connectionId: UUID,
36+
databaseName: String,
37+
executionTime: TimeInterval,
38+
rowCount: Int,
39+
wasSuccessful: Bool,
40+
errorMessage: String? = nil
41+
) {
42+
let entry = QueryHistoryEntry(
43+
query: query,
44+
connectionId: connectionId,
45+
databaseName: databaseName,
46+
executionTime: executionTime,
47+
rowCount: rowCount,
48+
wasSuccessful: wasSuccessful,
49+
errorMessage: errorMessage
50+
)
51+
52+
// Background write (non-blocking)
53+
storage.addHistory(entry) { success in
54+
if success {
55+
// Notify UI to refresh on main thread
56+
DispatchQueue.main.async {
57+
NotificationCenter.default.post(
58+
name: .queryHistoryDidUpdate,
59+
object: nil
60+
)
61+
}
62+
}
63+
}
64+
}
65+
66+
// MARK: - History Retrieval
67+
68+
/// Fetch history entries (synchronous - safe for UI)
69+
func fetchHistory(
70+
limit: Int = 100,
71+
offset: Int = 0,
72+
connectionId: UUID? = nil,
73+
searchText: String? = nil,
74+
dateFilter: DateFilter = .all
75+
) -> [QueryHistoryEntry] {
76+
return storage.fetchHistory(
77+
limit: limit,
78+
offset: offset,
79+
connectionId: connectionId,
80+
searchText: searchText,
81+
dateFilter: dateFilter
82+
)
83+
}
84+
85+
/// Search queries using FTS5 full-text search
86+
func searchQueries(_ text: String) -> [QueryHistoryEntry] {
87+
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
88+
return fetchHistory()
89+
}
90+
return storage.fetchHistory(searchText: text)
91+
}
92+
93+
/// Delete a history entry
94+
func deleteHistory(id: UUID) -> Bool {
95+
let success = storage.deleteHistory(id: id)
96+
if success {
97+
NotificationCenter.default.post(name: .queryHistoryDidUpdate, object: nil)
98+
}
99+
return success
100+
}
101+
102+
/// Get total history count
103+
func getHistoryCount() -> Int {
104+
return storage.getHistoryCount()
105+
}
106+
107+
// MARK: - Bookmarks
108+
109+
/// Save a new bookmark
110+
func saveBookmark(
111+
name: String,
112+
query: String,
113+
connectionId: UUID? = nil,
114+
tags: [String] = [],
115+
notes: String? = nil
116+
) -> Bool {
117+
let bookmark = QueryBookmark(
118+
name: name,
119+
query: query,
120+
connectionId: connectionId,
121+
tags: tags,
122+
notes: notes
123+
)
124+
125+
let success = storage.addBookmark(bookmark)
126+
if success {
127+
NotificationCenter.default.post(name: .queryBookmarksDidUpdate, object: nil)
128+
}
129+
return success
130+
}
131+
132+
/// Save bookmark from history entry
133+
func saveBookmarkFromHistory(_ entry: QueryHistoryEntry, name: String) -> Bool {
134+
return saveBookmark(
135+
name: name,
136+
query: entry.query,
137+
connectionId: entry.connectionId
138+
)
139+
}
140+
141+
/// Update an existing bookmark
142+
func updateBookmark(_ bookmark: QueryBookmark) -> Bool {
143+
let success = storage.updateBookmark(bookmark)
144+
if success {
145+
NotificationCenter.default.post(name: .queryBookmarksDidUpdate, object: nil)
146+
}
147+
return success
148+
}
149+
150+
/// Update bookmark's last used timestamp
151+
func markBookmarkUsed(id: UUID) {
152+
if var bookmark = fetchBookmarks().first(where: { $0.id == id }) {
153+
bookmark.lastUsedAt = Date()
154+
_ = storage.updateBookmark(bookmark)
155+
}
156+
}
157+
158+
/// Fetch bookmarks with optional filters
159+
func fetchBookmarks(searchText: String? = nil, tag: String? = nil) -> [QueryBookmark] {
160+
return storage.fetchBookmarks(searchText: searchText, tag: tag)
161+
}
162+
163+
/// Delete a bookmark
164+
func deleteBookmark(id: UUID) -> Bool {
165+
let success = storage.deleteBookmark(id: id)
166+
if success {
167+
NotificationCenter.default.post(name: .queryBookmarksDidUpdate, object: nil)
168+
}
169+
return success
170+
}
171+
172+
// MARK: - Cleanup
173+
174+
/// Manually trigger cleanup (normally runs automatically)
175+
func cleanup() {
176+
storage.cleanup()
177+
}
178+
}

0 commit comments

Comments
 (0)