Skip to main content

mz_pgwire/
protocol.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::convert::TryFrom;
12use std::future::Future;
13use std::ops::Deref;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16use std::{iter, mem};
17
18use base64::prelude::*;
19use byteorder::{ByteOrder, NetworkEndian};
20use csv_core::ReadRecordResult;
21use futures::future::{BoxFuture, FutureExt, pending};
22use itertools::Itertools;
23use mz_adapter::client::{RecordFirstRowStream, redact_sql_for_logging};
24use mz_adapter::session::{
25    EndTransactionAction, InProgressRows, LifecycleTimestamps, PortalRefMut, PortalState, Session,
26    SessionConfig, TransactionStatus,
27};
28use mz_adapter::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
29use mz_adapter::{
30    AdapterError, AdapterNotice, ExecuteContextGuard, ExecuteResponse, PeekResponseUnary, metrics,
31    verify_datum_desc,
32};
33use mz_adapter_types::dyncfgs::OIDC_GROUP_CLAIM;
34use mz_auth::Authenticated;
35use mz_auth::password::Password;
36use mz_authenticator::{Authenticator, GenericOidcAuthenticator};
37use mz_frontegg_auth::Authenticator as FronteggAuthenticator;
38use mz_ore::cast::CastFrom;
39use mz_ore::netio::AsyncReady;
40use mz_ore::now::{EpochMillis, SYSTEM_TIME};
41use mz_ore::str::StrExt;
42use mz_ore::{assert_none, assert_ok, instrument, soft_assert_eq_or_log, soft_assert_or_log};
43use mz_pgcopy::{CopyCsvFormatParams, CopyFormatParams, CopyTextFormatParams};
44use mz_pgwire_common::{
45    ConnectionCounter, Cursor, ErrorResponse, Format, FrontendMessage, Severity, VERSION_3,
46    VERSIONS,
47};
48use mz_repr::{
49    CatalogItemId, ColumnIndex, Datum, RelationDesc, RowArena, RowIterator, RowRef,
50    SqlRelationType, SqlScalarType,
51};
52use mz_server_core::TlsMode;
53use mz_server_core::listeners;
54use mz_server_core::listeners::AllowedRoles;
55use mz_sql::ast::display::AstDisplay;
56use mz_sql::ast::{
57    CopyDirection, CopyStatement, CopyTarget, FetchDirection, Ident, Raw, Statement,
58};
59use mz_sql::parse::StatementParseResult;
60use mz_sql::plan::{CopyFormat, ExecuteTimeout, StatementDesc};
61use mz_sql::session::metadata::SessionMetadata;
62use mz_sql::session::user::INTERNAL_USER_NAMES;
63use mz_sql::session::vars::VarInput;
64use postgres::error::SqlState;
65use tokio::io::{self, AsyncRead, AsyncWrite};
66use tokio::select;
67use tokio::time::{self};
68use tokio_metrics::TaskMetrics;
69use tokio_stream::wrappers::UnboundedReceiverStream;
70use tracing::{Instrument, debug, debug_span, info, warn};
71use uuid::Uuid;
72
73use crate::codec::{
74    FramedConn, decode_password, decode_sasl_initial_response, decode_sasl_response,
75};
76use crate::message::{
77    self, BackendMessage, SASLServerFinalMessage, SASLServerFinalMessageKinds,
78    SASLServerFirstMessage,
79};
80
81/// Reports whether the given stream begins with a pgwire handshake.
82///
83/// To avoid false negatives, there must be at least eight bytes in `buf`.
84pub fn match_handshake(buf: &[u8]) -> bool {
85    // The pgwire StartupMessage looks like this:
86    //
87    //     i32 - Length of entire message.
88    //     i32 - Protocol version number.
89    //     [String] - Arbitrary key-value parameters of any length.
90    //
91    // Since arbitrary parameters can be included in the StartupMessage, the
92    // first Int32 is worthless, since the message could have any length.
93    // Instead, we sniff the protocol version number.
94    if buf.len() < 8 {
95        return false;
96    }
97    let version = NetworkEndian::read_i32(&buf[4..8]);
98    VERSIONS.contains(&version)
99}
100
101/// Parameters for the [`run`] function.
102pub struct RunParams<'a, A, I>
103where
104    I: Iterator<Item = TaskMetrics> + Send,
105{
106    /// The TLS mode of the pgwire server.
107    pub tls_mode: Option<TlsMode>,
108    /// A client for the adapter.
109    pub adapter_client: mz_adapter::Client,
110    /// The connection to the client.
111    pub conn: &'a mut FramedConn<A>,
112    /// The universally unique identifier for the connection.
113    pub conn_uuid: Uuid,
114    /// The protocol version that the client provided in the startup message.
115    pub version: i32,
116    /// The parameters that the client provided in the startup message.
117    pub params: BTreeMap<String, String>,
118    /// Frontegg JWT authenticator.
119    pub frontegg: Option<FronteggAuthenticator>,
120    /// OIDC authenticator.
121    pub oidc: GenericOidcAuthenticator,
122    /// The authentication method defined by the server's listener
123    /// configuration.
124    pub authenticator_kind: listeners::AuthenticatorKind,
125    /// Global connection limit and count
126    pub active_connection_counter: ConnectionCounter,
127    /// Helm chart version
128    pub helm_chart_version: Option<String>,
129    /// Whether to allow reserved users (ie: mz_system).
130    pub allowed_roles: AllowedRoles,
131    /// Tokio metrics
132    pub tokio_metrics_intervals: I,
133}
134
135/// Runs a pgwire connection to completion.
136///
137/// This involves responding to `FrontendMessage::StartupMessage` and all future
138/// requests until the client terminates the connection or a fatal error occurs.
139///
140/// Note that this function returns successfully even upon delivering a fatal
141/// error to the client. It only returns `Err` if an unexpected I/O error occurs
142/// while communicating with the client, e.g., if the connection is severed in
143/// the middle of a request.
144#[mz_ore::instrument(level = "debug")]
145pub async fn run<'a, A, I>(
146    RunParams {
147        tls_mode,
148        adapter_client,
149        conn,
150        conn_uuid,
151        version,
152        mut params,
153        frontegg,
154        oidc,
155        authenticator_kind,
156        active_connection_counter,
157        helm_chart_version,
158        allowed_roles,
159        tokio_metrics_intervals,
160    }: RunParams<'a, A, I>,
161) -> Result<(), io::Error>
162where
163    A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
164    I: Iterator<Item = TaskMetrics> + Send,
165{
166    if version != VERSION_3 {
167        return conn
168            .send(ErrorResponse::fatal(
169                SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
170                "server does not support the client's requested protocol version",
171            ))
172            .await;
173    }
174
175    let user = params.remove("user").unwrap_or_else(String::new);
176    let options = parse_options(params.get("options").unwrap_or(&String::new()));
177    let authenticator =
178        get_authenticator(authenticator_kind, frontegg, oidc, adapter_client.clone());
179    // TODO move this somewhere it can be shared with HTTP
180    let is_internal_user = INTERNAL_USER_NAMES.contains(&user);
181    // this is a superset of internal users
182    let is_reserved_user = mz_adapter::catalog::is_reserved_role_name(user.as_str());
183    let role_allowed = match allowed_roles {
184        AllowedRoles::Normal => !is_reserved_user,
185        AllowedRoles::Internal => is_internal_user,
186        AllowedRoles::NormalAndInternal => !is_reserved_user || is_internal_user,
187    };
188    if !role_allowed {
189        let msg = format!("unauthorized login to user '{user}'");
190        return conn
191            .send(ErrorResponse::fatal(SqlState::INSUFFICIENT_PRIVILEGE, msg))
192            .await;
193    }
194
195    if let Err(err) = conn.inner().ensure_tls_compatibility(&tls_mode) {
196        return conn.send(err).await;
197    }
198
199    let authenticator_kind = authenticator.kind();
200
201    let (mut session, expired) = match authenticator {
202        Authenticator::Frontegg(frontegg) => {
203            let password = match request_cleartext_password(conn).await {
204                Ok(password) => password,
205                Err(PasswordRequestError::IoError(e)) => return Err(e),
206                Err(PasswordRequestError::InvalidPasswordError(e)) => {
207                    return conn.send(e).await;
208                }
209            };
210
211            let group_claim =
212                OIDC_GROUP_CLAIM.get(adapter_client.get_system_vars().await.dyncfgs());
213            let auth_response = frontegg
214                .authenticate(&user, &password, Some(&group_claim))
215                .await;
216            match auth_response {
217                // Create a session based on the auth session.
218                //
219                // In particular, it's important that the username come from the
220                // auth session, as Frontegg may return an email address with
221                // different casing than the user supplied via the pgwire
222                // username fN
223                Ok((mut auth_session, authenticated)) => {
224                    let groups = auth_session.groups();
225                    let session = adapter_client.new_session(
226                        SessionConfig {
227                            conn_id: conn.conn_id().clone(),
228                            uuid: conn_uuid,
229                            user: auth_session.user().into(),
230                            client_ip: conn.peer_addr().clone(),
231                            external_metadata_rx: Some(auth_session.external_metadata_rx()),
232                            helm_chart_version,
233                            authenticator_kind,
234                            groups,
235                        },
236                        authenticated,
237                    );
238                    let expired = async move { auth_session.expired().await };
239                    (session, expired.left_future())
240                }
241                Err(err) => {
242                    warn!(?err, "pgwire connection failed authentication");
243                    return conn
244                        .send(ErrorResponse::fatal(
245                            SqlState::INVALID_PASSWORD,
246                            "invalid password",
247                        ))
248                        .await;
249                }
250            }
251        }
252        Authenticator::Oidc(oidc) => {
253            // OIDC listener: accepts either a JWT (uses OIDC authentication) or a
254            // plain SQL password (uses SQL password authentication).
255            let password = match request_cleartext_password(conn).await {
256                Ok(password) => password,
257                Err(PasswordRequestError::IoError(e)) => return Err(e),
258                Err(PasswordRequestError::InvalidPasswordError(e)) => {
259                    return conn.send(e).await;
260                }
261            };
262            if is_jwt(&password) {
263                let auth_response = oidc.authenticate(&password, Some(&user)).await;
264                match auth_response {
265                    Ok((mut claims, authenticated)) => {
266                        let groups = claims.groups.take();
267                        let session = adapter_client.new_session(
268                            SessionConfig {
269                                conn_id: conn.conn_id().clone(),
270                                uuid: conn_uuid,
271                                user: std::mem::take(&mut claims.user),
272                                client_ip: conn.peer_addr().clone(),
273                                external_metadata_rx: None,
274                                helm_chart_version,
275                                authenticator_kind,
276                                groups,
277                            },
278                            authenticated,
279                        );
280                        // No invalidation of the auth session once authenticated,
281                        // so auth session lasts indefinitely.
282                        (session, pending().right_future())
283                    }
284                    Err(err) => {
285                        warn!(?err, "pgwire connection failed authentication");
286                        return conn.send(err.into_response()).await;
287                    }
288                }
289            } else {
290                let session = match authenticate_with_password(
291                    conn,
292                    &adapter_client,
293                    user,
294                    Password(password),
295                    conn_uuid,
296                    helm_chart_version,
297                )
298                .await
299                {
300                    Ok(session) => session,
301                    Err(PasswordRequestError::IoError(e)) => return Err(e),
302                    Err(PasswordRequestError::InvalidPasswordError(e)) => {
303                        return conn.send(e).await;
304                    }
305                };
306                (session, pending().right_future())
307            }
308        }
309        Authenticator::Password(adapter_client) => {
310            let password = match request_cleartext_password(conn).await {
311                Ok(password) => password,
312                Err(PasswordRequestError::IoError(e)) => return Err(e),
313                Err(PasswordRequestError::InvalidPasswordError(e)) => {
314                    return conn.send(e).await;
315                }
316            };
317            let session = match authenticate_with_password(
318                conn,
319                &adapter_client,
320                user,
321                Password(password),
322                conn_uuid,
323                helm_chart_version,
324            )
325            .await
326            {
327                Ok(session) => session,
328                Err(PasswordRequestError::IoError(e)) => return Err(e),
329                Err(PasswordRequestError::InvalidPasswordError(e)) => {
330                    return conn.send(e).await;
331                }
332            };
333            // No frontegg check, so auth session lasts indefinitely.
334            (session, pending().right_future())
335        }
336        Authenticator::Sasl(adapter_client) => {
337            // Start the handshake
338            conn.send(BackendMessage::AuthenticationSASL).await?;
339            conn.flush().await?;
340            // Get the initial response indicating chosen mechanism
341            let (mechanism, initial_response) = match conn.recv().await? {
342                Some(FrontendMessage::RawAuthentication(data)) => {
343                    match decode_sasl_initial_response(Cursor::new(&data)).ok() {
344                        Some(FrontendMessage::SASLInitialResponse {
345                            gs2_header,
346                            mechanism,
347                            initial_response,
348                        }) => {
349                            // We do not support channel binding
350                            if gs2_header.channel_binding_enabled() {
351                                return conn
352                                    .send(ErrorResponse::fatal(
353                                        SqlState::PROTOCOL_VIOLATION,
354                                        "channel binding not supported",
355                                    ))
356                                    .await;
357                            }
358                            (mechanism, initial_response)
359                        }
360                        _ => {
361                            return conn
362                                .send(ErrorResponse::fatal(
363                                    SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
364                                    "expected SASLInitialResponse message",
365                                ))
366                                .await;
367                        }
368                    }
369                }
370                _ => {
371                    return conn
372                        .send(ErrorResponse::fatal(
373                            SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
374                            "expected SASLInitialResponse message",
375                        ))
376                        .await;
377                }
378            };
379
380            if mechanism != "SCRAM-SHA-256" {
381                return conn
382                    .send(ErrorResponse::fatal(
383                        SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
384                        "unsupported SASL mechanism",
385                    ))
386                    .await;
387            }
388
389            if initial_response.nonce.len() > 256 {
390                return conn
391                    .send(ErrorResponse::fatal(
392                        SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
393                        "nonce too long",
394                    ))
395                    .await;
396            }
397
398            let (server_first_message_raw, mock_hash) = match adapter_client
399                .generate_sasl_challenge(&user, &initial_response.nonce)
400                .await
401            {
402                Ok(response) => {
403                    let server_first_message_raw = format!(
404                        "r={},s={},i={}",
405                        response.nonce, response.salt, response.iteration_count
406                    );
407
408                    let client_key = [0u8; 32];
409                    let server_key = [1u8; 32];
410                    let mock_hash = format!(
411                        "SCRAM-SHA-256${}:{}${}:{}",
412                        response.iteration_count,
413                        response.salt,
414                        BASE64_STANDARD.encode(client_key),
415                        BASE64_STANDARD.encode(server_key)
416                    );
417
418                    conn.send(BackendMessage::AuthenticationSASLContinue(
419                        SASLServerFirstMessage {
420                            iteration_count: response.iteration_count,
421                            nonce: response.nonce,
422                            salt: response.salt,
423                        },
424                    ))
425                    .await?;
426                    conn.flush().await?;
427                    (server_first_message_raw, mock_hash)
428                }
429                Err(e) => {
430                    return conn.send(e.into_response(Severity::Fatal)).await;
431                }
432            };
433
434            let authenticated = match conn.recv().await? {
435                Some(FrontendMessage::RawAuthentication(data)) => {
436                    match decode_sasl_response(Cursor::new(&data)).ok() {
437                        Some(FrontendMessage::SASLResponse(response)) => {
438                            let auth_message = format!(
439                                "{},{},{}",
440                                initial_response.client_first_message_bare_raw,
441                                server_first_message_raw,
442                                response.client_final_message_bare_raw
443                            );
444                            if response.proof.len() > 1024 {
445                                return conn
446                                    .send(ErrorResponse::fatal(
447                                        SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
448                                        "proof too long",
449                                    ))
450                                    .await;
451                            }
452                            match adapter_client
453                                .verify_sasl_proof(
454                                    &user,
455                                    &response.proof,
456                                    &auth_message,
457                                    &mock_hash,
458                                )
459                                .await
460                            {
461                                Ok((proof_response, authenticated)) => {
462                                    conn.send(BackendMessage::AuthenticationSASLFinal(
463                                        SASLServerFinalMessage {
464                                            kind: SASLServerFinalMessageKinds::Verifier(
465                                                proof_response.verifier,
466                                            ),
467                                            extensions: vec![],
468                                        },
469                                    ))
470                                    .await?;
471                                    conn.flush().await?;
472                                    authenticated
473                                }
474                                Err(_) => {
475                                    return conn
476                                        .send(ErrorResponse::fatal(
477                                            SqlState::INVALID_PASSWORD,
478                                            "invalid password",
479                                        ))
480                                        .await;
481                                }
482                            }
483                        }
484                        _ => {
485                            return conn
486                                .send(ErrorResponse::fatal(
487                                    SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
488                                    "expected SASLResponse message",
489                                ))
490                                .await;
491                        }
492                    }
493                }
494                _ => {
495                    return conn
496                        .send(ErrorResponse::fatal(
497                            SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
498                            "expected SASLResponse message",
499                        ))
500                        .await;
501                }
502            };
503
504            let session = adapter_client.new_session(
505                SessionConfig {
506                    conn_id: conn.conn_id().clone(),
507                    uuid: conn_uuid,
508                    user,
509                    client_ip: conn.peer_addr().clone(),
510                    external_metadata_rx: None,
511                    helm_chart_version,
512                    authenticator_kind,
513                    groups: None,
514                },
515                authenticated,
516            );
517            // No frontegg check, so auth session lasts indefinitely.
518            let auth_session = pending().right_future();
519            (session, auth_session)
520        }
521
522        Authenticator::None => {
523            let session = adapter_client.new_session(
524                SessionConfig {
525                    conn_id: conn.conn_id().clone(),
526                    uuid: conn_uuid,
527                    user,
528                    client_ip: conn.peer_addr().clone(),
529                    external_metadata_rx: None,
530                    helm_chart_version,
531                    authenticator_kind,
532                    groups: None,
533                },
534                Authenticated,
535            );
536            // No frontegg check, so auth session lasts indefinitely.
537            let auth_session = pending().right_future();
538            (session, auth_session)
539        }
540    };
541
542    let system_vars = adapter_client.get_system_vars().await;
543    // Startup parameters that were successfully applied. They additionally
544    // become the session's default values below, once role defaults have been
545    // applied too.
546    let mut applied_params = vec![];
547    for (name, value) in params {
548        let settings = match name.as_str() {
549            "options" => match &options {
550                Ok(opts) => opts,
551                Err(()) => {
552                    session.add_notice(AdapterNotice::BadStartupSetting {
553                        name,
554                        reason: "could not parse".into(),
555                    });
556                    continue;
557                }
558            },
559            _ => &vec![(name, value)],
560        };
561        for (key, val) in settings {
562            const LOCAL: bool = false;
563            // TODO: Issuing an error here is better than what we did before
564            // (silently ignore errors on set), but erroring the connection
565            // might be the better behavior. We maybe need to support more
566            // options sent by psql and drivers before we can safely do this.
567            match session
568                .vars_mut()
569                .set(&system_vars, key, VarInput::Flat(val), LOCAL)
570            {
571                Ok(()) => applied_params.push((key.clone(), val.clone())),
572                Err(err) => {
573                    session.add_notice(AdapterNotice::BadStartupSetting {
574                        name: key.clone(),
575                        reason: err.to_string(),
576                    });
577                }
578            }
579        }
580    }
581    session
582        .vars_mut()
583        .end_transaction(EndTransactionAction::Commit);
584
585    let _guard = match active_connection_counter.allocate_connection(session.user()) {
586        Ok(drop_connection) => drop_connection,
587        Err(e) => {
588            let e: AdapterError = e.into();
589            return conn.send(e.into_response(Severity::Fatal)).await;
590        }
591    };
592
593    // Register session with adapter.
594    let mut adapter_client = match adapter_client.startup(session).await {
595        Ok(adapter_client) => adapter_client,
596        Err(e) => return conn.send(e.into_response(Severity::Fatal)).await,
597    };
598
599    // Make the startup parameters the session's default values, so that RESET
600    // and DISCARD ALL restore them rather than the server defaults. This
601    // matches PostgreSQL, where client-supplied startup parameters take
602    // precedence over role defaults (which startup registration applied) both
603    // as the current value and as the reset value. Connection poolers rely on
604    // this. For example, pgbouncer's default server_reset_query is DISCARD
605    // ALL, which must not rebind a pooled connection to the default database.
606    for (key, val) in applied_params {
607        if let Err(err) = adapter_client
608            .session()
609            .vars_mut()
610            .set_default(&key, VarInput::Flat(&val))
611        {
612            // Unexpected, since the same value was accepted by set() above.
613            mz_ore::soft_panic_or_log!("failed to apply startup parameter as default: {err:?}");
614        }
615    }
616
617    let mut buf = vec![BackendMessage::AuthenticationOk];
618    for var in adapter_client.session().vars().notify_set() {
619        buf.push(BackendMessage::ParameterStatus(var.name(), var.value()));
620    }
621    buf.push(BackendMessage::BackendKeyData {
622        conn_id: adapter_client.session().conn_id().unhandled(),
623        secret_key: adapter_client.session().secret_key(),
624    });
625    buf.extend(
626        adapter_client
627            .session()
628            .drain_notices()
629            .into_iter()
630            .map(|notice| BackendMessage::ErrorResponse(notice.into_response())),
631    );
632    buf.push(BackendMessage::ReadyForQuery(
633        adapter_client.session().transaction().into(),
634    ));
635    conn.send_all(buf).await?;
636    conn.flush().await?;
637
638    let machine = StateMachine {
639        conn,
640        adapter_client,
641        txn_needs_commit: false,
642        tokio_metrics_intervals,
643    };
644
645    select! {
646        r = machine.run() => {
647            // Errors produced internally (like MAX_REQUEST_SIZE being exceeded) should send an
648            // error to the client informing them why the connection was closed. We still want to
649            // return the original error up the stack, though, so we skip error checking during conn
650            // operations.
651            if let Err(err) = &r {
652                let _ = conn
653                    .send(ErrorResponse::fatal(
654                        SqlState::CONNECTION_FAILURE,
655                        err.to_string(),
656                    ))
657                    .await;
658                let _ = conn.flush().await;
659            }
660            r
661        },
662        _ = expired => {
663            conn
664                .send(ErrorResponse::fatal(SqlState::INVALID_AUTHORIZATION_SPECIFICATION, "authentication expired"))
665                .await?;
666            conn.flush().await
667        }
668    }
669}
670
671/// Decides if a given password is a JWT by checking
672/// if we can decode its header.
673fn is_jwt(password: &str) -> bool {
674    jsonwebtoken::decode_header(password).is_ok()
675}
676
677/// Returns (name, value) session settings pairs from an options value.
678///
679/// From Postgres, see pg_split_opts in postinit.c and process_postgres_switches
680/// in postgres.c.
681fn parse_options(value: &str) -> Result<Vec<(String, String)>, ()> {
682    let opts = split_options(value);
683    let mut pairs = Vec::with_capacity(opts.len());
684    let mut seen_prefix = false;
685    for opt in opts {
686        if !seen_prefix {
687            if opt == "-c" {
688                seen_prefix = true;
689            } else {
690                let (key, val) = parse_option(&opt)?;
691                pairs.push((key.to_owned(), val.to_owned()));
692            }
693        } else {
694            let (key, val) = opt.split_once('=').ok_or(())?;
695            pairs.push((key.to_owned(), val.to_owned()));
696            seen_prefix = false;
697        }
698    }
699    Ok(pairs)
700}
701
702/// Returns the parsed key and value from option of the form `--key=value`, `-c
703/// key=value`, or `-ckey=value`. Keys replace `-` with `_`. Returns an error if
704/// there was some other prefix.
705fn parse_option(option: &str) -> Result<(&str, &str), ()> {
706    let (key, value) = option.split_once('=').ok_or(())?;
707    for prefix in &["-c", "--"] {
708        if let Some(key) = key.strip_prefix(prefix) {
709            return Ok((key, value));
710        }
711    }
712    Err(())
713}
714
715/// Splits value by any number of spaces except those preceded by `\`.
716fn split_options(value: &str) -> Vec<String> {
717    let mut strs = Vec::new();
718    // Need to build a string because of the escaping, so we can't simply
719    // subslice into value, and this isn't called enough to need to make it
720    // smart so it only builds a string if needed.
721    let mut current = String::new();
722    let mut was_slash = false;
723    for c in value.chars() {
724        was_slash = match c {
725            ' ' => {
726                if was_slash {
727                    current.push(' ');
728                } else if !current.is_empty() {
729                    // To ignore multiple spaces in a row, only push if current
730                    // is not empty.
731                    strs.push(std::mem::take(&mut current));
732                }
733                false
734            }
735            '\\' => {
736                if was_slash {
737                    // Two slashes in a row will add a slash and not escape the
738                    // next char.
739                    current.push('\\');
740                    false
741                } else {
742                    true
743                }
744            }
745            _ => {
746                current.push(c);
747                false
748            }
749        };
750    }
751    // A `\` at the end will be ignored.
752    if !current.is_empty() {
753        strs.push(current);
754    }
755    strs
756}
757
758enum PasswordRequestError {
759    InvalidPasswordError(ErrorResponse),
760    IoError(io::Error),
761}
762
763impl From<io::Error> for PasswordRequestError {
764    fn from(e: io::Error) -> Self {
765        PasswordRequestError::IoError(e)
766    }
767}
768
769/// Requests a cleartext password from a connection and returns it if it is valid.
770/// Sends an error response in the connection if the password
771/// is not valid.
772async fn request_cleartext_password<A>(
773    conn: &mut FramedConn<A>,
774) -> Result<String, PasswordRequestError>
775where
776    A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
777{
778    conn.send(BackendMessage::AuthenticationCleartextPassword)
779        .await?;
780    conn.flush().await?;
781
782    if let Some(message) = conn.recv().await? {
783        if let FrontendMessage::RawAuthentication(data) = message {
784            if let Some(FrontendMessage::Password { password }) =
785                decode_password(Cursor::new(&data)).ok()
786            {
787                return Ok(password);
788            }
789        }
790    }
791
792    Err(PasswordRequestError::InvalidPasswordError(
793        ErrorResponse::fatal(
794            SqlState::INVALID_AUTHORIZATION_SPECIFICATION,
795            "expected Password message",
796        ),
797    ))
798}
799
800/// Helper for password-based authentication using AdapterClient
801/// and returns an authenticated session.
802async fn authenticate_with_password<A>(
803    conn: &FramedConn<A>,
804    adapter_client: &mz_adapter::Client,
805    user: String,
806    password: Password,
807    conn_uuid: Uuid,
808    helm_chart_version: Option<String>,
809) -> Result<Session, PasswordRequestError>
810where
811    A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
812{
813    let authenticated = match adapter_client.authenticate(&user, &password).await {
814        Ok(authenticated) => authenticated,
815        Err(err) => {
816            warn!(?err, "pgwire connection failed authentication");
817            return Err(PasswordRequestError::InvalidPasswordError(
818                ErrorResponse::fatal(SqlState::INVALID_PASSWORD, "invalid password"),
819            ));
820        }
821    };
822
823    let session = adapter_client.new_session(
824        SessionConfig {
825            conn_id: conn.conn_id().clone(),
826            uuid: conn_uuid,
827            user,
828            client_ip: conn.peer_addr().clone(),
829            external_metadata_rx: None,
830            helm_chart_version,
831            authenticator_kind: mz_auth::AuthenticatorKind::Password,
832            groups: None,
833        },
834        authenticated,
835    );
836
837    Ok(session)
838}
839
840#[derive(Debug)]
841enum State {
842    Ready,
843    Drain,
844    Done,
845}
846
847struct StateMachine<'a, A, I>
848where
849    I: Iterator<Item = TaskMetrics> + Send + 'a,
850{
851    conn: &'a mut FramedConn<A>,
852    adapter_client: mz_adapter::SessionClient,
853    txn_needs_commit: bool,
854    tokio_metrics_intervals: I,
855}
856
857enum SendRowsEndedReason {
858    Success {
859        result_size: u64,
860        rows_returned: u64,
861    },
862    Errored {
863        error: String,
864    },
865    Canceled,
866}
867
868const ABORTED_TXN_MSG: &str =
869    "current transaction is aborted, commands ignored until end of transaction block";
870
871impl<'a, A, I> StateMachine<'a, A, I>
872where
873    A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin + 'a,
874    I: Iterator<Item = TaskMetrics> + Send + 'a,
875{
876    // Manually desugar this (don't use `async fn run`) here because a much better
877    // error message is produced if there are problems with Send or other traits
878    // somewhere within the Future.
879    #[allow(clippy::manual_async_fn)]
880    #[mz_ore::instrument(level = "debug")]
881    fn run(mut self) -> impl Future<Output = Result<(), io::Error>> + Send + 'a {
882        async move {
883            let mut state = State::Ready;
884            loop {
885                self.send_pending_notices().await?;
886                state = match state {
887                    State::Ready => self.advance_ready().await?,
888                    State::Drain => self.advance_drain().await?,
889                    State::Done => return Ok(()),
890                };
891                self.adapter_client
892                    .add_idle_in_transaction_session_timeout();
893            }
894        }
895    }
896
897    #[instrument(level = "debug")]
898    async fn advance_ready(&mut self) -> Result<State, io::Error> {
899        // Start a new metrics interval before the `recv()` call.
900        self.tokio_metrics_intervals
901            .next()
902            .expect("infinite iterator");
903
904        // Handle timeouts first so we don't execute any statements when there's a pending timeout.
905        let message = select! {
906            biased;
907
908            // `recv_timeout()` is cancel-safe as per it's docs.
909            Some(timeout) = self.adapter_client.recv_timeout() => {
910                let err: AdapterError = timeout.into();
911                let conn_id = self.adapter_client.session().conn_id();
912                tracing::warn!("session timed out, conn_id {}", conn_id);
913
914                // Process the error, doing any state cleanup.
915                let error_response = err.into_response(Severity::Fatal);
916                let error_state = self.send_error_and_get_state(error_response).await;
917
918                // Terminate __after__ we do any cleanup.
919                self.adapter_client.terminate().await;
920
921                // We must wait for the client to send a request before we can send the error response.
922                // Due to the PG wire protocol, we can't send an ErrorResponse unless it is in response
923                // to a client message.
924                let _ = self.conn.recv().await?;
925                return error_state;
926            },
927            // `recv()` is cancel-safe as per it's docs.
928            message = self.conn.recv() => message?,
929        };
930
931        // Take the metrics since just before the `recv`.
932        let interval = self
933            .tokio_metrics_intervals
934            .next()
935            .expect("infinite iterator");
936        let recv_scheduling_delay_ms = interval.total_scheduled_duration.as_secs_f64() * 1000.0;
937
938        // TODO(ggevay): Consider subtracting the scheduling delay from `received`. It's not obvious
939        // whether we should do this, because the result wouldn't exactly correspond to either first
940        // byte received or last byte received (for msgs that arrive in more than one network packet).
941        let received = SYSTEM_TIME();
942
943        self.adapter_client
944            .remove_idle_in_transaction_session_timeout();
945
946        // NOTE(guswynn): we could consider adding spans to all message types. Currently
947        // only a few message types seem useful.
948        let message_name = message.as_ref().map(|m| m.name()).unwrap_or_default();
949
950        if let Some(message) = &message {
951            self.maybe_log_message_arrival(message).await;
952        }
953
954        let start = message.as_ref().map(|_| Instant::now());
955        let next_state = match message {
956            Some(FrontendMessage::Query { sql }) => {
957                let query_root_span =
958                    tracing::info_span!(parent: None, "advance_ready", otel.name = message_name);
959                query_root_span.follows_from(tracing::Span::current());
960                self.query(sql, received)
961                    .instrument(query_root_span)
962                    .await?
963            }
964            Some(FrontendMessage::Parse {
965                name,
966                sql,
967                param_types,
968            }) => self.parse(name, sql, param_types).await?,
969            Some(FrontendMessage::Bind {
970                portal_name,
971                statement_name,
972                param_formats,
973                raw_params,
974                result_formats,
975            }) => {
976                self.bind(
977                    portal_name,
978                    statement_name,
979                    param_formats,
980                    raw_params,
981                    result_formats,
982                )
983                .await?
984            }
985            Some(FrontendMessage::Execute {
986                portal_name,
987                max_rows,
988            }) => {
989                let max_rows = match usize::try_from(max_rows) {
990                    Ok(0) | Err(_) => ExecuteCount::All, // If `max_rows < 0`, no limit.
991                    Ok(n) => ExecuteCount::Count(n),
992                };
993                let execute_root_span =
994                    tracing::info_span!(parent: None, "advance_ready", otel.name = message_name);
995                execute_root_span.follows_from(tracing::Span::current());
996                let state = self
997                    .execute(
998                        portal_name,
999                        max_rows,
1000                        portal_exec_message,
1001                        None,
1002                        ExecuteTimeout::None,
1003                        None,
1004                        Some(received),
1005                    )
1006                    .instrument(execute_root_span)
1007                    .await?;
1008                // In PostgreSQL, when using the extended query protocol, some statements may
1009                // trigger an eager commit of the current implicit transaction,
1010                // see: <https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f92944137>.
1011                //
1012                // In Materialize, however, we eagerly commit every statement outside of an explicit
1013                // transaction when using the extended query protocol. This allows us to eliminate
1014                // the possibility of a multiple statement implicit transaction, which in turn
1015                // allows us to apply single-statement optimizations to queries issued in implicit
1016                // transactions in the extended query protocol.
1017                //
1018                // We don't immediately commit here to allow users to page through the portal if
1019                // necessary. Committing the transaction would destroy the portal before the next
1020                // Execute command has a chance to resume it. So we instead mark the transaction
1021                // for commit the next time that `ensure_transaction` is called.
1022                if self.adapter_client.session().transaction().is_implicit() {
1023                    self.txn_needs_commit = true;
1024                }
1025                state
1026            }
1027            Some(FrontendMessage::DescribeStatement { name }) => {
1028                self.describe_statement(&name).await?
1029            }
1030            Some(FrontendMessage::DescribePortal { name }) => self.describe_portal(&name).await?,
1031            Some(FrontendMessage::CloseStatement { name }) => self.close_statement(name).await?,
1032            Some(FrontendMessage::ClosePortal { name }) => self.close_portal(name).await?,
1033            Some(FrontendMessage::Flush) => self.flush().await?,
1034            Some(FrontendMessage::Sync) => self.sync().await?,
1035            Some(FrontendMessage::Terminate) => State::Done,
1036
1037            // Accept but ignore stray COPY subprotocol messages, mirroring
1038            // PostgreSQL. Clients stream COPY data optimistically, so when a
1039            // COPY statement fails before COPY mode is entered, its pipelined
1040            // CopyData/CopyDone/CopyFail arrive here. Draining instead would
1041            // discard unrelated messages until the next Sync, hanging simple
1042            // protocol clients that never send one.
1043            Some(FrontendMessage::CopyData(_))
1044            | Some(FrontendMessage::CopyDone)
1045            | Some(FrontendMessage::CopyFail(_)) => State::Ready,
1046
1047            Some(FrontendMessage::Password { .. })
1048            | Some(FrontendMessage::RawAuthentication(_))
1049            | Some(FrontendMessage::SASLInitialResponse { .. })
1050            | Some(FrontendMessage::SASLResponse(_)) => State::Drain,
1051            None => State::Done,
1052        };
1053
1054        if let Some(start) = start {
1055            self.adapter_client
1056                .inner()
1057                .metrics()
1058                .pgwire_message_processing_seconds
1059                .with_label_values(&[message_name])
1060                .observe(start.elapsed().as_secs_f64());
1061        }
1062        self.adapter_client
1063            .inner()
1064            .metrics()
1065            .pgwire_recv_scheduling_delay_ms
1066            .with_label_values(&[message_name])
1067            .observe(recv_scheduling_delay_ms);
1068
1069        Ok(next_state)
1070    }
1071
1072    async fn advance_drain(&mut self) -> Result<State, io::Error> {
1073        let message = self.conn.recv().await?;
1074        if message.is_some() {
1075            self.adapter_client
1076                .remove_idle_in_transaction_session_timeout();
1077        }
1078        match message {
1079            Some(FrontendMessage::Sync) => self.sync().await,
1080            None => Ok(State::Done),
1081            _ => Ok(State::Drain),
1082        }
1083    }
1084
1085    /// Note that `lifecycle_timestamps` belongs to the whole "Simple Query", because the whole
1086    /// Simple Query is received and parsed together. This means that if there are multiple
1087    /// statements in a Simple Query, then all of them have the same `lifecycle_timestamps`.
1088    #[instrument(level = "debug")]
1089    async fn one_query(
1090        &mut self,
1091        stmt: Statement<Raw>,
1092        sql: String,
1093        lifecycle_timestamps: LifecycleTimestamps,
1094    ) -> Result<State, io::Error> {
1095        // Bind the portal. Note that this does not set the empty string prepared
1096        // statement.
1097        const EMPTY_PORTAL: &str = "";
1098        if let Err(e) = self
1099            .adapter_client
1100            .declare(EMPTY_PORTAL.to_string(), stmt, sql)
1101            .await
1102        {
1103            return self
1104                .send_error_and_get_state(e.into_response(Severity::Error))
1105                .await;
1106        }
1107        let portal = self
1108            .adapter_client
1109            .session()
1110            .get_portal_unverified_mut(EMPTY_PORTAL)
1111            .expect("unnamed portal should be present");
1112
1113        *portal.lifecycle_timestamps = Some(lifecycle_timestamps);
1114
1115        let stmt_desc = portal.desc.clone();
1116        if !stmt_desc.param_types.is_empty() {
1117            return self
1118                .send_error_and_get_state(ErrorResponse::error(
1119                    SqlState::UNDEFINED_PARAMETER,
1120                    "there is no parameter $1",
1121                ))
1122                .await;
1123        }
1124
1125        // Maybe send row description.
1126        if let Some(relation_desc) = &stmt_desc.relation_desc {
1127            if !stmt_desc.is_copy {
1128                let formats = vec![Format::Text; stmt_desc.arity()];
1129                self.send(BackendMessage::RowDescription(
1130                    message::encode_row_description(relation_desc, &formats),
1131                ))
1132                .await?;
1133            }
1134        }
1135
1136        let result = match self
1137            .adapter_client
1138            .execute(EMPTY_PORTAL.to_string(), self.conn.wait_closed(), None)
1139            .await
1140        {
1141            Ok((response, execute_started)) => {
1142                self.send_pending_notices().await?;
1143                self.send_execute_response(
1144                    response,
1145                    stmt_desc.relation_desc,
1146                    EMPTY_PORTAL.to_string(),
1147                    ExecuteCount::All,
1148                    portal_exec_message,
1149                    None,
1150                    ExecuteTimeout::None,
1151                    execute_started,
1152                )
1153                .await
1154            }
1155            Err(e) => {
1156                self.send_pending_notices().await?;
1157                self.send_error_and_get_state(e.into_response(Severity::Error))
1158                    .await
1159            }
1160        };
1161
1162        // Destroy the portal.
1163        self.adapter_client.session().remove_portal(EMPTY_PORTAL);
1164
1165        result
1166    }
1167
1168    async fn ensure_transaction(
1169        &mut self,
1170        num_stmts: usize,
1171        message_type: &str,
1172    ) -> Result<(), io::Error> {
1173        let start = Instant::now();
1174        if self.txn_needs_commit {
1175            self.commit_transaction().await?;
1176        }
1177        // start_transaction can't error (but assert that just in case it changes in
1178        // the future.
1179        let res = self.adapter_client.start_transaction(Some(num_stmts));
1180        assert_ok!(res);
1181        self.adapter_client
1182            .inner()
1183            .metrics()
1184            .pgwire_ensure_transaction_seconds
1185            .with_label_values(&[message_type])
1186            .observe(start.elapsed().as_secs_f64());
1187        Ok(())
1188    }
1189
1190    /// Logs an arriving frontend message at info level, when
1191    /// `enable_statement_arrival_logging` is on. Runs before the message is
1192    /// processed, so a message whose processing crashes the process still
1193    /// appears in the log. The `kind` field says which message it is, and
1194    /// thereby also whether the statement came in through the simple protocol
1195    /// (`query`) or the extended protocol (`parse`, `bind`, `execute`, ...).
1196    /// The prepared statement and portal names, together with the connection
1197    /// id, allow connecting a `bind` or `execute` back to the `parse` that
1198    /// carried the SQL text.
1199    ///
1200    /// SQL text is parsed and logged with its literals redacted, the same
1201    /// redaction the statement log applies. This means a statement that
1202    /// crashes the parser is not captured, an accepted limitation. Bind
1203    /// parameter values are data that redaction cannot reach, so only their
1204    /// count is logged. Authentication payloads are never logged. COPY data
1205    /// is logged as its length only, and only when it arrives as a stray
1206    /// message in the ready state: messages consumed by the COPY subprotocol
1207    /// or the post-error drain loop don't pass through here at all.
1208    async fn maybe_log_message_arrival(&mut self, message: &FrontendMessage) {
1209        if !self
1210            .adapter_client
1211            .statement_arrival_logging_enabled()
1212            .await
1213        {
1214            return;
1215        }
1216        let session = self.adapter_client.session();
1217        let conn_id = session.conn_id();
1218        let session_uuid = session.uuid();
1219        let kind = message.name();
1220        match message {
1221            FrontendMessage::Query { sql } => {
1222                info!(
1223                    %conn_id, %session_uuid, kind, sql = %redact_sql_for_logging(sql),
1224                    "statement arrival"
1225                );
1226            }
1227            FrontendMessage::Parse { name, sql, .. } => {
1228                info!(
1229                    %conn_id, %session_uuid, kind, name, sql = %redact_sql_for_logging(sql),
1230                    "statement arrival"
1231                );
1232            }
1233            FrontendMessage::Bind {
1234                portal_name,
1235                statement_name,
1236                raw_params,
1237                ..
1238            } => {
1239                info!(
1240                    %conn_id, %session_uuid, kind, portal_name, statement_name,
1241                    num_params = raw_params.len(),
1242                    "statement arrival"
1243                );
1244            }
1245            // COPY payloads would flood the log. Log only their length.
1246            FrontendMessage::CopyData(data) => {
1247                info!(%conn_id, %session_uuid, kind, len = data.len(), "statement arrival");
1248            }
1249            // Authentication payloads must never be logged.
1250            FrontendMessage::Password { .. }
1251            | FrontendMessage::RawAuthentication(_)
1252            | FrontendMessage::SASLInitialResponse { .. }
1253            | FrontendMessage::SASLResponse(_) => {
1254                info!(%conn_id, %session_uuid, kind, "statement arrival");
1255            }
1256            // CopyFail carries a client-supplied free-text error message,
1257            // which we don't log.
1258            FrontendMessage::CopyFail(_) => {
1259                info!(%conn_id, %session_uuid, kind, "statement arrival");
1260            }
1261            // Log the full Debug representation for all other variants, which
1262            // carry only object names or no payload.
1263            FrontendMessage::DescribeStatement { .. }
1264            | FrontendMessage::DescribePortal { .. }
1265            | FrontendMessage::Execute { .. }
1266            | FrontendMessage::Flush
1267            | FrontendMessage::Sync
1268            | FrontendMessage::CloseStatement { .. }
1269            | FrontendMessage::ClosePortal { .. }
1270            | FrontendMessage::Terminate
1271            | FrontendMessage::CopyDone => {
1272                // WARNING: When adding a variant here, consider whether its payload is sensitive or
1273                // bulky!
1274                //
1275                // (The field must not be named `message`, that name is
1276                // reserved for the event text in tracing.)
1277                info!(%conn_id, %session_uuid, kind, contents = ?message, "statement arrival");
1278            }
1279        }
1280    }
1281
1282    fn parse_sql<'b>(&self, sql: &'b str) -> Result<Vec<StatementParseResult<'b>>, ErrorResponse> {
1283        let parse_start = Instant::now();
1284        let result = match self.adapter_client.parse(sql) {
1285            Ok(result) => result.map_err(|e| {
1286                // Convert our 0-based byte position to pgwire's 1-based character
1287                // position.
1288                let pos = sql[..e.error.pos].chars().count() + 1;
1289                ErrorResponse::error(SqlState::SYNTAX_ERROR, e.error.message).with_position(pos)
1290            }),
1291            Err(msg) => Err(ErrorResponse::error(SqlState::PROGRAM_LIMIT_EXCEEDED, msg)),
1292        };
1293        self.adapter_client
1294            .inner()
1295            .metrics()
1296            .parse_seconds
1297            .observe(parse_start.elapsed().as_secs_f64());
1298        result
1299    }
1300
1301    /// Executes a "Simple Query", see
1302    /// <https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-SIMPLE-QUERY>
1303    ///
1304    /// For implicit transaction handling, see "Multiple Statements in a Simple Query" in the above.
1305    #[instrument(level = "debug")]
1306    async fn query(&mut self, sql: String, received: EpochMillis) -> Result<State, io::Error> {
1307        // Parse first before doing any transaction checking.
1308        let stmts = match self.parse_sql(&sql) {
1309            Ok(stmts) => stmts,
1310            Err(err) => {
1311                self.send_error_and_get_state(err).await?;
1312                return self.ready().await;
1313            }
1314        };
1315
1316        let num_stmts = stmts.len();
1317
1318        // Compare with postgres' backend/tcop/postgres.c exec_simple_query.
1319        for StatementParseResult { ast: stmt, sql } in stmts {
1320            // In an aborted transaction, reject all commands except COMMIT/ROLLBACK.
1321            if self.is_aborted_txn() && !is_txn_exit_stmt(Some(&stmt)) {
1322                self.aborted_txn_error().await?;
1323                break;
1324            }
1325
1326            // Start an implicit transaction if we aren't in any transaction and there's
1327            // more than one statement. This mirrors the `use_implicit_block` variable in
1328            // postgres.
1329            //
1330            // This needs to be done in the loop instead of once at the top because
1331            // a COMMIT/ROLLBACK statement needs to start a new transaction on next
1332            // statement.
1333            self.ensure_transaction(num_stmts, "query").await?;
1334
1335            match self
1336                .one_query(stmt, sql.to_string(), LifecycleTimestamps { received })
1337                .await?
1338            {
1339                State::Ready => (),
1340                State::Drain => break,
1341                State::Done => return Ok(State::Done),
1342            }
1343        }
1344
1345        // Implicit transactions are closed at the end of a Query message.
1346        {
1347            if self.adapter_client.session().transaction().is_implicit() {
1348                self.commit_transaction().await?;
1349            }
1350        }
1351
1352        if num_stmts == 0 {
1353            self.send(BackendMessage::EmptyQueryResponse).await?;
1354        }
1355
1356        self.ready().await
1357    }
1358
1359    #[instrument(level = "debug")]
1360    async fn parse(
1361        &mut self,
1362        name: String,
1363        sql: String,
1364        param_oids: Vec<u32>,
1365    ) -> Result<State, io::Error> {
1366        // Start a transaction if we aren't in one.
1367        self.ensure_transaction(1, "parse").await?;
1368
1369        let mut param_types = vec![];
1370        for oid in param_oids {
1371            match mz_pgrepr::Type::from_oid(oid) {
1372                Ok(ty) => match SqlScalarType::try_from(&ty) {
1373                    Ok(ty) => param_types.push(Some(ty)),
1374                    Err(err) => {
1375                        return self
1376                            .send_error_and_get_state(ErrorResponse::error(
1377                                SqlState::INVALID_PARAMETER_VALUE,
1378                                err.to_string(),
1379                            ))
1380                            .await;
1381                    }
1382                },
1383                Err(_) if oid == 0 => param_types.push(None),
1384                Err(e) => {
1385                    return self
1386                        .send_error_and_get_state(ErrorResponse::error(
1387                            SqlState::PROTOCOL_VIOLATION,
1388                            e.to_string(),
1389                        ))
1390                        .await;
1391                }
1392            }
1393        }
1394
1395        let stmts = match self.parse_sql(&sql) {
1396            Ok(stmts) => stmts,
1397            Err(err) => {
1398                return self.send_error_and_get_state(err).await;
1399            }
1400        };
1401        if stmts.len() > 1 {
1402            return self
1403                .send_error_and_get_state(ErrorResponse::error(
1404                    SqlState::INTERNAL_ERROR,
1405                    "cannot insert multiple commands into a prepared statement",
1406                ))
1407                .await;
1408        }
1409        let (maybe_stmt, sql) = match stmts.into_iter().next() {
1410            None => (None, ""),
1411            Some(StatementParseResult { ast, sql }) => (Some(ast), sql),
1412        };
1413        if self.is_aborted_txn() && !is_txn_exit_stmt(maybe_stmt.as_ref()) {
1414            return self.aborted_txn_error().await;
1415        }
1416        match self
1417            .adapter_client
1418            .prepare(name, maybe_stmt, sql.to_string(), param_types)
1419            .await
1420        {
1421            Ok(()) => {
1422                self.send(BackendMessage::ParseComplete).await?;
1423                Ok(State::Ready)
1424            }
1425            Err(e) => {
1426                self.send_error_and_get_state(e.into_response(Severity::Error))
1427                    .await
1428            }
1429        }
1430    }
1431
1432    /// Commits and clears the current transaction.
1433    #[instrument(level = "debug")]
1434    async fn commit_transaction(&mut self) -> Result<(), io::Error> {
1435        self.end_transaction(EndTransactionAction::Commit).await
1436    }
1437
1438    /// Rollback and clears the current transaction.
1439    #[instrument(level = "debug")]
1440    async fn rollback_transaction(&mut self) -> Result<(), io::Error> {
1441        self.end_transaction(EndTransactionAction::Rollback).await
1442    }
1443
1444    /// End a transaction and report to the user if an error occurred.
1445    #[instrument(level = "debug")]
1446    async fn end_transaction(&mut self, action: EndTransactionAction) -> Result<(), io::Error> {
1447        self.txn_needs_commit = false;
1448        let resp = self.adapter_client.end_transaction(action).await;
1449        if let Err(err) = resp {
1450            self.send(BackendMessage::ErrorResponse(
1451                err.into_response(Severity::Error),
1452            ))
1453            .await?;
1454        }
1455        Ok(())
1456    }
1457
1458    #[instrument(level = "debug")]
1459    async fn bind(
1460        &mut self,
1461        portal_name: String,
1462        statement_name: String,
1463        param_formats: Vec<Format>,
1464        raw_params: Vec<Option<Vec<u8>>>,
1465        result_formats: Vec<Format>,
1466    ) -> Result<State, io::Error> {
1467        // Start a transaction if we aren't in one.
1468        self.ensure_transaction(1, "bind").await?;
1469
1470        let aborted_txn = self.is_aborted_txn();
1471        let stmt = match self
1472            .adapter_client
1473            .get_prepared_statement(&statement_name)
1474            .await
1475        {
1476            Ok(stmt) => stmt,
1477            Err(err) => {
1478                return self
1479                    .send_error_and_get_state(err.into_response(Severity::Error))
1480                    .await;
1481            }
1482        };
1483
1484        let param_types = &stmt.desc().param_types;
1485        if param_types.len() != raw_params.len() {
1486            let message = format!(
1487                "bind message supplies {actual} parameters, \
1488                 but prepared statement \"{name}\" requires {expected}",
1489                name = statement_name,
1490                actual = raw_params.len(),
1491                expected = param_types.len()
1492            );
1493            return self
1494                .send_error_and_get_state(ErrorResponse::error(
1495                    SqlState::PROTOCOL_VIOLATION,
1496                    message,
1497                ))
1498                .await;
1499        }
1500        let param_formats = match pad_formats(param_formats, raw_params.len()) {
1501            Ok(param_formats) => param_formats,
1502            Err(msg) => {
1503                return self
1504                    .send_error_and_get_state(ErrorResponse::error(
1505                        SqlState::PROTOCOL_VIOLATION,
1506                        msg,
1507                    ))
1508                    .await;
1509            }
1510        };
1511        if aborted_txn && !is_txn_exit_stmt(stmt.stmt()) {
1512            return self.aborted_txn_error().await;
1513        }
1514        let buf = RowArena::new();
1515        let mut params = vec![];
1516        for ((raw_param, mz_typ), format) in raw_params
1517            .into_iter()
1518            .zip_eq(param_types)
1519            .zip_eq(param_formats)
1520        {
1521            let pg_typ = mz_pgrepr::Type::from(mz_typ);
1522            let datum = match raw_param {
1523                None => Datum::Null,
1524                Some(bytes) => match mz_pgrepr::Value::decode(format, &pg_typ, &bytes) {
1525                    Ok(param) => match param.into_datum_decode_error(&buf, &pg_typ, "parameter") {
1526                        Ok(datum) => datum,
1527                        Err(msg) => {
1528                            return self
1529                                .send_error_and_get_state(ErrorResponse::error(
1530                                    SqlState::INVALID_PARAMETER_VALUE,
1531                                    msg,
1532                                ))
1533                                .await;
1534                        }
1535                    },
1536                    Err(err) => {
1537                        // NUL characters get the same SQLSTATE that PostgreSQL
1538                        // reports for them.
1539                        let (code, msg) = if err.is::<mz_pgrepr::NulCharacterError>() {
1540                            (SqlState::CHARACTER_NOT_IN_REPERTOIRE, err.to_string())
1541                        } else {
1542                            (
1543                                SqlState::INVALID_PARAMETER_VALUE,
1544                                format!("unable to decode parameter: {}", err),
1545                            )
1546                        };
1547                        return self
1548                            .send_error_and_get_state(ErrorResponse::error(code, msg))
1549                            .await;
1550                    }
1551                },
1552            };
1553            params.push((datum, mz_typ.clone()))
1554        }
1555
1556        let result_formats = match pad_formats(
1557            result_formats,
1558            stmt.desc()
1559                .relation_desc
1560                .clone()
1561                .map(|desc| desc.typ().column_types.len())
1562                .unwrap_or(0),
1563        ) {
1564            Ok(result_formats) => result_formats,
1565            Err(msg) => {
1566                return self
1567                    .send_error_and_get_state(ErrorResponse::error(
1568                        SqlState::PROTOCOL_VIOLATION,
1569                        msg,
1570                    ))
1571                    .await;
1572            }
1573        };
1574
1575        // Binary encodings are disabled for list, map, and aclitem types, but this doesn't
1576        // apply to COPY TO statements.
1577        if !stmt.stmt().map_or(false, |stmt| match stmt {
1578            Statement::Copy(CopyStatement {
1579                direction: CopyDirection::To,
1580                ..
1581            }) => true,
1582            Statement::Copy(CopyStatement {
1583                direction: CopyDirection::From,
1584                // To be conservative, we are restricting COPY FROM to only allow list/map/aclitem types if it is not
1585                // copying from STDIN. It is likely that this works in theory, but is risky and likely to OOM anyways
1586                // as all the data will be held in a buffer in memory before being processed.
1587                target: CopyTarget::Expr(_),
1588                ..
1589            }) => true,
1590            _ => false,
1591        }) {
1592            if let Some(desc) = stmt.desc().relation_desc.clone() {
1593                for (format, ty) in result_formats.iter().zip_eq(desc.iter_types()) {
1594                    if let Format::Binary = format {
1595                        if let Err(msg) = mz_pgrepr::Value::binary_encoding_error(&ty.scalar_type) {
1596                            return self
1597                                .send_error_and_get_state(ErrorResponse::error(
1598                                    SqlState::UNDEFINED_FUNCTION,
1599                                    msg,
1600                                ))
1601                                .await;
1602                        }
1603                    }
1604                }
1605            }
1606        }
1607
1608        let desc = stmt.desc().clone();
1609        let logging = Arc::clone(stmt.logging());
1610        let stmt_ast = stmt.stmt().cloned();
1611        let state_revision = stmt.state_revision;
1612        if let Err(err) = self.adapter_client.session().set_portal(
1613            portal_name,
1614            desc,
1615            stmt_ast,
1616            logging,
1617            params,
1618            result_formats,
1619            state_revision,
1620        ) {
1621            return self
1622                .send_error_and_get_state(err.into_response(Severity::Error))
1623                .await;
1624        }
1625
1626        self.send(BackendMessage::BindComplete).await?;
1627        Ok(State::Ready)
1628    }
1629
1630    /// `outer_ctx_extra` is Some when we are executing as part of an outer statement, e.g., a FETCH
1631    /// triggering the execution of the underlying query.
1632    fn execute(
1633        &mut self,
1634        portal_name: String,
1635        max_rows: ExecuteCount,
1636        get_response: GetResponse,
1637        fetch_portal_name: Option<String>,
1638        timeout: ExecuteTimeout,
1639        outer_ctx_extra: Option<ExecuteContextGuard>,
1640        received: Option<EpochMillis>,
1641    ) -> BoxFuture<'_, Result<State, io::Error>> {
1642        async move {
1643            let aborted_txn = self.is_aborted_txn();
1644
1645            // Check if the portal has been started and can be continued.
1646            let portal = match self
1647                .adapter_client
1648                .session()
1649                .get_portal_unverified_mut(&portal_name)
1650            {
1651                Some(portal) => portal,
1652                None => {
1653                    let msg = format!("portal {} does not exist", portal_name.quoted());
1654                    if let Some(outer_ctx_extra) = outer_ctx_extra {
1655                        self.adapter_client.retire_execute(
1656                            outer_ctx_extra,
1657                            StatementEndedExecutionReason::Errored { error: msg.clone() },
1658                        );
1659                    }
1660                    return self
1661                        .send_error_and_get_state(ErrorResponse::error(
1662                            SqlState::INVALID_CURSOR_NAME,
1663                            msg,
1664                        ))
1665                        .await;
1666                }
1667            };
1668
1669            *portal.lifecycle_timestamps = received.map(LifecycleTimestamps::new);
1670
1671            // In an aborted transaction, reject all commands except COMMIT/ROLLBACK.
1672            let txn_exit_stmt = is_txn_exit_stmt(portal.stmt.as_deref());
1673            if aborted_txn && !txn_exit_stmt {
1674                if let Some(outer_ctx_extra) = outer_ctx_extra {
1675                    self.adapter_client.retire_execute(
1676                        outer_ctx_extra,
1677                        StatementEndedExecutionReason::Errored {
1678                            error: ABORTED_TXN_MSG.to_string(),
1679                        },
1680                    );
1681                }
1682                return self.aborted_txn_error().await;
1683            }
1684
1685            let row_desc = portal.desc.relation_desc.clone();
1686            match portal.state {
1687                PortalState::NotStarted => {
1688                    // Start a transaction if we aren't in one.
1689                    self.ensure_transaction(1, "execute").await?;
1690                    match self
1691                        .adapter_client
1692                        .execute(
1693                            portal_name.clone(),
1694                            self.conn.wait_closed(),
1695                            outer_ctx_extra,
1696                        )
1697                        .await
1698                    {
1699                        Ok((response, execute_started)) => {
1700                            self.send_pending_notices().await?;
1701                            self.send_execute_response(
1702                                response,
1703                                row_desc,
1704                                portal_name,
1705                                max_rows,
1706                                get_response,
1707                                fetch_portal_name,
1708                                timeout,
1709                                execute_started,
1710                            )
1711                            .await
1712                        }
1713                        Err(e) => {
1714                            self.send_pending_notices().await?;
1715                            self.send_error_and_get_state(e.into_response(Severity::Error))
1716                                .await
1717                        }
1718                    }
1719                }
1720                PortalState::InProgress(rows) => {
1721                    let rows = rows.take().expect("InProgress rows must be populated");
1722                    let (result, statement_ended_execution_reason) = match self
1723                        .send_rows(
1724                            row_desc.expect("portal missing row desc on resumption"),
1725                            portal_name,
1726                            rows,
1727                            max_rows,
1728                            get_response,
1729                            fetch_portal_name,
1730                            timeout,
1731                        )
1732                        .await
1733                    {
1734                        Err(e) => {
1735                            // This is an error communicating with the connection.
1736                            // We consider that to be a cancelation, rather than a query error.
1737                            (Err(e), StatementEndedExecutionReason::Canceled)
1738                        }
1739                        Ok((ok, SendRowsEndedReason::Canceled)) => {
1740                            (Ok(ok), StatementEndedExecutionReason::Canceled)
1741                        }
1742                        // NOTE: For now the values for `result_size` and
1743                        // `rows_returned` in fetches are a bit confusing.
1744                        // We record `Some(n)` for the first fetch, where `n` is
1745                        // the number of bytes/rows returned by the inner
1746                        // execute (regardless of how many rows the
1747                        // fetch fetched), and `None` for subsequent fetches.
1748                        //
1749                        // This arguably makes sense since the size/rows
1750                        // returned measures how much work the compute
1751                        // layer had to do to satisfy the query, but
1752                        // we should revisit it if/when we start
1753                        // logging the inner execute separately.
1754                        Ok((
1755                            ok,
1756                            SendRowsEndedReason::Success {
1757                                result_size: _,
1758                                rows_returned: _,
1759                            },
1760                        )) => (
1761                            Ok(ok),
1762                            StatementEndedExecutionReason::Success {
1763                                result_size: None,
1764                                rows_returned: None,
1765                                execution_strategy: None,
1766                            },
1767                        ),
1768                        Ok((ok, SendRowsEndedReason::Errored { error })) => {
1769                            (Ok(ok), StatementEndedExecutionReason::Errored { error })
1770                        }
1771                    };
1772                    if let Some(outer_ctx_extra) = outer_ctx_extra {
1773                        self.adapter_client
1774                            .retire_execute(outer_ctx_extra, statement_ended_execution_reason);
1775                    }
1776                    result
1777                }
1778                // FETCH is an awkward command for our current architecture. In Postgres it
1779                // will extract <count> rows from the target portal, cache them, and return
1780                // them to the user as requested. Its command tag is always FETCH <num rows
1781                // extracted>. In Materialize, since we have chosen to not fully support FETCH,
1782                // we must remember the number of rows that were returned. Use this tag to
1783                // remember that information and return it.
1784                PortalState::Completed(Some(tag)) => {
1785                    let tag = tag.to_string();
1786                    if let Some(outer_ctx_extra) = outer_ctx_extra {
1787                        self.adapter_client.retire_execute(
1788                            outer_ctx_extra,
1789                            StatementEndedExecutionReason::Success {
1790                                result_size: None,
1791                                rows_returned: None,
1792                                execution_strategy: None,
1793                            },
1794                        );
1795                    }
1796                    self.send(BackendMessage::CommandComplete { tag }).await?;
1797                    Ok(State::Ready)
1798                }
1799                PortalState::Completed(None) => {
1800                    let error = format!(
1801                        "portal {} cannot be run",
1802                        Ident::new_unchecked(portal_name).to_ast_string_stable()
1803                    );
1804                    if let Some(outer_ctx_extra) = outer_ctx_extra {
1805                        self.adapter_client.retire_execute(
1806                            outer_ctx_extra,
1807                            StatementEndedExecutionReason::Errored {
1808                                error: error.clone(),
1809                            },
1810                        );
1811                    }
1812                    self.send_error_and_get_state(ErrorResponse::error(
1813                        SqlState::OBJECT_NOT_IN_PREREQUISITE_STATE,
1814                        error,
1815                    ))
1816                    .await
1817                }
1818            }
1819        }
1820        .instrument(debug_span!("execute"))
1821        .boxed()
1822    }
1823
1824    #[instrument(level = "debug")]
1825    async fn describe_statement(&mut self, name: &str) -> Result<State, io::Error> {
1826        // Start a transaction if we aren't in one.
1827        self.ensure_transaction(1, "describe_statement").await?;
1828
1829        let stmt = match self.adapter_client.get_prepared_statement(name).await {
1830            Ok(stmt) => stmt,
1831            Err(err) => {
1832                return self
1833                    .send_error_and_get_state(err.into_response(Severity::Error))
1834                    .await;
1835            }
1836        };
1837        // Cloning to avoid a mutable borrow issue because `send` also uses `adapter_client`
1838        let parameter_desc = BackendMessage::ParameterDescription(
1839            stmt.desc()
1840                .param_types
1841                .iter()
1842                .map(mz_pgrepr::Type::from)
1843                .collect(),
1844        );
1845        // Claim that all results will be output in text format, even
1846        // though the true result formats are not yet known. A bit
1847        // weird, but this is the behavior that PostgreSQL specifies.
1848        let formats = vec![Format::Text; stmt.desc().arity()];
1849        let row_desc = describe_rows(stmt.desc(), &formats);
1850        self.send_all([parameter_desc, row_desc]).await?;
1851        Ok(State::Ready)
1852    }
1853
1854    #[instrument(level = "debug")]
1855    async fn describe_portal(&mut self, name: &str) -> Result<State, io::Error> {
1856        // Start a transaction if we aren't in one.
1857        self.ensure_transaction(1, "describe_portal").await?;
1858
1859        let session = self.adapter_client.session();
1860        let row_desc = session
1861            .get_portal_unverified(name)
1862            .map(|portal| describe_rows(&portal.desc, &portal.result_formats));
1863        match row_desc {
1864            Some(row_desc) => {
1865                self.send(row_desc).await?;
1866                Ok(State::Ready)
1867            }
1868            None => {
1869                self.send_error_and_get_state(ErrorResponse::error(
1870                    SqlState::INVALID_CURSOR_NAME,
1871                    format!("portal {} does not exist", name.quoted()),
1872                ))
1873                .await
1874            }
1875        }
1876    }
1877
1878    #[instrument(level = "debug")]
1879    async fn close_statement(&mut self, name: String) -> Result<State, io::Error> {
1880        self.adapter_client
1881            .session()
1882            .remove_prepared_statement(&name);
1883        self.send(BackendMessage::CloseComplete).await?;
1884        Ok(State::Ready)
1885    }
1886
1887    #[instrument(level = "debug")]
1888    async fn close_portal(&mut self, name: String) -> Result<State, io::Error> {
1889        self.adapter_client.session().remove_portal(&name);
1890        self.send(BackendMessage::CloseComplete).await?;
1891        Ok(State::Ready)
1892    }
1893
1894    fn complete_portal(&mut self, name: &str) {
1895        let portal = self
1896            .adapter_client
1897            .session()
1898            .get_portal_unverified_mut(name)
1899            .expect("portal should exist");
1900        *portal.state = PortalState::Completed(None);
1901    }
1902
1903    async fn fetch(
1904        &mut self,
1905        name: String,
1906        count: Option<FetchDirection>,
1907        max_rows: ExecuteCount,
1908        fetch_portal_name: Option<String>,
1909        timeout: ExecuteTimeout,
1910        ctx_extra: ExecuteContextGuard,
1911    ) -> Result<State, io::Error> {
1912        // Unlike Execute, no count specified in FETCH returns 1 row, and 0 means 0
1913        // instead of All.
1914        let count = count.unwrap_or(FetchDirection::ForwardCount(1));
1915
1916        // Figure out how many rows we should send back by looking at the various
1917        // combinations of the execute and fetch.
1918        //
1919        // In Postgres, Fetch will cache <count> rows from the target portal and
1920        // return those as requested (if, say, an Execute message was sent with a
1921        // max_rows < the Fetch's count). We expect that case to be incredibly rare and
1922        // so have chosen to not support it until users request it. This eases
1923        // implementation difficulty since we don't have to be able to "send" rows to
1924        // a buffer.
1925        //
1926        // TODO(mjibson): Test this somehow? Need to divide up the pgtest files in
1927        // order to have some that are not Postgres compatible.
1928        let count = match (max_rows, count) {
1929            (ExecuteCount::Count(max_rows), FetchDirection::ForwardCount(count)) => {
1930                let count = usize::cast_from(count);
1931                if max_rows < count {
1932                    let msg = "Execute with max_rows < a FETCH's count is not supported";
1933                    self.adapter_client.retire_execute(
1934                        ctx_extra,
1935                        StatementEndedExecutionReason::Errored {
1936                            error: msg.to_string(),
1937                        },
1938                    );
1939                    return self
1940                        .send_error_and_get_state(ErrorResponse::error(
1941                            SqlState::FEATURE_NOT_SUPPORTED,
1942                            msg,
1943                        ))
1944                        .await;
1945                }
1946                ExecuteCount::Count(count)
1947            }
1948            (ExecuteCount::Count(_), FetchDirection::ForwardAll) => {
1949                let msg = "Execute with max_rows of a FETCH ALL is not supported";
1950                self.adapter_client.retire_execute(
1951                    ctx_extra,
1952                    StatementEndedExecutionReason::Errored {
1953                        error: msg.to_string(),
1954                    },
1955                );
1956                return self
1957                    .send_error_and_get_state(ErrorResponse::error(
1958                        SqlState::FEATURE_NOT_SUPPORTED,
1959                        msg,
1960                    ))
1961                    .await;
1962            }
1963            (ExecuteCount::All, FetchDirection::ForwardAll) => ExecuteCount::All,
1964            (ExecuteCount::All, FetchDirection::ForwardCount(count)) => {
1965                ExecuteCount::Count(usize::cast_from(count))
1966            }
1967        };
1968        let cursor_name = name.to_string();
1969        self.execute(
1970            cursor_name,
1971            count,
1972            fetch_message,
1973            fetch_portal_name,
1974            timeout,
1975            Some(ctx_extra),
1976            None,
1977        )
1978        .await
1979    }
1980
1981    async fn flush(&mut self) -> Result<State, io::Error> {
1982        self.conn.flush().await?;
1983        Ok(State::Ready)
1984    }
1985
1986    /// Sends a backend message to the client, after applying a severity filter.
1987    ///
1988    /// The message is only sent if its severity is above the severity set
1989    /// in the session, with the default value being NOTICE.
1990    #[instrument(level = "debug")]
1991    async fn send<M>(&mut self, message: M) -> Result<(), io::Error>
1992    where
1993        M: Into<BackendMessage>,
1994    {
1995        let message: BackendMessage = message.into();
1996        let is_error =
1997            matches!(&message, BackendMessage::ErrorResponse(e) if e.severity.is_error());
1998
1999        self.conn.send(message).await?;
2000
2001        // Flush immediately after sending an error response, as some clients
2002        // expect to be able to read the error response before sending a Sync
2003        // message. This is arguably in violation of the protocol specification,
2004        // but the specification is somewhat ambiguous, and easier to match
2005        // PostgreSQL here than to fix all the clients that have this
2006        // expectation.
2007        if is_error {
2008            self.conn.flush().await?;
2009        }
2010
2011        Ok(())
2012    }
2013
2014    #[instrument(level = "debug")]
2015    pub async fn send_all(
2016        &mut self,
2017        messages: impl IntoIterator<Item = BackendMessage>,
2018    ) -> Result<(), io::Error> {
2019        for m in messages {
2020            self.send(m).await?;
2021        }
2022        Ok(())
2023    }
2024
2025    #[instrument(level = "debug")]
2026    async fn sync(&mut self) -> Result<State, io::Error> {
2027        // Close the current transaction if we are in an implicit transaction.
2028        if self.adapter_client.session().transaction().is_implicit() {
2029            self.commit_transaction().await?;
2030        }
2031        self.ready().await
2032    }
2033
2034    #[instrument(level = "debug")]
2035    async fn ready(&mut self) -> Result<State, io::Error> {
2036        let txn_state = self.adapter_client.session().transaction().into();
2037        self.send(BackendMessage::ReadyForQuery(txn_state)).await?;
2038        self.flush().await
2039    }
2040
2041    #[allow(clippy::too_many_arguments)]
2042    #[instrument(level = "debug")]
2043    async fn send_execute_response(
2044        &mut self,
2045        response: ExecuteResponse,
2046        row_desc: Option<RelationDesc>,
2047        portal_name: String,
2048        max_rows: ExecuteCount,
2049        get_response: GetResponse,
2050        fetch_portal_name: Option<String>,
2051        timeout: ExecuteTimeout,
2052        execute_started: Instant,
2053    ) -> Result<State, io::Error> {
2054        let mut tag = response.tag();
2055
2056        macro_rules! command_complete {
2057            () => {{
2058                self.send(BackendMessage::CommandComplete {
2059                    tag: tag
2060                        .take()
2061                        .expect("command_complete only called on tag-generating results"),
2062                })
2063                .await?;
2064                Ok(State::Ready)
2065            }};
2066        }
2067
2068        let r = match response {
2069            ExecuteResponse::ClosedCursor => {
2070                self.complete_portal(&portal_name);
2071                command_complete!()
2072            }
2073            ExecuteResponse::DeclaredCursor => {
2074                self.complete_portal(&portal_name);
2075                command_complete!()
2076            }
2077            ExecuteResponse::EmptyQuery => {
2078                self.send(BackendMessage::EmptyQueryResponse).await?;
2079                Ok(State::Ready)
2080            }
2081            ExecuteResponse::Fetch {
2082                name,
2083                count,
2084                timeout,
2085                ctx_extra,
2086            } => {
2087                self.fetch(
2088                    name,
2089                    count,
2090                    max_rows,
2091                    Some(portal_name.to_string()),
2092                    timeout,
2093                    ctx_extra,
2094                )
2095                .await
2096            }
2097            ExecuteResponse::SendingRowsStreaming {
2098                rows,
2099                instance_id,
2100                strategy,
2101            } => {
2102                let row_desc = row_desc
2103                    .expect("missing row description for ExecuteResponse::SendingRowsStreaming");
2104
2105                let span = tracing::debug_span!("sending_rows_streaming");
2106
2107                self.send_rows(
2108                    row_desc,
2109                    portal_name,
2110                    InProgressRows::new(RecordFirstRowStream::new(
2111                        Box::new(rows),
2112                        execute_started,
2113                        &self.adapter_client,
2114                        Some(instance_id),
2115                        Some(strategy),
2116                    )),
2117                    max_rows,
2118                    get_response,
2119                    fetch_portal_name,
2120                    timeout,
2121                )
2122                .instrument(span)
2123                .await
2124                .map(|(state, _)| state)
2125            }
2126            ExecuteResponse::SendingRowsImmediate { rows } => {
2127                let row_desc = row_desc
2128                    .expect("missing row description for ExecuteResponse::SendingRowsImmediate");
2129
2130                let span = tracing::debug_span!("sending_rows_immediate");
2131
2132                let stream =
2133                    futures::stream::once(futures::future::ready(PeekResponseUnary::Rows(rows)));
2134                self.send_rows(
2135                    row_desc,
2136                    portal_name,
2137                    InProgressRows::new(RecordFirstRowStream::new(
2138                        Box::new(stream),
2139                        execute_started,
2140                        &self.adapter_client,
2141                        None,
2142                        Some(StatementExecutionStrategy::Constant),
2143                    )),
2144                    max_rows,
2145                    get_response,
2146                    fetch_portal_name,
2147                    timeout,
2148                )
2149                .instrument(span)
2150                .await
2151                .map(|(state, _)| state)
2152            }
2153            ExecuteResponse::SetVariable { name, .. } => {
2154                // This code is somewhat awkwardly structured because we
2155                // can't hold `var` across an await point.
2156                let qn = name.to_string();
2157                let msg = if let Some(var) = self
2158                    .adapter_client
2159                    .session()
2160                    .vars_mut()
2161                    .notify_set()
2162                    .find(|v| v.name() == qn)
2163                {
2164                    Some(BackendMessage::ParameterStatus(var.name(), var.value()))
2165                } else {
2166                    None
2167                };
2168                if let Some(msg) = msg {
2169                    self.send(msg).await?;
2170                }
2171                command_complete!()
2172            }
2173            ExecuteResponse::Subscribing {
2174                rx,
2175                ctx_extra,
2176                instance_id,
2177            } => {
2178                if fetch_portal_name.is_none() {
2179                    let mut msg = ErrorResponse::notice(
2180                        SqlState::WARNING,
2181                        "streaming SUBSCRIBE rows directly requires a client that does not buffer output",
2182                    );
2183                    if self.adapter_client.session().vars().application_name() == "psql" {
2184                        msg.hint = Some(
2185                            "Wrap your SUBSCRIBE statement in `COPY (SUBSCRIBE ...) TO STDOUT`."
2186                                .into(),
2187                        )
2188                    }
2189                    self.send(msg).await?;
2190                    self.conn.flush().await?;
2191                }
2192                let row_desc =
2193                    row_desc.expect("missing row description for ExecuteResponse::Subscribing");
2194                let (result, statement_ended_execution_reason) = match self
2195                    .send_rows(
2196                        row_desc,
2197                        portal_name,
2198                        InProgressRows::new(RecordFirstRowStream::new(
2199                            Box::new(UnboundedReceiverStream::new(rx)),
2200                            execute_started,
2201                            &self.adapter_client,
2202                            Some(instance_id),
2203                            None,
2204                        )),
2205                        max_rows,
2206                        get_response,
2207                        fetch_portal_name,
2208                        timeout,
2209                    )
2210                    .await
2211                {
2212                    Err(e) => {
2213                        // This is an error communicating with the connection.
2214                        // We consider that to be a cancelation, rather than a query error.
2215                        (Err(e), StatementEndedExecutionReason::Canceled)
2216                    }
2217                    Ok((ok, SendRowsEndedReason::Canceled)) => {
2218                        (Ok(ok), StatementEndedExecutionReason::Canceled)
2219                    }
2220                    Ok((
2221                        ok,
2222                        SendRowsEndedReason::Success {
2223                            result_size,
2224                            rows_returned,
2225                        },
2226                    )) => (
2227                        Ok(ok),
2228                        StatementEndedExecutionReason::Success {
2229                            result_size: Some(result_size),
2230                            rows_returned: Some(rows_returned),
2231                            execution_strategy: None,
2232                        },
2233                    ),
2234                    Ok((ok, SendRowsEndedReason::Errored { error })) => {
2235                        (Ok(ok), StatementEndedExecutionReason::Errored { error })
2236                    }
2237                };
2238                self.adapter_client
2239                    .retire_execute(ctx_extra, statement_ended_execution_reason);
2240                return result;
2241            }
2242            ExecuteResponse::CopyTo { format, resp } => {
2243                let row_desc =
2244                    row_desc.expect("missing row description for ExecuteResponse::CopyTo");
2245                match *resp {
2246                    ExecuteResponse::Subscribing {
2247                        rx,
2248                        ctx_extra,
2249                        instance_id,
2250                    } => {
2251                        let (result, statement_ended_execution_reason) = match self
2252                            .copy_rows(
2253                                format,
2254                                row_desc,
2255                                RecordFirstRowStream::new(
2256                                    Box::new(UnboundedReceiverStream::new(rx)),
2257                                    execute_started,
2258                                    &self.adapter_client,
2259                                    Some(instance_id),
2260                                    None,
2261                                ),
2262                            )
2263                            .await
2264                        {
2265                            Err(e) => {
2266                                // This is an error communicating with the connection.
2267                                // We consider that to be a cancelation, rather than a query error.
2268                                (Err(e), StatementEndedExecutionReason::Canceled)
2269                            }
2270                            Ok((
2271                                state,
2272                                SendRowsEndedReason::Success {
2273                                    result_size,
2274                                    rows_returned,
2275                                },
2276                            )) => (
2277                                Ok(state),
2278                                StatementEndedExecutionReason::Success {
2279                                    result_size: Some(result_size),
2280                                    rows_returned: Some(rows_returned),
2281                                    execution_strategy: None,
2282                                },
2283                            ),
2284                            Ok((state, SendRowsEndedReason::Errored { error })) => {
2285                                (Ok(state), StatementEndedExecutionReason::Errored { error })
2286                            }
2287                            Ok((state, SendRowsEndedReason::Canceled)) => {
2288                                (Ok(state), StatementEndedExecutionReason::Canceled)
2289                            }
2290                        };
2291                        self.adapter_client
2292                            .retire_execute(ctx_extra, statement_ended_execution_reason);
2293                        return result;
2294                    }
2295                    ExecuteResponse::SendingRowsStreaming {
2296                        rows,
2297                        instance_id,
2298                        strategy,
2299                    } => {
2300                        // We don't need to finalize execution here;
2301                        // it was already done in the
2302                        // coordinator. Just extract the state and
2303                        // return that.
2304                        return self
2305                            .copy_rows(
2306                                format,
2307                                row_desc,
2308                                RecordFirstRowStream::new(
2309                                    Box::new(rows),
2310                                    execute_started,
2311                                    &self.adapter_client,
2312                                    Some(instance_id),
2313                                    Some(strategy),
2314                                ),
2315                            )
2316                            .await
2317                            .map(|(state, _)| state);
2318                    }
2319                    ExecuteResponse::SendingRowsImmediate { rows } => {
2320                        let span = tracing::debug_span!("sending_rows_immediate");
2321
2322                        let rows = futures::stream::once(futures::future::ready(
2323                            PeekResponseUnary::Rows(rows),
2324                        ));
2325                        // We don't need to finalize execution here;
2326                        // it was already done in the
2327                        // coordinator. Just extract the state and
2328                        // return that.
2329                        return self
2330                            .copy_rows(
2331                                format,
2332                                row_desc,
2333                                RecordFirstRowStream::new(
2334                                    Box::new(rows),
2335                                    execute_started,
2336                                    &self.adapter_client,
2337                                    None,
2338                                    Some(StatementExecutionStrategy::Constant),
2339                                ),
2340                            )
2341                            .instrument(span)
2342                            .await
2343                            .map(|(state, _)| state);
2344                    }
2345                    _ => {
2346                        return self
2347                            .send_error_and_get_state(ErrorResponse::error(
2348                                SqlState::INTERNAL_ERROR,
2349                                "unsupported COPY response type".to_string(),
2350                            ))
2351                            .await;
2352                    }
2353                };
2354            }
2355            ExecuteResponse::CopyFrom {
2356                target_id,
2357                target_name,
2358                columns,
2359                params,
2360                ctx_extra,
2361            } => {
2362                let row_desc =
2363                    row_desc.expect("missing row description for ExecuteResponse::CopyFrom");
2364                self.copy_from(target_id, target_name, columns, params, row_desc, ctx_extra)
2365                    .await
2366            }
2367            ExecuteResponse::TransactionCommitted { params }
2368            | ExecuteResponse::TransactionRolledBack { params } => {
2369                let notify_set: mz_ore::collections::HashSet<String> = self
2370                    .adapter_client
2371                    .session()
2372                    .vars()
2373                    .notify_set()
2374                    .map(|v| v.name().to_string())
2375                    .collect();
2376
2377                // Only report on parameters that are in the notify set.
2378                for (name, value) in params
2379                    .into_iter()
2380                    .filter(|(name, _v)| notify_set.contains(*name))
2381                {
2382                    let msg = BackendMessage::ParameterStatus(name, value);
2383                    self.send(msg).await?;
2384                }
2385                command_complete!()
2386            }
2387
2388            ExecuteResponse::AlteredDefaultPrivileges
2389            | ExecuteResponse::AlteredObject(..)
2390            | ExecuteResponse::AlteredRole
2391            | ExecuteResponse::AlteredSystemConfiguration
2392            | ExecuteResponse::CreatedCluster { .. }
2393            | ExecuteResponse::CreatedClusterReplica { .. }
2394            | ExecuteResponse::CreatedConnection { .. }
2395            | ExecuteResponse::CreatedDatabase { .. }
2396            | ExecuteResponse::CreatedIndex { .. }
2397            | ExecuteResponse::CreatedIntrospectionSubscribe
2398            | ExecuteResponse::CreatedMaterializedView { .. }
2399            | ExecuteResponse::CreatedRole
2400            | ExecuteResponse::CreatedSchema { .. }
2401            | ExecuteResponse::CreatedSecret { .. }
2402            | ExecuteResponse::CreatedSink { .. }
2403            | ExecuteResponse::CreatedSource { .. }
2404            | ExecuteResponse::CreatedTable { .. }
2405            | ExecuteResponse::CreatedType
2406            | ExecuteResponse::CreatedView { .. }
2407            | ExecuteResponse::CreatedViews { .. }
2408            | ExecuteResponse::CreatedNetworkPolicy
2409            | ExecuteResponse::Comment
2410            | ExecuteResponse::Deallocate { .. }
2411            | ExecuteResponse::Deleted(..)
2412            | ExecuteResponse::DiscardedAll
2413            | ExecuteResponse::DiscardedTemp
2414            | ExecuteResponse::DroppedObject(_)
2415            | ExecuteResponse::DroppedOwned
2416            | ExecuteResponse::GrantedPrivilege
2417            | ExecuteResponse::GrantedRole
2418            | ExecuteResponse::Inserted(..)
2419            | ExecuteResponse::Copied(..)
2420            | ExecuteResponse::Prepare
2421            | ExecuteResponse::Raised
2422            | ExecuteResponse::ReassignOwned
2423            | ExecuteResponse::RevokedPrivilege
2424            | ExecuteResponse::RevokedRole
2425            | ExecuteResponse::StartedTransaction { .. }
2426            | ExecuteResponse::Updated(..)
2427            | ExecuteResponse::ValidatedConnection => {
2428                command_complete!()
2429            }
2430        };
2431
2432        assert_none!(tag, "tag created but not consumed: {:?}", tag);
2433        r
2434    }
2435
2436    #[allow(clippy::too_many_arguments)]
2437    // TODO(guswynn): figure out how to get it to compile without skip_all
2438    #[mz_ore::instrument(level = "debug")]
2439    async fn send_rows(
2440        &mut self,
2441        row_desc: RelationDesc,
2442        portal_name: String,
2443        mut rows: InProgressRows,
2444        max_rows: ExecuteCount,
2445        get_response: GetResponse,
2446        fetch_portal_name: Option<String>,
2447        timeout: ExecuteTimeout,
2448    ) -> Result<(State, SendRowsEndedReason), io::Error> {
2449        // If this portal is being executed from a FETCH then we need to use the result
2450        // format type of the outer portal.
2451        let result_format_portal_name: &str = if let Some(ref name) = fetch_portal_name {
2452            name
2453        } else {
2454            &portal_name
2455        };
2456        let result_formats = self
2457            .adapter_client
2458            .session()
2459            .get_portal_unverified(result_format_portal_name)
2460            .expect("valid fetch portal name for send rows")
2461            .result_formats
2462            .clone();
2463
2464        let (mut wait_once, mut deadline) = match timeout {
2465            ExecuteTimeout::None => (false, None),
2466            ExecuteTimeout::Seconds(t) => (
2467                false,
2468                Some(tokio::time::Instant::now() + tokio::time::Duration::from_secs_f64(t)),
2469            ),
2470            ExecuteTimeout::WaitOnce => (true, None),
2471        };
2472
2473        // Sanity check that the various `RelationDesc`s match up.
2474        {
2475            let portal_name_desc = &self
2476                .adapter_client
2477                .session()
2478                .get_portal_unverified(portal_name.as_str())
2479                .expect("portal should exist")
2480                .desc
2481                .relation_desc;
2482            if let Some(portal_name_desc) = portal_name_desc {
2483                soft_assert_eq_or_log!(portal_name_desc, &row_desc);
2484            }
2485            if let Some(fetch_portal_name) = &fetch_portal_name {
2486                let fetch_portal_desc = &self
2487                    .adapter_client
2488                    .session()
2489                    .get_portal_unverified(fetch_portal_name)
2490                    .expect("portal should exist")
2491                    .desc
2492                    .relation_desc;
2493                if let Some(fetch_portal_desc) = fetch_portal_desc {
2494                    soft_assert_eq_or_log!(fetch_portal_desc, &row_desc);
2495                }
2496            }
2497        }
2498
2499        self.conn.set_encode_state(
2500            row_desc
2501                .typ()
2502                .column_types
2503                .iter()
2504                .map(|ty| mz_pgrepr::Type::from(&ty.scalar_type))
2505                .zip_eq(result_formats)
2506                .collect(),
2507        );
2508
2509        let mut total_sent_rows = 0;
2510        let mut total_sent_bytes = 0;
2511        // want_rows is the maximum number of rows the client wants.
2512        let mut want_rows = match max_rows {
2513            ExecuteCount::All => usize::MAX,
2514            ExecuteCount::Count(count) => count,
2515        };
2516
2517        // Send rows while the client still wants them and there are still rows to send.
2518        loop {
2519            // Fetch next batch of rows, waiting for a possible requested
2520            // timeout or notice.
2521            let batch = if rows.current.is_some() {
2522                FetchResult::Rows(rows.current.take())
2523            } else if want_rows == 0 {
2524                FetchResult::Rows(None)
2525            } else {
2526                let notice_fut = self.adapter_client.session().recv_notice();
2527                // Biased: drain available data before checking the deadline.
2528                // This is critical for the WaitOnce case, where the deadline
2529                // is set to `Instant::now()` right after the first batch:
2530                // without `biased`, `recv()` and the already-expired deadline
2531                // race nondeterministically, so we might break the loop
2532                // before `no_more_rows` is set (or even before ready rows
2533                // are consumed). With an explicit `TIMEOUT`, missing a batch
2534                // right at the boundary is acceptable, but WaitOnce fires
2535                // immediately and the race is not.
2536                //
2537                // Trade-off: if `recv()` keeps returning Ready (unlikely in
2538                // practice—row processing + flush is slower than upstream
2539                // tick granularity), a `TIMEOUT` deadline could be delayed.
2540                // See database-issues#9470.
2541                tokio::select! {
2542                    biased;
2543                    err = self.conn.wait_closed() => return Err(err),
2544                    batch = rows.remaining.recv() => match batch {
2545                        None => FetchResult::Rows(None),
2546                        Some(PeekResponseUnary::Rows(rows)) => FetchResult::Rows(Some(rows)),
2547                        Some(PeekResponseUnary::Error(err)) => {
2548                            FetchResult::Error(ErrorResponse::error(SqlState::INTERNAL_ERROR, err))
2549                        }
2550                        Some(PeekResponseUnary::DependencyDropped(dep)) => {
2551                            FetchResult::Error(
2552                                dep.to_concurrent_dependency_drop()
2553                                    .into_response(Severity::Error),
2554                            )
2555                        }
2556                        Some(PeekResponseUnary::Canceled) => FetchResult::Canceled,
2557                    },
2558                    notice = notice_fut => {
2559                        FetchResult::Notice(notice)
2560                    }
2561                    _ = time::sleep_until(
2562                        deadline.unwrap_or_else(tokio::time::Instant::now),
2563                    ), if deadline.is_some() => FetchResult::Rows(None),
2564                }
2565            };
2566
2567            match batch {
2568                FetchResult::Rows(None) => break,
2569                FetchResult::Rows(Some(mut batch_rows)) => {
2570                    if let Err(err) = verify_datum_desc(&row_desc, &mut batch_rows) {
2571                        let msg = err.to_string();
2572                        return self
2573                            .send_error_and_get_state(err.into_response(Severity::Error))
2574                            .await
2575                            .map(|state| (state, SendRowsEndedReason::Errored { error: msg }));
2576                    }
2577
2578                    // If wait_once is true: the first time this fn is called it blocks (same as
2579                    // deadline == None). The second time this fn is called it should behave the
2580                    // same a 0s timeout.
2581                    if wait_once && batch_rows.peek().is_some() {
2582                        deadline = Some(tokio::time::Instant::now());
2583                        wait_once = false;
2584                    }
2585
2586                    // Send a portion of the rows.
2587                    let mut sent_rows = 0;
2588                    let mut sent_bytes = 0;
2589                    let messages = (&mut batch_rows)
2590                        // TODO(parkmycar): This is a fair bit of juggling between iterator types
2591                        // to count the total number of bytes. Alternatively we could track the
2592                        // total sent bytes in this .map(...) call, but having side effects in map
2593                        // is a code smell.
2594                        .map(|row| {
2595                            let row_len = row.byte_len();
2596                            let values = mz_pgrepr::values_from_row(row, row_desc.typ());
2597                            (row_len, BackendMessage::DataRow(values))
2598                        })
2599                        .inspect(|(row_len, _)| {
2600                            sent_bytes += row_len;
2601                            sent_rows += 1
2602                        })
2603                        .map(|(_row_len, row)| row)
2604                        .take(want_rows);
2605                    self.send_all(messages).await?;
2606
2607                    total_sent_rows += sent_rows;
2608                    total_sent_bytes += sent_bytes;
2609                    want_rows -= sent_rows;
2610
2611                    // If we have sent the number of requested rows, put the remainder of the batch
2612                    // (if any) back and stop sending.
2613                    if want_rows == 0 {
2614                        if batch_rows.peek().is_some() {
2615                            rows.current = Some(batch_rows);
2616                        }
2617                        break;
2618                    }
2619
2620                    self.conn.flush().await?;
2621                }
2622                FetchResult::Notice(notice) => {
2623                    self.send(notice.into_response()).await?;
2624                    self.conn.flush().await?;
2625                }
2626                FetchResult::Error(err) => {
2627                    let text = err.message.clone();
2628                    return self
2629                        .send_error_and_get_state(err)
2630                        .await
2631                        .map(|state| (state, SendRowsEndedReason::Errored { error: text }));
2632                }
2633                FetchResult::Canceled => {
2634                    return self
2635                        .send_error_and_get_state(ErrorResponse::error(
2636                            SqlState::QUERY_CANCELED,
2637                            "canceling statement due to user request",
2638                        ))
2639                        .await
2640                        .map(|state| (state, SendRowsEndedReason::Canceled));
2641                }
2642            }
2643        }
2644
2645        let portal = self
2646            .adapter_client
2647            .session()
2648            .get_portal_unverified_mut(&portal_name)
2649            .expect("valid portal name for send rows");
2650
2651        let saw_rows = rows.remaining.saw_rows;
2652        let no_more_rows = rows.no_more_rows();
2653        let metric_recorded = rows.remaining.metric_recorded;
2654        let recorded_first_row_instant = rows.remaining.recorded_first_row_instant;
2655
2656        if no_more_rows && !metric_recorded {
2657            rows.remaining.metric_recorded = true;
2658        }
2659
2660        // Always return rows back, even if it's empty. This prevents an unclosed
2661        // portal from re-executing after it has been emptied.
2662        *portal.state = PortalState::InProgress(Some(rows));
2663
2664        let fetch_portal = fetch_portal_name.map(|name| {
2665            self.adapter_client
2666                .session()
2667                .get_portal_unverified_mut(&name)
2668                .expect("valid fetch portal")
2669        });
2670        let response_message = get_response(max_rows, total_sent_rows, fetch_portal);
2671        self.send(response_message).await?;
2672
2673        // Attend to metrics if there are no more rows. Only record once per stream
2674        // to avoid polluting the histogram when an exhausted cursor is FETCHed again.
2675        if no_more_rows && !metric_recorded {
2676            let statement_type = if let Some(stmt) = &self
2677                .adapter_client
2678                .session()
2679                .get_portal_unverified(&portal_name)
2680                .expect("valid portal name for send_rows")
2681                .stmt
2682            {
2683                metrics::statement_type_label_value(stmt.deref())
2684            } else {
2685                "no-statement"
2686            };
2687            let duration = if saw_rows {
2688                recorded_first_row_instant
2689                    .expect("recorded_first_row_instant because saw_rows")
2690                    .elapsed()
2691            } else {
2692                // If the result is empty, then we define time from first to last row as 0.
2693                // (Note that, currently, an empty result involves a PeekResponse with 0 rows, which
2694                // does flip `saw_rows`, so this code path is currently not exercised.)
2695                Duration::ZERO
2696            };
2697            self.adapter_client
2698                .inner()
2699                .metrics()
2700                .result_rows_first_to_last_byte_seconds
2701                .with_label_values(&[statement_type])
2702                .observe(duration.as_secs_f64());
2703        }
2704
2705        Ok((
2706            State::Ready,
2707            SendRowsEndedReason::Success {
2708                result_size: u64::cast_from(total_sent_bytes),
2709                rows_returned: u64::cast_from(total_sent_rows),
2710            },
2711        ))
2712    }
2713
2714    #[mz_ore::instrument(level = "debug")]
2715    async fn copy_rows(
2716        &mut self,
2717        format: CopyFormat,
2718        row_desc: RelationDesc,
2719        mut stream: RecordFirstRowStream,
2720    ) -> Result<(State, SendRowsEndedReason), io::Error> {
2721        let (row_format, encode_format) = match format {
2722            CopyFormat::Text => (
2723                CopyFormatParams::Text(CopyTextFormatParams::default()),
2724                Format::Text,
2725            ),
2726            CopyFormat::Binary => (CopyFormatParams::Binary, Format::Binary),
2727            CopyFormat::Csv => (
2728                CopyFormatParams::Csv(CopyCsvFormatParams::default()),
2729                Format::Text,
2730            ),
2731            CopyFormat::Parquet => {
2732                let text = "Parquet format is not supported".to_string();
2733                return self
2734                    .send_error_and_get_state(ErrorResponse::error(
2735                        SqlState::INTERNAL_ERROR,
2736                        text.clone(),
2737                    ))
2738                    .await
2739                    .map(|state| (state, SendRowsEndedReason::Errored { error: text }));
2740            }
2741        };
2742
2743        // Binary encoding is not implemented for some types (e.g., list, map,
2744        // and aclitem). Unlike the extended query protocol's Bind handler, COPY
2745        // does not validate this when binding the portal: the portal's result
2746        // formats describe the `CopyData` wrapper, not the COPY format itself,
2747        // so the Bind handler explicitly skips `COPY TO` statements. We must
2748        // therefore check here, before streaming any rows, otherwise
2749        // `encode_binary` would panic mid-stream (SQL-323).
2750        if let CopyFormat::Binary = format {
2751            if let Some(msg) = row_desc
2752                .iter_types()
2753                .find_map(|ty| mz_pgrepr::Value::binary_encoding_error(&ty.scalar_type).err())
2754            {
2755                return self
2756                    .send_error_and_get_state(ErrorResponse::error(
2757                        SqlState::UNDEFINED_FUNCTION,
2758                        msg,
2759                    ))
2760                    .await
2761                    .map(|state| {
2762                        (
2763                            state,
2764                            SendRowsEndedReason::Errored {
2765                                error: msg.to_string(),
2766                            },
2767                        )
2768                    });
2769            }
2770        }
2771
2772        let encode_fn = |row: &RowRef, typ: &SqlRelationType, out: &mut Vec<u8>| {
2773            mz_pgcopy::encode_copy_format(&row_format, row, typ, out)
2774        };
2775
2776        let typ = row_desc.typ();
2777        let column_formats = iter::repeat(encode_format)
2778            .take(typ.column_types.len())
2779            .collect();
2780        self.send(BackendMessage::CopyOutResponse {
2781            overall_format: encode_format,
2782            column_formats,
2783        })
2784        .await?;
2785
2786        // In Postgres, binary copy has a header that is followed (in the same
2787        // CopyData) by the first row. In order to replicate their behavior, use a
2788        // common vec that we can extend one time now and then fill up with the encode
2789        // functions.
2790        let mut out = Vec::new();
2791
2792        if let CopyFormat::Binary = format {
2793            // 11-byte signature.
2794            out.extend(b"PGCOPY\n\xFF\r\n\0");
2795            // 32-bit flags field.
2796            out.extend([0, 0, 0, 0]);
2797            // 32-bit header extension length field.
2798            out.extend([0, 0, 0, 0]);
2799        }
2800
2801        let mut count = 0;
2802        let mut total_sent_bytes = 0;
2803        loop {
2804            tokio::select! {
2805                e = self.conn.wait_closed() => return Err(e),
2806                batch = stream.recv() => match batch {
2807                    None => break,
2808                    Some(PeekResponseUnary::Error(text)) => {
2809                        let err =
2810                            ErrorResponse::error(SqlState::INTERNAL_ERROR, text.clone());
2811                        return self
2812                            .send_error_and_get_state(err)
2813                            .await
2814                            .map(|state| (state, SendRowsEndedReason::Errored { error: text }));
2815                    }
2816                    Some(PeekResponseUnary::DependencyDropped(dep)) => {
2817                        let err = dep.to_concurrent_dependency_drop();
2818                        let text = err.to_string();
2819                        let resp = err.into_response(Severity::Error);
2820                        return self
2821                            .send_error_and_get_state(resp)
2822                            .await
2823                            .map(|state| (state, SendRowsEndedReason::Errored { error: text }));
2824                    }
2825                    Some(PeekResponseUnary::Canceled) => {
2826                        return self.send_error_and_get_state(ErrorResponse::error(
2827                                SqlState::QUERY_CANCELED,
2828                                "canceling statement due to user request",
2829                            ))
2830                            .await.map(|state| (state, SendRowsEndedReason::Canceled));
2831                    }
2832                    Some(PeekResponseUnary::Rows(mut rows)) => {
2833                        count += rows.count();
2834                        while let Some(row) = rows.next() {
2835                            total_sent_bytes += row.byte_len();
2836                            encode_fn(row, typ, &mut out)?;
2837                            self.send(BackendMessage::CopyData(mem::take(&mut out)))
2838                                .await?;
2839                        }
2840                    }
2841                },
2842                notice = self.adapter_client.session().recv_notice() => {
2843                    self.send(notice.into_response())
2844                        .await?;
2845                    self.conn.flush().await?;
2846                }
2847            }
2848
2849            self.conn.flush().await?;
2850        }
2851        // Send required trailers.
2852        if let CopyFormat::Binary = format {
2853            let trailer: i16 = -1;
2854            out.extend(trailer.to_be_bytes());
2855            self.send(BackendMessage::CopyData(mem::take(&mut out)))
2856                .await?;
2857        }
2858
2859        let tag = format!("COPY {}", count);
2860        self.send(BackendMessage::CopyDone).await?;
2861        self.send(BackendMessage::CommandComplete { tag }).await?;
2862        Ok((
2863            State::Ready,
2864            SendRowsEndedReason::Success {
2865                result_size: u64::cast_from(total_sent_bytes),
2866                rows_returned: u64::cast_from(count),
2867            },
2868        ))
2869    }
2870
2871    /// Handles the copy-in mode of the postgres protocol from transferring
2872    /// data to the server.
2873    #[instrument(level = "debug")]
2874    async fn copy_from(
2875        &mut self,
2876        target_id: CatalogItemId,
2877        target_name: String,
2878        columns: Vec<ColumnIndex>,
2879        params: CopyFormatParams<'static>,
2880        row_desc: RelationDesc,
2881        mut ctx_extra: ExecuteContextGuard,
2882    ) -> Result<State, io::Error> {
2883        let res = self
2884            .copy_from_inner(
2885                target_id,
2886                target_name,
2887                columns,
2888                params,
2889                row_desc,
2890                &mut ctx_extra,
2891            )
2892            .await;
2893        match &res {
2894            Ok(State::Ready) => {
2895                self.adapter_client.retire_execute(
2896                    ctx_extra,
2897                    StatementEndedExecutionReason::Success {
2898                        result_size: None,
2899                        rows_returned: None,
2900                        execution_strategy: None,
2901                    },
2902                );
2903            }
2904            Ok(State::Done) => {
2905                // The connection closed gracefully without sending us a `CopyDone`,
2906                // causing us to just drop the copy request.
2907                // For the purposes of statement logging, we count this as a cancellation.
2908                self.adapter_client
2909                    .retire_execute(ctx_extra, StatementEndedExecutionReason::Canceled);
2910            }
2911            Err(e) => {
2912                self.adapter_client.retire_execute(
2913                    ctx_extra,
2914                    StatementEndedExecutionReason::Errored {
2915                        error: format!("{e}"),
2916                    },
2917                );
2918            }
2919            Ok(State::Drain) => {}
2920        }
2921        res
2922    }
2923
2924    async fn copy_from_inner(
2925        &mut self,
2926        target_id: CatalogItemId,
2927        target_name: String,
2928        columns: Vec<ColumnIndex>,
2929        params: CopyFormatParams<'static>,
2930        row_desc: RelationDesc,
2931        ctx_extra: &mut ExecuteContextGuard,
2932    ) -> Result<State, io::Error> {
2933        let typ = row_desc.typ();
2934        let column_formats = vec![Format::Text; typ.column_types.len()];
2935        self.send(BackendMessage::CopyInResponse {
2936            overall_format: Format::Text,
2937            column_formats,
2938        })
2939        .await?;
2940        self.conn.flush().await?;
2941
2942        // Set up the parallel streaming batch builders in the coordinator.
2943        let writer = match self
2944            .adapter_client
2945            .start_copy_from_stdin(
2946                target_id,
2947                target_name.clone(),
2948                columns.clone(),
2949                row_desc.clone(),
2950                params.clone(),
2951            )
2952            .await
2953        {
2954            Ok(writer) => writer,
2955            Err(e) => {
2956                // Drain remaining CopyData/CopyDone/CopyFail messages from the
2957                // socket. Since CopyInResponse was already sent, the client may
2958                // have pipelined copy data that we must consume before returning
2959                // the error, otherwise they'd be misinterpreted as top-level
2960                // protocol messages and cause a deadlock.
2961                loop {
2962                    match self.conn.recv().await? {
2963                        Some(FrontendMessage::CopyData(_)) => {}
2964                        Some(FrontendMessage::CopyDone) | Some(FrontendMessage::CopyFail(_)) => {
2965                            break;
2966                        }
2967                        Some(FrontendMessage::Flush) | Some(FrontendMessage::Sync) => {}
2968                        Some(_) => break,
2969                        None => return Ok(State::Done),
2970                    }
2971                }
2972                self.adapter_client.retire_execute(
2973                    std::mem::take(ctx_extra),
2974                    StatementEndedExecutionReason::Errored {
2975                        error: e.to_string(),
2976                    },
2977                );
2978                return self
2979                    .send_error_and_get_state(e.into_response(Severity::Error))
2980                    .await;
2981            }
2982        };
2983
2984        // Enable copy mode on the codec to skip aggregate buffer size checks.
2985        self.conn.set_copy_mode(true);
2986
2987        // Batch size for splitting raw data across parallel workers (~32MB).
2988        const BATCH_SIZE: usize = 32 * 1024 * 1024;
2989        let max_copy_from_row_size = self
2990            .adapter_client
2991            .get_system_vars()
2992            .await
2993            .max_copy_from_row_size()
2994            .try_into()
2995            .unwrap_or(usize::MAX);
2996
2997        let mut data = Vec::new();
2998        let mut row_scanner = CopyRowScanner::new(&params);
2999        let num_workers = writer.batch_txs.len();
3000        let mut next_worker: usize = 0;
3001        let mut saw_copy_done = false;
3002        let mut saw_end_marker = false;
3003        let mut copy_from_error: Option<(SqlState, String)> = None;
3004
3005        // Receive loop: accumulate CopyData, split at row boundaries,
3006        // round-robin raw chunks to parallel batch builder workers.
3007        loop {
3008            let message = self.conn.recv().await?;
3009            match message {
3010                Some(FrontendMessage::CopyData(buf)) => {
3011                    if saw_end_marker {
3012                        // Per PostgreSQL COPY behavior, ignore all bytes after
3013                        // the end-of-copy marker until CopyDone.
3014                        continue;
3015                    }
3016                    data.extend(buf);
3017                    row_scanner.scan_new_bytes(&data);
3018
3019                    if let Some(end_pos) = row_scanner.end_marker_end() {
3020                        data.truncate(end_pos);
3021                        row_scanner.on_truncate(end_pos);
3022                        saw_end_marker = true;
3023                    }
3024
3025                    // Guard against pathological single rows that never terminate.
3026                    if row_scanner.current_row_size(data.len()) > max_copy_from_row_size {
3027                        copy_from_error = Some((
3028                            SqlState::INSUFFICIENT_RESOURCES,
3029                            format!(
3030                                "COPY FROM STDIN row exceeded max_copy_from_row_size \
3031                                 ({max_copy_from_row_size} bytes)"
3032                            ),
3033                        ));
3034                        break;
3035                    }
3036
3037                    // When buffer exceeds batch size, split at the last complete row
3038                    // and send the complete rows chunk to the next worker.
3039                    let mut send_failed = false;
3040                    while data.len() >= BATCH_SIZE {
3041                        let split_pos = match row_scanner.last_row_end() {
3042                            Some(pos) => pos,
3043                            None => break, // no complete row yet
3044                        };
3045                        let remainder = data.split_off(split_pos);
3046                        let chunk = std::mem::replace(&mut data, remainder);
3047                        row_scanner.on_split(split_pos);
3048                        if writer.batch_txs[next_worker].send(chunk).await.is_err() {
3049                            send_failed = true;
3050                            break;
3051                        }
3052                        next_worker = (next_worker + 1) % num_workers;
3053                    }
3054                    // Worker dropped (likely errored) — stop sending,
3055                    // fall through to completion_rx for the real error.
3056                    if send_failed {
3057                        break;
3058                    }
3059                }
3060                Some(FrontendMessage::CopyDone) => {
3061                    // Send any remaining data to the next worker.
3062                    if !data.is_empty() {
3063                        let chunk = std::mem::take(&mut data);
3064                        // Ignore send failure — completion_rx will have the error.
3065                        let _ = writer.batch_txs[next_worker].send(chunk).await;
3066                    }
3067                    saw_copy_done = true;
3068                    break;
3069                }
3070                Some(FrontendMessage::CopyFail(err)) => {
3071                    self.adapter_client.retire_execute(
3072                        std::mem::take(ctx_extra),
3073                        StatementEndedExecutionReason::Canceled,
3074                    );
3075                    // Drop the writer to signal cancellation to the background tasks.
3076                    drop(writer);
3077                    self.conn.set_copy_mode(false);
3078                    return self
3079                        .send_error_and_get_state(ErrorResponse::error(
3080                            SqlState::QUERY_CANCELED,
3081                            format!("COPY from stdin failed: {}", err),
3082                        ))
3083                        .await;
3084                }
3085                Some(FrontendMessage::Flush) | Some(FrontendMessage::Sync) => {}
3086                Some(_) => {
3087                    let msg = "unexpected message type during COPY from stdin";
3088                    self.adapter_client.retire_execute(
3089                        std::mem::take(ctx_extra),
3090                        StatementEndedExecutionReason::Errored {
3091                            error: msg.to_string(),
3092                        },
3093                    );
3094                    drop(writer);
3095                    self.conn.set_copy_mode(false);
3096                    return self
3097                        .send_error_and_get_state(ErrorResponse::error(
3098                            SqlState::PROTOCOL_VIOLATION,
3099                            msg,
3100                        ))
3101                        .await;
3102                }
3103                None => {
3104                    drop(writer);
3105                    self.conn.set_copy_mode(false);
3106                    return Ok(State::Done);
3107                }
3108            }
3109        }
3110
3111        // If we exited the receive loop before seeing `CopyDone` (e.g. because
3112        // a worker failed and dropped its channel), keep draining COPY input to
3113        // avoid desynchronizing the protocol state machine.
3114        if !saw_copy_done {
3115            loop {
3116                match self.conn.recv().await? {
3117                    Some(FrontendMessage::CopyData(_)) => {}
3118                    Some(FrontendMessage::CopyDone) | Some(FrontendMessage::CopyFail(_)) => {
3119                        break;
3120                    }
3121                    Some(FrontendMessage::Flush) | Some(FrontendMessage::Sync) => {}
3122                    Some(_) => {
3123                        let msg = "unexpected message type during COPY from stdin";
3124                        self.adapter_client.retire_execute(
3125                            std::mem::take(ctx_extra),
3126                            StatementEndedExecutionReason::Errored {
3127                                error: msg.to_string(),
3128                            },
3129                        );
3130                        drop(writer);
3131                        self.conn.set_copy_mode(false);
3132                        return self
3133                            .send_error_and_get_state(ErrorResponse::error(
3134                                SqlState::PROTOCOL_VIOLATION,
3135                                msg,
3136                            ))
3137                            .await;
3138                    }
3139                    None => {
3140                        drop(writer);
3141                        self.conn.set_copy_mode(false);
3142                        return Ok(State::Done);
3143                    }
3144                }
3145            }
3146        }
3147
3148        if let Some((code, msg)) = copy_from_error {
3149            self.adapter_client.retire_execute(
3150                std::mem::take(ctx_extra),
3151                StatementEndedExecutionReason::Errored { error: msg.clone() },
3152            );
3153            drop(writer);
3154            self.conn.set_copy_mode(false);
3155            return self
3156                .send_error_and_get_state(ErrorResponse::error(code, msg))
3157                .await;
3158        }
3159
3160        self.conn.set_copy_mode(false);
3161
3162        // Drop all senders to signal EOF to the background batch builders.
3163        // If copy_err is set, a worker already failed — dropping the senders
3164        // will cause remaining workers to stop, and we'll get the real error
3165        // from completion_rx below.
3166        drop(writer.batch_txs);
3167
3168        // Wait for all parallel workers to finish building batches.
3169        let (proto_batches, row_count) = match writer.completion_rx.await {
3170            Ok(Ok(result)) => result,
3171            Ok(Err(e)) => {
3172                self.adapter_client.retire_execute(
3173                    std::mem::take(ctx_extra),
3174                    StatementEndedExecutionReason::Errored {
3175                        error: e.to_string(),
3176                    },
3177                );
3178                return self
3179                    .send_error_and_get_state(e.into_response(Severity::Error))
3180                    .await;
3181            }
3182            Err(_) => {
3183                let msg = "COPY FROM STDIN: background batch builder tasks dropped";
3184                self.adapter_client.retire_execute(
3185                    std::mem::take(ctx_extra),
3186                    StatementEndedExecutionReason::Errored {
3187                        error: msg.to_string(),
3188                    },
3189                );
3190                return self
3191                    .send_error_and_get_state(ErrorResponse::error(SqlState::INTERNAL_ERROR, msg))
3192                    .await;
3193            }
3194        };
3195
3196        // Stage all batches in the session's transaction for atomic commit.
3197        if let Err(e) = self
3198            .adapter_client
3199            .stage_copy_from_stdin_batches(target_id, proto_batches)
3200        {
3201            self.adapter_client.retire_execute(
3202                std::mem::take(ctx_extra),
3203                StatementEndedExecutionReason::Errored {
3204                    error: e.to_string(),
3205                },
3206            );
3207            return self
3208                .send_error_and_get_state(e.into_response(Severity::Error))
3209                .await;
3210        }
3211
3212        let tag = format!("COPY {}", row_count);
3213        self.send(BackendMessage::CommandComplete { tag }).await?;
3214
3215        Ok(State::Ready)
3216    }
3217
3218    #[instrument(level = "debug")]
3219    async fn send_pending_notices(&mut self) -> Result<(), io::Error> {
3220        let notices = self
3221            .adapter_client
3222            .session()
3223            .drain_notices()
3224            .into_iter()
3225            .map(|notice| BackendMessage::ErrorResponse(notice.into_response()));
3226        self.send_all(notices).await?;
3227        Ok(())
3228    }
3229
3230    #[instrument(level = "debug")]
3231    async fn send_error_and_get_state(&mut self, err: ErrorResponse) -> Result<State, io::Error> {
3232        assert!(err.severity.is_error());
3233        debug!(
3234            "cid={} error code={}",
3235            self.adapter_client.session().conn_id(),
3236            err.code.code()
3237        );
3238        let is_fatal = err.severity.is_fatal();
3239        self.send(BackendMessage::ErrorResponse(err)).await?;
3240
3241        let txn = self.adapter_client.session().transaction();
3242        match txn {
3243            // Error can be called from describe and parse and so might not be in an active
3244            // transaction.
3245            TransactionStatus::Default | TransactionStatus::Failed(_) => {}
3246            // In Started (i.e., a single statement), cleanup ourselves.
3247            TransactionStatus::Started(_) => {
3248                self.rollback_transaction().await?;
3249            }
3250            // Implicit transactions also clear themselves.
3251            TransactionStatus::InTransactionImplicit(_) => {
3252                self.rollback_transaction().await?;
3253            }
3254            // Explicit transactions move to failed.
3255            TransactionStatus::InTransaction(_) => {
3256                self.adapter_client.fail_transaction();
3257            }
3258        };
3259        if is_fatal {
3260            Ok(State::Done)
3261        } else {
3262            Ok(State::Drain)
3263        }
3264    }
3265
3266    #[instrument(level = "debug")]
3267    async fn aborted_txn_error(&mut self) -> Result<State, io::Error> {
3268        self.send(BackendMessage::ErrorResponse(ErrorResponse::error(
3269            SqlState::IN_FAILED_SQL_TRANSACTION,
3270            ABORTED_TXN_MSG,
3271        )))
3272        .await?;
3273        Ok(State::Drain)
3274    }
3275
3276    fn is_aborted_txn(&mut self) -> bool {
3277        matches!(
3278            self.adapter_client.session().transaction(),
3279            TransactionStatus::Failed(_)
3280        )
3281    }
3282}
3283
3284fn pad_formats(formats: Vec<Format>, n: usize) -> Result<Vec<Format>, String> {
3285    match (formats.len(), n) {
3286        (0, e) => Ok(vec![Format::Text; e]),
3287        (1, e) => Ok(iter::repeat(formats[0]).take(e).collect()),
3288        (a, e) if a == e => Ok(formats),
3289        (a, e) => Err(format!(
3290            "expected {} field format specifiers, but got {}",
3291            e, a
3292        )),
3293    }
3294}
3295
3296fn describe_rows(stmt_desc: &StatementDesc, formats: &[Format]) -> BackendMessage {
3297    match &stmt_desc.relation_desc {
3298        Some(desc) if !stmt_desc.is_copy => {
3299            BackendMessage::RowDescription(message::encode_row_description(desc, formats))
3300        }
3301        _ => BackendMessage::NoData,
3302    }
3303}
3304
3305type GetResponse = fn(
3306    max_rows: ExecuteCount,
3307    total_sent_rows: usize,
3308    fetch_portal: Option<PortalRefMut>,
3309) -> BackendMessage;
3310
3311// A GetResponse used by send_rows during execute messages on portals or for
3312// simple query messages.
3313fn portal_exec_message(
3314    max_rows: ExecuteCount,
3315    total_sent_rows: usize,
3316    _fetch_portal: Option<PortalRefMut>,
3317) -> BackendMessage {
3318    // If max_rows is not specified, we will always send back a CommandComplete. If
3319    // max_rows is specified, we only send CommandComplete if there were more rows
3320    // requested than were remaining. That is, if max_rows == number of rows that
3321    // were remaining before sending (not that are remaining after sending), then
3322    // we still send a PortalSuspended. The number of remaining rows after the rows
3323    // have been sent doesn't matter. This matches postgres.
3324    match max_rows {
3325        ExecuteCount::Count(max_rows) if max_rows <= total_sent_rows => {
3326            BackendMessage::PortalSuspended
3327        }
3328        _ => BackendMessage::CommandComplete {
3329            tag: format!("SELECT {}", total_sent_rows),
3330        },
3331    }
3332}
3333
3334// A GetResponse used by send_rows during FETCH queries.
3335fn fetch_message(
3336    _max_rows: ExecuteCount,
3337    total_sent_rows: usize,
3338    fetch_portal: Option<PortalRefMut>,
3339) -> BackendMessage {
3340    let tag = format!("FETCH {}", total_sent_rows);
3341    if let Some(portal) = fetch_portal {
3342        *portal.state = PortalState::Completed(Some(tag.clone()));
3343    }
3344    BackendMessage::CommandComplete { tag }
3345}
3346
3347fn get_authenticator(
3348    authenticator_kind: listeners::AuthenticatorKind,
3349    frontegg: Option<FronteggAuthenticator>,
3350    oidc: GenericOidcAuthenticator,
3351    adapter_client: mz_adapter::Client,
3352) -> Authenticator {
3353    match authenticator_kind {
3354        listeners::AuthenticatorKind::Frontegg => Authenticator::Frontegg(frontegg.expect(
3355            "Frontegg authenticator should exist with listeners::AuthenticatorKind::Frontegg",
3356        )),
3357        listeners::AuthenticatorKind::Password => Authenticator::Password(adapter_client),
3358        listeners::AuthenticatorKind::Sasl => Authenticator::Sasl(adapter_client),
3359        listeners::AuthenticatorKind::Oidc => Authenticator::Oidc(oidc),
3360        listeners::AuthenticatorKind::None => Authenticator::None,
3361    }
3362}
3363
3364#[derive(Debug, Copy, Clone)]
3365enum ExecuteCount {
3366    All,
3367    Count(usize),
3368}
3369
3370// See postgres' backend/tcop/postgres.c IsTransactionExitStmt.
3371fn is_txn_exit_stmt(stmt: Option<&Statement<Raw>>) -> bool {
3372    match stmt {
3373        // Add PREPARE to this if we ever support it.
3374        Some(stmt) => matches!(stmt, Statement::Commit(_) | Statement::Rollback(_)),
3375        None => false,
3376    }
3377}
3378
3379#[derive(Debug)]
3380enum FetchResult {
3381    Rows(Option<Box<dyn RowIterator + Send + Sync>>),
3382    Canceled,
3383    Error(ErrorResponse),
3384    Notice(AdapterNotice),
3385}
3386
3387#[derive(Debug)]
3388struct CopyRowScanner {
3389    scan_pos: usize,
3390    last_row_end: Option<usize>,
3391    end_marker_end: Option<usize>,
3392    // Byte offset within `data` at which the in-progress CSV record begins.
3393    // Used to verify the end-of-copy marker against the raw input bytes,
3394    // distinguishing a literal `\.` line from a quoted CSV value `"\."`
3395    // whose decoded form is also `\.`.
3396    record_start: usize,
3397    csv: Option<CsvScanState>,
3398}
3399
3400#[derive(Debug)]
3401struct CsvScanState {
3402    reader: csv_core::Reader,
3403    output: Vec<u8>,
3404    ends: Vec<usize>,
3405    skip_first_record: bool,
3406}
3407
3408impl CopyRowScanner {
3409    fn new(params: &CopyFormatParams<'_>) -> Self {
3410        let csv = match params {
3411            CopyFormatParams::Csv(CopyCsvFormatParams {
3412                delimiter,
3413                quote,
3414                escape,
3415                header,
3416                ..
3417            }) => Some(CsvScanState::new(*delimiter, *quote, *escape, *header)),
3418            _ => None,
3419        };
3420
3421        CopyRowScanner {
3422            scan_pos: 0,
3423            last_row_end: None,
3424            end_marker_end: None,
3425            record_start: 0,
3426            csv,
3427        }
3428    }
3429
3430    fn scan_new_bytes(&mut self, data: &[u8]) {
3431        if self.scan_pos >= data.len() {
3432            return;
3433        }
3434
3435        if let Some(csv) = self.csv.as_mut() {
3436            let mut input = &data[self.scan_pos..];
3437            let mut consumed = 0usize;
3438            while !input.is_empty() {
3439                let (result, n_input, _n_output, _n_ends) =
3440                    csv.reader
3441                        .read_record(input, &mut csv.output, &mut csv.ends);
3442                consumed += n_input;
3443                input = &input[n_input..];
3444
3445                match result {
3446                    ReadRecordResult::InputEmpty => break,
3447                    ReadRecordResult::OutputFull => {
3448                        if n_input == 0 {
3449                            csv.output
3450                                .resize(csv.output.len().saturating_mul(2).max(1), 0);
3451                        }
3452                    }
3453                    ReadRecordResult::OutputEndsFull => {
3454                        if n_input == 0 {
3455                            csv.ends.resize(csv.ends.len().saturating_mul(2).max(1), 0);
3456                        }
3457                    }
3458                    ReadRecordResult::Record | ReadRecordResult::End => {
3459                        let row_end = self.scan_pos + consumed;
3460                        self.last_row_end = Some(row_end);
3461                        if self.end_marker_end.is_none() {
3462                            let is_marker = if csv.skip_first_record {
3463                                csv.skip_first_record = false;
3464                                false
3465                            } else {
3466                                // Detect the marker against the raw input
3467                                // bytes, not the CSV-decoded record. A quoted
3468                                // data row `"\."` decodes to `\.` but must be
3469                                // imported as data; only a bare `\.` line
3470                                // terminates the COPY.
3471                                let raw = &data[self.record_start..row_end];
3472                                // csv-core ends a CRLF record after the `\r`,
3473                                // leaving the trailing `\n` as the leading byte
3474                                // of the next record's span; a CR-only record
3475                                // ends in a lone `\r`. So a `\.` marker record's
3476                                // raw span can be `\.\n` (LF), `\n\.\r` (CRLF)
3477                                // or `\.\r` (CR). Trim CR/LF from both ends
3478                                // before comparing — a trailing-only strip would
3479                                // miss the CRLF/CR forms. Quoted `"\."` data
3480                                // keeps its surrounding quotes after trimming and
3481                                // is therefore correctly rejected.
3482                                let start = raw
3483                                    .iter()
3484                                    .take_while(|&&b| b == b'\r' || b == b'\n')
3485                                    .count();
3486                                let trailing = raw[start..]
3487                                    .iter()
3488                                    .rev()
3489                                    .take_while(|&&b| b == b'\r' || b == b'\n')
3490                                    .count();
3491                                let trimmed = &raw[start..raw.len() - trailing];
3492                                trimmed == b"\\."
3493                            };
3494                            if is_marker {
3495                                self.end_marker_end = Some(row_end);
3496                                self.record_start = row_end;
3497                                break;
3498                            }
3499                        }
3500                        self.record_start = row_end;
3501                    }
3502                }
3503            }
3504        } else {
3505            let mut row_start = self.last_row_end.unwrap_or(0);
3506            for (offset, b) in data[self.scan_pos..].iter().enumerate() {
3507                if *b == b'\n' {
3508                    let row_end = self.scan_pos + offset + 1;
3509                    self.last_row_end = Some(row_end);
3510                    if self.end_marker_end.is_none() {
3511                        let row = &data[row_start..row_end];
3512                        if row.get(0..2) == Some(b"\\.") {
3513                            self.end_marker_end = Some(row_end);
3514                            break;
3515                        }
3516                    }
3517                    row_start = row_end;
3518                }
3519            }
3520        }
3521
3522        self.scan_pos = data.len();
3523    }
3524
3525    fn last_row_end(&self) -> Option<usize> {
3526        self.last_row_end
3527    }
3528
3529    fn end_marker_end(&self) -> Option<usize> {
3530        self.end_marker_end
3531    }
3532
3533    fn current_row_size(&self, data_len: usize) -> usize {
3534        data_len.saturating_sub(self.last_row_end.unwrap_or(0))
3535    }
3536
3537    fn on_split(&mut self, split_pos: usize) {
3538        self.scan_pos = self.scan_pos.saturating_sub(split_pos);
3539        self.last_row_end = None;
3540        self.end_marker_end = self
3541            .end_marker_end
3542            .and_then(|end| end.checked_sub(split_pos));
3543        // `record_start` is only maintained for the CSV path; the text and
3544        // binary paths leave it at 0. For CSV, splits always occur at a
3545        // completed-row boundary, so the in-progress record (if any) starts at
3546        // the new beginning of the buffer. Assert that invariant so the
3547        // `saturating_sub` below doesn't silently paper over a bug that
3548        // bisected an in-progress record — but only when CSV is in use, since
3549        // otherwise `record_start` is meaninglessly 0.
3550        soft_assert_or_log!(
3551            self.csv.is_none() || self.record_start >= split_pos,
3552            "split bisected an in-progress CSV record: record_start={} < split_pos={}",
3553            self.record_start,
3554            split_pos,
3555        );
3556        self.record_start = self.record_start.saturating_sub(split_pos);
3557    }
3558
3559    fn on_truncate(&mut self, new_len: usize) {
3560        self.scan_pos = self.scan_pos.min(new_len);
3561        self.last_row_end = self.last_row_end.filter(|&end| end <= new_len);
3562        self.end_marker_end = self.end_marker_end.filter(|&end| end <= new_len);
3563        self.record_start = self.record_start.min(new_len);
3564    }
3565}
3566
3567impl CsvScanState {
3568    fn new(delimiter: u8, quote: u8, escape: u8, header: bool) -> Self {
3569        let (double_quote, escape) = if quote == escape {
3570            (true, None)
3571        } else {
3572            (false, Some(escape))
3573        };
3574        CsvScanState {
3575            reader: csv_core::ReaderBuilder::new()
3576                .delimiter(delimiter)
3577                .quote(quote)
3578                .double_quote(double_quote)
3579                .escape(escape)
3580                .build(),
3581            output: vec![0; 1],
3582            ends: vec![0; 1],
3583            skip_first_record: header,
3584        }
3585    }
3586}
3587
3588#[cfg(test)]
3589mod test {
3590    use super::*;
3591
3592    #[mz_ore::test]
3593    fn test_copy_row_scanner_end_marker_line_endings() {
3594        // The pgwire COPY row scanner must detect a bare `\.` end-of-copy
3595        // marker for every line ending, and must never mistake a quoted
3596        // `"\."` data row for it. csv-core ends a CRLF record after the `\r`
3597        // (leaving the `\n` as the next record's leading byte), so the raw
3598        // record span of a `\.` marker is `\.\n` (LF), `\n\.\r` (CRLF) or
3599        // `\.\r` (CR); a trailing-only strip would miss the CRLF/CR forms and
3600        // silently import post-marker rows.
3601        let params = CopyFormatParams::Csv(CopyCsvFormatParams::default());
3602
3603        let marker_end = |data: &[u8]| -> Option<usize> {
3604            let mut scanner = CopyRowScanner::new(&params);
3605            scanner.scan_new_bytes(data);
3606            scanner.end_marker_end()
3607        };
3608
3609        for eol in [&b"\n"[..], b"\r\n", b"\r"] {
3610            let join = |lines: &[&str]| -> Vec<u8> {
3611                let mut out = Vec::new();
3612                for line in lines {
3613                    out.extend_from_slice(line.as_bytes());
3614                    out.extend_from_slice(eol);
3615                }
3616                out
3617            };
3618
3619            // Bare `\.` (the marker is the second record, so record_start has
3620            // already advanced past the orphaned terminator of `first`).
3621            // csv-core reports the record after a single terminator byte, so
3622            // the marker boundary sits just past `first<eol>\.` + one byte.
3623            let data = join(&["first", "\\.", "after"]);
3624            let mut prefix = Vec::new();
3625            prefix.extend_from_slice(b"first");
3626            prefix.extend_from_slice(eol);
3627            prefix.extend_from_slice(b"\\.");
3628            assert_eq!(
3629                marker_end(&data),
3630                Some(prefix.len() + 1),
3631                "bare marker, eol={eol:?}"
3632            );
3633
3634            // Quoted "\." is data, not the marker.
3635            let data = join(&["before", "\"\\.\"", "after"]);
3636            assert_eq!(marker_end(&data), None, "quoted marker, eol={eol:?}");
3637        }
3638    }
3639
3640    #[mz_ore::test]
3641    fn test_copy_row_scanner_non_csv_split() {
3642        // Regression: `record_start` is only maintained for the CSV path; the
3643        // text and binary paths leave it at 0. `on_split` must therefore not
3644        // assert `record_start >= split_pos` for those formats — that fires on
3645        // every split of a large text/binary COPY stream (soft-assertions
3646        // panic under test). Mirrors `COPY ... FROM STDIN` (default text
3647        // format) splitting at a row boundary once the buffer fills.
3648        for params in [
3649            CopyFormatParams::Text(CopyTextFormatParams::default()),
3650            CopyFormatParams::Binary,
3651        ] {
3652            let mut scanner = CopyRowScanner::new(&params);
3653            let data = b"1\thello world\t2\tsome text value here\n\
3654                         3\thello world\t6\tsome text value here\n";
3655            scanner.scan_new_bytes(data);
3656            let split_pos = scanner.last_row_end().expect("a complete row");
3657            assert!(split_pos > 0, "params={params:?}");
3658            // Must not panic via the CSV-only `on_split` soft-assert.
3659            scanner.on_split(split_pos);
3660            assert_eq!(scanner.record_start, 0, "params={params:?}");
3661        }
3662    }
3663
3664    #[mz_ore::test]
3665    fn test_parse_options() {
3666        struct TestCase {
3667            input: &'static str,
3668            expect: Result<Vec<(&'static str, &'static str)>, ()>,
3669        }
3670        let tests = vec![
3671            TestCase {
3672                input: "",
3673                expect: Ok(vec![]),
3674            },
3675            TestCase {
3676                input: "--key",
3677                expect: Err(()),
3678            },
3679            TestCase {
3680                input: "--key=val",
3681                expect: Ok(vec![("key", "val")]),
3682            },
3683            TestCase {
3684                input: r#"--key=val -ckey2=val2 -c key3=val3 -c key4=val4 -ckey5=val5"#,
3685                expect: Ok(vec![
3686                    ("key", "val"),
3687                    ("key2", "val2"),
3688                    ("key3", "val3"),
3689                    ("key4", "val4"),
3690                    ("key5", "val5"),
3691                ]),
3692            },
3693            TestCase {
3694                input: r#"-c\ key=val"#,
3695                expect: Ok(vec![(" key", "val")]),
3696            },
3697            TestCase {
3698                input: "--key=val -ckey2 val2",
3699                expect: Err(()),
3700            },
3701            // Unclear what this should do.
3702            TestCase {
3703                input: "--key=",
3704                expect: Ok(vec![("key", "")]),
3705            },
3706        ];
3707        for test in tests {
3708            let got = parse_options(test.input);
3709            let expect = test.expect.map(|r| {
3710                r.into_iter()
3711                    .map(|(k, v)| (k.to_owned(), v.to_owned()))
3712                    .collect()
3713            });
3714            assert_eq!(got, expect, "input: {}", test.input);
3715        }
3716    }
3717
3718    #[mz_ore::test]
3719    fn test_parse_option() {
3720        struct TestCase {
3721            input: &'static str,
3722            expect: Result<(&'static str, &'static str), ()>,
3723        }
3724        let tests = vec![
3725            TestCase {
3726                input: "",
3727                expect: Err(()),
3728            },
3729            TestCase {
3730                input: "--",
3731                expect: Err(()),
3732            },
3733            TestCase {
3734                input: "--c",
3735                expect: Err(()),
3736            },
3737            TestCase {
3738                input: "a=b",
3739                expect: Err(()),
3740            },
3741            TestCase {
3742                input: "--a=b",
3743                expect: Ok(("a", "b")),
3744            },
3745            TestCase {
3746                input: "--ca=b",
3747                expect: Ok(("ca", "b")),
3748            },
3749            TestCase {
3750                input: "-ca=b",
3751                expect: Ok(("a", "b")),
3752            },
3753            // Unclear what this should error, but at least test it.
3754            TestCase {
3755                input: "--=",
3756                expect: Ok(("", "")),
3757            },
3758        ];
3759        for test in tests {
3760            let got = parse_option(test.input);
3761            assert_eq!(got, test.expect, "input: {}", test.input);
3762        }
3763    }
3764
3765    #[mz_ore::test]
3766    fn test_split_options() {
3767        struct TestCase {
3768            input: &'static str,
3769            expect: Vec<&'static str>,
3770        }
3771        let tests = vec![
3772            TestCase {
3773                input: "",
3774                expect: vec![],
3775            },
3776            TestCase {
3777                input: "  ",
3778                expect: vec![],
3779            },
3780            TestCase {
3781                input: " a ",
3782                expect: vec!["a"],
3783            },
3784            TestCase {
3785                input: "  ab     cd   ",
3786                expect: vec!["ab", "cd"],
3787            },
3788            TestCase {
3789                input: r#"  ab\     cd   "#,
3790                expect: vec!["ab ", "cd"],
3791            },
3792            TestCase {
3793                input: r#"  ab\\     cd   "#,
3794                expect: vec![r#"ab\"#, "cd"],
3795            },
3796            TestCase {
3797                input: r#"  ab\\\     cd   "#,
3798                expect: vec![r#"ab\ "#, "cd"],
3799            },
3800            TestCase {
3801                input: r#"  ab\\\ cd   "#,
3802                expect: vec![r#"ab\ cd"#],
3803            },
3804            TestCase {
3805                input: r#"  ab\\\cd   "#,
3806                expect: vec![r#"ab\cd"#],
3807            },
3808            TestCase {
3809                input: r#"a\"#,
3810                expect: vec!["a"],
3811            },
3812            TestCase {
3813                input: r#"a\ "#,
3814                expect: vec!["a "],
3815            },
3816            TestCase {
3817                input: r#"\"#,
3818                expect: vec![],
3819            },
3820            TestCase {
3821                input: r#"\ "#,
3822                expect: vec![r#" "#],
3823            },
3824            TestCase {
3825                input: r#" \ "#,
3826                expect: vec![r#" "#],
3827            },
3828            TestCase {
3829                input: r#"\  "#,
3830                expect: vec![r#" "#],
3831            },
3832        ];
3833        for test in tests {
3834            let got = split_options(test.input);
3835            assert_eq!(got, test.expect, "input: {}", test.input);
3836        }
3837    }
3838
3839    #[mz_ore::test]
3840    fn test_is_jwt() {
3841        // A real JWT header decodes successfully.
3842        assert!(is_jwt("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.signature"));
3843        // Not JWTs: plain strings, wrong segment count, non-JSON headers.
3844        for s in [
3845            "",
3846            "secure_password",
3847            "p4ss.w0rd",
3848            "aaa.bbb.ccc",
3849            "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0",
3850            "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig.extra",
3851        ] {
3852            assert!(!is_jwt(s), "is_jwt({s:?})");
3853        }
3854    }
3855}