fix(structure): save schema changes on the editing tab's own connection (#2015) - #2016
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2015.
Root cause
TableStructureView+Schema.swiftcalled a 3-argumentexecuteSchemaChanges(tableName:changes:databaseType:)that resolved its target from the app-globalDatabaseManager.currentSessionIdinstead of the editing tab's ownconnection.id, which was already in scope and used correctly by every other call in the same file.That global is never re-anchored by window focus. It moves on any fresh connect, on reopening an already-connected connection, on SSH tunnel recovery, and on connect failure, where it falls back to
activeSessions.keys.firston an unordered dictionary. With two connections open it routinely points somewhere other than the window the user is working in.Four consequences, all from that one missing argument:
driver(for:)ALTER TABLEran on the other connection's driver, inside its transaction. With a same-named table it silently mutated the wrong database. This is the "connection corruption" in the issue title.refreshData.send(connectionId)ExecutionGate.authorizerecordQueryThe tab that saved was meanwhile filtered out of its own refresh by its correct
guard changedConnectionId == connection.id, so it never refreshed while a window the user never touched did.Approach
Adding the missing argument would have made the symptom disappear and left the ambient overload as a trap for the next caller, so the overload is deleted instead. It had exactly one call site. The defect is now a compile error rather than something that can be reintroduced silently.
DBeaver hit this same class from a globally scoped active database and rescoped it per-editor in 6.3.1 (dbeaver/dbeaver#5172, #7473). Apple documents the same caveat on its own
NSDocumentController.currentDocument, prescribing resolution from the owning object rather than a global.Changes
executeSchemaChangesoverload. The remaining one takes explicitdatabaseName:,schemaName:, andconnectionId:.execute(query:),fetchTables(),fetchColumns(table:),reconnectCurrentSession(),activeDriver,status.currentSessionIdtolastActiveSessionIdandcurrentSessiontolastActiveSession, documenting that they are for switcher highlighting and for entry points with no window of their own (a new contentless window, a file opened from Finder), never for resolving the target of an operation.SELECT; a misdirectedALTER TABLEis silent and often irreversible.contentWindowinstead ofNSApp.keyWindow, which is nil when the app is inactive and points at the sheet while one is up.MainContentCoordinatoralready documentscontentWindowas existing for exactly this reason.Why the schema is pinned too
Switching database on a schema-grouped engine calls
resetSchemaand drops the driver to the engine's default schema, and those drivers qualify their DDL with whatever schema they are currently on. Without restoring it, a save onsales.ordersin MSSQL would have generatedALTER TABLE [dbo].[orders]. The schema is captured before the switch and restored after it.Tests
There was no test anywhere calling
executeSchemaChangeswith two active sessions and asserting where the DDL landed.TableProTests/Core/Database/DatabaseManagerSchemaChangeRoutingTests.swiftadds six:lastActiveSessionIdpoints at a different one. Direct regression test for this issue.swiftlint lint --strictis clean on every changed file, and these plusCancelledConnectionCleanupTests,DatabaseManagerSessionTests,DatabaseManagerDatabaseSwitchTests, andMultiConnectionTestspass.No UI automation: reproducing this needs two independently connected live sessions racing in CI, which the existing UI harness does not provide.
Reviewer notes
genuineFailureSwitchesToRemainingSessionassertedlastActiveSessionId == otherId, which only holds if that test owns the entire global session table. Swift Testing runs suites in parallel and several suites inject sessions, so it was already flaky; this suite widened the window and it failed once. It now asserts the real contract: the id moved off the failed session and onto one that still exists. The underlyingactiveSessions.keys.firstnondeterminism infinalizeConnectionFailureis real and left alone as separate scope.MetadataConnectionPoolkeys its pooled driver offsession.activeDatabaseread at call time rather than a tab-pinned value, so the same drift can affect metadata reads. That is a shared abstraction behind every metadata read, and this issue is a write landing on the wrong connection. Worth its own change.