1use std::borrow::Cow;
11use std::collections::BTreeMap;
12use std::fmt::{Debug, Display, Formatter};
13use std::future::Future;
14use std::pin::{self, Pin};
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17
18use anyhow::bail;
19use chrono::{DateTime, Utc};
20use derivative::Derivative;
21use futures::{FutureExt, Stream, StreamExt};
22use itertools::Itertools;
23use mz_adapter_types::connection::{ConnectionId, ConnectionIdType};
24use mz_auth::password::Password;
25use mz_auth::{Authenticated, AuthenticatorKind};
26use mz_build_info::BuildInfo;
27use mz_compute_types::ComputeInstanceId;
28use mz_expr::UnmaterializableFunc;
29use mz_expr::{CollectionPlan, RowSetFinishing};
30use mz_ore::channel::OneshotReceiverExt;
31use mz_ore::collections::CollectionExt;
32use mz_ore::id_gen::{IdAllocator, IdAllocatorInnerBitSet, MAX_ORG_ID, org_id_conn_bits};
33use mz_ore::instrument;
34use mz_ore::now::{EpochMillis, NowFn, to_datetime};
35use mz_ore::str::StrExt;
36use mz_ore::task::AbortOnDropHandle;
37use mz_ore::thread::JoinOnDropHandle;
38use mz_ore::tracing::OpenTelemetryContext;
39use mz_repr::user::InternalUserMetadata;
40use mz_repr::{CatalogItemId, ColumnIndex, SqlScalarType};
41use mz_sql::ast::ConstantVisitor;
42use mz_sql::ast::{Raw, Statement};
43use mz_sql::catalog::{EnvironmentId, SessionCatalog};
44use mz_sql::plan::{MutationKind, Plan, ReadThenWritePlan};
45use mz_sql::session::hint::ApplicationNameHint;
46use mz_sql::session::metadata::SessionMetadata;
47use mz_sql::session::user::SUPPORT_USER;
48use mz_sql::session::vars::{
49 CLUSTER, ENABLE_FRONTEND_PEEK_SEQUENCING, OwnedVarInput, SystemVars, Var,
50};
51use mz_sql_parser::ast::display::AstDisplay;
52use mz_sql_parser::ast::{InsertStatement, StatementKind};
53use mz_sql_parser::parser::{ParserStatementError, StatementParseResult};
54use prometheus::Histogram;
55use serde_json::json;
56use tokio::sync::{mpsc, oneshot};
57use tracing::{debug, error};
58use uuid::Uuid;
59
60use crate::catalog::Catalog;
61use crate::command::{
62 CatalogDump, CatalogSnapshot, Command, CopyFromStdinWriter, ExecuteResponse, Response,
63 SASLChallengeResponse, SASLVerifyProofResponse, SuperuserAttribute,
64};
65use crate::config::{ScopedParameters, ScopedParametersScope, SystemParameterFrontend};
66use crate::coord::{Coordinator, ExecuteContextGuard};
67use crate::error::AdapterError;
68use crate::frontend_read_then_write::{
69 FrontendWriteAttemptState, FrontendWriteCancellation, contains_mz_now,
70 validate_selection_dependencies,
71};
72use crate::metrics::Metrics;
73use crate::optimize::dataflows::{EvalTime, ExprPrepOneShot};
74use crate::optimize::{self, Optimize, OptimizerError};
75use crate::peek_client::{ExecutionLogging, TakeOver};
76use crate::session::{
77 EndTransactionAction, PreparedStatement, Session, SessionConfig, StateRevision, TransactionId,
78 TransactionStatus,
79};
80use crate::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
81use crate::telemetry::{self, EventDetails, SegmentClientExt, StatementFailureType};
82use crate::webhook::AppendWebhookResponse;
83use crate::{AdapterNotice, AppendWebhookError, PeekClient, PeekResponseUnary, StartupResponse};
84
85pub struct Handle {
91 pub(crate) session_id: Uuid,
92 pub(crate) start_instant: Instant,
93 pub(crate) _thread: JoinOnDropHandle<()>,
94}
95
96impl Handle {
97 pub fn session_id(&self) -> Uuid {
103 self.session_id
104 }
105
106 pub fn start_instant(&self) -> Instant {
108 self.start_instant
109 }
110}
111
112#[derive(Debug, Clone)]
120pub struct Client {
121 build_info: &'static BuildInfo,
122 inner_cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
123 id_alloc: IdAllocator<IdAllocatorInnerBitSet>,
124 now: NowFn,
125 metrics: Metrics,
126 environment_id: EnvironmentId,
127 segment_client: Option<mz_segment::Client>,
128}
129
130impl Client {
131 pub(crate) fn new(
132 build_info: &'static BuildInfo,
133 cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
134 metrics: Metrics,
135 now: NowFn,
136 environment_id: EnvironmentId,
137 segment_client: Option<mz_segment::Client>,
138 ) -> Client {
139 let env_lower = org_id_conn_bits(&environment_id.organization_id());
147 Client {
148 build_info,
149 inner_cmd_tx: cmd_tx,
150 id_alloc: IdAllocator::new(1, MAX_ORG_ID, env_lower),
151 now,
152 metrics,
153 environment_id,
154 segment_client,
155 }
156 }
157
158 pub fn new_conn_id(&self) -> Result<ConnectionId, AdapterError> {
160 self.id_alloc.alloc().ok_or(AdapterError::IdExhaustionError)
161 }
162
163 pub fn new_session(&self, config: SessionConfig, _authenticated: Authenticated) -> Session {
169 Session::new(self.build_info, config, self.metrics().session_metrics())
173 }
174
175 pub async fn authenticate(
179 &self,
180 user: &String,
181 password: &Password,
182 ) -> Result<Authenticated, AdapterError> {
183 let (tx, rx) = oneshot::channel();
184 self.send(Command::AuthenticatePassword {
185 role_name: user.to_string(),
186 password: Some(password.clone()),
187 tx,
188 });
189 rx.await.expect("sender dropped")?;
190 Ok(Authenticated)
191 }
192
193 pub async fn generate_sasl_challenge(
196 &self,
197 user: &String,
198 client_nonce: &String,
199 ) -> Result<SASLChallengeResponse, AdapterError> {
200 let (tx, rx) = oneshot::channel();
201 self.send(Command::AuthenticateGetSASLChallenge {
202 role_name: user.to_string(),
203 nonce: client_nonce.to_string(),
204 tx,
205 });
206 let response = rx.await.expect("sender dropped")?;
207 Ok(response)
208 }
209
210 pub async fn verify_sasl_proof(
213 &self,
214 user: &String,
215 proof: &String,
216 nonce: &String,
217 mock_hash: &String,
218 ) -> Result<(SASLVerifyProofResponse, Authenticated), AdapterError> {
219 let (tx, rx) = oneshot::channel();
220 self.send(Command::AuthenticateVerifySASLProof {
221 role_name: user.to_string(),
222 proof: proof.to_string(),
223 auth_message: nonce.to_string(),
224 mock_hash: mock_hash.to_string(),
225 tx,
226 });
227 let response = rx.await.expect("sender dropped")?;
228 Ok((response, Authenticated))
229 }
230
231 pub async fn role_can_login(&self, role_name: &str) -> Result<(), AdapterError> {
233 let (tx, rx) = oneshot::channel();
234 self.send(Command::CheckRoleCanLogin {
235 role_name: role_name.to_string(),
236 tx,
237 });
238 rx.await.expect("sender dropped")
239 }
240
241 #[mz_ore::instrument(level = "debug")]
250 pub async fn startup(&self, session: Session) -> Result<SessionClient, AdapterError> {
251 let user = session.user().clone();
252 let conn_id = session.conn_id().clone();
253 let secret_key = session.secret_key();
254 let uuid = session.uuid();
255 let client_ip = session.client_ip();
256 let application_name = session.application_name().into();
257 let notice_tx = session.retain_notice_transmitter();
258
259 let (tx, rx) = oneshot::channel();
260
261 let rx = rx.with_guard(|resp: Result<StartupResponse, _>| {
272 if resp.is_ok() {
273 self.send(Command::Terminate {
274 conn_id: conn_id.clone(),
275 tx: None,
276 });
277 }
278 });
279
280 self.send(Command::Startup {
281 tx,
282 user,
283 conn_id: conn_id.clone(),
284 secret_key,
285 uuid,
286 client_ip: client_ip.copied(),
287 application_name,
288 notice_tx,
289 });
290
291 let response = rx.await.expect("sender dropped")?;
294
295 let StartupResponse {
299 role_id,
300 write_notify,
301 session_defaults,
302 catalog,
303 storage_collections,
304 transient_id_gen,
305 optimizer_metrics,
306 persist_client,
307 statement_logging_frontend,
308 superuser_attribute,
309 occ_write_semaphore,
310 frontend_read_then_write_enabled,
311 group_commit_notifier,
312 read_only,
313 } = response;
314
315 let peek_client = PeekClient::new(
316 self.clone(),
317 &catalog,
318 storage_collections,
319 transient_id_gen,
320 optimizer_metrics,
321 persist_client,
322 statement_logging_frontend,
323 occ_write_semaphore,
324 frontend_read_then_write_enabled,
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_peek_sequencing: false, };
338
339 let session = client.session();
340
341 if let SuperuserAttribute(Some(superuser)) = superuser_attribute {
344 session.apply_internal_user_metadata(InternalUserMetadata { superuser });
345 }
346
347 session.initialize_role_metadata(role_id);
348 let vars_mut = session.vars_mut();
349 for (name, val) in session_defaults {
350 if let Err(err) = vars_mut.set_default(&name, val.borrow()) {
351 tracing::error!("failed to set peristed default, {err:?}");
354 }
355 }
356 session
357 .vars_mut()
358 .end_transaction(EndTransactionAction::Commit);
359
360 session.set_builtin_table_updates(write_notify);
368
369 let catalog = catalog.for_session(session);
370
371 let cluster_active = session.vars().cluster().to_string();
372 if session.vars().welcome_message() {
373 let cluster_info = if catalog.resolve_cluster(Some(&cluster_active)).is_err() {
374 format!("{cluster_active} (does not exist)")
375 } else {
376 cluster_active.to_string()
377 };
378
379 session.add_notice(AdapterNotice::Welcome(format!(
383 "connected to Materialize v{}
384 Environment ID: {}
385 Region: {}
386 User: {}
387 Cluster: {}
388 Database: {}
389 {}
390 Session UUID: {}
391
392Issue a SQL query to get started. Need help?
393 View documentation: https://materialize.com/s/docs
394 Join our Slack community: https://materialize.com/s/chat
395 ",
396 session.vars().build_info().semver_version(),
397 self.environment_id,
398 self.environment_id.region(),
399 session.vars().user().name,
400 cluster_info,
401 session.vars().database(),
402 match session.vars().search_path() {
403 [schema] => format!("Schema: {}", schema),
404 schemas => format!(
405 "Search path: {}",
406 schemas.iter().map(|id| id.to_string()).join(", ")
407 ),
408 },
409 session.uuid(),
410 )));
411 }
412
413 if session.vars().current_object_missing_warnings() {
414 if catalog.active_database().is_none() {
415 let db = session.vars().database().into();
416 session.add_notice(AdapterNotice::UnknownSessionDatabase(db));
417 }
418 }
419
420 let cluster_var = session
423 .vars()
424 .inspect(CLUSTER.name())
425 .expect("cluster should exist");
426 if session.vars().current_object_missing_warnings()
427 && catalog.resolve_cluster(Some(&cluster_active)).is_err()
428 {
429 let cluster_notice = 'notice: {
430 if cluster_var.inspect_session_value().is_some() {
431 break 'notice Some(AdapterNotice::DefaultClusterDoesNotExist {
432 name: cluster_active,
433 kind: "session",
434 suggested_action: "Pick an extant cluster with SET CLUSTER = name. Run SHOW CLUSTERS to see available clusters.".into(),
435 });
436 }
437
438 let role_default = catalog.get_role(catalog.active_role_id());
439 let role_cluster = match role_default.vars().get(CLUSTER.name()) {
440 Some(OwnedVarInput::Flat(name)) => Some(name),
441 None => None,
442 Some(v @ OwnedVarInput::SqlSet(_)) => {
444 tracing::warn!(?v, "SqlSet found for cluster Role Default");
445 break 'notice None;
446 }
447 };
448
449 let alter_role = "with `ALTER ROLE <role> SET cluster TO <cluster>;`";
450 match role_cluster {
451 None => Some(AdapterNotice::DefaultClusterDoesNotExist {
453 name: cluster_active,
454 kind: "system",
455 suggested_action: format!(
456 "Set a default cluster for the current role {alter_role}."
457 ),
458 }),
459 Some(_) => Some(AdapterNotice::DefaultClusterDoesNotExist {
461 name: cluster_active,
462 kind: "role",
463 suggested_action: format!(
464 "Change the default cluster for the current role {alter_role}."
465 ),
466 }),
467 }
468 };
469
470 if let Some(notice) = cluster_notice {
471 session.add_notice(notice);
472 }
473 }
474
475 client.enable_frontend_peek_sequencing = ENABLE_FRONTEND_PEEK_SEQUENCING
476 .require(catalog.system_vars())
477 .is_ok();
478
479 Ok(client)
480 }
481
482 pub fn cancel_request(&self, conn_id: ConnectionIdType, secret_key: u32) {
484 self.send(Command::CancelRequest {
485 conn_id,
486 secret_key,
487 });
488 }
489
490 pub async fn support_execute_one(
493 &self,
494 sql: &str,
495 ) -> Result<Pin<Box<dyn Stream<Item = PeekResponseUnary> + Send>>, anyhow::Error> {
496 let conn_id = self.new_conn_id()?;
498 let session = self.new_session(
499 SessionConfig {
500 conn_id,
501 uuid: Uuid::new_v4(),
502 user: SUPPORT_USER.name.clone(),
503 client_ip: None,
504 external_metadata_rx: None,
505 helm_chart_version: None,
506 authenticator_kind: AuthenticatorKind::None,
507 groups: None,
508 },
509 Authenticated,
510 );
511 let mut session_client = self.startup(session).await?;
512
513 let stmts = mz_sql::parse::parse(sql)?;
515 if stmts.len() != 1 {
516 bail!("must supply exactly one query");
517 }
518 let StatementParseResult { ast: stmt, sql } = stmts.into_element();
519
520 const EMPTY_PORTAL: &str = "";
521 session_client.start_transaction(Some(1))?;
522 session_client
523 .declare(EMPTY_PORTAL.into(), stmt, sql.to_string())
524 .await?;
525
526 let execute_result = session_client
527 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
528 .await?;
529 match execute_result {
530 (ExecuteResponse::SendingRowsStreaming { mut rows, .. }, _) => {
531 let owning_response_stream = async_stream::stream! {
536 while let Some(rows) = rows.next().await {
537 yield rows;
538 }
539 drop(session_client);
540 };
541 Ok(Box::pin(owning_response_stream))
542 }
543 r => bail!("unsupported response type: {r:?}"),
544 }
545 }
546
547 pub fn metrics(&self) -> &Metrics {
549 &self.metrics
550 }
551
552 pub fn now(&self) -> DateTime<Utc> {
554 to_datetime((self.now)())
555 }
556
557 pub async fn get_webhook_appender(
559 &self,
560 database: String,
561 schema: String,
562 name: String,
563 ) -> Result<AppendWebhookResponse, AppendWebhookError> {
564 let (tx, rx) = oneshot::channel();
565
566 self.send(Command::GetWebhook {
568 database,
569 schema,
570 name,
571 tx,
572 });
573
574 let response = rx
576 .await
577 .map_err(|_| anyhow::anyhow!("failed to receive webhook response"))?;
578
579 response
580 }
581
582 pub async fn get_system_vars(&self) -> SystemVars {
584 let (tx, rx) = oneshot::channel();
585 self.send(Command::GetSystemVars { tx });
586 rx.await.expect("coordinator unexpectedly gone")
587 }
588
589 pub async fn catalog_snapshot_expensive(&self) -> Arc<Catalog> {
595 let (tx, rx) = oneshot::channel();
596 self.send(Command::CatalogSnapshot { tx });
597 let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone");
598 catalog
599 }
600
601 pub async fn update_scoped_system_parameters(
610 &self,
611 overrides: ScopedParameters,
612 prune_scope: ScopedParametersScope,
613 ) {
614 let (tx, rx) = oneshot::channel();
615 self.send(Command::UpdateScopedSystemParameters {
616 overrides,
617 prune_scope,
618 tx,
619 });
620 let _ = rx.await;
621 }
622
623 pub fn install_scoped_system_parameter_frontend(&self, frontend: Arc<SystemParameterFrontend>) {
628 self.send(Command::InstallScopedSystemParameterFrontend { frontend });
629 }
630
631 #[instrument(level = "debug")]
632 pub(crate) fn try_send(&self, cmd: Command) -> bool {
633 self.inner_cmd_tx
634 .send((OpenTelemetryContext::obtain(), cmd))
635 .is_ok()
636 }
637
638 #[instrument(level = "debug")]
639 pub(crate) fn send(&self, cmd: Command) {
640 assert!(self.try_send(cmd), "coordinator unexpectedly gone");
641 }
642}
643
644pub struct SessionClient {
648 inner: Option<Client>,
652 session: Option<Session>,
655 timeouts: Timeout,
656 segment_client: Option<mz_segment::Client>,
657 environment_id: EnvironmentId,
658 peek_client: PeekClient,
660 pub enable_frontend_peek_sequencing: bool,
665}
666
667impl SessionClient {
668 pub fn parse<'a>(
671 &self,
672 sql: &'a str,
673 ) -> Result<Result<Vec<StatementParseResult<'a>>, ParserStatementError>, String> {
674 match mz_sql::parse::parse_with_limit(sql) {
675 Ok(Err(e)) => {
676 self.track_statement_parse_failure(&e);
677 Ok(Err(e))
678 }
679 r => r,
680 }
681 }
682
683 fn track_statement_parse_failure(&self, parse_error: &ParserStatementError) {
684 let session = self.session.as_ref().expect("session invariant violated");
685 let Some(user_id) = session.user().external_metadata.as_ref().map(|m| m.user_id) else {
686 return;
687 };
688 let Some(segment_client) = &self.segment_client else {
689 return;
690 };
691 let Some(statement_kind) = parse_error.statement else {
692 return;
693 };
694 let Some((action, object_type)) = telemetry::analyze_audited_statement(statement_kind)
695 else {
696 return;
697 };
698 let event_type = StatementFailureType::ParseFailure;
699 let event_name = format!(
700 "{} {} {}",
701 object_type.as_title_case(),
702 action.as_title_case(),
703 event_type.as_title_case(),
704 );
705 segment_client.environment_track(
706 &self.environment_id,
707 event_name,
708 json!({
709 "statement_kind": statement_kind,
710 "error": &parse_error.error,
711 }),
712 EventDetails {
713 user_id: Some(user_id),
714 application_name: Some(session.application_name()),
715 ..Default::default()
716 },
717 );
718 }
719
720 pub async fn get_prepared_statement(
723 &mut self,
724 name: &str,
725 ) -> Result<&PreparedStatement, AdapterError> {
726 let catalog = self.catalog_snapshot("get_prepared_statement").await;
727 Coordinator::verify_prepared_statement(&catalog, self.session(), name)?;
728 Ok(self
729 .session()
730 .get_prepared_statement_unverified(name)
731 .expect("must exist"))
732 }
733
734 pub async fn prepare(
739 &mut self,
740 name: String,
741 stmt: Option<Statement<Raw>>,
742 sql: String,
743 param_types: Vec<Option<SqlScalarType>>,
744 ) -> Result<(), AdapterError> {
745 let catalog = self.catalog_snapshot("prepare").await;
746
747 let mut async_pause = false;
750 (|| {
751 fail::fail_point!("async_prepare", |val| {
752 async_pause = val.map_or(false, |val| val.parse().unwrap_or(false))
753 });
754 })();
755 if async_pause {
756 tokio::time::sleep(Duration::from_secs(1)).await;
757 };
758
759 let desc = Coordinator::describe(&catalog, self.session(), stmt.clone(), param_types)?;
760 let now = self.now();
761 let state_revision = StateRevision {
762 catalog_revision: catalog.transient_revision(),
763 session_state_revision: self.session().state_revision(),
764 };
765 self.session()
766 .set_prepared_statement(name, stmt, sql, desc, state_revision, now);
767 Ok(())
768 }
769
770 #[mz_ore::instrument(level = "debug")]
772 pub async fn declare(
773 &mut self,
774 name: String,
775 stmt: Statement<Raw>,
776 sql: String,
777 ) -> Result<(), AdapterError> {
778 let catalog = self.catalog_snapshot("declare").await;
779 let param_types = vec![];
780 let desc =
781 Coordinator::describe(&catalog, self.session(), Some(stmt.clone()), param_types)?;
782 let params = vec![];
783 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
784 let now = self.now();
785 let logging = self.session().mint_logging(sql, Some(&stmt), now);
786 let state_revision = StateRevision {
787 catalog_revision: catalog.transient_revision(),
788 session_state_revision: self.session().state_revision(),
789 };
790 self.session().set_portal(
791 name,
792 desc,
793 Some(stmt),
794 logging,
795 params,
796 result_formats,
797 state_revision,
798 )?;
799 Ok(())
800 }
801
802 #[mz_ore::instrument(level = "debug")]
809 pub async fn execute(
810 &mut self,
811 portal_name: String,
812 cancel_future: impl Future<Output = std::io::Error> + Send,
813 outer_ctx_extra: Option<ExecuteContextGuard>,
814 ) -> Result<(ExecuteResponse, Instant), AdapterError> {
815 let execute_started = Instant::now();
816 let cancel_future = cancel_future.map(|_| ()).shared();
817
818 let mut logging = ExecutionLogging::adopt(outer_ctx_extra, &self.peek_client);
822
823 let result = self
824 .execute_attempts(portal_name, &mut logging, cancel_future)
825 .await;
826
827 logging.retire(&result);
828
829 result.map(|response| (response, execute_started))
830 }
831
832 async fn execute_attempts(
836 &mut self,
837 portal_name: String,
838 logging: &mut ExecutionLogging,
839 cancel_future: impl Future<Output = ()> + Send + Clone,
840 ) -> Result<ExecuteResponse, AdapterError> {
841 let portal_name = self.unroll_sql_execute(portal_name, logging).await?;
854
855 let peek_result = self.try_frontend_peek(&portal_name, logging).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_frontend_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 let res = self
1077 .send(|tx, session| Command::Commit {
1078 action,
1079 session,
1080 tx,
1081 })
1082 .await;
1083 let _ = self.session().clear_transaction();
1087 res
1088 }
1089
1090 pub fn fail_transaction(&mut self) {
1092 let session = self.session.take().expect("session invariant violated");
1093 let session = session.fail_transaction();
1094 self.session = Some(session);
1095 }
1096
1097 #[instrument(level = "debug")]
1101 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
1102 self.peek_client.catalog_snapshot(context).await
1103 }
1104
1105 pub async fn statement_arrival_logging_enabled(&mut self) -> bool {
1107 let catalog = self.catalog_snapshot("statement_arrival_logging").await;
1108 catalog.system_config().enable_statement_arrival_logging()
1109 }
1110
1111 pub async fn extended_protocol_implicit_transaction_enabled(&mut self) -> bool {
1113 let catalog = self
1114 .catalog_snapshot("extended_protocol_implicit_transaction")
1115 .await;
1116 catalog
1117 .system_config()
1118 .enable_extended_protocol_implicit_transaction()
1119 }
1120
1121 pub async fn dump_catalog(&mut self) -> Result<CatalogDump, AdapterError> {
1126 let catalog = self.catalog_snapshot("dump_catalog").await;
1127 catalog.dump().map_err(AdapterError::from)
1128 }
1129
1130 pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> {
1136 let catalog = self.catalog_snapshot("check_catalog").await;
1137 catalog.check_consistency()
1138 }
1139
1140 pub async fn check_coordinator(&self) -> Result<(), serde_json::Value> {
1146 self.send_without_session(|tx| Command::CheckConsistency { tx })
1147 .await
1148 .map_err(|inconsistencies| {
1149 serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1150 serde_json::Value::String("failed to serialize inconsistencies".to_string())
1151 })
1152 })
1153 }
1154
1155 pub async fn dump_coordinator_state(&self) -> Result<serde_json::Value, anyhow::Error> {
1156 self.send_without_session(|tx| Command::Dump { tx }).await
1157 }
1158
1159 pub fn retire_execute(
1162 &self,
1163 guard: ExecuteContextGuard,
1164 reason: StatementEndedExecutionReason,
1165 ) {
1166 if !guard.is_trivial() {
1167 let data = guard.defuse();
1168 let cmd = Command::RetireExecute { data, reason };
1169 self.inner().send(cmd);
1170 }
1171 }
1172
1173 pub async fn start_copy_from_stdin(
1179 &mut self,
1180 target_id: CatalogItemId,
1181 target_name: String,
1182 columns: Vec<ColumnIndex>,
1183 row_desc: mz_repr::RelationDesc,
1184 params: mz_pgcopy::CopyFormatParams<'static>,
1185 ) -> Result<CopyFromStdinWriter, AdapterError> {
1186 self.send(|tx, session| Command::StartCopyFromStdin {
1187 target_id,
1188 target_name,
1189 columns,
1190 row_desc,
1191 params,
1192 session,
1193 tx,
1194 })
1195 .await
1196 }
1197
1198 pub fn stage_copy_from_stdin_batches(
1203 &mut self,
1204 target_id: CatalogItemId,
1205 batches: Vec<mz_persist_client::batch::ProtoBatch>,
1206 ) -> Result<(), AdapterError> {
1207 use crate::session::{TransactionOps, WriteOp};
1208 use mz_storage_client::client::TableData;
1209
1210 self.session()
1211 .add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
1212 id: target_id,
1213 rows: TableData::Batches(batches.into()),
1214 }]))?;
1215 Ok(())
1216 }
1217
1218 pub async fn get_system_vars(&self) -> SystemVars {
1220 self.inner().get_system_vars().await
1221 }
1222
1223 pub async fn set_system_vars(
1225 &mut self,
1226 vars: BTreeMap<String, String>,
1227 ) -> Result<(), AdapterError> {
1228 let conn_id = self.session().conn_id().clone();
1229 self.send_without_session(|tx| Command::SetSystemVars { vars, conn_id, tx })
1230 .await
1231 }
1232
1233 pub async fn inject_audit_events(
1238 &mut self,
1239 events: Vec<crate::catalog::InjectedAuditEvent>,
1240 ) -> Result<(), AdapterError> {
1241 let conn_id = self.session().conn_id().clone();
1242 self.send_without_session(|tx| Command::InjectAuditEvents {
1243 events,
1244 conn_id,
1245 tx,
1246 })
1247 .await
1248 }
1249
1250 pub async fn terminate(&mut self) {
1252 let conn_id = self.session().conn_id().clone();
1253 let res = self
1254 .send_without_session(|tx| Command::Terminate {
1255 conn_id,
1256 tx: Some(tx),
1257 })
1258 .await;
1259 if let Err(e) = res {
1260 error!("Unable to terminate session: {e:?}");
1262 }
1263 self.inner = None;
1265 }
1266
1267 pub fn session(&mut self) -> &mut Session {
1269 self.session.as_mut().expect("session invariant violated")
1270 }
1271
1272 pub fn inner(&self) -> &Client {
1274 self.inner.as_ref().expect("inner invariant violated")
1275 }
1276
1277 async fn send_without_session<T, F>(&self, f: F) -> T
1278 where
1279 F: FnOnce(oneshot::Sender<T>) -> Command,
1280 {
1281 let (tx, rx) = oneshot::channel();
1282 self.inner().send(f(tx));
1283 rx.await.expect("sender dropped")
1284 }
1285
1286 #[instrument(level = "debug")]
1287 async fn send<T, F>(&mut self, f: F) -> Result<T, AdapterError>
1288 where
1289 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1290 {
1291 self.send_with_cancel(f, futures::future::pending()).await
1292 }
1293
1294 #[instrument(level = "debug")]
1298 async fn send_with_cancel<T, F>(
1299 &mut self,
1300 f: F,
1301 cancel_future: impl Future<Output = ()> + Send,
1302 ) -> Result<T, AdapterError>
1303 where
1304 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1305 {
1306 let session = self.session.take().expect("session invariant violated");
1307 let mut typ = None;
1308 let application_name = session.application_name();
1309 let name_hint = ApplicationNameHint::from_str(application_name);
1310 let conn_id = session.conn_id().clone();
1311 let (tx, rx) = oneshot::channel();
1312
1313 let Self {
1316 inner: inner_client,
1317 session: client_session,
1318 ..
1319 } = self;
1320
1321 let inner_client = inner_client.as_ref().expect("inner invariant violated");
1324
1325 let mut guarded_rx = rx.with_guard(|response: Response<_>| {
1331 *client_session = Some(response.session);
1332 });
1333
1334 inner_client.send({
1335 let cmd = f(tx, session);
1336 match cmd {
1340 Command::Execute { .. } => typ = Some("execute"),
1341 Command::GetWebhook { .. } => typ = Some("webhook"),
1342 Command::StartCopyFromStdin { .. }
1343 | Command::Startup { .. }
1344 | Command::AuthenticatePassword { .. }
1345 | Command::AuthenticateGetSASLChallenge { .. }
1346 | Command::AuthenticateVerifySASLProof { .. }
1347 | Command::CheckRoleCanLogin { .. }
1348 | Command::CatalogSnapshot { .. }
1349 | Command::Commit { .. }
1350 | Command::CancelRequest { .. }
1351 | Command::PrivilegedCancelRequest { .. }
1352 | Command::GetSystemVars { .. }
1353 | Command::SetSystemVars { .. }
1354 | Command::UpdateScopedSystemParameters { .. }
1355 | Command::InstallScopedSystemParameterFrontend { .. }
1356 | Command::Terminate { .. }
1357 | Command::RetireExecute { .. }
1358 | Command::CheckConsistency { .. }
1359 | Command::Dump { .. }
1360 | Command::GetComputeInstanceClient { .. }
1361 | Command::GetOracle { .. }
1362 | Command::DetermineRealTimeRecentTimestamp { .. }
1363 | Command::GetTransactionReadHoldsBundle { .. }
1364 | Command::StoreTransactionReadHolds { .. }
1365 | Command::ExecuteSlowPathPeek { .. }
1366 | Command::ExecuteSubscribe { .. }
1367 | Command::CopyToPreflight { .. }
1368 | Command::ExecuteCopyTo { .. }
1369 | Command::ExecuteSideEffectingFunc { .. }
1370 | Command::LookupConnection { .. }
1371 | Command::RegisterFrontendPeek { .. }
1372 | Command::UnregisterFrontendPeek { .. }
1373 | Command::ExplainTimestamp { .. }
1374 | Command::FrontendStatementLogging(..)
1375 | Command::InjectAuditEvents { .. }
1376 | Command::RegisterConnectionCancelWatch { .. }
1377 | Command::CreateInternalSubscribe { .. }
1378 | Command::AttemptWrite { .. }
1379 | Command::DropInternalSubscribe { .. } => {}
1380 };
1381 cmd
1382 });
1383
1384 let mut cancel_future = pin::pin!(cancel_future);
1385 let mut cancelled = false;
1386 loop {
1387 tokio::select! {
1388 res = &mut guarded_rx => {
1389 drop(guarded_rx);
1391
1392 let res = res.expect("sender dropped");
1393 let status = res.result.is_ok().then_some("success").unwrap_or("error");
1394 if let Err(err) = res.result.as_ref() {
1395 if name_hint.should_trace_errors() {
1396 tracing::warn!(?err, ?name_hint, "adapter response error");
1397 }
1398 }
1399
1400 if let Some(typ) = typ {
1401 inner_client
1402 .metrics
1403 .commands
1404 .with_label_values(&[typ, status, name_hint.as_str()])
1405 .inc();
1406 }
1407 *client_session = Some(res.session);
1408 return res.result;
1409 },
1410 _ = &mut cancel_future, if !cancelled => {
1411 cancelled = true;
1412 inner_client.send(Command::PrivilegedCancelRequest {
1413 conn_id: conn_id.clone(),
1414 });
1415 }
1416 };
1417 }
1418 }
1419
1420 pub fn add_idle_in_transaction_session_timeout(&mut self) {
1421 let session = self.session();
1422 let timeout_dur = session.vars().idle_in_transaction_session_timeout();
1423 if !timeout_dur.is_zero() {
1424 let timeout_dur = timeout_dur.clone();
1425 if let Some(txn) = session.transaction().inner() {
1426 let txn_id = txn.id.clone();
1427 let timeout = TimeoutType::IdleInTransactionSession(txn_id);
1428 self.timeouts.add_timeout(timeout, timeout_dur);
1429 }
1430 }
1431 }
1432
1433 pub fn remove_idle_in_transaction_session_timeout(&mut self) {
1434 let session = self.session();
1435 if let Some(txn) = session.transaction().inner() {
1436 let txn_id = txn.id.clone();
1437 self.timeouts
1438 .remove_timeout(&TimeoutType::IdleInTransactionSession(txn_id));
1439 }
1440 }
1441
1442 pub async fn recv_timeout(&mut self) -> Option<TimeoutType> {
1449 self.timeouts.recv().await
1450 }
1451
1452 pub(crate) async fn try_frontend_peek(
1457 &mut self,
1458 portal_name: &str,
1459 logging: &mut ExecutionLogging,
1460 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1461 if self.enable_frontend_peek_sequencing {
1462 let session = self.session.as_mut().expect("SessionClient invariant");
1463 self.peek_client
1464 .try_frontend_peek(portal_name, session, logging)
1465 .await
1466 } else {
1467 Ok(None)
1468 }
1469 }
1470
1471 fn frontend_read_then_write_applies(&self, portal_name: &str) -> bool {
1479 if !self.peek_client.frontend_read_then_write_enabled {
1480 return false;
1481 }
1482 let session = self.session.as_ref().expect("SessionClient invariant");
1483 match session.get_portal_unverified(portal_name) {
1484 Some(portal) => portal
1485 .stmt
1486 .as_deref()
1487 .is_some_and(is_read_then_write_statement),
1488 None => false,
1489 }
1490 }
1491
1492 async fn try_frontend_read_then_write_with_cancel(
1503 &mut self,
1504 portal_name: &str,
1505 logging: &mut ExecutionLogging,
1506 cancel_future: impl Future<Output = ()> + Send,
1507 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1508 if !self.frontend_read_then_write_applies(portal_name) {
1513 return Ok(None);
1514 }
1515
1516 let conn_id = self.session().conn_id().clone();
1517 let statement_timeout = *self.session().vars().statement_timeout();
1518 let inner_client = self.inner().clone();
1519 let attempt_state = Arc::new(FrontendWriteAttemptState::new());
1520
1521 let mut cancel_future = pin::pin!(cancel_future);
1522 let statement_timeout = async move {
1523 if statement_timeout.is_zero() {
1524 futures::future::pending::<()>().await;
1525 } else {
1526 tokio::time::sleep(statement_timeout).await;
1527 }
1528 };
1529 tokio::pin!(statement_timeout);
1530
1531 let mut connection_cancel_rx = {
1537 let register =
1538 self.peek_client
1539 .call_coordinator(|tx| Command::RegisterConnectionCancelWatch {
1540 conn_id: conn_id.clone(),
1541 tx,
1542 });
1543 tokio::pin!(register);
1544 tokio::select! {
1545 rx = &mut register => rx,
1546 _ = &mut cancel_future => {
1547 inner_client.try_send(Command::PrivilegedCancelRequest {
1548 conn_id: conn_id.clone(),
1549 });
1550 return Err(AdapterError::Canceled);
1551 }
1552 _ = &mut statement_timeout => {
1553 inner_client.try_send(Command::PrivilegedCancelRequest {
1554 conn_id: conn_id.clone(),
1555 });
1556 return Err(AdapterError::StatementTimeout);
1557 }
1558 }
1559 };
1560 if *connection_cancel_rx.borrow() {
1561 return Err(AdapterError::Canceled);
1562 }
1563 let connection_cancel = async move {
1564 if connection_cancel_rx.wait_for(|v| *v).await.is_err() {
1565 futures::future::pending::<()>().await;
1566 }
1567 };
1568 tokio::pin!(connection_cancel);
1569
1570 let frontend_read_then_write =
1571 self.try_frontend_read_then_write(portal_name, logging, Arc::clone(&attempt_state));
1572 tokio::pin!(frontend_read_then_write);
1573
1574 let requested = tokio::select! {
1575 response = &mut frontend_read_then_write => return response,
1576 _ = &mut cancel_future => FrontendWriteCancellation::Canceled,
1577 _ = &mut connection_cancel => FrontendWriteCancellation::Canceled,
1578 _ = &mut statement_timeout => FrontendWriteCancellation::StatementTimeout,
1579 };
1580
1581 attempt_state.request(requested);
1582 inner_client.try_send(Command::PrivilegedCancelRequest {
1583 conn_id: conn_id.clone(),
1584 });
1585
1586 if !attempt_state.write_submitted() {
1587 return Err(requested.into());
1588 }
1589
1590 frontend_read_then_write.await
1593 }
1594
1595 async fn try_frontend_read_then_write(
1602 &mut self,
1603 portal_name: &str,
1604 logging: &mut ExecutionLogging,
1605 attempt_state: Arc<FrontendWriteAttemptState>,
1606 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1607 if !self.peek_client.frontend_read_then_write_enabled {
1611 return Ok(None);
1612 }
1613
1614 let catalog = self.catalog_snapshot("try_frontend_read_then_write").await;
1615
1616 let stmt = {
1617 let session = self.session.as_ref().expect("SessionClient invariant");
1618 let portal = match session.get_portal_unverified(portal_name) {
1619 Some(portal) => portal,
1620 None => return Ok(None), };
1622 portal.stmt.clone()
1623 };
1624
1625 let stmt = match stmt {
1626 Some(stmt) if is_read_then_write_statement(&stmt) => stmt,
1627 Some(_stmt) => {
1628 return Ok(None);
1629 }
1630 None => {
1631 return Ok(None);
1632 }
1633 };
1634
1635 Coordinator::verify_portal(
1640 &catalog,
1641 self.session.as_mut().expect("SessionClient invariant"),
1642 portal_name,
1643 )?;
1644
1645 let (params, logging_info, lifecycle_timestamps) = {
1646 let portal = self
1647 .session
1648 .as_ref()
1649 .expect("SessionClient invariant")
1650 .get_portal_unverified(portal_name)
1651 .expect("verified above");
1652 (
1653 portal.parameters.clone(),
1654 Arc::clone(&portal.logging),
1655 portal.lifecycle_timestamps.clone(),
1656 )
1657 };
1658
1659 logging.take_over(
1663 &self.peek_client,
1664 self.session.as_mut().expect("SessionClient invariant"),
1665 Some(&stmt),
1666 ¶ms,
1667 &logging_info,
1668 &catalog,
1669 lifecycle_timestamps,
1670 TakeOver::StatementToRun,
1671 );
1672
1673 {
1685 let session = self.session.as_ref().expect("SessionClient invariant");
1686 let contains_ops = session.transaction().contains_ops();
1694 match session.transaction() {
1695 TransactionStatus::Default | TransactionStatus::Failed(_) => {}
1696 TransactionStatus::Started(_) if !contains_ops => {}
1697 TransactionStatus::Started(_)
1698 | TransactionStatus::InTransactionImplicit(_)
1699 | TransactionStatus::InTransaction(_) => {
1700 let constant_insert = matches!(
1701 &*stmt,
1702 Statement::Insert(InsertStatement {
1703 source, returning, ..
1704 }) if returning.is_empty() && ConstantVisitor::insert_source(source)
1705 );
1706 if !constant_insert {
1707 return Err(prohibited_in_transaction(&stmt));
1708 }
1709 }
1710 }
1711 }
1712
1713 let (plan, target_cluster, resolved_ids, sql_impl_ids) = {
1714 let session = self.session.as_mut().expect("SessionClient invariant");
1715 let conn_catalog = catalog.for_session(session);
1716 let (stmt, resolved_ids) = mz_sql::names::resolve(&conn_catalog, (*stmt).clone())?;
1717 let pcx = session.pcx();
1718 let (plan, sql_impl_ids) =
1719 mz_sql::plan::plan(Some(pcx), &conn_catalog, stmt, ¶ms, &resolved_ids)?;
1720
1721 let target_cluster = match session.transaction().cluster() {
1722 Some(cluster_id) => crate::coord::TargetCluster::Transaction(cluster_id),
1723 None => crate::coord::catalog_serving::auto_run_on_catalog_server(
1724 &conn_catalog,
1725 session,
1726 &plan,
1727 ),
1728 };
1729
1730 (plan, target_cluster, resolved_ids, sql_impl_ids)
1731 };
1732
1733 if self.peek_client.read_only {
1740 return Err(AdapterError::ReadOnly);
1741 }
1742
1743 let (target_cluster_id, target_cluster_name) = {
1749 let session = self.session.as_ref().expect("SessionClient invariant");
1750 match catalog.resolve_target_cluster(target_cluster.clone(), session) {
1751 Ok(cluster) => (Some(cluster.id), Some(cluster.name.clone())),
1752 Err(_) => (None, None),
1753 }
1754 };
1755
1756 if let (Some(logging_id), Some(cluster_id), Some(cluster_name)) =
1759 (logging.id(), target_cluster_id, target_cluster_name.clone())
1760 {
1761 self.peek_client
1762 .log_set_cluster(logging_id, cluster_id, cluster_name);
1763 }
1764
1765 {
1766 let session = self.session.as_ref().expect("SessionClient invariant");
1767 let conn_catalog = catalog.for_session(session);
1768 if let Some(cluster_name) = &target_cluster_name {
1769 crate::coord::catalog_serving::check_cluster_restrictions(
1770 cluster_name,
1771 &conn_catalog,
1772 &plan,
1773 )?;
1774 }
1775 if let Err(e) = mz_sql::rbac::check_plan(
1776 &conn_catalog,
1777 None,
1778 session,
1779 &plan,
1780 target_cluster_id,
1781 &resolved_ids,
1782 &sql_impl_ids,
1783 ) {
1784 return Err(e.into());
1785 }
1786 }
1787
1788 {
1792 let session = self.session.as_mut().expect("SessionClient invariant");
1793 if let Some((_, wait_future)) =
1794 crate::coord::appends::waiting_on_startup_appends(&catalog, session, &plan)
1795 {
1796 wait_future.await;
1797 }
1798 }
1799
1800 {
1812 let session = self.session.as_ref().expect("SessionClient invariant");
1813 if session.transaction().is_in_multi_statement_transaction()
1814 && !session.transaction().allows_writes()
1815 {
1816 return Err(AdapterError::ReadOnlyTransaction);
1817 }
1818 if session
1819 .vars()
1820 .transaction_isolation()
1821 .is_bounded_staleness()
1822 {
1823 return Err(AdapterError::BoundedStalenessReadOnly);
1824 }
1825 }
1826
1827 let rtw_plan = match plan {
1829 Plan::ReadThenWrite(rtw_plan) => rtw_plan,
1830 Plan::Insert(insert_plan) => {
1831 let has_read_deps = !insert_plan.values.depends_on().is_empty();
1851
1852 if !has_read_deps {
1853 let optimized_mir = if insert_plan.values.as_const().is_some() {
1854 let expr = insert_plan
1856 .values
1857 .clone()
1858 .lower(catalog.system_config(), None)?;
1859 mz_expr::OptimizedMirRelationExpr(expr)
1860 } else {
1861 let optimizer_config =
1866 optimize::OptimizerConfig::from(catalog.system_config());
1867 let session = self.session.as_ref().expect("SessionClient invariant");
1868 let prep = ExprPrepOneShot {
1869 logical_time: EvalTime::NotAvailable,
1870 session,
1871 catalog_state: catalog.state(),
1872 };
1873 let mut optimizer =
1874 optimize::view::Optimizer::new_with_prep(optimizer_config, None, prep);
1875 match optimizer.optimize(insert_plan.values.clone()) {
1876 Ok(expr) => expr,
1877 Err(OptimizerError::UncallableFunction {
1878 func: UnmaterializableFunc::MzNow,
1879 ..
1880 }) => {
1881 let expr = insert_plan
1884 .values
1885 .clone()
1886 .lower(catalog.system_config(), None)?;
1887 mz_expr::OptimizedMirRelationExpr(expr)
1888 }
1889 Err(e) => return Err(e.into()),
1890 }
1891 };
1892
1893 let inner_mir = optimized_mir.into_inner();
1894 if inner_mir.as_const().is_some() && insert_plan.returning.is_empty() {
1895 let session = self.session.as_mut().expect("SessionClient invariant");
1896 let result = Coordinator::insert_constant(
1897 &catalog,
1898 session,
1899 insert_plan.id,
1900 inner_mir,
1901 );
1902
1903 return Ok(Some(result?));
1904 }
1905 }
1906
1907 let desc_arity = match catalog.try_get_entry(&insert_plan.id) {
1908 Some(table) => {
1909 let desc = table
1910 .relation_desc_latest()
1911 .ok_or_else(|| AdapterError::Internal("table has no desc".into()))?;
1912 desc.arity()
1913 }
1914 None => {
1915 return Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1916 kind: mz_catalog::memory::error::ErrorKind::Sql(
1917 mz_sql::catalog::CatalogError::UnknownItem(
1918 insert_plan.id.to_string(),
1919 ),
1920 ),
1921 }));
1922 }
1923 };
1924
1925 let finishing = RowSetFinishing {
1926 order_by: vec![],
1927 limit: None,
1928 offset: 0,
1929 project: (0..desc_arity).collect(),
1930 };
1931
1932 ReadThenWritePlan {
1933 id: insert_plan.id,
1934 selection: insert_plan.values,
1935 finishing,
1936 assignments: BTreeMap::new(),
1937 kind: MutationKind::Insert,
1938 returning: insert_plan.returning,
1939 }
1940 }
1941 _ => {
1942 return Err(AdapterError::Internal(
1943 "unexpected plan type for mutation".into(),
1944 ));
1945 }
1946 };
1947
1948 {
1959 let session = self.session.as_ref().expect("SessionClient invariant");
1960 let in_transaction = session
1961 .transaction()
1962 .may_share_transaction_with_other_statements();
1963 let depends_on = rtw_plan.selection.depends_on();
1964 if in_transaction && !depends_on.is_empty() {
1965 if contains_mz_now(&rtw_plan) {
1972 return Err(AdapterError::Unsupported(
1973 "calls to mz_now in write statements",
1974 ));
1975 }
1976 validate_selection_dependencies(&catalog, &depends_on)?;
1977 return Err(prohibited_in_transaction(&stmt));
1978 }
1979 }
1980
1981 let session = self.session.as_mut().expect("SessionClient invariant");
1982 self.peek_client
1983 .frontend_read_then_write(
1984 session,
1985 rtw_plan,
1986 target_cluster,
1987 &catalog,
1988 logging.id(),
1989 attempt_state,
1990 )
1991 .await
1992 .map(Some)
1993 }
1994}
1995
1996fn is_read_then_write_statement(stmt: &Statement<Raw>) -> bool {
2002 matches!(
2003 stmt,
2004 Statement::Delete(_) | Statement::Update(_) | Statement::Insert(_)
2005 )
2006}
2007
2008fn prohibited_in_transaction(stmt: &Statement<Raw>) -> AdapterError {
2013 let op = if StatementKind::from(stmt).is_sensitive() {
2014 stmt.to_ast_string_redacted()
2015 } else {
2016 stmt.to_string()
2017 };
2018 AdapterError::OperationProhibitsTransaction(op)
2019}
2020
2021impl Drop for SessionClient {
2022 fn drop(&mut self) {
2023 if let Some(session) = self.session.take() {
2027 if let Some(inner) = &self.inner {
2030 inner.send(Command::Terminate {
2031 conn_id: session.conn_id().clone(),
2032 tx: None,
2033 })
2034 }
2035 }
2036 }
2037}
2038
2039pub fn redact_sql_for_logging(sql: &str) -> String {
2046 match mz_sql_parser::parser::parse_statements_with_limit(sql) {
2047 Ok(Ok(stmts)) => stmts
2048 .into_iter()
2049 .map(|stmt| stmt.ast.to_ast_string_redacted())
2050 .join("; "),
2051 Ok(Err(_)) => format!("<unparseable ({} bytes)>", sql.len()),
2052 Err(_) => format!("<too large ({} bytes)>", sql.len()),
2053 }
2054}
2055
2056#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
2057pub enum TimeoutType {
2058 IdleInTransactionSession(TransactionId),
2059}
2060
2061impl Display for TimeoutType {
2062 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2063 match self {
2064 TimeoutType::IdleInTransactionSession(txn_id) => {
2065 writeln!(f, "Idle in transaction session for transaction '{txn_id}'")
2066 }
2067 }
2068 }
2069}
2070
2071impl From<TimeoutType> for AdapterError {
2072 fn from(timeout: TimeoutType) -> Self {
2073 match timeout {
2074 TimeoutType::IdleInTransactionSession(_) => {
2075 AdapterError::IdleInTransactionSessionTimeout
2076 }
2077 }
2078 }
2079}
2080
2081struct Timeout {
2082 tx: mpsc::UnboundedSender<TimeoutType>,
2083 rx: mpsc::UnboundedReceiver<TimeoutType>,
2084 active_timeouts: BTreeMap<TimeoutType, AbortOnDropHandle<()>>,
2085}
2086
2087impl Timeout {
2088 fn new() -> Self {
2089 let (tx, rx) = mpsc::unbounded_channel();
2090 Timeout {
2091 tx,
2092 rx,
2093 active_timeouts: BTreeMap::new(),
2094 }
2095 }
2096
2097 async fn recv(&mut self) -> Option<TimeoutType> {
2106 self.rx.recv().await
2107 }
2108
2109 fn add_timeout(&mut self, timeout: TimeoutType, duration: Duration) {
2110 let tx = self.tx.clone();
2111 let timeout_key = timeout.clone();
2112 let handle = mz_ore::task::spawn(|| format!("{timeout_key}"), async move {
2113 tokio::time::sleep(duration).await;
2114 let _ = tx.send(timeout);
2115 })
2116 .abort_on_drop();
2117 self.active_timeouts.insert(timeout_key, handle);
2118 }
2119
2120 fn remove_timeout(&mut self, timeout: &TimeoutType) {
2121 self.active_timeouts.remove(timeout);
2122
2123 let mut timeouts = Vec::new();
2125 while let Ok(pending_timeout) = self.rx.try_recv() {
2126 if timeout != &pending_timeout {
2127 timeouts.push(pending_timeout);
2128 }
2129 }
2130 for pending_timeout in timeouts {
2131 self.tx.send(pending_timeout).expect("rx is in this struct");
2132 }
2133 }
2134}
2135
2136#[derive(Derivative)]
2140#[derivative(Debug)]
2141pub struct RecordFirstRowStream {
2142 #[derivative(Debug = "ignore")]
2144 pub rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2145 pub execute_started: Instant,
2147 pub time_to_first_row_seconds: Histogram,
2150 pub saw_rows: bool,
2152 pub recorded_first_row_instant: Option<Instant>,
2154 pub no_more_rows: bool,
2156 pub metric_recorded: bool,
2158}
2159
2160impl RecordFirstRowStream {
2161 pub fn new(
2163 rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2164 execute_started: Instant,
2165 client: &SessionClient,
2166 instance_id: Option<ComputeInstanceId>,
2167 strategy: Option<StatementExecutionStrategy>,
2168 ) -> Self {
2169 let histogram = Self::histogram(client, instance_id, strategy);
2170 Self {
2171 rows,
2172 execute_started,
2173 time_to_first_row_seconds: histogram,
2174 saw_rows: false,
2175 recorded_first_row_instant: None,
2176 no_more_rows: false,
2177 metric_recorded: false,
2178 }
2179 }
2180
2181 fn histogram(
2182 client: &SessionClient,
2183 instance_id: Option<ComputeInstanceId>,
2184 strategy: Option<StatementExecutionStrategy>,
2185 ) -> Histogram {
2186 let session = client.session.as_ref().expect("session invariant");
2187 let isolation_level = *session.vars().transaction_isolation();
2188 let name_hint = ApplicationNameHint::from_str(session.application_name());
2189 let instance = match instance_id {
2190 Some(i) => Cow::Owned(i.to_string()),
2191 None => Cow::Borrowed("none"),
2192 };
2193 let strategy = match strategy {
2194 Some(s) => s.name(),
2195 None => "none",
2196 };
2197
2198 client
2199 .inner()
2200 .metrics()
2201 .time_to_first_row_seconds
2202 .with_label_values(&[
2203 instance.as_ref(),
2204 isolation_level.as_variant_str(),
2205 strategy,
2206 name_hint.as_str(),
2207 ])
2208 }
2209
2210 pub fn record(
2213 execute_started: Instant,
2214 client: &SessionClient,
2215 instance_id: Option<ComputeInstanceId>,
2216 strategy: Option<StatementExecutionStrategy>,
2217 ) {
2218 Self::histogram(client, instance_id, strategy)
2219 .observe(execute_started.elapsed().as_secs_f64());
2220 }
2221
2222 pub async fn recv(&mut self) -> Option<PeekResponseUnary> {
2223 let msg = self.rows.next().await;
2224 if !self.saw_rows && matches!(msg, Some(PeekResponseUnary::Rows(_))) {
2225 self.saw_rows = true;
2226 self.time_to_first_row_seconds
2227 .observe(self.execute_started.elapsed().as_secs_f64());
2228 self.recorded_first_row_instant = Some(Instant::now());
2229 }
2230 if msg.is_none() {
2231 self.no_more_rows = true;
2232 }
2233 msg
2234 }
2235}