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::{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_ore::channel::OneshotReceiverExt;
29use mz_ore::collections::CollectionExt;
30use mz_ore::id_gen::{IdAllocator, IdAllocatorInnerBitSet, MAX_ORG_ID, org_id_conn_bits};
31use mz_ore::instrument;
32use mz_ore::now::{EpochMillis, NowFn, to_datetime};
33use mz_ore::str::StrExt;
34use mz_ore::task::AbortOnDropHandle;
35use mz_ore::thread::JoinOnDropHandle;
36use mz_ore::tracing::OpenTelemetryContext;
37use mz_repr::user::InternalUserMetadata;
38use mz_repr::{CatalogItemId, ColumnIndex, SqlScalarType};
39use mz_sql::ast::{Raw, Statement};
40use mz_sql::catalog::{EnvironmentId, SessionCatalog};
41use mz_sql::session::hint::ApplicationNameHint;
42use mz_sql::session::metadata::SessionMetadata;
43use mz_sql::session::user::SUPPORT_USER;
44use mz_sql::session::vars::{
45 CLUSTER, ENABLE_FRONTEND_PEEK_SEQUENCING, OwnedVarInput, SystemVars, Var,
46};
47use mz_sql_parser::ast::display::AstDisplay;
48use mz_sql_parser::parser::{ParserStatementError, StatementParseResult};
49use prometheus::Histogram;
50use serde_json::json;
51use tokio::sync::{mpsc, oneshot};
52use tracing::{debug, error};
53use uuid::Uuid;
54
55use crate::catalog::Catalog;
56use crate::command::{
57 CatalogDump, CatalogSnapshot, Command, CopyFromStdinWriter, ExecuteResponse, Response,
58 SASLChallengeResponse, SASLVerifyProofResponse, SuperuserAttribute,
59};
60use crate::config::{ScopedParameters, ScopedParametersScope, SystemParameterFrontend};
61use crate::coord::{Coordinator, ExecuteContextGuard};
62use crate::error::AdapterError;
63use crate::metrics::{self, Metrics};
64use crate::session::{
65 EndTransactionAction, PreparedStatement, Session, SessionConfig, StateRevision, TransactionId,
66};
67use crate::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
68use crate::telemetry::{self, EventDetails, SegmentClientExt, StatementFailureType};
69use crate::webhook::AppendWebhookResponse;
70use crate::{AdapterNotice, AppendWebhookError, PeekClient, PeekResponseUnary, StartupResponse};
71
72pub struct Handle {
78 pub(crate) session_id: Uuid,
79 pub(crate) start_instant: Instant,
80 pub(crate) _thread: JoinOnDropHandle<()>,
81}
82
83impl Handle {
84 pub fn session_id(&self) -> Uuid {
90 self.session_id
91 }
92
93 pub fn start_instant(&self) -> Instant {
95 self.start_instant
96 }
97}
98
99#[derive(Debug, Clone)]
107pub struct Client {
108 build_info: &'static BuildInfo,
109 inner_cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
110 id_alloc: IdAllocator<IdAllocatorInnerBitSet>,
111 now: NowFn,
112 metrics: Metrics,
113 environment_id: EnvironmentId,
114 segment_client: Option<mz_segment::Client>,
115}
116
117impl Client {
118 pub(crate) fn new(
119 build_info: &'static BuildInfo,
120 cmd_tx: mpsc::UnboundedSender<(OpenTelemetryContext, Command)>,
121 metrics: Metrics,
122 now: NowFn,
123 environment_id: EnvironmentId,
124 segment_client: Option<mz_segment::Client>,
125 ) -> Client {
126 let env_lower = org_id_conn_bits(&environment_id.organization_id());
134 Client {
135 build_info,
136 inner_cmd_tx: cmd_tx,
137 id_alloc: IdAllocator::new(1, MAX_ORG_ID, env_lower),
138 now,
139 metrics,
140 environment_id,
141 segment_client,
142 }
143 }
144
145 pub fn new_conn_id(&self) -> Result<ConnectionId, AdapterError> {
147 self.id_alloc.alloc().ok_or(AdapterError::IdExhaustionError)
148 }
149
150 pub fn new_session(&self, config: SessionConfig, _authenticated: Authenticated) -> Session {
156 Session::new(self.build_info, config, self.metrics().session_metrics())
160 }
161
162 pub async fn authenticate(
166 &self,
167 user: &String,
168 password: &Password,
169 ) -> Result<Authenticated, AdapterError> {
170 let (tx, rx) = oneshot::channel();
171 self.send(Command::AuthenticatePassword {
172 role_name: user.to_string(),
173 password: Some(password.clone()),
174 tx,
175 });
176 rx.await.expect("sender dropped")?;
177 Ok(Authenticated)
178 }
179
180 pub async fn generate_sasl_challenge(
183 &self,
184 user: &String,
185 client_nonce: &String,
186 ) -> Result<SASLChallengeResponse, AdapterError> {
187 let (tx, rx) = oneshot::channel();
188 self.send(Command::AuthenticateGetSASLChallenge {
189 role_name: user.to_string(),
190 nonce: client_nonce.to_string(),
191 tx,
192 });
193 let response = rx.await.expect("sender dropped")?;
194 Ok(response)
195 }
196
197 pub async fn verify_sasl_proof(
200 &self,
201 user: &String,
202 proof: &String,
203 nonce: &String,
204 mock_hash: &String,
205 ) -> Result<(SASLVerifyProofResponse, Authenticated), AdapterError> {
206 let (tx, rx) = oneshot::channel();
207 self.send(Command::AuthenticateVerifySASLProof {
208 role_name: user.to_string(),
209 proof: proof.to_string(),
210 auth_message: nonce.to_string(),
211 mock_hash: mock_hash.to_string(),
212 tx,
213 });
214 let response = rx.await.expect("sender dropped")?;
215 Ok((response, Authenticated))
216 }
217
218 pub async fn role_can_login(&self, role_name: &str) -> Result<(), AdapterError> {
220 let (tx, rx) = oneshot::channel();
221 self.send(Command::CheckRoleCanLogin {
222 role_name: role_name.to_string(),
223 tx,
224 });
225 rx.await.expect("sender dropped")
226 }
227
228 #[mz_ore::instrument(level = "debug")]
237 pub async fn startup(&self, session: Session) -> Result<SessionClient, AdapterError> {
238 let user = session.user().clone();
239 let conn_id = session.conn_id().clone();
240 let secret_key = session.secret_key();
241 let uuid = session.uuid();
242 let client_ip = session.client_ip();
243 let application_name = session.application_name().into();
244 let notice_tx = session.retain_notice_transmitter();
245
246 let (tx, rx) = oneshot::channel();
247
248 let rx = rx.with_guard(|_| {
254 self.send(Command::Terminate {
255 conn_id: conn_id.clone(),
256 tx: None,
257 });
258 });
259
260 self.send(Command::Startup {
261 tx,
262 user,
263 conn_id: conn_id.clone(),
264 secret_key,
265 uuid,
266 client_ip: client_ip.copied(),
267 application_name,
268 notice_tx,
269 });
270
271 let response = rx.await.expect("sender dropped")?;
274
275 let StartupResponse {
279 role_id,
280 write_notify,
281 session_defaults,
282 catalog,
283 storage_collections,
284 transient_id_gen,
285 optimizer_metrics,
286 persist_client,
287 statement_logging_frontend,
288 superuser_attribute,
289 } = response;
290
291 let peek_client = PeekClient::new(
292 self.clone(),
293 &catalog,
294 storage_collections,
295 transient_id_gen,
296 optimizer_metrics,
297 persist_client,
298 statement_logging_frontend,
299 );
300
301 let mut client = SessionClient {
302 inner: Some(self.clone()),
303 session: Some(session),
304 timeouts: Timeout::new(),
305 environment_id: self.environment_id.clone(),
306 segment_client: self.segment_client.clone(),
307 peek_client,
308 enable_frontend_peek_sequencing: false, };
310
311 let session = client.session();
312
313 if let SuperuserAttribute(Some(superuser)) = superuser_attribute {
316 session.apply_internal_user_metadata(InternalUserMetadata { superuser });
317 }
318
319 session.initialize_role_metadata(role_id);
320 let vars_mut = session.vars_mut();
321 for (name, val) in session_defaults {
322 if let Err(err) = vars_mut.set_default(&name, val.borrow()) {
323 tracing::error!("failed to set peristed default, {err:?}");
326 }
327 }
328 session
329 .vars_mut()
330 .end_transaction(EndTransactionAction::Commit);
331
332 session.set_builtin_table_updates(write_notify);
340
341 let catalog = catalog.for_session(session);
342
343 let cluster_active = session.vars().cluster().to_string();
344 if session.vars().welcome_message() {
345 let cluster_info = if catalog.resolve_cluster(Some(&cluster_active)).is_err() {
346 format!("{cluster_active} (does not exist)")
347 } else {
348 cluster_active.to_string()
349 };
350
351 session.add_notice(AdapterNotice::Welcome(format!(
355 "connected to Materialize v{}
356 Environment ID: {}
357 Region: {}
358 User: {}
359 Cluster: {}
360 Database: {}
361 {}
362 Session UUID: {}
363
364Issue a SQL query to get started. Need help?
365 View documentation: https://materialize.com/s/docs
366 Join our Slack community: https://materialize.com/s/chat
367 ",
368 session.vars().build_info().semver_version(),
369 self.environment_id,
370 self.environment_id.region(),
371 session.vars().user().name,
372 cluster_info,
373 session.vars().database(),
374 match session.vars().search_path() {
375 [schema] => format!("Schema: {}", schema),
376 schemas => format!(
377 "Search path: {}",
378 schemas.iter().map(|id| id.to_string()).join(", ")
379 ),
380 },
381 session.uuid(),
382 )));
383 }
384
385 if session.vars().current_object_missing_warnings() {
386 if catalog.active_database().is_none() {
387 let db = session.vars().database().into();
388 session.add_notice(AdapterNotice::UnknownSessionDatabase(db));
389 }
390 }
391
392 let cluster_var = session
395 .vars()
396 .inspect(CLUSTER.name())
397 .expect("cluster should exist");
398 if session.vars().current_object_missing_warnings()
399 && catalog.resolve_cluster(Some(&cluster_active)).is_err()
400 {
401 let cluster_notice = 'notice: {
402 if cluster_var.inspect_session_value().is_some() {
403 break 'notice Some(AdapterNotice::DefaultClusterDoesNotExist {
404 name: cluster_active,
405 kind: "session",
406 suggested_action: "Pick an extant cluster with SET CLUSTER = name. Run SHOW CLUSTERS to see available clusters.".into(),
407 });
408 }
409
410 let role_default = catalog.get_role(catalog.active_role_id());
411 let role_cluster = match role_default.vars().get(CLUSTER.name()) {
412 Some(OwnedVarInput::Flat(name)) => Some(name),
413 None => None,
414 Some(v @ OwnedVarInput::SqlSet(_)) => {
416 tracing::warn!(?v, "SqlSet found for cluster Role Default");
417 break 'notice None;
418 }
419 };
420
421 let alter_role = "with `ALTER ROLE <role> SET cluster TO <cluster>;`";
422 match role_cluster {
423 None => Some(AdapterNotice::DefaultClusterDoesNotExist {
425 name: cluster_active,
426 kind: "system",
427 suggested_action: format!(
428 "Set a default cluster for the current role {alter_role}."
429 ),
430 }),
431 Some(_) => Some(AdapterNotice::DefaultClusterDoesNotExist {
433 name: cluster_active,
434 kind: "role",
435 suggested_action: format!(
436 "Change the default cluster for the current role {alter_role}."
437 ),
438 }),
439 }
440 };
441
442 if let Some(notice) = cluster_notice {
443 session.add_notice(notice);
444 }
445 }
446
447 client.enable_frontend_peek_sequencing = ENABLE_FRONTEND_PEEK_SEQUENCING
448 .require(catalog.system_vars())
449 .is_ok();
450
451 Ok(client)
452 }
453
454 pub fn cancel_request(&self, conn_id: ConnectionIdType, secret_key: u32) {
456 self.send(Command::CancelRequest {
457 conn_id,
458 secret_key,
459 });
460 }
461
462 pub async fn support_execute_one(
465 &self,
466 sql: &str,
467 ) -> Result<Pin<Box<dyn Stream<Item = PeekResponseUnary> + Send>>, anyhow::Error> {
468 let conn_id = self.new_conn_id()?;
470 let session = self.new_session(
471 SessionConfig {
472 conn_id,
473 uuid: Uuid::new_v4(),
474 user: SUPPORT_USER.name.clone(),
475 client_ip: None,
476 external_metadata_rx: None,
477 helm_chart_version: None,
478 authenticator_kind: AuthenticatorKind::None,
479 groups: None,
480 },
481 Authenticated,
482 );
483 let mut session_client = self.startup(session).await?;
484
485 let stmts = mz_sql::parse::parse(sql)?;
487 if stmts.len() != 1 {
488 bail!("must supply exactly one query");
489 }
490 let StatementParseResult { ast: stmt, sql } = stmts.into_element();
491
492 const EMPTY_PORTAL: &str = "";
493 session_client.start_transaction(Some(1))?;
494 session_client
495 .declare(EMPTY_PORTAL.into(), stmt, sql.to_string())
496 .await?;
497
498 let execute_result = session_client
499 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
500 .await?;
501 match execute_result {
502 (ExecuteResponse::SendingRowsStreaming { mut rows, .. }, _) => {
503 let owning_response_stream = async_stream::stream! {
508 while let Some(rows) = rows.next().await {
509 yield rows;
510 }
511 drop(session_client);
512 };
513 Ok(Box::pin(owning_response_stream))
514 }
515 r => bail!("unsupported response type: {r:?}"),
516 }
517 }
518
519 pub fn metrics(&self) -> &Metrics {
521 &self.metrics
522 }
523
524 pub fn now(&self) -> DateTime<Utc> {
526 to_datetime((self.now)())
527 }
528
529 pub async fn get_webhook_appender(
531 &self,
532 database: String,
533 schema: String,
534 name: String,
535 ) -> Result<AppendWebhookResponse, AppendWebhookError> {
536 let (tx, rx) = oneshot::channel();
537
538 self.send(Command::GetWebhook {
540 database,
541 schema,
542 name,
543 tx,
544 });
545
546 let response = rx
548 .await
549 .map_err(|_| anyhow::anyhow!("failed to receive webhook response"))?;
550
551 response
552 }
553
554 pub async fn get_system_vars(&self) -> SystemVars {
556 let (tx, rx) = oneshot::channel();
557 self.send(Command::GetSystemVars { tx });
558 rx.await.expect("coordinator unexpectedly gone")
559 }
560
561 pub async fn catalog_snapshot_expensive(&self) -> Arc<Catalog> {
567 let (tx, rx) = oneshot::channel();
568 self.send(Command::CatalogSnapshot { tx });
569 let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone");
570 catalog
571 }
572
573 pub async fn update_scoped_system_parameters(
583 &self,
584 overrides: ScopedParameters,
585 prune_scope: Option<ScopedParametersScope>,
586 ) {
587 let (tx, rx) = oneshot::channel();
588 self.send(Command::UpdateScopedSystemParameters {
589 overrides,
590 prune_scope,
591 tx,
592 });
593 let _ = rx.await;
594 }
595
596 pub fn install_scoped_system_parameter_frontend(&self, frontend: Arc<SystemParameterFrontend>) {
601 self.send(Command::InstallScopedSystemParameterFrontend { frontend });
602 }
603
604 #[instrument(level = "debug")]
605 pub(crate) fn send(&self, cmd: Command) {
606 self.inner_cmd_tx
607 .send((OpenTelemetryContext::obtain(), cmd))
608 .expect("coordinator unexpectedly gone");
609 }
610}
611
612pub struct SessionClient {
616 inner: Option<Client>,
620 session: Option<Session>,
623 timeouts: Timeout,
624 segment_client: Option<mz_segment::Client>,
625 environment_id: EnvironmentId,
626 peek_client: PeekClient,
628 pub enable_frontend_peek_sequencing: bool,
633}
634
635impl SessionClient {
636 pub fn parse<'a>(
639 &self,
640 sql: &'a str,
641 ) -> Result<Result<Vec<StatementParseResult<'a>>, ParserStatementError>, String> {
642 match mz_sql::parse::parse_with_limit(sql) {
643 Ok(Err(e)) => {
644 self.track_statement_parse_failure(&e);
645 Ok(Err(e))
646 }
647 r => r,
648 }
649 }
650
651 fn track_statement_parse_failure(&self, parse_error: &ParserStatementError) {
652 let session = self.session.as_ref().expect("session invariant violated");
653 let Some(user_id) = session.user().external_metadata.as_ref().map(|m| m.user_id) else {
654 return;
655 };
656 let Some(segment_client) = &self.segment_client else {
657 return;
658 };
659 let Some(statement_kind) = parse_error.statement else {
660 return;
661 };
662 let Some((action, object_type)) = telemetry::analyze_audited_statement(statement_kind)
663 else {
664 return;
665 };
666 let event_type = StatementFailureType::ParseFailure;
667 let event_name = format!(
668 "{} {} {}",
669 object_type.as_title_case(),
670 action.as_title_case(),
671 event_type.as_title_case(),
672 );
673 segment_client.environment_track(
674 &self.environment_id,
675 event_name,
676 json!({
677 "statement_kind": statement_kind,
678 "error": &parse_error.error,
679 }),
680 EventDetails {
681 user_id: Some(user_id),
682 application_name: Some(session.application_name()),
683 ..Default::default()
684 },
685 );
686 }
687
688 pub async fn get_prepared_statement(
691 &mut self,
692 name: &str,
693 ) -> Result<&PreparedStatement, AdapterError> {
694 let catalog = self.catalog_snapshot("get_prepared_statement").await;
695 Coordinator::verify_prepared_statement(&catalog, self.session(), name)?;
696 Ok(self
697 .session()
698 .get_prepared_statement_unverified(name)
699 .expect("must exist"))
700 }
701
702 pub async fn prepare(
707 &mut self,
708 name: String,
709 stmt: Option<Statement<Raw>>,
710 sql: String,
711 param_types: Vec<Option<SqlScalarType>>,
712 ) -> Result<(), AdapterError> {
713 let catalog = self.catalog_snapshot("prepare").await;
714
715 let mut async_pause = false;
718 (|| {
719 fail::fail_point!("async_prepare", |val| {
720 async_pause = val.map_or(false, |val| val.parse().unwrap_or(false))
721 });
722 })();
723 if async_pause {
724 tokio::time::sleep(Duration::from_secs(1)).await;
725 };
726
727 let desc = Coordinator::describe(&catalog, self.session(), stmt.clone(), param_types)?;
728 let now = self.now();
729 let state_revision = StateRevision {
730 catalog_revision: catalog.transient_revision(),
731 session_state_revision: self.session().state_revision(),
732 };
733 self.session()
734 .set_prepared_statement(name, stmt, sql, desc, state_revision, now);
735 Ok(())
736 }
737
738 #[mz_ore::instrument(level = "debug")]
740 pub async fn declare(
741 &mut self,
742 name: String,
743 stmt: Statement<Raw>,
744 sql: String,
745 ) -> Result<(), AdapterError> {
746 let catalog = self.catalog_snapshot("declare").await;
747 let param_types = vec![];
748 let desc =
749 Coordinator::describe(&catalog, self.session(), Some(stmt.clone()), param_types)?;
750 let params = vec![];
751 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
752 let now = self.now();
753 let logging = self.session().mint_logging(sql, Some(&stmt), now);
754 let state_revision = StateRevision {
755 catalog_revision: catalog.transient_revision(),
756 session_state_revision: self.session().state_revision(),
757 };
758 self.session().set_portal(
759 name,
760 desc,
761 Some(stmt),
762 logging,
763 params,
764 result_formats,
765 state_revision,
766 )?;
767 Ok(())
768 }
769
770 #[mz_ore::instrument(level = "debug")]
777 pub async fn execute(
778 &mut self,
779 portal_name: String,
780 cancel_future: impl Future<Output = std::io::Error> + Send,
781 outer_ctx_extra: Option<ExecuteContextGuard>,
782 ) -> Result<(ExecuteResponse, Instant), AdapterError> {
783 let execute_started = Instant::now();
784
785 let mut outer_ctx_extra = outer_ctx_extra;
786
787 let portal_name = self
800 .unroll_sql_execute(portal_name, &mut outer_ctx_extra)
801 .await?;
802
803 let peek_result = self
807 .try_frontend_peek(&portal_name, &mut outer_ctx_extra)
808 .await?;
809 if let Some(resp) = peek_result {
810 debug!("frontend peek succeeded");
811 return Ok((resp, execute_started));
814 } else {
815 debug!("frontend peek did not happen, falling back to `Command::Execute`");
816 }
821
822 let response = self
823 .send_with_cancel(
824 |tx, session| Command::Execute {
825 portal_name,
826 session,
827 tx,
828 outer_ctx_extra,
829 },
830 cancel_future,
831 )
832 .await?;
833 Ok((response, execute_started))
834 }
835
836 async fn unroll_sql_execute(
849 &mut self,
850 portal_name: String,
851 outer_ctx_extra: &mut Option<ExecuteContextGuard>,
852 ) -> Result<String, AdapterError> {
853 let (stmt, params, outer_logging, outer_lifecycle_timestamps) = {
854 let session = self.session.as_ref().expect("SessionClient invariant");
855 let portal = match session.get_portal_unverified(&portal_name) {
856 Some(p) => p,
857 None => return Ok(portal_name),
860 };
861 match &portal.stmt {
862 Some(stmt) => (
863 Arc::clone(stmt),
864 portal.parameters.clone(),
865 Arc::clone(&portal.logging),
866 portal.lifecycle_timestamps.clone(),
867 ),
868 None => return Ok(portal_name),
869 }
870 };
871
872 if !matches!(&*stmt, Statement::Execute(_)) {
875 return Ok(portal_name);
876 }
877
878 let catalog = self.catalog_snapshot("unroll_sql_execute").await;
879
880 {
884 let session = self.session.as_mut().expect("SessionClient invariant");
885 Coordinator::verify_portal(&catalog, session, &portal_name)?;
886 }
887
888 {
892 let session = self.session.as_ref().expect("SessionClient invariant");
893 session
894 .metrics()
895 .query_total(&[
896 metrics::session_type_label_value(session.user()),
897 metrics::statement_type_label_value(&stmt),
898 ])
899 .inc();
900 }
901
902 let began_outer_logging = outer_ctx_extra.is_none();
912 let logging_id: Option<crate::statement_logging::StatementLoggingId> =
913 if began_outer_logging {
914 let session = self.session.as_mut().expect("SessionClient invariant");
915 let result = self
916 .peek_client
917 .statement_logging_frontend
918 .begin_statement_execution(
919 session,
920 ¶ms,
921 &outer_logging,
922 catalog.system_config(),
923 outer_lifecycle_timestamps,
924 );
925 if let Some((id, began_execution, mseh_update, prepared_statement)) = result {
926 self.peek_client.log_began_execution(
927 began_execution,
928 mseh_update,
929 prepared_statement,
930 );
931 Some(id)
932 } else {
933 None
934 }
935 } else {
936 None
937 };
938
939 let new_portal_name = match self.install_inner_portal_for_execute(&catalog, &stmt, ¶ms)
940 {
941 Ok(name) => name,
942 Err(err) => {
943 if let Some(id) = logging_id {
944 self.peek_client.log_ended_execution(
945 id,
946 StatementEndedExecutionReason::Errored {
947 error: err.to_string(),
948 },
949 );
950 }
951 return Err(err);
952 }
953 };
954
955 if began_outer_logging {
967 let (dummy_tx, _dummy_rx) = mpsc::unbounded_channel();
975 *outer_ctx_extra = Some(ExecuteContextGuard::new(logging_id, dummy_tx));
976 }
977
978 Ok(new_portal_name)
979 }
980
981 fn install_inner_portal_for_execute(
990 &mut self,
991 catalog: &Arc<Catalog>,
992 stmt: &Arc<Statement<Raw>>,
993 params: &mz_sql::plan::Params,
994 ) -> Result<String, AdapterError> {
995 use mz_sql::plan::Plan;
996
997 let execute_plan = {
998 let session = self.session.as_mut().expect("SessionClient invariant");
999 let conn_catalog = catalog.for_session(session);
1000 let (resolved_stmt, resolved_ids) =
1001 mz_sql::names::resolve(&conn_catalog, (**stmt).clone())?;
1002 let pcx = session.pcx();
1003 let (plan, _sql_impl_ids) = mz_sql::plan::plan(
1004 Some(pcx),
1005 &conn_catalog,
1006 resolved_stmt,
1007 params,
1008 &resolved_ids,
1009 )?;
1010 match plan {
1011 Plan::Execute(plan) => plan,
1012 other => {
1013 return Err(AdapterError::Internal(format!(
1017 "planning Statement::Execute yielded unexpected plan: {:?}",
1018 mz_sql::plan::PlanKind::from(&other),
1019 )));
1020 }
1021 }
1022 };
1023
1024 let session = self.session.as_mut().expect("SessionClient invariant");
1030 Coordinator::verify_prepared_statement(catalog, session, &execute_plan.name)?;
1031 let ps = session
1032 .get_prepared_statement_unverified(&execute_plan.name)
1033 .expect("verified above");
1034 let inner_stmt = ps.stmt().cloned();
1035 let inner_desc = ps.desc().clone();
1036 let state_revision = ps.state_revision;
1037 let inner_logging = Arc::clone(ps.logging());
1038
1039 if let Some(inner) = inner_stmt.as_ref() {
1044 if matches!(inner, Statement::Execute(_)) {
1045 return Err(AdapterError::Internal(format!(
1046 "nested EXECUTE: prepared statement {} resolves to another EXECUTE; \
1047 parser should reject `PREPARE ... AS EXECUTE ...`",
1048 execute_plan.name.quoted(),
1049 )));
1050 }
1051 }
1052
1053 session.create_new_portal(
1054 inner_stmt,
1055 inner_logging,
1056 inner_desc,
1057 execute_plan.params,
1058 Vec::new(),
1059 state_revision,
1060 )
1061 }
1062
1063 fn now(&self) -> EpochMillis {
1064 (self.inner().now)()
1065 }
1066
1067 fn now_datetime(&self) -> DateTime<Utc> {
1068 to_datetime(self.now())
1069 }
1070
1071 pub fn start_transaction(&mut self, implicit: Option<usize>) -> Result<(), AdapterError> {
1077 let now = self.now_datetime();
1078 let session = self.session.as_mut().expect("session invariant violated");
1079 let result = match implicit {
1080 None => session.start_transaction(now, None, None),
1081 Some(stmts) => {
1082 session.start_transaction_implicit(now, stmts);
1083 Ok(())
1084 }
1085 };
1086 result
1087 }
1088
1089 #[instrument(level = "debug")]
1092 pub async fn end_transaction(
1093 &mut self,
1094 action: EndTransactionAction,
1095 ) -> Result<ExecuteResponse, AdapterError> {
1096 let res = self
1097 .send(|tx, session| Command::Commit {
1098 action,
1099 session,
1100 tx,
1101 })
1102 .await;
1103 let _ = self.session().clear_transaction();
1107 res
1108 }
1109
1110 pub fn fail_transaction(&mut self) {
1112 let session = self.session.take().expect("session invariant violated");
1113 let session = session.fail_transaction();
1114 self.session = Some(session);
1115 }
1116
1117 #[instrument(level = "debug")]
1121 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
1122 self.peek_client.catalog_snapshot(context).await
1123 }
1124
1125 pub async fn statement_arrival_logging_enabled(&mut self) -> bool {
1127 let catalog = self.catalog_snapshot("statement_arrival_logging").await;
1128 catalog.system_config().enable_statement_arrival_logging()
1129 }
1130
1131 pub async fn dump_catalog(&mut self) -> Result<CatalogDump, AdapterError> {
1136 let catalog = self.catalog_snapshot("dump_catalog").await;
1137 catalog.dump().map_err(AdapterError::from)
1138 }
1139
1140 pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> {
1146 let catalog = self.catalog_snapshot("check_catalog").await;
1147 catalog.check_consistency()
1148 }
1149
1150 pub async fn check_coordinator(&self) -> Result<(), serde_json::Value> {
1156 self.send_without_session(|tx| Command::CheckConsistency { tx })
1157 .await
1158 .map_err(|inconsistencies| {
1159 serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1160 serde_json::Value::String("failed to serialize inconsistencies".to_string())
1161 })
1162 })
1163 }
1164
1165 pub async fn dump_coordinator_state(&self) -> Result<serde_json::Value, anyhow::Error> {
1166 self.send_without_session(|tx| Command::Dump { tx }).await
1167 }
1168
1169 pub fn retire_execute(
1172 &self,
1173 guard: ExecuteContextGuard,
1174 reason: StatementEndedExecutionReason,
1175 ) {
1176 if !guard.is_trivial() {
1177 let data = guard.defuse();
1178 let cmd = Command::RetireExecute { data, reason };
1179 self.inner().send(cmd);
1180 }
1181 }
1182
1183 pub async fn start_copy_from_stdin(
1189 &mut self,
1190 target_id: CatalogItemId,
1191 target_name: String,
1192 columns: Vec<ColumnIndex>,
1193 row_desc: mz_repr::RelationDesc,
1194 params: mz_pgcopy::CopyFormatParams<'static>,
1195 ) -> Result<CopyFromStdinWriter, AdapterError> {
1196 self.send(|tx, session| Command::StartCopyFromStdin {
1197 target_id,
1198 target_name,
1199 columns,
1200 row_desc,
1201 params,
1202 session,
1203 tx,
1204 })
1205 .await
1206 }
1207
1208 pub fn stage_copy_from_stdin_batches(
1213 &mut self,
1214 target_id: CatalogItemId,
1215 batches: Vec<mz_persist_client::batch::ProtoBatch>,
1216 ) -> Result<(), AdapterError> {
1217 use crate::session::{TransactionOps, WriteOp};
1218 use mz_storage_client::client::TableData;
1219
1220 self.session()
1221 .add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
1222 id: target_id,
1223 rows: TableData::Batches(batches.into()),
1224 }]))?;
1225 Ok(())
1226 }
1227
1228 pub async fn get_system_vars(&self) -> SystemVars {
1230 self.inner().get_system_vars().await
1231 }
1232
1233 pub async fn set_system_vars(
1235 &mut self,
1236 vars: BTreeMap<String, String>,
1237 ) -> Result<(), AdapterError> {
1238 let conn_id = self.session().conn_id().clone();
1239 self.send_without_session(|tx| Command::SetSystemVars { vars, conn_id, tx })
1240 .await
1241 }
1242
1243 pub async fn inject_audit_events(
1248 &mut self,
1249 events: Vec<crate::catalog::InjectedAuditEvent>,
1250 ) -> Result<(), AdapterError> {
1251 let conn_id = self.session().conn_id().clone();
1252 self.send_without_session(|tx| Command::InjectAuditEvents {
1253 events,
1254 conn_id,
1255 tx,
1256 })
1257 .await
1258 }
1259
1260 pub async fn terminate(&mut self) {
1262 let conn_id = self.session().conn_id().clone();
1263 let res = self
1264 .send_without_session(|tx| Command::Terminate {
1265 conn_id,
1266 tx: Some(tx),
1267 })
1268 .await;
1269 if let Err(e) = res {
1270 error!("Unable to terminate session: {e:?}");
1272 }
1273 self.inner = None;
1275 }
1276
1277 pub fn session(&mut self) -> &mut Session {
1279 self.session.as_mut().expect("session invariant violated")
1280 }
1281
1282 pub fn inner(&self) -> &Client {
1284 self.inner.as_ref().expect("inner invariant violated")
1285 }
1286
1287 async fn send_without_session<T, F>(&self, f: F) -> T
1288 where
1289 F: FnOnce(oneshot::Sender<T>) -> Command,
1290 {
1291 let (tx, rx) = oneshot::channel();
1292 self.inner().send(f(tx));
1293 rx.await.expect("sender dropped")
1294 }
1295
1296 #[instrument(level = "debug")]
1297 async fn send<T, F>(&mut self, f: F) -> Result<T, AdapterError>
1298 where
1299 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1300 {
1301 self.send_with_cancel(f, futures::future::pending()).await
1302 }
1303
1304 #[instrument(level = "debug")]
1308 async fn send_with_cancel<T, F>(
1309 &mut self,
1310 f: F,
1311 cancel_future: impl Future<Output = std::io::Error> + Send,
1312 ) -> Result<T, AdapterError>
1313 where
1314 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1315 {
1316 let session = self.session.take().expect("session invariant violated");
1317 let mut typ = None;
1318 let application_name = session.application_name();
1319 let name_hint = ApplicationNameHint::from_str(application_name);
1320 let conn_id = session.conn_id().clone();
1321 let (tx, rx) = oneshot::channel();
1322
1323 let Self {
1326 inner: inner_client,
1327 session: client_session,
1328 ..
1329 } = self;
1330
1331 let inner_client = inner_client.as_ref().expect("inner invariant violated");
1334
1335 let mut guarded_rx = rx.with_guard(|response: Response<_>| {
1341 *client_session = Some(response.session);
1342 });
1343
1344 inner_client.send({
1345 let cmd = f(tx, session);
1346 match cmd {
1350 Command::Execute { .. } => typ = Some("execute"),
1351 Command::GetWebhook { .. } => typ = Some("webhook"),
1352 Command::StartCopyFromStdin { .. }
1353 | Command::Startup { .. }
1354 | Command::AuthenticatePassword { .. }
1355 | Command::AuthenticateGetSASLChallenge { .. }
1356 | Command::AuthenticateVerifySASLProof { .. }
1357 | Command::CheckRoleCanLogin { .. }
1358 | Command::CatalogSnapshot { .. }
1359 | Command::Commit { .. }
1360 | Command::CancelRequest { .. }
1361 | Command::PrivilegedCancelRequest { .. }
1362 | Command::GetSystemVars { .. }
1363 | Command::SetSystemVars { .. }
1364 | Command::UpdateScopedSystemParameters { .. }
1365 | Command::InstallScopedSystemParameterFrontend { .. }
1366 | Command::Terminate { .. }
1367 | Command::RetireExecute { .. }
1368 | Command::CheckConsistency { .. }
1369 | Command::Dump { .. }
1370 | Command::GetComputeInstanceClient { .. }
1371 | Command::GetOracle { .. }
1372 | Command::DetermineRealTimeRecentTimestamp { .. }
1373 | Command::GetTransactionReadHoldsBundle { .. }
1374 | Command::StoreTransactionReadHolds { .. }
1375 | Command::ExecuteSlowPathPeek { .. }
1376 | Command::ExecuteSubscribe { .. }
1377 | Command::CopyToPreflight { .. }
1378 | Command::ExecuteCopyTo { .. }
1379 | Command::ExecuteSideEffectingFunc { .. }
1380 | Command::LookupConnection { .. }
1381 | Command::RegisterFrontendPeek { .. }
1382 | Command::UnregisterFrontendPeek { .. }
1383 | Command::ExplainTimestamp { .. }
1384 | Command::FrontendStatementLogging(..)
1385 | Command::InjectAuditEvents { .. } => {}
1386 };
1387 cmd
1388 });
1389
1390 let mut cancel_future = pin::pin!(cancel_future);
1391 let mut cancelled = false;
1392 loop {
1393 tokio::select! {
1394 res = &mut guarded_rx => {
1395 drop(guarded_rx);
1397
1398 let res = res.expect("sender dropped");
1399 let status = res.result.is_ok().then_some("success").unwrap_or("error");
1400 if let Err(err) = res.result.as_ref() {
1401 if name_hint.should_trace_errors() {
1402 tracing::warn!(?err, ?name_hint, "adapter response error");
1403 }
1404 }
1405
1406 if let Some(typ) = typ {
1407 inner_client
1408 .metrics
1409 .commands
1410 .with_label_values(&[typ, status, name_hint.as_str()])
1411 .inc();
1412 }
1413 *client_session = Some(res.session);
1414 return res.result;
1415 },
1416 _err = &mut cancel_future, if !cancelled => {
1417 cancelled = true;
1418 inner_client.send(Command::PrivilegedCancelRequest {
1419 conn_id: conn_id.clone(),
1420 });
1421 }
1422 };
1423 }
1424 }
1425
1426 pub fn add_idle_in_transaction_session_timeout(&mut self) {
1427 let session = self.session();
1428 let timeout_dur = session.vars().idle_in_transaction_session_timeout();
1429 if !timeout_dur.is_zero() {
1430 let timeout_dur = timeout_dur.clone();
1431 if let Some(txn) = session.transaction().inner() {
1432 let txn_id = txn.id.clone();
1433 let timeout = TimeoutType::IdleInTransactionSession(txn_id);
1434 self.timeouts.add_timeout(timeout, timeout_dur);
1435 }
1436 }
1437 }
1438
1439 pub fn remove_idle_in_transaction_session_timeout(&mut self) {
1440 let session = self.session();
1441 if let Some(txn) = session.transaction().inner() {
1442 let txn_id = txn.id.clone();
1443 self.timeouts
1444 .remove_timeout(&TimeoutType::IdleInTransactionSession(txn_id));
1445 }
1446 }
1447
1448 pub async fn recv_timeout(&mut self) -> Option<TimeoutType> {
1455 self.timeouts.recv().await
1456 }
1457
1458 pub(crate) async fn try_frontend_peek(
1466 &mut self,
1467 portal_name: &str,
1468 outer_ctx_extra: &mut Option<ExecuteContextGuard>,
1469 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1470 if self.enable_frontend_peek_sequencing {
1471 let session = self.session.as_mut().expect("SessionClient invariant");
1472 self.peek_client
1473 .try_frontend_peek(portal_name, session, outer_ctx_extra)
1474 .await
1475 } else {
1476 Ok(None)
1477 }
1478 }
1479}
1480
1481impl Drop for SessionClient {
1482 fn drop(&mut self) {
1483 if let Some(session) = self.session.take() {
1487 if let Some(inner) = &self.inner {
1490 inner.send(Command::Terminate {
1491 conn_id: session.conn_id().clone(),
1492 tx: None,
1493 })
1494 }
1495 }
1496 }
1497}
1498
1499pub fn redact_sql_for_logging(sql: &str) -> String {
1506 match mz_sql_parser::parser::parse_statements_with_limit(sql) {
1507 Ok(Ok(stmts)) => stmts
1508 .into_iter()
1509 .map(|stmt| stmt.ast.to_ast_string_redacted())
1510 .join("; "),
1511 Ok(Err(_)) => format!("<unparseable ({} bytes)>", sql.len()),
1512 Err(_) => format!("<too large ({} bytes)>", sql.len()),
1513 }
1514}
1515
1516#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
1517pub enum TimeoutType {
1518 IdleInTransactionSession(TransactionId),
1519}
1520
1521impl Display for TimeoutType {
1522 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1523 match self {
1524 TimeoutType::IdleInTransactionSession(txn_id) => {
1525 writeln!(f, "Idle in transaction session for transaction '{txn_id}'")
1526 }
1527 }
1528 }
1529}
1530
1531impl From<TimeoutType> for AdapterError {
1532 fn from(timeout: TimeoutType) -> Self {
1533 match timeout {
1534 TimeoutType::IdleInTransactionSession(_) => {
1535 AdapterError::IdleInTransactionSessionTimeout
1536 }
1537 }
1538 }
1539}
1540
1541struct Timeout {
1542 tx: mpsc::UnboundedSender<TimeoutType>,
1543 rx: mpsc::UnboundedReceiver<TimeoutType>,
1544 active_timeouts: BTreeMap<TimeoutType, AbortOnDropHandle<()>>,
1545}
1546
1547impl Timeout {
1548 fn new() -> Self {
1549 let (tx, rx) = mpsc::unbounded_channel();
1550 Timeout {
1551 tx,
1552 rx,
1553 active_timeouts: BTreeMap::new(),
1554 }
1555 }
1556
1557 async fn recv(&mut self) -> Option<TimeoutType> {
1566 self.rx.recv().await
1567 }
1568
1569 fn add_timeout(&mut self, timeout: TimeoutType, duration: Duration) {
1570 let tx = self.tx.clone();
1571 let timeout_key = timeout.clone();
1572 let handle = mz_ore::task::spawn(|| format!("{timeout_key}"), async move {
1573 tokio::time::sleep(duration).await;
1574 let _ = tx.send(timeout);
1575 })
1576 .abort_on_drop();
1577 self.active_timeouts.insert(timeout_key, handle);
1578 }
1579
1580 fn remove_timeout(&mut self, timeout: &TimeoutType) {
1581 self.active_timeouts.remove(timeout);
1582
1583 let mut timeouts = Vec::new();
1585 while let Ok(pending_timeout) = self.rx.try_recv() {
1586 if timeout != &pending_timeout {
1587 timeouts.push(pending_timeout);
1588 }
1589 }
1590 for pending_timeout in timeouts {
1591 self.tx.send(pending_timeout).expect("rx is in this struct");
1592 }
1593 }
1594}
1595
1596#[derive(Derivative)]
1600#[derivative(Debug)]
1601pub struct RecordFirstRowStream {
1602 #[derivative(Debug = "ignore")]
1604 pub rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
1605 pub execute_started: Instant,
1607 pub time_to_first_row_seconds: Histogram,
1610 pub saw_rows: bool,
1612 pub recorded_first_row_instant: Option<Instant>,
1614 pub no_more_rows: bool,
1616 pub metric_recorded: bool,
1618}
1619
1620impl RecordFirstRowStream {
1621 pub fn new(
1623 rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
1624 execute_started: Instant,
1625 client: &SessionClient,
1626 instance_id: Option<ComputeInstanceId>,
1627 strategy: Option<StatementExecutionStrategy>,
1628 ) -> Self {
1629 let histogram = Self::histogram(client, instance_id, strategy);
1630 Self {
1631 rows,
1632 execute_started,
1633 time_to_first_row_seconds: histogram,
1634 saw_rows: false,
1635 recorded_first_row_instant: None,
1636 no_more_rows: false,
1637 metric_recorded: false,
1638 }
1639 }
1640
1641 fn histogram(
1642 client: &SessionClient,
1643 instance_id: Option<ComputeInstanceId>,
1644 strategy: Option<StatementExecutionStrategy>,
1645 ) -> Histogram {
1646 let session = client.session.as_ref().expect("session invariant");
1647 let isolation_level = *session.vars().transaction_isolation();
1648 let name_hint = ApplicationNameHint::from_str(session.application_name());
1649 let instance = match instance_id {
1650 Some(i) => Cow::Owned(i.to_string()),
1651 None => Cow::Borrowed("none"),
1652 };
1653 let strategy = match strategy {
1654 Some(s) => s.name(),
1655 None => "none",
1656 };
1657
1658 client
1659 .inner()
1660 .metrics()
1661 .time_to_first_row_seconds
1662 .with_label_values(&[
1663 instance.as_ref(),
1664 isolation_level.as_variant_str(),
1665 strategy,
1666 name_hint.as_str(),
1667 ])
1668 }
1669
1670 pub fn record(
1673 execute_started: Instant,
1674 client: &SessionClient,
1675 instance_id: Option<ComputeInstanceId>,
1676 strategy: Option<StatementExecutionStrategy>,
1677 ) {
1678 Self::histogram(client, instance_id, strategy)
1679 .observe(execute_started.elapsed().as_secs_f64());
1680 }
1681
1682 pub async fn recv(&mut self) -> Option<PeekResponseUnary> {
1683 let msg = self.rows.next().await;
1684 if !self.saw_rows && matches!(msg, Some(PeekResponseUnary::Rows(_))) {
1685 self.saw_rows = true;
1686 self.time_to_first_row_seconds
1687 .observe(self.execute_started.elapsed().as_secs_f64());
1688 self.recorded_first_row_instant = Some(Instant::now());
1689 }
1690 if msg.is_none() {
1691 self.no_more_rows = true;
1692 }
1693 msg
1694 }
1695}