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 FrontendWriteAttemptState, FrontendWriteCancellation, contains_mz_now,
68 validate_selection_dependencies,
69};
70use crate::metrics::Metrics;
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::{AdapterNotice, AppendWebhookError, PeekClient, PeekResponseUnary, StartupResponse};
82
83pub struct Handle {
89 pub(crate) session_id: Uuid,
90 pub(crate) start_instant: Instant,
91 pub(crate) _thread: JoinOnDropHandle<()>,
92}
93
94impl Handle {
95 pub fn session_id(&self) -> Uuid {
101 self.session_id
102 }
103
104 pub fn start_instant(&self) -> Instant {
106 self.start_instant
107 }
108}
109
110#[derive(Debug, Clone)]
118pub struct Client {
119 build_info: &'static BuildInfo,
120 inner_cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
121 id_alloc: IdAllocator<IdAllocatorInnerBitSet>,
122 now: NowFn,
123 metrics: Metrics,
124 environment_id: EnvironmentId,
125 segment_client: Option<mz_segment::Client>,
126}
127
128impl Client {
129 pub(crate) fn new(
130 build_info: &'static BuildInfo,
131 cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
132 metrics: Metrics,
133 now: NowFn,
134 environment_id: EnvironmentId,
135 segment_client: Option<mz_segment::Client>,
136 ) -> Client {
137 let env_lower = org_id_conn_bits(&environment_id.organization_id());
145 Client {
146 build_info,
147 inner_cmd_tx: cmd_tx,
148 id_alloc: IdAllocator::new(1, MAX_ORG_ID, env_lower),
149 now,
150 metrics,
151 environment_id,
152 segment_client,
153 }
154 }
155
156 pub fn new_conn_id(&self) -> Result<ConnectionId, AdapterError> {
158 self.id_alloc.alloc().ok_or(AdapterError::IdExhaustionError)
159 }
160
161 pub fn new_session(&self, config: SessionConfig, _authenticated: Authenticated) -> Session {
167 Session::new(self.build_info, config, self.metrics().session_metrics())
171 }
172
173 pub async fn authenticate(
177 &self,
178 user: &String,
179 password: &Password,
180 ) -> Result<Authenticated, AdapterError> {
181 let (tx, rx) = oneshot::channel();
182 self.send(Command::AuthenticatePassword {
183 role_name: user.to_string(),
184 password: Some(password.clone()),
185 tx,
186 });
187 rx.await.expect("sender dropped")?;
188 Ok(Authenticated)
189 }
190
191 pub async fn generate_sasl_challenge(
194 &self,
195 user: &String,
196 client_nonce: &String,
197 ) -> Result<SASLChallengeResponse, AdapterError> {
198 let (tx, rx) = oneshot::channel();
199 self.send(Command::AuthenticateGetSASLChallenge {
200 role_name: user.to_string(),
201 nonce: client_nonce.to_string(),
202 tx,
203 });
204 let response = rx.await.expect("sender dropped")?;
205 Ok(response)
206 }
207
208 pub async fn verify_sasl_proof(
211 &self,
212 user: &String,
213 proof: &String,
214 nonce: &String,
215 mock_hash: &String,
216 ) -> Result<(SASLVerifyProofResponse, Authenticated), AdapterError> {
217 let (tx, rx) = oneshot::channel();
218 self.send(Command::AuthenticateVerifySASLProof {
219 role_name: user.to_string(),
220 proof: proof.to_string(),
221 auth_message: nonce.to_string(),
222 mock_hash: mock_hash.to_string(),
223 tx,
224 });
225 let response = rx.await.expect("sender dropped")?;
226 Ok((response, Authenticated))
227 }
228
229 pub async fn role_can_login(&self, role_name: &str) -> Result<(), AdapterError> {
231 let (tx, rx) = oneshot::channel();
232 self.send(Command::CheckRoleCanLogin {
233 role_name: role_name.to_string(),
234 tx,
235 });
236 rx.await.expect("sender dropped")
237 }
238
239 #[mz_ore::instrument(level = "debug")]
248 pub async fn startup(&self, session: Session) -> Result<SessionClient, AdapterError> {
249 let user = session.user().clone();
250 let conn_id = session.conn_id().clone();
251 let secret_key = session.secret_key();
252 let uuid = session.uuid();
253 let client_ip = session.client_ip();
254 let application_name = session.application_name().into();
255 let notice_tx = session.retain_notice_transmitter();
256
257 let (tx, rx) = oneshot::channel();
258
259 let rx = rx.with_guard(|resp: Result<StartupResponse, _>| {
270 if resp.is_ok() {
271 self.send(Command::Terminate {
272 conn_id: conn_id.clone(),
273 tx: None,
274 });
275 }
276 });
277
278 self.send(Command::Startup {
279 tx,
280 user,
281 conn_id: conn_id.clone(),
282 secret_key,
283 uuid,
284 client_ip: client_ip.copied(),
285 application_name,
286 notice_tx,
287 });
288
289 let response = rx.await.expect("sender dropped")?;
292
293 let StartupResponse {
297 role_id,
298 write_notify,
299 session_defaults,
300 catalog,
301 storage_collections,
302 transient_id_gen,
303 optimizer_metrics,
304 persist_client,
305 statement_logging_frontend,
306 superuser_attribute,
307 occ_write_semaphore,
308 group_commit_notifier,
309 read_only,
310 } = response;
311
312 let peek_client = PeekClient::new(
313 CoordinatorClient::Session(self.clone()),
314 &catalog,
315 storage_collections,
316 transient_id_gen,
317 optimizer_metrics,
318 persist_client,
319 statement_logging_frontend,
320 occ_write_semaphore,
321 group_commit_notifier,
322 read_only,
323 );
324
325 let mut client = SessionClient {
326 inner: Some(self.clone()),
327 session: Some(session),
328 timeouts: Timeout::new(),
329 environment_id: self.environment_id.clone(),
330 segment_client: self.segment_client.clone(),
331 peek_client,
332 };
333
334 let session = client.session();
335
336 if let SuperuserAttribute(Some(superuser)) = superuser_attribute {
339 session.apply_internal_user_metadata(InternalUserMetadata { superuser });
340 }
341
342 session.initialize_role_metadata(role_id);
343 let vars_mut = session.vars_mut();
344 for (name, val) in session_defaults {
345 if let Err(err) = vars_mut.set_default(&name, val.borrow()) {
346 tracing::error!("failed to set peristed default, {err:?}");
349 }
350 }
351 session
352 .vars_mut()
353 .end_transaction(EndTransactionAction::Commit);
354
355 session.set_builtin_table_updates(write_notify);
363
364 let catalog = catalog.for_session(session);
365
366 let cluster_active = session.vars().cluster().to_string();
367 if session.vars().welcome_message() {
368 let cluster_info = if catalog.resolve_cluster(Some(&cluster_active)).is_err() {
369 format!("{cluster_active} (does not exist)")
370 } else {
371 cluster_active.to_string()
372 };
373
374 session.add_notice(AdapterNotice::Welcome(format!(
378 "connected to Materialize v{}
379 Environment ID: {}
380 Region: {}
381 User: {}
382 Cluster: {}
383 Database: {}
384 {}
385 Session UUID: {}
386
387Issue a SQL query to get started. Need help?
388 View documentation: https://materialize.com/s/docs
389 Join our Slack community: https://materialize.com/s/chat
390 ",
391 session.vars().build_info().semver_version(),
392 self.environment_id,
393 self.environment_id.region(),
394 session.vars().user().name,
395 cluster_info,
396 session.vars().database(),
397 match session.vars().search_path() {
398 [schema] => format!("Schema: {}", schema),
399 schemas => format!(
400 "Search path: {}",
401 schemas.iter().map(|id| id.to_string()).join(", ")
402 ),
403 },
404 session.uuid(),
405 )));
406 }
407
408 if session.vars().current_object_missing_warnings() {
409 if catalog.active_database().is_none() {
410 let db = session.vars().database().into();
411 session.add_notice(AdapterNotice::UnknownSessionDatabase(db));
412 }
413 }
414
415 let cluster_var = session
418 .vars()
419 .inspect(CLUSTER.name())
420 .expect("cluster should exist");
421 if session.vars().current_object_missing_warnings()
422 && catalog.resolve_cluster(Some(&cluster_active)).is_err()
423 {
424 let cluster_notice = 'notice: {
425 if cluster_var.inspect_session_value().is_some() {
426 break 'notice Some(AdapterNotice::DefaultClusterDoesNotExist {
427 name: cluster_active,
428 kind: "session",
429 suggested_action: "Pick an extant cluster with SET CLUSTER = name. Run SHOW CLUSTERS to see available clusters.".into(),
430 });
431 }
432
433 let role_default = catalog.get_role(catalog.active_role_id());
434 let role_cluster = match role_default.vars().get(CLUSTER.name()) {
435 Some(OwnedVarInput::Flat(name)) => Some(name),
436 None => None,
437 Some(v @ OwnedVarInput::SqlSet(_)) => {
439 tracing::warn!(?v, "SqlSet found for cluster Role Default");
440 break 'notice None;
441 }
442 };
443
444 let alter_role = "with `ALTER ROLE <role> SET cluster TO <cluster>;`";
445 match role_cluster {
446 None => Some(AdapterNotice::DefaultClusterDoesNotExist {
448 name: cluster_active,
449 kind: "system",
450 suggested_action: format!(
451 "Set a default cluster for the current role {alter_role}."
452 ),
453 }),
454 Some(_) => Some(AdapterNotice::DefaultClusterDoesNotExist {
456 name: cluster_active,
457 kind: "role",
458 suggested_action: format!(
459 "Change the default cluster for the current role {alter_role}."
460 ),
461 }),
462 }
463 };
464
465 if let Some(notice) = cluster_notice {
466 session.add_notice(notice);
467 }
468 }
469
470 Ok(client)
471 }
472
473 pub fn cancel_request(&self, conn_id: ConnectionIdType, secret_key: u32) {
475 self.send(Command::CancelRequest {
476 conn_id,
477 secret_key,
478 });
479 }
480
481 pub async fn support_execute_one(
484 &self,
485 sql: &str,
486 ) -> Result<Pin<Box<dyn Stream<Item = PeekResponseUnary> + Send>>, anyhow::Error> {
487 let conn_id = self.new_conn_id()?;
489 let session = self.new_session(
490 SessionConfig {
491 conn_id,
492 uuid: Uuid::new_v4(),
493 user: SUPPORT_USER.name.clone(),
494 client_ip: None,
495 external_metadata_rx: None,
496 helm_chart_version: None,
497 authenticator_kind: AuthenticatorKind::None,
498 groups: None,
499 },
500 Authenticated,
501 );
502 let mut session_client = self.startup(session).await?;
503
504 let stmts = mz_sql::parse::parse(sql)?;
506 if stmts.len() != 1 {
507 bail!("must supply exactly one query");
508 }
509 let StatementParseResult { ast: stmt, sql } = stmts.into_element();
510
511 const EMPTY_PORTAL: &str = "";
512 session_client.start_transaction(Some(1))?;
513 session_client
514 .declare(EMPTY_PORTAL.into(), stmt, sql.to_string())
515 .await?;
516
517 let execute_result = session_client
518 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
519 .await?;
520 match execute_result {
521 (ExecuteResponse::SendingRowsStreaming { mut rows, .. }, _) => {
522 let owning_response_stream = async_stream::stream! {
527 while let Some(rows) = rows.next().await {
528 yield rows;
529 }
530 drop(session_client);
531 };
532 Ok(Box::pin(owning_response_stream))
533 }
534 r => bail!("unsupported response type: {r:?}"),
535 }
536 }
537
538 pub fn metrics(&self) -> &Metrics {
540 &self.metrics
541 }
542
543 pub fn now(&self) -> DateTime<Utc> {
545 to_datetime((self.now)())
546 }
547
548 pub async fn get_webhook_appender(
550 &self,
551 database: String,
552 schema: String,
553 name: String,
554 ) -> Result<AppendWebhookResponse, AppendWebhookError> {
555 let (tx, rx) = oneshot::channel();
556
557 self.send(Command::GetWebhook {
559 database,
560 schema,
561 name,
562 tx,
563 });
564
565 let response = rx
567 .await
568 .map_err(|_| anyhow::anyhow!("failed to receive webhook response"))?;
569
570 response
571 }
572
573 pub async fn get_system_vars(&self) -> SystemVars {
575 let (tx, rx) = oneshot::channel();
576 self.send(Command::GetSystemVars { tx });
577 rx.await.expect("coordinator unexpectedly gone")
578 }
579
580 pub async fn catalog_snapshot_expensive(&self) -> Arc<Catalog> {
586 let (tx, rx) = oneshot::channel();
587 self.send(Command::CatalogSnapshot { tx });
588 let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone");
589 catalog
590 }
591
592 pub async fn update_scoped_system_parameters(
601 &self,
602 overrides: ScopedParameters,
603 prune_scope: ScopedParametersScope,
604 ) {
605 let (tx, rx) = oneshot::channel();
606 self.send(Command::UpdateScopedSystemParameters {
607 overrides,
608 prune_scope,
609 tx,
610 });
611 let _ = rx.await;
612 }
613
614 pub fn install_scoped_system_parameter_frontend(&self, frontend: Arc<SystemParameterFrontend>) {
619 self.send(Command::InstallScopedSystemParameterFrontend { frontend });
620 }
621
622 #[instrument(level = "debug")]
623 pub(crate) fn try_send(&self, cmd: Command) -> bool {
624 self.inner_cmd_tx
625 .send((OpenTelemetryContext::obtain(), cmd))
626 .is_ok()
627 }
628
629 #[instrument(level = "debug")]
630 pub(crate) fn send(&self, cmd: Command) {
631 assert!(self.try_send(cmd), "coordinator unexpectedly gone");
632 }
633}
634
635pub struct SessionClient {
639 inner: Option<Client>,
643 session: Option<Session>,
646 timeouts: Timeout,
647 segment_client: Option<mz_segment::Client>,
648 environment_id: EnvironmentId,
649 peek_client: PeekClient,
651}
652
653impl SessionClient {
654 pub fn parse<'a>(
657 &self,
658 sql: &'a str,
659 ) -> Result<Result<Vec<StatementParseResult<'a>>, ParserStatementError>, String> {
660 match mz_sql::parse::parse_with_limit(sql) {
661 Ok(Err(e)) => {
662 self.track_statement_parse_failure(&e);
663 Ok(Err(e))
664 }
665 r => r,
666 }
667 }
668
669 fn track_statement_parse_failure(&self, parse_error: &ParserStatementError) {
670 let session = self.session.as_ref().expect("session invariant violated");
671 let Some(user_id) = session.user().external_metadata.as_ref().map(|m| m.user_id) else {
672 return;
673 };
674 let Some(segment_client) = &self.segment_client else {
675 return;
676 };
677 let Some(statement_kind) = parse_error.statement else {
678 return;
679 };
680 let Some((action, object_type)) = telemetry::analyze_audited_statement(statement_kind)
681 else {
682 return;
683 };
684 let event_type = StatementFailureType::ParseFailure;
685 let event_name = format!(
686 "{} {} {}",
687 object_type.as_title_case(),
688 action.as_title_case(),
689 event_type.as_title_case(),
690 );
691 segment_client.environment_track(
692 &self.environment_id,
693 event_name,
694 json!({
695 "statement_kind": statement_kind,
696 "error": &parse_error.error,
697 }),
698 EventDetails {
699 user_id: Some(user_id),
700 application_name: Some(session.application_name()),
701 ..Default::default()
702 },
703 );
704 }
705
706 pub async fn get_prepared_statement(
709 &mut self,
710 name: &str,
711 ) -> Result<&PreparedStatement, AdapterError> {
712 let catalog = self.catalog_snapshot("get_prepared_statement").await;
713 Coordinator::verify_prepared_statement(&catalog, self.session(), name)?;
714 Ok(self
715 .session()
716 .get_prepared_statement_unverified(name)
717 .expect("must exist"))
718 }
719
720 pub async fn prepare(
725 &mut self,
726 name: String,
727 stmt: Option<Statement<Raw>>,
728 sql: String,
729 param_types: Vec<Option<SqlScalarType>>,
730 ) -> Result<(), AdapterError> {
731 let catalog = self.catalog_snapshot("prepare").await;
732
733 let mut async_pause = false;
736 (|| {
737 fail::fail_point!("async_prepare", |val| {
738 async_pause = val.map_or(false, |val| val.parse().unwrap_or(false))
739 });
740 })();
741 if async_pause {
742 tokio::time::sleep(Duration::from_secs(1)).await;
743 };
744
745 let desc = Coordinator::describe(&catalog, self.session(), stmt.clone(), param_types)?;
746 let now = self.now();
747 let state_revision = StateRevision {
748 catalog_revision: catalog.transient_revision(),
749 session_state_revision: self.session().state_revision(),
750 };
751 self.session()
752 .set_prepared_statement(name, stmt, sql, desc, state_revision, now);
753 Ok(())
754 }
755
756 #[mz_ore::instrument(level = "debug")]
758 pub async fn declare(
759 &mut self,
760 name: String,
761 stmt: Statement<Raw>,
762 sql: String,
763 ) -> Result<(), AdapterError> {
764 let catalog = self.catalog_snapshot("declare").await;
765 let param_types = vec![];
766 let desc =
767 Coordinator::describe(&catalog, self.session(), Some(stmt.clone()), param_types)?;
768 let params = vec![];
769 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
770 let now = self.now();
771 let logging = self.session().mint_logging(sql, Some(&stmt), now);
772 let state_revision = StateRevision {
773 catalog_revision: catalog.transient_revision(),
774 session_state_revision: self.session().state_revision(),
775 };
776 self.session().set_portal(
777 name,
778 desc,
779 Some(stmt),
780 logging,
781 params,
782 result_formats,
783 state_revision,
784 )?;
785 Ok(())
786 }
787
788 #[mz_ore::instrument(level = "debug")]
795 pub async fn execute(
796 &mut self,
797 portal_name: String,
798 cancel_future: impl Future<Output = std::io::Error> + Send,
799 outer_ctx_extra: Option<ExecuteContextGuard>,
800 ) -> Result<(ExecuteResponse, Instant), AdapterError> {
801 let execute_started = Instant::now();
802 let cancel_future = cancel_future.map(|_| ()).shared();
803
804 let mut logging = ExecutionLogging::adopt(outer_ctx_extra, &self.peek_client);
808
809 let result = self
810 .execute_attempts(portal_name, &mut logging, cancel_future)
811 .await;
812
813 logging.retire(&result);
814
815 result.map(|response| (response, execute_started))
816 }
817
818 async fn execute_attempts(
822 &mut self,
823 portal_name: String,
824 logging: &mut ExecutionLogging,
825 cancel_future: impl Future<Output = ()> + Send + Clone,
826 ) -> Result<ExecuteResponse, AdapterError> {
827 let portal_name = self.unroll_sql_execute(portal_name, logging).await?;
840
841 let peek_result = self.try_frontend_peek(&portal_name, logging).await?;
845 if let Some(resp) = peek_result {
846 debug!("frontend peek succeeded");
847 return Ok(resp);
848 }
849 debug!("frontend peek did not happen, trying frontend read-then-write");
850
851 let rtw_result = self
853 .try_frontend_read_then_write_with_cancel(&portal_name, logging, cancel_future.clone())
854 .await?;
855 if let Some(resp) = rtw_result {
856 debug!("frontend read-then-write succeeded");
857 return Ok(resp);
858 }
859 debug!("frontend read-then-write did not happen, falling back to `Command::Execute`");
860
861 let outer_ctx_extra = logging.release();
864 self.send_with_cancel(
865 |tx, session| Command::Execute {
866 portal_name,
867 session,
868 tx,
869 outer_ctx_extra,
870 },
871 cancel_future,
872 )
873 .await
874 }
875
876 async fn unroll_sql_execute(
890 &mut self,
891 portal_name: String,
892 logging: &mut ExecutionLogging,
893 ) -> Result<String, AdapterError> {
894 let (stmt, params, outer_logging, outer_lifecycle_timestamps) = {
895 let session = self.session.as_ref().expect("SessionClient invariant");
896 let portal = match session.get_portal_unverified(&portal_name) {
897 Some(p) => p,
898 None => return Ok(portal_name),
902 };
903 match &portal.stmt {
904 Some(stmt) => (
905 Arc::clone(stmt),
906 portal.parameters.clone(),
907 Arc::clone(&portal.logging),
908 portal.lifecycle_timestamps.clone(),
909 ),
910 None => return Ok(portal_name),
911 }
912 };
913
914 if !matches!(&*stmt, Statement::Execute(_)) {
917 return Ok(portal_name);
918 }
919
920 let catalog = self.catalog_snapshot("unroll_sql_execute").await;
921
922 {
926 let session = self.session.as_mut().expect("SessionClient invariant");
927 Coordinator::verify_portal(&catalog, session, &portal_name)?;
928 }
929
930 logging.take_over(
938 &self.peek_client,
939 self.session.as_mut().expect("SessionClient invariant"),
940 Some(&stmt),
941 ¶ms,
942 &outer_logging,
943 &catalog,
944 outer_lifecycle_timestamps,
945 TakeOver::UnrolledExecute,
946 );
947
948 self.install_inner_portal_for_execute(&catalog, &stmt, ¶ms)
949 }
950
951 fn install_inner_portal_for_execute(
956 &mut self,
957 catalog: &Arc<Catalog>,
958 stmt: &Arc<Statement<Raw>>,
959 params: &mz_sql::plan::Params,
960 ) -> Result<String, AdapterError> {
961 use mz_sql::plan::Plan;
962
963 let execute_plan = {
964 let session = self.session.as_mut().expect("SessionClient invariant");
965 let conn_catalog = catalog.for_session(session);
966 let (resolved_stmt, resolved_ids) =
967 mz_sql::names::resolve(&conn_catalog, (**stmt).clone())?;
968 let pcx = session.pcx();
969 let (plan, _sql_impl_ids) = mz_sql::plan::plan(
970 Some(pcx),
971 &conn_catalog,
972 resolved_stmt,
973 params,
974 &resolved_ids,
975 )?;
976 match plan {
977 Plan::Execute(plan) => plan,
978 other => {
979 return Err(AdapterError::Internal(format!(
983 "planning Statement::Execute yielded unexpected plan: {:?}",
984 mz_sql::plan::PlanKind::from(&other),
985 )));
986 }
987 }
988 };
989
990 let session = self.session.as_mut().expect("SessionClient invariant");
996 Coordinator::verify_prepared_statement(catalog, session, &execute_plan.name)?;
997 let ps = session
998 .get_prepared_statement_unverified(&execute_plan.name)
999 .expect("verified above");
1000 let inner_stmt = ps.stmt().cloned();
1001 let inner_desc = ps.desc().clone();
1002 let state_revision = ps.state_revision;
1003 let inner_logging = Arc::clone(ps.logging());
1004
1005 if let Some(inner) = inner_stmt.as_ref() {
1010 if matches!(inner, Statement::Execute(_)) {
1011 return Err(AdapterError::Internal(format!(
1012 "nested EXECUTE: prepared statement {} resolves to another EXECUTE; \
1013 parser should reject `PREPARE ... AS EXECUTE ...`",
1014 execute_plan.name.quoted(),
1015 )));
1016 }
1017 }
1018
1019 session.create_new_portal(
1020 inner_stmt,
1021 inner_logging,
1022 inner_desc,
1023 execute_plan.params,
1024 Vec::new(),
1025 state_revision,
1026 )
1027 }
1028
1029 fn now(&self) -> EpochMillis {
1030 (self.inner().now)()
1031 }
1032
1033 fn now_datetime(&self) -> DateTime<Utc> {
1034 to_datetime(self.now())
1035 }
1036
1037 pub fn start_transaction(&mut self, implicit: Option<usize>) -> Result<(), AdapterError> {
1043 let now = self.now_datetime();
1044 let session = self.session.as_mut().expect("session invariant violated");
1045 let result = match implicit {
1046 None => session.start_transaction(now, None, None),
1047 Some(stmts) => {
1048 session.start_transaction_implicit(now, stmts);
1049 Ok(())
1050 }
1051 };
1052 result
1053 }
1054
1055 #[instrument(level = "debug")]
1058 pub async fn end_transaction(
1059 &mut self,
1060 action: EndTransactionAction,
1061 ) -> Result<ExecuteResponse, AdapterError> {
1062 let res = self
1063 .send(|tx, session| Command::Commit {
1064 action,
1065 session,
1066 tx,
1067 })
1068 .await;
1069 let _ = self.session().clear_transaction();
1073 res
1074 }
1075
1076 pub fn fail_transaction(&mut self) {
1078 let session = self.session.take().expect("session invariant violated");
1079 let session = session.fail_transaction();
1080 self.session = Some(session);
1081 }
1082
1083 #[instrument(level = "debug")]
1087 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
1088 self.peek_client.catalog_snapshot(context).await
1089 }
1090
1091 pub async fn statement_arrival_logging_enabled(&mut self) -> bool {
1093 let catalog = self.catalog_snapshot("statement_arrival_logging").await;
1094 catalog.system_config().enable_statement_arrival_logging()
1095 }
1096
1097 pub async fn extended_protocol_implicit_transaction_enabled(&mut self) -> bool {
1099 let catalog = self
1100 .catalog_snapshot("extended_protocol_implicit_transaction")
1101 .await;
1102 catalog
1103 .system_config()
1104 .enable_extended_protocol_implicit_transaction()
1105 }
1106
1107 pub async fn dump_catalog(&mut self) -> Result<CatalogDump, AdapterError> {
1112 let catalog = self.catalog_snapshot("dump_catalog").await;
1113 catalog.dump().map_err(AdapterError::from)
1114 }
1115
1116 pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> {
1122 let catalog = self.catalog_snapshot("check_catalog").await;
1123 catalog.check_consistency()
1124 }
1125
1126 pub async fn check_coordinator(&self) -> Result<(), serde_json::Value> {
1132 self.send_without_session(|tx| Command::CheckConsistency { tx })
1133 .await
1134 .map_err(|inconsistencies| {
1135 serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1136 serde_json::Value::String("failed to serialize inconsistencies".to_string())
1137 })
1138 })
1139 }
1140
1141 pub async fn dump_coordinator_state(&self) -> Result<serde_json::Value, anyhow::Error> {
1142 self.send_without_session(|tx| Command::Dump { tx }).await
1143 }
1144
1145 pub fn retire_execute(
1148 &self,
1149 guard: ExecuteContextGuard,
1150 reason: StatementEndedExecutionReason,
1151 ) {
1152 if !guard.is_trivial() {
1153 let data = guard.defuse();
1154 let cmd = Command::RetireExecute { data, reason };
1155 self.inner().send(cmd);
1156 }
1157 }
1158
1159 pub async fn start_copy_from_stdin(
1165 &mut self,
1166 target_id: CatalogItemId,
1167 target_name: String,
1168 columns: Vec<ColumnIndex>,
1169 row_desc: mz_repr::RelationDesc,
1170 params: mz_pgcopy::CopyFormatParams<'static>,
1171 ) -> Result<CopyFromStdinWriter, AdapterError> {
1172 self.send(|tx, session| Command::StartCopyFromStdin {
1173 target_id,
1174 target_name,
1175 columns,
1176 row_desc,
1177 params,
1178 session,
1179 tx,
1180 })
1181 .await
1182 }
1183
1184 pub fn stage_copy_from_stdin_batches(
1189 &mut self,
1190 target_id: CatalogItemId,
1191 batches: Vec<mz_persist_client::batch::ProtoBatch>,
1192 ) -> Result<(), AdapterError> {
1193 use crate::session::{TransactionOps, WriteOp};
1194 use mz_storage_client::client::TableData;
1195
1196 self.session()
1197 .add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
1198 id: target_id,
1199 rows: TableData::Batches(batches.into()),
1200 }]))?;
1201 Ok(())
1202 }
1203
1204 pub async fn get_system_vars(&self) -> SystemVars {
1206 self.inner().get_system_vars().await
1207 }
1208
1209 pub async fn set_system_vars(
1211 &mut self,
1212 vars: BTreeMap<String, String>,
1213 ) -> Result<(), AdapterError> {
1214 let conn_id = self.session().conn_id().clone();
1215 self.send_without_session(|tx| Command::SetSystemVars { vars, conn_id, tx })
1216 .await
1217 }
1218
1219 pub async fn inject_audit_events(
1224 &mut self,
1225 events: Vec<crate::catalog::InjectedAuditEvent>,
1226 ) -> Result<(), AdapterError> {
1227 let conn_id = self.session().conn_id().clone();
1228 self.send_without_session(|tx| Command::InjectAuditEvents {
1229 events,
1230 conn_id,
1231 tx,
1232 })
1233 .await
1234 }
1235
1236 pub async fn terminate(&mut self) {
1238 let conn_id = self.session().conn_id().clone();
1239 let res = self
1240 .send_without_session(|tx| Command::Terminate {
1241 conn_id,
1242 tx: Some(tx),
1243 })
1244 .await;
1245 if let Err(e) = res {
1246 error!("Unable to terminate session: {e:?}");
1248 }
1249 self.inner = None;
1251 }
1252
1253 pub fn session(&mut self) -> &mut Session {
1255 self.session.as_mut().expect("session invariant violated")
1256 }
1257
1258 pub fn inner(&self) -> &Client {
1260 self.inner.as_ref().expect("inner invariant violated")
1261 }
1262
1263 async fn send_without_session<T, F>(&self, f: F) -> T
1264 where
1265 F: FnOnce(oneshot::Sender<T>) -> Command,
1266 {
1267 let (tx, rx) = oneshot::channel();
1268 self.inner().send(f(tx));
1269 rx.await.expect("sender dropped")
1270 }
1271
1272 #[instrument(level = "debug")]
1273 async fn send<T, F>(&mut self, f: F) -> Result<T, AdapterError>
1274 where
1275 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1276 {
1277 self.send_with_cancel(f, futures::future::pending()).await
1278 }
1279
1280 #[instrument(level = "debug")]
1284 async fn send_with_cancel<T, F>(
1285 &mut self,
1286 f: F,
1287 cancel_future: impl Future<Output = ()> + Send,
1288 ) -> Result<T, AdapterError>
1289 where
1290 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1291 {
1292 let session = self.session.take().expect("session invariant violated");
1293 let mut typ = None;
1294 let application_name = session.application_name();
1295 let name_hint = ApplicationNameHint::from_str(application_name);
1296 let conn_id = session.conn_id().clone();
1297 let (tx, rx) = oneshot::channel();
1298
1299 let Self {
1302 inner: inner_client,
1303 session: client_session,
1304 ..
1305 } = self;
1306
1307 let inner_client = inner_client.as_ref().expect("inner invariant violated");
1310
1311 let mut guarded_rx = rx.with_guard(|response: Response<_>| {
1317 *client_session = Some(response.session);
1318 });
1319
1320 inner_client.send({
1321 let cmd = f(tx, session);
1322 match cmd {
1326 Command::Execute { .. } => typ = Some("execute"),
1327 Command::GetWebhook { .. } => typ = Some("webhook"),
1328 Command::StartCopyFromStdin { .. }
1329 | Command::Startup { .. }
1330 | Command::AuthenticatePassword { .. }
1331 | Command::AuthenticateGetSASLChallenge { .. }
1332 | Command::AuthenticateVerifySASLProof { .. }
1333 | Command::CheckRoleCanLogin { .. }
1334 | Command::CatalogSnapshot { .. }
1335 | Command::Commit { .. }
1336 | Command::CancelRequest { .. }
1337 | Command::PrivilegedCancelRequest { .. }
1338 | Command::GetSystemVars { .. }
1339 | Command::SetSystemVars { .. }
1340 | Command::UpdateScopedSystemParameters { .. }
1341 | Command::InstallScopedSystemParameterFrontend { .. }
1342 | Command::Terminate { .. }
1343 | Command::RetireExecute { .. }
1344 | Command::CheckConsistency { .. }
1345 | Command::Dump { .. }
1346 | Command::GetComputeInstanceClient { .. }
1347 | Command::GetOracle { .. }
1348 | Command::DetermineRealTimeRecentTimestamp { .. }
1349 | Command::GetTransactionReadHoldsBundle { .. }
1350 | Command::StoreTransactionReadHolds { .. }
1351 | Command::ExecuteSlowPathPeek { .. }
1352 | Command::ExecuteSubscribe { .. }
1353 | Command::CopyToPreflight { .. }
1354 | Command::ExecuteCopyTo { .. }
1355 | Command::ExecuteSideEffectingFunc { .. }
1356 | Command::LookupConnection { .. }
1357 | Command::RegisterFrontendPeek { .. }
1358 | Command::UnregisterFrontendPeek { .. }
1359 | Command::ExplainTimestamp { .. }
1360 | Command::FrontendStatementLogging(..)
1361 | Command::InjectAuditEvents { .. }
1362 | Command::RegisterConnectionCancelWatch { .. }
1363 | Command::CreateInternalSubscribe { .. }
1364 | Command::AttemptWrite { .. }
1365 | Command::DropInternalSubscribe { .. } => {}
1366 };
1367 cmd
1368 });
1369
1370 let mut cancel_future = pin::pin!(cancel_future);
1371 let mut cancelled = false;
1372 loop {
1373 tokio::select! {
1374 res = &mut guarded_rx => {
1375 drop(guarded_rx);
1377
1378 let res = res.expect("sender dropped");
1379 let status = res.result.is_ok().then_some("success").unwrap_or("error");
1380 if let Err(err) = res.result.as_ref() {
1381 if name_hint.should_trace_errors() {
1382 tracing::warn!(?err, ?name_hint, "adapter response error");
1383 }
1384 }
1385
1386 if let Some(typ) = typ {
1387 inner_client
1388 .metrics
1389 .commands
1390 .with_label_values(&[typ, status, name_hint.as_str()])
1391 .inc();
1392 }
1393 *client_session = Some(res.session);
1394 return res.result;
1395 },
1396 _ = &mut cancel_future, if !cancelled => {
1397 cancelled = true;
1398 inner_client.send(Command::PrivilegedCancelRequest {
1399 conn_id: conn_id.clone(),
1400 });
1401 }
1402 };
1403 }
1404 }
1405
1406 pub fn add_idle_in_transaction_session_timeout(&mut self) {
1407 let session = self.session();
1408 let timeout_dur = session.vars().idle_in_transaction_session_timeout();
1409 if !timeout_dur.is_zero() {
1410 let timeout_dur = timeout_dur.clone();
1411 if let Some(txn) = session.transaction().inner() {
1412 let txn_id = txn.id.clone();
1413 let timeout = TimeoutType::IdleInTransactionSession(txn_id);
1414 self.timeouts.add_timeout(timeout, timeout_dur);
1415 }
1416 }
1417 }
1418
1419 pub fn remove_idle_in_transaction_session_timeout(&mut self) {
1420 let session = self.session();
1421 if let Some(txn) = session.transaction().inner() {
1422 let txn_id = txn.id.clone();
1423 self.timeouts
1424 .remove_timeout(&TimeoutType::IdleInTransactionSession(txn_id));
1425 }
1426 }
1427
1428 pub async fn recv_timeout(&mut self) -> Option<TimeoutType> {
1435 self.timeouts.recv().await
1436 }
1437
1438 pub(crate) async fn try_frontend_peek(
1444 &mut self,
1445 portal_name: &str,
1446 logging: &mut ExecutionLogging,
1447 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1448 let session = self.session.as_mut().expect("SessionClient invariant");
1449 self.peek_client
1450 .try_frontend_peek(portal_name, session, logging)
1451 .await
1452 }
1453
1454 fn frontend_read_then_write_applies(&self, portal_name: &str) -> bool {
1462 let session = self.session.as_ref().expect("SessionClient invariant");
1463 match session.get_portal_unverified(portal_name) {
1464 Some(portal) => portal
1465 .stmt
1466 .as_deref()
1467 .is_some_and(is_read_then_write_statement),
1468 None => false,
1469 }
1470 }
1471
1472 async fn try_frontend_read_then_write_with_cancel(
1483 &mut self,
1484 portal_name: &str,
1485 logging: &mut ExecutionLogging,
1486 cancel_future: impl Future<Output = ()> + Send,
1487 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1488 if !self.frontend_read_then_write_applies(portal_name) {
1493 return Ok(None);
1494 }
1495
1496 let conn_id = self.session().conn_id().clone();
1497 let statement_timeout = *self.session().vars().statement_timeout();
1498 let inner_client = self.inner().clone();
1499 let attempt_state = Arc::new(FrontendWriteAttemptState::new());
1500
1501 let mut cancel_future = pin::pin!(cancel_future);
1502 let statement_timeout = async move {
1503 if statement_timeout.is_zero() {
1504 futures::future::pending::<()>().await;
1505 } else {
1506 tokio::time::sleep(statement_timeout).await;
1507 }
1508 };
1509 tokio::pin!(statement_timeout);
1510
1511 let mut connection_cancel_rx = {
1517 let register =
1518 self.peek_client
1519 .call_coordinator(|tx| Command::RegisterConnectionCancelWatch {
1520 conn_id: conn_id.clone(),
1521 tx,
1522 });
1523 tokio::pin!(register);
1524 tokio::select! {
1525 rx = &mut register => rx?,
1526 _ = &mut cancel_future => {
1527 inner_client.try_send(Command::PrivilegedCancelRequest {
1528 conn_id: conn_id.clone(),
1529 });
1530 return Err(AdapterError::Canceled);
1531 }
1532 _ = &mut statement_timeout => {
1533 inner_client.try_send(Command::PrivilegedCancelRequest {
1534 conn_id: conn_id.clone(),
1535 });
1536 return Err(AdapterError::StatementTimeout);
1537 }
1538 }
1539 };
1540 if *connection_cancel_rx.borrow() {
1541 return Err(AdapterError::Canceled);
1542 }
1543 let connection_cancel = async move {
1544 if connection_cancel_rx.wait_for(|v| *v).await.is_err() {
1545 futures::future::pending::<()>().await;
1546 }
1547 };
1548 tokio::pin!(connection_cancel);
1549
1550 let frontend_read_then_write =
1551 self.try_frontend_read_then_write(portal_name, logging, Arc::clone(&attempt_state));
1552 tokio::pin!(frontend_read_then_write);
1553
1554 let requested = tokio::select! {
1555 response = &mut frontend_read_then_write => return response,
1556 _ = &mut cancel_future => FrontendWriteCancellation::Canceled,
1557 _ = &mut connection_cancel => FrontendWriteCancellation::Canceled,
1558 _ = &mut statement_timeout => FrontendWriteCancellation::StatementTimeout,
1559 };
1560
1561 attempt_state.request(requested);
1562 inner_client.try_send(Command::PrivilegedCancelRequest {
1563 conn_id: conn_id.clone(),
1564 });
1565
1566 if !attempt_state.write_submitted() {
1567 return Err(requested.into());
1568 }
1569
1570 frontend_read_then_write.await
1573 }
1574
1575 async fn try_frontend_read_then_write(
1582 &mut self,
1583 portal_name: &str,
1584 logging: &mut ExecutionLogging,
1585 attempt_state: Arc<FrontendWriteAttemptState>,
1586 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1587 let catalog = self.catalog_snapshot("try_frontend_read_then_write").await;
1588
1589 let stmt = {
1590 let session = self.session.as_ref().expect("SessionClient invariant");
1591 let portal = match session.get_portal_unverified(portal_name) {
1592 Some(portal) => portal,
1593 None => return Ok(None), };
1595 portal.stmt.clone()
1596 };
1597
1598 let stmt = match stmt {
1599 Some(stmt) if is_read_then_write_statement(&stmt) => stmt,
1600 Some(_stmt) => {
1601 return Ok(None);
1602 }
1603 None => {
1604 return Ok(None);
1605 }
1606 };
1607
1608 Coordinator::verify_portal(
1613 &catalog,
1614 self.session.as_mut().expect("SessionClient invariant"),
1615 portal_name,
1616 )?;
1617
1618 let (params, logging_info, lifecycle_timestamps) = {
1619 let portal = self
1620 .session
1621 .as_ref()
1622 .expect("SessionClient invariant")
1623 .get_portal_unverified(portal_name)
1624 .expect("verified above");
1625 (
1626 portal.parameters.clone(),
1627 Arc::clone(&portal.logging),
1628 portal.lifecycle_timestamps.clone(),
1629 )
1630 };
1631
1632 logging.take_over(
1636 &self.peek_client,
1637 self.session.as_mut().expect("SessionClient invariant"),
1638 Some(&stmt),
1639 ¶ms,
1640 &logging_info,
1641 &catalog,
1642 lifecycle_timestamps,
1643 TakeOver::StatementToRun,
1644 );
1645
1646 {
1658 let session = self.session.as_ref().expect("SessionClient invariant");
1659 let contains_ops = session.transaction().contains_ops();
1667 match session.transaction() {
1668 TransactionStatus::Default | TransactionStatus::Failed(_) => {}
1669 TransactionStatus::Started(_) if !contains_ops => {}
1670 TransactionStatus::Started(_)
1671 | TransactionStatus::InTransactionImplicit(_)
1672 | TransactionStatus::InTransaction(_) => {
1673 let constant_insert = matches!(
1674 &*stmt,
1675 Statement::Insert(InsertStatement {
1676 source, returning, ..
1677 }) if returning.is_empty() && ConstantVisitor::insert_source(source)
1678 );
1679 if !constant_insert {
1680 return Err(prohibited_in_transaction(&stmt));
1681 }
1682 }
1683 }
1684 }
1685
1686 let (plan, target_cluster, resolved_ids, sql_impl_ids) = {
1687 let session = self.session.as_mut().expect("SessionClient invariant");
1688 let conn_catalog = catalog.for_session(session);
1689 let (stmt, resolved_ids) = mz_sql::names::resolve(&conn_catalog, (*stmt).clone())?;
1690 let pcx = session.pcx();
1691 let (plan, sql_impl_ids) =
1692 mz_sql::plan::plan(Some(pcx), &conn_catalog, stmt, ¶ms, &resolved_ids)?;
1693
1694 let target_cluster = match session.transaction().cluster() {
1695 Some(cluster_id) => crate::coord::TargetCluster::Transaction(cluster_id),
1696 None => crate::coord::catalog_serving::auto_run_on_catalog_server(
1697 &conn_catalog,
1698 session,
1699 &plan,
1700 ),
1701 };
1702
1703 (plan, target_cluster, resolved_ids, sql_impl_ids)
1704 };
1705
1706 if self.peek_client.read_only {
1713 return Err(AdapterError::ReadOnly);
1714 }
1715
1716 let (target_cluster_id, target_cluster_name) = {
1722 let session = self.session.as_ref().expect("SessionClient invariant");
1723 match catalog.resolve_target_cluster(target_cluster.clone(), session) {
1724 Ok(cluster) => (Some(cluster.id), Some(cluster.name.clone())),
1725 Err(_) => (None, None),
1726 }
1727 };
1728
1729 if let (Some(logging_id), Some(cluster_id), Some(cluster_name)) =
1732 (logging.id(), target_cluster_id, target_cluster_name.clone())
1733 {
1734 self.peek_client
1735 .log_set_cluster(logging_id, cluster_id, cluster_name);
1736 }
1737
1738 {
1739 let session = self.session.as_ref().expect("SessionClient invariant");
1740 let conn_catalog = catalog.for_session(session);
1741 if let Some(cluster_name) = &target_cluster_name {
1742 crate::coord::catalog_serving::check_cluster_restrictions(
1743 cluster_name,
1744 &conn_catalog,
1745 &plan,
1746 )?;
1747 }
1748 if let Err(e) = mz_sql::rbac::check_plan(
1749 &conn_catalog,
1750 None,
1751 session,
1752 &plan,
1753 target_cluster_id,
1754 &resolved_ids,
1755 &sql_impl_ids,
1756 ) {
1757 return Err(e.into());
1758 }
1759 }
1760
1761 {
1765 let session = self.session.as_mut().expect("SessionClient invariant");
1766 if let Some((_, wait_future)) =
1767 crate::coord::appends::waiting_on_startup_appends(&catalog, session, &plan)
1768 {
1769 wait_future.await;
1770 }
1771 }
1772
1773 {
1778 let session = self.session.as_ref().expect("SessionClient invariant");
1779 if session.transaction().is_in_multi_statement_transaction()
1780 && !session.transaction().allows_writes()
1781 {
1782 return Err(AdapterError::ReadOnlyTransaction);
1783 }
1784 if session
1785 .vars()
1786 .transaction_isolation()
1787 .is_bounded_staleness()
1788 {
1789 return Err(AdapterError::BoundedStalenessReadOnly);
1790 }
1791 }
1792
1793 let rtw_plan = match plan {
1795 Plan::ReadThenWrite(rtw_plan) => rtw_plan,
1796 Plan::Insert(insert_plan) => {
1797 let has_read_deps = !insert_plan.values.depends_on().is_empty();
1817
1818 if !has_read_deps {
1819 let optimized_mir = if insert_plan.values.as_const().is_some() {
1820 let expr = insert_plan
1822 .values
1823 .clone()
1824 .lower(catalog.system_config(), None)?;
1825 mz_expr::OptimizedMirRelationExpr(expr)
1826 } else {
1827 let optimizer_config =
1832 optimize::OptimizerConfig::from(catalog.system_config());
1833 let session = self.session.as_ref().expect("SessionClient invariant");
1834 let prep = ExprPrepOneShot {
1835 logical_time: EvalTime::NotAvailable,
1836 session,
1837 catalog_state: catalog.state(),
1838 };
1839 let mut optimizer =
1840 optimize::view::Optimizer::new_with_prep(optimizer_config, None, prep);
1841 match optimizer.optimize(insert_plan.values.clone()) {
1842 Ok(expr) => expr,
1843 Err(OptimizerError::UncallableFunction {
1844 func: UnmaterializableFunc::MzNow,
1845 ..
1846 }) => {
1847 let expr = insert_plan
1850 .values
1851 .clone()
1852 .lower(catalog.system_config(), None)?;
1853 mz_expr::OptimizedMirRelationExpr(expr)
1854 }
1855 Err(e) => return Err(e.into()),
1856 }
1857 };
1858
1859 let inner_mir = optimized_mir.into_inner();
1860 if inner_mir.as_const().is_some() && insert_plan.returning.is_empty() {
1861 let session = self.session.as_mut().expect("SessionClient invariant");
1862 let result = Coordinator::insert_constant(
1863 &catalog,
1864 session,
1865 insert_plan.id,
1866 inner_mir,
1867 );
1868
1869 return Ok(Some(result?));
1870 }
1871 }
1872
1873 let desc_arity = match catalog.try_get_entry(&insert_plan.id) {
1874 Some(table) => {
1875 let desc = table
1876 .relation_desc_latest()
1877 .ok_or_else(|| AdapterError::Internal("table has no desc".into()))?;
1878 desc.arity()
1879 }
1880 None => {
1881 return Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
1882 kind: mz_catalog::memory::error::ErrorKind::Sql(
1883 mz_sql::catalog::CatalogError::UnknownItem(
1884 insert_plan.id.to_string(),
1885 ),
1886 ),
1887 }));
1888 }
1889 };
1890
1891 let finishing = RowSetFinishing {
1892 order_by: vec![],
1893 limit: None,
1894 offset: 0,
1895 project: (0..desc_arity).collect(),
1896 };
1897
1898 ReadThenWritePlan {
1899 id: insert_plan.id,
1900 selection: insert_plan.values,
1901 finishing,
1902 assignments: BTreeMap::new(),
1903 kind: MutationKind::Insert,
1904 returning: insert_plan.returning,
1905 }
1906 }
1907 _ => {
1908 return Err(AdapterError::Internal(
1909 "unexpected plan type for mutation".into(),
1910 ));
1911 }
1912 };
1913
1914 {
1925 let session = self.session.as_ref().expect("SessionClient invariant");
1926 let in_transaction = session
1927 .transaction()
1928 .may_share_transaction_with_other_statements();
1929 let depends_on = rtw_plan.selection.depends_on();
1930 if in_transaction && !depends_on.is_empty() {
1931 if contains_mz_now(&rtw_plan) {
1938 return Err(AdapterError::Unsupported(
1939 "calls to mz_now in write statements",
1940 ));
1941 }
1942 validate_selection_dependencies(&catalog, &depends_on, DependencyPolicy::UserDml)?;
1943 return Err(prohibited_in_transaction(&stmt));
1944 }
1945 }
1946
1947 let session = self.session.as_mut().expect("SessionClient invariant");
1948 self.peek_client
1949 .frontend_read_then_write(
1950 session,
1951 rtw_plan,
1952 target_cluster,
1953 &catalog,
1954 logging.id(),
1955 attempt_state,
1956 )
1957 .await
1958 .map(Some)
1959 }
1960}
1961
1962fn is_read_then_write_statement(stmt: &Statement<Raw>) -> bool {
1968 matches!(
1969 stmt,
1970 Statement::Delete(_) | Statement::Update(_) | Statement::Insert(_)
1971 )
1972}
1973
1974fn prohibited_in_transaction(stmt: &Statement<Raw>) -> AdapterError {
1979 let op = if StatementKind::from(stmt).is_sensitive() {
1980 stmt.to_ast_string_redacted()
1981 } else {
1982 stmt.to_string()
1983 };
1984 AdapterError::OperationProhibitsTransaction(op)
1985}
1986
1987impl Drop for SessionClient {
1988 fn drop(&mut self) {
1989 if let Some(session) = self.session.take() {
1993 if let Some(inner) = &self.inner {
1996 inner.send(Command::Terminate {
1997 conn_id: session.conn_id().clone(),
1998 tx: None,
1999 })
2000 }
2001 }
2002 }
2003}
2004
2005pub fn redact_sql_for_logging(sql: &str) -> String {
2013 match mz_sql_parser::parser::parse_statements_with_limit(sql) {
2014 Ok(Ok(stmts)) => truncate_sql_for_logging(
2015 stmts
2016 .into_iter()
2017 .map(|stmt| stmt.ast.to_ast_string_redacted())
2018 .join("; "),
2019 ),
2020 Ok(Err(_)) => format!("<unparseable ({} bytes)>", sql.len()),
2021 Err(_) => format!("<too large ({} bytes)>", sql.len()),
2022 }
2023}
2024
2025const MAX_LOGGED_SQL_LEN: usize = 8 * 1024;
2032
2033pub fn truncate_sql_for_logging(sql: String) -> String {
2036 if sql.len() <= MAX_LOGGED_SQL_LEN {
2037 return sql;
2038 }
2039 let end = sql.floor_char_boundary(MAX_LOGGED_SQL_LEN);
2040 format!("{}... <truncated from {} bytes>", &sql[..end], sql.len())
2041}
2042
2043#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
2044pub enum TimeoutType {
2045 IdleInTransactionSession(TransactionId),
2046}
2047
2048impl Display for TimeoutType {
2049 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2050 match self {
2051 TimeoutType::IdleInTransactionSession(txn_id) => {
2052 writeln!(f, "Idle in transaction session for transaction '{txn_id}'")
2053 }
2054 }
2055 }
2056}
2057
2058impl From<TimeoutType> for AdapterError {
2059 fn from(timeout: TimeoutType) -> Self {
2060 match timeout {
2061 TimeoutType::IdleInTransactionSession(_) => {
2062 AdapterError::IdleInTransactionSessionTimeout
2063 }
2064 }
2065 }
2066}
2067
2068struct Timeout {
2069 tx: mpsc::UnboundedSender<TimeoutType>,
2070 rx: mpsc::UnboundedReceiver<TimeoutType>,
2071 active_timeouts: BTreeMap<TimeoutType, AbortOnDropHandle<()>>,
2072}
2073
2074impl Timeout {
2075 fn new() -> Self {
2076 let (tx, rx) = mpsc::unbounded_channel();
2077 Timeout {
2078 tx,
2079 rx,
2080 active_timeouts: BTreeMap::new(),
2081 }
2082 }
2083
2084 async fn recv(&mut self) -> Option<TimeoutType> {
2093 self.rx.recv().await
2094 }
2095
2096 fn add_timeout(&mut self, timeout: TimeoutType, duration: Duration) {
2097 let tx = self.tx.clone();
2098 let timeout_key = timeout.clone();
2099 let handle = mz_ore::task::spawn(|| format!("{timeout_key}"), async move {
2100 tokio::time::sleep(duration).await;
2101 let _ = tx.send(timeout);
2102 })
2103 .abort_on_drop();
2104 self.active_timeouts.insert(timeout_key, handle);
2105 }
2106
2107 fn remove_timeout(&mut self, timeout: &TimeoutType) {
2108 self.active_timeouts.remove(timeout);
2109
2110 let mut timeouts = Vec::new();
2112 while let Ok(pending_timeout) = self.rx.try_recv() {
2113 if timeout != &pending_timeout {
2114 timeouts.push(pending_timeout);
2115 }
2116 }
2117 for pending_timeout in timeouts {
2118 self.tx.send(pending_timeout).expect("rx is in this struct");
2119 }
2120 }
2121}
2122
2123#[derive(Derivative)]
2127#[derivative(Debug)]
2128pub struct RecordFirstRowStream {
2129 #[derivative(Debug = "ignore")]
2131 pub rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2132 pub execute_started: Instant,
2134 pub time_to_first_row_seconds: Histogram,
2137 pub saw_rows: bool,
2139 pub recorded_first_row_instant: Option<Instant>,
2141 pub no_more_rows: bool,
2143 pub metric_recorded: bool,
2145}
2146
2147impl RecordFirstRowStream {
2148 pub fn new(
2150 rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
2151 execute_started: Instant,
2152 client: &SessionClient,
2153 instance_id: Option<ComputeInstanceId>,
2154 strategy: Option<StatementExecutionStrategy>,
2155 ) -> Self {
2156 let histogram = Self::histogram(client, instance_id, strategy);
2157 Self {
2158 rows,
2159 execute_started,
2160 time_to_first_row_seconds: histogram,
2161 saw_rows: false,
2162 recorded_first_row_instant: None,
2163 no_more_rows: false,
2164 metric_recorded: false,
2165 }
2166 }
2167
2168 fn histogram(
2169 client: &SessionClient,
2170 instance_id: Option<ComputeInstanceId>,
2171 strategy: Option<StatementExecutionStrategy>,
2172 ) -> Histogram {
2173 let session = client.session.as_ref().expect("session invariant");
2174 let isolation_level = *session.vars().transaction_isolation();
2175 let name_hint = ApplicationNameHint::from_str(session.application_name());
2176
2177 client
2178 .inner()
2179 .metrics()
2180 .by_cluster
2181 .time_to_first_row_seconds(instance_id, isolation_level, strategy, name_hint)
2182 }
2183
2184 pub fn record(
2187 execute_started: Instant,
2188 client: &SessionClient,
2189 instance_id: Option<ComputeInstanceId>,
2190 strategy: Option<StatementExecutionStrategy>,
2191 ) {
2192 Self::histogram(client, instance_id, strategy)
2193 .observe(execute_started.elapsed().as_secs_f64());
2194 }
2195
2196 pub async fn recv(&mut self) -> Option<PeekResponseUnary> {
2197 let msg = self.rows.next().await;
2198 if !self.saw_rows && matches!(msg, Some(PeekResponseUnary::Rows(_))) {
2199 self.saw_rows = true;
2200 self.time_to_first_row_seconds
2201 .observe(self.execute_started.elapsed().as_secs_f64());
2202 self.recorded_first_row_instant = Some(Instant::now());
2203 }
2204 if msg.is_none() {
2205 self.no_more_rows = true;
2206 }
2207 msg
2208 }
2209}
2210
2211#[cfg(test)]
2212mod tests {
2213 use super::*;
2214
2215 #[mz_ore::test]
2216 fn truncate_sql_for_logging_caps_length() {
2217 let short = "SELECT 1".to_string();
2218 assert_eq!(truncate_sql_for_logging(short.clone()), short);
2219
2220 let exact = "x".repeat(MAX_LOGGED_SQL_LEN);
2221 assert_eq!(truncate_sql_for_logging(exact.clone()), exact);
2222
2223 let long = format!(
2226 "{}é{}",
2227 "x".repeat(MAX_LOGGED_SQL_LEN - 1),
2228 "y".repeat(1000)
2229 );
2230 let truncated = truncate_sql_for_logging(long.clone());
2231 assert_eq!(
2232 truncated,
2233 format!(
2234 "{}... <truncated from {} bytes>",
2235 "x".repeat(MAX_LOGGED_SQL_LEN - 1),
2236 long.len()
2237 )
2238 );
2239 }
2240}