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