1use std::collections::BTreeMap;
11use std::fmt::{Debug, Display, Formatter};
12use std::future::Future;
13use std::pin::{self, Pin};
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use anyhow::bail;
18use chrono::{DateTime, Utc};
19use derivative::Derivative;
20use futures::{FutureExt, Stream, StreamExt};
21use itertools::Itertools;
22use mz_adapter_types::connection::{ConnectionId, ConnectionIdType};
23use mz_auth::password::Password;
24use mz_auth::{Authenticated, AuthenticatorKind};
25use mz_build_info::BuildInfo;
26use mz_compute_types::ComputeInstanceId;
27use mz_expr::UnmaterializableFunc;
28use mz_expr::{CollectionPlan, RowSetFinishing};
29use mz_ore::channel::OneshotReceiverExt;
30use mz_ore::collections::CollectionExt;
31use mz_ore::id_gen::{IdAllocator, IdAllocatorInnerBitSet, MAX_ORG_ID, org_id_conn_bits};
32use mz_ore::instrument;
33use mz_ore::now::{EpochMillis, NowFn, to_datetime};
34use mz_ore::str::StrExt;
35use mz_ore::task::AbortOnDropHandle;
36use mz_ore::thread::JoinOnDropHandle;
37use mz_ore::tracing::OpenTelemetryContext;
38use mz_repr::user::InternalUserMetadata;
39use mz_repr::{CatalogItemId, ColumnIndex, SqlScalarType};
40use mz_sql::ast::ConstantVisitor;
41use mz_sql::ast::{Raw, Statement};
42use mz_sql::catalog::{EnvironmentId, SessionCatalog};
43use mz_sql::plan::{MutationKind, Plan, ReadThenWritePlan};
44use mz_sql::session::hint::ApplicationNameHint;
45use mz_sql::session::metadata::SessionMetadata;
46use mz_sql::session::user::SUPPORT_USER;
47use mz_sql::session::vars::{CLUSTER, OwnedVarInput, SystemVars, Var};
48use mz_sql_parser::ast::display::AstDisplay;
49use mz_sql_parser::ast::{InsertStatement, StatementKind};
50use mz_sql_parser::parser::{ParserStatementError, StatementParseResult};
51use prometheus::Histogram;
52use serde_json::json;
53use tokio::sync::{mpsc, oneshot};
54use tracing::{debug, error};
55use uuid::Uuid;
56
57use crate::catalog::Catalog;
58use crate::command::{
59 CatalogDump, CatalogSnapshot, Command, CopyFromStdinWriter, ExecuteResponse, Response,
60 SASLChallengeResponse, SASLVerifyProofResponse, SuperuserAttribute,
61};
62use crate::config::{ScopedParameters, ScopedParametersScope, SystemParameterFrontend};
63use crate::coord::read_then_write::DependencyPolicy;
64use crate::coord::{Coordinator, ExecuteContextGuard};
65use crate::error::AdapterError;
66use crate::frontend_read_then_write::{
67 WriteAttemptState, WriteCancellation, contains_mz_now, validate_selection_dependencies,
68};
69use crate::metrics::Metrics;
70use crate::notice::ExecutionTimeKind;
71use crate::optimize::dataflows::{EvalTime, ExprPrepOneShot};
72use crate::optimize::{self, Optimize, OptimizerError};
73use crate::peek_client::{CoordinatorClient, ExecutionLogging, TakeOver};
74use crate::session::{
75 EndTransactionAction, PreparedStatement, Session, SessionConfig, StateRevision, TransactionId,
76 TransactionStatus,
77};
78use crate::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
79use crate::telemetry::{self, EventDetails, SegmentClientExt, StatementFailureType};
80use crate::webhook::AppendWebhookResponse;
81use crate::{
82 AdapterNotice, AppendWebhookError, ExecutionTime, PeekClient, PeekResponseUnary,
83 StartupResponse,
84};
85
86pub struct Handle {
92 pub(crate) session_id: Uuid,
93 pub(crate) start_instant: Instant,
94 pub(crate) _thread: JoinOnDropHandle<()>,
95}
96
97impl Handle {
98 pub fn session_id(&self) -> Uuid {
104 self.session_id
105 }
106
107 pub fn start_instant(&self) -> Instant {
109 self.start_instant
110 }
111}
112
113#[derive(Debug, Clone)]
121pub struct Client {
122 build_info: &'static BuildInfo,
123 inner_cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
124 id_alloc: IdAllocator<IdAllocatorInnerBitSet>,
125 now: NowFn,
126 metrics: Metrics,
127 environment_id: EnvironmentId,
128 segment_client: Option<mz_segment::Client>,
129}
130
131impl Client {
132 pub(crate) fn new(
133 build_info: &'static BuildInfo,
134 cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
135 metrics: Metrics,
136 now: NowFn,
137 environment_id: EnvironmentId,
138 segment_client: Option<mz_segment::Client>,
139 ) -> Client {
140 let env_lower = org_id_conn_bits(&environment_id.organization_id());
148 Client {
149 build_info,
150 inner_cmd_tx: cmd_tx,
151 id_alloc: IdAllocator::new(1, MAX_ORG_ID, env_lower),
152 now,
153 metrics,
154 environment_id,
155 segment_client,
156 }
157 }
158
159 pub fn new_conn_id(&self) -> Result<ConnectionId, AdapterError> {
161 self.id_alloc.alloc().ok_or(AdapterError::IdExhaustionError)
162 }
163
164 pub fn new_session(&self, config: SessionConfig, _authenticated: Authenticated) -> Session {
170 Session::new(self.build_info, config, self.metrics().session_metrics())
174 }
175
176 pub async fn authenticate(
180 &self,
181 user: &String,
182 password: &Password,
183 ) -> Result<Authenticated, AdapterError> {
184 let (tx, rx) = oneshot::channel();
185 self.send(Command::AuthenticatePassword {
186 role_name: user.to_string(),
187 password: Some(password.clone()),
188 tx,
189 });
190 rx.await.expect("sender dropped")?;
191 Ok(Authenticated)
192 }
193
194 pub async fn generate_sasl_challenge(
197 &self,
198 user: &String,
199 client_nonce: &String,
200 ) -> Result<SASLChallengeResponse, AdapterError> {
201 let (tx, rx) = oneshot::channel();
202 self.send(Command::AuthenticateGetSASLChallenge {
203 role_name: user.to_string(),
204 nonce: client_nonce.to_string(),
205 tx,
206 });
207 let response = rx.await.expect("sender dropped")?;
208 Ok(response)
209 }
210
211 pub async fn verify_sasl_proof(
214 &self,
215 user: &String,
216 proof: &String,
217 nonce: &String,
218 mock_hash: &String,
219 ) -> Result<(SASLVerifyProofResponse, Authenticated), AdapterError> {
220 let (tx, rx) = oneshot::channel();
221 self.send(Command::AuthenticateVerifySASLProof {
222 role_name: user.to_string(),
223 proof: proof.to_string(),
224 auth_message: nonce.to_string(),
225 mock_hash: mock_hash.to_string(),
226 tx,
227 });
228 let response = rx.await.expect("sender dropped")?;
229 Ok((response, Authenticated))
230 }
231
232 pub async fn role_can_login(&self, role_name: &str) -> Result<(), AdapterError> {
234 let (tx, rx) = oneshot::channel();
235 self.send(Command::CheckRoleCanLogin {
236 role_name: role_name.to_string(),
237 tx,
238 });
239 rx.await.expect("sender dropped")
240 }
241
242 #[mz_ore::instrument(level = "debug")]
251 pub async fn startup(&self, session: Session) -> Result<SessionClient, AdapterError> {
252 let user = session.user().clone();
253 let conn_id = session.conn_id().clone();
254 let secret_key = session.secret_key();
255 let uuid = session.uuid();
256 let client_ip = session.client_ip();
257 let application_name = session.application_name().into();
258 let notice_tx = session.retain_notice_transmitter();
259
260 let (tx, rx) = oneshot::channel();
261
262 let rx = rx.with_guard(|resp: Result<StartupResponse, _>| {
273 if resp.is_ok() {
274 self.send(Command::Terminate {
275 conn_id: conn_id.clone(),
276 tx: None,
277 });
278 }
279 });
280
281 self.send(Command::Startup {
282 tx,
283 user,
284 conn_id: conn_id.clone(),
285 secret_key,
286 uuid,
287 client_ip: client_ip.copied(),
288 application_name,
289 notice_tx,
290 });
291
292 let response = rx.await.expect("sender dropped")?;
295
296 let StartupResponse {
300 role_id,
301 write_notify,
302 session_defaults,
303 catalog,
304 storage_collections,
305 transient_id_gen,
306 optimizer_metrics,
307 persist_client,
308 statement_logging_frontend,
309 superuser_attribute,
310 occ_write_semaphore,
311 group_commit_notifier,
312 read_only,
313 frontend_cancel_rx,
314 } = response;
315
316 let peek_client = PeekClient::new(
317 CoordinatorClient::Session(self.clone()),
318 &catalog,
319 storage_collections,
320 transient_id_gen,
321 optimizer_metrics,
322 persist_client,
323 statement_logging_frontend,
324 occ_write_semaphore,
325 group_commit_notifier,
326 read_only,
327 );
328
329 let mut client = SessionClient {
330 inner: Some(self.clone()),
331 session: Some(session),
332 timeouts: Timeout::new(),
333 environment_id: self.environment_id.clone(),
334 segment_client: self.segment_client.clone(),
335 peek_client,
336 enable_frontend_transaction_completion: false,
337 frontend_cancel_rx,
338 };
339
340 let session = client.session();
341
342 if let SuperuserAttribute(Some(superuser)) = superuser_attribute {
345 session.apply_internal_user_metadata(InternalUserMetadata { superuser });
346 }
347
348 session.initialize_role_metadata(role_id);
349 let vars_mut = session.vars_mut();
350 for (name, val) in session_defaults {
351 if let Err(err) = vars_mut.set_default(&name, val.borrow()) {
352 tracing::error!("failed to set peristed default, {err:?}");
355 }
356 }
357 session
358 .vars_mut()
359 .end_transaction(EndTransactionAction::Commit);
360
361 session.set_builtin_table_updates(write_notify);
369
370 let catalog = catalog.for_session(session);
371
372 let cluster_active = session.vars().cluster().to_string();
373 if session.vars().welcome_message() {
374 let cluster_info = if catalog.resolve_cluster(Some(&cluster_active)).is_err() {
375 format!("{cluster_active} (does not exist)")
376 } else {
377 cluster_active.to_string()
378 };
379
380 session.add_notice(AdapterNotice::Welcome(format!(
384 "connected to Materialize v{}
385 Environment ID: {}
386 Region: {}
387 User: {}
388 Cluster: {}
389 Database: {}
390 {}
391 Session UUID: {}
392
393Issue a SQL query to get started. Need help?
394 View documentation: https://materialize.com/s/docs
395 Join our Slack community: https://materialize.com/s/chat
396 ",
397 session.vars().build_info().semver_version(),
398 self.environment_id,
399 self.environment_id.region(),
400 session.vars().user().name,
401 cluster_info,
402 session.vars().database(),
403 match session.vars().search_path() {
404 [schema] => format!("Schema: {}", schema),
405 schemas => format!(
406 "Search path: {}",
407 schemas.iter().map(|id| id.to_string()).join(", ")
408 ),
409 },
410 session.uuid(),
411 )));
412 }
413
414 if session.vars().current_object_missing_warnings() {
415 if catalog.active_database().is_none() {
416 let db = session.vars().database().into();
417 session.add_notice(AdapterNotice::UnknownSessionDatabase(db));
418 }
419 }
420
421 let cluster_var = session
424 .vars()
425 .inspect(CLUSTER.name())
426 .expect("cluster should exist");
427 if session.vars().current_object_missing_warnings()
428 && catalog.resolve_cluster(Some(&cluster_active)).is_err()
429 {
430 let cluster_notice = 'notice: {
431 if cluster_var.inspect_session_value().is_some() {
432 break 'notice Some(AdapterNotice::DefaultClusterDoesNotExist {
433 name: cluster_active,
434 kind: "session",
435 suggested_action: "Pick an extant cluster with SET CLUSTER = name. Run SHOW CLUSTERS to see available clusters.".into(),
436 });
437 }
438
439 let role_default = catalog.get_role(catalog.active_role_id());
440 let role_cluster = match role_default.vars().get(CLUSTER.name()) {
441 Some(OwnedVarInput::Flat(name)) => Some(name),
442 None => None,
443 Some(v @ OwnedVarInput::SqlSet(_)) => {
445 tracing::warn!(?v, "SqlSet found for cluster Role Default");
446 break 'notice None;
447 }
448 };
449
450 let alter_role = "with `ALTER ROLE <role> SET cluster TO <cluster>;`";
451 match role_cluster {
452 None => Some(AdapterNotice::DefaultClusterDoesNotExist {
454 name: cluster_active,
455 kind: "system",
456 suggested_action: format!(
457 "Set a default cluster for the current role {alter_role}."
458 ),
459 }),
460 Some(_) => Some(AdapterNotice::DefaultClusterDoesNotExist {
462 name: cluster_active,
463 kind: "role",
464 suggested_action: format!(
465 "Change the default cluster for the current role {alter_role}."
466 ),
467 }),
468 }
469 };
470
471 if let Some(notice) = cluster_notice {
472 session.add_notice(notice);
473 }
474 }
475
476 let enable_frontend_transaction_completion =
477 mz_sql::session::vars::ENABLE_FRONTEND_TRANSACTION_COMPLETION
478 .require(catalog.system_vars())
479 .is_ok();
480 client.enable_frontend_transaction_completion = enable_frontend_transaction_completion;
481 Ok(client)
482 }
483
484 pub fn cancel_request(&self, conn_id: ConnectionIdType, secret_key: u32) {
486 self.send(Command::CancelRequest {
487 conn_id,
488 secret_key,
489 });
490 }
491
492 pub async fn support_execute_one(
495 &self,
496 sql: &str,
497 ) -> Result<Pin<Box<dyn Stream<Item = PeekResponseUnary> + Send>>, anyhow::Error> {
498 let conn_id = self.new_conn_id()?;
500 let session = self.new_session(
501 SessionConfig {
502 conn_id,
503 uuid: Uuid::new_v4(),
504 user: SUPPORT_USER.name.clone(),
505 client_ip: None,
506 external_metadata_rx: None,
507 helm_chart_version: None,
508 authenticator_kind: AuthenticatorKind::None,
509 groups: None,
510 },
511 Authenticated,
512 );
513 let mut session_client = self.startup(session).await?;
514
515 let stmts = mz_sql::parse::parse(sql)?;
517 if stmts.len() != 1 {
518 bail!("must supply exactly one query");
519 }
520 let StatementParseResult { ast: stmt, sql } = stmts.into_element();
521
522 const EMPTY_PORTAL: &str = "";
523 session_client.start_transaction(Some(1))?;
524 session_client
525 .declare(EMPTY_PORTAL.into(), stmt, sql.to_string())
526 .await?;
527
528 let execute_result = session_client
529 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
530 .await?;
531 match execute_result {
532 (ExecuteResponse::SendingRowsStreaming { mut rows, .. }, _) => {
533 let owning_response_stream = async_stream::stream! {
538 while let Some(rows) = rows.next().await {
539 yield rows;
540 }
541 drop(session_client);
542 };
543 Ok(Box::pin(owning_response_stream))
544 }
545 r => bail!("unsupported response type: {r:?}"),
546 }
547 }
548
549 pub fn metrics(&self) -> &Metrics {
551 &self.metrics
552 }
553
554 pub fn now(&self) -> DateTime<Utc> {
556 to_datetime((self.now)())
557 }
558
559 pub async fn get_webhook_appender(
561 &self,
562 database: String,
563 schema: String,
564 name: String,
565 ) -> Result<AppendWebhookResponse, AppendWebhookError> {
566 let (tx, rx) = oneshot::channel();
567
568 self.send(Command::GetWebhook {
570 database,
571 schema,
572 name,
573 tx,
574 });
575
576 let response = rx
578 .await
579 .map_err(|_| anyhow::anyhow!("failed to receive webhook response"))?;
580
581 response
582 }
583
584 pub async fn get_system_vars(&self) -> SystemVars {
586 let (tx, rx) = oneshot::channel();
587 self.send(Command::GetSystemVars { tx });
588 rx.await.expect("coordinator unexpectedly gone")
589 }
590
591 pub async fn catalog_snapshot_expensive(&self) -> Arc<Catalog> {
597 let (tx, rx) = oneshot::channel();
598 self.send(Command::CatalogSnapshot { tx });
599 let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone");
600 catalog
601 }
602
603 pub async fn update_scoped_system_parameters(
612 &self,
613 overrides: ScopedParameters,
614 prune_scope: ScopedParametersScope,
615 ) {
616 let (tx, rx) = oneshot::channel();
617 self.send(Command::UpdateScopedSystemParameters {
618 overrides,
619 prune_scope,
620 tx,
621 });
622 let _ = rx.await;
623 }
624
625 pub fn install_scoped_system_parameter_frontend(&self, frontend: Arc<SystemParameterFrontend>) {
630 self.send(Command::InstallScopedSystemParameterFrontend { frontend });
631 }
632
633 #[instrument(level = "debug")]
634 pub(crate) fn try_send(&self, cmd: Command) -> bool {
635 self.inner_cmd_tx
636 .send((OpenTelemetryContext::obtain(), cmd))
637 .is_ok()
638 }
639
640 #[instrument(level = "debug")]
641 pub(crate) fn send(&self, cmd: Command) {
642 assert!(self.try_send(cmd), "coordinator unexpectedly gone");
643 }
644}
645
646pub struct SessionClient {
650 inner: Option<Client>,
654 session: Option<Session>,
657 timeouts: Timeout,
658 segment_client: Option<mz_segment::Client>,
659 environment_id: EnvironmentId,
660 peek_client: PeekClient,
662 enable_frontend_transaction_completion: bool,
663 frontend_cancel_rx: tokio::sync::watch::Receiver<()>,
664}
665
666impl SessionClient {
667 pub fn parse<'a>(
670 &self,
671 sql: &'a str,
672 ) -> Result<Result<Vec<StatementParseResult<'a>>, ParserStatementError>, String> {
673 match mz_sql::parse::parse_with_limit(sql) {
674 Ok(Err(e)) => {
675 self.track_statement_parse_failure(&e);
676 Ok(Err(e))
677 }
678 r => r,
679 }
680 }
681
682 fn track_statement_parse_failure(&self, parse_error: &ParserStatementError) {
683 let session = self.session.as_ref().expect("session invariant violated");
684 let Some(user_id) = session.user().external_metadata.as_ref().map(|m| m.user_id) else {
685 return;
686 };
687 let Some(segment_client) = &self.segment_client else {
688 return;
689 };
690 let Some(statement_kind) = parse_error.statement else {
691 return;
692 };
693 let Some((action, object_type)) = telemetry::analyze_audited_statement(statement_kind)
694 else {
695 return;
696 };
697 let event_type = StatementFailureType::ParseFailure;
698 let event_name = format!(
699 "{} {} {}",
700 object_type.as_title_case(),
701 action.as_title_case(),
702 event_type.as_title_case(),
703 );
704 segment_client.environment_track(
705 &self.environment_id,
706 event_name,
707 json!({
708 "statement_kind": statement_kind,
709 "error": &parse_error.error,
710 }),
711 EventDetails {
712 user_id: Some(user_id),
713 application_name: Some(session.application_name()),
714 ..Default::default()
715 },
716 );
717 }
718
719 pub async fn get_prepared_statement(
722 &mut self,
723 name: &str,
724 ) -> Result<&PreparedStatement, AdapterError> {
725 let catalog = self.catalog_snapshot("get_prepared_statement").await;
726 Coordinator::verify_prepared_statement(&catalog, self.session(), name)?;
727 Ok(self
728 .session()
729 .get_prepared_statement_unverified(name)
730 .expect("must exist"))
731 }
732
733 pub async fn prepare(
738 &mut self,
739 name: String,
740 stmt: Option<Statement<Raw>>,
741 sql: String,
742 param_types: Vec<Option<SqlScalarType>>,
743 ) -> Result<(), AdapterError> {
744 let catalog = self.catalog_snapshot("prepare").await;
745
746 let mut async_pause = false;
749 (|| {
750 fail::fail_point!("async_prepare", |val| {
751 async_pause = val.map_or(false, |val| val.parse().unwrap_or(false))
752 });
753 })();
754 if async_pause {
755 tokio::time::sleep(Duration::from_secs(1)).await;
756 };
757
758 let desc = Coordinator::describe(&catalog, self.session(), stmt.clone(), param_types)?;
759 let now = self.now();
760 let state_revision = StateRevision {
761 catalog_revision: catalog.transient_revision(),
762 session_state_revision: self.session().state_revision(),
763 };
764 self.session()
765 .set_prepared_statement(name, stmt, sql, desc, state_revision, now);
766 Ok(())
767 }
768
769 #[mz_ore::instrument(level = "debug")]
771 pub async fn declare(
772 &mut self,
773 name: String,
774 stmt: Statement<Raw>,
775 sql: String,
776 ) -> Result<(), AdapterError> {
777 let catalog = self.catalog_snapshot("declare").await;
778 let param_types = vec![];
779 let desc =
780 Coordinator::describe(&catalog, self.session(), Some(stmt.clone()), param_types)?;
781 let params = vec![];
782 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
783 let now = self.now();
784 let logging = self.session().mint_logging(sql, Some(&stmt), now);
785 let state_revision = StateRevision {
786 catalog_revision: catalog.transient_revision(),
787 session_state_revision: self.session().state_revision(),
788 };
789 self.session().set_portal(
790 name,
791 desc,
792 Some(stmt),
793 logging,
794 params,
795 result_formats,
796 state_revision,
797 )?;
798 Ok(())
799 }
800
801 #[mz_ore::instrument(level = "debug")]
808 pub async fn execute(
809 &mut self,
810 portal_name: String,
811 cancel_future: impl Future<Output = std::io::Error> + Send,
812 outer_ctx_extra: Option<ExecuteContextGuard>,
813 ) -> Result<(ExecuteResponse, Instant), AdapterError> {
814 let execute_started = Instant::now();
815 let cancel_future = cancel_future.map(|_| ()).shared();
816
817 let mut logging = ExecutionLogging::adopt(outer_ctx_extra, &self.peek_client);
821
822 let result = self
823 .execute_attempts(portal_name, &mut logging, cancel_future)
824 .await;
825
826 logging.retire(&result);
827
828 result.map(|response| (response, execute_started))
829 }
830
831 async fn execute_attempts(
835 &mut self,
836 portal_name: String,
837 logging: &mut ExecutionLogging,
838 cancel_future: impl Future<Output = ()> + Send + Clone,
839 ) -> Result<ExecuteResponse, AdapterError> {
840 let portal_name = self.unroll_sql_execute(portal_name, logging).await?;
853
854 let peek_result = self
857 .try_peek(&portal_name, logging, cancel_future.clone())
858 .await?;
859 if let Some(resp) = peek_result {
860 debug!("frontend peek succeeded");
861 return Ok(resp);
862 }
863 debug!("frontend peek did not happen, trying frontend read-then-write");
864
865 let rtw_result = self
867 .try_read_then_write_with_cancel(&portal_name, logging, cancel_future.clone())
868 .await?;
869 if let Some(resp) = rtw_result {
870 debug!("frontend read-then-write succeeded");
871 return Ok(resp);
872 }
873 debug!("frontend read-then-write did not happen, falling back to `Command::Execute`");
874
875 let outer_ctx_extra = logging.release();
878 self.send_with_cancel(
879 |tx, session| Command::Execute {
880 portal_name,
881 session,
882 tx,
883 outer_ctx_extra,
884 },
885 cancel_future,
886 )
887 .await
888 }
889
890 async fn unroll_sql_execute(
904 &mut self,
905 portal_name: String,
906 logging: &mut ExecutionLogging,
907 ) -> Result<String, AdapterError> {
908 let (stmt, params, outer_logging, outer_lifecycle_timestamps) = {
909 let session = self.session.as_ref().expect("SessionClient invariant");
910 let portal = match session.get_portal_unverified(&portal_name) {
911 Some(p) => p,
912 None => return Ok(portal_name),
916 };
917 match &portal.stmt {
918 Some(stmt) => (
919 Arc::clone(stmt),
920 portal.parameters.clone(),
921 Arc::clone(&portal.logging),
922 portal.lifecycle_timestamps.clone(),
923 ),
924 None => return Ok(portal_name),
925 }
926 };
927
928 if !matches!(&*stmt, Statement::Execute(_)) {
931 return Ok(portal_name);
932 }
933
934 let catalog = self.catalog_snapshot("unroll_sql_execute").await;
935
936 {
940 let session = self.session.as_mut().expect("SessionClient invariant");
941 Coordinator::verify_portal(&catalog, session, &portal_name)?;
942 }
943
944 logging.take_over(
952 &self.peek_client,
953 self.session.as_mut().expect("SessionClient invariant"),
954 Some(&stmt),
955 ¶ms,
956 &outer_logging,
957 &catalog,
958 outer_lifecycle_timestamps,
959 TakeOver::UnrolledExecute,
960 );
961
962 self.install_inner_portal_for_execute(&catalog, &stmt, ¶ms)
963 }
964
965 fn install_inner_portal_for_execute(
970 &mut self,
971 catalog: &Arc<Catalog>,
972 stmt: &Arc<Statement<Raw>>,
973 params: &mz_sql::plan::Params,
974 ) -> Result<String, AdapterError> {
975 use mz_sql::plan::Plan;
976
977 let execute_plan = {
978 let session = self.session.as_mut().expect("SessionClient invariant");
979 let conn_catalog = catalog.for_session(session);
980 let (resolved_stmt, resolved_ids) =
981 mz_sql::names::resolve(&conn_catalog, (**stmt).clone())?;
982 let pcx = session.pcx();
983 let (plan, _sql_impl_ids) = mz_sql::plan::plan(
984 Some(pcx),
985 &conn_catalog,
986 resolved_stmt,
987 params,
988 &resolved_ids,
989 )?;
990 match plan {
991 Plan::Execute(plan) => plan,
992 other => {
993 return Err(AdapterError::Internal(format!(
997 "planning Statement::Execute yielded unexpected plan: {:?}",
998 mz_sql::plan::PlanKind::from(&other),
999 )));
1000 }
1001 }
1002 };
1003
1004 let session = self.session.as_mut().expect("SessionClient invariant");
1010 Coordinator::verify_prepared_statement(catalog, session, &execute_plan.name)?;
1011 let ps = session
1012 .get_prepared_statement_unverified(&execute_plan.name)
1013 .expect("verified above");
1014 let inner_stmt = ps.stmt().cloned();
1015 let inner_desc = ps.desc().clone();
1016 let state_revision = ps.state_revision;
1017 let inner_logging = Arc::clone(ps.logging());
1018
1019 if let Some(inner) = inner_stmt.as_ref() {
1024 if matches!(inner, Statement::Execute(_)) {
1025 return Err(AdapterError::Internal(format!(
1026 "nested EXECUTE: prepared statement {} resolves to another EXECUTE; \
1027 parser should reject `PREPARE ... AS EXECUTE ...`",
1028 execute_plan.name.quoted(),
1029 )));
1030 }
1031 }
1032
1033 session.create_new_portal(
1034 inner_stmt,
1035 inner_logging,
1036 inner_desc,
1037 execute_plan.params,
1038 Vec::new(),
1039 state_revision,
1040 )
1041 }
1042
1043 fn now(&self) -> EpochMillis {
1044 (self.inner().now)()
1045 }
1046
1047 fn now_datetime(&self) -> DateTime<Utc> {
1048 to_datetime(self.now())
1049 }
1050
1051 pub fn start_transaction(&mut self, implicit: Option<usize>) -> Result<(), AdapterError> {
1057 let now = self.now_datetime();
1058 let session = self.session.as_mut().expect("session invariant violated");
1059 let result = match implicit {
1060 None => session.start_transaction(now, None, None),
1061 Some(stmts) => {
1062 session.start_transaction_implicit(now, stmts);
1063 Ok(())
1064 }
1065 };
1066 result
1067 }
1068
1069 #[instrument(level = "debug")]
1072 pub async fn end_transaction(
1073 &mut self,
1074 action: EndTransactionAction,
1075 ) -> Result<ExecuteResponse, AdapterError> {
1076 if self.enable_frontend_transaction_completion {
1077 if let Some(completion) = self.session().read_only_completion(action) {
1078 return crate::frontend_transaction::complete_read_only_transaction(
1079 self.session.as_mut().expect("session invariant violated"),
1080 &mut self.peek_client,
1081 &mut self.frontend_cancel_rx,
1082 completion,
1083 )
1084 .await;
1085 }
1086 }
1087 let res = self
1088 .send(|tx, session| Command::Commit {
1089 action,
1090 session,
1091 tx,
1092 })
1093 .await;
1094 let _ = self.session().clear_transaction();
1098 res
1099 }
1100
1101 pub async fn settle_statement(
1110 &mut self,
1111 commit_implicit_transaction: bool,
1112 ) -> Result<SettledStatement, AdapterError> {
1113 let mut reverted = BTreeMap::new();
1114 if commit_implicit_transaction && self.session().transaction().is_implicit() {
1115 let response = self.end_transaction(EndTransactionAction::Commit).await?;
1116 if let ExecuteResponse::TransactionCommitted { params } = response {
1117 reverted = params;
1118 }
1119 }
1120 Ok(SettledStatement { reverted })
1121 }
1122
1123 pub fn fail_transaction(&mut self) {
1125 let session = self.session.take().expect("session invariant violated");
1126 let session = session.fail_transaction();
1127 self.session = Some(session);
1128 }
1129
1130 #[instrument(level = "debug")]
1134 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
1135 self.peek_client.catalog_snapshot(context).await
1136 }
1137
1138 pub async fn statement_arrival_logging_enabled(&mut self) -> bool {
1140 let catalog = self.catalog_snapshot("statement_arrival_logging").await;
1141 catalog.system_config().enable_statement_arrival_logging()
1142 }
1143
1144 pub async fn extended_protocol_implicit_transaction_enabled(&mut self) -> bool {
1146 let catalog = self
1147 .catalog_snapshot("extended_protocol_implicit_transaction")
1148 .await;
1149 catalog
1150 .system_config()
1151 .enable_extended_protocol_implicit_transaction()
1152 }
1153
1154 pub async fn dump_catalog(&mut self) -> Result<CatalogDump, AdapterError> {
1159 let catalog = self.catalog_snapshot("dump_catalog").await;
1160 catalog.dump().map_err(AdapterError::from)
1161 }
1162
1163 pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> {
1169 let catalog = self.catalog_snapshot("check_catalog").await;
1170 catalog.check_consistency()
1171 }
1172
1173 pub async fn check_coordinator(&self) -> Result<(), serde_json::Value> {
1179 self.send_without_session(|tx| Command::CheckConsistency { tx })
1180 .await
1181 .map_err(|inconsistencies| {
1182 serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1183 serde_json::Value::String("failed to serialize inconsistencies".to_string())
1184 })
1185 })
1186 }
1187
1188 pub async fn dump_coordinator_state(&self) -> Result<serde_json::Value, anyhow::Error> {
1189 self.send_without_session(|tx| Command::Dump { tx }).await
1190 }
1191
1192 pub fn retire_execute(
1195 &self,
1196 guard: ExecuteContextGuard,
1197 reason: StatementEndedExecutionReason,
1198 ) {
1199 if !guard.is_trivial() {
1200 let data = guard.defuse();
1201 let cmd = Command::RetireExecute { data, reason };
1202 self.inner().send(cmd);
1203 }
1204 }
1205
1206 pub async fn start_copy_from_stdin(
1212 &mut self,
1213 target_id: CatalogItemId,
1214 target_name: String,
1215 columns: Vec<ColumnIndex>,
1216 row_desc: mz_repr::RelationDesc,
1217 params: mz_pgcopy::CopyFormatParams<'static>,
1218 ) -> Result<CopyFromStdinWriter, AdapterError> {
1219 self.send(|tx, session| Command::StartCopyFromStdin {
1220 target_id,
1221 target_name,
1222 columns,
1223 row_desc,
1224 params,
1225 session,
1226 tx,
1227 })
1228 .await
1229 }
1230
1231 pub fn stage_copy_from_stdin_batches(
1236 &mut self,
1237 target_id: CatalogItemId,
1238 batches: Vec<mz_persist_client::batch::ProtoBatch>,
1239 ) -> Result<(), AdapterError> {
1240 use crate::session::{TransactionOps, WriteOp};
1241 use mz_storage_client::client::TableData;
1242
1243 self.session()
1244 .add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
1245 id: target_id,
1246 rows: TableData::Batches(batches.into()),
1247 }]))?;
1248 Ok(())
1249 }
1250
1251 pub async fn get_system_vars(&self) -> SystemVars {
1253 self.inner().get_system_vars().await
1254 }
1255
1256 pub async fn set_system_vars(
1258 &mut self,
1259 vars: BTreeMap<String, String>,
1260 ) -> Result<(), AdapterError> {
1261 let conn_id = self.session().conn_id().clone();
1262 self.send_without_session(|tx| Command::SetSystemVars { vars, conn_id, tx })
1263 .await
1264 }
1265
1266 pub async fn inject_audit_events(
1271 &mut self,
1272 events: Vec<crate::catalog::InjectedAuditEvent>,
1273 ) -> Result<(), AdapterError> {
1274 let conn_id = self.session().conn_id().clone();
1275 self.send_without_session(|tx| Command::InjectAuditEvents {
1276 events,
1277 conn_id,
1278 tx,
1279 })
1280 .await
1281 }
1282
1283 pub async fn terminate(&mut self) {
1285 let conn_id = self.session().conn_id().clone();
1286 let res = self
1287 .send_without_session(|tx| Command::Terminate {
1288 conn_id,
1289 tx: Some(tx),
1290 })
1291 .await;
1292 if let Err(e) = res {
1293 error!("Unable to terminate session: {e:?}");
1295 }
1296 self.inner = None;
1298 }
1299
1300 pub fn session(&mut self) -> &mut Session {
1302 self.session.as_mut().expect("session invariant violated")
1303 }
1304
1305 pub fn inner(&self) -> &Client {
1307 self.inner.as_ref().expect("inner invariant violated")
1308 }
1309
1310 async fn send_without_session<T, F>(&self, f: F) -> T
1311 where
1312 F: FnOnce(oneshot::Sender<T>) -> Command,
1313 {
1314 let (tx, rx) = oneshot::channel();
1315 self.inner().send(f(tx));
1316 rx.await.expect("sender dropped")
1317 }
1318
1319 #[instrument(level = "debug")]
1320 async fn send<T, F>(&mut self, f: F) -> Result<T, AdapterError>
1321 where
1322 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1323 {
1324 self.send_with_cancel(f, futures::future::pending()).await
1325 }
1326
1327 #[instrument(level = "debug")]
1331 async fn send_with_cancel<T, F>(
1332 &mut self,
1333 f: F,
1334 cancel_future: impl Future<Output = ()> + Send,
1335 ) -> Result<T, AdapterError>
1336 where
1337 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1338 {
1339 let session = self.session.take().expect("session invariant violated");
1340 let mut typ = None;
1341 let application_name = session.application_name();
1342 let name_hint = ApplicationNameHint::from_str(application_name);
1343 let conn_id = session.conn_id().clone();
1344 let (tx, rx) = oneshot::channel();
1345
1346 let Self {
1349 inner: inner_client,
1350 session: client_session,
1351 ..
1352 } = self;
1353
1354 let inner_client = inner_client.as_ref().expect("inner invariant violated");
1357
1358 let mut guarded_rx = rx.with_guard(|response: Response<_>| {
1364 *client_session = Some(response.session);
1365 });
1366
1367 inner_client.send({
1368 let cmd = f(tx, session);
1369 match cmd {
1373 Command::Execute { .. } => typ = Some("execute"),
1374 Command::GetWebhook { .. } => typ = Some("webhook"),
1375 Command::StartCopyFromStdin { .. }
1376 | Command::Startup { .. }
1377 | Command::AuthenticatePassword { .. }
1378 | Command::AuthenticateGetSASLChallenge { .. }
1379 | Command::AuthenticateVerifySASLProof { .. }
1380 | Command::CheckRoleCanLogin { .. }
1381 | Command::CatalogSnapshot { .. }
1382 | Command::Commit { .. }
1383 | Command::CancelRequest { .. }
1384 | Command::PrivilegedCancelRequest { .. }
1385 | Command::GetSystemVars { .. }
1386 | Command::SetSystemVars { .. }
1387 | Command::UpdateScopedSystemParameters { .. }
1388 | Command::InstallScopedSystemParameterFrontend { .. }
1389 | Command::Terminate { .. }
1390 | Command::RetireExecute { .. }
1391 | Command::CheckConsistency { .. }
1392 | Command::Dump { .. }
1393 | Command::GetComputeInstanceClient { .. }
1394 | Command::GetOracle { .. }
1395 | Command::DetermineRealTimeRecentTimestamp { .. }
1396 | Command::GetTransactionReadHoldsBundle { .. }
1397 | Command::StoreTransactionReadHolds { .. }
1398 | Command::ExecuteSlowPathPeek { .. }
1399 | Command::ExecuteSubscribe { .. }
1400 | Command::CopyToPreflight { .. }
1401 | Command::ExecuteCopyTo { .. }
1402 | Command::ExecuteSideEffectingFunc { .. }
1403 | Command::LookupConnection { .. }
1404 | Command::RegisterPeek { .. }
1405 | Command::UnregisterPeek { .. }
1406 | Command::ExplainTimestamp { .. }
1407 | Command::FrontendStatementLogging(..)
1408 | Command::InjectAuditEvents { .. }
1409 | Command::RegisterConnectionCancelWatch { .. }
1410 | Command::CreateInternalSubscribe { .. }
1411 | Command::AttemptWrite { .. }
1412 | Command::DropInternalSubscribe { .. } => {}
1413 };
1414 cmd
1415 });
1416
1417 let mut cancel_future = pin::pin!(cancel_future);
1418 let mut cancelled = false;
1419 loop {
1420 tokio::select! {
1421 res = &mut guarded_rx => {
1422 drop(guarded_rx);
1424
1425 let res = res.expect("sender dropped");
1426 let status = res.result.is_ok().then_some("success").unwrap_or("error");
1427 if let Err(err) = res.result.as_ref() {
1428 if name_hint.should_trace_errors() {
1429 tracing::warn!(?err, ?name_hint, "adapter response error");
1430 }
1431 }
1432
1433 if let Some(typ) = typ {
1434 inner_client
1435 .metrics
1436 .commands
1437 .with_label_values(&[typ, status, name_hint.as_str()])
1438 .inc();
1439 }
1440 *client_session = Some(res.session);
1441 return res.result;
1442 },
1443 _ = &mut cancel_future, if !cancelled => {
1444 cancelled = true;
1445 inner_client.send(Command::PrivilegedCancelRequest {
1446 conn_id: conn_id.clone(),
1447 });
1448 }
1449 };
1450 }
1451 }
1452
1453 pub fn add_idle_in_transaction_session_timeout(&mut self) {
1454 let session = self.session();
1455 let timeout_dur = session.vars().idle_in_transaction_session_timeout();
1456 if !timeout_dur.is_zero() {
1457 let timeout_dur = timeout_dur.clone();
1458 if let Some(txn) = session.transaction().inner() {
1459 let txn_id = txn.id.clone();
1460 let timeout = TimeoutType::IdleInTransactionSession(txn_id);
1461 self.timeouts.add_timeout(timeout, timeout_dur);
1462 }
1463 }
1464 }
1465
1466 pub fn remove_idle_in_transaction_session_timeout(&mut self) {
1467 let session = self.session();
1468 if let Some(txn) = session.transaction().inner() {
1469 let txn_id = txn.id.clone();
1470 self.timeouts
1471 .remove_timeout(&TimeoutType::IdleInTransactionSession(txn_id));
1472 }
1473 }
1474
1475 pub async fn recv_timeout(&mut self) -> Option<TimeoutType> {
1482 self.timeouts.recv().await
1483 }
1484
1485 pub(crate) async fn try_peek(
1491 &mut self,
1492 portal_name: &str,
1493 logging: &mut ExecutionLogging,
1494 connection_closed: impl Future<Output = ()> + Send,
1495 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1496 let session = self.session.as_mut().expect("SessionClient invariant");
1497 self.peek_client
1498 .try_peek(portal_name, session, logging, connection_closed)
1499 .await
1500 }
1501
1502 fn read_then_write_applies(&self, portal_name: &str) -> bool {
1510 let session = self.session.as_ref().expect("SessionClient invariant");
1511 match session.get_portal_unverified(portal_name) {
1512 Some(portal) => portal
1513 .stmt
1514 .as_deref()
1515 .is_some_and(is_read_then_write_statement),
1516 None => false,
1517 }
1518 }
1519
1520 async fn try_read_then_write_with_cancel(
1531 &mut self,
1532 portal_name: &str,
1533 logging: &mut ExecutionLogging,
1534 cancel_future: impl Future<Output = ()> + Send,
1535 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1536 if !self.read_then_write_applies(portal_name) {
1541 return Ok(None);
1542 }
1543
1544 self.session().require_coordinator_completion();
1545 let conn_id = self.session().conn_id().clone();
1546 let statement_timeout = *self.session().vars().statement_timeout();
1547 let inner_client = self.inner().clone();
1548 let attempt_state = Arc::new(WriteAttemptState::new());
1549
1550 let mut cancel_future = pin::pin!(cancel_future);
1551 let statement_timeout = async move {
1552 if statement_timeout.is_zero() {
1553 futures::future::pending::<()>().await;
1554 } else {
1555 tokio::time::sleep(statement_timeout).await;
1556 }
1557 };
1558 tokio::pin!(statement_timeout);
1559
1560 let mut connection_cancel_rx = {
1566 let register =
1567 self.peek_client
1568 .call_coordinator(|tx| Command::RegisterConnectionCancelWatch {
1569 conn_id: conn_id.clone(),
1570 tx,
1571 });
1572 tokio::pin!(register);
1573 tokio::select! {
1574 rx = &mut register => rx?,
1575 _ = &mut cancel_future => {
1576 inner_client.try_send(Command::PrivilegedCancelRequest {
1577 conn_id: conn_id.clone(),
1578 });
1579 return Err(AdapterError::Canceled);
1580 }
1581 _ = &mut statement_timeout => {
1582 inner_client.try_send(Command::PrivilegedCancelRequest {
1583 conn_id: conn_id.clone(),
1584 });
1585 return Err(AdapterError::StatementTimeout);
1586 }
1587 }
1588 };
1589 if *connection_cancel_rx.borrow() {
1590 return Err(AdapterError::Canceled);
1591 }
1592 let connection_cancel = async move {
1593 if connection_cancel_rx.wait_for(|v| *v).await.is_err() {
1594 futures::future::pending::<()>().await;
1595 }
1596 };
1597 tokio::pin!(connection_cancel);
1598
1599 let read_then_write =
1600 self.try_read_then_write(portal_name, logging, Arc::clone(&attempt_state));
1601 tokio::pin!(read_then_write);
1602
1603 let requested = tokio::select! {
1604 response = &mut read_then_write => return response,
1605 _ = &mut cancel_future => WriteCancellation::Canceled,
1606 _ = &mut connection_cancel => WriteCancellation::Canceled,
1607 _ = &mut statement_timeout => WriteCancellation::StatementTimeout,
1608 };
1609
1610 attempt_state.request(requested);
1611 inner_client.try_send(Command::PrivilegedCancelRequest {
1612 conn_id: conn_id.clone(),
1613 });
1614
1615 if !attempt_state.write_submitted() {
1616 return Err(requested.into());
1617 }
1618
1619 read_then_write.await
1622 }
1623
1624 async fn try_read_then_write(
1631 &mut self,
1632 portal_name: &str,
1633 logging: &mut ExecutionLogging,
1634 attempt_state: Arc<WriteAttemptState>,
1635 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1636 let catalog = self.catalog_snapshot("try_read_then_write").await;
1637
1638 let stmt = {
1639 let session = self.session.as_ref().expect("SessionClient invariant");
1640 let portal = match session.get_portal_unverified(portal_name) {
1641 Some(portal) => portal,
1642 None => return Ok(None), };
1644 portal.stmt.clone()
1645 };
1646
1647 let stmt = match stmt {
1648 Some(stmt) if is_read_then_write_statement(&stmt) => stmt,
1649 Some(_stmt) => {
1650 return Ok(None);
1651 }
1652 None => {
1653 return Ok(None);
1654 }
1655 };
1656
1657 Coordinator::verify_portal(
1662 &catalog,
1663 self.session.as_mut().expect("SessionClient invariant"),
1664 portal_name,
1665 )?;
1666
1667 let (params, logging_info, lifecycle_timestamps) = {
1668 let portal = self
1669 .session
1670 .as_ref()
1671 .expect("SessionClient invariant")
1672 .get_portal_unverified(portal_name)
1673 .expect("verified above");
1674 (
1675 portal.parameters.clone(),
1676 Arc::clone(&portal.logging),
1677 portal.lifecycle_timestamps.clone(),
1678 )
1679 };
1680
1681 logging.take_over(
1685 &self.peek_client,
1686 self.session.as_mut().expect("SessionClient invariant"),
1687 Some(&stmt),
1688 ¶ms,
1689 &logging_info,
1690 &catalog,
1691 lifecycle_timestamps,
1692 TakeOver::StatementToRun,
1693 );
1694
1695 {
1707 let session = self.session.as_ref().expect("SessionClient invariant");
1708 let contains_ops = session.transaction().contains_ops();
1716 match session.transaction() {
1717 TransactionStatus::Default | TransactionStatus::Failed(_) => {}
1718 TransactionStatus::Started(_) if !contains_ops => {}
1719 TransactionStatus::Started(_)
1720 | TransactionStatus::InTransactionImplicit(_)
1721 | TransactionStatus::InTransaction(_) => {
1722 let constant_insert = matches!(
1723 &*stmt,
1724 Statement::Insert(InsertStatement {
1725 source, returning, ..
1726 }) if returning.is_empty() && ConstantVisitor::insert_source(source)
1727 );
1728 if !constant_insert {
1729 return Err(prohibited_in_transaction(&stmt));
1730 }
1731 }
1732 }
1733 }
1734
1735 let (plan, target_cluster, resolved_ids, sql_impl_ids) = {
1736 let session = self.session.as_mut().expect("SessionClient invariant");
1737 let conn_catalog = catalog.for_session(session);
1738 let (stmt, resolved_ids) = mz_sql::names::resolve(&conn_catalog, (*stmt).clone())?;
1739 let pcx = session.pcx();
1740 let (plan, sql_impl_ids) =
1741 mz_sql::plan::plan(Some(pcx), &conn_catalog, stmt, ¶ms, &resolved_ids)?;
1742
1743 let target_cluster = match session.transaction().cluster() {
1744 Some(cluster_id) => crate::coord::TargetCluster::Transaction(cluster_id),
1745 None => crate::coord::catalog_serving::auto_run_on_catalog_server(
1746 &conn_catalog,
1747 session,
1748 &plan,
1749 ),
1750 };
1751
1752 (plan, target_cluster, resolved_ids, sql_impl_ids)
1753 };
1754
1755 if self.peek_client.read_only {
1762 return Err(AdapterError::ReadOnly);
1763 }
1764
1765 let (target_cluster_id, target_cluster_name) = {
1771 let session = self.session.as_ref().expect("SessionClient invariant");
1772 match catalog.resolve_target_cluster(target_cluster.clone(), session) {
1773 Ok(cluster) => (Some(cluster.id), Some(cluster.name.clone())),
1774 Err(_) => (None, None),
1775 }
1776 };
1777
1778 if let (Some(logging_id), Some(cluster_id), Some(cluster_name)) =
1781 (logging.id(), target_cluster_id, target_cluster_name.clone())
1782 {
1783 self.peek_client
1784 .log_set_cluster(logging_id, cluster_id, cluster_name);
1785 }
1786
1787 {
1788 let session = self.session.as_ref().expect("SessionClient invariant");
1789 let conn_catalog = catalog.for_session(session);
1790 if let Some(cluster_name) = &target_cluster_name {
1791 crate::coord::catalog_serving::check_cluster_restrictions(
1792 cluster_name,
1793 &conn_catalog,
1794 &plan,
1795 )?;
1796 }
1797 if let Err(e) = mz_sql::rbac::check_plan(
1798 &conn_catalog,
1799 None,
1800 session,
1801 &plan,
1802 target_cluster_id,
1803 &resolved_ids,
1804 &sql_impl_ids,
1805 ) {
1806 return Err(e.into());
1807 }
1808 }
1809
1810 {
1814 let session = self.session.as_mut().expect("SessionClient invariant");
1815 if let Some(wait_future) =
1816 crate::coord::appends::waiting_on_startup_appends(&catalog, session, &plan)
1817 {
1818 wait_future.await;
1819 }
1820 }
1821
1822 {
1827 let session = self.session.as_ref().expect("SessionClient invariant");
1828 if session.transaction().is_in_multi_statement_transaction()
1829 && !session.transaction().allows_writes()
1830 {
1831 return Err(AdapterError::ReadOnlyTransaction);
1832 }
1833 if session
1834 .vars()
1835 .transaction_isolation()
1836 .is_bounded_staleness()
1837 {
1838 return Err(AdapterError::BoundedStalenessReadOnly);
1839 }
1840 }
1841
1842 let rtw_plan = match plan {
1844 Plan::ReadThenWrite(rtw_plan) => rtw_plan,
1845 Plan::Insert(insert_plan) => {
1846 let has_read_deps = !insert_plan.values.depends_on().is_empty();
1866
1867 if !has_read_deps {
1868 let optimized_mir = if insert_plan.values.as_const().is_some() {
1869 let expr = insert_plan
1871 .values
1872 .clone()
1873 .lower(catalog.system_config(), None)?;
1874 mz_expr::OptimizedMirRelationExpr(expr)
1875 } else {
1876 let optimizer_config =
1881 optimize::OptimizerConfig::from(catalog.system_config());
1882 let session = self.session.as_ref().expect("SessionClient invariant");
1883 let prep = ExprPrepOneShot {
1884 logical_time: EvalTime::NotAvailable,
1885 session,
1886 catalog_state: catalog.state(),
1887 };
1888 let mut optimizer =
1889 optimize::view::Optimizer::new_with_prep(optimizer_config, None, prep);
1890 match optimizer.optimize(insert_plan.values.clone()) {
1891 Ok(expr) => expr,
1892 Err(OptimizerError::UncallableFunction {
1893 func: UnmaterializableFunc::MzNow,
1894 ..
1895 }) => {
1896 let expr = insert_plan
1899 .values
1900 .clone()
1901 .lower(catalog.system_config(), None)?;
1902 mz_expr::OptimizedMirRelationExpr(expr)
1903 }
1904 Err(e) => return Err(e.into()),
1905 }
1906 };
1907
1908 let inner_mir = optimized_mir.into_inner();
1909 if inner_mir.as_const().is_some() && insert_plan.returning.is_empty() {
1910 let session = self.session.as_mut().expect("SessionClient invariant");
1911 let result = Coordinator::insert_constant(
1912 &catalog,
1913 session,
1914 insert_plan.id,
1915 inner_mir,
1916 );
1917
1918 return Ok(Some(result?));
1919 }
1920 }
1921
1922 let desc_arity = match catalog.try_get_entry(&insert_plan.id) {
1923 Some(table) => {
1924 let desc = table
1925 .relation_desc_latest()
1926 .ok_or_else(|| AdapterError::Internal("table has no desc".into()))?;
1927 desc.arity()
1928 }
1929 None => {
1930 return Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1931 kind: mz_catalog::memory::error::ErrorKind::Sql(
1932 mz_sql::catalog::CatalogError::UnknownItem(
1933 insert_plan.id.to_string(),
1934 ),
1935 ),
1936 }));
1937 }
1938 };
1939
1940 let finishing = RowSetFinishing {
1941 order_by: vec![],
1942 limit: None,
1943 offset: 0,
1944 project: (0..desc_arity).collect(),
1945 };
1946
1947 ReadThenWritePlan {
1948 id: insert_plan.id,
1949 selection: insert_plan.values,
1950 finishing,
1951 assignments: BTreeMap::new(),
1952 kind: MutationKind::Insert,
1953 returning: insert_plan.returning,
1954 }
1955 }
1956 _ => {
1957 return Err(AdapterError::Internal(
1958 "unexpected plan type for mutation".into(),
1959 ));
1960 }
1961 };
1962
1963 {
1974 let session = self.session.as_ref().expect("SessionClient invariant");
1975 let in_transaction = session
1976 .transaction()
1977 .may_share_transaction_with_other_statements();
1978 let depends_on = rtw_plan.selection.depends_on();
1979 if in_transaction && !depends_on.is_empty() {
1980 if contains_mz_now(&rtw_plan) {
1987 return Err(AdapterError::Unsupported(
1988 "calls to mz_now in write statements",
1989 ));
1990 }
1991 validate_selection_dependencies(&catalog, &depends_on, DependencyPolicy::UserDml)?;
1992 return Err(prohibited_in_transaction(&stmt));
1993 }
1994 }
1995
1996 let session = self.session.as_mut().expect("SessionClient invariant");
1997 self.peek_client
1998 .session_read_then_write(
1999 session,
2000 rtw_plan,
2001 target_cluster,
2002 &catalog,
2003 logging.id(),
2004 attempt_state,
2005 )
2006 .await
2007 .map(Some)
2008 }
2009}
2010
2011fn is_read_then_write_statement(stmt: &Statement<Raw>) -> bool {
2017 matches!(
2018 stmt,
2019 Statement::Delete(_) | Statement::Update(_) | Statement::Insert(_)
2020 )
2021}
2022
2023fn prohibited_in_transaction(stmt: &Statement<Raw>) -> AdapterError {
2028 let op = if StatementKind::from(stmt).is_sensitive() {
2029 stmt.to_ast_string_redacted()
2030 } else {
2031 stmt.to_string()
2032 };
2033 AdapterError::OperationProhibitsTransaction(op)
2034}
2035
2036impl Drop for SessionClient {
2037 fn drop(&mut self) {
2038 if let Some(session) = self.session.take() {
2042 if let Some(inner) = &self.inner {
2045 inner.send(Command::Terminate {
2046 conn_id: session.conn_id().clone(),
2047 tx: None,
2048 })
2049 }
2050 }
2051 }
2052}
2053
2054pub fn redact_sql_for_logging(sql: &str) -> String {
2062 match mz_sql_parser::parser::parse_statements_with_limit(sql) {
2063 Ok(Ok(stmts)) => truncate_sql_for_logging(
2064 stmts
2065 .into_iter()
2066 .map(|stmt| stmt.ast.to_ast_string_redacted())
2067 .join("; "),
2068 ),
2069 Ok(Err(_)) => format!("<unparseable ({} bytes)>", sql.len()),
2070 Err(_) => format!("<too large ({} bytes)>", sql.len()),
2071 }
2072}
2073
2074const MAX_LOGGED_SQL_LEN: usize = 8 * 1024;
2081
2082pub fn truncate_sql_for_logging(sql: String) -> String {
2085 if sql.len() <= MAX_LOGGED_SQL_LEN {
2086 return sql;
2087 }
2088 let end = sql.floor_char_boundary(MAX_LOGGED_SQL_LEN);
2089 format!("{}... <truncated from {} bytes>", &sql[..end], sql.len())
2090}
2091
2092#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
2093pub enum TimeoutType {
2094 IdleInTransactionSession(TransactionId),
2095}
2096
2097impl Display for TimeoutType {
2098 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2099 match self {
2100 TimeoutType::IdleInTransactionSession(txn_id) => {
2101 writeln!(f, "Idle in transaction session for transaction '{txn_id}'")
2102 }
2103 }
2104 }
2105}
2106
2107impl From<TimeoutType> for AdapterError {
2108 fn from(timeout: TimeoutType) -> Self {
2109 match timeout {
2110 TimeoutType::IdleInTransactionSession(_) => {
2111 AdapterError::IdleInTransactionSessionTimeout
2112 }
2113 }
2114 }
2115}
2116
2117struct Timeout {
2118 tx: mpsc::UnboundedSender<TimeoutType>,
2119 rx: mpsc::UnboundedReceiver<TimeoutType>,
2120 active_timeouts: BTreeMap<TimeoutType, AbortOnDropHandle<()>>,
2121}
2122
2123impl Timeout {
2124 fn new() -> Self {
2125 let (tx, rx) = mpsc::unbounded_channel();
2126 Timeout {
2127 tx,
2128 rx,
2129 active_timeouts: BTreeMap::new(),
2130 }
2131 }
2132
2133 async fn recv(&mut self) -> Option<TimeoutType> {
2142 self.rx.recv().await
2143 }
2144
2145 fn add_timeout(&mut self, timeout: TimeoutType, duration: Duration) {
2146 let tx = self.tx.clone();
2147 let timeout_key = timeout.clone();
2148 let handle = mz_ore::task::spawn(|| format!("{timeout_key}"), async move {
2149 tokio::time::sleep(duration).await;
2150 let _ = tx.send(timeout);
2151 })
2152 .abort_on_drop();
2153 self.active_timeouts.insert(timeout_key, handle);
2154 }
2155
2156 fn remove_timeout(&mut self, timeout: &TimeoutType) {
2157 self.active_timeouts.remove(timeout);
2158
2159 let mut timeouts = Vec::new();
2161 while let Ok(pending_timeout) = self.rx.try_recv() {
2162 if timeout != &pending_timeout {
2163 timeouts.push(pending_timeout);
2164 }
2165 }
2166 for pending_timeout in timeouts {
2167 self.tx.send(pending_timeout).expect("rx is in this struct");
2168 }
2169 }
2170}
2171
2172#[derive(Debug)]
2175pub struct SettledStatement {
2176 pub reverted: BTreeMap<&'static str, String>,
2179}
2180
2181#[derive(Derivative)]
2185#[derivative(Debug)]
2186pub struct RecordFirstRowStream {
2187 #[derivative(Debug = "ignore")]
2189 pub rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2190 pub execute_started: Instant,
2192 pub time_to_first_row_seconds: Histogram,
2195 pub saw_rows: bool,
2197 pub recorded_first_row_instant: Option<Instant>,
2199 pub no_more_rows: bool,
2201 pub metric_recorded: bool,
2203 execution_time: Option<ExecutionTime>,
2204 execution_time_reported: bool,
2205}
2206
2207impl RecordFirstRowStream {
2208 pub fn new(
2210 rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2211 execute_started: Instant,
2212 client: &SessionClient,
2213 instance_id: Option<ComputeInstanceId>,
2214 strategy: Option<StatementExecutionStrategy>,
2215 ) -> Self {
2216 let histogram = Self::histogram(client, instance_id, strategy);
2217 Self {
2218 rows,
2219 execute_started,
2220 time_to_first_row_seconds: histogram,
2221 saw_rows: false,
2222 recorded_first_row_instant: None,
2223 no_more_rows: false,
2224 metric_recorded: false,
2225 execution_time: None,
2226 execution_time_reported: false,
2227 }
2228 }
2229
2230 fn histogram(
2231 client: &SessionClient,
2232 instance_id: Option<ComputeInstanceId>,
2233 strategy: Option<StatementExecutionStrategy>,
2234 ) -> Histogram {
2235 let session = client.session.as_ref().expect("session invariant");
2236 let isolation_level = *session.vars().transaction_isolation();
2237 let name_hint = ApplicationNameHint::from_str(session.application_name());
2238
2239 client
2240 .inner()
2241 .metrics()
2242 .by_cluster
2243 .time_to_first_row_seconds(instance_id, isolation_level, strategy, name_hint)
2244 }
2245
2246 pub fn record(
2249 execute_started: Instant,
2250 client: &SessionClient,
2251 instance_id: Option<ComputeInstanceId>,
2252 strategy: Option<StatementExecutionStrategy>,
2253 ) {
2254 Self::histogram(client, instance_id, strategy)
2255 .observe(execute_started.elapsed().as_secs_f64());
2256 }
2257
2258 pub async fn recv(&mut self) -> Option<PeekResponseUnary> {
2259 let mut msg = self.rows.next().await;
2260 if !self.saw_rows && matches!(msg, Some(PeekResponseUnary::Rows(_))) {
2261 self.saw_rows = true;
2262 self.time_to_first_row_seconds
2263 .observe(self.execute_started.elapsed().as_secs_f64());
2264 self.recorded_first_row_instant = Some(Instant::now());
2265 }
2266 if msg.is_none() {
2267 self.no_more_rows = true;
2268 }
2269 if self.execution_time.is_none() && !self.execution_time_reported {
2270 let kind = match &mut msg {
2271 Some(PeekResponseUnary::Rows(rows)) => {
2274 rows.peek().is_some().then_some(ExecutionTimeKind::FirstRow)
2275 }
2276 None => Some(ExecutionTimeKind::EmptyResult),
2277 _ => None,
2278 };
2279 self.execution_time = kind.map(|kind| ExecutionTime {
2280 kind,
2281 elapsed: self.execute_started.elapsed(),
2282 });
2283 }
2284 msg
2285 }
2286
2287 pub fn without_execution_time(mut self) -> Self {
2289 self.execution_time = None;
2290 self.execution_time_reported = true;
2291 self
2292 }
2293
2294 pub fn take_execution_time(&mut self) -> Option<ExecutionTime> {
2297 self.execution_time_reported = true;
2298 self.execution_time.take()
2299 }
2300}
2301
2302#[cfg(test)]
2303mod tests {
2304 use super::*;
2305
2306 #[mz_ore::test]
2307 fn truncate_sql_for_logging_caps_length() {
2308 let short = "SELECT 1".to_string();
2309 assert_eq!(truncate_sql_for_logging(short.clone()), short);
2310
2311 let exact = "x".repeat(MAX_LOGGED_SQL_LEN);
2312 assert_eq!(truncate_sql_for_logging(exact.clone()), exact);
2313
2314 let long = format!(
2317 "{}é{}",
2318 "x".repeat(MAX_LOGGED_SQL_LEN - 1),
2319 "y".repeat(1000)
2320 );
2321 let truncated = truncate_sql_for_logging(long.clone());
2322 assert_eq!(
2323 truncated,
2324 format!(
2325 "{}... <truncated from {} bytes>",
2326 "x".repeat(MAX_LOGGED_SQL_LEN - 1),
2327 long.len()
2328 )
2329 );
2330 }
2331}