Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,23 @@ extension MainContentCoordinator {
// Wrap in a transaction for atomicity
try await driver.beginTransaction()

/// Rollback transaction and reset executing state for early exits.
@MainActor func rollbackAndResetState() async {
try? await driver.rollbackTransaction()
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
currentQueryTask = nil
toolbarState.setExecuting(false)
}

for (stmtIndex, sql) in statements.enumerated() {
guard !Task.isCancelled else { break }
guard !Task.isCancelled else {
await rollbackAndResetState()
return
}
guard capturedGeneration == queryGeneration else {
try? await driver.rollbackTransaction()
await rollbackAndResetState()
return
}

Expand Down Expand Up @@ -123,7 +136,19 @@ extension MainContentCoordinator {
try? await driver.rollbackTransaction()
}

guard capturedGeneration == queryGeneration else { return }
// Always reset isExecuting even if generation is stale —
// skipping this leaves the tab permanently stuck in "executing" state.
if capturedGeneration != queryGeneration {
await MainActor.run { [weak self] in
guard let self else { return }
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
currentQueryTask = nil
toolbarState.setExecuting(false)
}
return
}

let failedStmtIndex = executedCount + 1
let contextMsg = "Statement \(failedStmtIndex)/\(totalCount) failed: "
Expand Down Expand Up @@ -189,7 +214,13 @@ extension MainContentCoordinator {
toolbarState.setExecuting(false)
toolbarState.lastQueryDuration = cumulativeTime

guard capturedGeneration == queryGeneration else { return }
// Always reset isExecuting even if generation is stale
if capturedGeneration != queryGeneration {
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
return
}
guard let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) else {
return
}
Expand Down
21 changes: 17 additions & 4 deletions TablePro/Views/Main/MainContentCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,7 @@ final class MainContentCoordinator {
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
currentQueryTask = nil
toolbarState.setExecuting(false)
toolbarState.lastQueryDuration = safeExecutionTime
}
Expand Down Expand Up @@ -939,8 +940,13 @@ final class MainContentCoordinator {
toolbarState.setExecuting(false)
toolbarState.lastQueryDuration = safeExecutionTime

guard capturedGeneration == queryGeneration else { return }
guard !Task.isCancelled else { return }
// Always reset isExecuting even if generation is stale
if capturedGeneration != queryGeneration || Task.isCancelled {
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
return
}

applyPhase1Result(
tabId: tabId,
Expand Down Expand Up @@ -987,10 +993,17 @@ final class MainContentCoordinator {
}
}
} catch {
guard capturedGeneration == queryGeneration else { return }

// Always reset isExecuting even if generation is stale —
// skipping this leaves the tab permanently stuck in "executing"
// state, requiring a reconnect to recover.
await MainActor.run { [weak self] in
guard let self else { return }
if let idx = tabManager.tabs.firstIndex(where: { $0.id == tabId }) {
tabManager.tabs[idx].isExecuting = false
}
currentQueryTask = nil
toolbarState.setExecuting(false)
guard capturedGeneration == queryGeneration else { return }
handleQueryExecutionError(error, sql: sql, tabId: tabId, connection: conn)
}
}
Expand Down
Loading