snowflake_connector_rs/statement/handle.rs
1use std::{fmt, sync::Arc, time::Duration};
2
3use crate::{
4 Result,
5 result_cursor::{ResultCursor, TypedResultCursor},
6 result_table::{FromRow, RowPlanContext},
7};
8
9use super::{
10 StatementExecutor, StatementParts,
11 api::QueryApiClient,
12 cancel::{CancelDecision, QueryControl},
13};
14
15/// What a successful [`QueryCanceller::cancel`] call achieved.
16///
17/// This is a report, not a control-flow discriminant.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum QueryCancelStatus {
21 /// Cancellation won before the query submit request began; neither the query nor an abort request was sent.
22 NotSubmitted,
23 /// Snowflake accepted the abort request; query terminality is not implied.
24 Accepted,
25 /// The connector already processed a terminal query response; no abort request was sent.
26 AlreadyFinished,
27}
28
29/// An owned statement execution that can be explicitly cancelled through a [`QueryCanceller`].
30///
31/// Cancellation runs only through [`QueryCanceller::cancel`] on a canceller retained from [`Self::canceller`];
32/// dropping the execution future never cancels the remote query.
33pub struct QueryHandle {
34 statement: StatementParts,
35 executor: StatementExecutor,
36 control: Arc<QueryControl>,
37}
38
39/// A cloneable controller that cancels the query of the [`QueryHandle`] it was obtained from.
40///
41/// It remains usable from any task, even after the handle has been consumed by execution or the execution future
42/// has been dropped.
43#[derive(Clone)]
44pub struct QueryCanceller {
45 api: QueryApiClient,
46 control: Arc<QueryControl>,
47 request_timeout: Duration,
48}
49
50impl QueryHandle {
51 pub(crate) fn new(
52 statement: StatementParts,
53 executor: StatementExecutor,
54 control: Arc<QueryControl>,
55 ) -> Self {
56 Self {
57 statement,
58 executor,
59 control,
60 }
61 }
62
63 /// Returns the client-generated query request ID used by the submit and abort requests.
64 pub fn request_id(&self) -> &str {
65 self.control.query_request_id()
66 }
67
68 /// Returns a cloneable cancellation controller for this query.
69 pub fn canceller(&self) -> QueryCanceller {
70 QueryCanceller {
71 api: self.executor.api_client(),
72 control: Arc::clone(&self.control),
73 request_timeout: self.executor.cancel_request_timeout(),
74 }
75 }
76
77 /// Submits the statement and returns a streaming result cursor.
78 ///
79 /// # Errors
80 ///
81 /// Returns the same errors as [`Session::query`](crate::Session::query). If cancellation wins before submission,
82 /// this returns an [`ErrorKind::Cancelled`](crate::ErrorKind::Cancelled) error without sending either the query
83 /// or abort request.
84 pub async fn execute(self) -> Result<ResultCursor> {
85 let Self {
86 statement,
87 executor,
88 control,
89 } = self;
90 executor.execute(statement, control).await
91 }
92
93 /// Submits the statement and builds a typed streaming result cursor.
94 ///
95 /// # Errors
96 ///
97 /// Returns the same errors as [`Self::execute`]. After the statement succeeds, this also propagates plan-time
98 /// decode failures from [`FromRow::build_plan`] for `T`.
99 pub async fn execute_as<T>(self) -> Result<TypedResultCursor<T>>
100 where
101 T: FromRow,
102 {
103 let result = self.execute().await?;
104 let plan = T::build_plan(RowPlanContext::new(result.shared_schema()))?;
105 Ok(TypedResultCursor::new(result, plan))
106 }
107}
108
109impl fmt::Debug for QueryHandle {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.debug_struct("QueryHandle")
112 .field("request_id", &self.request_id())
113 .field("phase", &self.control.execution_phase())
114 .finish_non_exhaustive()
115 }
116}
117
118impl QueryCanceller {
119 /// Returns the client-generated query request ID targeted by this canceller.
120 pub fn request_id(&self) -> &str {
121 self.control.query_request_id()
122 }
123
124 /// Requests remote cancellation and waits for the abort response.
125 ///
126 /// On success, reports what the cancellation achieved as a [`QueryCancelStatus`].
127 ///
128 /// This method never returns an [`ErrorKind::Cancelled`](crate::ErrorKind::Cancelled) error; that error is what
129 /// [`QueryHandle::execute`] returns once the cancellation takes effect.
130 ///
131 /// # Errors
132 ///
133 /// Returns [`ErrorKind::Network`](crate::ErrorKind::Network), [`ErrorKind::Timeout`](crate::ErrorKind::Timeout),
134 /// [`ErrorKind::Server`](crate::ErrorKind::Server),
135 /// [`ErrorKind::SessionExpired`](crate::ErrorKind::SessionExpired), or
136 /// [`ErrorKind::Protocol`](crate::ErrorKind::Protocol). Transient transport failures are retried within the
137 /// configured cancellation deadline, and a failed call never caches a successful status, so a later call can
138 /// retry the cancellation.
139 pub async fn cancel(&self) -> Result<QueryCancelStatus> {
140 let _gate = self.control.lock_cancel_gate().await;
141
142 match self.control.begin_cancel_attempt() {
143 CancelDecision::Return(outcome) => Ok(outcome),
144 CancelDecision::SendAbort => {
145 self.api
146 .abort_query(self.control.query_request_id(), self.request_timeout)
147 .await?;
148 self.control.record_abort_accepted();
149 Ok(QueryCancelStatus::Accepted)
150 }
151 }
152 }
153}
154
155impl fmt::Debug for QueryCanceller {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.debug_struct("QueryCanceller")
158 .field("request_id", &self.request_id())
159 .field("phase", &self.control.execution_phase())
160 .finish_non_exhaustive()
161 }
162}
163
164const _: fn() = || {
165 fn assert_send_sync_static<T: Send + Sync + 'static>() {}
166 assert_send_sync_static::<QueryHandle>();
167 assert_send_sync_static::<QueryCanceller>();
168};