Skip to main content

mz_adapter/
session.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
10//! Per-connection configuration parameters and state.
11
12#![warn(missing_docs)]
13
14use std::collections::btree_map::Entry;
15use std::collections::{BTreeMap, BTreeSet};
16use std::future::Future;
17use std::mem;
18use std::net::IpAddr;
19use std::pin::Pin;
20use std::sync::Arc;
21
22use chrono::{DateTime, Utc};
23use derivative::Derivative;
24use itertools::Itertools;
25use mz_adapter_types::connection::ConnectionId;
26use mz_auth::AuthenticatorKind;
27use mz_auth::user::ExternalUserMetadata;
28use mz_build_info::{BuildInfo, DUMMY_BUILD_INFO};
29use mz_controller_types::ClusterId;
30use mz_ore::metrics::{MetricsFutureExt, MetricsRegistry};
31use mz_ore::now::{EpochMillis, NowFn};
32use mz_pgwire_common::Format;
33use mz_repr::role_id::RoleId;
34use mz_repr::user::InternalUserMetadata;
35use mz_repr::{CatalogItemId, Datum, Row, RowIterator, SqlScalarType, Timestamp};
36use mz_sql::ast::{AstInfo, Raw, Statement, TransactionAccessMode};
37use mz_sql::plan::{Params, PlanContext, QueryWhen, StatementDesc};
38use mz_sql::session::metadata::SessionMetadata;
39use mz_sql::session::user::{
40    INTERNAL_USER_NAME_TO_DEFAULT_CLUSTER, RoleMetadata, SYSTEM_USER, User,
41};
42use mz_sql::session::vars::IsolationLevel;
43pub use mz_sql::session::vars::{
44    DEFAULT_DATABASE_NAME, EndTransactionAction, SERVER_MAJOR_VERSION, SERVER_MINOR_VERSION,
45    SERVER_PATCH_VERSION, SessionVars, Var,
46};
47use mz_sql_parser::ast::TransactionIsolationLevel;
48use mz_storage_client::client::TableData;
49use mz_storage_types::sources::Timeline;
50use qcell::{QCell, QCellOwner};
51use timely::progress::Timestamp as _;
52use tokio::sync::mpsc::{self, UnboundedSender};
53use tokio::sync::watch;
54use uuid::Uuid;
55
56use crate::catalog::CatalogState;
57use crate::client::RecordFirstRowStream;
58use crate::coord::appends::BuiltinTableAppendNotify;
59use crate::coord::in_memory_oracle::InMemoryTimestampOracle;
60use crate::coord::peek::PeekResponseUnary;
61use crate::coord::timestamp_selection::{TimestampContext, TimestampDetermination};
62use crate::coord::{Coordinator, ExplainContext};
63use crate::error::AdapterError;
64use crate::metrics::{Metrics, SessionMetrics};
65use crate::statement_logging::PreparedStatementLoggingInfo;
66use crate::{AdapterNotice, ExecuteContext};
67use mz_catalog::durable::Snapshot;
68
69const DUMMY_CONNECTION_ID: ConnectionId = ConnectionId::Static(0);
70
71/// A session holds per-connection state.
72#[derive(Derivative)]
73#[derivative(Debug)]
74pub struct Session {
75    conn_id: ConnectionId,
76    /// A globally unique identifier for the session. Not to be confused
77    /// with `conn_id`, which may be reused.
78    uuid: Uuid,
79    prepared_statements: BTreeMap<String, PreparedStatement>,
80    portals: BTreeMap<String, Portal>,
81    transaction: TransactionStatus,
82    pcx: Option<PlanContext>,
83    metrics: SessionMetrics,
84    #[derivative(Debug = "ignore")]
85    builtin_updates: Option<BuiltinTableAppendNotify>,
86
87    /// The role metadata of the current session.
88    ///
89    /// Invariant: role_metadata must be `Some` after the user has
90    /// successfully connected to and authenticated with Materialize.
91    ///
92    /// Prefer using this value over [`SessionConfig::user`].
93    //
94    // It would be better for this not to be an Option, but the
95    // `Session` is initialized before the user has connected to
96    // Materialize and is able to look up the `RoleMetadata`. The `Session`
97    // is also used to return an error when no role exists and
98    // therefore there is no valid `RoleMetadata`.
99    role_metadata: Option<RoleMetadata>,
100    client_ip: Option<IpAddr>,
101    vars: SessionVars,
102    notices_tx: mpsc::UnboundedSender<AdapterNotice>,
103    notices_rx: mpsc::UnboundedReceiver<AdapterNotice>,
104    next_transaction_id: TransactionId,
105    secret_key: u32,
106    external_metadata_rx: Option<watch::Receiver<ExternalUserMetadata>>,
107    // Token allowing us to access `Arc<QCell<StatementLogging>>`
108    // metadata. We want these to be reference-counted, because the same
109    // statement might be referenced from multiple portals simultaneously.
110    //
111    // However, they can't be `Rc<RefCell<StatementLogging>>`, because
112    // the `Session` is sent around to different threads.
113    //
114    // On the other hand, they don't need to be
115    // `Arc<Mutex<StatementLogging>>`, because they will always be
116    // accessed from the same thread that the `Session` is currently
117    // on. We express this by gating access with this token.
118    #[derivative(Debug = "ignore")]
119    qcell_owner: QCellOwner,
120    session_oracles: BTreeMap<Timeline, InMemoryTimestampOracle>,
121    /// Incremented when session state that is relevant to prepared statement planning changes.
122    /// Currently, only changes to `portals` are tracked. Changes to `prepared_statements` don't
123    /// need to be tracked, because prepared statements can't depend on other prepared statements.
124    /// TODO: We might want to track changes also to session variables.
125    /// (`Catalog::transient_revision` similarly tracks changes on the catalog side.)
126    state_revision: u64,
127}
128
129impl SessionMetadata for Session {
130    fn conn_id(&self) -> &ConnectionId {
131        &self.conn_id
132    }
133
134    fn client_ip(&self) -> Option<&IpAddr> {
135        self.client_ip.as_ref()
136    }
137
138    fn pcx(&self) -> &PlanContext {
139        &self
140            .transaction()
141            .inner()
142            .expect("no active transaction")
143            .pcx
144    }
145
146    fn role_metadata(&self) -> &RoleMetadata {
147        self.role_metadata
148            .as_ref()
149            .expect("role_metadata invariant violated")
150    }
151
152    fn vars(&self) -> &SessionVars {
153        &self.vars
154    }
155}
156
157/// Data structure suitable for passing to other threads that need access to some common Session
158/// properties.
159#[derive(Debug)]
160pub struct SessionMeta {
161    conn_id: ConnectionId,
162    client_ip: Option<IpAddr>,
163    pcx: PlanContext,
164    role_metadata: RoleMetadata,
165    vars: SessionVars,
166}
167
168impl SessionMetadata for SessionMeta {
169    fn vars(&self) -> &SessionVars {
170        &self.vars
171    }
172
173    fn conn_id(&self) -> &ConnectionId {
174        &self.conn_id
175    }
176
177    fn client_ip(&self) -> Option<&IpAddr> {
178        self.client_ip.as_ref()
179    }
180
181    fn pcx(&self) -> &PlanContext {
182        &self.pcx
183    }
184
185    fn role_metadata(&self) -> &RoleMetadata {
186        &self.role_metadata
187    }
188}
189
190/// Configures a new [`Session`].
191#[derive(Debug, Clone)]
192pub struct SessionConfig {
193    /// The connection ID for the session.
194    ///
195    /// May be reused after the session terminates.
196    pub conn_id: ConnectionId,
197    /// A universally unique identifier for the session, across all processes,
198    /// region, and all time.
199    ///
200    /// Must not be reused, even after the session terminates.
201    pub uuid: Uuid,
202    /// The peer address of the client
203    pub client_ip: Option<IpAddr>,
204    /// The name of the user associated with the session.
205    pub user: String,
206    /// An optional receiver that the session will periodically check for
207    /// updates to a user's external metadata.
208    pub external_metadata_rx: Option<watch::Receiver<ExternalUserMetadata>>,
209    /// Helm chart version
210    pub helm_chart_version: Option<String>,
211    /// The authenticator that authenticated this user, if any.
212    pub authenticator_kind: AuthenticatorKind,
213    /// Groups from JWT claims for OIDC group-to-role sync.
214    pub groups: Option<Vec<String>>,
215}
216
217impl Session {
218    /// Creates a new session for the specified connection ID.
219    pub(crate) fn new(
220        build_info: &'static BuildInfo,
221        config: SessionConfig,
222        metrics: SessionMetrics,
223    ) -> Session {
224        assert_ne!(config.conn_id, DUMMY_CONNECTION_ID);
225        Self::new_internal(build_info, config, metrics)
226    }
227
228    /// Returns a reference-less collection of data usable by other tasks that don't have ownership
229    /// of the Session.
230    pub fn meta(&self) -> SessionMeta {
231        SessionMeta {
232            conn_id: self.conn_id().clone(),
233            client_ip: self.client_ip().copied(),
234            pcx: self.pcx().clone(),
235            role_metadata: self.role_metadata().clone(),
236            vars: self.vars.clone(),
237        }
238
239        // TODO: soft_assert that these are the same as Session.
240    }
241
242    /// Creates new statement logging metadata for a one-off
243    /// statement.
244    // Normally, such logging information would be created as part of
245    // allocating a new prepared statement, and a refcounted handle
246    // would be copied from that prepared statement to portals during
247    // binding. However, we also support (via `Command::declare`)
248    // binding a statement directly to a portal without creating an
249    // intermediate prepared statement. Thus, for those cases, a
250    // mechanism for generating the logging metadata directly is needed.
251    pub(crate) fn mint_logging<A: AstInfo>(
252        &self,
253        raw_sql: String,
254        stmt: Option<&Statement<A>>,
255        now: EpochMillis,
256    ) -> Arc<QCell<PreparedStatementLoggingInfo>> {
257        Arc::new(QCell::new(
258            &self.qcell_owner,
259            PreparedStatementLoggingInfo::still_to_log(
260                raw_sql,
261                stmt,
262                now,
263                "".to_string(),
264                self.uuid,
265                false,
266            ),
267        ))
268    }
269
270    pub(crate) fn qcell_ro<'a, T2: 'a>(&'a self, cell: &'a Arc<QCell<T2>>) -> &'a T2 {
271        self.qcell_owner.ro(&*cell)
272    }
273
274    pub(crate) fn qcell_rw<'a, T2: 'a>(&'a mut self, cell: &'a Arc<QCell<T2>>) -> &'a mut T2 {
275        self.qcell_owner.rw(&*cell)
276    }
277
278    /// Returns a unique ID for the session.
279    /// Not to be confused with `connection_id`, which can be reused.
280    pub fn uuid(&self) -> Uuid {
281        self.uuid
282    }
283
284    /// Creates a new dummy session.
285    ///
286    /// Dummy sessions are intended for use when executing queries on behalf of
287    /// the system itself, rather than on behalf of a user.
288    pub fn dummy() -> Session {
289        let registry = MetricsRegistry::new();
290        let metrics = Metrics::register_into(&registry);
291        let metrics = metrics.session_metrics();
292        let mut dummy = Self::new_internal(
293            &DUMMY_BUILD_INFO,
294            SessionConfig {
295                conn_id: DUMMY_CONNECTION_ID,
296                uuid: Uuid::new_v4(),
297                user: SYSTEM_USER.name.clone(),
298                client_ip: None,
299                external_metadata_rx: None,
300                helm_chart_version: None,
301                authenticator_kind: AuthenticatorKind::None,
302                groups: None,
303            },
304            metrics,
305        );
306        dummy.initialize_role_metadata(RoleId::User(0));
307        dummy
308    }
309
310    fn new_internal(
311        build_info: &'static BuildInfo,
312        SessionConfig {
313            conn_id,
314            uuid,
315            user,
316            client_ip,
317            mut external_metadata_rx,
318            helm_chart_version,
319            authenticator_kind,
320            groups,
321        }: SessionConfig,
322        metrics: SessionMetrics,
323    ) -> Session {
324        let (notices_tx, notices_rx) = mpsc::unbounded_channel();
325        let default_cluster = INTERNAL_USER_NAME_TO_DEFAULT_CLUSTER.get(&user);
326        let user = User {
327            name: user,
328            internal_metadata: None,
329            external_metadata: external_metadata_rx
330                .as_mut()
331                .map(|rx| rx.borrow_and_update().clone()),
332            authenticator_kind: Some(authenticator_kind),
333            groups,
334        };
335        let mut vars = SessionVars::new_unchecked(build_info, user, helm_chart_version);
336        if let Some(default_cluster) = default_cluster {
337            vars.set_cluster(default_cluster.clone());
338        }
339        Session {
340            conn_id,
341            uuid,
342            transaction: TransactionStatus::Default,
343            pcx: None,
344            metrics,
345            builtin_updates: None,
346            prepared_statements: BTreeMap::new(),
347            portals: BTreeMap::new(),
348            role_metadata: None,
349            client_ip,
350            vars,
351            notices_tx,
352            notices_rx,
353            next_transaction_id: 0,
354            secret_key: rand::random(),
355            external_metadata_rx,
356            qcell_owner: QCellOwner::new(),
357            session_oracles: BTreeMap::new(),
358            state_revision: 0,
359        }
360    }
361
362    /// Returns the secret key associated with the session.
363    pub fn secret_key(&self) -> u32 {
364        self.secret_key
365    }
366
367    fn new_pcx(&self, mut wall_time: DateTime<Utc>) -> PlanContext {
368        if let Some(mock_time) = self.vars().unsafe_new_transaction_wall_time() {
369            wall_time = *mock_time;
370        }
371        PlanContext::new(wall_time)
372    }
373
374    /// Starts an explicit transaction, or changes an implicit to an explicit
375    /// transaction.
376    pub fn start_transaction(
377        &mut self,
378        wall_time: DateTime<Utc>,
379        access: Option<TransactionAccessMode>,
380        isolation_level: Option<TransactionIsolationLevel>,
381    ) -> Result<(), AdapterError> {
382        // Check that current transaction state is compatible with new `access`
383        if let Some(txn) = self.transaction.inner() {
384            // `READ WRITE` prohibited if:
385            // - Currently in `READ ONLY`
386            // - Already performed a query
387            let read_write_prohibited = match txn.ops {
388                TransactionOps::Peeks { .. } | TransactionOps::Subscribe => {
389                    txn.access == Some(TransactionAccessMode::ReadOnly)
390                }
391                TransactionOps::None
392                | TransactionOps::Writes(_)
393                | TransactionOps::SingleStatement { .. }
394                | TransactionOps::DDL { .. } => false,
395            };
396
397            if read_write_prohibited && access == Some(TransactionAccessMode::ReadWrite) {
398                return Err(AdapterError::ReadWriteUnavailable);
399            }
400        }
401
402        match std::mem::take(&mut self.transaction) {
403            TransactionStatus::Default => {
404                let id = self.next_transaction_id;
405                self.next_transaction_id = self.next_transaction_id.wrapping_add(1);
406                self.transaction = TransactionStatus::InTransaction(Transaction {
407                    pcx: self.new_pcx(wall_time),
408                    ops: TransactionOps::None,
409                    write_lock_guards: None,
410                    access,
411                    id,
412                });
413            }
414            TransactionStatus::Started(mut txn)
415            | TransactionStatus::InTransactionImplicit(mut txn)
416            | TransactionStatus::InTransaction(mut txn) => {
417                if access.is_some() {
418                    txn.access = access;
419                }
420                self.transaction = TransactionStatus::InTransaction(txn);
421            }
422            TransactionStatus::Failed(_) => unreachable!(),
423        };
424
425        if let Some(isolation_level) = isolation_level {
426            self.vars
427                .set_local_transaction_isolation(isolation_level.into());
428        }
429
430        Ok(())
431    }
432
433    /// Starts either a single statement or implicit transaction based on the
434    /// number of statements, but only if no transaction has been started already.
435    pub fn start_transaction_implicit(&mut self, wall_time: DateTime<Utc>, stmts: usize) {
436        if let TransactionStatus::Default = self.transaction {
437            let id = self.next_transaction_id;
438            self.next_transaction_id = self.next_transaction_id.wrapping_add(1);
439            let txn = Transaction {
440                pcx: self.new_pcx(wall_time),
441                ops: TransactionOps::None,
442                write_lock_guards: None,
443                access: None,
444                id,
445            };
446            match stmts {
447                1 => self.transaction = TransactionStatus::Started(txn),
448                n if n > 1 => self.transaction = TransactionStatus::InTransactionImplicit(txn),
449                _ => {}
450            }
451        }
452    }
453
454    /// Starts a single statement transaction, but only if no transaction has been started already.
455    pub fn start_transaction_single_stmt(&mut self, wall_time: DateTime<Utc>) {
456        self.start_transaction_implicit(wall_time, 1);
457    }
458
459    /// Clears a transaction, setting its state to Default and destroying all
460    /// portals. Returned are:
461    /// - sinks that were started in this transaction and need to be dropped
462    /// - the cleared transaction so its operations can be handled
463    ///
464    /// The [Postgres protocol docs](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY) specify:
465    /// > a named portal object lasts till the end of the current transaction
466    /// and
467    /// > An unnamed portal is destroyed at the end of the transaction
468    #[must_use]
469    pub fn clear_transaction(&mut self) -> TransactionStatus {
470        self.portals.clear();
471        self.pcx = None;
472        self.state_revision += 1;
473        mem::take(&mut self.transaction)
474    }
475
476    /// Marks the current transaction as failed.
477    pub fn fail_transaction(mut self) -> Self {
478        match self.transaction {
479            TransactionStatus::Default => unreachable!(),
480            TransactionStatus::Started(txn)
481            | TransactionStatus::InTransactionImplicit(txn)
482            | TransactionStatus::InTransaction(txn) => {
483                self.transaction = TransactionStatus::Failed(txn);
484            }
485            TransactionStatus::Failed(_) => {}
486        };
487        self
488    }
489
490    /// Returns the current transaction status.
491    pub fn transaction(&self) -> &TransactionStatus {
492        &self.transaction
493    }
494
495    /// Returns the current transaction status.
496    pub fn transaction_mut(&mut self) -> &mut TransactionStatus {
497        &mut self.transaction
498    }
499
500    /// Returns the session's transaction code.
501    pub fn transaction_code(&self) -> TransactionCode {
502        self.transaction().into()
503    }
504
505    /// Adds operations to the current transaction. An error is produced if
506    /// they cannot be merged (i.e., a timestamp-dependent read cannot be
507    /// merged to an insert).
508    pub fn add_transaction_ops(&mut self, add_ops: TransactionOps) -> Result<(), AdapterError> {
509        self.transaction.add_ops(add_ops)
510    }
511
512    /// Returns a channel on which to send notices to the session.
513    pub fn retain_notice_transmitter(&self) -> UnboundedSender<AdapterNotice> {
514        self.notices_tx.clone()
515    }
516
517    /// Adds a notice to the session.
518    pub fn add_notice(&self, notice: AdapterNotice) {
519        self.add_notices([notice])
520    }
521
522    /// Adds multiple notices to the session.
523    pub fn add_notices(&self, notices: impl IntoIterator<Item = AdapterNotice>) {
524        for notice in notices {
525            let _ = self.notices_tx.send(notice);
526        }
527    }
528
529    /// Awaits a possible notice.
530    ///
531    /// This method is cancel safe.
532    pub async fn recv_notice(&mut self) -> AdapterNotice {
533        // This method is cancel safe because recv is cancel safe.
534        loop {
535            let notice = self
536                .notices_rx
537                .recv()
538                .await
539                .expect("Session also holds a sender, so recv won't ever return None");
540            match self.notice_filter(notice) {
541                Some(notice) => return notice,
542                None => continue,
543            }
544        }
545    }
546
547    /// Returns a draining iterator over the notices attached to the session.
548    pub fn drain_notices(&mut self) -> Vec<AdapterNotice> {
549        let mut notices = Vec::new();
550        while let Ok(notice) = self.notices_rx.try_recv() {
551            if let Some(notice) = self.notice_filter(notice) {
552                notices.push(notice);
553            }
554        }
555        notices
556    }
557
558    /// Returns Some if the notice should be reported, otherwise None.
559    fn notice_filter(&self, notice: AdapterNotice) -> Option<AdapterNotice> {
560        // Filter out low threshold severity.
561        let minimum_client_severity = self.vars.client_min_messages();
562        let sev = notice.severity();
563        if !minimum_client_severity.should_output_to_client(&sev) {
564            return None;
565        }
566        // Filter out notices for other clusters.
567        if let AdapterNotice::ClusterReplicaStatusChanged { cluster, .. } = &notice {
568            if cluster != self.vars.cluster() {
569                return None;
570            }
571        }
572        Some(notice)
573    }
574
575    /// Sets the transaction ops to `TransactionOps::None`. Must only be used after
576    /// verifying that no transaction anomalies will occur if cleared.
577    pub fn clear_transaction_ops(&mut self) {
578        if let Some(txn) = self.transaction.inner_mut() {
579            txn.ops = TransactionOps::None;
580        }
581    }
582
583    /// If the current transaction ops belong to a read, then sets the
584    /// ops to `None`, returning the old read timestamp context if
585    /// any existed. Must only be used after verifying that no transaction
586    /// anomalies will occur if cleared.
587    pub fn take_transaction_timestamp_context(&mut self) -> Option<TimestampContext> {
588        if let Some(Transaction { ops, .. }) = self.transaction.inner_mut() {
589            if let TransactionOps::Peeks { .. } = ops {
590                let ops = std::mem::take(ops);
591                Some(
592                    ops.timestamp_determination()
593                        .expect("checked above")
594                        .timestamp_context,
595                )
596            } else {
597                None
598            }
599        } else {
600            None
601        }
602    }
603
604    /// Returns the transaction's read timestamp determination, if set.
605    ///
606    /// Returns `None` if there is no active transaction, or if the active
607    /// transaction is not a read transaction.
608    pub fn get_transaction_timestamp_determination(&self) -> Option<TimestampDetermination> {
609        match self.transaction.inner() {
610            Some(Transaction {
611                pcx: _,
612                ops: TransactionOps::Peeks { determination, .. },
613                write_lock_guards: _,
614                access: _,
615                id: _,
616            }) => Some(determination.clone()),
617            _ => None,
618        }
619    }
620
621    /// Whether this session has a timestamp for a read transaction.
622    pub fn contains_read_timestamp(&self) -> bool {
623        matches!(
624            self.transaction.inner(),
625            Some(Transaction {
626                pcx: _,
627                ops: TransactionOps::Peeks {
628                    determination: TimestampDetermination {
629                        timestamp_context: TimestampContext::TimelineTimestamp { .. },
630                        ..
631                    },
632                    ..
633                },
634                write_lock_guards: _,
635                access: _,
636                id: _,
637            })
638        )
639    }
640
641    /// Registers the prepared statement under `name`.
642    pub fn set_prepared_statement(
643        &mut self,
644        name: String,
645        stmt: Option<Statement<Raw>>,
646        raw_sql: String,
647        desc: StatementDesc,
648        state_revision: StateRevision,
649        now: EpochMillis,
650    ) {
651        let logging = PreparedStatementLoggingInfo::still_to_log(
652            raw_sql,
653            stmt.as_ref(),
654            now,
655            name.clone(),
656            self.uuid,
657            false,
658        );
659        let statement = PreparedStatement {
660            stmt,
661            desc,
662            state_revision,
663            logging: Arc::new(QCell::new(&self.qcell_owner, logging)),
664        };
665        self.prepared_statements.insert(name, statement);
666    }
667
668    /// Removes the prepared statement associated with `name`.
669    ///
670    /// Returns whether a statement previously existed.
671    pub fn remove_prepared_statement(&mut self, name: &str) -> bool {
672        self.prepared_statements.remove(name).is_some()
673    }
674
675    /// Removes all prepared statements.
676    pub fn remove_all_prepared_statements(&mut self) {
677        self.prepared_statements.clear();
678    }
679
680    /// Retrieves the prepared statement associated with `name`.
681    ///
682    /// This is unverified and could be incorrect if the underlying catalog has
683    /// changed.
684    pub fn get_prepared_statement_unverified(&self, name: &str) -> Option<&PreparedStatement> {
685        self.prepared_statements.get(name)
686    }
687
688    /// Retrieves the prepared statement associated with `name`.
689    ///
690    /// This is unverified and could be incorrect if the underlying catalog has
691    /// changed.
692    pub fn get_prepared_statement_mut_unverified(
693        &mut self,
694        name: &str,
695    ) -> Option<&mut PreparedStatement> {
696        self.prepared_statements.get_mut(name)
697    }
698
699    /// Returns the prepared statements for the session.
700    pub fn prepared_statements(&self) -> &BTreeMap<String, PreparedStatement> {
701        &self.prepared_statements
702    }
703
704    /// Returns the portals for the session.
705    pub fn portals(&self) -> &BTreeMap<String, Portal> {
706        &self.portals
707    }
708
709    /// Binds the specified portal to the specified prepared statement.
710    ///
711    /// If the prepared statement contains parameters, the values and types of
712    /// those parameters must be provided in `params`. It is the caller's
713    /// responsibility to ensure that the correct number of parameters is
714    /// provided.
715    ///
716    /// The `results_formats` parameter sets the desired format of the results,
717    /// and is stored on the portal.
718    pub fn set_portal(
719        &mut self,
720        portal_name: String,
721        desc: StatementDesc,
722        stmt: Option<Statement<Raw>>,
723        logging: Arc<QCell<PreparedStatementLoggingInfo>>,
724        params: Vec<(Datum, SqlScalarType)>,
725        result_formats: Vec<Format>,
726        state_revision: StateRevision,
727    ) -> Result<(), AdapterError> {
728        // The empty portal can be silently replaced.
729        if !portal_name.is_empty() && self.portals.contains_key(&portal_name) {
730            return Err(AdapterError::DuplicateCursor(portal_name));
731        }
732        self.state_revision += 1;
733        let param_types = desc.param_types.clone();
734        self.portals.insert(
735            portal_name,
736            Portal {
737                stmt: stmt.map(Arc::new),
738                desc,
739                state_revision,
740                parameters: Params {
741                    datums: Row::pack(params.iter().map(|(d, _t)| d)),
742                    execute_types: params.into_iter().map(|(_d, t)| t).collect(),
743                    expected_types: param_types,
744                },
745                result_formats,
746                state: PortalState::NotStarted,
747                logging,
748                lifecycle_timestamps: None,
749            },
750        );
751        Ok(())
752    }
753
754    /// Removes the specified portal.
755    ///
756    /// If there is no such portal, this method does nothing. Returns whether that portal existed.
757    pub fn remove_portal(&mut self, portal_name: &str) -> bool {
758        self.state_revision += 1;
759        self.portals.remove(portal_name).is_some()
760    }
761
762    /// Retrieves a reference to the specified portal.
763    ///
764    /// If there is no such portal, returns `None`.
765    pub fn get_portal_unverified(&self, portal_name: &str) -> Option<&Portal> {
766        self.portals.get(portal_name)
767    }
768
769    /// Retrieves a mutable reference to the specified portal.
770    ///
771    /// If there is no such portal, returns `None`.
772    ///
773    /// Note: When using the returned `PortalRefMut`, there is no need to increment
774    /// `Session::state_revision`, because the portal's meaning is not changed.
775    pub fn get_portal_unverified_mut(&mut self, portal_name: &str) -> Option<PortalRefMut<'_>> {
776        self.portals.get_mut(portal_name).map(|p| PortalRefMut {
777            stmt: &p.stmt,
778            desc: &p.desc,
779            state_revision: &mut p.state_revision,
780            parameters: &mut p.parameters,
781            result_formats: &mut p.result_formats,
782            logging: &mut p.logging,
783            state: &mut p.state,
784            lifecycle_timestamps: &mut p.lifecycle_timestamps,
785        })
786    }
787
788    /// Creates and installs a new portal.
789    pub fn create_new_portal(
790        &mut self,
791        stmt: Option<Statement<Raw>>,
792        logging: Arc<QCell<PreparedStatementLoggingInfo>>,
793        desc: StatementDesc,
794        parameters: Params,
795        result_formats: Vec<Format>,
796        state_revision: StateRevision,
797    ) -> Result<String, AdapterError> {
798        self.state_revision += 1;
799
800        // See: https://github.com/postgres/postgres/blob/84f5c2908dad81e8622b0406beea580e40bb03ac/src/backend/utils/mmgr/portalmem.c#L234
801        for i in 0usize.. {
802            let name = format!("<unnamed portal {}>", i);
803            match self.portals.entry(name.clone()) {
804                Entry::Occupied(_) => continue,
805                Entry::Vacant(entry) => {
806                    entry.insert(Portal {
807                        stmt: stmt.map(Arc::new),
808                        desc,
809                        state_revision,
810                        parameters,
811                        result_formats,
812                        state: PortalState::NotStarted,
813                        logging,
814                        lifecycle_timestamps: None,
815                    });
816                    return Ok(name);
817                }
818            }
819        }
820
821        coord_bail!("unable to create a new portal");
822    }
823
824    /// Resets the session to its initial state.
825    ///
826    /// Returns the parameters changed by [`SessionVars::reset_all`].
827    pub fn reset(&mut self) -> BTreeMap<&'static str, String> {
828        let _ = self.clear_transaction();
829        self.prepared_statements.clear();
830        self.vars.reset_all()
831    }
832
833    /// Returns the [application_name] that created this session.
834    ///
835    /// [application_name]: (https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-APPLICATION-NAME)
836    pub fn application_name(&self) -> &str {
837        self.vars.application_name()
838    }
839
840    /// Returns a reference to the variables in this session.
841    pub fn vars(&self) -> &SessionVars {
842        &self.vars
843    }
844
845    /// Returns a mutable reference to the variables in this session.
846    pub fn vars_mut(&mut self) -> &mut SessionVars {
847        &mut self.vars
848    }
849
850    /// Grants a set of write locks to this session's inner [`Transaction`].
851    ///
852    /// # Panics
853    /// If the inner transaction is idle. See [`TransactionStatus::try_grant_write_locks`].
854    ///
855    pub fn try_grant_write_locks(&mut self, guards: WriteLocks) -> Result<(), &WriteLocks> {
856        self.transaction.try_grant_write_locks(guards)
857    }
858
859    /// Drains any external metadata updates and applies the changes from the latest update.
860    pub fn apply_external_metadata_updates(&mut self) {
861        // If no sender is registered then there isn't anything to do.
862        let Some(rx) = &mut self.external_metadata_rx else {
863            return;
864        };
865
866        // If the value hasn't changed then return.
867        if !rx.has_changed().unwrap_or(false) {
868            return;
869        }
870
871        // Update our metadata! Note the short critical section (just a clone) to avoid blocking
872        // the sending side of this watch channel.
873        let metadata = rx.borrow_and_update().clone();
874        self.vars.set_external_user_metadata(metadata);
875    }
876
877    /// Applies the internal user metadata to the session.
878    pub fn apply_internal_user_metadata(&mut self, metadata: InternalUserMetadata) {
879        self.vars.set_internal_user_metadata(metadata);
880    }
881
882    /// Initializes the session's role metadata.
883    pub fn initialize_role_metadata(&mut self, role_id: RoleId) {
884        self.role_metadata = Some(RoleMetadata::new(role_id));
885    }
886
887    /// Ensures that a timestamp oracle exists for `timeline` and returns a mutable reference to
888    /// the timestamp oracle.
889    pub fn ensure_timestamp_oracle(&mut self, timeline: Timeline) -> &mut InMemoryTimestampOracle {
890        self.session_oracles.entry(timeline).or_insert_with(|| {
891            InMemoryTimestampOracle::new(Timestamp::minimum(), NowFn::from(Timestamp::minimum))
892        })
893    }
894
895    /// Ensures that a timestamp oracle exists for reads and writes from/to a local input and
896    /// returns a mutable reference to the timestamp oracle.
897    pub fn ensure_local_timestamp_oracle(&mut self) -> &mut InMemoryTimestampOracle {
898        self.ensure_timestamp_oracle(Timeline::EpochMilliseconds)
899    }
900
901    /// Returns a reference to the timestamp oracle for `timeline`.
902    pub fn get_timestamp_oracle(&self, timeline: &Timeline) -> Option<&InMemoryTimestampOracle> {
903        self.session_oracles.get(timeline)
904    }
905
906    /// If the current session is using the Strong Session Serializable isolation level advance the
907    /// session local timestamp oracle to `write_ts`.
908    pub fn apply_write(&mut self, timestamp: Timestamp) {
909        if self.vars().transaction_isolation() == &IsolationLevel::StrongSessionSerializable {
910            self.ensure_local_timestamp_oracle().apply_write(timestamp);
911        }
912    }
913
914    /// Returns the [`SessionMetrics`] instance associated with this [`Session`].
915    pub fn metrics(&self) -> &SessionMetrics {
916        &self.metrics
917    }
918
919    /// Sets the `BuiltinTableAppendNotify` for this session.
920    pub fn set_builtin_table_updates(&mut self, fut: BuiltinTableAppendNotify) {
921        let prev = self.builtin_updates.replace(fut);
922        mz_ore::soft_assert_or_log!(prev.is_none(), "replacing old builtin table notify");
923    }
924
925    /// Takes the stashed `BuiltinTableAppendNotify`, if one exists, and returns a [`Future`] that
926    /// waits for the writes to complete.
927    pub fn clear_builtin_table_updates(&mut self) -> Option<impl Future<Output = ()> + 'static> {
928        if let Some(fut) = self.builtin_updates.take() {
929            // Record how long we blocked for, if we blocked at all.
930            let histogram = self
931                .metrics()
932                .session_startup_table_writes_seconds()
933                .clone();
934            Some(async move {
935                fut.wall_time().observe(histogram).await;
936            })
937        } else {
938            None
939        }
940    }
941
942    /// Return the state_revision of the session, which can be used by dependent objects for knowing
943    /// when to re-plan due to session state changes.
944    pub fn state_revision(&self) -> u64 {
945        self.state_revision
946    }
947}
948
949/// A prepared statement.
950#[derive(Derivative, Clone)]
951#[derivative(Debug)]
952pub struct PreparedStatement {
953    stmt: Option<Statement<Raw>>,
954    desc: StatementDesc,
955    /// The most recent state revision that has verified this statement.
956    pub state_revision: StateRevision,
957    #[derivative(Debug = "ignore")]
958    logging: Arc<QCell<PreparedStatementLoggingInfo>>,
959}
960
961impl PreparedStatement {
962    /// Returns the AST associated with this prepared statement,
963    /// if the prepared statement was not the empty query.
964    pub fn stmt(&self) -> Option<&Statement<Raw>> {
965        self.stmt.as_ref()
966    }
967
968    /// Returns the description of the prepared statement.
969    pub fn desc(&self) -> &StatementDesc {
970        &self.desc
971    }
972
973    /// Returns a handle to the metadata for statement logging.
974    pub fn logging(&self) -> &Arc<QCell<PreparedStatementLoggingInfo>> {
975        &self.logging
976    }
977}
978
979/// A portal represents the execution state of a running or runnable query.
980#[derive(Derivative)]
981#[derivative(Debug)]
982pub struct Portal {
983    /// The statement that is bound to this portal.
984    pub stmt: Option<Arc<Statement<Raw>>>,
985    /// The statement description.
986    pub desc: StatementDesc,
987    /// The most recent state revision that has verified this portal.
988    pub state_revision: StateRevision,
989    /// The bound values for the parameters in the prepared statement, if any.
990    pub parameters: Params,
991    /// The desired output format for each column in the result set.
992    pub result_formats: Vec<Format>,
993    /// A handle to metadata needed for statement logging.
994    #[derivative(Debug = "ignore")]
995    pub logging: Arc<QCell<PreparedStatementLoggingInfo>>,
996    /// The execution state of the portal.
997    #[derivative(Debug = "ignore")]
998    pub state: PortalState,
999    /// Statement lifecycle timestamps coming from `mz-pgwire`.
1000    pub lifecycle_timestamps: Option<LifecycleTimestamps>,
1001}
1002
1003/// A mutable reference to a portal, capturing its state and associated metadata. Importantly, it
1004/// does _not_ give _mutable_ access to `stmt` and `desc`, which means that you do not need to
1005/// increment `Session::state_revision` when modifying fields through a `PortalRefMut`, because the
1006/// portal's meaning is not changed.
1007pub struct PortalRefMut<'a> {
1008    /// The statement that is bound to this portal.
1009    pub stmt: &'a Option<Arc<Statement<Raw>>>,
1010    /// The statement description.
1011    pub desc: &'a StatementDesc,
1012    /// The most recent state revision that has verified this portal.
1013    pub state_revision: &'a mut StateRevision,
1014    /// The bound values for the parameters in the prepared statement, if any.
1015    pub parameters: &'a mut Params,
1016    /// The desired output format for each column in the result set.
1017    pub result_formats: &'a mut Vec<Format>,
1018    /// A handle to metadata needed for statement logging.
1019    pub logging: &'a mut Arc<QCell<PreparedStatementLoggingInfo>>,
1020    /// The execution state of the portal.
1021    pub state: &'a mut PortalState,
1022    /// Statement lifecycle timestamps coming from `mz-pgwire`.
1023    pub lifecycle_timestamps: &'a mut Option<LifecycleTimestamps>,
1024}
1025
1026/// Points to a revision of catalog state and session state. When the current revisions are not the
1027/// same as the revisions when a prepared statement or a portal was described, we need to check
1028/// whether the description is still valid.
1029#[derive(Debug, Clone, Copy, PartialEq)]
1030pub struct StateRevision {
1031    /// A revision of the catalog.
1032    pub catalog_revision: u64,
1033    /// A revision of the session state.
1034    pub session_state_revision: u64,
1035}
1036
1037/// Execution states of a portal.
1038pub enum PortalState {
1039    /// Portal not yet started.
1040    NotStarted,
1041    /// Portal is a rows-returning statement in progress with 0 or more rows
1042    /// remaining.
1043    InProgress(Option<InProgressRows>),
1044    /// Portal has completed and should not be re-executed. If the optional string
1045    /// is present, it is returned as a CommandComplete tag, otherwise an error
1046    /// is sent.
1047    Completed(Option<String>),
1048}
1049
1050/// State of an in-progress, rows-returning portal.
1051pub struct InProgressRows {
1052    /// The current batch of rows.
1053    pub current: Option<Box<dyn RowIterator + Send + Sync>>,
1054    /// A stream from which to fetch more row batches.
1055    pub remaining: RecordFirstRowStream,
1056}
1057
1058impl InProgressRows {
1059    /// Creates a new InProgressRows from a batch stream.
1060    pub fn new(remaining: RecordFirstRowStream) -> Self {
1061        Self {
1062            current: None,
1063            remaining,
1064        }
1065    }
1066
1067    /// Determines whether the underlying stream has ended and there are also no more rows
1068    /// stashed in `current`.
1069    pub fn no_more_rows(&self) -> bool {
1070        self.remaining.no_more_rows && self.current.is_none()
1071    }
1072}
1073
1074/// A stream of batched rows delivered to a client writer.
1075///
1076/// This is a boxed stream, not a bare channel receiver, so the coordinator can
1077/// insert an adapter between itself and the client writer. The SUBSCRIBE path
1078/// wraps the receiver in a byte-accounting `map` whose closure type cannot be
1079/// named, so it has to be boxed. Consumers box the receiver into a
1080/// `RecordFirstRowStream` regardless, so this adds no allocation.
1081pub type RowBatchStream = Box<dyn futures::Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>;
1082
1083/// Part of statement lifecycle. These are timestamps that come from the Adapter frontend
1084/// (`mz-pgwire`) part of the lifecycle.
1085#[derive(Debug, Clone)]
1086pub struct LifecycleTimestamps {
1087    /// When the query was received. More specifically, when the tokio recv returned.
1088    /// For a Simple Query, this is for the whole query, for the Extended Query flow, this is only
1089    /// for `FrontendMessage::Execute`. (This means that this is after parsing for the
1090    /// Extended Query flow.)
1091    pub received: EpochMillis,
1092}
1093
1094impl LifecycleTimestamps {
1095    /// Creates a new `LifecycleTimestamps`.
1096    pub fn new(received: EpochMillis) -> Self {
1097        Self { received }
1098    }
1099}
1100
1101/// The transaction status of a session.
1102///
1103/// PostgreSQL's transaction states are in backend/access/transam/xact.c.
1104#[derive(Debug)]
1105pub enum TransactionStatus {
1106    /// Idle. Matches `TBLOCK_DEFAULT`.
1107    Default,
1108    /// Running a transaction started outside of any `BEGIN`. Matches
1109    /// `TBLOCK_STARTED`. In PostgreSQL, when using the extended query protocol, this
1110    /// may be upgraded into multi-statement implicit query (see [`Self::InTransactionImplicit`]).
1111    /// Additionally, some statements may trigger an eager commit of the implicit transaction,
1112    /// see: <https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f92944137>. In
1113    /// Materialize we never upgrade it. We eagerly commit it unless it can take on further
1114    /// statements of the same pipeline, see [`Self::may_span_pipeline`].
1115    Started(Transaction),
1116    /// Currently in a transaction issued from a `BEGIN`. Matches `TBLOCK_INPROGRESS`.
1117    InTransaction(Transaction),
1118    /// Currently in an implicit transaction started from a multi-statement query
1119    /// with more than 1 statements. Matches `TBLOCK_IMPLICIT_INPROGRESS`.
1120    InTransactionImplicit(Transaction),
1121    /// In a failed transaction. Matches `TBLOCK_ABORT`.
1122    Failed(Transaction),
1123}
1124
1125impl TransactionStatus {
1126    /// Extracts the inner transaction ops and write lock guard if not failed.
1127    pub fn into_ops_and_lock_guard(self) -> (Option<TransactionOps>, Option<WriteLocks>) {
1128        match self {
1129            TransactionStatus::Default | TransactionStatus::Failed(_) => (None, None),
1130            TransactionStatus::Started(txn)
1131            | TransactionStatus::InTransaction(txn)
1132            | TransactionStatus::InTransactionImplicit(txn) => {
1133                (Some(txn.ops), txn.write_lock_guards)
1134            }
1135        }
1136    }
1137
1138    /// Exposes the inner transaction.
1139    pub fn inner(&self) -> Option<&Transaction> {
1140        match self {
1141            TransactionStatus::Default => None,
1142            TransactionStatus::Started(txn)
1143            | TransactionStatus::InTransaction(txn)
1144            | TransactionStatus::InTransactionImplicit(txn)
1145            | TransactionStatus::Failed(txn) => Some(txn),
1146        }
1147    }
1148
1149    /// Exposes the inner transaction.
1150    pub fn inner_mut(&mut self) -> Option<&mut Transaction> {
1151        match self {
1152            TransactionStatus::Default => None,
1153            TransactionStatus::Started(txn)
1154            | TransactionStatus::InTransaction(txn)
1155            | TransactionStatus::InTransactionImplicit(txn)
1156            | TransactionStatus::Failed(txn) => Some(txn),
1157        }
1158    }
1159
1160    /// Whether the transaction's ops are DDL.
1161    pub fn is_ddl(&self) -> bool {
1162        match self {
1163            TransactionStatus::Default => false,
1164            TransactionStatus::Started(txn)
1165            | TransactionStatus::InTransaction(txn)
1166            | TransactionStatus::InTransactionImplicit(txn)
1167            | TransactionStatus::Failed(txn) => {
1168                matches!(txn.ops, TransactionOps::DDL { .. })
1169            }
1170        }
1171    }
1172
1173    /// Expresses whether or not the transaction was implicitly started.
1174    /// However, its negation does not imply explicitly started.
1175    pub fn is_implicit(&self) -> bool {
1176        match self {
1177            TransactionStatus::Started(_) | TransactionStatus::InTransactionImplicit(_) => true,
1178            TransactionStatus::Default
1179            | TransactionStatus::InTransaction(_)
1180            | TransactionStatus::Failed(_) => false,
1181        }
1182    }
1183
1184    /// Whether this implicit transaction may stay open for the rest of its
1185    /// extended-protocol pipeline, so that the pipeline commits or rolls back as a
1186    /// unit like PostgreSQL's.
1187    ///
1188    /// Only writes may, because they are merely staged. A read already pinned its
1189    /// timestamp without timedomain read holds, so a second read could neither join
1190    /// that timestamp nor pick its own.
1191    pub fn may_span_pipeline(&self) -> bool {
1192        match self {
1193            TransactionStatus::Started(txn) => match &txn.ops {
1194                TransactionOps::Writes(_) => true,
1195                TransactionOps::None
1196                | TransactionOps::Peeks { .. }
1197                | TransactionOps::Subscribe
1198                | TransactionOps::SingleStatement { .. }
1199                | TransactionOps::DDL { .. } => false,
1200            },
1201            TransactionStatus::Default
1202            | TransactionStatus::InTransaction(_)
1203            | TransactionStatus::InTransactionImplicit(_)
1204            | TransactionStatus::Failed(_) => false,
1205        }
1206    }
1207
1208    /// Whether the transaction may contain multiple statements.
1209    pub fn is_in_multi_statement_transaction(&self) -> bool {
1210        match self {
1211            TransactionStatus::InTransaction(_) | TransactionStatus::InTransactionImplicit(_) => {
1212                true
1213            }
1214            TransactionStatus::Default
1215            | TransactionStatus::Started(_)
1216            | TransactionStatus::Failed(_) => false,
1217        }
1218    }
1219
1220    /// Whether a statement other than the current one can belong to this
1221    /// transaction, so committing here would commit more than this statement.
1222    ///
1223    /// This is [`Self::is_in_multi_statement_transaction`] widened to cover the
1224    /// `Started` trap. An extended-protocol pipeline stays `Started` from its
1225    /// first statement until `Sync`, so a `Started` transaction already holding
1226    /// ops has a pipeline accumulating in it, and a statement running now runs
1227    /// alongside those ops.
1228    ///
1229    /// Callers must evaluate this before the current statement stages ops of
1230    /// its own. Afterwards `contains_ops` reports the statement's own ops and
1231    /// every statement looks like it shares a transaction.
1232    ///
1233    /// Reports true for a `Failed` transaction that holds ops, since
1234    /// `contains_ops` reads through to the inner transaction in that state.
1235    /// Callers that treat a failed transaction as one a statement may run in
1236    /// need their own check, because pgwire admits only `COMMIT` and `ROLLBACK`
1237    /// once a transaction has failed and so nothing else can observe the
1238    /// difference.
1239    pub fn may_share_transaction_with_other_statements(&self) -> bool {
1240        self.is_in_multi_statement_transaction() || self.contains_ops()
1241    }
1242
1243    /// Whether we are in a multi-statement transaction, AND the query is immediate.
1244    pub fn in_immediate_multi_stmt_txn(&self, when: &QueryWhen) -> bool {
1245        self.is_in_multi_statement_transaction() && when == &QueryWhen::Immediately
1246    }
1247
1248    /// Grants the writes lock to the inner transaction, returning an error if the transaction
1249    /// has already been granted write locks.
1250    ///
1251    /// # Panics
1252    /// If `self` is `TransactionStatus::Default`, which indicates that the
1253    /// transaction is idle, which is not appropriate to assign the
1254    /// coordinator's write lock to.
1255    ///
1256    pub fn try_grant_write_locks(&mut self, guards: WriteLocks) -> Result<(), &WriteLocks> {
1257        match self {
1258            TransactionStatus::Default => panic!("cannot grant write lock to txn not yet started"),
1259            TransactionStatus::Started(txn)
1260            | TransactionStatus::InTransaction(txn)
1261            | TransactionStatus::InTransactionImplicit(txn)
1262            | TransactionStatus::Failed(txn) => txn.try_grant_write_locks(guards),
1263        }
1264    }
1265
1266    /// Returns the currently held [`WriteLocks`], if this transaction holds any.
1267    pub fn write_locks(&self) -> Option<&WriteLocks> {
1268        match self {
1269            TransactionStatus::Default => None,
1270            TransactionStatus::Started(txn)
1271            | TransactionStatus::InTransaction(txn)
1272            | TransactionStatus::InTransactionImplicit(txn)
1273            | TransactionStatus::Failed(txn) => txn.write_lock_guards.as_ref(),
1274        }
1275    }
1276
1277    /// The timeline of the transaction, if one exists.
1278    pub fn timeline(&self) -> Option<Timeline> {
1279        match self {
1280            TransactionStatus::Default => None,
1281            TransactionStatus::Started(txn)
1282            | TransactionStatus::InTransaction(txn)
1283            | TransactionStatus::InTransactionImplicit(txn)
1284            | TransactionStatus::Failed(txn) => txn.timeline(),
1285        }
1286    }
1287
1288    /// The cluster of the transaction, if one exists.
1289    pub fn cluster(&self) -> Option<ClusterId> {
1290        match self {
1291            TransactionStatus::Default => None,
1292            TransactionStatus::Started(txn)
1293            | TransactionStatus::InTransaction(txn)
1294            | TransactionStatus::InTransactionImplicit(txn)
1295            | TransactionStatus::Failed(txn) => txn.cluster(),
1296        }
1297    }
1298
1299    /// Snapshot of the catalog that reflects DDL operations run in this transaction.
1300    pub fn catalog_state(&self) -> Option<&CatalogState> {
1301        match self.inner() {
1302            Some(Transaction {
1303                ops: TransactionOps::DDL { state, .. },
1304                ..
1305            }) => Some(state),
1306            _ => None,
1307        }
1308    }
1309
1310    /// Reports whether any operations have been executed as part of this transaction
1311    pub fn contains_ops(&self) -> bool {
1312        match self.inner() {
1313            Some(txn) => txn.contains_ops(),
1314            None => false,
1315        }
1316    }
1317
1318    /// Checks whether the current state of this transaction allows writes
1319    /// (adding write ops).
1320    pub fn allows_writes(&self) -> bool {
1321        match self {
1322            TransactionStatus::Started(Transaction { ops, access, .. })
1323            | TransactionStatus::InTransaction(Transaction { ops, access, .. })
1324            | TransactionStatus::InTransactionImplicit(Transaction { ops, access, .. }) => {
1325                match ops {
1326                    TransactionOps::None => access != &Some(TransactionAccessMode::ReadOnly),
1327                    TransactionOps::Peeks { determination, .. } => {
1328                        // We can switch a peek-only transaction to a write
1329                        // transaction only if the peeks thus far are constant
1330                        // (i.e. they do not have a timestamp) and the
1331                        // transaction is not explicitly marked read-only.
1332                        access != &Some(TransactionAccessMode::ReadOnly)
1333                            && !determination.timestamp_context.contains_timestamp()
1334                    }
1335                    TransactionOps::Subscribe => false,
1336                    TransactionOps::Writes(_) => true,
1337                    TransactionOps::SingleStatement { .. } => false,
1338                    TransactionOps::DDL { .. } => false,
1339                }
1340            }
1341            TransactionStatus::Default | TransactionStatus::Failed(_) => {
1342                unreachable!()
1343            }
1344        }
1345    }
1346
1347    /// Adds operations to the current transaction. An error is produced if they cannot be merged
1348    /// (i.e., a timestamp-dependent read cannot be merged to an insert).
1349    ///
1350    /// The `DDL` variant is an exception and does not merge operations, but instead overwrites the
1351    /// old ops with the new ops. This is correct because it is only used in conjunction with the
1352    /// Dry Run catalog op which returns an error containing all of the ops, and those ops are
1353    /// passed to this function which then overwrites.
1354    ///
1355    /// # Panics
1356    /// If the operations are compatible but the operation metadata doesn't match. Such as reads at
1357    /// different timestamps, reads on different timelines, reads on different clusters, etc. It's
1358    /// up to the caller to make sure these are aligned.
1359    pub fn add_ops(&mut self, add_ops: TransactionOps) -> Result<(), AdapterError> {
1360        match self {
1361            TransactionStatus::Started(Transaction { ops, access, .. })
1362            | TransactionStatus::InTransaction(Transaction { ops, access, .. })
1363            | TransactionStatus::InTransactionImplicit(Transaction { ops, access, .. }) => {
1364                match ops {
1365                    TransactionOps::None => {
1366                        if matches!(access, Some(TransactionAccessMode::ReadOnly))
1367                            && matches!(add_ops, TransactionOps::Writes(_))
1368                        {
1369                            return Err(AdapterError::ReadOnlyTransaction);
1370                        }
1371                        *ops = add_ops;
1372                    }
1373                    TransactionOps::Peeks {
1374                        determination,
1375                        cluster_id,
1376                        requires_linearization,
1377                    } => match add_ops {
1378                        TransactionOps::Peeks {
1379                            determination: add_timestamp_determination,
1380                            cluster_id: add_cluster_id,
1381                            requires_linearization: add_requires_linearization,
1382                        } => {
1383                            assert_eq!(*cluster_id, add_cluster_id);
1384                            match (
1385                                &determination.timestamp_context,
1386                                &add_timestamp_determination.timestamp_context,
1387                            ) {
1388                                (
1389                                    TimestampContext::TimelineTimestamp {
1390                                        timeline: txn_timeline,
1391                                        chosen_ts: txn_ts,
1392                                        oracle_ts: _,
1393                                    },
1394                                    TimestampContext::TimelineTimestamp {
1395                                        timeline: add_timeline,
1396                                        chosen_ts: add_ts,
1397                                        oracle_ts: _,
1398                                    },
1399                                ) => {
1400                                    assert_eq!(txn_timeline, add_timeline);
1401                                    assert_eq!(txn_ts, add_ts);
1402                                }
1403                                (TimestampContext::NoTimestamp, _) => {
1404                                    *determination = add_timestamp_determination
1405                                }
1406                                (_, TimestampContext::NoTimestamp) => {}
1407                            };
1408                            if matches!(requires_linearization, RequireLinearization::NotRequired)
1409                                && matches!(
1410                                    add_requires_linearization,
1411                                    RequireLinearization::Required
1412                                )
1413                            {
1414                                *requires_linearization = add_requires_linearization;
1415                            }
1416                        }
1417                        // If the peeks thus far are constant (i.e. they do not
1418                        // have a timestamp), writes can follow and switch the
1419                        // transaction to a write transaction. But a read-only
1420                        // transaction must still reject the write. Without that
1421                        // check the write would silently turn a read-only
1422                        // transaction into a write transaction, and a later
1423                        // write would then trip the assert in the `Writes` arm
1424                        // below.
1425                        writes @ TransactionOps::Writes(..)
1426                            if !determination.timestamp_context.contains_timestamp() =>
1427                        {
1428                            if matches!(access, Some(TransactionAccessMode::ReadOnly)) {
1429                                return Err(AdapterError::ReadOnlyTransaction);
1430                            }
1431                            *ops = writes;
1432                        }
1433                        _ => return Err(AdapterError::ReadOnlyTransaction),
1434                    },
1435                    TransactionOps::Subscribe => {
1436                        return Err(AdapterError::SubscribeOnlyTransaction);
1437                    }
1438                    TransactionOps::Writes(txn_writes) => match add_ops {
1439                        TransactionOps::Writes(mut add_writes) => {
1440                            // We should have already checked the access above, but make sure we don't miss
1441                            // it anyway.
1442                            assert!(!matches!(access, Some(TransactionAccessMode::ReadOnly)));
1443                            txn_writes.append(&mut add_writes);
1444                        }
1445                        // Iff peeks do not have a timestamp (i.e. they are
1446                        // constant), we can permit them.
1447                        TransactionOps::Peeks { determination, .. }
1448                            if !determination.timestamp_context.contains_timestamp() => {}
1449                        _ => {
1450                            return Err(AdapterError::WriteOnlyTransaction);
1451                        }
1452                    },
1453                    TransactionOps::SingleStatement { .. } => {
1454                        return Err(AdapterError::SingleStatementTransaction);
1455                    }
1456                    TransactionOps::DDL {
1457                        ops: og_ops,
1458                        revision: og_revision,
1459                        state: og_state,
1460                        side_effects,
1461                        snapshot: og_snapshot,
1462                    } => match add_ops {
1463                        TransactionOps::DDL {
1464                            ops: new_ops,
1465                            revision: new_revision,
1466                            side_effects: mut net_new_side_effects,
1467                            state: new_state,
1468                            snapshot: new_snapshot,
1469                        } => {
1470                            if *og_revision != new_revision {
1471                                return Err(AdapterError::DDLTransactionRace);
1472                            }
1473                            // The old og_ops are overwritten, not extended.
1474                            if !new_ops.is_empty() {
1475                                *og_ops = new_ops;
1476                                *og_state = new_state;
1477                                *og_snapshot = new_snapshot;
1478                            }
1479                            side_effects.append(&mut net_new_side_effects);
1480                        }
1481                        _ => return Err(AdapterError::DDLOnlyTransaction),
1482                    },
1483                }
1484            }
1485            TransactionStatus::Default | TransactionStatus::Failed(_) => {
1486                unreachable!()
1487            }
1488        }
1489        Ok(())
1490    }
1491}
1492
1493/// An abstraction allowing us to identify different transactions.
1494pub type TransactionId = u64;
1495
1496impl Default for TransactionStatus {
1497    fn default() -> Self {
1498        TransactionStatus::Default
1499    }
1500}
1501
1502/// State data for transactions.
1503#[derive(Debug)]
1504pub struct Transaction {
1505    /// Plan context.
1506    pub pcx: PlanContext,
1507    /// Transaction operations.
1508    pub ops: TransactionOps,
1509    /// Uniquely identifies the transaction on a per connection basis.
1510    /// Two transactions started from separate connections may share the
1511    /// same ID.
1512    /// If all IDs have been exhausted, this will wrap around back to 0.
1513    pub id: TransactionId,
1514    /// Locks for objects this transaction will operate on.
1515    write_lock_guards: Option<WriteLocks>,
1516    /// Access mode (read only, read write).
1517    access: Option<TransactionAccessMode>,
1518}
1519
1520impl Transaction {
1521    /// Tries to grant the write lock to this transaction for the remainder of its lifetime. Errors
1522    /// if this [`Transaction`] has already been granted write locks.
1523    fn try_grant_write_locks(&mut self, guards: WriteLocks) -> Result<(), &WriteLocks> {
1524        match &mut self.write_lock_guards {
1525            Some(existing) => Err(existing),
1526            locks @ None => {
1527                *locks = Some(guards);
1528                Ok(())
1529            }
1530        }
1531    }
1532
1533    /// The timeline of the transaction, if one exists.
1534    fn timeline(&self) -> Option<Timeline> {
1535        match &self.ops {
1536            TransactionOps::Peeks {
1537                determination:
1538                    TimestampDetermination {
1539                        timestamp_context: TimestampContext::TimelineTimestamp { timeline, .. },
1540                        ..
1541                    },
1542                ..
1543            } => Some(timeline.clone()),
1544            TransactionOps::Peeks { .. }
1545            | TransactionOps::None
1546            | TransactionOps::Subscribe
1547            | TransactionOps::Writes(_)
1548            | TransactionOps::SingleStatement { .. }
1549            | TransactionOps::DDL { .. } => None,
1550        }
1551    }
1552
1553    /// The cluster of the transaction, if one exists.
1554    pub fn cluster(&self) -> Option<ClusterId> {
1555        match &self.ops {
1556            TransactionOps::Peeks { cluster_id, .. } => Some(cluster_id.clone()),
1557            TransactionOps::None
1558            | TransactionOps::Subscribe
1559            | TransactionOps::Writes(_)
1560            | TransactionOps::SingleStatement { .. }
1561            | TransactionOps::DDL { .. } => None,
1562        }
1563    }
1564
1565    /// Reports whether any operations have been executed as part of this transaction
1566    fn contains_ops(&self) -> bool {
1567        !matches!(self.ops, TransactionOps::None)
1568    }
1569}
1570
1571/// A transaction's status code.
1572#[derive(Debug, Clone, Copy)]
1573pub enum TransactionCode {
1574    /// Not currently in a transaction
1575    Idle,
1576    /// Currently in a transaction
1577    InTransaction,
1578    /// Currently in a transaction block which is failed
1579    Failed,
1580}
1581
1582impl From<TransactionCode> for u8 {
1583    fn from(code: TransactionCode) -> Self {
1584        match code {
1585            TransactionCode::Idle => b'I',
1586            TransactionCode::InTransaction => b'T',
1587            TransactionCode::Failed => b'E',
1588        }
1589    }
1590}
1591
1592impl From<TransactionCode> for String {
1593    fn from(code: TransactionCode) -> Self {
1594        char::from(u8::from(code)).to_string()
1595    }
1596}
1597
1598impl From<&TransactionStatus> for TransactionCode {
1599    /// Convert from the Session's version
1600    fn from(status: &TransactionStatus) -> TransactionCode {
1601        match status {
1602            TransactionStatus::Default => TransactionCode::Idle,
1603            TransactionStatus::Started(_) => TransactionCode::InTransaction,
1604            TransactionStatus::InTransaction(_) => TransactionCode::InTransaction,
1605            TransactionStatus::InTransactionImplicit(_) => TransactionCode::InTransaction,
1606            TransactionStatus::Failed(_) => TransactionCode::Failed,
1607        }
1608    }
1609}
1610
1611/// The type of operation being performed by the transaction.
1612///
1613/// This is needed because we currently do not allow mixing reads and writes in
1614/// a transaction. Use this to record what we have done, and what may need to
1615/// happen at commit.
1616#[derive(Derivative)]
1617#[derivative(Debug)]
1618pub enum TransactionOps {
1619    /// The transaction has been initiated, but no statement has yet been executed
1620    /// in it.
1621    None,
1622    /// This transaction has had a peek (`SELECT`, `SUBSCRIBE`). If the inner value
1623    /// is has a timestamp, it must only do other peeks. However, if it doesn't
1624    /// have a timestamp (i.e. the values are constants), the transaction can still
1625    /// perform writes.
1626    Peeks {
1627        /// The timestamp and timestamp related metadata for the peek.
1628        determination: TimestampDetermination,
1629        /// The cluster used to execute peeks.
1630        cluster_id: ClusterId,
1631        /// Whether this peek needs to be linearized.
1632        requires_linearization: RequireLinearization,
1633    },
1634    /// This transaction has done a `SUBSCRIBE` and must do nothing else.
1635    Subscribe,
1636    /// This transaction has had a write (`INSERT`, `UPDATE`, `DELETE`) and must
1637    /// only do other writes, or reads whose timestamp is None (i.e. constants).
1638    Writes(Vec<WriteOp>),
1639    /// This transaction has a prospective statement that will execute during commit.
1640    SingleStatement {
1641        /// The prospective statement.
1642        stmt: Arc<Statement<Raw>>,
1643        /// The statement params.
1644        params: mz_sql::plan::Params,
1645    },
1646    /// This transaction has run some _simple_ DDL and must do nothing else. Any statement/plan that
1647    /// uses this must return false in `must_serialize_ddl()` because this is serialized instead in
1648    /// `sequence_plan()` during `COMMIT`.
1649    DDL {
1650        /// Catalog operations that have already run, and must run before each subsequent op.
1651        ops: Vec<crate::catalog::Op>,
1652        /// In-memory state that reflects the previously applied ops.
1653        state: CatalogState,
1654        /// A list of side effects that should be executed if this DDL transaction commits.
1655        #[derivative(Debug = "ignore")]
1656        side_effects: Vec<
1657            Box<
1658                dyn for<'a> FnOnce(
1659                        &'a mut Coordinator,
1660                        Option<&'a mut ExecuteContext>,
1661                    ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
1662                    + Send
1663                    + Sync,
1664            >,
1665        >,
1666        /// Transient revision of the `Catalog` when this transaction started.
1667        revision: u64,
1668        /// Snapshot of the durable transaction state after the last dry run.
1669        /// Used to initialize the next dry run's transaction so it starts
1670        /// in sync with the accumulated `state`. `None` for the first
1671        /// statement in the transaction (before any dry run).
1672        snapshot: Option<Snapshot>,
1673    },
1674}
1675
1676impl TransactionOps {
1677    fn timestamp_determination(self) -> Option<TimestampDetermination> {
1678        match self {
1679            TransactionOps::Peeks { determination, .. } => Some(determination),
1680            TransactionOps::None
1681            | TransactionOps::Subscribe
1682            | TransactionOps::Writes(_)
1683            | TransactionOps::SingleStatement { .. }
1684            | TransactionOps::DDL { .. } => None,
1685        }
1686    }
1687}
1688
1689impl Default for TransactionOps {
1690    fn default() -> Self {
1691        Self::None
1692    }
1693}
1694
1695/// An `INSERT` waiting to be committed.
1696#[derive(Debug, Clone, PartialEq)]
1697pub struct WriteOp {
1698    /// The target table.
1699    pub id: CatalogItemId,
1700    /// The data rows.
1701    pub rows: TableData,
1702}
1703
1704/// Whether a transaction requires linearization.
1705#[derive(Debug)]
1706pub enum RequireLinearization {
1707    /// Linearization is required.
1708    Required,
1709    /// Linearization is not required.
1710    NotRequired,
1711}
1712
1713impl From<&ExplainContext> for RequireLinearization {
1714    fn from(ctx: &ExplainContext) -> Self {
1715        match ctx {
1716            ExplainContext::None | ExplainContext::PlanInsightsNotice(_) => {
1717                RequireLinearization::Required
1718            }
1719            _ => RequireLinearization::NotRequired,
1720        }
1721    }
1722}
1723
1724/// A complete set of exclusive locks for writing to collections identified by [`CatalogItemId`]s.
1725///
1726/// To prevent deadlocks between two sessions, we do not allow acquiring a partial set of locks.
1727#[derive(Debug)]
1728pub struct WriteLocks {
1729    locks: BTreeMap<CatalogItemId, tokio::sync::OwnedMutexGuard<()>>,
1730    /// Connection that currently holds these locks, used for tracing purposes only.
1731    conn_id: ConnectionId,
1732}
1733
1734impl WriteLocks {
1735    /// Create a [`WriteLocksBuilder`] pre-defining all of the locks we need.
1736    ///
1737    /// When "finishing" the builder with [`WriteLocksBuilder::all_or_nothing`], if we haven't
1738    /// acquired all of the necessary locks we drop any partially acquired ones.
1739    pub fn builder(sources: impl IntoIterator<Item = CatalogItemId>) -> WriteLocksBuilder {
1740        let locks = sources.into_iter().map(|gid| (gid, None)).collect();
1741        WriteLocksBuilder { locks }
1742    }
1743
1744    /// Validate this set of [`WriteLocks`] is sufficient for the provided collections.
1745    /// Dropping the currently held locks if it's not.
1746    pub fn validate(
1747        self,
1748        collections: impl Iterator<Item = CatalogItemId>,
1749    ) -> Result<Self, BTreeSet<CatalogItemId>> {
1750        let mut missing = BTreeSet::new();
1751        for collection in collections {
1752            if !self.locks.contains_key(&collection) {
1753                missing.insert(collection);
1754            }
1755        }
1756
1757        if missing.is_empty() {
1758            Ok(self)
1759        } else {
1760            // Explicitly drop the already acquired locks.
1761            drop(self);
1762            Err(missing)
1763        }
1764    }
1765}
1766
1767impl Drop for WriteLocks {
1768    fn drop(&mut self) {
1769        // We may have merged the locks into GroupCommitWriteLocks, thus it could be empty.
1770        if !self.locks.is_empty() {
1771            tracing::info!(
1772                conn_id = %self.conn_id,
1773                locks = ?self.locks,
1774                "dropping write locks",
1775            );
1776        }
1777    }
1778}
1779
1780/// A builder struct that helps us acquire all of the locks we need, or none of them.
1781///
1782/// See [`WriteLocks::builder`].
1783#[derive(Debug)]
1784pub struct WriteLocksBuilder {
1785    locks: BTreeMap<CatalogItemId, Option<tokio::sync::OwnedMutexGuard<()>>>,
1786}
1787
1788impl WriteLocksBuilder {
1789    /// Adds a lock to this builder.
1790    pub fn insert_lock(&mut self, id: CatalogItemId, lock: tokio::sync::OwnedMutexGuard<()>) {
1791        self.locks.insert(id, Some(lock));
1792    }
1793
1794    /// Finish this builder by returning either all of the necessary locks, or none of them.
1795    ///
1796    /// If we fail to acquire all of the locks, returns one of the [`CatalogItemId`]s that we
1797    /// failed to acquire a lock for, that should be awaited so we know when to run again.
1798    pub fn all_or_nothing(self, conn_id: &ConnectionId) -> Result<WriteLocks, CatalogItemId> {
1799        let (locks, missing): (BTreeMap<_, _>, BTreeSet<_>) =
1800            self.locks
1801                .into_iter()
1802                .partition_map(|(gid, lock)| match lock {
1803                    Some(lock) => itertools::Either::Left((gid, lock)),
1804                    None => itertools::Either::Right(gid),
1805                });
1806
1807        match missing.iter().next() {
1808            None => {
1809                tracing::info!(%conn_id, ?locks, "acquired write locks");
1810                Ok(WriteLocks {
1811                    locks,
1812                    conn_id: conn_id.clone(),
1813                })
1814            }
1815            Some(gid) => {
1816                tracing::info!(?missing, "failed to acquire write locks");
1817                // Explicitly drop the already acquired locks.
1818                drop(locks);
1819                Err(*gid)
1820            }
1821        }
1822    }
1823}
1824
1825/// Collection of [`WriteLocks`] gathered during [`stage_group_commit`].
1826///
1827/// Note: This struct should __never__ be used outside of group commit because it attempts to merge
1828/// together several collections of [`WriteLocks`] which if not done carefully can cause deadlocks
1829/// or consistency violations.
1830///
1831/// We must prevent writes from occurring to tables during read then write plans (e.g. `UPDATE`)
1832/// but we can allow blind writes (e.g. `INSERT`) to get committed concurrently at the same
1833/// timestamp when submitting the updates from a read then write plan.
1834///
1835/// Naively it would seem as though we could allow blind writes to occur whenever as blind writes
1836/// could never cause invalid retractions, but it could cause us to violate serializability because
1837/// there is no total order we could define for the transactions. Consider the following scenario:
1838///
1839/// ```text
1840/// table: foo
1841///
1842///  a | b
1843/// --------
1844///  x   2
1845///  y   3
1846///  z   4
1847///
1848/// -- Session(A)
1849/// -- read then write plan, reads at t0, writes at t3, transaction Ta
1850/// DELETE FROM foo WHERE b % 2 = 0;
1851///
1852///
1853/// -- Session(B)
1854/// -- blind write into foo, writes at t1, transaction Tb
1855/// INSERT INTO foo VALUES ('q', 6);
1856/// -- select from foo, reads at t2, transaction Tc
1857/// SELECT * FROM foo;
1858///
1859///
1860/// The times these operations occur at are ordered:
1861/// t0 < t1 < t2 < t3
1862///
1863/// Given the timing of the operations, the transactions must have the following order:
1864///
1865/// * Ta does not observe ('q', 6), so Ta < Tb
1866/// * Tc does observe ('q', 6), so Tb < Tc
1867/// * Tc does not observe the retractions from Ta, so Tc < Ta
1868///
1869/// For total order to exist, Ta < Tb < Tc < Ta, which is impossible.
1870/// ```
1871///
1872/// [`stage_group_commit`]: super::coord::Coordinator::stage_group_commit
1873#[derive(Debug, Default)]
1874pub(crate) struct GroupCommitWriteLocks {
1875    locks: BTreeMap<CatalogItemId, tokio::sync::OwnedMutexGuard<()>>,
1876}
1877
1878impl GroupCommitWriteLocks {
1879    /// Merge a set of [`WriteLocks`] into this collection for group commit.
1880    pub fn merge(&mut self, mut locks: WriteLocks) {
1881        // Note: Ideally we would use `.drain`, but that method doesn't exist for BTreeMap.
1882        //
1883        // See: <https://github.com/rust-lang/rust/issues/81074>
1884        let existing = std::mem::take(&mut locks.locks);
1885        self.locks.extend(existing);
1886    }
1887
1888    /// Absorbs a disjoint group-commit lock collection.
1889    pub fn extend(&mut self, mut other: GroupCommitWriteLocks) {
1890        assert!(
1891            self.locks.keys().all(|id| !other.locks.contains_key(id)),
1892            "separately staged group commits must have disjoint lock sets"
1893        );
1894        self.locks.extend(std::mem::take(&mut other.locks));
1895    }
1896
1897    /// Inserts a single lock, keyed by the collection it guards.
1898    pub fn insert_lock(&mut self, id: CatalogItemId, lock: tokio::sync::OwnedMutexGuard<()>) {
1899        self.locks.insert(id, lock);
1900    }
1901
1902    /// Returns the collections we're missing locks for, if any.
1903    pub fn missing_locks(
1904        &self,
1905        writes: impl Iterator<Item = CatalogItemId>,
1906    ) -> BTreeSet<CatalogItemId> {
1907        let mut missing = BTreeSet::new();
1908        for write in writes {
1909            if !self.locks.contains_key(&write) {
1910                missing.insert(write);
1911            }
1912        }
1913        missing
1914    }
1915}
1916
1917impl Drop for GroupCommitWriteLocks {
1918    fn drop(&mut self) {
1919        if !self.locks.is_empty() {
1920            tracing::info!(
1921                locks = ?self.locks,
1922                "dropping group commit write locks",
1923            );
1924        }
1925    }
1926}