Experimental postgres query (PR 4/4): cancellation, timeout, TUI#5143
Open
simonfaltum wants to merge 9 commits intosimonfaltum/postgres-query-pr3-multi-inputfrom
Open
Experimental postgres query (PR 4/4): cancellation, timeout, TUI#5143simonfaltum wants to merge 9 commits intosimonfaltum/postgres-query-pr3-multi-inputfrom
simonfaltum wants to merge 9 commits intosimonfaltum/postgres-query-pr3-multi-inputfrom
Conversation
This is PR 4 of the experimental postgres query stack and finishes the plan's v1 scope. Cancellation watcher: pgx.ConnConfig now installs a CancelRequestContextWatcherHandler with CancelRequestDelay=0 (send the cancel-request immediately on ctx cancel) and DeadlineDelay=5s (fall back to deadlining the connection if the cancel-request hasn't terminated the query within 5s). Without this, Ctrl+C tears down the TCP connection but leaves the server-side query running until it next writes. Signal handling: a per-invocation signal goroutine watches SIGINT and SIGTERM, cancelling the connection-scoped ctx. The defer'd stop drains the signal channel so a queued signal during shutdown does not leak. On Windows, Go's console-control-handler routes Ctrl+C to os.Interrupt, so the same code path covers the Windows runner. --timeout: per-statement deadline applied via context.WithTimeout. A fresh deadline starts for each input unit; the connection-scoped ctx remains the parent so a SIGINT during unit N immediately cancels both. reportCancellation distinguishes the three error sources (ctx.Canceled, ctx.DeadlineExceeded, plain pg error) so the user-visible message is "Query cancelled.", "Query timed out after Xs.", or the formatted pg error respectively. TUI for >30 rows: when --output text and stdout is a prompt-capable TTY, results larger than staticTableThreshold (=30, matching aitools) hand off to libs/tableview's interactive viewer. Smaller results stay in the static tabwriter path so non-interactive callers see no change. Integration tests live in integration/cmd/postgres/. Skipped unless DATABRICKS_POSTGRES_INTEGRATION_TARGET is set; covers single-input JSON, command-only, --timeout firing, multi-input JSON, and a CSV streaming smoke test (generate_series(1, 100)). Ctrl+C is documented as needing a separate harness because it requires a child process. Co-authored-by: Isaac
This was referenced Apr 30, 2026
SHOULDs: - signals.go: drop the false "drains the signal channel" claim from the doc comment. signal.Stop blocks future deliveries; the 1-buffered channel is GC'd on return so no explicit drain is needed. - integration test: drop the dead `_ "cmd/experimental"` blank import (testcli.NewRunner already pulls cmd/experimental in transitively). - integration test: delete the cancel-on-interrupt stub; documented as a follow-up because Ctrl+C testing requires a child-process harness that's outside the scope of this PR. - query.go: when an invocation-scoped error fires (Ctrl+C, --timeout) in multi-input mode, drop the `<source>:` prefix. The user knows which invocation they cancelled; "--file foo.sql: Query cancelled." reads worse than "Query cancelled." reportCancellation now returns (msg, invocationScoped) so the caller picks the right shape. - withStatementTimeout: trim the v2-speculation from the doc comment. CONSIDERs: - C2: rename watchInterruptSignals's stop closure semantics to acknowledge it cancels the parent ctx as a side effect. - C4: TestReportCancellation_BothFire_CancelWinsRace pins the precedence (user cancel beats coincidental deadline). - C6: drop the redundant require.Error after RequireErrorRun (which already calls require.Error internally). Plus integration test polish: - Parse JSON outputs instead of substring-matching so encoder drift doesn't break tests. - Tighten timeout assertion from <5s to <3s so a regression to TCP-keepalive timeout (~minutes) would show. - Bump generate_series bound from 100 to 100k so streaming actually exercises memory pressure. Co-authored-by: Isaac
Round-2 reviewer flagged that the previous <3s bound was tight enough to flake on a cold Lakebase autoscaling endpoint, where auth + connect + cold-start can plausibly take >2s on its own. The regression we actually want to catch (silent fall-back to TCP keepalive) takes minutes, so <5s is enough. Add a warm-up RequireSuccessfulRun before timing so the assertion measures what it claims to measure: how long the 1-second deadline takes to actually cancel the in-flight statement. Co-authored-by: Isaac
Other experimental commands (aitools) have no integration tests; an experimental command is by definition pre-stabilization, and gating its real-wire test on a custom env var introduces friction without a clear win. Acceptance tests + unit tests already cover argument validation, targeting resolution (SDK-mocked), and the streaming / multi-input output shapes. The cancellation watcher and --timeout are unit-tested via the seam in cancel_test.go. When this command graduates from experimental, integration tests are the right addition; for v1 they were over-engineered. Co-authored-by: Isaac
4 tasks
…altum/postgres-query-pr4-cancel
…altum/postgres-query-pr4-cancel # Conflicts: # experimental/postgres/cmd/query.go
Contributor
|
JSON duplicate column name collision produces duplicate keys in output
No graceful degradation when
🔍 Reviewed by nitpicker |
…altum/postgres-query-pr4-cancel
If tableview.Run errors (TUI startup failure, terminal resize race, etc.), fall through to the static tabwriter path instead of returning the error to the caller. Without the fallback, a successful query surfaces as "viewer failed" with no data — the user paid for the query but doesn't see the rows. Co-authored-by: Isaac
arsenyinfo
approved these changes
Apr 30, 2026
…altum/postgres-query-pr4-cancel
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.
PR Stack
Stacked on PR 3.
Why
PR 3 finished the input ergonomics. The remaining commitments before this command earns the "experimental" label:
--timeoutso a single statement can't pin a runner.Changes
Before: Ctrl+C tears down TCP but the query runs to completion server-side.
--timeoutdoesn't exist. >30 rows scroll past in the terminal.Now: Ctrl+C cancels the in-flight query at the server.
--timeout 30senforces a per-statement deadline. >30 rows on a TTY open the libs/tableview viewer.Specifically:
buildPgxConfignow installsCancelRequestContextWatcherHandlerwithCancelRequestDelay=0, DeadlineDelay=5s. ZeroDeadlineDelaywould race the cancel-request and could leave the connection unusable; 5s gives the cancel round-trip time to land.context.WithTimeoutchild of the signal-scoped ctx.reportCancellationdistinguishes Ctrl+C ("Query cancelled."), timeout ("Query timed out after Xs."), and plain query errors. Returns(msg, invocationScoped)so the multi-input loop can drop the source prefix on user-cancel.textSinknow has aninteractivemode;runQueryenables it when stdout is a prompt-capable TTY. Static tabwriter table for small results and pipes; libs/tableview for big interactive ones. Iftableview.Runfails (TUI startup, terminal resize race) the sink falls through to the static tabwriter path so the user still sees the rows.Integration tests aren't included: aitools (the other experimental command) doesn't have them either. Acceptance + unit tests cover argument validation, targeting resolution (SDK-mocked), and output shapes; cancellation/timeout are unit-tested via the seam in
cancel_test.go. Real-wire integration tests are the right addition when this command graduates from experimental.Test plan
go test ./experimental/postgres/...(cancel/timeout/signal helpers, race-precedence pinning)go test ./acceptance -run TestAccept/cmd/(psql|experimental/postgres)(no regressions)go tool ... golangci-lint run ./experimental/postgres/...(0 issues)SELECT pg_sleep(60)cancels the server-side query within ~5s.