Skip to main content

mz_environmentd/http/
sql.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::BTreeMap;
11use std::net::{IpAddr, SocketAddr};
12use std::pin::pin;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
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, redact_sql_for_logging};
29use mz_adapter::session::{EndTransactionAction, TransactionStatus};
30use mz_adapter::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
31use mz_adapter::{
32    AdapterError, AdapterNotice, ExecuteContextGuard, ExecuteResponse, ExecuteResponseKind,
33    ExecutionTime, 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 tower_sessions::Session as TowerSession;
55use tracing::{debug, info};
56use tungstenite::protocol::frame::coding::CloseCode;
57
58use crate::http::prometheus::PrometheusSqlQuery;
59use crate::http::{
60    AuthError, AuthedClient, AuthedUser, MAX_REQUEST_SIZE, WsState, ensure_session_unexpired,
61    init_ws, maybe_get_authenticated_session,
62};
63
64#[derive(Debug, thiserror::Error)]
65pub enum Error {
66    #[error(transparent)]
67    Adapter(#[from] AdapterError),
68    #[error(transparent)]
69    Json(#[from] serde_json::Error),
70    #[error(transparent)]
71    Axum(#[from] axum::Error),
72    #[error("SUBSCRIBE only supported over websocket")]
73    SubscribeOnlyOverWs,
74    #[error("current transaction is aborted, commands ignored until end of transaction block")]
75    AbortedTransaction,
76    #[error("unsupported via this API: {0}")]
77    Unsupported(String),
78    #[error("{0}")]
79    Unstructured(anyhow::Error),
80}
81
82impl Error {
83    pub fn detail(&self) -> Option<String> {
84        match self {
85            Error::Adapter(err) => err.detail(),
86            _ => None,
87        }
88    }
89
90    pub fn hint(&self) -> Option<String> {
91        match self {
92            Error::Adapter(err) => err.hint(),
93            _ => None,
94        }
95    }
96
97    pub fn position(&self) -> Option<usize> {
98        match self {
99            Error::Adapter(err) => err.position(),
100            _ => None,
101        }
102    }
103
104    pub fn code(&self) -> SqlState {
105        match self {
106            Error::Adapter(err) => err.code(),
107            Error::AbortedTransaction => SqlState::IN_FAILED_SQL_TRANSACTION,
108            _ => SqlState::INTERNAL_ERROR,
109        }
110    }
111}
112
113static PER_REPLICA_LABELS: &[&str] = &["replica_full_name", "instance_id", "replica_id"];
114
115async fn execute_promsql_query(
116    client: &mut AuthedClient,
117    query: &PrometheusSqlQuery<'_>,
118    metrics_registry: &MetricsRegistry,
119    metrics_by_name: &mut BTreeMap<String, GenericGaugeVec<AtomicF64>>,
120    cluster: Option<(&Cluster, &ClusterReplica)>,
121) {
122    assert_eq!(query.per_replica, cluster.is_some());
123
124    let mut res = SqlResponse {
125        results: Vec::new(),
126    };
127
128    execute_request(client, query.to_sql_request(cluster), &mut res)
129        .await
130        .expect("valid SQL query");
131
132    let result = match res.results.as_slice() {
133        // Each query issued is preceded by several SET commands
134        // to make sure it is routed to the right cluster replica.
135        [
136            SqlResult::Ok { .. },
137            SqlResult::Ok { .. },
138            SqlResult::Ok { .. },
139            result,
140        ] => result,
141        // Transient errors are fine, like if the cluster or replica
142        // was dropped before the promsql query was executed. We
143        // should not see errors in the steady state.
144        _ => {
145            info!(
146                "error executing prometheus query {}: {:?}",
147                query.metric_name, res
148            );
149            return;
150        }
151    };
152
153    let SqlResult::Rows { desc, rows, .. } = result else {
154        info!(
155            "did not receive rows for SQL query for prometheus metric {}: {:?}, {:?}",
156            query.metric_name, result, cluster
157        );
158        return;
159    };
160
161    let gauge_vec = metrics_by_name
162        .entry(query.metric_name.to_string())
163        .or_insert_with(|| {
164            let mut label_names: Vec<String> = desc
165                .columns
166                .iter()
167                .filter(|col| col.name != query.value_column_name)
168                .map(|col| col.name.clone())
169                .collect();
170
171            if query.per_replica {
172                label_names.extend(PER_REPLICA_LABELS.iter().map(|label| label.to_string()));
173            }
174
175            metrics_registry.register::<GenericGaugeVec<AtomicF64>>(MakeCollectorOpts {
176                opts: Opts::new(query.metric_name, query.help).variable_labels(label_names),
177                buckets: None,
178            })
179        });
180
181    for row in rows {
182        // Rows are stored as pre-serialized JSON arrays. Parse each one back to
183        // `Value`s here. Promsql results are tiny, so the parse cost is
184        // negligible.
185        let row: Vec<serde_json::Value> =
186            serde_json::from_str(row.get()).expect("row is a valid JSON array");
187
188        // Non-value columns become Prometheus label values. A SQL `NULL`
189        // arrives as JSON `null` and yields `None` from `as_str()`; fall back
190        // to an empty label rather than panicking. The query author is
191        // responsible for `COALESCE`ing nullable columns to a meaningful
192        // value; this is a defensive backstop.
193        let mut label_values = desc
194            .columns
195            .iter()
196            .zip_eq(&row)
197            .filter(|(col, _)| col.name != query.value_column_name)
198            .map(|(_, val)| val.as_str().unwrap_or(""))
199            .collect::<Vec<_>>();
200
201        let value = desc
202            .columns
203            .iter()
204            .zip_eq(&row)
205            .find(|(col, _)| col.name == query.value_column_name)
206            .map(|(_, val)| val.as_str().unwrap_or("0").parse::<f64>().unwrap_or(0.0))
207            .unwrap_or(0.0);
208
209        match cluster {
210            Some((cluster, replica)) => {
211                let replica_full_name = format!("{}.{}", cluster.name, replica.name);
212                let cluster_id = cluster.id.to_string();
213                let replica_id = replica.replica_id.to_string();
214
215                label_values.push(&replica_full_name);
216                label_values.push(&cluster_id);
217                label_values.push(&replica_id);
218
219                gauge_vec
220                    .get_metric_with_label_values(&label_values)
221                    .expect("valid labels")
222                    .set(value);
223            }
224            None => {
225                gauge_vec
226                    .get_metric_with_label_values(&label_values)
227                    .expect("valid labels")
228                    .set(value);
229            }
230        }
231    }
232}
233
234async fn handle_promsql_query(
235    client: &mut AuthedClient,
236    query: &PrometheusSqlQuery<'_>,
237    metrics_registry: &MetricsRegistry,
238    metrics_by_name: &mut BTreeMap<String, GenericGaugeVec<AtomicF64>>,
239) {
240    if !query.per_replica {
241        execute_promsql_query(client, query, metrics_registry, metrics_by_name, None).await;
242        return;
243    }
244
245    let catalog = client.client.catalog_snapshot("handle_promsql_query").await;
246    let clusters: Vec<&Cluster> = catalog.clusters().collect();
247
248    for cluster in clusters {
249        for replica in cluster.replicas() {
250            execute_promsql_query(
251                client,
252                query,
253                metrics_registry,
254                metrics_by_name,
255                Some((cluster, replica)),
256            )
257            .await;
258        }
259    }
260}
261
262pub async fn handle_promsql(
263    mut client: AuthedClient,
264    queries: &[PrometheusSqlQuery<'_>],
265) -> MetricsRegistry {
266    let metrics_registry = MetricsRegistry::new();
267    let mut metrics_by_name = BTreeMap::new();
268
269    for query in queries {
270        handle_promsql_query(&mut client, query, &metrics_registry, &mut metrics_by_name).await;
271    }
272
273    metrics_registry
274}
275
276pub async fn handle_sql(
277    mut client: AuthedClient,
278    Json(request): Json<SqlRequest>,
279) -> impl IntoResponse {
280    let mut res = SqlResponse {
281        results: Vec::new(),
282    };
283    // Don't need to worry about timeouts or resetting cancel here because there is always exactly 1
284    // request.
285    match execute_request(&mut client, request, &mut res).await {
286        Ok(()) => Ok(Json(res)),
287        Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
288    }
289}
290
291#[derive(Debug)]
292pub enum ExistingUser {
293    /// An AuthedUser provided by the
294    /// `x_materialize_user_header_auth` middleware
295    XMaterializeUserHeader(AuthedUser),
296    /// An AuthedUser provided by an authenticated session
297    /// established via [`crate::http::handle_login`].
298    Session(AuthedUser),
299}
300
301pub(crate) async fn handle_sql_ws(
302    State(state): State<WsState>,
303    existing_user: Option<Extension<AuthedUser>>,
304    ws: WebSocketUpgrade,
305    ConnectInfo(addr): ConnectInfo<SocketAddr>,
306    tower_session: Option<Extension<TowerSession>>,
307) -> Result<impl IntoResponse, AuthError> {
308    let session = tower_session.map(|Extension(session)| session);
309    // The `x_materialize_user_header_auth` middleware may have already provided the user for us
310    let user = match existing_user {
311        Some(Extension(user)) => Some(ExistingUser::XMaterializeUserHeader(user)),
312        None => {
313            let session = maybe_get_authenticated_session(session.as_ref()).await;
314            if let Some((session, session_data)) = session {
315                let user = ensure_session_unexpired(session, session_data).await?;
316                Some(ExistingUser::Session(user))
317            } else {
318                None
319            }
320        }
321    };
322
323    let addr = Box::new(addr.ip());
324    Ok(ws
325        .max_message_size(MAX_REQUEST_SIZE)
326        .on_upgrade(|ws| async move { run_ws(state, user, *addr, ws).await }))
327}
328
329#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
330#[serde(untagged)]
331pub enum WebSocketAuth {
332    Basic {
333        user: String,
334        password: Password,
335        #[serde(default)]
336        options: BTreeMap<String, String>,
337    },
338    Bearer {
339        token: String,
340        #[serde(default)]
341        options: BTreeMap<String, String>,
342    },
343    OptionsOnly {
344        #[serde(default)]
345        options: BTreeMap<String, String>,
346    },
347}
348
349async fn run_ws(state: WsState, user: Option<ExistingUser>, peer_addr: IpAddr, mut ws: WebSocket) {
350    let mut client = match init_ws(state, user, peer_addr, &mut ws).await {
351        Ok(client) => client,
352        Err(e) => {
353            // We omit most detail from the error message we send to the client, to
354            // avoid giving attackers unnecessary information during auth. AdapterErrors
355            // are safe to return because they're generated after authentication.
356            debug!("WS request failed init: {}", e);
357            let reason: Utf8Bytes = match e.downcast_ref::<AdapterError>() {
358                Some(error) => error.to_string().into(),
359                None => "unauthorized".to_string().into(),
360            };
361            let _ = ws
362                .send(Message::Close(Some(CloseFrame {
363                    code: CloseCode::Protocol.into(),
364                    reason,
365                })))
366                .await;
367            return;
368        }
369    };
370
371    // Successful auth, send startup messages.
372    let mut msgs = Vec::new();
373    let session = client.client.session();
374    for var in session.vars().notify_set() {
375        msgs.push(WebSocketResponse::ParameterStatus(ParameterStatus {
376            name: var.name().to_string(),
377            value: var.value(),
378        }));
379    }
380    msgs.push(WebSocketResponse::BackendKeyData(BackendKeyData {
381        conn_id: session.conn_id().unhandled(),
382        secret_key: session.secret_key(),
383    }));
384    msgs.push(WebSocketResponse::ReadyForQuery(
385        session.transaction_code().into(),
386    ));
387    for msg in msgs {
388        let _ = ws
389            .send(Message::Text(
390                serde_json::to_string(&msg).expect("must serialize").into(),
391            ))
392            .await;
393    }
394
395    // Send any notices that might have been generated on startup.
396    let notices = session.drain_notices();
397    if let Err(err) = forward_notices(&mut ws, notices).await {
398        debug!("failed to forward notices to WebSocket, {err:?}");
399        return;
400    }
401
402    loop {
403        // Handle timeouts first so we don't execute any statements when there's a pending timeout.
404        let msg = select! {
405            biased;
406
407            // `recv_timeout()` is cancel-safe as per it's docs.
408            Some(timeout) = client.client.recv_timeout() => {
409                client.client.terminate().await;
410                // We must wait for the client to send a request before we can send the error
411                // response. Although this isn't the PG wire protocol, we choose to mirror it by
412                // only sending errors as responses to requests.
413                let _ = ws.recv().await;
414                let err = Error::from(AdapterError::from(timeout));
415                let _ = send_ws_response(&mut ws, WebSocketResponse::Error(err.into())).await;
416                return;
417            },
418            message = ws.recv() => message,
419        };
420
421        client.client.remove_idle_in_transaction_session_timeout();
422
423        let msg = match msg {
424            Some(Ok(msg)) => msg,
425            _ => {
426                // client disconnected
427                return;
428            }
429        };
430
431        let req: Result<SqlRequest, Error> = match msg {
432            Message::Text(data) => serde_json::from_str(&data).err_into(),
433            Message::Binary(data) => serde_json::from_slice(&data).err_into(),
434            // Handled automatically by the server.
435            Message::Ping(_) => {
436                continue;
437            }
438            Message::Pong(_) => {
439                continue;
440            }
441            Message::Close(_) => {
442                return;
443            }
444        };
445
446        // Figure out if we need to send an error, any notices, but always the ready message.
447        let err = match run_ws_request(req, &mut client, &mut ws).await {
448            Ok(()) => None,
449            Err(err) => Some(WebSocketResponse::Error(err.into())),
450        };
451
452        // After running our request, there are several messages we need to send in a
453        // specific order.
454        //
455        // Note: we nest these into a closure so we can centralize our error handling
456        // for when sending over the WebSocket fails. We could also use a try {} block
457        // here, but those aren't stabilized yet.
458        let ws_response = || async {
459            // First respond with any error that might have occurred.
460            if let Some(e_resp) = err {
461                send_ws_response(&mut ws, e_resp).await?;
462            }
463
464            // Then forward along any notices we generated.
465            let notices = client.client.session().drain_notices();
466            forward_notices(&mut ws, notices).await?;
467
468            // Finally, respond that we're ready for the next query.
469            let ready =
470                WebSocketResponse::ReadyForQuery(client.client.session().transaction_code().into());
471            send_ws_response(&mut ws, ready).await?;
472
473            Ok::<_, Error>(())
474        };
475
476        if let Err(err) = ws_response().await {
477            debug!("failed to send response over WebSocket, {err:?}");
478            return;
479        }
480    }
481}
482
483async fn run_ws_request(
484    req: Result<SqlRequest, Error>,
485    client: &mut AuthedClient,
486    ws: &mut WebSocket,
487) -> Result<(), Error> {
488    let req = req?;
489    execute_request(client, req, ws).await
490}
491
492/// Sends a single [`WebSocketResponse`] over the provided [`WebSocket`].
493async fn send_ws_response(ws: &mut WebSocket, resp: WebSocketResponse) -> Result<(), Error> {
494    let msg = serde_json::to_string(&resp).unwrap();
495    let msg = Message::Text(msg.into());
496    ws.send(msg).await?;
497
498    Ok(())
499}
500
501/// Forwards a collection of Notices to the provided [`WebSocket`].
502async fn forward_notices(
503    ws: &mut WebSocket,
504    notices: impl IntoIterator<Item = AdapterNotice>,
505) -> Result<(), Error> {
506    let ws_notices = notices
507        .into_iter()
508        .map(|notice| WebSocketResponse::Notice(Notice::from(notice)));
509
510    for notice in ws_notices {
511        send_ws_response(ws, notice).await?;
512    }
513
514    Ok(())
515}
516
517/// A request to execute SQL over HTTP or WebSocket. Execution stops at the first statement
518/// that fails, including a statement whose implicit transaction fails to commit.
519#[derive(Serialize, Deserialize, Debug)]
520#[serde(untagged)]
521pub enum SqlRequest {
522    /// One SQL string without parameters, as in PostgreSQL's simple query protocol. Its
523    /// statements, separated by semicolons, share one implicit transaction, unless `BEGIN` and
524    /// `COMMIT` among them group statements into an explicit transaction.
525    Simple { query: Sql },
526    /// A list of statements, each with its own parameters. Each statement runs in its own
527    /// implicit transaction, as if it were followed by a `Sync` in PostgreSQL's extended query
528    /// protocol, unless `BEGIN` and `COMMIT` entries group statements into an explicit transaction.
529    Extended { queries: Vec<ExtendedRequest> },
530}
531
532/// One statement of an [`SqlRequest::Extended`].
533#[derive(Serialize, Deserialize, Debug)]
534pub struct ExtendedRequest {
535    /// A SQL string containing exactly one statement.
536    query: String,
537    /// The values of the statement's parameters, as text. `None` is SQL `NULL`.
538    #[serde(default)]
539    params: Vec<Option<String>>,
540}
541
542/// The response to a `SqlRequest`.
543#[derive(Debug, Serialize, Deserialize)]
544pub struct SqlResponse {
545    /// The results for each query in the request.
546    pub(in crate::http) results: Vec<SqlResult>,
547}
548
549impl SqlResponse {
550    /// Creates a new empty SqlResponse for collecting results.
551    pub(in crate::http) fn new() -> Self {
552        Self {
553            results: Vec::new(),
554        }
555    }
556}
557
558pub(in crate::http) enum StatementResult {
559    /// A complete result: an error, or the success of a statement without rows.
560    SqlResult(SqlResult),
561    /// A result whose rows are streamed, such as that of a `SELECT` or of a
562    /// write with `RETURNING`. The stream is consumed lazily by the sender so a
563    /// large result is not buffered whole. Contrast `SqlResult::Rows`, which
564    /// holds already-collected rows and exists only for the buffered JSON
565    /// transport.
566    Rows {
567        desc: RelationDesc,
568        rows_stream: RecordFirstRowStream,
569        max_result_size: usize,
570        /// Whether the statement ends its statement group. If it does, the group's implicit
571        /// transaction, if any, commits after the last row and before the statement's completion.
572        ends_group: bool,
573    },
574    Subscribe {
575        desc: RelationDesc,
576        tag: String,
577        rx: RecordFirstRowStream,
578        ctx_extra: ExecuteContextGuard,
579    },
580}
581
582impl From<SqlResult> for StatementResult {
583    fn from(inner: SqlResult) -> Self {
584        Self::SqlResult(inner)
585    }
586}
587
588/// The result of a single query in a [`SqlResponse`].
589#[derive(Debug, Serialize, Deserialize)]
590#[serde(untagged)]
591pub enum SqlResult {
592    /// The query returned rows.
593    Rows {
594        /// The command complete tag.
595        tag: String,
596        /// The result rows, each already serialized to a compact JSON array.
597        ///
598        /// We accumulate pre-serialized rows rather than a `serde_json::Value`
599        /// tree so the buffered footprint stays close to the wire size. A
600        /// `Value` tree is ~10-15x larger (each cell a heap `Value`, in a
601        /// per-row `Vec`, in the outer `Vec`), which is what let a large
602        /// `SELECT` over the JSON endpoint OOM the process. `RawValue`
603        /// re-serializes verbatim, so the wire format is unchanged.
604        rows: Vec<Box<serde_json::value::RawValue>>,
605        /// Information about each column.
606        desc: Description,
607        // Any notices generated during execution of the query.
608        notices: Vec<Notice>,
609    },
610    /// The query executed successfully but did not return rows.
611    Ok {
612        /// The command complete tag.
613        ok: String,
614        /// Any notices generated during execution of the query.
615        notices: Vec<Notice>,
616        /// Any parameters that may have changed.
617        ///
618        /// Note: skip serializing this field in a response if the list of parameters is empty.
619        #[serde(skip_serializing_if = "Vec::is_empty")]
620        parameters: Vec<ParameterStatus>,
621        /// The opted-in execution time notice, kept apart from `notices` because the WebSocket API
622        /// sends it before `CommandComplete` and the other notices after.
623        #[serde(skip)]
624        execution_time: Option<Notice>,
625    },
626    /// The query returned an error.
627    Err {
628        error: SqlError,
629        // Any notices generated during execution of the query.
630        notices: Vec<Notice>,
631    },
632}
633
634impl SqlResult {
635    /// Convert adapter Row results into the buffered web row result format.
636    /// Return an error result if, for example, the row format does not match the
637    /// expected descriptor, the result exceeds `max_query_result_size`, or the
638    /// commit of an implicit transaction that the statement ends fails. `Err`
639    /// means that the connection failed.
640    ///
641    /// This buffers the whole result, so it is used only by the JSON transport,
642    /// whose response is a single document. The WebSocket transport streams rows
643    /// through `StatementResult::Rows` and never calls this. The size guard
644    /// counts `Row::byte_len`, matching the WebSocket transport and pgwire, and
645    /// rows are stored as pre-serialized compact `RawValue`s to avoid the
646    /// amplified `Value`-tree buffering that could OOM.
647    async fn rows<S>(
648        sender: &mut S,
649        client: &mut SessionClient,
650        mut rows_stream: RecordFirstRowStream,
651        max_query_result_size: usize,
652        desc: &RelationDesc,
653        ends_group: bool,
654    ) -> Result<SqlResult, Error>
655    where
656        S: ResultSender,
657    {
658        let mut rows: Vec<Box<serde_json::value::RawValue>> = vec![];
659        let mut datum_vec = mz_repr::DatumVec::new();
660        let types = &desc.typ().column_types;
661
662        let mut query_result_size: usize = 0;
663
664        loop {
665            let peek_response = tokio::select! {
666                notice = client.session().recv_notice(), if S::SUPPORTS_STREAMING_NOTICES => {
667                    sender.emit_streaming_notices(vec![notice]).await?;
668                    continue;
669                }
670                e = sender.connection_error() => return Err(e),
671                r = rows_stream.recv() => {
672                    match r {
673                        Some(r) => r,
674                        None => break,
675                    }
676                },
677            };
678
679            let mut sql_rows = match peek_response {
680                PeekResponseUnary::Rows(rows) => rows,
681                PeekResponseUnary::Error(e) => {
682                    return Ok(SqlResult::err(client, e));
683                }
684                PeekResponseUnary::DependencyDropped(dep) => {
685                    return Ok(SqlResult::err(client, dep.to_concurrent_dependency_drop()));
686                }
687                PeekResponseUnary::Canceled => {
688                    return Ok(SqlResult::err(client, AdapterError::Canceled));
689                }
690            };
691
692            if let Err(err) = verify_datum_desc(desc, &mut sql_rows) {
693                return Ok(SqlResult::Err {
694                    error: err.into(),
695                    notices: make_notices(client),
696                });
697            }
698
699            while let Some(row) = sql_rows.next() {
700                // Enforce `max_result_size` on `Row::byte_len`, the same quantity
701                // pgwire and the WebSocket transport use, so the cap means one
702                // thing across every transport.
703                query_result_size = query_result_size.saturating_add(row.byte_len());
704                if query_result_size > max_query_result_size {
705                    use bytesize::ByteSize;
706                    return Ok(SqlResult::err(
707                        client,
708                        AdapterError::ResultSize(format!(
709                            "result exceeds max size of {}",
710                            ByteSize::b(u64::cast_from(max_query_result_size))
711                        )),
712                    ));
713                }
714                let datums = datum_vec.borrow_with(row);
715                let json_row: Vec<serde_json::Value> = datums
716                    .iter()
717                    .enumerate()
718                    .map(|(i, d)| {
719                        TypedDatum::new(*d, &types[i])
720                            .json(&JsonNumberPolicy::ConvertNumberToString)
721                    })
722                    .collect();
723                // Keep only the compact serialized JSON text. The transient
724                // `Value` tree above is dropped per row, so at most one row's tree
725                // is resident, avoiding the amplified buffering that could OOM.
726                let raw = serde_json::value::to_raw_value(&json_row)
727                    .expect("row of JSON values always serializes");
728                rows.push(raw);
729            }
730        }
731
732        let execution_time = rows_stream
733            .take_execution_time()
734            .map(|time| client.session().maybe_mark_staged(time));
735        // TODO(SQL-815): `SqlResult::Rows` has no `parameters` field, so the parameters the
736        // commit reverted (a `SET LOCAL` earlier in the group) are dropped.
737        if let Err(err) = client.settle_statement(ends_group).await {
738            return Ok(SqlResult::err(client, err));
739        }
740        let mut notices = make_notices(client);
741        notices.extend(
742            execution_time
743                .and_then(|time| client.session().execution_time_notice(time))
744                .map(Notice::from),
745        );
746        let tag = format!("SELECT {}", rows.len());
747        Ok(SqlResult::Rows {
748            tag,
749            rows,
750            desc: Description::from(desc),
751            notices,
752        })
753    }
754
755    fn err(client: &mut SessionClient, error: impl Into<SqlError>) -> SqlResult {
756        SqlResult::Err {
757            error: error.into(),
758            notices: make_notices(client),
759        }
760    }
761
762    /// The result of a statement whose response carries no rows. If `ends_group` and the
763    /// statement runs in an implicit transaction, that transaction commits first, a failed commit
764    /// becomes the result, and the parameters the commit reverted follow `params`.
765    async fn ok(
766        client: &mut SessionClient,
767        tag: String,
768        mut params: Vec<ParameterStatus>,
769        ends_group: bool,
770        execution_time: ExecutionTime,
771    ) -> SqlResult {
772        match client.settle_statement(ends_group).await {
773            Ok(settled) => {
774                params.extend(notify_params(client, settled.reverted));
775                SqlResult::Ok {
776                    ok: tag,
777                    parameters: params,
778                    notices: make_notices(client),
779                    execution_time: client
780                        .session()
781                        .execution_time_notice(execution_time)
782                        .map(Notice::from),
783                }
784            }
785            Err(err) => SqlResult::err(client, err),
786        }
787    }
788}
789
790#[derive(Debug, Deserialize, Serialize)]
791pub struct SqlError {
792    pub message: String,
793    pub code: String,
794    #[serde(skip_serializing_if = "Option::is_none")]
795    pub detail: Option<String>,
796    #[serde(skip_serializing_if = "Option::is_none")]
797    pub hint: Option<String>,
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub position: Option<usize>,
800}
801
802impl From<Error> for SqlError {
803    fn from(err: Error) -> Self {
804        SqlError {
805            message: err.to_string(),
806            code: err.code().code().to_string(),
807            detail: err.detail(),
808            hint: err.hint(),
809            position: err.position(),
810        }
811    }
812}
813
814impl From<AdapterError> for SqlError {
815    fn from(value: AdapterError) -> Self {
816        Error::from(value).into()
817    }
818}
819
820#[derive(Debug, Deserialize, Serialize)]
821#[serde(tag = "type", content = "payload")]
822pub enum WebSocketResponse {
823    ReadyForQuery(String),
824    Notice(Notice),
825    Rows(Description),
826    Row(Vec<serde_json::Value>),
827    CommandStarting(CommandStarting),
828    CommandComplete(String),
829    Error(SqlError),
830    ParameterStatus(ParameterStatus),
831    BackendKeyData(BackendKeyData),
832}
833
834#[derive(Debug, Serialize, Deserialize)]
835pub struct Notice {
836    message: String,
837    code: String,
838    severity: String,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub detail: Option<String>,
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub hint: Option<String>,
843}
844
845impl Notice {
846    pub fn message(&self) -> &str {
847        &self.message
848    }
849}
850
851#[derive(Debug, Serialize, Deserialize)]
852pub struct Description {
853    pub columns: Vec<Column>,
854}
855
856impl From<&RelationDesc> for Description {
857    fn from(desc: &RelationDesc) -> Self {
858        let columns = desc
859            .iter()
860            .map(|(name, typ)| {
861                let pg_type = mz_pgrepr::Type::from(&typ.scalar_type);
862                Column {
863                    name: name.to_string(),
864                    type_oid: pg_type.oid(),
865                    type_len: pg_type.typlen(),
866                    type_mod: pg_type.typmod(),
867                }
868            })
869            .collect();
870        Description { columns }
871    }
872}
873
874#[derive(Debug, Serialize, Deserialize)]
875pub struct Column {
876    pub name: String,
877    pub type_oid: u32,
878    pub type_len: i16,
879    pub type_mod: i32,
880}
881
882#[derive(Debug, Serialize, Deserialize)]
883pub struct ParameterStatus {
884    name: String,
885    value: String,
886}
887
888#[derive(Debug, Serialize, Deserialize)]
889pub struct BackendKeyData {
890    conn_id: u32,
891    secret_key: u32,
892}
893
894#[derive(Debug, Serialize, Deserialize)]
895pub struct CommandStarting {
896    has_rows: bool,
897    is_streaming: bool,
898}
899
900/// Trait describing how to transmit a response to a client. HTTP clients
901/// accumulate into a Vec and send all at once. WebSocket clients send each
902/// message as they occur.
903#[async_trait]
904pub(in crate::http) trait ResultSender: Send {
905    const SUPPORTS_STREAMING_NOTICES: bool = false;
906
907    /// Adds a result to the client. The first component of the return value is
908    /// Err if sending to the client
909    /// produced an error and the server should disconnect. It is Ok(Err) if the statement
910    /// produced an error and should error the transaction, but remain connected. It is Ok(Ok(()))
911    /// if the statement succeeded.
912    /// The second component of the return value is `Some` if execution still
913    /// needs to be retired for statement logging purposes.
914    async fn add_result(
915        &mut self,
916        client: &mut SessionClient,
917        res: StatementResult,
918    ) -> (
919        Result<Result<(), ()>, Error>,
920        Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
921    );
922
923    /// Returns a future that resolves only when the client connection has gone away.
924    fn connection_error(&mut self) -> BoxFuture<'_, Error>;
925    /// Reports whether the client supports streaming SUBSCRIBE results.
926    fn allow_subscribe(&self) -> bool;
927
928    /// Emits a streaming notice if the sender supports it.
929    ///
930    /// Does nothing if `SUPPORTS_STREAMING_NOTICES` is false.
931    async fn emit_streaming_notices(&mut self, _: Vec<AdapterNotice>) -> Result<(), Error> {
932        unreachable!("streaming notices marked as unsupported")
933    }
934}
935
936#[async_trait]
937impl ResultSender for SqlResponse {
938    // The first component of the return value is
939    // Err if sending to the client
940    // produced an error and the server should disconnect. It is Ok(Err) if the statement
941    // produced an error and should error the transaction, but remain connected. It is Ok(Ok(()))
942    // if the statement succeeded.
943    // The second component of the return value is `Some` if execution still
944    // needs to be retired for statement logging purposes.
945    async fn add_result(
946        &mut self,
947        client: &mut SessionClient,
948        res: StatementResult,
949    ) -> (
950        Result<Result<(), ()>, Error>,
951        Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
952    ) {
953        let (res, stmt_logging) = match res {
954            StatementResult::SqlResult(mut res) => {
955                if let SqlResult::Ok {
956                    notices,
957                    execution_time,
958                    ..
959                } = &mut res
960                {
961                    notices.extend(execution_time.take());
962                }
963                let is_err = matches!(res, SqlResult::Err { .. });
964                self.results.push(res);
965                let res = if is_err { Err(()) } else { Ok(()) };
966                (res, None)
967            }
968            StatementResult::Rows {
969                desc,
970                rows_stream,
971                max_result_size,
972                ends_group,
973            } => {
974                // The JSON transport is a single buffered document, so the rows
975                // must be collected before the response is serialized.
976                // `SqlResult::rows` bounds that buffer against `max_result_size`.
977                let res = match SqlResult::rows(
978                    self,
979                    client,
980                    rows_stream,
981                    max_result_size,
982                    &desc,
983                    ends_group,
984                )
985                .await
986                {
987                    Ok(res) => res,
988                    Err(e) => return (Err(e), None),
989                };
990                let is_err = matches!(res, SqlResult::Err { .. });
991                self.results.push(res);
992                let res = if is_err { Err(()) } else { Ok(()) };
993                (res, None)
994            }
995            StatementResult::Subscribe { ctx_extra, .. } => {
996                let message = "SUBSCRIBE only supported over websocket";
997                self.results.push(SqlResult::Err {
998                    error: Error::SubscribeOnlyOverWs.into(),
999                    notices: Vec::new(),
1000                });
1001                (
1002                    Err(()),
1003                    Some((
1004                        StatementEndedExecutionReason::Errored {
1005                            error: message.into(),
1006                        },
1007                        ctx_extra,
1008                    )),
1009                )
1010            }
1011        };
1012        (Ok(res), stmt_logging)
1013    }
1014
1015    fn connection_error(&mut self) -> BoxFuture<'_, Error> {
1016        Box::pin(futures::future::pending())
1017    }
1018
1019    fn allow_subscribe(&self) -> bool {
1020        false
1021    }
1022}
1023
1024#[async_trait]
1025impl ResultSender for WebSocket {
1026    const SUPPORTS_STREAMING_NOTICES: bool = true;
1027
1028    // The first component of the return value is Err if sending to the client produced an error and
1029    // the server should disconnect. It is Ok(Err) if the statement produced an error and should
1030    // error the transaction, but remain connected. It is Ok(Ok(())) if the statement succeeded. The
1031    // second component of the return value is `Some` if execution still needs to be retired for
1032    // statement logging purposes.
1033    async fn add_result(
1034        &mut self,
1035        client: &mut SessionClient,
1036        res: StatementResult,
1037    ) -> (
1038        Result<Result<(), ()>, Error>,
1039        Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
1040    ) {
1041        let (has_rows, is_streaming) = match res {
1042            StatementResult::SqlResult(SqlResult::Err { .. }) => (false, false),
1043            StatementResult::SqlResult(SqlResult::Ok { .. }) => (false, false),
1044            StatementResult::SqlResult(SqlResult::Rows { .. }) => (true, false),
1045            StatementResult::Rows { .. } => (true, false),
1046            StatementResult::Subscribe { .. } => (true, true),
1047        };
1048        if let Err(e) = send_ws_response(
1049            self,
1050            WebSocketResponse::CommandStarting(CommandStarting {
1051                has_rows,
1052                is_streaming,
1053            }),
1054        )
1055        .await
1056        {
1057            return (Err(e), None);
1058        }
1059
1060        let (is_err, msgs, stmt_logging) = match res {
1061            StatementResult::SqlResult(SqlResult::Rows { .. }) => {
1062                // `SqlResult::Rows` holds already-collected rows for the buffered
1063                // JSON transport only. The WebSocket transport streams peek
1064                // results through `StatementResult::Rows` and never materializes
1065                // a `SqlResult::Rows`.
1066                unreachable!("WebSocket streams peek rows via StatementResult::Rows")
1067            }
1068            StatementResult::Rows {
1069                ref desc,
1070                mut rows_stream,
1071                max_result_size,
1072                ends_group,
1073            } => match stream_ws_peek_rows(
1074                self,
1075                client,
1076                desc,
1077                &mut rows_stream,
1078                max_result_size,
1079                ends_group,
1080            )
1081            .await
1082            {
1083                Ok(result) => result,
1084                // A write failure means the remote broke the connection, which we
1085                // treat as a cancellation to match pgwire.
1086                Err(e) => return (Err(e), None),
1087            },
1088            StatementResult::SqlResult(SqlResult::Ok {
1089                ok,
1090                parameters,
1091                notices,
1092                execution_time,
1093            }) => {
1094                // The execution time precedes `CommandComplete`, so clients attribute it to this
1095                // statement rather than to the next one.
1096                let mut msgs: Vec<_> = execution_time
1097                    .into_iter()
1098                    .map(WebSocketResponse::Notice)
1099                    .collect();
1100                msgs.push(WebSocketResponse::CommandComplete(ok));
1101                msgs.extend(notices.into_iter().map(WebSocketResponse::Notice));
1102                msgs.extend(
1103                    parameters
1104                        .into_iter()
1105                        .map(WebSocketResponse::ParameterStatus),
1106                );
1107                (false, msgs, None)
1108            }
1109            StatementResult::SqlResult(SqlResult::Err { error, notices }) => {
1110                let mut msgs = vec![WebSocketResponse::Error(error)];
1111                msgs.extend(notices.into_iter().map(WebSocketResponse::Notice));
1112                (true, msgs, None)
1113            }
1114            StatementResult::Subscribe {
1115                ref desc,
1116                tag,
1117                mut rx,
1118                ctx_extra,
1119            } => {
1120                if let Err(e) = send_ws_response(self, WebSocketResponse::Rows(desc.into())).await {
1121                    // We consider the remote breaking the connection to be a cancellation,
1122                    // matching the behavior for pgwire
1123                    return (
1124                        Err(e),
1125                        Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1126                    );
1127                }
1128
1129                let mut datum_vec = mz_repr::DatumVec::new();
1130                let mut result_size: usize = 0;
1131                let mut rows_returned = 0;
1132                loop {
1133                    let res = match await_rows(self, client, rx.recv()).await {
1134                        Ok(res) => res,
1135                        Err(e) => {
1136                            // We consider the remote breaking the connection to be a cancellation,
1137                            // matching the behavior for pgwire
1138                            return (
1139                                Err(e),
1140                                Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1141                            );
1142                        }
1143                    };
1144                    match res {
1145                        Some(PeekResponseUnary::Rows(mut rows)) => {
1146                            if let Err(err) = verify_datum_desc(desc, &mut rows) {
1147                                let error = err.to_string();
1148                                break (
1149                                    true,
1150                                    vec![WebSocketResponse::Error(err.into())],
1151                                    Some((
1152                                        StatementEndedExecutionReason::Errored { error },
1153                                        ctx_extra,
1154                                    )),
1155                                );
1156                            }
1157
1158                            rows_returned += rows.count();
1159                            while let Some(row) = rows.next() {
1160                                result_size = result_size.saturating_add(row.byte_len());
1161                                let datums = datum_vec.borrow_with(row);
1162                                let types = &desc.typ().column_types;
1163                                if let Err(e) = send_ws_response(
1164                                    self,
1165                                    WebSocketResponse::Row(
1166                                        datums
1167                                            .iter()
1168                                            .enumerate()
1169                                            .map(|(i, d)| {
1170                                                TypedDatum::new(*d, &types[i])
1171                                                    .json(&JsonNumberPolicy::ConvertNumberToString)
1172                                            })
1173                                            .collect(),
1174                                    ),
1175                                )
1176                                .await
1177                                {
1178                                    // We consider the remote breaking the connection to be a cancellation,
1179                                    // matching the behavior for pgwire
1180                                    return (
1181                                        Err(e),
1182                                        Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1183                                    );
1184                                }
1185                            }
1186                        }
1187                        Some(PeekResponseUnary::Error(err)) => {
1188                            let error = err.to_string();
1189                            break (
1190                                true,
1191                                vec![WebSocketResponse::Error(err.into())],
1192                                Some((StatementEndedExecutionReason::Errored { error }, ctx_extra)),
1193                            );
1194                        }
1195                        Some(PeekResponseUnary::DependencyDropped(dep)) => {
1196                            let err = dep.to_concurrent_dependency_drop();
1197                            let error = err.to_string();
1198                            break (
1199                                true,
1200                                vec![WebSocketResponse::Error(err.into())],
1201                                Some((StatementEndedExecutionReason::Errored { error }, ctx_extra)),
1202                            );
1203                        }
1204                        Some(PeekResponseUnary::Canceled) => {
1205                            break (
1206                                true,
1207                                vec![WebSocketResponse::Error(AdapterError::Canceled.into())],
1208                                Some((StatementEndedExecutionReason::Canceled, ctx_extra)),
1209                            );
1210                        }
1211                        None => {
1212                            break (
1213                                false,
1214                                vec![WebSocketResponse::CommandComplete(tag)],
1215                                Some((
1216                                    StatementEndedExecutionReason::Success {
1217                                        result_size: Some(u64::cast_from(result_size)),
1218                                        rows_returned: Some(u64::cast_from(rows_returned)),
1219                                        execution_strategy: Some(
1220                                            StatementExecutionStrategy::Standard,
1221                                        ),
1222                                    },
1223                                    ctx_extra,
1224                                )),
1225                            );
1226                        }
1227                    }
1228                }
1229            }
1230        };
1231        for msg in msgs {
1232            if let Err(e) = send_ws_response(self, msg).await {
1233                return (
1234                    Err(e),
1235                    stmt_logging.map(|(_old_reason, ctx_extra)| {
1236                        (StatementEndedExecutionReason::Canceled, ctx_extra)
1237                    }),
1238                );
1239            }
1240        }
1241        (Ok(if is_err { Err(()) } else { Ok(()) }), stmt_logging)
1242    }
1243
1244    // Send a websocket Ping every second to verify the client is still
1245    // connected.
1246    fn connection_error(&mut self) -> BoxFuture<'_, Error> {
1247        Box::pin(async {
1248            let mut tick = time::interval(Duration::from_secs(1));
1249            tick.tick().await;
1250            loop {
1251                tick.tick().await;
1252                if let Err(err) = self.send(Message::Ping(Vec::new().into())).await {
1253                    return err.into();
1254                }
1255            }
1256        })
1257    }
1258
1259    fn allow_subscribe(&self) -> bool {
1260        true
1261    }
1262
1263    async fn emit_streaming_notices(&mut self, notices: Vec<AdapterNotice>) -> Result<(), Error> {
1264        forward_notices(self, notices).await
1265    }
1266}
1267
1268async fn await_rows<S, F, R>(sender: &mut S, client: &mut SessionClient, f: F) -> Result<R, Error>
1269where
1270    S: ResultSender,
1271    F: Future<Output = R> + Send,
1272{
1273    let mut f = pin!(f);
1274    loop {
1275        tokio::select! {
1276            notice = client.session().recv_notice(), if S::SUPPORTS_STREAMING_NOTICES => {
1277                sender.emit_streaming_notices(vec![notice]).await?;
1278            }
1279            e = sender.connection_error() => return Err(e),
1280            r = &mut f => return Ok(r),
1281        }
1282    }
1283}
1284
1285/// Streams a result with rows, such as that of a `SELECT`, to a WebSocket
1286/// client one batch at a time, flushing between batches so a slow client
1287/// applies real backpressure and only one batch is resident. This gives a large
1288/// `SELECT` over WebSocket the same bounded memory profile as pgwire `SELECT`,
1289/// and mirrors the `Subscribe` arm of `WebSocket::add_result`.
1290///
1291/// On success returns the `(is_err, msgs, stmt_logging)` triple that
1292/// `add_result` folds into its response. An `Err` means a write to the socket
1293/// failed, so the server should disconnect. `add_result` turns that into a
1294/// cancellation to match pgwire.
1295///
1296/// The `Rows` descriptor is sent lazily, right before the first batch of rows
1297/// or an empty successful result, but never before an error. So a query that
1298/// fails before producing any rows emits only an `Error`.
1299async fn stream_ws_peek_rows(
1300    ws: &mut WebSocket,
1301    client: &mut SessionClient,
1302    desc: &RelationDesc,
1303    rows_stream: &mut RecordFirstRowStream,
1304    max_result_size: usize,
1305    ends_group: bool,
1306) -> Result<
1307    (
1308        bool,
1309        Vec<WebSocketResponse>,
1310        Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
1311    ),
1312    Error,
1313> {
1314    let mut datum_vec = mz_repr::DatumVec::new();
1315    let mut result_size: usize = 0;
1316    let mut rows_returned: usize = 0;
1317    let mut sent_rows_desc = false;
1318    loop {
1319        // Bind before matching so the `Option<PeekResponseUnary>` (which has a
1320        // significant `Drop`) is not a temporary living for the whole match.
1321        let res = await_rows(ws, client, rows_stream.recv()).await?;
1322        match res {
1323            Some(PeekResponseUnary::Rows(mut rows)) => {
1324                if let Err(err) = verify_datum_desc(desc, &mut rows) {
1325                    return Ok(ws_peek_result(
1326                        client,
1327                        true,
1328                        vec![WebSocketResponse::Error(err.into())],
1329                    ));
1330                }
1331                // The header waits until a batch has passed `verify_datum_desc`,
1332                // so a query that fails before producing any rows emits only an
1333                // `Error`. Sending it before the loop, as the `Subscribe` arm
1334                // does, would put a `Rows` header in front of that `Error`.
1335                if !sent_rows_desc {
1336                    send_ws_response(ws, WebSocketResponse::Rows(desc.into())).await?;
1337                    sent_rows_desc = true;
1338                }
1339                let types = &desc.typ().column_types;
1340                while let Some(row) = rows.next() {
1341                    result_size = result_size.saturating_add(row.byte_len());
1342                    if result_size > max_result_size {
1343                        use bytesize::ByteSize;
1344                        return Ok(ws_peek_result(
1345                            client,
1346                            true,
1347                            vec![WebSocketResponse::Error(
1348                                AdapterError::ResultSize(format!(
1349                                    "result exceeds max size of {}",
1350                                    ByteSize::b(u64::cast_from(max_result_size))
1351                                ))
1352                                .into(),
1353                            )],
1354                        ));
1355                    }
1356                    let datums = datum_vec.borrow_with(row);
1357                    send_ws_response(
1358                        ws,
1359                        WebSocketResponse::Row(
1360                            datums
1361                                .iter()
1362                                .enumerate()
1363                                .map(|(i, d)| {
1364                                    TypedDatum::new(*d, &types[i])
1365                                        .json(&JsonNumberPolicy::ConvertNumberToString)
1366                                })
1367                                .collect(),
1368                        ),
1369                    )
1370                    .await?;
1371                    rows_returned += 1;
1372                }
1373            }
1374            Some(PeekResponseUnary::Error(error)) => {
1375                return Ok(ws_peek_result(
1376                    client,
1377                    true,
1378                    vec![WebSocketResponse::Error(error.into())],
1379                ));
1380            }
1381            Some(PeekResponseUnary::DependencyDropped(dep)) => {
1382                return Ok(ws_peek_result(
1383                    client,
1384                    true,
1385                    vec![WebSocketResponse::Error(
1386                        dep.to_concurrent_dependency_drop().into(),
1387                    )],
1388                ));
1389            }
1390            Some(PeekResponseUnary::Canceled) => {
1391                return Ok(ws_peek_result(
1392                    client,
1393                    true,
1394                    vec![WebSocketResponse::Error(AdapterError::Canceled.into())],
1395                ));
1396            }
1397            None => {
1398                let execution_time = rows_stream
1399                    .take_execution_time()
1400                    .map(|time| client.session().maybe_mark_staged(time));
1401                let settled = match client.settle_statement(ends_group).await {
1402                    Ok(settled) => settled,
1403                    Err(err) => {
1404                        return Ok(ws_peek_result(
1405                            client,
1406                            true,
1407                            vec![WebSocketResponse::Error(err.into())],
1408                        ));
1409                    }
1410                };
1411                // An empty successful result still owes the client a `Rows`
1412                // descriptor before `CommandComplete`.
1413                if !sent_rows_desc {
1414                    send_ws_response(ws, WebSocketResponse::Rows(desc.into())).await?;
1415                }
1416                let command_complete =
1417                    WebSocketResponse::CommandComplete(format!("SELECT {rows_returned}"));
1418                let msgs = execution_time
1419                    .and_then(|time| client.session().execution_time_notice(time))
1420                    .map(|notice| WebSocketResponse::Notice(Notice::from(notice)))
1421                    .into_iter()
1422                    .chain([command_complete])
1423                    .collect();
1424                let (is_err, mut msgs, stmt_logging) = ws_peek_result(client, false, msgs);
1425                let reverted = notify_params(client, settled.reverted);
1426                msgs.extend(reverted.into_iter().map(WebSocketResponse::ParameterStatus));
1427                return Ok((is_err, msgs, stmt_logging));
1428            }
1429        }
1430    }
1431}
1432
1433/// Packages a terminal result of `stream_ws_peek_rows`, appending any notices
1434/// still buffered on the session after `msgs`.
1435///
1436/// The streaming loop forwards notices live through `await_rows`' `recv_notice`
1437/// select, but the terminal `recv() -> None` can race a still-buffered notice
1438/// and end the loop first. Draining here restores the buffered path's guarantee
1439/// that all notices reach the client, after `CommandComplete` or `Error`.
1440fn ws_peek_result(
1441    client: &mut SessionClient,
1442    is_err: bool,
1443    mut msgs: Vec<WebSocketResponse>,
1444) -> (
1445    bool,
1446    Vec<WebSocketResponse>,
1447    Option<(StatementEndedExecutionReason, ExecuteContextGuard)>,
1448) {
1449    msgs.extend(
1450        make_notices(client)
1451            .into_iter()
1452            .map(WebSocketResponse::Notice),
1453    );
1454    (is_err, msgs, None)
1455}
1456
1457async fn send_and_retire<S: ResultSender>(
1458    res: StatementResult,
1459    client: &mut SessionClient,
1460    sender: &mut S,
1461) -> Result<Result<(), ()>, Error> {
1462    let (res, stmt_logging) = sender.add_result(client, res).await;
1463    if let Some((reason, ctx_extra)) = stmt_logging {
1464        client.retire_execute(ctx_extra, reason);
1465    }
1466    res
1467}
1468
1469/// Returns Ok(Err) if any statement error'd during execution.
1470async fn execute_stmt_group<S: ResultSender>(
1471    client: &mut SessionClient,
1472    sender: &mut S,
1473    stmt_group: Vec<(Statement<Raw>, String, Vec<Option<String>>)>,
1474) -> Result<Result<(), ()>, Error> {
1475    let num_stmts = stmt_group.len();
1476    for (idx, (stmt, sql, params)) in stmt_group.into_iter().enumerate() {
1477        assert!(
1478            num_stmts <= 1 || params.is_empty(),
1479            "statement groups contain more than 1 statement iff Simple request, which does not support parameters"
1480        );
1481
1482        let is_aborted_txn = matches!(client.session().transaction(), TransactionStatus::Failed(_));
1483        if is_aborted_txn && !is_txn_exit_stmt(&stmt) {
1484            let err = SqlResult::err(client, Error::AbortedTransaction);
1485            let _ = send_and_retire(err.into(), client, sender).await?;
1486            return Ok(Err(()));
1487        }
1488
1489        // Mirror the behavior of the PostgreSQL simple query protocol.
1490        // See the pgwire::protocol::StateMachine::query method for details.
1491        if let Err(e) = client.start_transaction(Some(num_stmts)) {
1492            let err = SqlResult::err(client, e);
1493            let _ = send_and_retire(err.into(), client, sender).await?;
1494            return Ok(Err(()));
1495        }
1496        let ends_group = idx + 1 == num_stmts;
1497        let res = execute_stmt(client, sender, stmt, sql, params, ends_group).await?;
1498        let is_err = send_and_retire(res, client, sender).await?;
1499
1500        if is_err.is_err() {
1501            // Mirror StateMachine::error, which sometimes will clean up the
1502            // transaction state instead of always leaving it in Failed.
1503            let txn = client.session().transaction();
1504            match txn {
1505                // Error can be called from describe and parse and so might not be in an active
1506                // transaction.
1507                TransactionStatus::Default | TransactionStatus::Failed(_) => {}
1508                // In Started (i.e., a single statement) and implicit transactions cleanup themselves.
1509                TransactionStatus::Started(_) | TransactionStatus::InTransactionImplicit(_) => {
1510                    if let Err(err) = client.end_transaction(EndTransactionAction::Rollback).await {
1511                        let err = SqlResult::err(client, err);
1512                        let _ = send_and_retire(err.into(), client, sender).await?;
1513                    }
1514                }
1515                // Explicit transactions move to failed.
1516                TransactionStatus::InTransaction(_) => {
1517                    client.fail_transaction();
1518                }
1519            }
1520            return Ok(Err(()));
1521        }
1522    }
1523    Ok(Ok(()))
1524}
1525
1526/// Executes an entire [`SqlRequest`].
1527///
1528/// See the user-facing documentation about the HTTP API for a description of
1529/// the semantics of this function.
1530/// Executes a SQL request and sends results to the provided sender.
1531///
1532/// Made visible to http submodules (like mcp) via `pub(in crate::http)` to allow
1533/// reuse of SQL execution logic.
1534pub(in crate::http) async fn execute_request<S: ResultSender>(
1535    client: &mut AuthedClient,
1536    request: SqlRequest,
1537    sender: &mut S,
1538) -> Result<(), Error> {
1539    let client = &mut client.client;
1540
1541    if client.statement_arrival_logging_enabled().await {
1542        let session = client.session();
1543        let conn_id = session.conn_id();
1544        let session_uuid = session.uuid();
1545        match &request {
1546            SqlRequest::Simple { query } => {
1547                info!(
1548                    %conn_id, %session_uuid, kind = "http_simple",
1549                    sql = %redact_sql_for_logging(query.as_str()),
1550                    "statement arrival"
1551                );
1552            }
1553            SqlRequest::Extended { queries } => {
1554                for ExtendedRequest { query, params } in queries {
1555                    // Parameter values are data that redaction cannot reach,
1556                    // so only their count is logged.
1557                    info!(
1558                        %conn_id, %session_uuid, kind = "http_extended",
1559                        sql = %redact_sql_for_logging(query), num_params = params.len(),
1560                        "statement arrival"
1561                    );
1562                }
1563            }
1564        }
1565    }
1566
1567    // This API prohibits executing statements with responses whose
1568    // semantics are at odds with an HTTP response.
1569    fn check_prohibited_stmts<S: ResultSender>(
1570        sender: &S,
1571        stmt: &Statement<Raw>,
1572    ) -> Result<(), Error> {
1573        let kind: StatementKind = stmt.into();
1574        let execute_responses = Plan::generated_from(&kind)
1575            .into_iter()
1576            .map(ExecuteResponse::generated_from)
1577            .flatten()
1578            .collect::<Vec<_>>();
1579
1580        // Special-case `COPY TO` statements that are not `COPY ... TO STDOUT`, since
1581        // StatementKind::Copy links to several `ExecuteResponseKind`s that are not supported,
1582        // but this specific statement should be allowed.
1583        let is_valid_copy = matches!(
1584            stmt,
1585            Statement::Copy(CopyStatement {
1586                direction: CopyDirection::To,
1587                target: CopyTarget::Expr(_),
1588                ..
1589            }) | Statement::Copy(CopyStatement {
1590                direction: CopyDirection::From,
1591                target: CopyTarget::Expr(_),
1592                ..
1593            })
1594        );
1595
1596        if !is_valid_copy
1597            && execute_responses.iter().any(|execute_response| {
1598                // Returns true if a statement or execute response are unsupported.
1599                match execute_response {
1600                    ExecuteResponseKind::Subscribing if sender.allow_subscribe() => false,
1601                    ExecuteResponseKind::Fetch
1602                    | ExecuteResponseKind::Subscribing
1603                    | ExecuteResponseKind::CopyFrom
1604                    | ExecuteResponseKind::DeclaredCursor
1605                    | ExecuteResponseKind::ClosedCursor => true,
1606                    // Various statements generate `PeekPlan` (`SELECT`, `COPY`,
1607                    // `EXPLAIN`, `SHOW`) which has both `SendRows` and `CopyTo` as its
1608                    // possible response types. but `COPY` needs be picked out because
1609                    // http don't support its response type
1610                    ExecuteResponseKind::CopyTo if matches!(kind, StatementKind::Copy) => true,
1611                    _ => false,
1612                }
1613            })
1614        {
1615            return Err(Error::Unsupported(stmt.to_ast_string_simple()));
1616        }
1617        Ok(())
1618    }
1619
1620    fn parse<'a>(
1621        client: &SessionClient,
1622        query: &'a str,
1623    ) -> Result<Vec<StatementParseResult<'a>>, Error> {
1624        let result = client
1625            .parse(query)
1626            .map_err(|e| Error::Unstructured(anyhow!(e)))?;
1627        result.map_err(|e| AdapterError::from(e).into())
1628    }
1629
1630    let mut stmt_groups = vec![];
1631
1632    match request {
1633        SqlRequest::Simple { query } => match parse(client, query.as_str()) {
1634            Ok(stmts) => {
1635                let mut stmt_group = Vec::with_capacity(stmts.len());
1636                let mut stmt_err = None;
1637                for StatementParseResult { ast: stmt, sql } in stmts {
1638                    if let Err(err) = check_prohibited_stmts(sender, &stmt) {
1639                        stmt_err = Some(err);
1640                        break;
1641                    }
1642                    stmt_group.push((stmt, sql.to_string(), vec![]));
1643                }
1644                stmt_groups.push(stmt_err.map(Err).unwrap_or_else(|| Ok(stmt_group)));
1645            }
1646            Err(e) => stmt_groups.push(Err(e)),
1647        },
1648        SqlRequest::Extended { queries } => {
1649            for ExtendedRequest { query, params } in queries {
1650                match parse(client, &query) {
1651                    Ok(mut stmts) => {
1652                        if stmts.len() != 1 {
1653                            return Err(Error::Unstructured(anyhow!(
1654                                "each query must contain exactly 1 statement, but \"{}\" contains {}",
1655                                query,
1656                                stmts.len()
1657                            )));
1658                        }
1659
1660                        let StatementParseResult { ast: stmt, sql } = stmts.pop().unwrap();
1661                        stmt_groups.push(
1662                            check_prohibited_stmts(sender, &stmt)
1663                                .map(|_| vec![(stmt, sql.to_string(), params)]),
1664                        );
1665                    }
1666                    Err(e) => stmt_groups.push(Err(e)),
1667                };
1668            }
1669        }
1670    }
1671
1672    for stmt_group_res in stmt_groups {
1673        let executed = match stmt_group_res {
1674            Ok(stmt_group) => execute_stmt_group(client, sender, stmt_group).await,
1675            Err(e) => {
1676                let err = SqlResult::err(client, e);
1677                let _ = send_and_retire(err.into(), client, sender).await?;
1678                Ok(Err(()))
1679            }
1680        };
1681        // At the end of each group, commit implicit transactions. Do that here so that any `?`
1682        // early return can still be handled here.
1683        let mut commit_failed = false;
1684        if client.session().transaction().is_implicit() {
1685            let ended = client.end_transaction(EndTransactionAction::Commit).await;
1686            if let Err(err) = ended {
1687                let err = SqlResult::err(client, err);
1688                let _ = send_and_retire(StatementResult::SqlResult(err), client, sender).await?;
1689                commit_failed = true;
1690            }
1691        }
1692        if executed?.is_err() || commit_failed {
1693            break;
1694        }
1695    }
1696
1697    Ok(())
1698}
1699
1700/// Executes a single statement in a [`SqlRequest`].
1701async fn execute_stmt<S: ResultSender>(
1702    client: &mut SessionClient,
1703    sender: &mut S,
1704    stmt: Statement<Raw>,
1705    sql: String,
1706    raw_params: Vec<Option<String>>,
1707    ends_group: bool,
1708) -> Result<StatementResult, Error> {
1709    const EMPTY_PORTAL: &str = "";
1710    if let Err(e) = client
1711        .prepare(EMPTY_PORTAL.into(), Some(stmt.clone()), sql, vec![])
1712        .await
1713    {
1714        return Ok(SqlResult::err(client, e).into());
1715    }
1716
1717    let prep_stmt = match client.get_prepared_statement(EMPTY_PORTAL).await {
1718        Ok(stmt) => stmt,
1719        Err(err) => {
1720            return Ok(SqlResult::err(client, err).into());
1721        }
1722    };
1723
1724    let param_types = &prep_stmt.desc().param_types;
1725    if param_types.len() != raw_params.len() {
1726        let message = anyhow!(
1727            "request supplied {actual} parameters, \
1728                        but {statement} requires {expected}",
1729            statement = stmt.to_ast_string_simple(),
1730            actual = raw_params.len(),
1731            expected = param_types.len()
1732        );
1733        return Ok(SqlResult::err(client, Error::Unstructured(message)).into());
1734    }
1735
1736    let buf = RowArena::new();
1737    let mut params = vec![];
1738    for (raw_param, mz_typ) in raw_params.into_iter().zip_eq(param_types) {
1739        let pg_typ = mz_pgrepr::Type::from(mz_typ);
1740        let datum = match raw_param {
1741            None => Datum::Null,
1742            Some(raw_param) => {
1743                match mz_pgrepr::Value::decode(
1744                    mz_pgwire_common::Format::Text,
1745                    &pg_typ,
1746                    raw_param.as_bytes(),
1747                ) {
1748                    Ok(param) => match param.into_datum_decode_error(&buf, &pg_typ, "parameter") {
1749                        Ok(datum) => datum,
1750                        Err(msg) => {
1751                            return Ok(
1752                                SqlResult::err(client, Error::Unstructured(anyhow!(msg))).into()
1753                            );
1754                        }
1755                    },
1756                    Err(err) => {
1757                        let msg = anyhow!("unable to decode parameter: {}", err);
1758                        return Ok(SqlResult::err(client, Error::Unstructured(msg)).into());
1759                    }
1760                }
1761            }
1762        };
1763        params.push((datum, mz_typ.clone()))
1764    }
1765
1766    let result_formats = vec![
1767        mz_pgwire_common::Format::Text;
1768        prep_stmt
1769            .desc()
1770            .relation_desc
1771            .clone()
1772            .map(|desc| desc.typ().column_types.len())
1773            .unwrap_or(0)
1774    ];
1775
1776    let desc = prep_stmt.desc().clone();
1777    let logging = Arc::clone(prep_stmt.logging());
1778    let stmt_ast = prep_stmt.stmt().cloned();
1779    let state_revision = prep_stmt.state_revision;
1780    if let Err(err) = client.session().set_portal(
1781        EMPTY_PORTAL.into(),
1782        desc,
1783        stmt_ast,
1784        logging,
1785        params,
1786        result_formats,
1787        state_revision,
1788    ) {
1789        return Ok(SqlResult::err(client, err).into());
1790    }
1791
1792    let desc = client
1793        .session()
1794        // We do not need to verify here because `client.execute` verifies below.
1795        .get_portal_unverified(EMPTY_PORTAL)
1796        .map(|portal| portal.desc.clone())
1797        .expect("unnamed portal should be present");
1798
1799    let res = client
1800        .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
1801        .await;
1802    // The execution time of a statement whose response carries no rows is taken here, before
1803    // notices are sent.
1804    let executed = Instant::now();
1805
1806    if S::SUPPORTS_STREAMING_NOTICES {
1807        sender
1808            .emit_streaming_notices(client.session().drain_notices())
1809            .await?;
1810    }
1811
1812    let (res, execute_started) = match res {
1813        Ok(res) => res,
1814        Err(e) => {
1815            return Ok(SqlResult::err(client, e).into());
1816        }
1817    };
1818    let execution_time = client.session().maybe_mark_staged(ExecutionTime::completed(
1819        executed.duration_since(execute_started),
1820    ));
1821    let tag = res.tag();
1822
1823    Ok(match res {
1824        ExecuteResponse::CreatedConnection { .. }
1825        | ExecuteResponse::CreatedDatabase { .. }
1826        | ExecuteResponse::CreatedSchema { .. }
1827        | ExecuteResponse::CreatedRole
1828        | ExecuteResponse::CreatedCluster { .. }
1829        | ExecuteResponse::CreatedClusterReplica { .. }
1830        | ExecuteResponse::CreatedTable { .. }
1831        | ExecuteResponse::CreatedIndex { .. }
1832        | ExecuteResponse::CreatedMetricSink { .. }
1833        | ExecuteResponse::CreatedIntrospectionSubscribe
1834        | ExecuteResponse::CreatedSecret { .. }
1835        | ExecuteResponse::CreatedSource { .. }
1836        | ExecuteResponse::CreatedSink { .. }
1837        | ExecuteResponse::CreatedView { .. }
1838        | ExecuteResponse::CreatedViews { .. }
1839        | ExecuteResponse::CreatedMaterializedView { .. }
1840        | ExecuteResponse::CreatedType
1841        | ExecuteResponse::CreatedNetworkPolicy
1842        | ExecuteResponse::Comment
1843        | ExecuteResponse::Deleted(_)
1844        | ExecuteResponse::DiscardedTemp
1845        | ExecuteResponse::DroppedObject(_)
1846        | ExecuteResponse::DroppedOwned
1847        | ExecuteResponse::EmptyQuery
1848        | ExecuteResponse::GrantedPrivilege
1849        | ExecuteResponse::GrantedRole
1850        | ExecuteResponse::Inserted(_)
1851        | ExecuteResponse::Copied(_)
1852        | ExecuteResponse::Raised
1853        | ExecuteResponse::ReassignOwned
1854        | ExecuteResponse::RevokedPrivilege
1855        | ExecuteResponse::AlteredDefaultPrivileges
1856        | ExecuteResponse::RevokedRole
1857        | ExecuteResponse::StartedTransaction { .. }
1858        | ExecuteResponse::Updated(_)
1859        | ExecuteResponse::AlteredObject(_)
1860        | ExecuteResponse::AlteredRole
1861        | ExecuteResponse::AlteredSystemConfiguration
1862        | ExecuteResponse::Deallocate { .. }
1863        | ExecuteResponse::ValidatedConnection
1864        | ExecuteResponse::Prepare => SqlResult::ok(
1865            client,
1866            tag.expect("ok only called on tag-generating results"),
1867            Vec::default(),
1868            ends_group,
1869            execution_time,
1870        )
1871        .await
1872        .into(),
1873        ExecuteResponse::TransactionCommitted { params }
1874        | ExecuteResponse::TransactionRolledBack { params }
1875        | ExecuteResponse::DiscardedAll { params } => {
1876            let params = notify_params(client, params);
1877            SqlResult::ok(
1878                client,
1879                tag.expect("ok only called on tag-generating results"),
1880                params,
1881                ends_group,
1882                execution_time,
1883            )
1884            .await
1885            .into()
1886        }
1887        ExecuteResponse::SetVariable { name, .. } => {
1888            let mut params = Vec::with_capacity(1);
1889            if let Some(var) = client
1890                .session()
1891                .vars()
1892                .notify_set()
1893                .find(|v| v.name() == &name)
1894            {
1895                params.push(ParameterStatus {
1896                    name,
1897                    value: var.value(),
1898                });
1899            };
1900            SqlResult::ok(
1901                client,
1902                tag.expect("ok only called on tag-generating results"),
1903                params,
1904                ends_group,
1905                execution_time,
1906            )
1907            .await
1908            .into()
1909        }
1910        ExecuteResponse::SendingRowsStreaming {
1911            rows,
1912            instance_id,
1913            strategy,
1914        } => {
1915            let max_result_size = max_result_size(client).await;
1916
1917            let rows_stream = RecordFirstRowStream::new(
1918                Box::new(rows),
1919                execute_started,
1920                client,
1921                Some(instance_id),
1922                Some(strategy),
1923            );
1924
1925            StatementResult::Rows {
1926                desc: desc.relation_desc.expect("RelationDesc must exist"),
1927                rows_stream,
1928                max_result_size,
1929                ends_group,
1930            }
1931        }
1932        ExecuteResponse::SendingRowsImmediate { rows } => {
1933            let max_result_size = max_result_size(client).await;
1934
1935            let rows = futures::stream::once(futures::future::ready(PeekResponseUnary::Rows(rows)));
1936            let rows_stream = RecordFirstRowStream::new(
1937                Box::new(rows),
1938                execute_started,
1939                client,
1940                None,
1941                Some(StatementExecutionStrategy::Constant),
1942            );
1943
1944            StatementResult::Rows {
1945                desc: desc.relation_desc.expect("RelationDesc must exist"),
1946                rows_stream,
1947                max_result_size,
1948                ends_group,
1949            }
1950        }
1951        ExecuteResponse::Subscribing {
1952            rx,
1953            ctx_extra,
1954            instance_id,
1955        } => StatementResult::Subscribe {
1956            tag: "SUBSCRIBE".into(),
1957            desc: desc.relation_desc.unwrap(),
1958            rx: RecordFirstRowStream::new(rx, execute_started, client, Some(instance_id), None)
1959                .without_execution_time(),
1960            ctx_extra,
1961        },
1962        res @ (ExecuteResponse::Fetch { .. }
1963        | ExecuteResponse::CopyTo { .. }
1964        | ExecuteResponse::CopyFrom { .. }
1965        | ExecuteResponse::DeclaredCursor
1966        | ExecuteResponse::ClosedCursor) => SqlResult::err(
1967            client,
1968            Error::Unstructured(anyhow!(
1969                "internal error: encountered prohibited ExecuteResponse {:?}.\n\n
1970            This is a bug. Can you please file an bug report letting us know?\n
1971            https://github.com/MaterializeInc/materialize/discussions/new?category=bug-reports",
1972                ExecuteResponseKind::from(res)
1973            )),
1974        )
1975        .into(),
1976    })
1977}
1978
1979/// The current `max_result_size` system variable.
1980///
1981/// Reads the session's cached catalog, which usually needs no coordinator round trip.
1982async fn max_result_size(client: &mut SessionClient) -> usize {
1983    let catalog = client.catalog_snapshot("http_max_result_size").await;
1984    usize::cast_from(catalog.system_config().max_result_size())
1985}
1986
1987/// The session parameters among `params` that clients are notified about.
1988fn notify_params(
1989    client: &mut SessionClient,
1990    params: BTreeMap<&'static str, String>,
1991) -> Vec<ParameterStatus> {
1992    let notify_set: mz_ore::collections::HashSet<_> = client
1993        .session()
1994        .vars()
1995        .notify_set()
1996        .map(|v| v.name().to_string())
1997        .collect();
1998    params
1999        .into_iter()
2000        .filter(|(name, _value)| notify_set.contains(*name))
2001        .map(|(name, value)| ParameterStatus {
2002            name: name.to_string(),
2003            value,
2004        })
2005        .collect()
2006}
2007
2008fn make_notices(client: &mut SessionClient) -> Vec<Notice> {
2009    client
2010        .session()
2011        .drain_notices()
2012        .into_iter()
2013        .map(Notice::from)
2014        .collect()
2015}
2016
2017impl From<AdapterNotice> for Notice {
2018    fn from(notice: AdapterNotice) -> Self {
2019        Notice {
2020            message: notice.to_string(),
2021            code: notice.code().code().to_string(),
2022            severity: notice.severity().as_str().to_lowercase(),
2023            detail: notice.detail(),
2024            hint: notice.hint(),
2025        }
2026    }
2027}
2028
2029// Duplicated from protocol.rs.
2030// See postgres' backend/tcop/postgres.c IsTransactionExitStmt.
2031fn is_txn_exit_stmt(stmt: &Statement<Raw>) -> bool {
2032    matches!(
2033        stmt,
2034        Statement::Commit(_) | Statement::Rollback(_) | Statement::Prepare(_)
2035    )
2036}
2037
2038#[cfg(test)]
2039mod tests {
2040    use std::collections::BTreeMap;
2041
2042    use super::{Password, WebSocketAuth};
2043
2044    #[mz_ore::test]
2045    fn smoke_test_websocket_auth_parse() {
2046        struct TestCase {
2047            json: &'static str,
2048            expected: WebSocketAuth,
2049        }
2050
2051        let test_cases = vec![
2052            TestCase {
2053                json: r#"{ "user": "mz", "password": "1234" }"#,
2054                expected: WebSocketAuth::Basic {
2055                    user: "mz".to_string(),
2056                    password: Password("1234".to_string()),
2057                    options: BTreeMap::default(),
2058                },
2059            },
2060            TestCase {
2061                json: r#"{ "user": "mz", "password": "1234", "options": {} }"#,
2062                expected: WebSocketAuth::Basic {
2063                    user: "mz".to_string(),
2064                    password: Password("1234".to_string()),
2065                    options: BTreeMap::default(),
2066                },
2067            },
2068            TestCase {
2069                json: r#"{ "token": "i_am_a_token" }"#,
2070                expected: WebSocketAuth::Bearer {
2071                    token: "i_am_a_token".to_string(),
2072                    options: BTreeMap::default(),
2073                },
2074            },
2075            TestCase {
2076                json: r#"{ "token": "i_am_a_token", "options": { "foo": "bar" } }"#,
2077                expected: WebSocketAuth::Bearer {
2078                    token: "i_am_a_token".to_string(),
2079                    options: BTreeMap::from([("foo".to_string(), "bar".to_string())]),
2080                },
2081            },
2082        ];
2083
2084        fn assert_parse(json: &'static str, expected: WebSocketAuth) {
2085            let parsed: WebSocketAuth = serde_json::from_str(json).unwrap();
2086            assert_eq!(parsed, expected);
2087        }
2088
2089        for TestCase { json, expected } in test_cases {
2090            assert_parse(json, expected)
2091        }
2092    }
2093}