From 13098429bf7fd076b2421a1476d5fc2c90dfafb1 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Tue, 28 Jul 2026 21:09:46 +0000 Subject: [PATCH 1/2] docs(bigquery): improve Query handles docs --- src/bigquery/src/query/query_handle.rs | 208 ++++++++++++++++++++-- src/bigquery/src/query/query_reference.rs | 10 -- src/bigquery/src/query/run_query.rs | 124 +++++++++++-- 3 files changed, 301 insertions(+), 41 deletions(-) diff --git a/src/bigquery/src/query/query_handle.rs b/src/bigquery/src/query/query_handle.rs index d27fbf804a..8041920008 100644 --- a/src/bigquery/src/query/query_handle.rs +++ b/src/bigquery/src/query/query_handle.rs @@ -19,32 +19,81 @@ use google_cloud_bigquery_v2::client::JobService; use google_cloud_bigquery_v2::model::{ GetQueryResultsRequest, GetQueryResultsResponse, Job, JobReference, QueryResponse, }; -use google_cloud_gax::backoff_policy::BackoffPolicy; use google_cloud_gax::exponential_backoff::ExponentialBackoffBuilder; use google_cloud_gax::polling_backoff_policy::PollingBackoffPolicy; use google_cloud_gax::polling_state::PollingState; use std::collections::VecDeque; use std::sync::Arc; -/// A handle representing a running query. +/// A handle representing a running or completed SQL query execution. +/// +/// An instance of `Query` is returned by [`RunQuery::run()`](crate::query::RunQuery::run). +/// Depending on how the query was routed and executed, this handle may represent an asynchronous +/// background job currently executing on BigQuery, or a fast-path query that has already completed. +/// +/// To obtain the final result set, call [`until_done()`](Query::until_done), which will check the +/// execution status and automatically poll the service if the job is still running in the background. +/// +/// # Example +/// +/// ``` +/// # async fn sample() -> anyhow::Result<()> { +/// use google_cloud_bigquery::client::BigQuery; +/// +/// let client = BigQuery::builder().build().await?; +/// let query_handle = client +/// .query("SELECT 42 AS answer") +/// .with_project_id("my-project-id") +/// .run() +/// .await?; +/// +/// // Poll until execution completes. +/// let completed = query_handle.until_done().await?; +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Debug)] pub struct Query { pub(crate) job_service: Arc, pub(crate) job_ref: Option, pub(crate) completed: bool, + #[allow(dead_code)] pub(crate) initial_job: Option, pub(crate) initial_response: Option, pub(crate) max_results: Option, } impl Query { - /// Returns the [`QueryReference`] for this query. + /// Returns the [`QueryReference`](crate::model::QueryReference) identifying this query execution. /// - /// The reference will be [`QueryReference::Job`] with a query [job reference], - /// or [`QueryReference::Stateless`] with an opaque query ID if job creation - /// was skipped. + /// The reference will be [`QueryReference::Job`](crate::model::QueryReference::Job) containing a BigQuery [job reference] + /// if a stateful query job was created, or [`QueryReference::Stateless`](crate::model::QueryReference::Stateless) with an opaque + /// query ID if the execution ran statelessly via the fast path. /// /// [job reference]: https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/JobReference + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// use google_cloud_bigquery::model::QueryReference; + /// + /// let client = BigQuery::builder().build().await?; + /// let query_handle = client + /// .query("SELECT 'hello' AS msg") + /// .with_project_id("my-project-id") + /// .run() + /// .await?; + /// + /// match query_handle.query_reference() { + /// QueryReference::Job(job_ref) => println!("Running job ID: {}", job_ref.job_id), + /// QueryReference::Stateless { query_id } => println!("Stateless query ID: {query_id}"), + /// _ => println!("Other query reference"), + /// } + /// # Ok(()) + /// # } + /// ``` pub fn query_reference(&self) -> QueryReference { let from_query_id = self .initial_response @@ -59,8 +108,34 @@ impl Query { .expect("query must have either a job reference or query id") } - /// Periodically checks the status of the background job until it finishes. - /// Returns an error if a remote service or connection failure happens during polling. + /// Periodically polls the background job status until query execution finishes. + /// + /// If the query was executed via the fast path and already completed during the initial request, + /// this method immediately returns a [`CompleteQuery`](crate::query::CompleteQuery) without making additional network calls. + /// Otherwise, it implements an automated exponential backoff loop querying the job status until it succeeds or fails. + /// + /// # Errors + /// + /// Returns an error if a remote service or network failure happens during polling, or if the BigQuery job + /// fails due to runtime execution errors (such as division by zero or resource limits). + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; + /// let complete = client + /// .query("SELECT 1 + 1 AS result") + /// .with_project_id("my-project-id") + /// .run() + /// .await? + /// .until_done() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub async fn until_done(self) -> Result { let Query { job_service, @@ -99,7 +174,32 @@ impl Query { } } -/// A handle representing a successfully completed query ready for reading. +/// A handle representing a successfully completed query ready for reading results. +/// +/// An instance of `CompleteQuery` is returned by [`Query::until_done()`](crate::query::Query::until_done). +/// +/// This handle provides access to cached execution metadata, schema definitions, and a streaming +/// row iterator via [`read()`](CompleteQuery::read). +/// +/// # Example +/// +/// ``` +/// # async fn sample() -> anyhow::Result<()> { +/// use google_cloud_bigquery::client::BigQuery; +/// +/// let client = BigQuery::builder().build().await?; +/// let complete = client +/// .query("SELECT 'done' AS status") +/// .with_project_id("my-project-id") +/// .run() +/// .await? +/// .until_done() +/// .await?; +/// +/// println!("Cache hit: {:?}", complete.metadata().cache_hit); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone)] pub struct CompleteQuery { pub(crate) job_service: Arc, @@ -177,19 +277,97 @@ impl CompleteQuery { } } - /// Returns a row iterator for the query result. + /// Returns a streaming row iterator for reading the query result set. + /// + /// Consumes the `CompleteQuery` handle and initializes a [`RowIterator`](crate::query::RowIterator) that + /// iterates over initial cached rows in memory before automatically fetching subsequent pages from the API. + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; + /// let mut rows = client + /// .query("SELECT 100 AS score") + /// .with_project_id("my-project-id") + /// .run() + /// .await? + /// .until_done() + /// .await? + /// .read(); + /// + /// while let Some(row) = rows.next().await.transpose()? { + /// let score: i64 = row.get("score"); + /// println!("Score: {score}"); + /// } + /// # Ok(()) + /// # } + /// ``` pub fn read(self) -> RowIterator { RowIterator::new(self) } - /// Returns the cached metadata for this query. + /// Returns a reference to the cached summary metadata for this query. + /// + /// The returned [`QueryMetadata`](crate::model::QueryMetadata) contains useful summary statistics such as + /// total rows, schema details, cache hit indicators, and estimated bytes processed without making additional RPC calls. + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; + /// let completed = client + /// .query("SELECT 'metadata_check'") + /// .with_project_id("my-project-id") + /// .run() + /// .await? + /// .until_done() + /// .await?; + /// + /// let meta = completed.metadata(); + /// println!("Total rows: {:?}", meta.total_rows); + /// # Ok(()) + /// # } + /// ``` pub fn metadata(&self) -> &QueryMetadata { &self.metadata } - /// Fetches the full `Job` information for the given query. + /// Fetches full job execution metadata from the service for this query. /// - /// Stateless queries will return `QueryError::StatelessQuery`. + /// Unlike [`metadata()`](CompleteQuery::metadata), this method executes an RPC request (`jobs.get`) to + /// retrieve complete runtime job details, including user email, execution timelines, billing tier estimates, and detailed error summaries. + /// + /// > [!IMPORTANT] + /// > Queries executed via the stateless fast-path do not create persistent job resources on the service. + /// > Calling this method on a stateless query will return [`QueryError::StatelessQuery`](crate::error::QueryError::StatelessQuery). + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; + /// let completed = client + /// .query("SELECT 1") + /// .with_project_id("my-project-id") + /// .set_allow_large_results(true) // Forces stateful job creation + /// .run() + /// .await? + /// .until_done() + /// .await?; + /// + /// let job_info = completed.job_metadata().await?; + /// println!("Executed by user: {}", job_info.user_email); + /// # Ok(()) + /// # } + /// ``` pub async fn job_metadata(&self) -> Result { let job_ref = self.job_ref.as_ref().ok_or(QueryError::StatelessQuery)?; @@ -253,9 +431,7 @@ pub(crate) async fn poll_query_results( #[cfg(test)] mod tests { use super::*; - use crate::query::tests::{ - MockBackoffPolicy, MockJobService, create_job_service, create_test_backoff_policy, - }; + use crate::query::tests::{MockJobService, create_job_service, create_test_backoff_policy}; use google_cloud_bigquery_v2::model::{ ErrorProto, GetQueryResultsResponse, Job, JobReference, QueryResponse, TableFieldSchema, TableSchema, diff --git a/src/bigquery/src/query/query_reference.rs b/src/bigquery/src/query/query_reference.rs index 0587e124ec..8d52051350 100644 --- a/src/bigquery/src/query/query_reference.rs +++ b/src/bigquery/src/query/query_reference.rs @@ -42,13 +42,6 @@ impl QueryReference { pub(crate) fn from_query_id(query_id: String) -> Self { Self::Stateless { query_id } } - - pub(crate) fn to_job_ref(&self) -> Option { - match self { - Self::Job(job_ref) => Some(job_ref.clone()), - Self::Stateless { .. } => None, - } - } } #[cfg(test)] @@ -64,7 +57,6 @@ mod tests { .set_location("US"); let job_ref = QueryReference::from(proto.clone()); assert_eq!(job_ref, QueryReference::Job(proto.clone())); - assert_eq!(job_ref.to_job_ref(), Some(proto)); // Without location let proto = google_cloud_bigquery_v2::model::JobReference::new() @@ -72,7 +64,6 @@ mod tests { .set_job_id("a-job-id"); let job_ref = QueryReference::from(proto.clone()); assert_eq!(job_ref, QueryReference::Job(proto.clone())); - assert_eq!(job_ref.to_job_ref(), Some(proto)); } #[test] @@ -84,6 +75,5 @@ mod tests { query_id: "a-query-id".to_string(), } ); - assert_eq!(query_ref.to_job_ref(), None); } } diff --git a/src/bigquery/src/query/run_query.rs b/src/bigquery/src/query/run_query.rs index c2b456234a..e8cc856d42 100644 --- a/src/bigquery/src/query/run_query.rs +++ b/src/bigquery/src/query/run_query.rs @@ -19,13 +19,63 @@ use crate::query::{Query, Result}; use google_cloud_bigquery_v2::client::JobService; use google_cloud_bigquery_v2::model::query_request::JobCreationMode; use google_cloud_bigquery_v2::model::{ - InsertJobRequest, Job, JobConfiguration, JobConfigurationQuery, PostQueryRequest, QueryRequest, + InsertJobRequest, Job, JobConfiguration, PostQueryRequest, QueryRequest, }; use std::sync::Arc; -/// A unified request builder for configuring and running a SQL query. -/// It automatically routes to either `jobs.query` (fast path) or `jobs.insert` (job path) -/// depending on the configured fields. +/// A unified request builder for configuring and executing a SQL query. +/// +/// Instances of this struct are returned by [`BigQuery::query()`](crate::client::BigQuery::query). +/// +/// This builder allows you to chain configuration methods to define query parameters, set dataset defaults, +/// specify locations, and configure result limitations before initiating execution with [`run()`](RunQuery::run). +/// +/// # Automatic Path Routing +/// +/// The builder automatically decides whether to execute via [`jobs.query`](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query) +/// (the low-latency fast path) or [`jobs.insert`](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert) +/// (the asynchronous background job creation path) depending on which configuration options are enabled: +/// +/// - **Fast path (`jobs.query`)**: Taken by default when executing queries with standard parameters and limits. +/// - **Job path (`jobs.insert`)**: Automatically chosen if options exclusive to job creation are configured (such as setting a destination table, enabling large result allowances, or customizing job labels). +/// +/// # Common Configuration Methods +/// +/// In addition to setting the target GCP project via [`with_project_id()`](RunQuery::with_project_id), +/// this builder inherits generated configuration setter methods including: +/// - `set_location("US")`: Sets the geographic routing location where the job should run. +/// - `set_max_results(100)`: Limits the number of rows buffered per result page from the API. +/// - `set_use_cache(true)`: Enables or disables query result caching (enabled by default). +/// - `set_dry_run(true)`: Validates the SQL syntax and calculates bytes processed without executing the query or incurring billing. +/// - `set_parameter_mode("NAMED")` and `set_query_parameters(...)`: Configures parameterized queries to prevent SQL injection and reuse execution plans. +/// +/// # Example +/// +/// ``` +/// # async fn sample() -> anyhow::Result<()> { +/// use google_cloud_bigquery::client::BigQuery; +/// +/// let client = BigQuery::builder().build().await?; +/// +/// // Configure and run a simple query with a custom geographic location and result limit. +/// let mut rows = client +/// .query("SELECT name FROM `bigquery-public-data.usa_names.usa_1910_2013` WHERE state = 'TX' LIMIT 100") +/// .with_project_id("my-project-id") +/// .set_location("US") +/// .set_max_results(50_u32) +/// .run() +/// .await? +/// .until_done() +/// .await? +/// .read(); +/// +/// while let Some(row) = rows.next().await.transpose()? { +/// let name: String = row.get("name"); +/// println!("Name: {name}"); +/// } +/// # Ok(()) +/// # } +/// ``` #[derive(Clone)] pub struct RunQuery { pub(crate) job_service: Arc, @@ -46,21 +96,67 @@ impl RunQuery { } } - /// Sets the project ID to override the default client project ID. + /// Sets the target Google Cloud Project ID for query execution and billing. + /// + /// This parameter is required before initiating execution with [`run()`](RunQuery::run). + /// If omitted, calling `run()` will return [`QueryError::MissingProjectId`](crate::error::QueryError::MissingProjectId). + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; + /// let query_handle = client + /// .query("SELECT 1 AS count") + /// .with_project_id("my-project-id") + /// .run() + /// .await?; + /// # Ok(()) + /// # } + /// ``` pub fn with_project_id>(mut self, project_id: S) -> Self { self.project_id = Some(project_id.into()); self } - /// Executes the SQL query + /// Submits the configured SQL query for execution. + /// + /// This is the terminal method of the [`RunQuery`] builder. Upon success, it returns a [`Query`](crate::query::Query) + /// handle representing either a running background job or a fast-path execution that has already finished. + /// You can call [`until_done()`](crate::query::Query::until_done) on the returned handle to wait for the final results. + /// + /// # Errors + /// + /// Returns [`QueryError::MissingProjectId`](crate::error::QueryError::MissingProjectId) if [`with_project_id()`](RunQuery::with_project_id) + /// was not called prior to running. Returns an RPC error if the initial service communication fails or if syntax errors occur during immediate fast-path validation. + /// + /// # Example + /// + /// ``` + /// # async fn sample() -> anyhow::Result<()> { + /// use google_cloud_bigquery::client::BigQuery; + /// + /// let client = BigQuery::builder().build().await?; /// - /// The implementation routes internally to [jobs.query] (fast path) - /// or [jobs.insert] (job path) depending on configured fields. - /// If the fast path is available, the client library takes it. - /// If not, it falls back to creating a job, which is typically slower. + /// // Execute the query and poll until complete. + /// let completed_query = client + /// .query("SELECT CURRENT_TIMESTAMP() AS now") + /// .with_project_id("my-project-id") + /// .run() + /// .await? + /// .until_done() + /// .await?; /// - /// [jobs.query]: https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query - /// [jobs.insert]: https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert + /// let mut rows = completed_query.read(); + /// if let Some(row) = rows.next().await.transpose()? { + /// let now: String = row.get("now"); + /// println!("Current time: {now}"); + /// } + /// # Ok(()) + /// # } + /// ``` pub async fn run(self) -> Result { let project_id = self.project_id.ok_or(QueryError::MissingProjectId)?; let max_results = self.request.max_results; @@ -102,11 +198,9 @@ mod tests { use crate::query::tests::{MockJobService, create_job_service}; use google_cloud_bigquery_v2::model::query_request::JobCreationMode; use google_cloud_bigquery_v2::model::{ - Job, JobConfiguration, JobConfigurationQuery, JobReference, JobStatus, QueryRequest, - QueryResponse, + Job, JobConfiguration, JobReference, JobStatus, QueryRequest, QueryResponse, }; use google_cloud_gax::response::Response; - use std::sync::Arc; type TestResult = anyhow::Result<()>; From 0628cea6be1d0c69ef19db446a1ddba266f8bc64 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Wed, 29 Jul 2026 14:18:28 +0000 Subject: [PATCH 2/2] docs: more improvements --- src/bigquery/src/query/query_handle.rs | 25 ++++++++++++++----------- src/bigquery/src/query/run_query.rs | 5 +++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/bigquery/src/query/query_handle.rs b/src/bigquery/src/query/query_handle.rs index 8041920008..2d851f1ac3 100644 --- a/src/bigquery/src/query/query_handle.rs +++ b/src/bigquery/src/query/query_handle.rs @@ -66,11 +66,12 @@ pub struct Query { impl Query { /// Returns the [`QueryReference`](crate::model::QueryReference) identifying this query execution. /// - /// The reference will be [`QueryReference::Job`](crate::model::QueryReference::Job) containing a BigQuery [job reference] - /// if a stateful query job was created, or [`QueryReference::Stateless`](crate::model::QueryReference::Stateless) with an opaque - /// query ID if the execution ran statelessly via the fast path. + /// The reference will be [`QueryReference::Job`](crate::model::QueryReference::Job) containing a BigQuery Query [job reference] + /// if a job was created, or [`QueryReference::Stateless`](crate::model::QueryReference::Stateless) with an opaque + /// query ID if the execution ran statelessly via [jobs.query]. /// /// [job reference]: https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/JobReference + /// [jobs.query]: https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query /// /// # Example /// @@ -112,12 +113,12 @@ impl Query { /// /// If the query was executed via the fast path and already completed during the initial request, /// this method immediately returns a [`CompleteQuery`](crate::query::CompleteQuery) without making additional network calls. - /// Otherwise, it implements an automated exponential backoff loop querying the job status until it succeeds or fails. + /// Otherwise, it implements a backoff loop querying the job status until it succeeds or fails. /// /// # Errors /// /// Returns an error if a remote service or network failure happens during polling, or if the BigQuery job - /// fails due to runtime execution errors (such as division by zero or resource limits). + /// fails due to runtime execution errors. /// /// # Example /// @@ -178,7 +179,7 @@ impl Query { /// /// An instance of `CompleteQuery` is returned by [`Query::until_done()`](crate::query::Query::until_done). /// -/// This handle provides access to cached execution metadata, schema definitions, and a streaming +/// This handle provides access to cached execution metadata, schema definitions, and a /// row iterator via [`read()`](CompleteQuery::read). /// /// # Example @@ -277,7 +278,7 @@ impl CompleteQuery { } } - /// Returns a streaming row iterator for reading the query result set. + /// Returns a row iterator for reading the query result set. /// /// Consumes the `CompleteQuery` handle and initializes a [`RowIterator`](crate::query::RowIterator) that /// iterates over initial cached rows in memory before automatically fetching subsequent pages from the API. @@ -338,26 +339,28 @@ impl CompleteQuery { &self.metadata } - /// Fetches full job execution metadata from the service for this query. + /// Fetches full [Job] execution metadata from the service for this query. /// /// Unlike [`metadata()`](CompleteQuery::metadata), this method executes an RPC request (`jobs.get`) to - /// retrieve complete runtime job details, including user email, execution timelines, billing tier estimates, and detailed error summaries. + /// retrieve complete job details, including user email, execution timelines, billing tier estimates, and detailed error summaries. /// /// > [!IMPORTANT] - /// > Queries executed via the stateless fast-path do not create persistent job resources on the service. /// > Calling this method on a stateless query will return [`QueryError::StatelessQuery`](crate::error::QueryError::StatelessQuery). /// + /// [Job]: https://docs.cloud.google.com/bigquery/docs/reference/rest/v2/Job + /// /// # Example /// /// ``` /// # async fn sample() -> anyhow::Result<()> { /// use google_cloud_bigquery::client::BigQuery; + /// use google_cloud_bigquery::model::query_request::JobCreationMode; /// /// let client = BigQuery::builder().build().await?; /// let completed = client /// .query("SELECT 1") /// .with_project_id("my-project-id") - /// .set_allow_large_results(true) // Forces stateful job creation + /// .set_job_creation_mode(JobCreationMode::JobCreationRequired) // Forces a job to be created /// .run() /// .await? /// .until_done() diff --git a/src/bigquery/src/query/run_query.rs b/src/bigquery/src/query/run_query.rs index e8cc856d42..e7b01666f0 100644 --- a/src/bigquery/src/query/run_query.rs +++ b/src/bigquery/src/query/run_query.rs @@ -34,15 +34,16 @@ use std::sync::Arc; /// /// The builder automatically decides whether to execute via [`jobs.query`](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query) /// (the low-latency fast path) or [`jobs.insert`](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert) -/// (the asynchronous background job creation path) depending on which configuration options are enabled: +/// (the async job creation path) depending on which configuration options are enabled: /// /// - **Fast path (`jobs.query`)**: Taken by default when executing queries with standard parameters and limits. -/// - **Job path (`jobs.insert`)**: Automatically chosen if options exclusive to job creation are configured (such as setting a destination table, enabling large result allowances, or customizing job labels). +/// - **Job path (`jobs.insert`)**: Chosen if options require job creation (such as setting a destination table, enabling large result allowances, or customizing job labels). /// /// # Common Configuration Methods /// /// In addition to setting the target GCP project via [`with_project_id()`](RunQuery::with_project_id), /// this builder inherits generated configuration setter methods including: +/// - `set_job_creation_mode(JobCreationMode::JobCreationRequired)`: Explicitly forces job creation. By default, the SDK sets this as JobCreationOptional. /// - `set_location("US")`: Sets the geographic routing location where the job should run. /// - `set_max_results(100)`: Limits the number of rows buffered per result page from the API. /// - `set_use_cache(true)`: Enables or disables query result caching (enabled by default).