1use std::collections::{BTreeMap, BTreeSet};
11
12use maplit::btreemap;
13use mz_catalog::memory::error::ErrorKind;
14use mz_catalog::memory::objects::{CatalogItem, View};
15use mz_expr::CollectionPlan;
16use mz_ore::instrument;
17use mz_repr::explain::{ExprHumanizerExt, TransientItem};
18use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
19use mz_repr::{Datum, RelationDesc, Row};
20use mz_sql::ast::ExplainStage;
21use mz_sql::catalog::CatalogError;
22use mz_sql::names::ResolvedIds;
23use mz_sql::plan::{self};
24use mz_sql::session::metadata::SessionMetadata;
25use tracing::Span;
26
27use crate::command::ExecuteResponse;
28use crate::coord::sequencer::inner::return_if_err;
29use crate::coord::{
30 Coordinator, CreateViewExplain, CreateViewFinish, CreateViewOptimize, CreateViewStage,
31 ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged,
32 infer_sql_type_for_catalog,
33};
34use crate::error::AdapterError;
35use crate::explain::explain_plan;
36use crate::explain::optimizer_trace::OptimizerTrace;
37use crate::optimize::{self, Optimize};
38use crate::session::Session;
39use crate::{AdapterNotice, ExecuteContext, catalog};
40
41impl Staged for CreateViewStage {
42 type Ctx = ExecuteContext;
43
44 fn validity(&mut self) -> &mut PlanValidity {
45 match self {
46 Self::Optimize(stage) => &mut stage.validity,
47 Self::Finish(stage) => &mut stage.validity,
48 Self::Explain(stage) => &mut stage.validity,
49 }
50 }
51
52 async fn stage(
53 self,
54 coord: &mut Coordinator,
55 ctx: &mut ExecuteContext,
56 ) -> Result<StageResult<Box<Self>>, AdapterError> {
57 match self {
58 CreateViewStage::Optimize(stage) => coord.create_view_optimize(stage).await,
59 CreateViewStage::Finish(stage) => coord.create_view_finish(ctx.session(), stage).await,
60 CreateViewStage::Explain(stage) => {
61 coord.create_view_explain(ctx.session(), stage).await
62 }
63 }
64 }
65
66 fn message(self, ctx: ExecuteContext, span: Span) -> Message {
67 Message::CreateViewStageReady {
68 ctx,
69 span,
70 stage: self,
71 }
72 }
73
74 fn cancel_enabled(&self) -> bool {
75 true
76 }
77}
78
79impl Coordinator {
80 pub(crate) async fn sequence_create_view(
81 &mut self,
82 ctx: ExecuteContext,
83 plan: plan::CreateViewPlan,
84 resolved_ids: ResolvedIds,
85 ) {
86 let stage = return_if_err!(
87 self.create_view_validate(ctx.session(), plan, resolved_ids, ExplainContext::None),
88 ctx
89 );
90 self.sequence_staged(ctx, Span::current(), stage).await;
91 }
92
93 #[instrument]
94 pub(crate) async fn explain_create_view(
95 &mut self,
96 ctx: ExecuteContext,
97 plan::ExplainPlanPlan {
98 stage,
99 format,
100 config,
101 explainee,
102 }: plan::ExplainPlanPlan,
103 ) {
104 let plan::Explainee::Statement(stmt) = explainee else {
105 unreachable!()
108 };
109 let plan::ExplaineeStatement::CreateView { broken, plan } = stmt else {
110 unreachable!()
113 };
114
115 let optimizer_trace = OptimizerTrace::new(stage.paths());
118
119 let resolved_ids = ResolvedIds::empty();
121
122 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
123 broken,
124 config,
125 format,
126 stage,
127 replan: None,
128 desc: None,
129 optimizer_trace,
130 });
131 let stage = return_if_err!(
132 self.create_view_validate(ctx.session(), plan, resolved_ids, explain_ctx),
133 ctx
134 );
135 self.sequence_staged(ctx, Span::current(), stage).await;
136 }
137
138 #[instrument]
139 pub(crate) async fn explain_replan_view(
140 &mut self,
141 ctx: ExecuteContext,
142 plan::ExplainPlanPlan {
143 stage,
144 format,
145 config,
146 explainee,
147 }: plan::ExplainPlanPlan,
148 ) {
149 let plan::Explainee::ReplanView(id) = explainee else {
150 unreachable!() };
152 let CatalogItem::View(item) = self.catalog().get_entry(&id).item() else {
153 unreachable!() };
155 let gid = item.global_id();
156
157 let create_sql = item.create_sql.clone();
158 let plan_result = self
159 .catalog_mut()
160 .deserialize_plan_with_enable_for_item_parsing(&create_sql, true);
161 let (plan, resolved_ids) = return_if_err!(plan_result, ctx);
162
163 let plan::Plan::CreateView(plan) = plan else {
164 unreachable!() };
166
167 let broken = false;
170
171 let optimizer_trace = OptimizerTrace::new(stage.paths());
174
175 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
176 broken,
177 config,
178 format,
179 stage,
180 replan: Some(gid),
181 desc: None,
182 optimizer_trace,
183 });
184 let stage = return_if_err!(
185 self.create_view_validate(ctx.session(), plan, resolved_ids, explain_ctx),
186 ctx
187 );
188 self.sequence_staged(ctx, Span::current(), stage).await;
189 }
190
191 #[instrument]
192 pub(crate) fn explain_view(
193 &self,
194 ctx: &ExecuteContext,
195 plan::ExplainPlanPlan {
196 stage,
197 format,
198 config,
199 explainee,
200 }: plan::ExplainPlanPlan,
201 ) -> Result<ExecuteResponse, AdapterError> {
202 let plan::Explainee::View(id) = explainee else {
203 unreachable!() };
205 let CatalogItem::View(view) = self.catalog().get_entry(&id).item() else {
206 unreachable!() };
208
209 let target_cluster = None; let features =
212 OptimizerFeatures::from(self.catalog().system_config()).override_from(&config.features);
213
214 let cardinality_stats = BTreeMap::new();
215
216 let explain = match stage {
217 ExplainStage::RawPlan => explain_plan(
218 view.raw_expr.as_ref().clone(),
219 format,
220 &config,
221 &features,
222 &self.catalog().for_session(ctx.session()),
223 cardinality_stats,
224 target_cluster,
225 )?,
226 ExplainStage::LocalPlan => explain_plan(
227 view.locally_optimized_expr.as_inner().clone(),
228 format,
229 &config,
230 &features,
231 &self.catalog().for_session(ctx.session()),
232 cardinality_stats,
233 target_cluster,
234 )?,
235 _ => {
236 coord_bail!("cannot EXPLAIN {} FOR VIEW", stage);
237 }
238 };
239
240 let row = Row::pack_slice(&[Datum::from(explain.as_str())]);
241
242 Ok(Self::send_immediate_rows(row))
243 }
244
245 #[instrument]
246 fn create_view_validate(
247 &self,
248 session: &Session,
249 plan: plan::CreateViewPlan,
250 resolved_ids: ResolvedIds,
251 explain_ctx: ExplainContext,
254 ) -> Result<CreateViewStage, AdapterError> {
255 let plan::CreateViewPlan {
256 view: plan::View { expr, .. },
257 ambiguous_columns,
258 ..
259 } = &plan;
260
261 let expr_depends_on = expr.depends_on();
266 self.catalog()
267 .validate_timeline_context(expr_depends_on.iter().copied())?;
268 self.validate_system_column_references(*ambiguous_columns, &expr_depends_on)?;
269
270 let validity = PlanValidity::new(
274 self.catalog(),
275 resolved_ids.items().copied().collect(),
276 None,
277 None,
278 session.role_metadata().clone(),
279 );
280
281 Ok(CreateViewStage::Optimize(CreateViewOptimize {
282 validity,
283 plan,
284 resolved_ids,
285 explain_ctx,
286 }))
287 }
288
289 #[instrument]
290 async fn create_view_optimize(
291 &mut self,
292 CreateViewOptimize {
293 validity,
294 plan,
295 resolved_ids,
296 explain_ctx,
297 }: CreateViewOptimize,
298 ) -> Result<StageResult<Box<CreateViewStage>>, AdapterError> {
299 let (item_id, global_id) = self.allocate_user_id().await?;
300
301 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
303 .override_from(&explain_ctx);
304 let optimizer_features = optimizer_config.features.clone();
305
306 let mut optimizer =
308 optimize::view::Optimizer::new(optimizer_config, Some(self.optimizer_metrics()));
309
310 let span = Span::current();
311 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
312 || "optimize create view",
313 move || {
314 span.in_scope(|| {
315 let mut pipeline =
316 || -> Result<mz_expr::OptimizedMirRelationExpr, AdapterError> {
317 let _dispatch_guard = explain_ctx.dispatch_guard();
318
319 let raw_expr = plan.view.expr.clone();
321 let optimized_expr = optimizer.catch_unwind_optimize(raw_expr)?;
322
323 Ok(optimized_expr)
324 };
325
326 let stage = match pipeline() {
327 Ok(optimized_expr) => {
328 if let ExplainContext::Plan(explain_ctx) = explain_ctx {
329 CreateViewStage::Explain(CreateViewExplain {
330 validity,
331 id: global_id,
332 plan,
333 explain_ctx,
334 })
335 } else {
336 CreateViewStage::Finish(CreateViewFinish {
337 validity,
338 item_id,
339 global_id,
340 plan,
341 optimized_expr,
342 resolved_ids,
343 optimizer_features,
344 })
345 }
346 }
347 Err(err) => {
350 let ExplainContext::Plan(explain_ctx) = explain_ctx else {
351 return Err(err);
353 };
354
355 if explain_ctx.broken {
356 tracing::error!("error while handling EXPLAIN statement: {}", err);
360 CreateViewStage::Explain(CreateViewExplain {
361 validity,
362 id: global_id,
363 plan,
364 explain_ctx,
365 })
366 } else {
367 return Err(err);
369 }
370 }
371 };
372
373 Ok(Box::new(stage))
374 })
375 },
376 )))
377 }
378
379 #[instrument]
380 async fn create_view_finish(
381 &mut self,
382 session: &Session,
383 CreateViewFinish {
384 item_id,
385 global_id,
386 plan:
387 plan::CreateViewPlan {
388 name,
389 view:
390 plan::View {
391 create_sql,
392 expr: raw_expr,
393 dependencies,
394 column_names,
395 temporary,
396 },
397 drop_ids,
398 if_not_exists,
399 ..
400 },
401 optimized_expr,
402 resolved_ids,
403 optimizer_features,
404 ..
405 }: CreateViewFinish,
406 ) -> Result<StageResult<Box<CreateViewStage>>, AdapterError> {
407 let typ = infer_sql_type_for_catalog(&raw_expr, &optimized_expr);
408
409 if !temporary {
411 self.catalog()
412 .cache_local_expression(global_id, optimized_expr.clone(), optimizer_features)
413 .await;
414 }
415 let replaced_global_ids: BTreeSet<_> = drop_ids
418 .iter()
419 .flat_map(|id| self.catalog().get_entry(id).global_ids())
420 .collect();
421
422 let ops = vec![
423 catalog::Op::DropObjects(
424 drop_ids
425 .iter()
426 .map(|id| catalog::DropObjectInfo::Item(*id))
427 .collect(),
428 ),
429 catalog::Op::CreateItem {
430 id: item_id,
431 name: name.clone(),
432 item: CatalogItem::View(View {
433 create_sql: create_sql.clone(),
434 global_id,
435 raw_expr: raw_expr.into(),
436 desc: RelationDesc::new(typ, column_names.clone()),
437 locally_optimized_expr: optimized_expr.into(),
438 conn_id: if temporary {
439 Some(session.conn_id().clone())
440 } else {
441 None
442 },
443 resolved_ids: resolved_ids.clone(),
444 dependencies: dependencies.clone(),
445 }),
446 owner_id: *session.current_role_id(),
447 },
448 ];
449
450 match self.catalog_transact(Some(session), ops).await {
451 Ok(()) => {
452 if !replaced_global_ids.is_empty() {
453 let _fut = self.catalog().update_expression_cache(
454 Vec::new(),
455 Vec::new(),
456 replaced_global_ids,
457 );
458 }
459 Ok(StageResult::Response(ExecuteResponse::CreatedView))
460 }
461 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
462 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
463 })) if if_not_exists => {
464 session.add_notice(AdapterNotice::ObjectAlreadyExists {
465 name: name.item,
466 ty: "view",
467 });
468 Ok(StageResult::Response(ExecuteResponse::CreatedView))
469 }
470 Err(err) => Err(err),
471 }
472 }
473
474 #[instrument]
475 async fn create_view_explain(
476 &self,
477 session: &Session,
478 CreateViewExplain {
479 id,
480 plan:
481 plan::CreateViewPlan {
482 name,
483 view: plan::View { column_names, .. },
484 ..
485 },
486 explain_ctx:
487 ExplainPlanContext {
488 config,
489 format,
490 stage,
491 optimizer_trace,
492 ..
493 },
494 ..
495 }: CreateViewExplain,
496 ) -> Result<StageResult<Box<CreateViewStage>>, AdapterError> {
497 let session_catalog = self.catalog().for_session(session);
498 let expr_humanizer = {
499 let full_name = self.catalog().resolve_full_name(&name, None);
500 let transient_items = btreemap! {
501 id => TransientItem::new(
502 Some(full_name.into_parts()),
503 Some(column_names.iter().map(|c| c.to_string()).collect()),
504 )
505 };
506 ExprHumanizerExt::new(transient_items, &session_catalog)
507 };
508
509 let features =
510 OptimizerFeatures::from(self.catalog().system_config()).override_from(&config.features);
511
512 let rows = optimizer_trace
513 .into_rows(
514 format,
515 &config,
516 &features,
517 &expr_humanizer,
518 None,
519 None, Default::default(),
521 stage,
522 plan::ExplaineeStatementKind::CreateView,
523 None,
524 )
525 .await?;
526
527 Ok(StageResult::Response(Self::send_immediate_rows(rows)))
528 }
529}