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