1use crate::model::DirectedReadOptions;
16use crate::model::execute_sql_request::QueryMode;
17use crate::model::execute_sql_request::QueryOptions;
18use crate::model::request_options::Priority;
19use crate::to_value::ToValue;
20use crate::types::Type;
21use crate::value::Value;
22use google_cloud_gax::backoff_policy::BackoffPolicyArg;
23use google_cloud_gax::options::RequestOptions as GaxRequestOptions;
24use google_cloud_gax::retry_policy::RetryPolicyArg;
25use std::collections::BTreeMap;
26use std::time::Duration;
27
28#[derive(Clone, Debug)]
38pub struct StatementBuilder {
39 sql: String,
40 params: BTreeMap<String, Value>,
41 param_types: BTreeMap<String, Type>,
42 request_options: Option<crate::model::RequestOptions>,
43 directed_read_options: Option<DirectedReadOptions>,
44 query_options: Option<QueryOptions>,
45 query_mode: Option<QueryMode>,
46 gax_options: GaxRequestOptions,
47}
48
49impl StatementBuilder {
50 pub(crate) fn new(sql: impl Into<String>) -> Self {
51 Self {
52 sql: sql.into(),
53 params: BTreeMap::new(),
54 param_types: BTreeMap::new(),
55 request_options: None,
56 directed_read_options: None,
57 query_options: None,
58 query_mode: None,
59 gax_options: GaxRequestOptions::default(),
60 }
61 }
62
63 pub fn add_param<T: ToValue + ?Sized>(mut self, name: impl Into<String>, value: &T) -> Self {
71 self.params.insert(name.into(), value.to_value());
72 self
73 }
74
75 pub fn add_typed_param<T: ToValue + ?Sized>(
80 mut self,
81 name: impl Into<String>,
82 value: &T,
83 param_type: Type,
84 ) -> Self {
85 let name = name.into();
86 self.params.insert(name.clone(), value.to_value());
87 self.param_types.insert(name, param_type);
88 self
89 }
90
91 pub fn set_request_tag(mut self, tag: impl Into<String>) -> Self {
103 self.request_options
104 .get_or_insert_with(crate::model::RequestOptions::default)
105 .request_tag = tag.into();
106 self
107 }
108
109 pub fn set_priority(mut self, priority: Priority) -> Self {
120 self.request_options
121 .get_or_insert_with(crate::model::RequestOptions::default)
122 .priority = priority;
123 self
124 }
125
126 pub fn set_directed_read_options(mut self, options: DirectedReadOptions) -> Self {
140 self.directed_read_options = Some(options);
141 self
142 }
143
144 pub fn set_query_options(mut self, options: QueryOptions) -> Self {
157 self.query_options = Some(options);
158 self
159 }
160
161 pub fn set_query_mode(mut self, mode: QueryMode) -> Self {
172 self.query_mode = Some(mode);
173 self
174 }
175
176 pub fn with_attempt_timeout(mut self, timeout: Duration) -> Self {
178 self.gax_options.set_attempt_timeout(timeout);
179 self
180 }
181
182 pub fn with_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
184 self.gax_options.set_retry_policy(policy);
185 self
186 }
187
188 pub fn with_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
190 self.gax_options.set_backoff_policy(policy);
191 self
192 }
193
194 pub fn build(self) -> Statement {
196 Statement {
197 sql: self.sql,
198 params: self.params,
199 param_types: self.param_types,
200 request_options: self.request_options,
201 directed_read_options: self.directed_read_options,
202 query_options: self.query_options,
203 query_mode: self.query_mode,
204 gax_options: self.gax_options,
205 }
206 }
207}
208
209#[derive(Clone, Debug)]
233pub struct Statement {
234 pub(crate) sql: String,
235 pub(crate) params: BTreeMap<String, Value>,
236 pub(crate) param_types: BTreeMap<String, Type>,
237 pub(crate) request_options: Option<crate::model::RequestOptions>,
238 pub(crate) directed_read_options: Option<DirectedReadOptions>,
239 pub(crate) query_options: Option<QueryOptions>,
240 pub(crate) query_mode: Option<QueryMode>,
241 gax_options: GaxRequestOptions,
242}
243
244impl Statement {
245 pub fn builder(sql: impl Into<String>) -> StatementBuilder {
247 StatementBuilder::new(sql)
248 }
249
250 pub fn sql(&self) -> &str {
252 &self.sql
253 }
254
255 pub(crate) fn gax_options(&self) -> &GaxRequestOptions {
256 &self.gax_options
257 }
258
259 pub(crate) fn with_gax_options(mut self, options: GaxRequestOptions) -> Self {
261 self.gax_options = options;
262 self
263 }
264
265 pub fn set_query_mode(mut self, mode: QueryMode) -> Self {
283 self.query_mode = Some(mode);
284 self
285 }
286
287 fn into_parts(
288 self,
289 ) -> (
290 String,
291 Option<wkt::Struct>,
292 std::collections::HashMap<String, crate::model::Type>,
293 ) {
294 let params: Option<wkt::Struct> = if self.params.is_empty() {
295 None
296 } else {
297 Some(
298 self.params
299 .into_iter()
300 .map(|(k, v)| (k, v.into_serde_value()))
301 .collect(),
302 )
303 };
304 let param_types: std::collections::HashMap<String, crate::model::Type> = self
305 .param_types
306 .into_iter()
307 .map(|(k, v)| (k, v.0))
308 .collect();
309 (self.sql, params, param_types)
310 }
311
312 pub(crate) fn into_request(self) -> crate::model::ExecuteSqlRequest {
313 let request_options = self.request_options.clone();
314 let directed_read_options = self.directed_read_options.clone();
315 let query_options = self.query_options.clone();
316 let query_mode = self.query_mode.clone();
317 let (sql, params, param_types) = self.into_parts();
318 crate::model::ExecuteSqlRequest::default()
319 .set_sql(sql)
320 .set_or_clear_params(params)
321 .set_param_types(param_types)
322 .set_or_clear_request_options(request_options)
323 .set_or_clear_directed_read_options(directed_read_options)
324 .set_or_clear_query_options(query_options)
325 .set_query_mode(query_mode.unwrap_or_default())
326 }
327
328 pub(crate) fn into_batch_statement(self) -> crate::model::execute_batch_dml_request::Statement {
329 let (sql, params, param_types) = self.into_parts();
330 crate::model::execute_batch_dml_request::Statement::default()
331 .set_sql(sql)
332 .set_or_clear_params(params)
333 .set_param_types(param_types)
334 }
335
336 pub(crate) fn into_partition_query_request(self) -> crate::model::PartitionQueryRequest {
337 let (sql, params, param_types) = self.into_parts();
338 crate::model::PartitionQueryRequest::default()
339 .set_sql(sql)
340 .set_or_clear_params(params)
341 .set_param_types(param_types)
342 }
343}
344
345impl From<StatementBuilder> for Statement {
346 fn from(builder: StatementBuilder) -> Self {
347 builder.build()
348 }
349}
350
351impl From<String> for Statement {
352 fn from(sql: String) -> Self {
353 Statement::builder(sql).build()
354 }
355}
356
357impl From<&str> for Statement {
358 fn from(sql: &str) -> Self {
359 Statement::builder(sql).build()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use anyhow::Context;
367
368 #[test]
369 fn test_auto_traits() {
370 static_assertions::assert_impl_all!(Statement: Clone, std::fmt::Debug, Send, Sync);
371 static_assertions::assert_impl_all!(StatementBuilder: Clone, std::fmt::Debug, Send, Sync);
372 }
373
374 #[test]
375 fn test_untyped_param() {
376 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age")
377 .add_param("age", &21)
378 .build();
379
380 assert_eq!(stmt.sql, "SELECT * FROM users WHERE age > @age");
381 assert_eq!(stmt.param_types.len(), 0);
382 assert_eq!(stmt.params.len(), 1);
383 assert_eq!(stmt.request_options, None);
384
385 let val = stmt.params.get("age").unwrap();
386 assert_eq!(val.as_string(), "21");
387 }
388
389 #[test]
390 fn test_typed_param() {
391 use crate::types;
392 let stmt = Statement::builder("SELECT * FROM users WHERE id = @id")
393 .add_typed_param("id", &"user-123", types::string())
394 .build();
395
396 assert_eq!(stmt.param_types.len(), 1);
397 assert_eq!(stmt.param_types.get("id").unwrap(), &types::string());
398
399 assert_eq!(stmt.params.len(), 1);
400 let val = stmt.params.get("id").unwrap();
401 assert_eq!(val.as_string(), "user-123");
402 }
403
404 #[test]
405 fn test_multiple_params() {
406 use crate::types;
407 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
408 .add_param("age", &21)
409 .add_typed_param("role", &"admin", types::string())
410 .build();
411
412 assert_eq!(stmt.params.len(), 2);
413 assert_eq!(stmt.param_types.len(), 1);
414 }
415
416 #[test]
417 fn test_from_string_conversions() {
418 let stmt_str: Statement = "SELECT 1".into();
419 let stmt_string: Statement = "SELECT 1".to_string().into();
420 assert_eq!(stmt_str.sql, "SELECT 1");
421 assert_eq!(stmt_string.sql, "SELECT 1");
422 assert!(stmt_str.params.is_empty());
423 assert!(stmt_string.params.is_empty());
424 assert!(stmt_str.param_types.is_empty());
425 assert!(stmt_string.param_types.is_empty());
426 assert!(stmt_str.request_options.is_none());
427 assert!(stmt_string.request_options.is_none());
428 }
429
430 #[test]
431 fn test_from_builder_conversion() {
432 use crate::types;
433 let builder = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
434 .add_param("age", &21)
435 .add_typed_param("role", &"admin", types::string());
436
437 let stmt: Statement = builder.into();
438 assert_eq!(
439 stmt.sql,
440 "SELECT * FROM users WHERE age > @age AND role = @role"
441 );
442 assert_eq!(stmt.params.len(), 2);
443 assert_eq!(stmt.param_types.len(), 1);
444 }
445
446 #[test]
447 fn test_into_request() {
448 use crate::types;
449 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
450 .add_param("age", &21)
451 .add_typed_param("role", &"admin", types::string())
452 .build();
453
454 let req = stmt.into_request();
455
456 let params = req
457 .params
458 .expect("ExecuteSqlRequest parameters should be set after into_request conversion");
459 assert_eq!(params.len(), 2);
460 assert!(params.contains_key("age"));
461 assert!(params.contains_key("role"));
462
463 let param_types = req.param_types;
464 assert_eq!(param_types.len(), 1);
465 assert!(param_types.contains_key("role"));
466 }
467
468 #[test]
469 fn with_request_tag() {
470 let stmt = Statement::builder("SELECT * FROM users")
471 .set_request_tag("tag1")
472 .build();
473 assert_eq!(
474 stmt.request_options
475 .expect("request options missing")
476 .request_tag,
477 "tag1"
478 );
479 }
480
481 #[test]
482 fn with_priority() {
483 let stmt = Statement::builder("SELECT * FROM users")
484 .set_priority(Priority::High)
485 .build();
486 assert_eq!(
487 stmt.request_options
488 .expect("request options missing")
489 .priority,
490 Priority::High
491 );
492 }
493
494 #[test]
495 fn with_directed_read_options() {
496 let dro = DirectedReadOptions::default();
497 let stmt = Statement::builder("SELECT * FROM users")
498 .set_directed_read_options(dro.clone())
499 .build();
500 assert_eq!(stmt.directed_read_options, Some(dro));
501 }
502
503 #[test]
504 fn with_query_options() -> anyhow::Result<()> {
505 let query_options = QueryOptions::default().set_optimizer_version("1");
506 let stmt = Statement::builder("SELECT * FROM users")
507 .set_query_options(query_options.clone())
508 .build();
509 assert_eq!(
510 stmt.query_options
511 .as_ref()
512 .context("query options missing")?
513 .optimizer_version,
514 "1"
515 );
516
517 let req = stmt.into_request();
518 assert_eq!(
519 req.query_options
520 .context("query options missing in request")?
521 .optimizer_version,
522 "1"
523 );
524 Ok(())
525 }
526
527 #[test]
528 fn with_query_mode() -> anyhow::Result<()> {
529 let stmt = Statement::builder("SELECT * FROM users")
530 .set_query_mode(QueryMode::Plan)
531 .build();
532 assert_eq!(stmt.query_mode, Some(QueryMode::Plan));
533
534 let req = stmt.into_request();
535 assert_eq!(req.query_mode, QueryMode::Plan);
536 Ok(())
537 }
538
539 #[test]
540 fn statement_with_query_mode() -> anyhow::Result<()> {
541 let stmt = Statement::builder("SELECT * FROM users").build();
542 assert_eq!(stmt.query_mode, None);
543
544 let stmt = stmt.set_query_mode(QueryMode::Profile);
545 assert_eq!(stmt.query_mode, Some(QueryMode::Profile));
546
547 let req = stmt.into_request();
548 assert_eq!(req.query_mode, QueryMode::Profile);
549 Ok(())
550 }
551
552 #[test]
553 fn with_gax_options() -> anyhow::Result<()> {
554 use google_cloud_gax::exponential_backoff::ExponentialBackoff;
555 use google_cloud_gax::retry_policy::NeverRetry;
556 use std::time::Duration;
557
558 let stmt = Statement::builder("SELECT * FROM users")
559 .with_attempt_timeout(Duration::from_secs(10))
560 .with_retry_policy(NeverRetry)
561 .with_backoff_policy(ExponentialBackoff::default())
562 .build();
563
564 assert_eq!(
565 stmt.gax_options.attempt_timeout(),
566 &Some(Duration::from_secs(10))
567 );
568 assert!(stmt.gax_options.retry_policy().is_some());
569 assert!(stmt.gax_options.backoff_policy().is_some());
570
571 Ok(())
572 }
573}