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 last_statement: bool,
47 gax_options: GaxRequestOptions,
48}
49
50impl StatementBuilder {
51 pub(crate) fn new(sql: impl Into<String>) -> Self {
52 Self {
53 sql: sql.into(),
54 params: BTreeMap::new(),
55 param_types: BTreeMap::new(),
56 request_options: None,
57 directed_read_options: None,
58 query_options: None,
59 query_mode: None,
60 last_statement: false,
61 gax_options: GaxRequestOptions::default(),
62 }
63 }
64
65 pub fn add_param<T: ToValue + ?Sized>(mut self, name: impl Into<String>, value: &T) -> Self {
73 self.params.insert(name.into(), value.to_value());
74 self
75 }
76
77 pub fn add_typed_param<T: ToValue + ?Sized>(
82 mut self,
83 name: impl Into<String>,
84 value: &T,
85 param_type: Type,
86 ) -> Self {
87 let name = name.into();
88 self.params.insert(name.clone(), value.to_value());
89 self.param_types.insert(name, param_type);
90 self
91 }
92
93 pub fn set_request_tag(mut self, tag: impl Into<String>) -> Self {
105 self.request_options
106 .get_or_insert_with(crate::model::RequestOptions::default)
107 .request_tag = tag.into();
108 self
109 }
110
111 pub fn set_priority(mut self, priority: Priority) -> Self {
122 self.request_options
123 .get_or_insert_with(crate::model::RequestOptions::default)
124 .priority = priority;
125 self
126 }
127
128 pub fn set_directed_read_options(mut self, options: DirectedReadOptions) -> Self {
142 self.directed_read_options = Some(options);
143 self
144 }
145
146 pub fn set_query_options(mut self, options: QueryOptions) -> Self {
159 self.query_options = Some(options);
160 self
161 }
162
163 pub fn set_query_mode(mut self, mode: QueryMode) -> Self {
174 self.query_mode = Some(mode);
175 self
176 }
177
178 pub fn with_attempt_timeout(mut self, timeout: Duration) -> Self {
180 self.gax_options.set_attempt_timeout(timeout);
181 self
182 }
183
184 pub fn set_last_statement(mut self, last_statement: bool) -> Self {
208 self.last_statement = last_statement;
209 self
210 }
211
212 pub fn with_retry_policy(mut self, policy: impl Into<RetryPolicyArg>) -> Self {
214 self.gax_options.set_retry_policy(policy);
215 self
216 }
217
218 pub fn with_backoff_policy(mut self, policy: impl Into<BackoffPolicyArg>) -> Self {
220 self.gax_options.set_backoff_policy(policy);
221 self
222 }
223
224 pub fn build(self) -> Statement {
226 Statement {
227 sql: self.sql,
228 params: self.params,
229 param_types: self.param_types,
230 request_options: self.request_options,
231 directed_read_options: self.directed_read_options,
232 query_options: self.query_options,
233 query_mode: self.query_mode,
234 last_statement: self.last_statement,
235 gax_options: self.gax_options,
236 }
237 }
238}
239
240#[derive(Clone, Debug)]
264pub struct Statement {
265 pub(crate) sql: String,
266 pub(crate) params: BTreeMap<String, Value>,
267 pub(crate) param_types: BTreeMap<String, Type>,
268 pub(crate) request_options: Option<crate::model::RequestOptions>,
269 pub(crate) directed_read_options: Option<DirectedReadOptions>,
270 pub(crate) query_options: Option<QueryOptions>,
271 pub(crate) query_mode: Option<QueryMode>,
272 pub(crate) last_statement: bool,
273 pub(crate) gax_options: GaxRequestOptions,
274}
275
276impl Statement {
277 pub fn builder(sql: impl Into<String>) -> StatementBuilder {
279 StatementBuilder::new(sql)
280 }
281
282 pub fn sql(&self) -> &str {
284 &self.sql
285 }
286
287 pub(crate) fn gax_options(&self) -> &GaxRequestOptions {
288 &self.gax_options
289 }
290
291 pub(crate) fn with_gax_options(mut self, options: GaxRequestOptions) -> Self {
293 self.gax_options = options;
294 self
295 }
296
297 pub fn set_query_mode(mut self, mode: QueryMode) -> Self {
315 self.query_mode = Some(mode);
316 self
317 }
318
319 pub fn set_last_statement(mut self, last_statement: bool) -> Self {
343 self.last_statement = last_statement;
344 self
345 }
346
347 fn into_parts(
348 self,
349 ) -> (
350 String,
351 Option<wkt::Struct>,
352 std::collections::HashMap<String, crate::model::Type>,
353 ) {
354 let params: Option<wkt::Struct> = if self.params.is_empty() {
355 None
356 } else {
357 Some(
358 self.params
359 .into_iter()
360 .map(|(k, v)| (k, v.into_serde_value()))
361 .collect(),
362 )
363 };
364 let param_types: std::collections::HashMap<String, crate::model::Type> = self
365 .param_types
366 .into_iter()
367 .map(|(k, v)| (k, v.0))
368 .collect();
369 (self.sql, params, param_types)
370 }
371
372 pub(crate) fn into_request(self) -> crate::model::ExecuteSqlRequest {
373 let request_options = self.request_options.clone();
374 let directed_read_options = self.directed_read_options.clone();
375 let query_options = self.query_options.clone();
376 let query_mode = self.query_mode.clone();
377 let last_statement = self.last_statement;
378 let (sql, params, param_types) = self.into_parts();
379 crate::model::ExecuteSqlRequest::default()
380 .set_sql(sql)
381 .set_or_clear_params(params)
382 .set_param_types(param_types)
383 .set_or_clear_request_options(request_options)
384 .set_or_clear_directed_read_options(directed_read_options)
385 .set_or_clear_query_options(query_options)
386 .set_query_mode(query_mode.unwrap_or_default())
387 .set_last_statement(last_statement)
388 }
389
390 pub(crate) fn into_batch_statement(self) -> crate::model::execute_batch_dml_request::Statement {
391 let (sql, params, param_types) = self.into_parts();
392 crate::model::execute_batch_dml_request::Statement::default()
393 .set_sql(sql)
394 .set_or_clear_params(params)
395 .set_param_types(param_types)
396 }
397
398 pub(crate) fn into_partition_query_request(self) -> crate::model::PartitionQueryRequest {
399 let (sql, params, param_types) = self.into_parts();
400 crate::model::PartitionQueryRequest::default()
401 .set_sql(sql)
402 .set_or_clear_params(params)
403 .set_param_types(param_types)
404 }
405}
406
407impl From<StatementBuilder> for Statement {
408 fn from(builder: StatementBuilder) -> Self {
409 builder.build()
410 }
411}
412
413impl From<String> for Statement {
414 fn from(sql: String) -> Self {
415 Statement::builder(sql).build()
416 }
417}
418
419impl From<&str> for Statement {
420 fn from(sql: &str) -> Self {
421 Statement::builder(sql).build()
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use anyhow::Context;
429
430 #[test]
431 fn test_auto_traits() {
432 static_assertions::assert_impl_all!(Statement: Clone, std::fmt::Debug, Send, Sync);
433 static_assertions::assert_impl_all!(StatementBuilder: Clone, std::fmt::Debug, Send, Sync);
434 }
435
436 #[test]
437 fn test_untyped_param() {
438 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age")
439 .add_param("age", &21)
440 .build();
441
442 assert_eq!(stmt.sql, "SELECT * FROM users WHERE age > @age");
443 assert_eq!(stmt.param_types.len(), 0);
444 assert_eq!(stmt.params.len(), 1);
445 assert_eq!(stmt.request_options, None);
446
447 let val = stmt.params.get("age").unwrap();
448 assert_eq!(val.as_string(), "21");
449 }
450
451 #[test]
452 fn test_typed_param() {
453 use crate::types;
454 let stmt = Statement::builder("SELECT * FROM users WHERE id = @id")
455 .add_typed_param("id", &"user-123", types::string())
456 .build();
457
458 assert_eq!(stmt.param_types.len(), 1);
459 assert_eq!(stmt.param_types.get("id").unwrap(), &types::string());
460
461 assert_eq!(stmt.params.len(), 1);
462 let val = stmt.params.get("id").unwrap();
463 assert_eq!(val.as_string(), "user-123");
464 }
465
466 #[test]
467 fn test_multiple_params() {
468 use crate::types;
469 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
470 .add_param("age", &21)
471 .add_typed_param("role", &"admin", types::string())
472 .build();
473
474 assert_eq!(stmt.params.len(), 2);
475 assert_eq!(stmt.param_types.len(), 1);
476 }
477
478 #[test]
479 fn test_from_string_conversions() {
480 let stmt_str: Statement = "SELECT 1".into();
481 let stmt_string: Statement = "SELECT 1".to_string().into();
482 assert_eq!(stmt_str.sql, "SELECT 1");
483 assert_eq!(stmt_string.sql, "SELECT 1");
484 assert!(stmt_str.params.is_empty());
485 assert!(stmt_string.params.is_empty());
486 assert!(stmt_str.param_types.is_empty());
487 assert!(stmt_string.param_types.is_empty());
488 assert!(stmt_str.request_options.is_none());
489 assert!(stmt_string.request_options.is_none());
490 }
491
492 #[test]
493 fn test_from_builder_conversion() {
494 use crate::types;
495 let builder = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
496 .add_param("age", &21)
497 .add_typed_param("role", &"admin", types::string());
498
499 let stmt: Statement = builder.into();
500 assert_eq!(
501 stmt.sql,
502 "SELECT * FROM users WHERE age > @age AND role = @role"
503 );
504 assert_eq!(stmt.params.len(), 2);
505 assert_eq!(stmt.param_types.len(), 1);
506 }
507
508 #[test]
509 fn test_into_request() {
510 use crate::types;
511 let stmt = Statement::builder("SELECT * FROM users WHERE age > @age AND role = @role")
512 .add_param("age", &21)
513 .add_typed_param("role", &"admin", types::string())
514 .build();
515
516 let req = stmt.into_request();
517
518 let params = req
519 .params
520 .expect("ExecuteSqlRequest parameters should be set after into_request conversion");
521 assert_eq!(params.len(), 2);
522 assert!(params.contains_key("age"));
523 assert!(params.contains_key("role"));
524
525 let param_types = req.param_types;
526 assert_eq!(param_types.len(), 1);
527 assert!(param_types.contains_key("role"));
528 }
529
530 #[test]
531 fn with_request_tag() {
532 let stmt = Statement::builder("SELECT * FROM users")
533 .set_request_tag("tag1")
534 .build();
535 assert_eq!(
536 stmt.request_options
537 .expect("request options missing")
538 .request_tag,
539 "tag1"
540 );
541 }
542
543 #[test]
544 fn with_priority() {
545 let stmt = Statement::builder("SELECT * FROM users")
546 .set_priority(Priority::High)
547 .build();
548 assert_eq!(
549 stmt.request_options
550 .expect("request options missing")
551 .priority,
552 Priority::High
553 );
554 }
555
556 #[test]
557 fn with_directed_read_options() {
558 let dro = DirectedReadOptions::default();
559 let stmt = Statement::builder("SELECT * FROM users")
560 .set_directed_read_options(dro.clone())
561 .build();
562 assert_eq!(stmt.directed_read_options, Some(dro));
563 }
564
565 #[test]
566 fn with_query_options() -> anyhow::Result<()> {
567 let query_options = QueryOptions::default().set_optimizer_version("1");
568 let stmt = Statement::builder("SELECT * FROM users")
569 .set_query_options(query_options.clone())
570 .build();
571 assert_eq!(
572 stmt.query_options
573 .as_ref()
574 .context("query options missing")?
575 .optimizer_version,
576 "1"
577 );
578
579 let req = stmt.into_request();
580 assert_eq!(
581 req.query_options
582 .context("query options missing in request")?
583 .optimizer_version,
584 "1"
585 );
586 Ok(())
587 }
588
589 #[test]
590 fn with_query_mode() -> anyhow::Result<()> {
591 let stmt = Statement::builder("SELECT * FROM users")
592 .set_query_mode(QueryMode::Plan)
593 .build();
594 assert_eq!(stmt.query_mode, Some(QueryMode::Plan));
595
596 let req = stmt.into_request();
597 assert_eq!(req.query_mode, QueryMode::Plan);
598 Ok(())
599 }
600
601 #[test]
602 fn statement_with_query_mode() -> anyhow::Result<()> {
603 let stmt = Statement::builder("SELECT * FROM users").build();
604 assert_eq!(stmt.query_mode, None);
605
606 let stmt = stmt.set_query_mode(QueryMode::Profile);
607 assert_eq!(stmt.query_mode, Some(QueryMode::Profile));
608
609 let req = stmt.into_request();
610 assert_eq!(req.query_mode, QueryMode::Profile);
611 Ok(())
612 }
613
614 #[test]
615 fn with_gax_options() -> anyhow::Result<()> {
616 use google_cloud_gax::exponential_backoff::ExponentialBackoff;
617 use google_cloud_gax::retry_policy::NeverRetry;
618 use std::time::Duration;
619
620 let stmt = Statement::builder("SELECT * FROM users")
621 .with_attempt_timeout(Duration::from_secs(10))
622 .with_retry_policy(NeverRetry)
623 .with_backoff_policy(ExponentialBackoff::default())
624 .build();
625
626 assert_eq!(
627 stmt.gax_options.attempt_timeout(),
628 &Some(Duration::from_secs(10))
629 );
630 assert!(stmt.gax_options.retry_policy().is_some());
631 assert!(stmt.gax_options.backoff_policy().is_some());
632
633 Ok(())
634 }
635
636 #[test]
637 fn set_last_statement() -> anyhow::Result<()> {
638 let stmt = Statement::builder("SELECT * FROM users").build();
639 assert!(!stmt.last_statement);
640
641 let req = stmt.into_request();
642 assert!(!req.last_statement);
643
644 let stmt = Statement::builder("SELECT * FROM users")
645 .set_last_statement(true)
646 .build();
647 assert!(stmt.last_statement);
648
649 let req = stmt.into_request();
650 assert!(req.last_statement);
651
652 let stmt = Statement::builder("SELECT * FROM users")
653 .build()
654 .set_last_statement(true);
655 assert!(stmt.last_statement);
656
657 let req = stmt.into_request();
658 assert!(req.last_statement);
659
660 Ok(())
661 }
662}