Skip to content

Commit 467d2f1

Browse files
committed
Fix: Table tabs now load data when opened as first tab
Resolved race condition where opening a table tab as the first tab (no other tabs open) would not trigger data loading. Root cause: When two async runQuery() calls occurred (one from openTableData, one from lazy load in handleTabChange), the second call would cancel the first task via currentQueryTask?.cancel() before checking the isExecuting guard. This left the first query cancelled without starting a new one. Fix: Moved currentQueryTask?.cancel() after the isExecuting guard check, ensuring we only cancel previous tasks when actually starting a new query. Changes: - Moved task cancellation logic after isExecuting guard in runQuery() - Removed excessive debug logging added during investigation - Kept only critical warning logs for errors
1 parent 0da8bb1 commit 467d2f1

File tree

1 file changed

+42
-68
lines changed

1 file changed

+42
-68
lines changed

OpenTable/Views/MainContentView.swift

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,6 @@ struct MainContentView: View {
385385
tabManager.tabs = restoredTabs
386386
tabManager.selectedTabId = savedState.selectedTabId
387387
didRestoreTabs = true
388-
389-
print("[MainContentView] Restored \(restoredTabs.count) tabs from disk")
390-
// Debug: Print query text to verify restoration
391-
for (index, tab) in restoredTabs.enumerated() {
392-
print("[MainContentView] Tab \(index): \"\(tab.title)\" - Query: \(tab.query.prefix(50))...")
393-
}
394388
} else if let sessionId = DatabaseManager.shared.currentSessionId,
395389
let session = DatabaseManager.shared.activeSessions[sessionId],
396390
!session.tabs.isEmpty {
@@ -401,51 +395,35 @@ struct MainContentView: View {
401395
tabManager.tabs = session.tabs
402396
tabManager.selectedTabId = session.selectedTabId
403397
didRestoreTabs = true
404-
405-
print("[MainContentView] Restored \(session.tabs.count) tabs from session")
406398
}
407-
// CRITICAL: Execute query for table tabs to load data
408-
// Query tabs only restore text without auto-execution
399+
// Execute query for table tabs to load data
409400
if didRestoreTabs {
410-
if let selectedTab = tabManager.selectedTab {
411-
print("[MainContentView] Restored tab: \(selectedTab.title), type: \(selectedTab.tabType), hasQuery: \(!selectedTab.query.isEmpty)")
401+
if let selectedTab = tabManager.selectedTab,
402+
selectedTab.tabType == .table,
403+
!selectedTab.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
412404

413-
if selectedTab.tabType == .table,
414-
!selectedTab.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
415-
print("[MainContentView] Waiting for connection to be ready...")
416-
417-
// CRITICAL: Wait for connection to be established
418-
// Without this, query fails with "Not connected to database"
419-
var retryCount = 0
420-
while retryCount < 50 { // Max 5 seconds
421-
if let session = DatabaseManager.shared.currentSession,
422-
session.isConnected {
423-
print("[MainContentView] Connection ready! Executing query for restored table tab: \(selectedTab.title)")
424-
425-
// Small delay to ensure everything is initialized
426-
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1s
427-
await MainActor.run {
428-
justRestoredTab = true // Prevent lazy load from executing again
429-
runQuery()
430-
}
431-
break
405+
// Wait for connection to be established
406+
var retryCount = 0
407+
while retryCount < 50 { // Max 5 seconds
408+
if let session = DatabaseManager.shared.currentSession,
409+
session.isConnected {
410+
// Small delay to ensure everything is initialized
411+
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1s
412+
await MainActor.run {
413+
justRestoredTab = true // Prevent lazy load from executing again
414+
runQuery()
432415
}
433-
434-
// Wait 100ms and retry
435-
try? await Task.sleep(nanoseconds: 100_000_000)
436-
retryCount += 1
416+
break
437417
}
438418

439-
if retryCount >= 50 {
440-
print("[MainContentView] Warning: Connection timeout, query not executed")
441-
}
442-
} else if selectedTab.tabType == .query {
443-
print("[MainContentView] Restored query tab - skipping auto-execution")
444-
} else {
445-
print("[MainContentView] Restored table tab but no query - skipping auto-execution")
419+
// Wait 100ms and retry
420+
try? await Task.sleep(nanoseconds: 100_000_000)
421+
retryCount += 1
422+
}
423+
424+
if retryCount >= 50 {
425+
print("[MainContentView] ⚠️ Connection timeout, query not executed")
446426
}
447-
} else {
448-
print("[MainContentView] No selected tab after restore")
449427
}
450428
}
451429
}
@@ -896,17 +874,19 @@ struct MainContentView: View {
896874
return
897875
}
898876

877+
guard !tabManager.tabs[index].isExecuting else {
878+
return
879+
}
880+
899881
// Cancel any previous running query to prevent race conditions
900-
// This is critical for SSH connections where rapid sorting can cause
901-
// multiple queries to return out of order, leading to EXC_BAD_ACCESS
882+
// IMPORTANT: Only cancel AFTER checking isExecuting, otherwise we cancel
883+
// a valid running query without starting a new one
902884
currentQueryTask?.cancel()
903885

904886
// Increment generation - any query with a different generation will be ignored
905887
queryGeneration += 1
906888
let capturedGeneration = queryGeneration
907889

908-
guard !tabManager.tabs[index].isExecuting else { return }
909-
910890
tabManager.tabs[index].isExecuting = true
911891
tabManager.tabs[index].executionTime = nil
912892
tabManager.tabs[index].errorMessage = nil
@@ -1019,13 +999,16 @@ struct MainContentView: View {
1019999

10201000
// Find tab by ID (index may have changed) - must update on main thread
10211001
await MainActor.run {
1002+
10221003
// ALWAYS update toolbar state first - user should see query completion
10231004
toolbarState.isExecuting = false
10241005
toolbarState.lastQueryDuration = safeExecutionTime
10251006

10261007
// Only update tab if this is still the most recent query
10271008
// This prevents race conditions when navigating quickly between tables
1028-
guard capturedGeneration == queryGeneration else { return }
1009+
guard capturedGeneration == queryGeneration else {
1010+
return
1011+
}
10291012
guard !Task.isCancelled else { return }
10301013

10311014
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
@@ -1885,7 +1868,6 @@ struct MainContentView: View {
18851868
let newIndex = tabManager.tabs.firstIndex(where: { $0.id == newId })
18861869
{
18871870
let newTab = tabManager.tabs[newIndex]
1888-
print("[MainContentView] Tab change: \(oldTabId == nil ? "nil" : "tab") -> \(newTab.title) (type: \(newTab.tabType))")
18891871

18901872
// CRITICAL: Update these immediately for UI consistency
18911873
selectedRowIndices = newTab.selectedRowIndices
@@ -1912,38 +1894,22 @@ struct MainContentView: View {
19121894
// Otherwise, if lazy load is skipped due to flag=true, flag never resets!
19131895
let shouldSkipLazyLoad = justRestoredTab
19141896
if justRestoredTab {
1915-
print("[MainContentView] Resetting justRestoredTab flag")
19161897
justRestoredTab = false
19171898
}
19181899

1919-
// LAZY LOAD: Auto-execute query ONLY for table tabs
1920-
// Query tabs require manual execution by user (Cmd+Return)
1921-
// Skip if we just restored and executed this tab
1922-
1923-
// DEBUG: Log all conditions
1924-
print("[MainContentView] Lazy load check for \(newTab.title):")
1925-
print(" - shouldSkipLazyLoad: \(shouldSkipLazyLoad)")
1926-
print(" - tabType: \(newTab.tabType)")
1927-
print(" - resultRows.isEmpty: \(newTab.resultRows.isEmpty)")
1928-
print(" - lastExecutedAt == nil: \(newTab.lastExecutedAt == nil)")
1929-
print(" - has query: \(!newTab.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)")
19301900

19311901
if !shouldSkipLazyLoad &&
19321902
newTab.tabType == .table && // Only auto-execute for table tabs
19331903
newTab.resultRows.isEmpty &&
19341904
newTab.lastExecutedAt == nil &&
19351905
!newTab.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
19361906

1937-
// CRITICAL: Check connection before executing
1907+
// Check connection before executing
19381908
if let session = DatabaseManager.shared.currentSession, session.isConnected {
1939-
print("[MainContentView] Lazy loading data for table tab: \(newTab.title)")
19401909
runQuery()
19411910
} else {
1942-
print("[MainContentView] Table tab needs data but not connected - setting flag")
19431911
needsLazyLoad = true
19441912
}
1945-
} else {
1946-
print("[MainContentView] Skipping lazy load (one or more conditions not met)")
19471913
}
19481914
}
19491915
} else {
@@ -2280,7 +2246,15 @@ struct MainContentView: View {
22802246
// For existing tabs, onChange will restore their saved selection
22812247
if needsQuery {
22822248
selectedRowIndices = []
2283-
runQuery()
2249+
2250+
// Execute query for new/replaced tabs
2251+
// IMPORTANT: Wrapped in Task to ensure SwiftUI processes tab property updates first
2252+
// - For NEW tabs: selectedTabId changes → onChange fires → lazy load also triggers
2253+
// (both will try to run query, but the second will be blocked by isExecuting guard)
2254+
// - For REPLACED tabs: selectedTabId stays same → onChange doesn't fire → we MUST call runQuery
2255+
Task { @MainActor in
2256+
runQuery()
2257+
}
22842258
}
22852259
}
22862260

0 commit comments

Comments
 (0)