@@ -337,11 +337,20 @@ def query(
337337 return response
338338 if not follow or not response .truncated :
339339 return response
340- return self ._materialize_full (response , row_guard , byte_guard )
340+ # Auto-follow re-fetches the persisted result and the query run, both of
341+ # which are scoped to a database via the required X-Database-Id header.
342+ # /v1/query takes exactly one database source, so the effective database
343+ # is the header value when present, otherwise the request-body
344+ # database_id.
345+ effective_db = x_database_id or query_request .database_id or ""
346+ return self ._materialize_full (
347+ response , effective_db , row_guard , byte_guard
348+ )
341349
342350 def wait_for_result (
343351 self ,
344352 result_id : str ,
353+ x_database_id : str ,
345354 * ,
346355 results_api : Optional [_ResultsApi ] = None ,
347356 ) -> Any :
@@ -355,6 +364,10 @@ def wait_for_result(
355364 every poll, materializing the whole result into memory before the size
356365 guards can act.
357366
367+ :param result_id: Result ID to poll.
368+ :param x_database_id: Database the result belongs to. Results are scoped
369+ to a database via the required ``X-Database-Id`` header — pass the
370+ same database the query ran in.
358371 :raises ResultFailedError: the result failed (delivered as HTTP 409).
359372 :raises ResultTimeoutError: ``ready`` not reached within the poll deadline.
360373 """
@@ -365,7 +378,7 @@ def wait_for_result(
365378 last_status = "pending"
366379 while True :
367380 try :
368- result = api .get_result (result_id , limit = 0 )
381+ result = api .get_result (result_id , x_database_id = x_database_id , limit = 0 )
369382 except ApiException as exc :
370383 # A failed result is delivered as HTTP 409: the generated client
371384 # raises (response_deserialize raises on any non-2xx) rather than
@@ -449,16 +462,19 @@ def _backoff_delay(self, exc: ApiException, attempt: int) -> float:
449462 def _materialize_full (
450463 self ,
451464 preview : QueryResponse ,
465+ x_database_id : str ,
452466 max_auto_rows : Optional [int ],
453467 max_auto_bytes : Optional [int ],
454468 ) -> QueryResponse :
455469 if preview .result_id is None :
456470 raise ResultUnavailableError (warning = preview .warning )
457471
458472 results_api = _ResultsApi (self .api_client )
459- self .wait_for_result (preview .result_id , results_api = results_api )
473+ self .wait_for_result (
474+ preview .result_id , x_database_id , results_api = results_api
475+ )
460476
461- total = self ._authoritative_total (preview )
477+ total = self ._authoritative_total (preview , x_database_id )
462478 # Auto-follow does extra round-trips (poll + paginate) and materializes
463479 # the full result; log it so the hidden work behind one query() call is
464480 # observable without being noisy (info, not a warning).
@@ -477,7 +493,12 @@ def _materialize_full(
477493 )
478494
479495 rows = self ._fetch_all_rows (
480- preview .result_id , total , max_auto_rows , max_auto_bytes , results_api
496+ preview .result_id ,
497+ x_database_id ,
498+ total ,
499+ max_auto_rows ,
500+ max_auto_bytes ,
501+ results_api ,
481502 )
482503 # Replace the bounded preview with the full row set. truncated /
483504 # total_row_count stay as the server reported them so the caller can
@@ -487,22 +508,30 @@ def _materialize_full(
487508 preview .total_row_count = total
488509 return preview
489510
490- def _authoritative_total (self , preview : QueryResponse ) -> Optional [int ]:
511+ def _authoritative_total (
512+ self , preview : QueryResponse , x_database_id : str
513+ ) -> Optional [int ]:
491514 """The grand total row count. ``total_row_count`` is null while a
492515 truncated result is still persisting, so fall back to the query-run
493516 record, which carries the authoritative count once the run succeeds.
517+
518+ The query run is scoped to a database via the required ``X-Database-Id``
519+ header, so ``x_database_id`` is forwarded to the lookup.
494520 """
495521 if preview .total_row_count is not None :
496522 return preview .total_row_count
497523 try :
498- run = _QueryRunsApi (self .api_client ).get_query_run (preview .query_run_id )
524+ run = _QueryRunsApi (self .api_client ).get_query_run (
525+ preview .query_run_id , x_database_id = x_database_id
526+ )
499527 except ApiException :
500528 return None
501529 return run .row_count
502530
503531 def _fetch_all_rows (
504532 self ,
505533 result_id : str ,
534+ x_database_id : str ,
506535 total : Optional [int ],
507536 max_auto_rows : Optional [int ],
508537 max_auto_bytes : Optional [int ],
@@ -515,6 +544,7 @@ def _fetch_all_rows(
515544 while True :
516545 page = results_api .get_result (
517546 result_id ,
547+ x_database_id = x_database_id ,
518548 offset = offset ,
519549 limit = page_size ,
520550 format = ResultsFormatQuery .JSON ,
0 commit comments