1use std::collections::BTreeMap;
11use std::net::{IpAddr, SocketAddr};
12use std::pin::pin;
13use std::sync::Arc;
14use std::time::Duration;
15
16use anyhow::anyhow;
17use async_trait::async_trait;
18use axum::extract::connect_info::ConnectInfo;
19use axum::extract::ws::{CloseFrame, Message, Utf8Bytes, WebSocket};
20use axum::extract::{State, WebSocketUpgrade};
21use axum::response::IntoResponse;
22use axum::{Extension, Json};
23use futures::Future;
24use futures::future::BoxFuture;
25
26use http::StatusCode;
27use itertools::Itertools;
28use mz_adapter::client::RecordFirstRowStream;
29use mz_adapter::session::{EndTransactionAction, TransactionStatus};
30use mz_adapter::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
31use mz_adapter::{
32 AdapterError, AdapterNotice, ExecuteContextGuard, ExecuteResponse, ExecuteResponseKind,
33 PeekResponseUnary, SessionClient, verify_datum_desc,
34};
35use mz_auth::password::Password;
36use mz_catalog::memory::objects::{Cluster, ClusterReplica};
37use mz_interchange::encode::TypedDatum;
38use mz_interchange::json::{JsonNumberPolicy, ToJson};
39use mz_ore::cast::CastFrom;
40use mz_ore::metrics::{MakeCollectorOpts, MetricsRegistry};
41use mz_ore::result::ResultExt;
42use mz_ore::sql::Sql;
43use mz_repr::{Datum, RelationDesc, RowArena, RowIterator};
44use mz_sql::ast::display::AstDisplay;
45use mz_sql::ast::{CopyDirection, CopyStatement, CopyTarget, Raw, Statement, StatementKind};
46use mz_sql::parse::StatementParseResult;
47use mz_sql::plan::Plan;
48use mz_sql::session::metadata::SessionMetadata;
49use prometheus::Opts;
50use prometheus::core::{AtomicF64, GenericGaugeVec};
51use serde::{Deserialize, Serialize};
52use tokio::{select, time};
53use tokio_postgres::error::SqlState;
54use tokio_stream::wrappers::UnboundedReceiverStream;
55use tower_sessions::Session as TowerSession;
56use tracing::{debug, info};
57use tungstenite::protocol::frame::coding::CloseCode;
58
59use crate::http::prometheus::PrometheusSqlQuery;
60use crate::http::{
61 AuthError, AuthedClient, AuthedUser, MAX_REQUEST_SIZE, WsState, ensure_session_unexpired,
62 init_ws, maybe_get_authenticated_session,
63};
64
65#[derive(Debug, thiserror::Error)]
66pub enum Error {
67 #[error(transparent)]
68 Adapter(#[from] AdapterError),
69 #[error(transparent)]
70 Json(#[from] serde_json::Error),
71 #[error(transparent)]
72 Axum(#[from] axum::Error),
73 #[error("SUBSCRIBE only supported over websocket")]
74 SubscribeOnlyOverWs,
75 #[error("current transaction is aborted, commands ignored until end of transaction block")]
76 AbortedTransaction,
77 #[error("unsupported via this API: {0}")]
78 Unsupported(String),
79 #[error("{0}")]
80 Unstructured(anyhow::Error),
81}
82
83impl Error {
84 pub fn detail(&self) -> Option<String> {
85 match self {
86 Error::Adapter(err) => err.detail(),
87 _ => None,
88 }
89 }
90
91 pub fn hint(&self) -> Option<String> {
92 match self {
93 Error::Adapter(err) => err.hint(),
94 _ => None,
95 }
96 }
97
98 pub fn position(&self) -> Option<usize> {
99 match self {
100 Error::Adapter(err) => err.position(),
101 _ => None,
102 }
103 }
104
105 pub fn code(&self) -> SqlState {
106 match self {
107 Error::Adapter(err) => err.code(),
108 Error::AbortedTransaction => SqlState::IN_FAILED_SQL_TRANSACTION,
109 _ => SqlState::INTERNAL_ERROR,
110 }
111 }
112}
113
114static PER_REPLICA_LABELS: &[&str] = &["replica_full_name", "instance_id", "replica_id"];
115
116async fn execute_promsql_query(
117 client: &mut AuthedClient,
118 query: &PrometheusSqlQuery<'_>,
119 metrics_registry: &MetricsRegistry,
120 metrics_by_name: &mut BTreeMap<String, GenericGaugeVec<AtomicF64>>,
121 cluster: Option<(&Cluster, &ClusterReplica)>,
122) {
123 assert_eq!(query.per_replica, cluster.is_some());
124
125 let mut res = SqlResponse {
126 results: Vec::new(),
127 };
128
129 execute_request(client, query.to_sql_request(cluster), &mut res)
130 .await
131 .expect("valid SQL query");
132
133 let result = match res.results.as_slice() {
134 [
137 SqlResult::Ok { .. },
138 SqlResult::Ok { .. },
139 SqlResult::Ok { .. },
140 result,
141 ] => result,
142 _ => {
146 info!(
147 "error executing prometheus query {}: {:?}",
148 query.metric_name, res
149 );
150 return;
151 }
152 };
153
154 let SqlResult::Rows { desc, rows, .. } = result else {
155 info!(
156 "did not receive rows for SQL query for prometheus metric {}: {:?}, {:?}",
157 query.metric_name, result, cluster
158 );
159 return;
160 };
161
162 let gauge_vec = metrics_by_name
163 .entry(query.metric_name.to_string())
164 .or_insert_with(|| {
165 let mut label_names: Vec<String> = desc
166 .columns
167 .iter()
168 .filter(|col| col.name != query.value_column_name)
169 .map(|col| col.name.clone())
170 .collect();
171
172 if query.per_replica {
173 label_names.extend(PER_REPLICA_LABELS.iter().map(|label| label.to_string()));
174 }
175
176 metrics_registry.register::<GenericGaugeVec<AtomicF64>>(MakeCollectorOpts {
177 opts: Opts::new(query.metric_name, query.help).variable_labels(label_names),
178 buckets: None,
179 })
180 });
181
182 for row in rows {
183 let mut label_values = desc
189 .columns
190 .iter()
191 .zip_eq(row)
192 .filter(|(col, _)| col.name != query.value_column_name)
193 .map(|(_, val)| val.as_str().unwrap_or(""))
194 .collect::<Vec<_>>();
195
196 let value = desc
197 .columns
198 .iter()
199 .zip_eq(row)
200 .find(|(col, _)| col.name == query.value_column_name)
201 .map(|(_, val)| val.as_str().unwrap_or("0").parse::<f64>().unwrap_or(0.0))
202 .unwrap_or(0.0);
203
204 match cluster {
205 Some((cluster, replica)) => {
206 let replica_full_name = format!("{}.{}", cluster.name, replica.name);
207 let cluster_id = cluster.id.to_string();
208 let replica_id = replica.replica_id.to_string();
209
210 label_values.push(&replica_full_name);
211 label_values.push(&cluster_id);
212 label_values.push(&replica_id);
213
214 gauge_vec
215 .get_metric_with_label_values(&label_values)
216 .expect("valid labels")
217 .set(value);
218 }
219 None => {
220 gauge_vec
221 .get_metric_with_label_values(&label_values)
222 .expect("valid labels")
223 .set(value);
224 }
225 }
226 }
227}
228
229async fn handle_promsql_query(
230 client: &mut AuthedClient,
231 query: &PrometheusSqlQuery<'_>,
232 metrics_registry: &MetricsRegistry,
233 metrics_by_name: &mut BTreeMap<String, GenericGaugeVec<AtomicF64>>,
234) {
235 if !query.per_replica {
236 execute_promsql_query(client, query, metrics_registry, metrics_by_name, None).await;
237 return;
238 }
239
240 let catalog = client.client.catalog_snapshot("handle_promsql_query").await;
241 let clusters: Vec<&Cluster> = catalog.clusters().collect();
242
243 for cluster in clusters {
244 for replica in cluster.replicas() {
245 execute_promsql_query(
246 client,
247 query,
248 metrics_registry,
249 metrics_by_name,
250 Some((cluster, replica)),
251 )
252 .await;
253 }
254 }
255}
256
257pub async fn handle_promsql(
258 mut client: AuthedClient,
259 queries: &[PrometheusSqlQuery<'_>],
260) -> MetricsRegistry {
261 let metrics_registry = MetricsRegistry::new();
262 let mut metrics_by_name = BTreeMap::new();
263
264 for query in queries {
265 handle_promsql_query(&mut client, query, &metrics_registry, &mut metrics_by_name).await;
266 }
267
268 metrics_registry
269}
270
271pub async fn handle_sql(
272 mut client: AuthedClient,
273 Json(request): Json<SqlRequest>,
274) -> impl IntoResponse {
275 let mut res = SqlResponse {
276 results: Vec::new(),
277 };
278 match execute_request(&mut client, request, &mut res).await {
281 Ok(()) => Ok(Json(res)),
282 Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
283 }
284}
285
286#[derive(Debug)]
287pub enum ExistingUser {
288 XMaterializeUserHeader(AuthedUser),
291 Session(AuthedUser),
294}
295
296pub(crate) async fn handle_sql_ws(
297 State(state): State<WsState>,
298 existing_user: Option<Extension<AuthedUser>>,
299 ws: WebSocketUpgrade,
300 ConnectInfo(addr): ConnectInfo<SocketAddr>,
301 tower_session: Option<Extension<TowerSession>>,
302) -> Result<impl IntoResponse, AuthError> {
303 let session = tower_session.map(|Extension(session)| session);
304 let user = match existing_user {
306 Some(Extension(user)) => Some(ExistingUser::XMaterializeUserHeader(user)),
307 None => {
308 let session = maybe_get_authenticated_session(session.as_ref()).await;
309 if let Some((session, session_data)) = session {
310 let user = ensure_session_unexpired(session, session_data).await?;
311 Some(ExistingUser::Session(user))
312 } else {
313 None
314 }
315 }
316 };
317
318 let addr = Box::new(addr.ip());
319 Ok(ws
320 .max_message_size(MAX_REQUEST_SIZE)
321 .on_upgrade(|ws| async move { run_ws(state, user, *addr, ws).await }))
322}
323
324#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
325#[serde(untagged)]
326pub enum WebSocketAuth {
327 Basic {
328 user: String,
329 password: Password,
330 #[serde(default)]
331 options: BTreeMap<String, String>,
332 },
333 Bearer {
334 token: String,
335 #[serde(default)]
336 options: BTreeMap<String, String>,
337 },
338 OptionsOnly {
339 #[serde(default)]
340 options: BTreeMap<String, String>,
341 },
342}
343
344async fn run_ws(state: WsState, user: Option<ExistingUser>, peer_addr: IpAddr, mut ws: WebSocket) {
345 let mut client = match init_ws(state, user, peer_addr, &mut ws).await {
346 Ok(client) => client,
347 Err(e) => {
348 debug!("WS request failed init: {}", e);
352 let reason: Utf8Bytes = match e.downcast_ref::<AdapterError>() {
353 Some(error) => error.to_string().into(),
354 None => "unauthorized".to_string().into(),
355 };
356 let _ = ws
357 .send(Message::Close(Some(CloseFrame {
358 code: CloseCode::Protocol.into(),
359 reason,
360 })))
361 .await;
362 return;
363 }
364 };
365
366 let mut msgs = Vec::new();
368 let session = client.client.session();
369 for var in session.vars().notify_set() {
370 msgs.push(WebSocketResponse::ParameterStatus(ParameterStatus {
371 name: var.name().to_string(),
372 value: var.value(),
373 }));
374 }
375 msgs.push(WebSocketResponse::BackendKeyData(BackendKeyData {
376 conn_id: session.conn_id().unhandled(),
377 secret_key: session.secret_key(),
378 }));
379 msgs.push(WebSocketResponse::ReadyForQuery(
380 session.transaction_code().into(),
381 ));
382 for msg in msgs {
383 let _ = ws
384 .send(Message::Text(
385 serde_json::to_string(&msg).expect("must serialize").into(),
386 ))
387 .await;
388 }
389
390 let notices = session.drain_notices();
392 if let Err(err) = forward_notices(&mut ws, notices).await {
393 debug!("failed to forward notices to WebSocket, {err:?}");
394 return;
395 }
396
397 loop {
398 let msg = select! {
400 biased;
401
402 Some(timeout) = client.client.recv_timeout() => {
404 client.client.terminate().await;
405 let _ = ws.recv().await;
409 let err = Error::from(AdapterError::from(timeout));
410 let _ = send_ws_response(&mut ws, WebSocketResponse::Error(err.into())).await;
411 return;
412 },
413 message = ws.recv() => message,
414 };
415
416 client.client.remove_idle_in_transaction_session_timeout();
417
418 let msg = match msg {
419 Some(Ok(msg)) => msg,
420 _ => {
421 return;
423 }
424 };
425
426 let req: Result<SqlRequest, Error> = match msg {
427 Message::Text(data) => serde_json::from_str(&data).err_into(),
428 Message::Binary(data) => serde_json::from_slice(&data).err_into(),
429 Message::Ping(_) => {
431 continue;
432 }
433 Message::Pong(_) => {
434 continue;
435 }
436 Message::Close(_) => {
437 return;
438 }
439 };
440
441 let err = match run_ws_request(req, &mut client, &mut ws).await {
443 Ok(()) => None,
444 Err(err) => Some(WebSocketResponse::Error(err.into())),
445 };
446
447 let ws_response = || async {
454 if let Some(e_resp) = err {
456 send_ws_response(&mut ws, e_resp).await?;
457 }
458
459 let notices = client.client.session().drain_notices();
461 forward_notices(&mut ws, notices).await?;
462
463 let ready =
465 WebSocketResponse::ReadyForQuery(client.client.session().transaction_code().into());
466 send_ws_response(&mut ws, ready).await?;
467
468 Ok::<_, Error>(())
469 };
470
471 if let Err(err) = ws_response().await {
472 debug!("failed to send response over WebSocket, {err:?}");
473 return;
474 }
475 }
476}
477
478async fn run_ws_request(
479 req: Result<SqlRequest, Error>,
480 client: &mut AuthedClient,
481 ws: &mut WebSocket,
482) -> Result<(), Error> {
483 let req = req?;
484 execute_request(client, req, ws).await
485}
486
487async fn send_ws_response(ws: &mut WebSocket, resp: WebSocketResponse) -> Result<(), Error> {
489 let msg = serde_json::to_string(&resp).unwrap();
490 let msg = Message::Text(msg.into());
491 ws.send(msg).await?;
492
493 Ok(())
494}
495
496async fn forward_notices(
498 ws: &mut WebSocket,
499 notices: impl IntoIterator<Item = AdapterNotice>,
500) -> Result<(), Error> {
501 let ws_notices = notices.into_iter().map(|notice| {
502 WebSocketResponse::Notice(Notice {
503 message: notice.to_string(),
504 code: notice.code().code().to_string(),
505 severity: notice.severity().as_str().to_lowercase(),
506 detail: notice.detail(),
507 hint: notice.hint(),
508 })
509 });
510
511 for notice in ws_notices {
512 send_ws_response(ws, notice).await?;
513 }
514
515 Ok(())
516}
517
518#[derive(Serialize, Deserialize, Debug)]
520#[serde(untagged)]
521pub enum SqlRequest {
522 Simple {
524 query: Sql,
527 },
528 Extended {
530 queries: Vec<ExtendedRequest>,
532 },
533}
534
535#[derive(Serialize, Deserialize, Debug)]
537pub struct ExtendedRequest {
538 query: String,
540 #[serde(default)]
542 params: Vec<Option<String>>,
543}
544
545#[derive(Debug, Serialize, Deserialize)]
547pub struct SqlResponse {
548 pub(in crate::http) results: Vec<SqlResult>,
550}
551
552impl SqlResponse {
553 pub(in crate::http) fn new() -> Self {
555 Self {
556 results: Vec::new(),
557 }
558 }
559}
560
561pub(in crate::http) enum StatementResult {
562 SqlResult(SqlResult),
563 Subscribe {
564 desc: RelationDesc,
565 tag: String,
566 rx: RecordFirstRowStream,
567 ctx_extra: ExecuteContextGuard,
568 },
569}
570
571impl From<SqlResult> for StatementResult {
572 fn from(inner: SqlResult) -> Self {
573 Self::SqlResult(inner)
574 }
575}
576
577#[derive(Debug, Serialize, Deserialize)]
579#[serde(untagged)]
580pub enum SqlResult {
581 Rows {
583 tag: String,
585 rows: Vec<Vec<serde_json::Value>>,
587 desc: Description,
589 notices: Vec<Notice>,
591 },
592 Ok {
594 ok: String,
596 notices: Vec<Notice>,
598 #[serde(skip_serializing_if = "Vec::is_empty")]
602 parameters: Vec<ParameterStatus>,
603 },
604 Err {
606 error: SqlError,
607 notices: Vec<Notice>,
609 },
610}
611
612impl SqlResult {
613 async fn rows<S>(
617 sender: &mut S,
618 client: &mut SessionClient,
619 mut rows_stream: RecordFirstRowStream,
620 max_query_result_size: usize,
621 desc: &RelationDesc,
622 ) -> Result<SqlResult, Error>
623 where
624 S: ResultSender,
625 {
626 let mut rows: Vec<Vec<serde_json::Value>> = vec![];
627 let mut datum_vec = mz_repr::DatumVec::new();
628 let types = &desc.typ().column_types;
629
630 let mut query_result_size = 0;
631
632 loop {
633 let peek_response = tokio::select! {
634 notice = client.session().recv_notice(), if S::SUPPORTS_STREAMING_NOTICES => {
635 sender.emit_streaming_notices(vec![notice]).await?;
636 continue;
637 }
638 e = sender.connection_error() => return Err(e),
639 r = rows_stream.recv() => {
640 match r {
641 Some(r) => r,
642 None => break,
643 }
644 },
645 };
646
647 let mut sql_rows = match peek_response {
648 PeekResponseUnary::Rows(rows) => rows,
649 PeekResponseUnary::Error(e) => {
650 return Ok(SqlResult::err(client, Error::Unstructured(anyhow!(e))));
651 }
652 PeekResponseUnary::DependencyDropped(dep) => {
653 return Ok(SqlResult::err(client, dep.to_concurrent_dependency_drop()));
654 }
655 PeekResponseUnary::Canceled => {
656 return Ok(SqlResult::err(client, AdapterError::Canceled));
657 }
658 };
659
660 if let Err(err) = verify_datum_desc(desc, &mut sql_rows) {
661 return Ok(SqlResult::Err {
662 error: err.into(),
663 notices: make_notices(client),
664 });
665 }
666
667 while let Some(row) = sql_rows.next() {
668 query_result_size += row.byte_len();
669 if query_result_size > max_query_result_size {
670 use bytesize::ByteSize;
671 return Ok(SqlResult::err(
672 client,
673 AdapterError::ResultSize(format!(
674 "result exceeds max size of {}",
675 ByteSize::b(u64::cast_from(max_query_result_size))
676 )),
677 ));
678 }
679
680 let datums = datum_vec.borrow_with(row);
681 rows.push(
682 datums
683 .iter()
684 .enumerate()
685 .map(|(i, d)| {
686 TypedDatum::new(*d, &types[i])
687 .json(&JsonNumberPolicy::ConvertNumberToString)
688 })
689 .collect(),
690 );
691 }
692 }
693
694 let tag = format!("SELECT {}", rows.len());
695 Ok(SqlResult::Rows {
696 tag,
697 rows,
698 desc: Description::from(desc),
699 notices: make_notices(client),
700 })
701 }
702
703 fn err(client: &mut SessionClient, error: impl Into<SqlError>) -> SqlResult {
704 SqlResult::Err {
705 error: error.into(),
706 notices: make_notices(client),
707 }
708 }
709
710 fn ok(client: &mut SessionClient, tag: String, params: Vec<ParameterStatus>) -> SqlResult {
711 SqlResult::Ok {
712 ok: tag,
713 parameters: params,
714 notices: make_notices(client),
715 }
716 }
717}
718
719#[derive(Debug, Deserialize, Serialize)]
720pub struct SqlError {
721 pub message: String,
722 pub code: String,
723 #[serde(skip_serializing_if = "Option::is_none")]
724 pub detail: Option<String>,
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub hint: Option<String>,
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub position: Option<usize>,
729}
730
731impl From<Error> for SqlError {
732 fn from(err: Error) -> Self {
733 SqlError {
734 message: err.to_string(),
735 code: err.code().code().to_string(),
736 detail: err.detail(),
737 hint: err.hint(),
738 position: err.position(),
739 }
740 }
741}
742
743impl From<AdapterError> for SqlError {
744 fn from(value: AdapterError) -> Self {
745 Error::from(value).into()
746 }
747}
748
749#[derive(Debug, Deserialize, Serialize)]
750#[serde(tag = "type", content = "payload")]
751pub enum WebSocketResponse {
752 ReadyForQuery(String),
753 Notice(Notice),
754 Rows(Description),
755 Row(Vec<serde_json::Value>),
756 CommandStarting(CommandStarting),
757 CommandComplete(String),
758 Error(SqlError),
759 ParameterStatus(ParameterStatus),
760 BackendKeyData(BackendKeyData),
761}
762
763#[derive(Debug, Serialize, Deserialize)]
764pub struct Notice {
765 message: String,
766 code: String,
767 severity: String,
768 #[serde(skip_serializing_if = "Option::is_none")]
769 pub detail: Option<String>,
770 #[serde(skip_serializing_if = "Option::is_none")]
771 pub hint: Option<String>,
772}
773
774impl Notice {
775 pub fn message(&self) -> &str {
776 &self.message
777 }
778}
779
780#[derive(Debug, Serialize, Deserialize)]
781pub struct Description {
782 pub columns: Vec<Column>,
783}
784
785impl From<&RelationDesc> for Description {
786 fn from(desc: &RelationDesc) -> Self {
787 let columns = desc
788 .iter()
789 .map(|(name, typ)| {
790 let pg_type = mz_pgrepr::Type::from(&typ.scalar_type);
791 Column {
792 name: name.to_string(),
793 type_oid: pg_type.oid(),
794 type_len: pg_type.typlen(),
795 type_mod: pg_type.typmod(),
796 }
797 })
798 .collect();
799 Description { columns }
800 }
801}
802
803#[derive(Debug, Serialize, Deserialize)]
804pub struct Column {
805 pub name: String,
806 pub type_oid: u32,
807 pub type_len: i16,
808 pub type_mod: i32,
809}
810
811#[derive(Debug, Serialize, Deserialize)]
812pub struct ParameterStatus {
813 name: String,
814 value: String,
815}
816
817#[derive(Debug, Serialize, Deserialize)]
818pub struct BackendKeyData {
819 conn_id: u32,
820 secret_key: u32,
821}
822
823#[derive(Debug, Serialize, Deserialize)]
824pub struct CommandStarting {
825 has_rows: bool,
826 is_streaming: bool,
827}
828
829#[async_trait]
833pub(in crate::http) trait ResultSender: Send {
834 const SUPPORTS_STREAMING_NOTICES: bool = false;
835
836 async fn add_result(
844 &mut self,
845 client: &mut SessionClient,
846 res: StatementResult,
847 ) -> (
848 Result<Result<(), ()>, Error>,
849 Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
850 );
851
852 fn connection_error(&mut self) -> BoxFuture<'_, Error>;
854 fn allow_subscribe(&self) -> bool;
856
857 async fn emit_streaming_notices(&mut self, _: Vec<AdapterNotice>) -> Result<(), Error> {
861 unreachable!("streaming notices marked as unsupported")
862 }
863}
864
865#[async_trait]
866impl ResultSender for SqlResponse {
867 async fn add_result(
875 &mut self,
876 _client: &mut SessionClient,
877 res: StatementResult,
878 ) -> (
879 Result<Result<(), ()>, Error>,
880 Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
881 ) {
882 let (res, stmt_logging) = match res {
883 StatementResult::SqlResult(res) => {
884 let is_err = matches!(res, SqlResult::Err { .. });
885 self.results.push(res);
886 let res = if is_err { Err(()) } else { Ok(()) };
887 (res, None)
888 }
889 StatementResult::Subscribe { ctx_extra, .. } => {
890 let message = "SUBSCRIBE only supported over websocket";
891 self.results.push(SqlResult::Err {
892 error: Error::SubscribeOnlyOverWs.into(),
893 notices: Vec::new(),
894 });
895 (
896 Err(()),
897 Some((
898 StatementEndedExecutionReason::Errored {
899 error: message.into(),
900 },
901 ctx_extra,
902 )),
903 )
904 }
905 };
906 (Ok(res), stmt_logging)
907 }
908
909 fn connection_error(&mut self) -> BoxFuture<'_, Error> {
910 Box::pin(futures::future::pending())
911 }
912
913 fn allow_subscribe(&self) -> bool {
914 false
915 }
916}
917
918#[async_trait]
919impl ResultSender for WebSocket {
920 const SUPPORTS_STREAMING_NOTICES: bool = true;
921
922 async fn add_result(
928 &mut self,
929 client: &mut SessionClient,
930 res: StatementResult,
931 ) -> (
932 Result<Result<(), ()>, Error>,
933 Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
934 ) {
935 let (has_rows, is_streaming) = match res {
936 StatementResult::SqlResult(SqlResult::Err { .. }) => (false, false),
937 StatementResult::SqlResult(SqlResult::Ok { .. }) => (false, false),
938 StatementResult::SqlResult(SqlResult::Rows { .. }) => (true, false),
939 StatementResult::Subscribe { .. } => (true, true),
940 };
941 if let Err(e) = send_ws_response(
942 self,
943 WebSocketResponse::CommandStarting(CommandStarting {
944 has_rows,
945 is_streaming,
946 }),
947 )
948 .await
949 {
950 return (Err(e), None);
951 }
952
953 let (is_err, msgs, stmt_logging) = match res {
954 StatementResult::SqlResult(SqlResult::Rows {
955 tag,
956 rows,
957 desc,
958 notices,
959 }) => {
960 if let Err(e) = send_ws_response(self, WebSocketResponse::Rows(desc)).await {
963 return (Err(e), None);
964 }
965 for row in rows {
966 if let Err(e) = send_ws_response(self, WebSocketResponse::Row(row)).await {
967 return (Err(e), None);
968 }
969 }
970 let mut msgs = vec![WebSocketResponse::CommandComplete(tag)];
971 msgs.extend(notices.into_iter().map(WebSocketResponse::Notice));
972 (false, msgs, None)
973 }
974 StatementResult::SqlResult(SqlResult::Ok {
975 ok,
976 parameters,
977 notices,
978 }) => {
979 let mut msgs = vec![WebSocketResponse::CommandComplete(ok)];
980 msgs.extend(notices.into_iter().map(WebSocketResponse::Notice));
981 msgs.extend(
982 parameters
983 .into_iter()
984 .map(WebSocketResponse::ParameterStatus),
985 );
986 (false, msgs, None)
987 }
988 StatementResult::SqlResult(SqlResult::Err { error, notices }) => {
989 let mut msgs = vec![WebSocketResponse::Error(error)];
990 msgs.extend(notices.into_iter().map(WebSocketResponse::Notice));
991 (true, msgs, None)
992 }
993 StatementResult::Subscribe {
994 ref desc,
995 tag,
996 mut rx,
997 ctx_extra,
998 } => {
999 if let Err(e) = send_ws_response(self, WebSocketResponse::Rows(desc.into())).await {
1000 return (
1003 Err(e),
1004 Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1005 );
1006 }
1007
1008 let mut datum_vec = mz_repr::DatumVec::new();
1009 let mut result_size: usize = 0;
1010 let mut rows_returned = 0;
1011 loop {
1012 let res = match await_rows(self, client, rx.recv()).await {
1013 Ok(res) => res,
1014 Err(e) => {
1015 return (
1018 Err(e),
1019 Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1020 );
1021 }
1022 };
1023 match res {
1024 Some(PeekResponseUnary::Rows(mut rows)) => {
1025 if let Err(err) = verify_datum_desc(desc, &mut rows) {
1026 let error = err.to_string();
1027 break (
1028 true,
1029 vec![WebSocketResponse::Error(err.into())],
1030 Some((
1031 StatementEndedExecutionReason::Errored { error },
1032 ctx_extra,
1033 )),
1034 );
1035 }
1036
1037 rows_returned += rows.count();
1038 while let Some(row) = rows.next() {
1039 result_size += row.byte_len();
1040 let datums = datum_vec.borrow_with(row);
1041 let types = &desc.typ().column_types;
1042 if let Err(e) = send_ws_response(
1043 self,
1044 WebSocketResponse::Row(
1045 datums
1046 .iter()
1047 .enumerate()
1048 .map(|(i, d)| {
1049 TypedDatum::new(*d, &types[i])
1050 .json(&JsonNumberPolicy::ConvertNumberToString)
1051 })
1052 .collect(),
1053 ),
1054 )
1055 .await
1056 {
1057 return (
1060 Err(e),
1061 Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1062 );
1063 }
1064 }
1065 }
1066 Some(PeekResponseUnary::Error(error)) => {
1067 break (
1068 true,
1069 vec![WebSocketResponse::Error(
1070 Error::Unstructured(anyhow!(error.clone())).into(),
1071 )],
1072 Some((StatementEndedExecutionReason::Errored { error }, ctx_extra)),
1073 );
1074 }
1075 Some(PeekResponseUnary::DependencyDropped(dep)) => {
1076 let err = dep.to_concurrent_dependency_drop();
1077 let error = err.to_string();
1078 break (
1079 true,
1080 vec![WebSocketResponse::Error(err.into())],
1081 Some((StatementEndedExecutionReason::Errored { error }, ctx_extra)),
1082 );
1083 }
1084 Some(PeekResponseUnary::Canceled) => {
1085 break (
1086 true,
1087 vec![WebSocketResponse::Error(AdapterError::Canceled.into())],
1088 Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1089 );
1090 }
1091 None => {
1092 break (
1093 false,
1094 vec![WebSocketResponse::CommandComplete(tag)],
1095 Some((
1096 StatementEndedExecutionReason::Success {
1097 result_size: Some(u64::cast_from(result_size)),
1098 rows_returned: Some(u64::cast_from(rows_returned)),
1099 execution_strategy: Some(
1100 StatementExecutionStrategy::Standard,
1101 ),
1102 },
1103 ctx_extra,
1104 )),
1105 );
1106 }
1107 }
1108 }
1109 }
1110 };
1111 for msg in msgs {
1112 if let Err(e) = send_ws_response(self, msg).await {
1113 return (
1114 Err(e),
1115 stmt_logging.map(|(_old_reason, ctx_extra)| {
1116 (StatementEndedExecutionReason::Canceled, ctx_extra)
1117 }),
1118 );
1119 }
1120 }
1121 (Ok(if is_err { Err(()) } else { Ok(()) }), stmt_logging)
1122 }
1123
1124 fn connection_error(&mut self) -> BoxFuture<'_, Error> {
1127 Box::pin(async {
1128 let mut tick = time::interval(Duration::from_secs(1));
1129 tick.tick().await;
1130 loop {
1131 tick.tick().await;
1132 if let Err(err) = self.send(Message::Ping(Vec::new().into())).await {
1133 return err.into();
1134 }
1135 }
1136 })
1137 }
1138
1139 fn allow_subscribe(&self) -> bool {
1140 true
1141 }
1142
1143 async fn emit_streaming_notices(&mut self, notices: Vec<AdapterNotice>) -> Result<(), Error> {
1144 forward_notices(self, notices).await
1145 }
1146}
1147
1148async fn await_rows<S, F, R>(sender: &mut S, client: &mut SessionClient, f: F) -> Result<R, Error>
1149where
1150 S: ResultSender,
1151 F: Future<Output = R> + Send,
1152{
1153 let mut f = pin!(f);
1154 loop {
1155 tokio::select! {
1156 notice = client.session().recv_notice(), if S::SUPPORTS_STREAMING_NOTICES => {
1157 sender.emit_streaming_notices(vec![notice]).await?;
1158 }
1159 e = sender.connection_error() => return Err(e),
1160 r = &mut f => return Ok(r),
1161 }
1162 }
1163}
1164
1165async fn send_and_retire<S: ResultSender>(
1166 res: StatementResult,
1167 client: &mut SessionClient,
1168 sender: &mut S,
1169) -> Result<Result<(), ()>, Error> {
1170 let (res, stmt_logging) = sender.add_result(client, res).await;
1171 if let Some((reason, ctx_extra)) = stmt_logging {
1172 client.retire_execute(ctx_extra, reason);
1173 }
1174 res
1175}
1176
1177async fn execute_stmt_group<S: ResultSender>(
1179 client: &mut SessionClient,
1180 sender: &mut S,
1181 stmt_group: Vec<(Statement<Raw>, String, Vec<Option<String>>)>,
1182) -> Result<Result<(), ()>, Error> {
1183 let num_stmts = stmt_group.len();
1184 for (stmt, sql, params) in stmt_group {
1185 assert!(
1186 num_stmts <= 1 || params.is_empty(),
1187 "statement groups contain more than 1 statement iff Simple request, which does not support parameters"
1188 );
1189
1190 let is_aborted_txn = matches!(client.session().transaction(), TransactionStatus::Failed(_));
1191 if is_aborted_txn && !is_txn_exit_stmt(&stmt) {
1192 let err = SqlResult::err(client, Error::AbortedTransaction);
1193 let _ = send_and_retire(err.into(), client, sender).await?;
1194 return Ok(Err(()));
1195 }
1196
1197 if let Err(e) = client.start_transaction(Some(num_stmts)) {
1200 let err = SqlResult::err(client, e);
1201 let _ = send_and_retire(err.into(), client, sender).await?;
1202 return Ok(Err(()));
1203 }
1204 let res = execute_stmt(client, sender, stmt, sql, params).await?;
1205 let is_err = send_and_retire(res, client, sender).await?;
1206
1207 if is_err.is_err() {
1208 let txn = client.session().transaction();
1211 match txn {
1212 TransactionStatus::Default | TransactionStatus::Failed(_) => {}
1215 TransactionStatus::Started(_) | TransactionStatus::InTransactionImplicit(_) => {
1217 if let Err(err) = client.end_transaction(EndTransactionAction::Rollback).await {
1218 let err = SqlResult::err(client, err);
1219 let _ = send_and_retire(err.into(), client, sender).await?;
1220 }
1221 }
1222 TransactionStatus::InTransaction(_) => {
1224 client.fail_transaction();
1225 }
1226 }
1227 return Ok(Err(()));
1228 }
1229 }
1230 Ok(Ok(()))
1231}
1232
1233pub(in crate::http) async fn execute_request<S: ResultSender>(
1242 client: &mut AuthedClient,
1243 request: SqlRequest,
1244 sender: &mut S,
1245) -> Result<(), Error> {
1246 let client = &mut client.client;
1247
1248 fn check_prohibited_stmts<S: ResultSender>(
1251 sender: &S,
1252 stmt: &Statement<Raw>,
1253 ) -> Result<(), Error> {
1254 let kind: StatementKind = stmt.into();
1255 let execute_responses = Plan::generated_from(&kind)
1256 .into_iter()
1257 .map(ExecuteResponse::generated_from)
1258 .flatten()
1259 .collect::<Vec<_>>();
1260
1261 let is_valid_copy = matches!(
1265 stmt,
1266 Statement::Copy(CopyStatement {
1267 direction: CopyDirection::To,
1268 target: CopyTarget::Expr(_),
1269 ..
1270 }) | Statement::Copy(CopyStatement {
1271 direction: CopyDirection::From,
1272 target: CopyTarget::Expr(_),
1273 ..
1274 })
1275 );
1276
1277 if !is_valid_copy
1278 && execute_responses.iter().any(|execute_response| {
1279 match execute_response {
1281 ExecuteResponseKind::Subscribing if sender.allow_subscribe() => false,
1282 ExecuteResponseKind::Fetch
1283 | ExecuteResponseKind::Subscribing
1284 | ExecuteResponseKind::CopyFrom
1285 | ExecuteResponseKind::DeclaredCursor
1286 | ExecuteResponseKind::ClosedCursor => true,
1287 ExecuteResponseKind::CopyTo if matches!(kind, StatementKind::Copy) => true,
1292 _ => false,
1293 }
1294 })
1295 {
1296 return Err(Error::Unsupported(stmt.to_ast_string_simple()));
1297 }
1298 Ok(())
1299 }
1300
1301 fn parse<'a>(
1302 client: &SessionClient,
1303 query: &'a str,
1304 ) -> Result<Vec<StatementParseResult<'a>>, Error> {
1305 let result = client
1306 .parse(query)
1307 .map_err(|e| Error::Unstructured(anyhow!(e)))?;
1308 result.map_err(|e| AdapterError::from(e).into())
1309 }
1310
1311 let mut stmt_groups = vec![];
1312
1313 match request {
1314 SqlRequest::Simple { query } => match parse(client, query.as_str()) {
1315 Ok(stmts) => {
1316 let mut stmt_group = Vec::with_capacity(stmts.len());
1317 let mut stmt_err = None;
1318 for StatementParseResult { ast: stmt, sql } in stmts {
1319 if let Err(err) = check_prohibited_stmts(sender, &stmt) {
1320 stmt_err = Some(err);
1321 break;
1322 }
1323 stmt_group.push((stmt, sql.to_string(), vec![]));
1324 }
1325 stmt_groups.push(stmt_err.map(Err).unwrap_or_else(|| Ok(stmt_group)));
1326 }
1327 Err(e) => stmt_groups.push(Err(e)),
1328 },
1329 SqlRequest::Extended { queries } => {
1330 for ExtendedRequest { query, params } in queries {
1331 match parse(client, &query) {
1332 Ok(mut stmts) => {
1333 if stmts.len() != 1 {
1334 return Err(Error::Unstructured(anyhow!(
1335 "each query must contain exactly 1 statement, but \"{}\" contains {}",
1336 query,
1337 stmts.len()
1338 )));
1339 }
1340
1341 let StatementParseResult { ast: stmt, sql } = stmts.pop().unwrap();
1342 stmt_groups.push(
1343 check_prohibited_stmts(sender, &stmt)
1344 .map(|_| vec![(stmt, sql.to_string(), params)]),
1345 );
1346 }
1347 Err(e) => stmt_groups.push(Err(e)),
1348 };
1349 }
1350 }
1351 }
1352
1353 for stmt_group_res in stmt_groups {
1354 let executed = match stmt_group_res {
1355 Ok(stmt_group) => execute_stmt_group(client, sender, stmt_group).await,
1356 Err(e) => {
1357 let err = SqlResult::err(client, e);
1358 let _ = send_and_retire(err.into(), client, sender).await?;
1359 Ok(Err(()))
1360 }
1361 };
1362 if client.session().transaction().is_implicit() {
1365 let ended = client.end_transaction(EndTransactionAction::Commit).await;
1366 if let Err(err) = ended {
1367 let err = SqlResult::err(client, err);
1368 let _ = send_and_retire(StatementResult::SqlResult(err), client, sender).await?;
1369 }
1370 }
1371 if executed?.is_err() {
1372 break;
1373 }
1374 }
1375
1376 Ok(())
1377}
1378
1379async fn execute_stmt<S: ResultSender>(
1381 client: &mut SessionClient,
1382 sender: &mut S,
1383 stmt: Statement<Raw>,
1384 sql: String,
1385 raw_params: Vec<Option<String>>,
1386) -> Result<StatementResult, Error> {
1387 const EMPTY_PORTAL: &str = "";
1388 if let Err(e) = client
1389 .prepare(EMPTY_PORTAL.into(), Some(stmt.clone()), sql, vec![])
1390 .await
1391 {
1392 return Ok(SqlResult::err(client, e).into());
1393 }
1394
1395 let prep_stmt = match client.get_prepared_statement(EMPTY_PORTAL).await {
1396 Ok(stmt) => stmt,
1397 Err(err) => {
1398 return Ok(SqlResult::err(client, err).into());
1399 }
1400 };
1401
1402 let param_types = &prep_stmt.desc().param_types;
1403 if param_types.len() != raw_params.len() {
1404 let message = anyhow!(
1405 "request supplied {actual} parameters, \
1406 but {statement} requires {expected}",
1407 statement = stmt.to_ast_string_simple(),
1408 actual = raw_params.len(),
1409 expected = param_types.len()
1410 );
1411 return Ok(SqlResult::err(client, Error::Unstructured(message)).into());
1412 }
1413
1414 let buf = RowArena::new();
1415 let mut params = vec![];
1416 for (raw_param, mz_typ) in raw_params.into_iter().zip_eq(param_types) {
1417 let pg_typ = mz_pgrepr::Type::from(mz_typ);
1418 let datum = match raw_param {
1419 None => Datum::Null,
1420 Some(raw_param) => {
1421 match mz_pgrepr::Value::decode(
1422 mz_pgwire_common::Format::Text,
1423 &pg_typ,
1424 raw_param.as_bytes(),
1425 ) {
1426 Ok(param) => match param.into_datum_decode_error(&buf, &pg_typ, "parameter") {
1427 Ok(datum) => datum,
1428 Err(msg) => {
1429 return Ok(
1430 SqlResult::err(client, Error::Unstructured(anyhow!(msg))).into()
1431 );
1432 }
1433 },
1434 Err(err) => {
1435 let msg = anyhow!("unable to decode parameter: {}", err);
1436 return Ok(SqlResult::err(client, Error::Unstructured(msg)).into());
1437 }
1438 }
1439 }
1440 };
1441 params.push((datum, mz_typ.clone()))
1442 }
1443
1444 let result_formats = vec![
1445 mz_pgwire_common::Format::Text;
1446 prep_stmt
1447 .desc()
1448 .relation_desc
1449 .clone()
1450 .map(|desc| desc.typ().column_types.len())
1451 .unwrap_or(0)
1452 ];
1453
1454 let desc = prep_stmt.desc().clone();
1455 let logging = Arc::clone(prep_stmt.logging());
1456 let stmt_ast = prep_stmt.stmt().cloned();
1457 let state_revision = prep_stmt.state_revision;
1458 if let Err(err) = client.session().set_portal(
1459 EMPTY_PORTAL.into(),
1460 desc,
1461 stmt_ast,
1462 logging,
1463 params,
1464 result_formats,
1465 state_revision,
1466 ) {
1467 return Ok(SqlResult::err(client, err).into());
1468 }
1469
1470 let desc = client
1471 .session()
1472 .get_portal_unverified(EMPTY_PORTAL)
1474 .map(|portal| portal.desc.clone())
1475 .expect("unnamed portal should be present");
1476
1477 let res = client
1478 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
1479 .await;
1480
1481 if S::SUPPORTS_STREAMING_NOTICES {
1482 sender
1483 .emit_streaming_notices(client.session().drain_notices())
1484 .await?;
1485 }
1486
1487 let (res, execute_started) = match res {
1488 Ok(res) => res,
1489 Err(e) => {
1490 return Ok(SqlResult::err(client, e).into());
1491 }
1492 };
1493 let tag = res.tag();
1494
1495 Ok(match res {
1496 ExecuteResponse::CreatedConnection { .. }
1497 | ExecuteResponse::CreatedDatabase { .. }
1498 | ExecuteResponse::CreatedSchema { .. }
1499 | ExecuteResponse::CreatedRole
1500 | ExecuteResponse::CreatedCluster { .. }
1501 | ExecuteResponse::CreatedClusterReplica { .. }
1502 | ExecuteResponse::CreatedTable { .. }
1503 | ExecuteResponse::CreatedIndex { .. }
1504 | ExecuteResponse::CreatedIntrospectionSubscribe
1505 | ExecuteResponse::CreatedSecret { .. }
1506 | ExecuteResponse::CreatedSource { .. }
1507 | ExecuteResponse::CreatedSink { .. }
1508 | ExecuteResponse::CreatedView { .. }
1509 | ExecuteResponse::CreatedViews { .. }
1510 | ExecuteResponse::CreatedMaterializedView { .. }
1511 | ExecuteResponse::CreatedType
1512 | ExecuteResponse::CreatedNetworkPolicy
1513 | ExecuteResponse::Comment
1514 | ExecuteResponse::Deleted(_)
1515 | ExecuteResponse::DiscardedTemp
1516 | ExecuteResponse::DiscardedAll
1517 | ExecuteResponse::DroppedObject(_)
1518 | ExecuteResponse::DroppedOwned
1519 | ExecuteResponse::EmptyQuery
1520 | ExecuteResponse::GrantedPrivilege
1521 | ExecuteResponse::GrantedRole
1522 | ExecuteResponse::Inserted(_)
1523 | ExecuteResponse::Copied(_)
1524 | ExecuteResponse::Raised
1525 | ExecuteResponse::ReassignOwned
1526 | ExecuteResponse::RevokedPrivilege
1527 | ExecuteResponse::AlteredDefaultPrivileges
1528 | ExecuteResponse::RevokedRole
1529 | ExecuteResponse::StartedTransaction { .. }
1530 | ExecuteResponse::Updated(_)
1531 | ExecuteResponse::AlteredObject(_)
1532 | ExecuteResponse::AlteredRole
1533 | ExecuteResponse::AlteredSystemConfiguration
1534 | ExecuteResponse::Deallocate { .. }
1535 | ExecuteResponse::ValidatedConnection
1536 | ExecuteResponse::Prepare => SqlResult::ok(
1537 client,
1538 tag.expect("ok only called on tag-generating results"),
1539 Vec::default(),
1540 )
1541 .into(),
1542 ExecuteResponse::TransactionCommitted { params }
1543 | ExecuteResponse::TransactionRolledBack { params } => {
1544 let notify_set: mz_ore::collections::HashSet<_> = client
1545 .session()
1546 .vars()
1547 .notify_set()
1548 .map(|v| v.name().to_string())
1549 .collect();
1550 let params = params
1551 .into_iter()
1552 .filter(|(name, _value)| notify_set.contains(*name))
1553 .map(|(name, value)| ParameterStatus {
1554 name: name.to_string(),
1555 value,
1556 })
1557 .collect();
1558 SqlResult::ok(
1559 client,
1560 tag.expect("ok only called on tag-generating results"),
1561 params,
1562 )
1563 .into()
1564 }
1565 ExecuteResponse::SetVariable { name, .. } => {
1566 let mut params = Vec::with_capacity(1);
1567 if let Some(var) = client
1568 .session()
1569 .vars()
1570 .notify_set()
1571 .find(|v| v.name() == &name)
1572 {
1573 params.push(ParameterStatus {
1574 name,
1575 value: var.value(),
1576 });
1577 };
1578 SqlResult::ok(
1579 client,
1580 tag.expect("ok only called on tag-generating results"),
1581 params,
1582 )
1583 .into()
1584 }
1585 ExecuteResponse::SendingRowsStreaming {
1586 rows,
1587 instance_id,
1588 strategy,
1589 } => {
1590 let max_query_result_size =
1591 usize::cast_from(client.get_system_vars().await.max_result_size());
1592
1593 let rows_stream = RecordFirstRowStream::new(
1594 Box::new(rows),
1595 execute_started,
1596 client,
1597 Some(instance_id),
1598 Some(strategy),
1599 );
1600
1601 SqlResult::rows(
1602 sender,
1603 client,
1604 rows_stream,
1605 max_query_result_size,
1606 &desc.relation_desc.expect("RelationDesc must exist"),
1607 )
1608 .await?
1609 .into()
1610 }
1611 ExecuteResponse::SendingRowsImmediate { rows } => {
1612 let max_query_result_size =
1613 usize::cast_from(client.get_system_vars().await.max_result_size());
1614
1615 let rows = futures::stream::once(futures::future::ready(PeekResponseUnary::Rows(rows)));
1616 let rows_stream =
1617 RecordFirstRowStream::new(Box::new(rows), execute_started, client, None, None);
1618
1619 SqlResult::rows(
1620 sender,
1621 client,
1622 rows_stream,
1623 max_query_result_size,
1624 &desc.relation_desc.expect("RelationDesc must exist"),
1625 )
1626 .await?
1627 .into()
1628 }
1629 ExecuteResponse::Subscribing {
1630 rx,
1631 ctx_extra,
1632 instance_id,
1633 } => StatementResult::Subscribe {
1634 tag: "SUBSCRIBE".into(),
1635 desc: desc.relation_desc.unwrap(),
1636 rx: RecordFirstRowStream::new(
1637 Box::new(UnboundedReceiverStream::new(rx)),
1638 execute_started,
1639 client,
1640 Some(instance_id),
1641 None,
1642 ),
1643 ctx_extra,
1644 },
1645 res @ (ExecuteResponse::Fetch { .. }
1646 | ExecuteResponse::CopyTo { .. }
1647 | ExecuteResponse::CopyFrom { .. }
1648 | ExecuteResponse::DeclaredCursor
1649 | ExecuteResponse::ClosedCursor) => SqlResult::err(
1650 client,
1651 Error::Unstructured(anyhow!(
1652 "internal error: encountered prohibited ExecuteResponse {:?}.\n\n
1653 This is a bug. Can you please file an bug report letting us know?\n
1654 https://github.com/MaterializeInc/materialize/discussions/new?category=bug-reports",
1655 ExecuteResponseKind::from(res)
1656 )),
1657 )
1658 .into(),
1659 })
1660}
1661
1662fn make_notices(client: &mut SessionClient) -> Vec<Notice> {
1663 client
1664 .session()
1665 .drain_notices()
1666 .into_iter()
1667 .map(|notice| Notice {
1668 message: notice.to_string(),
1669 code: notice.code().code().to_string(),
1670 severity: notice.severity().as_str().to_lowercase(),
1671 detail: notice.detail(),
1672 hint: notice.hint(),
1673 })
1674 .collect()
1675}
1676
1677fn is_txn_exit_stmt(stmt: &Statement<Raw>) -> bool {
1680 matches!(
1681 stmt,
1682 Statement::Commit(_) | Statement::Rollback(_) | Statement::Prepare(_)
1683 )
1684}
1685
1686#[cfg(test)]
1687mod tests {
1688 use std::collections::BTreeMap;
1689
1690 use super::{Password, WebSocketAuth};
1691
1692 #[mz_ore::test]
1693 fn smoke_test_websocket_auth_parse() {
1694 struct TestCase {
1695 json: &'static str,
1696 expected: WebSocketAuth,
1697 }
1698
1699 let test_cases = vec![
1700 TestCase {
1701 json: r#"{ "user": "mz", "password": "1234" }"#,
1702 expected: WebSocketAuth::Basic {
1703 user: "mz".to_string(),
1704 password: Password("1234".to_string()),
1705 options: BTreeMap::default(),
1706 },
1707 },
1708 TestCase {
1709 json: r#"{ "user": "mz", "password": "1234", "options": {} }"#,
1710 expected: WebSocketAuth::Basic {
1711 user: "mz".to_string(),
1712 password: Password("1234".to_string()),
1713 options: BTreeMap::default(),
1714 },
1715 },
1716 TestCase {
1717 json: r#"{ "token": "i_am_a_token" }"#,
1718 expected: WebSocketAuth::Bearer {
1719 token: "i_am_a_token".to_string(),
1720 options: BTreeMap::default(),
1721 },
1722 },
1723 TestCase {
1724 json: r#"{ "token": "i_am_a_token", "options": { "foo": "bar" } }"#,
1725 expected: WebSocketAuth::Bearer {
1726 token: "i_am_a_token".to_string(),
1727 options: BTreeMap::from([("foo".to_string(), "bar".to_string())]),
1728 },
1729 },
1730 ];
1731
1732 fn assert_parse(json: &'static str, expected: WebSocketAuth) {
1733 let parsed: WebSocketAuth = serde_json::from_str(json).unwrap();
1734 assert_eq!(parsed, expected);
1735 }
1736
1737 for TestCase { json, expected } in test_cases {
1738 assert_parse(json, expected)
1739 }
1740 }
1741}