feat(editor): give each editor tab its own server session - #734
Merged
Merged
Conversation
A tab's statements went to whichever pooled connection was free, so anything tied to the session -- SET @var, SET SESSION, a temporary table, a transaction begun in one run and committed in the next -- could land on a connection where it did not exist, and silently not be there (#731). Other clients bind a tab or console to one session. Backend: ConnectionManager keeps per-tab sessions, one-connection pools opened on the tab's first statement and never recycled while it is open (no idle timeout, no max lifetime), with the same session setup as every lane. Route::Session sits beside the lanes; execute_query and explain_query take an optional session, and close_session ends one -- the server rolls back what the tab left uncommitted. Disconnect closes them all. Cancel still reaches a tab's statement (KILL QUERY leaves the session alive). A busy tab now says so instead of "pool timed out". The interactive-lane message says "queries" rather than "editor queries", since the editor no longer runs there. Frontend: the editor passes the active query tab's id as its session, through execution, the production confirmation, paging and explain. lib/server-sessions.ts ends a session when its tab closes or moves to another connection, from one editor-store subscription that starts with the first session. Everything that is not an editor tab still runs on the shared pool. Step 2 of #731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uLE7rJcohc7WDPstJ7yCL
…r test The SQLEditor browser test pinned the old three-argument call. The action now passes the active query tab's id as its session (#731), with no format of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uLE7rJcohc7WDPstJ7yCL
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.
Step 2 of #731.
Problem
An editor tab's statements went to whichever pooled connection was free. So anything tied to the server session could land on a connection where it didn't exist, and silently not be there:
SET @varandSET SESSION …DBeaver, DataGrip, Workbench and pgAdmin all bind a tab or console to one session.
Shown, not assumed: the new live tests fail against the old routing. With sessions pointed back at the shared pool, 7 of the 9 fail: the temporary table is gone, the transaction doesn't span runs, the variable leaks between tabs, and so on.
Change
Backend
ConnectionManagerkeeps a session per tab: a one-connection pool, opened on the tab's first statement. It is never recycled while the tab is open (no idle timeout, no max lifetime), since recycling it is exactly the state loss this fixes. It uses the same session setup as every lane, so its threads count as the app's own.Route::Session(tab)sits besideRoute::Lane(..).execute_queryandexplain_querytake an optionalsession. Explain has to run on the session too, orEXPLAIN SELECT … FROM tmpcan't see the tab's temporary table.close_sessionends a tab's session. The server then rolls back anything the tab left uncommitted, as closing a tab does in any client. Disconnect closes every session.KILL QUERYleaves the session alive, so the tab keeps its state.Frontend
lib/server-sessions.tsends a session when its tab closes or moves to another connection. It uses oneeditorStoresubscription, which starts with the first session, rather than a call in each of Close, Close Others, Close to the Right, the last-tab replacement and connection switching.session, since it has no server sessions.Behaviour to know
USE: the tab's database selector still wins. Each run starts withUSE <tab database>as before, so aUSE othertyped in an earlier run is reset on the next one.Verification
Run locally against MySQL 8.0 and MariaDB 11:
tests/editor_sessions.rs(9 live tests):COMMIT;close_sessionrolls back and releases row locks;EXPLAINsees the tab's temporary table;just test-integration: passes on both servers.cargo fmt --check,cargo clippy --all-targets --all-features -D warnings,just test-rustand the bindings regeneration all pass.bindings.tschanges only by the newsessionparameters andcloseSession.tsc,dprint checkandeslint(0 errors; the 162 warnings were already there) pass, and vitest has 2970 passing tests. New tests:lib/__tests__/server-sessions.test.ts: close rules, plus a realcloseTabending the session;resultStore: paging, confirmation and explain keep the session.--no-verify: the lefthookunit-testshook fails locally only onsrc/__tests__/main.test.tsx, a 10s hook timeout. It fails identically on untouchedmainin this environment and passes with a longer timeout (4/4 in 15s). CI's Test Frontend is the check that counts.🤖 Generated with Claude Code
https://claude.ai/code/session_019uLE7rJcohc7WDPstJ7yCL
Generated by Claude Code