Expose query progress and connection id as SQL functions#389
Open
sfc-gh-mslot wants to merge 1 commit into
Open
Expose query progress and connection id as SQL functions#389sfc-gh-mslot wants to merge 1 commit into
sfc-gh-mslot wants to merge 1 commit into
Conversation
75584d0 to
e2145d5
Compare
e2145d5 to
e06ca1a
Compare
sfc-gh-mslot
commented
Jun 9, 2026
e06ca1a to
cebdcd1
Compare
Add two utility functions to the duckdb_pglake extension:
- pg_lake_connection_id() returns the connection identifier
pgduck_server has assigned to the current session. This is the
same value libpq's PQbackendPID returns to a client connected
to pgduck (and the same value reported in the connection_id
column of pg_lake_stat_activity), so a client can capture its
own id from libpq directly and only call this function from
SQL contexts that have no libpq access.
- pg_lake_query_progress(connection_id) returns at most one row
with the percentage / rows_processed / total_rows_to_process
values that DuckDB's executor publishes on
ClientContext::query_progress for the matching session. The
aggregator only measures source-side scans (postgres_scan,
parquet, csv, range, ...); sinks and post-scan operators are
not measured, so the percentage tracks "how much of the source
has been read", not wall-clock progress. The result is empty
when no session has the requested id or the matching session
has no active query.
Behind the scenes pgduck_server now seeds the DuckDB-side connection
identifier from the cancellation proc id (the random int32 it
already sends in BackendKeyData) instead of the raw client socket
fd. That unifies the value with libpq's PQbackendPID, makes
pg_lake_stat_activity look more like real postgres pg_stat_activity,
and avoids the rapid fd recycling that would otherwise let a stat
snapshot taken across a couple of disconnect/reconnect cycles show
the same "id" for two different sessions.
duckdb_pglake_init_connection also enables enable_progress_bar by
default for every new pgduck session and disables print_progress_bar
since pgduck has no terminal to render to. Without this default the
new pg_lake_query_progress function would silently return -1 / 0 / 0
even though everything else is wired up; turning the bar on lets the
executor populate ClientContext::query_progress after the standard
wait_time delay (default 2000ms). Clients can still issue
SET enable_progress_bar = false per session to opt back out.
The progress fields are atomic and DuckDB documents
GetQueryProgress as safe to call from another thread, which is the
same access pattern used internally by duckdb_query_progress.
Signed-off-by: Marco Slot <marco.slot@snowflake.com>
cebdcd1 to
3963577
Compare
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.
Problem
DuckDB's executor already tracks per-query progress (
ClientContext::query_progress, populated whenenable_progress_baris on), but the only way to read it from outside the running query was to callduckdb_query_progressagainst the sameduckdb_connection. There is no SQL surface for asking another session's progress, which makes it impossible for a sidecar / monitor connection to report on a running query.Solution
Two new SQL functions registered by the
duckdb_pglakeextension:pg_lake_query_progress(connection_id)iterates theConnectionManager's connection list (the same patternpg_lake_stat_activityuses), finds the session whosePgLakeQueryListener.connectionIdmatches the argument, and returns one row with the values fromClientContext::GetQueryProgress(). The result is empty when no session has the requested id or the matching session has no active query. Returning by id rather than as a global list keeps this function orthogonal to whatever shapepg_lake_stat_activityevolves into; if we later want progress alongside the activity columns we can extendpg_lake_stat_activityitself.The aggregator only measures source-side scans — postgres_scan, parquet, csv, range, and so on. Sinks (CSV serialization, S3 multipart upload, parquet writer flush, COPY TO) and post-scan operators (sort, join, aggregate beyond their input) are not measured. The percentage therefore tracks "how much of the source has been read", not wall-clock progress; for
COPY (SELECT FROM postgres_scan(...)) TO ...the bar can sit pinned near 100% while the destination side is still flushing.pg_lake_connection_id()reads the currentClientContext'sPgLakeQueryListener.connectionId. Behind the scenes, pgduck_server now seeds that identifier from the cancellation proc id (the random int32 it already sends inBackendKeyData) instead of the raw client socket fd. That unifies the value with libpq'sPQbackendPID, makespg_lake_stat_activitylook more like real postgrespg_stat_activity, and avoids the rapid fd recycling that would otherwise let a stat snapshot taken across a couple of disconnect/reconnect cycles show the same "id" for two different sessions.duckdb_pglake_init_connectionalso enablesenable_progress_barby default for every new pgduck session and disablesprint_progress_bar(no terminal to render to). Without this default the new function would silently return-1 / 0 / 0even though everything else is wired up; turning the bar on lets the executor populateClientContext::query_progressafter the standardwait_timedelay (default 2000ms). Clients can still issueSET enable_progress_bar = falseper session to opt back out.The progress struct's fields are atomic and DuckDB documents
GetQueryProgressas safe to call from another thread, which is the same access pattern used by the C-APIduckdb_query_progress.Test plan
pg_lake_connection_id()returns the same value asPQbackendPID(conn)from a libpq client connected to the same session.pg_lake_query_progress(-1)returns an empty result;pg_lake_query_progress(pg_lake_connection_id())returns one row.COPY (SELECT FROM postgres_scan(...)) TO ...(no manual SET) and B queryingpg_lake_query_progress(<id-from-stat-activity>), the percentage advances after the 2-second wait_time.pgduck_server/tests/pytests/test_query_progress.pycovering both functions and the empty-result-for-unknown-id case.