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(|resp: Result<StartupResponse, _>| {
259 if resp.is_ok() {
260 self.send(Command::Terminate {
261 conn_id: conn_id.clone(),
262 tx: None,
263 });
264 }
265 });
266
267 self.send(Command::Startup {
268 tx,
269 user,
270 conn_id: conn_id.clone(),
271 secret_key,
272 uuid,
273 client_ip: client_ip.copied(),
274 application_name,
275 notice_tx,
276 });
277
278 let response = rx.await.expect("sender dropped")?;
281
282 let StartupResponse {
286 role_id,
287 write_notify,
288 session_defaults,
289 catalog,
290 storage_collections,
291 transient_id_gen,
292 optimizer_metrics,
293 persist_client,
294 statement_logging_frontend,
295 superuser_attribute,
296 } = response;
297
298 let peek_client = PeekClient::new(
299 self.clone(),
300 &catalog,
301 storage_collections,
302 transient_id_gen,
303 optimizer_metrics,
304 persist_client,
305 statement_logging_frontend,
306 );
307
308 let mut client = SessionClient {
309 inner: Some(self.clone()),
310 session: Some(session),
311 timeouts: Timeout::new(),
312 environment_id: self.environment_id.clone(),
313 segment_client: self.segment_client.clone(),
314 peek_client,
315 enable_frontend_peek_sequencing: false, };
317
318 let session = client.session();
319
320 if let SuperuserAttribute(Some(superuser)) = superuser_attribute {
323 session.apply_internal_user_metadata(InternalUserMetadata { superuser });
324 }
325
326 session.initialize_role_metadata(role_id);
327 let vars_mut = session.vars_mut();
328 for (name, val) in session_defaults {
329 if let Err(err) = vars_mut.set_default(&name, val.borrow()) {
330 tracing::error!("failed to set peristed default, {err:?}");
333 }
334 }
335 session
336 .vars_mut()
337 .end_transaction(EndTransactionAction::Commit);
338
339 session.set_builtin_table_updates(write_notify);
347
348 let catalog = catalog.for_session(session);
349
350 let cluster_active = session.vars().cluster().to_string();
351 if session.vars().welcome_message() {
352 let cluster_info = if catalog.resolve_cluster(Some(&cluster_active)).is_err() {
353 format!("{cluster_active} (does not exist)")
354 } else {
355 cluster_active.to_string()
356 };
357
358 session.add_notice(AdapterNotice::Welcome(format!(
362 "connected to Materialize v{}
363 Environment ID: {}
364 Region: {}
365 User: {}
366 Cluster: {}
367 Database: {}
368 {}
369 Session UUID: {}
370
371Issue a SQL query to get started. Need help?
372 View documentation: https://materialize.com/s/docs
373 Join our Slack community: https://materialize.com/s/chat
374 ",
375 session.vars().build_info().semver_version(),
376 self.environment_id,
377 self.environment_id.region(),
378 session.vars().user().name,
379 cluster_info,
380 session.vars().database(),
381 match session.vars().search_path() {
382 [schema] => format!("Schema: {}", schema),
383 schemas => format!(
384 "Search path: {}",
385 schemas.iter().map(|id| id.to_string()).join(", ")
386 ),
387 },
388 session.uuid(),
389 )));
390 }
391
392 if session.vars().current_object_missing_warnings() {
393 if catalog.active_database().is_none() {
394 let db = session.vars().database().into();
395 session.add_notice(AdapterNotice::UnknownSessionDatabase(db));
396 }
397 }
398
399 let cluster_var = session
402 .vars()
403 .inspect(CLUSTER.name())
404 .expect("cluster should exist");
405 if session.vars().current_object_missing_warnings()
406 && catalog.resolve_cluster(Some(&cluster_active)).is_err()
407 {
408 let cluster_notice = 'notice: {
409 if cluster_var.inspect_session_value().is_some() {
410 break 'notice Some(AdapterNotice::DefaultClusterDoesNotExist {
411 name: cluster_active,
412 kind: "session",
413 suggested_action: "Pick an extant cluster with SET CLUSTER = name. Run SHOW CLUSTERS to see available clusters.".into(),
414 });
415 }
416
417 let role_default = catalog.get_role(catalog.active_role_id());
418 let role_cluster = match role_default.vars().get(CLUSTER.name()) {
419 Some(OwnedVarInput::Flat(name)) => Some(name),
420 None => None,
421 Some(v @ OwnedVarInput::SqlSet(_)) => {
423 tracing::warn!(?v, "SqlSet found for cluster Role Default");
424 break 'notice None;
425 }
426 };
427
428 let alter_role = "with `ALTER ROLE <role> SET cluster TO <cluster>;`";
429 match role_cluster {
430 None => Some(AdapterNotice::DefaultClusterDoesNotExist {
432 name: cluster_active,
433 kind: "system",
434 suggested_action: format!(
435 "Set a default cluster for the current role {alter_role}."
436 ),
437 }),
438 Some(_) => Some(AdapterNotice::DefaultClusterDoesNotExist {
440 name: cluster_active,
441 kind: "role",
442 suggested_action: format!(
443 "Change the default cluster for the current role {alter_role}."
444 ),
445 }),
446 }
447 };
448
449 if let Some(notice) = cluster_notice {
450 session.add_notice(notice);
451 }
452 }
453
454 client.enable_frontend_peek_sequencing = ENABLE_FRONTEND_PEEK_SEQUENCING
455 .require(catalog.system_vars())
456 .is_ok();
457
458 Ok(client)
459 }
460
461 pub fn cancel_request(&self, conn_id: ConnectionIdType, secret_key: u32) {
463 self.send(Command::CancelRequest {
464 conn_id,
465 secret_key,
466 });
467 }
468
469 pub async fn support_execute_one(
472 &self,
473 sql: &str,
474 ) -> Result<Pin<Box<dyn Stream<Item = PeekResponseUnary> + Send>>, anyhow::Error> {
475 let conn_id = self.new_conn_id()?;
477 let session = self.new_session(
478 SessionConfig {
479 conn_id,
480 uuid: Uuid::new_v4(),
481 user: SUPPORT_USER.name.clone(),
482 client_ip: None,
483 external_metadata_rx: None,
484 helm_chart_version: None,
485 authenticator_kind: AuthenticatorKind::None,
486 groups: None,
487 },
488 Authenticated,
489 );
490 let mut session_client = self.startup(session).await?;
491
492 let stmts = mz_sql::parse::parse(sql)?;
494 if stmts.len() != 1 {
495 bail!("must supply exactly one query");
496 }
497 let StatementParseResult { ast: stmt, sql } = stmts.into_element();
498
499 const EMPTY_PORTAL: &str = "";
500 session_client.start_transaction(Some(1))?;
501 session_client
502 .declare(EMPTY_PORTAL.into(), stmt, sql.to_string())
503 .await?;
504
505 let execute_result = session_client
506 .execute(EMPTY_PORTAL.into(), futures::future::pending(), None)
507 .await?;
508 match execute_result {
509 (ExecuteResponse::SendingRowsStreaming { mut rows, .. }, _) => {
510 let owning_response_stream = async_stream::stream! {
515 while let Some(rows) = rows.next().await {
516 yield rows;
517 }
518 drop(session_client);
519 };
520 Ok(Box::pin(owning_response_stream))
521 }
522 r => bail!("unsupported response type: {r:?}"),
523 }
524 }
525
526 pub fn metrics(&self) -> &Metrics {
528 &self.metrics
529 }
530
531 pub fn now(&self) -> DateTime<Utc> {
533 to_datetime((self.now)())
534 }
535
536 pub async fn get_webhook_appender(
538 &self,
539 database: String,
540 schema: String,
541 name: String,
542 ) -> Result<AppendWebhookResponse, AppendWebhookError> {
543 let (tx, rx) = oneshot::channel();
544
545 self.send(Command::GetWebhook {
547 database,
548 schema,
549 name,
550 tx,
551 });
552
553 let response = rx
555 .await
556 .map_err(|_| anyhow::anyhow!("failed to receive webhook response"))?;
557
558 response
559 }
560
561 pub async fn get_system_vars(&self) -> SystemVars {
563 let (tx, rx) = oneshot::channel();
564 self.send(Command::GetSystemVars { tx });
565 rx.await.expect("coordinator unexpectedly gone")
566 }
567
568 pub async fn catalog_snapshot_expensive(&self) -> Arc<Catalog> {
574 let (tx, rx) = oneshot::channel();
575 self.send(Command::CatalogSnapshot { tx });
576 let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone");
577 catalog
578 }
579
580 pub async fn update_scoped_system_parameters(
590 &self,
591 overrides: ScopedParameters,
592 prune_scope: Option<ScopedParametersScope>,
593 ) {
594 let (tx, rx) = oneshot::channel();
595 self.send(Command::UpdateScopedSystemParameters {
596 overrides,
597 prune_scope,
598 tx,
599 });
600 let _ = rx.await;
601 }
602
603 pub fn install_scoped_system_parameter_frontend(&self, frontend: Arc<SystemParameterFrontend>) {
608 self.send(Command::InstallScopedSystemParameterFrontend { frontend });
609 }
610
611 #[instrument(level = "debug")]
612 pub(crate) fn send(&self, cmd: Command) {
613 self.inner_cmd_tx
614 .send((OpenTelemetryContext::obtain(), cmd))
615 .expect("coordinator unexpectedly gone");
616 }
617}
618
619pub struct SessionClient {
623 inner: Option<Client>,
627 session: Option<Session>,
630 timeouts: Timeout,
631 segment_client: Option<mz_segment::Client>,
632 environment_id: EnvironmentId,
633 peek_client: PeekClient,
635 pub enable_frontend_peek_sequencing: bool,
640}
641
642impl SessionClient {
643 pub fn parse<'a>(
646 &self,
647 sql: &'a str,
648 ) -> Result<Result<Vec<StatementParseResult<'a>>, ParserStatementError>, String> {
649 match mz_sql::parse::parse_with_limit(sql) {
650 Ok(Err(e)) => {
651 self.track_statement_parse_failure(&e);
652 Ok(Err(e))
653 }
654 r => r,
655 }
656 }
657
658 fn track_statement_parse_failure(&self, parse_error: &ParserStatementError) {
659 let session = self.session.as_ref().expect("session invariant violated");
660 let Some(user_id) = session.user().external_metadata.as_ref().map(|m| m.user_id) else {
661 return;
662 };
663 let Some(segment_client) = &self.segment_client else {
664 return;
665 };
666 let Some(statement_kind) = parse_error.statement else {
667 return;
668 };
669 let Some((action, object_type)) = telemetry::analyze_audited_statement(statement_kind)
670 else {
671 return;
672 };
673 let event_type = StatementFailureType::ParseFailure;
674 let event_name = format!(
675 "{} {} {}",
676 object_type.as_title_case(),
677 action.as_title_case(),
678 event_type.as_title_case(),
679 );
680 segment_client.environment_track(
681 &self.environment_id,
682 event_name,
683 json!({
684 "statement_kind": statement_kind,
685 "error": &parse_error.error,
686 }),
687 EventDetails {
688 user_id: Some(user_id),
689 application_name: Some(session.application_name()),
690 ..Default::default()
691 },
692 );
693 }
694
695 pub async fn get_prepared_statement(
698 &mut self,
699 name: &str,
700 ) -> Result<&PreparedStatement, AdapterError> {
701 let catalog = self.catalog_snapshot("get_prepared_statement").await;
702 Coordinator::verify_prepared_statement(&catalog, self.session(), name)?;
703 Ok(self
704 .session()
705 .get_prepared_statement_unverified(name)
706 .expect("must exist"))
707 }
708
709 pub async fn prepare(
714 &mut self,
715 name: String,
716 stmt: Option<Statement<Raw>>,
717 sql: String,
718 param_types: Vec<Option<SqlScalarType>>,
719 ) -> Result<(), AdapterError> {
720 let catalog = self.catalog_snapshot("prepare").await;
721
722 let mut async_pause = false;
725 (|| {
726 fail::fail_point!("async_prepare", |val| {
727 async_pause = val.map_or(false, |val| val.parse().unwrap_or(false))
728 });
729 })();
730 if async_pause {
731 tokio::time::sleep(Duration::from_secs(1)).await;
732 };
733
734 let desc = Coordinator::describe(&catalog, self.session(), stmt.clone(), param_types)?;
735 let now = self.now();
736 let state_revision = StateRevision {
737 catalog_revision: catalog.transient_revision(),
738 session_state_revision: self.session().state_revision(),
739 };
740 self.session()
741 .set_prepared_statement(name, stmt, sql, desc, state_revision, now);
742 Ok(())
743 }
744
745 #[mz_ore::instrument(level = "debug")]
747 pub async fn declare(
748 &mut self,
749 name: String,
750 stmt: Statement<Raw>,
751 sql: String,
752 ) -> Result<(), AdapterError> {
753 let catalog = self.catalog_snapshot("declare").await;
754 let param_types = vec![];
755 let desc =
756 Coordinator::describe(&catalog, self.session(), Some(stmt.clone()), param_types)?;
757 let params = vec![];
758 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
759 let now = self.now();
760 let logging = self.session().mint_logging(sql, Some(&stmt), now);
761 let state_revision = StateRevision {
762 catalog_revision: catalog.transient_revision(),
763 session_state_revision: self.session().state_revision(),
764 };
765 self.session().set_portal(
766 name,
767 desc,
768 Some(stmt),
769 logging,
770 params,
771 result_formats,
772 state_revision,
773 )?;
774 Ok(())
775 }
776
777 #[mz_ore::instrument(level = "debug")]
784 pub async fn execute(
785 &mut self,
786 portal_name: String,
787 cancel_future: impl Future<Output = std::io::Error> + Send,
788 outer_ctx_extra: Option<ExecuteContextGuard>,
789 ) -> Result<(ExecuteResponse, Instant), AdapterError> {
790 let execute_started = Instant::now();
791
792 let mut outer_ctx_extra = outer_ctx_extra;
793
794 let portal_name = self
807 .unroll_sql_execute(portal_name, &mut outer_ctx_extra)
808 .await?;
809
810 let peek_result = self
814 .try_frontend_peek(&portal_name, &mut outer_ctx_extra)
815 .await?;
816 if let Some(resp) = peek_result {
817 debug!("frontend peek succeeded");
818 return Ok((resp, execute_started));
821 } else {
822 debug!("frontend peek did not happen, falling back to `Command::Execute`");
823 }
828
829 let response = self
830 .send_with_cancel(
831 |tx, session| Command::Execute {
832 portal_name,
833 session,
834 tx,
835 outer_ctx_extra,
836 },
837 cancel_future,
838 )
839 .await?;
840 Ok((response, execute_started))
841 }
842
843 async fn unroll_sql_execute(
856 &mut self,
857 portal_name: String,
858 outer_ctx_extra: &mut Option<ExecuteContextGuard>,
859 ) -> Result<String, AdapterError> {
860 let (stmt, params, outer_logging, outer_lifecycle_timestamps) = {
861 let session = self.session.as_ref().expect("SessionClient invariant");
862 let portal = match session.get_portal_unverified(&portal_name) {
863 Some(p) => p,
864 None => return Ok(portal_name),
867 };
868 match &portal.stmt {
869 Some(stmt) => (
870 Arc::clone(stmt),
871 portal.parameters.clone(),
872 Arc::clone(&portal.logging),
873 portal.lifecycle_timestamps.clone(),
874 ),
875 None => return Ok(portal_name),
876 }
877 };
878
879 if !matches!(&*stmt, Statement::Execute(_)) {
882 return Ok(portal_name);
883 }
884
885 let catalog = self.catalog_snapshot("unroll_sql_execute").await;
886
887 {
891 let session = self.session.as_mut().expect("SessionClient invariant");
892 Coordinator::verify_portal(&catalog, session, &portal_name)?;
893 }
894
895 {
899 let session = self.session.as_ref().expect("SessionClient invariant");
900 session
901 .metrics()
902 .query_total(&[
903 metrics::session_type_label_value(session.user()),
904 metrics::statement_type_label_value(&stmt),
905 ])
906 .inc();
907 }
908
909 let began_outer_logging = outer_ctx_extra.is_none();
919 let logging_id: Option<crate::statement_logging::StatementLoggingId> =
920 if began_outer_logging {
921 let session = self.session.as_mut().expect("SessionClient invariant");
922 let result = self
923 .peek_client
924 .statement_logging_frontend
925 .begin_statement_execution(
926 session,
927 ¶ms,
928 &outer_logging,
929 catalog.system_config(),
930 outer_lifecycle_timestamps,
931 );
932 if let Some((id, began_execution, mseh_update, prepared_statement)) = result {
933 self.peek_client.log_began_execution(
934 began_execution,
935 mseh_update,
936 prepared_statement,
937 );
938 Some(id)
939 } else {
940 None
941 }
942 } else {
943 None
944 };
945
946 let new_portal_name = match self.install_inner_portal_for_execute(&catalog, &stmt, ¶ms)
947 {
948 Ok(name) => name,
949 Err(err) => {
950 if let Some(id) = logging_id {
951 self.peek_client.log_ended_execution(
952 id,
953 StatementEndedExecutionReason::Errored {
954 error: err.to_string(),
955 },
956 );
957 }
958 return Err(err);
959 }
960 };
961
962 if began_outer_logging {
974 let (dummy_tx, _dummy_rx) = mpsc::unbounded_channel();
982 *outer_ctx_extra = Some(ExecuteContextGuard::new(logging_id, dummy_tx));
983 }
984
985 Ok(new_portal_name)
986 }
987
988 fn install_inner_portal_for_execute(
997 &mut self,
998 catalog: &Arc<Catalog>,
999 stmt: &Arc<Statement<Raw>>,
1000 params: &mz_sql::plan::Params,
1001 ) -> Result<String, AdapterError> {
1002 use mz_sql::plan::Plan;
1003
1004 let execute_plan = {
1005 let session = self.session.as_mut().expect("SessionClient invariant");
1006 let conn_catalog = catalog.for_session(session);
1007 let (resolved_stmt, resolved_ids) =
1008 mz_sql::names::resolve(&conn_catalog, (**stmt).clone())?;
1009 let pcx = session.pcx();
1010 let (plan, _sql_impl_ids) = mz_sql::plan::plan(
1011 Some(pcx),
1012 &conn_catalog,
1013 resolved_stmt,
1014 params,
1015 &resolved_ids,
1016 )?;
1017 match plan {
1018 Plan::Execute(plan) => plan,
1019 other => {
1020 return Err(AdapterError::Internal(format!(
1024 "planning Statement::Execute yielded unexpected plan: {:?}",
1025 mz_sql::plan::PlanKind::from(&other),
1026 )));
1027 }
1028 }
1029 };
1030
1031 let session = self.session.as_mut().expect("SessionClient invariant");
1037 Coordinator::verify_prepared_statement(catalog, session, &execute_plan.name)?;
1038 let ps = session
1039 .get_prepared_statement_unverified(&execute_plan.name)
1040 .expect("verified above");
1041 let inner_stmt = ps.stmt().cloned();
1042 let inner_desc = ps.desc().clone();
1043 let state_revision = ps.state_revision;
1044 let inner_logging = Arc::clone(ps.logging());
1045
1046 if let Some(inner) = inner_stmt.as_ref() {
1051 if matches!(inner, Statement::Execute(_)) {
1052 return Err(AdapterError::Internal(format!(
1053 "nested EXECUTE: prepared statement {} resolves to another EXECUTE; \
1054 parser should reject `PREPARE ... AS EXECUTE ...`",
1055 execute_plan.name.quoted(),
1056 )));
1057 }
1058 }
1059
1060 session.create_new_portal(
1061 inner_stmt,
1062 inner_logging,
1063 inner_desc,
1064 execute_plan.params,
1065 Vec::new(),
1066 state_revision,
1067 )
1068 }
1069
1070 fn now(&self) -> EpochMillis {
1071 (self.inner().now)()
1072 }
1073
1074 fn now_datetime(&self) -> DateTime<Utc> {
1075 to_datetime(self.now())
1076 }
1077
1078 pub fn start_transaction(&mut self, implicit: Option<usize>) -> Result<(), AdapterError> {
1084 let now = self.now_datetime();
1085 let session = self.session.as_mut().expect("session invariant violated");
1086 let result = match implicit {
1087 None => session.start_transaction(now, None, None),
1088 Some(stmts) => {
1089 session.start_transaction_implicit(now, stmts);
1090 Ok(())
1091 }
1092 };
1093 result
1094 }
1095
1096 #[instrument(level = "debug")]
1099 pub async fn end_transaction(
1100 &mut self,
1101 action: EndTransactionAction,
1102 ) -> Result<ExecuteResponse, AdapterError> {
1103 let res = self
1104 .send(|tx, session| Command::Commit {
1105 action,
1106 session,
1107 tx,
1108 })
1109 .await;
1110 let _ = self.session().clear_transaction();
1114 res
1115 }
1116
1117 pub fn fail_transaction(&mut self) {
1119 let session = self.session.take().expect("session invariant violated");
1120 let session = session.fail_transaction();
1121 self.session = Some(session);
1122 }
1123
1124 #[instrument(level = "debug")]
1128 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
1129 self.peek_client.catalog_snapshot(context).await
1130 }
1131
1132 pub async fn statement_arrival_logging_enabled(&mut self) -> bool {
1134 let catalog = self.catalog_snapshot("statement_arrival_logging").await;
1135 catalog.system_config().enable_statement_arrival_logging()
1136 }
1137
1138 pub async fn dump_catalog(&mut self) -> Result<CatalogDump, AdapterError> {
1143 let catalog = self.catalog_snapshot("dump_catalog").await;
1144 catalog.dump().map_err(AdapterError::from)
1145 }
1146
1147 pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> {
1153 let catalog = self.catalog_snapshot("check_catalog").await;
1154 catalog.check_consistency()
1155 }
1156
1157 pub async fn check_coordinator(&self) -> Result<(), serde_json::Value> {
1163 self.send_without_session(|tx| Command::CheckConsistency { tx })
1164 .await
1165 .map_err(|inconsistencies| {
1166 serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1167 serde_json::Value::String("failed to serialize inconsistencies".to_string())
1168 })
1169 })
1170 }
1171
1172 pub async fn dump_coordinator_state(&self) -> Result<serde_json::Value, anyhow::Error> {
1173 self.send_without_session(|tx| Command::Dump { tx }).await
1174 }
1175
1176 pub fn retire_execute(
1179 &self,
1180 guard: ExecuteContextGuard,
1181 reason: StatementEndedExecutionReason,
1182 ) {
1183 if !guard.is_trivial() {
1184 let data = guard.defuse();
1185 let cmd = Command::RetireExecute { data, reason };
1186 self.inner().send(cmd);
1187 }
1188 }
1189
1190 pub async fn start_copy_from_stdin(
1196 &mut self,
1197 target_id: CatalogItemId,
1198 target_name: String,
1199 columns: Vec<ColumnIndex>,
1200 row_desc: mz_repr::RelationDesc,
1201 params: mz_pgcopy::CopyFormatParams<'static>,
1202 ) -> Result<CopyFromStdinWriter, AdapterError> {
1203 self.send(|tx, session| Command::StartCopyFromStdin {
1204 target_id,
1205 target_name,
1206 columns,
1207 row_desc,
1208 params,
1209 session,
1210 tx,
1211 })
1212 .await
1213 }
1214
1215 pub fn stage_copy_from_stdin_batches(
1220 &mut self,
1221 target_id: CatalogItemId,
1222 batches: Vec<mz_persist_client::batch::ProtoBatch>,
1223 ) -> Result<(), AdapterError> {
1224 use crate::session::{TransactionOps, WriteOp};
1225 use mz_storage_client::client::TableData;
1226
1227 self.session()
1228 .add_transaction_ops(TransactionOps::Writes(vec![WriteOp {
1229 id: target_id,
1230 rows: TableData::Batches(batches.into()),
1231 }]))?;
1232 Ok(())
1233 }
1234
1235 pub async fn get_system_vars(&self) -> SystemVars {
1237 self.inner().get_system_vars().await
1238 }
1239
1240 pub async fn set_system_vars(
1242 &mut self,
1243 vars: BTreeMap<String, String>,
1244 ) -> Result<(), AdapterError> {
1245 let conn_id = self.session().conn_id().clone();
1246 self.send_without_session(|tx| Command::SetSystemVars { vars, conn_id, tx })
1247 .await
1248 }
1249
1250 pub async fn inject_audit_events(
1255 &mut self,
1256 events: Vec<crate::catalog::InjectedAuditEvent>,
1257 ) -> Result<(), AdapterError> {
1258 let conn_id = self.session().conn_id().clone();
1259 self.send_without_session(|tx| Command::InjectAuditEvents {
1260 events,
1261 conn_id,
1262 tx,
1263 })
1264 .await
1265 }
1266
1267 pub async fn terminate(&mut self) {
1269 let conn_id = self.session().conn_id().clone();
1270 let res = self
1271 .send_without_session(|tx| Command::Terminate {
1272 conn_id,
1273 tx: Some(tx),
1274 })
1275 .await;
1276 if let Err(e) = res {
1277 error!("Unable to terminate session: {e:?}");
1279 }
1280 self.inner = None;
1282 }
1283
1284 pub fn session(&mut self) -> &mut Session {
1286 self.session.as_mut().expect("session invariant violated")
1287 }
1288
1289 pub fn inner(&self) -> &Client {
1291 self.inner.as_ref().expect("inner invariant violated")
1292 }
1293
1294 async fn send_without_session<T, F>(&self, f: F) -> T
1295 where
1296 F: FnOnce(oneshot::Sender<T>) -> Command,
1297 {
1298 let (tx, rx) = oneshot::channel();
1299 self.inner().send(f(tx));
1300 rx.await.expect("sender dropped")
1301 }
1302
1303 #[instrument(level = "debug")]
1304 async fn send<T, F>(&mut self, f: F) -> Result<T, AdapterError>
1305 where
1306 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1307 {
1308 self.send_with_cancel(f, futures::future::pending()).await
1309 }
1310
1311 #[instrument(level = "debug")]
1315 async fn send_with_cancel<T, F>(
1316 &mut self,
1317 f: F,
1318 cancel_future: impl Future<Output = std::io::Error> + Send,
1319 ) -> Result<T, AdapterError>
1320 where
1321 F: FnOnce(oneshot::Sender<Response<T>>, Session) -> Command,
1322 {
1323 let session = self.session.take().expect("session invariant violated");
1324 let mut typ = None;
1325 let application_name = session.application_name();
1326 let name_hint = ApplicationNameHint::from_str(application_name);
1327 let conn_id = session.conn_id().clone();
1328 let (tx, rx) = oneshot::channel();
1329
1330 let Self {
1333 inner: inner_client,
1334 session: client_session,
1335 ..
1336 } = self;
1337
1338 let inner_client = inner_client.as_ref().expect("inner invariant violated");
1341
1342 let mut guarded_rx = rx.with_guard(|response: Response<_>| {
1348 *client_session = Some(response.session);
1349 });
1350
1351 inner_client.send({
1352 let cmd = f(tx, session);
1353 match cmd {
1357 Command::Execute { .. } => typ = Some("execute"),
1358 Command::GetWebhook { .. } => typ = Some("webhook"),
1359 Command::StartCopyFromStdin { .. }
1360 | Command::Startup { .. }
1361 | Command::AuthenticatePassword { .. }
1362 | Command::AuthenticateGetSASLChallenge { .. }
1363 | Command::AuthenticateVerifySASLProof { .. }
1364 | Command::CheckRoleCanLogin { .. }
1365 | Command::CatalogSnapshot { .. }
1366 | Command::Commit { .. }
1367 | Command::CancelRequest { .. }
1368 | Command::PrivilegedCancelRequest { .. }
1369 | Command::GetSystemVars { .. }
1370 | Command::SetSystemVars { .. }
1371 | Command::UpdateScopedSystemParameters { .. }
1372 | Command::InstallScopedSystemParameterFrontend { .. }
1373 | Command::Terminate { .. }
1374 | Command::RetireExecute { .. }
1375 | Command::CheckConsistency { .. }
1376 | Command::Dump { .. }
1377 | Command::GetComputeInstanceClient { .. }
1378 | Command::GetOracle { .. }
1379 | Command::DetermineRealTimeRecentTimestamp { .. }
1380 | Command::GetTransactionReadHoldsBundle { .. }
1381 | Command::StoreTransactionReadHolds { .. }
1382 | Command::ExecuteSlowPathPeek { .. }
1383 | Command::ExecuteSubscribe { .. }
1384 | Command::CopyToPreflight { .. }
1385 | Command::ExecuteCopyTo { .. }
1386 | Command::ExecuteSideEffectingFunc { .. }
1387 | Command::LookupConnection { .. }
1388 | Command::RegisterFrontendPeek { .. }
1389 | Command::UnregisterFrontendPeek { .. }
1390 | Command::ExplainTimestamp { .. }
1391 | Command::FrontendStatementLogging(..)
1392 | Command::InjectAuditEvents { .. } => {}
1393 };
1394 cmd
1395 });
1396
1397 let mut cancel_future = pin::pin!(cancel_future);
1398 let mut cancelled = false;
1399 loop {
1400 tokio::select! {
1401 res = &mut guarded_rx => {
1402 drop(guarded_rx);
1404
1405 let res = res.expect("sender dropped");
1406 let status = res.result.is_ok().then_some("success").unwrap_or("error");
1407 if let Err(err) = res.result.as_ref() {
1408 if name_hint.should_trace_errors() {
1409 tracing::warn!(?err, ?name_hint, "adapter response error");
1410 }
1411 }
1412
1413 if let Some(typ) = typ {
1414 inner_client
1415 .metrics
1416 .commands
1417 .with_label_values(&[typ, status, name_hint.as_str()])
1418 .inc();
1419 }
1420 *client_session = Some(res.session);
1421 return res.result;
1422 },
1423 _err = &mut cancel_future, if !cancelled => {
1424 cancelled = true;
1425 inner_client.send(Command::PrivilegedCancelRequest {
1426 conn_id: conn_id.clone(),
1427 });
1428 }
1429 };
1430 }
1431 }
1432
1433 pub fn add_idle_in_transaction_session_timeout(&mut self) {
1434 let session = self.session();
1435 let timeout_dur = session.vars().idle_in_transaction_session_timeout();
1436 if !timeout_dur.is_zero() {
1437 let timeout_dur = timeout_dur.clone();
1438 if let Some(txn) = session.transaction().inner() {
1439 let txn_id = txn.id.clone();
1440 let timeout = TimeoutType::IdleInTransactionSession(txn_id);
1441 self.timeouts.add_timeout(timeout, timeout_dur);
1442 }
1443 }
1444 }
1445
1446 pub fn remove_idle_in_transaction_session_timeout(&mut self) {
1447 let session = self.session();
1448 if let Some(txn) = session.transaction().inner() {
1449 let txn_id = txn.id.clone();
1450 self.timeouts
1451 .remove_timeout(&TimeoutType::IdleInTransactionSession(txn_id));
1452 }
1453 }
1454
1455 pub async fn recv_timeout(&mut self) -> Option<TimeoutType> {
1462 self.timeouts.recv().await
1463 }
1464
1465 pub(crate) async fn try_frontend_peek(
1473 &mut self,
1474 portal_name: &str,
1475 outer_ctx_extra: &mut Option<ExecuteContextGuard>,
1476 ) -> Result<Option<ExecuteResponse>, AdapterError> {
1477 if self.enable_frontend_peek_sequencing {
1478 let session = self.session.as_mut().expect("SessionClient invariant");
1479 self.peek_client
1480 .try_frontend_peek(portal_name, session, outer_ctx_extra)
1481 .await
1482 } else {
1483 Ok(None)
1484 }
1485 }
1486}
1487
1488impl Drop for SessionClient {
1489 fn drop(&mut self) {
1490 if let Some(session) = self.session.take() {
1494 if let Some(inner) = &self.inner {
1497 inner.send(Command::Terminate {
1498 conn_id: session.conn_id().clone(),
1499 tx: None,
1500 })
1501 }
1502 }
1503 }
1504}
1505
1506pub fn redact_sql_for_logging(sql: &str) -> String {
1513 match mz_sql_parser::parser::parse_statements_with_limit(sql) {
1514 Ok(Ok(stmts)) => stmts
1515 .into_iter()
1516 .map(|stmt| stmt.ast.to_ast_string_redacted())
1517 .join("; "),
1518 Ok(Err(_)) => format!("<unparseable ({} bytes)>", sql.len()),
1519 Err(_) => format!("<too large ({} bytes)>", sql.len()),
1520 }
1521}
1522
1523#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
1524pub enum TimeoutType {
1525 IdleInTransactionSession(TransactionId),
1526}
1527
1528impl Display for TimeoutType {
1529 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1530 match self {
1531 TimeoutType::IdleInTransactionSession(txn_id) => {
1532 writeln!(f, "Idle in transaction session for transaction '{txn_id}'")
1533 }
1534 }
1535 }
1536}
1537
1538impl From<TimeoutType> for AdapterError {
1539 fn from(timeout: TimeoutType) -> Self {
1540 match timeout {
1541 TimeoutType::IdleInTransactionSession(_) => {
1542 AdapterError::IdleInTransactionSessionTimeout
1543 }
1544 }
1545 }
1546}
1547
1548struct Timeout {
1549 tx: mpsc::UnboundedSender<TimeoutType>,
1550 rx: mpsc::UnboundedReceiver<TimeoutType>,
1551 active_timeouts: BTreeMap<TimeoutType, AbortOnDropHandle<()>>,
1552}
1553
1554impl Timeout {
1555 fn new() -> Self {
1556 let (tx, rx) = mpsc::unbounded_channel();
1557 Timeout {
1558 tx,
1559 rx,
1560 active_timeouts: BTreeMap::new(),
1561 }
1562 }
1563
1564 async fn recv(&mut self) -> Option<TimeoutType> {
1573 self.rx.recv().await
1574 }
1575
1576 fn add_timeout(&mut self, timeout: TimeoutType, duration: Duration) {
1577 let tx = self.tx.clone();
1578 let timeout_key = timeout.clone();
1579 let handle = mz_ore::task::spawn(|| format!("{timeout_key}"), async move {
1580 tokio::time::sleep(duration).await;
1581 let _ = tx.send(timeout);
1582 })
1583 .abort_on_drop();
1584 self.active_timeouts.insert(timeout_key, handle);
1585 }
1586
1587 fn remove_timeout(&mut self, timeout: &TimeoutType) {
1588 self.active_timeouts.remove(timeout);
1589
1590 let mut timeouts = Vec::new();
1592 while let Ok(pending_timeout) = self.rx.try_recv() {
1593 if timeout != &pending_timeout {
1594 timeouts.push(pending_timeout);
1595 }
1596 }
1597 for pending_timeout in timeouts {
1598 self.tx.send(pending_timeout).expect("rx is in this struct");
1599 }
1600 }
1601}
1602
1603#[derive(Derivative)]
1607#[derivative(Debug)]
1608pub struct RecordFirstRowStream {
1609 #[derivative(Debug = "ignore")]
1611 pub rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
1612 pub execute_started: Instant,
1614 pub time_to_first_row_seconds: Histogram,
1617 pub saw_rows: bool,
1619 pub recorded_first_row_instant: Option<Instant>,
1621 pub no_more_rows: bool,
1623 pub metric_recorded: bool,
1625}
1626
1627impl RecordFirstRowStream {
1628 pub fn new(
1630 rows: Box<dyn Stream<Item = PeekResponseUnary> + Unpin + Send + Sync>,
1631 execute_started: Instant,
1632 client: &SessionClient,
1633 instance_id: Option<ComputeInstanceId>,
1634 strategy: Option<StatementExecutionStrategy>,
1635 ) -> Self {
1636 let histogram = Self::histogram(client, instance_id, strategy);
1637 Self {
1638 rows,
1639 execute_started,
1640 time_to_first_row_seconds: histogram,
1641 saw_rows: false,
1642 recorded_first_row_instant: None,
1643 no_more_rows: false,
1644 metric_recorded: false,
1645 }
1646 }
1647
1648 fn histogram(
1649 client: &SessionClient,
1650 instance_id: Option<ComputeInstanceId>,
1651 strategy: Option<StatementExecutionStrategy>,
1652 ) -> Histogram {
1653 let session = client.session.as_ref().expect("session invariant");
1654 let isolation_level = *session.vars().transaction_isolation();
1655 let name_hint = ApplicationNameHint::from_str(session.application_name());
1656 let instance = match instance_id {
1657 Some(i) => Cow::Owned(i.to_string()),
1658 None => Cow::Borrowed("none"),
1659 };
1660 let strategy = match strategy {
1661 Some(s) => s.name(),
1662 None => "none",
1663 };
1664
1665 client
1666 .inner()
1667 .metrics()
1668 .time_to_first_row_seconds
1669 .with_label_values(&[
1670 instance.as_ref(),
1671 isolation_level.as_variant_str(),
1672 strategy,
1673 name_hint.as_str(),
1674 ])
1675 }
1676
1677 pub fn record(
1680 execute_started: Instant,
1681 client: &SessionClient,
1682 instance_id: Option<ComputeInstanceId>,
1683 strategy: Option<StatementExecutionStrategy>,
1684 ) {
1685 Self::histogram(client, instance_id, strategy)
1686 .observe(execute_started.elapsed().as_secs_f64());
1687 }
1688
1689 pub async fn recv(&mut self) -> Option<PeekResponseUnary> {
1690 let msg = self.rows.next().await;
1691 if !self.saw_rows && matches!(msg, Some(PeekResponseUnary::Rows(_))) {
1692 self.saw_rows = true;
1693 self.time_to_first_row_seconds
1694 .observe(self.execute_started.elapsed().as_secs_f64());
1695 self.recorded_first_row_instant = Some(Instant::now());
1696 }
1697 if msg.is_none() {
1698 self.no_more_rows = true;
1699 }
1700 msg
1701 }
1702}