1use itertools::Itertools;
14use mz_adapter_types::connection::ConnectionId;
15use mz_ore::now::EpochMillis;
16use mz_repr::{Diff, GlobalId, SqlScalarType};
17use mz_sql::names::{Aug, ResolvedIds};
18use mz_sql::plan::{Params, StatementDesc};
19use mz_sql::session::metadata::SessionMetadata;
20use mz_sql_parser::ast::{Raw, Statement};
21
22use crate::active_compute_sink::{ActiveComputeSink, ActiveComputeSinkRetireReason};
23use crate::catalog::Catalog;
24use crate::coord::Coordinator;
25use crate::coord::appends::{BuiltinTableAppendCompletion, BuiltinTableAppendNotify};
26use crate::session::{Session, StateRevision, TransactionStatus};
27use crate::util::describe;
28use crate::{AdapterError, ExecuteContext, ExecuteResponse, metrics};
29
30impl Coordinator {
31 pub(crate) fn plan_statement(
35 &self,
36 session: &Session,
37 stmt: mz_sql::ast::Statement<Aug>,
38 params: &mz_sql::plan::Params,
39 resolved_ids: &ResolvedIds,
40 ) -> Result<(mz_sql::plan::Plan, ResolvedIds), AdapterError> {
41 let pcx = session.pcx();
42 let catalog = self.catalog().for_session(session);
43 let (plan, sql_impl_ids) =
44 mz_sql::plan::plan(Some(pcx), &catalog, stmt, params, resolved_ids)?;
45 Ok((plan, sql_impl_ids))
46 }
47
48 pub(crate) fn declare(
49 &self,
50 mut ctx: ExecuteContext,
51 name: String,
52 stmt: Statement<Raw>,
53 sql: String,
54 params: Params,
55 ) {
56 let catalog = self.owned_catalog();
57 let now = self.now();
58 mz_ore::task::spawn(|| "coord::declare", async move {
59 let result =
60 Self::declare_inner(ctx.session_mut(), &catalog, name, stmt, sql, params, now)
61 .map(|()| ExecuteResponse::DeclaredCursor);
62 ctx.retire(result);
63 });
64 }
65
66 fn declare_inner(
67 session: &mut Session,
68 catalog: &Catalog,
69 name: String,
70 stmt: Statement<Raw>,
71 sql: String,
72 params: Params,
73 now: EpochMillis,
74 ) -> Result<(), AdapterError> {
75 let param_types = params
76 .execute_types
77 .iter()
78 .map(|ty| Some(ty.clone()))
79 .collect::<Vec<_>>();
80 let desc = describe(catalog, stmt.clone(), ¶m_types, session)?;
81 session.retain_description(stmt.clone(), param_types, desc.clone());
82 let params = params
83 .datums
84 .into_iter()
85 .zip_eq(params.execute_types)
86 .collect();
87 let result_formats = vec![mz_pgwire_common::Format::Text; desc.arity()];
88 let logging = session.mint_logging(sql, Some(&stmt), now);
89 let state_revision = StateRevision {
90 catalog_revision: catalog.transient_revision(),
91 session_state_revision: session.state_revision(),
92 };
93 session.set_portal(
94 name,
95 desc,
96 Some(stmt),
97 logging,
98 params,
99 result_formats,
100 state_revision,
101 )?;
102 Ok(())
103 }
104
105 #[mz_ore::instrument(level = "debug")]
106 pub(crate) fn describe(
107 catalog: &Catalog,
108 session: &mut Session,
109 stmt: Option<Statement<Raw>>,
110 param_types: Vec<Option<SqlScalarType>>,
111 ) -> Result<StatementDesc, AdapterError> {
112 if let Some(stmt) = stmt {
113 let desc = describe(catalog, stmt.clone(), ¶m_types, session)?;
114 session.retain_description(stmt, param_types, desc.clone());
115 Ok(desc)
116 } else {
117 Ok(StatementDesc::new(None))
118 }
119 }
120
121 pub(crate) fn verify_prepared_statement(
125 catalog: &Catalog,
126 session: &mut Session,
127 name: &str,
128 ) -> Result<(), AdapterError> {
129 let ps = match session.get_prepared_statement_unverified(name) {
130 Some(ps) => ps,
131 None => return Err(AdapterError::UnknownPreparedStatement(name.to_string())),
132 };
133 let current_state_revision = StateRevision {
134 catalog_revision: catalog.transient_revision(),
135 session_state_revision: session.state_revision(),
136 };
137 if ps.state_revision == current_state_revision {
138 return Ok(());
139 }
140 let stmt = ps.stmt().cloned();
141 let desc = ps.desc().clone();
142 let revision = ps.state_revision;
143 if let Some(new_revision) =
144 Self::verify_statement_revision(catalog, session, stmt.as_ref(), &desc, revision)?
145 {
146 let ps = session
147 .get_prepared_statement_mut_unverified(name)
148 .expect("known to exist");
149 ps.state_revision = new_revision;
150 }
151
152 Ok(())
153 }
154
155 pub(crate) fn verify_portal(
157 catalog: &Catalog,
158 session: &mut Session,
159 name: &str,
160 ) -> Result<(), AdapterError> {
161 let portal = match session.get_portal_unverified(name) {
162 Some(portal) => portal,
163 None => return Err(AdapterError::UnknownCursor(name.to_string())),
164 };
165 let current_state_revision = StateRevision {
166 catalog_revision: catalog.transient_revision(),
167 session_state_revision: session.state_revision(),
168 };
169 if portal.state_revision == current_state_revision {
170 return Ok(());
171 }
172 let stmt = portal.stmt.as_deref().cloned();
173 let desc = portal.desc.clone();
174 let revision = portal.state_revision;
175 if let Some(new_revision) =
176 Self::verify_statement_revision(catalog, session, stmt.as_ref(), &desc, revision)?
177 {
178 let portal = session
179 .get_portal_unverified_mut(name)
180 .expect("known to exist");
181 *portal.state_revision = new_revision;
182 }
183 Ok(())
184 }
185
186 fn verify_statement_revision(
191 catalog: &Catalog,
192 session: &mut Session,
193 stmt: Option<&Statement<Raw>>,
194 desc: &StatementDesc,
195 old_state_revision: StateRevision,
196 ) -> Result<Option<StateRevision>, AdapterError> {
197 let current_state_revision = StateRevision {
198 catalog_revision: catalog.transient_revision(),
199 session_state_revision: session.state_revision(),
200 };
201 if old_state_revision != current_state_revision {
202 let current_desc = Self::describe(
203 catalog,
204 session,
205 stmt.cloned(),
206 desc.param_types.iter().cloned().map(Some).collect(),
207 )?;
208 if ¤t_desc != desc {
209 Err(AdapterError::ChangedPlan(
210 "cached plan must not change result type".to_string(),
211 ))
212 } else {
213 Ok(Some(current_state_revision))
214 }
215 } else {
216 Ok(None)
217 }
218 }
219
220 pub(crate) async fn clear_transaction(
226 &mut self,
227 session: &mut Session,
228 ) -> (TransactionStatus, BuiltinTableAppendCompletion) {
229 let retire_notify = self.clear_connection(session.conn_id()).await;
237 (session.clear_transaction(), retire_notify)
238 }
239
240 pub(crate) async fn clear_connection(
245 &mut self,
246 conn_id: &ConnectionId,
247 ) -> BuiltinTableAppendCompletion {
248 self.connection_cancel_watches.remove(conn_id);
249 let retire_notify = self
250 .retire_compute_sinks_for_conn(conn_id, ActiveComputeSinkRetireReason::Finished)
251 .await;
252
253 if let Some(txn_reads) = self.txn_read_holds.remove(conn_id) {
255 tracing::debug!(?txn_reads, "releasing txn read holds");
256
257 drop(txn_reads);
260 }
261
262 if let Some(guard) = self
263 .active_conns
264 .get_mut(conn_id)
265 .expect("must exist for active session")
266 .deferred_lock
267 .take()
268 {
269 self.release_ddl_lock(guard);
270 }
271
272 retire_notify
273 }
274
275 pub(crate) fn add_active_compute_sink(
280 &mut self,
281 id: GlobalId,
282 active_sink: ActiveComputeSink,
283 ) -> BuiltinTableAppendNotify {
284 let session_type = match active_sink.connection_id() {
285 Some(conn_id) => {
286 let session_type =
287 metrics::session_type_label_value(self.active_conns()[conn_id].user());
288 self.active_conns
289 .get_mut(conn_id)
290 .expect("must exist for active sessions")
291 .drop_sinks
292 .insert(id);
293 session_type
294 }
295 None => "system",
296 };
297
298 let ret_fut: BuiltinTableAppendNotify = match &active_sink {
299 ActiveComputeSink::Subscribe(active_subscribe) => {
300 match active_subscribe.introspection_session_uuid() {
301 None => {
308 self.metrics
309 .active_internal_subscribes
310 .with_label_values(&[session_type])
311 .inc();
312
313 Box::pin(std::future::ready(()))
314 }
315 Some(session_uuid) => {
316 let update = self.catalog().state().pack_subscribe_update(
317 id,
318 active_subscribe,
319 session_uuid,
320 Diff::ONE,
321 );
322 let update = self.catalog().state().resolve_builtin_table_update(update);
323
324 self.metrics
325 .active_subscribes
326 .with_label_values(&[session_type])
327 .inc();
328
329 self.builtin_table_update().defer(vec![update])
335 }
336 }
337 }
338 ActiveComputeSink::CopyTo(_) => {
339 self.metrics
340 .active_copy_tos
341 .with_label_values(&[session_type])
342 .inc();
343 Box::pin(std::future::ready(()))
344 }
345 };
346 self.active_compute_sinks.insert(id, active_sink);
347 ret_fut
348 }
349
350 #[mz_ore::instrument(level = "debug")]
364 pub(crate) async fn remove_active_compute_sink(
365 &mut self,
366 id: GlobalId,
367 ) -> Option<(ActiveComputeSink, BuiltinTableAppendNotify)> {
368 if let Some(sink) = self.active_compute_sinks.remove(&id) {
369 let session_type = match sink.connection_id() {
370 Some(conn_id) => {
371 let session_type =
372 metrics::session_type_label_value(self.active_conns()[conn_id].user());
373 self.active_conns
374 .get_mut(conn_id)
375 .expect("must exist for active compute sink")
376 .drop_sinks
377 .remove(&id);
378 session_type
379 }
380 None => "system",
381 };
382
383 let write_notify: BuiltinTableAppendNotify = match &sink {
384 ActiveComputeSink::Subscribe(active_subscribe) => {
385 match active_subscribe.introspection_session_uuid() {
386 None => {
390 self.metrics
391 .active_internal_subscribes
392 .with_label_values(&[session_type])
393 .dec();
394
395 Box::pin(std::future::ready(()))
396 }
397 Some(session_uuid) => {
398 let update = self.catalog().state().pack_subscribe_update(
399 id,
400 active_subscribe,
401 session_uuid,
402 Diff::MINUS_ONE,
403 );
404 let update =
405 self.catalog().state().resolve_builtin_table_update(update);
406
407 self.metrics
408 .active_subscribes
409 .with_label_values(&[session_type])
410 .dec();
411
412 self.builtin_table_update().defer(vec![update])
418 }
419 }
420 }
421 ActiveComputeSink::CopyTo(_) => {
422 self.metrics
423 .active_copy_tos
424 .with_label_values(&[session_type])
425 .dec();
426
427 Box::pin(std::future::ready(()))
428 }
429 };
430 Some((sink, write_notify))
431 } else {
432 None
433 }
434 }
435}