Skip to main content

mz_adapter/coord/sequencer/inner/
create_view.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use 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            // This is currently asserted in the `sequence_explain_plan` code that
106            // calls this method.
107            unreachable!()
108        };
109        let plan::ExplaineeStatement::CreateView { broken, plan } = stmt else {
110            // This is currently asserted in the `sequence_explain_plan` code that
111            // calls this method.
112            unreachable!()
113        };
114
115        // Create an OptimizerTrace instance to collect plans emitted when
116        // executing the optimizer pipeline.
117        let optimizer_trace = OptimizerTrace::new(stage.paths());
118
119        // Not used in the EXPLAIN path so it's OK to generate a dummy value.
120        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!() // Asserted in `sequence_explain_plan`.
151        };
152        let CatalogItem::View(item) = self.catalog().get_entry(&id).item() else {
153            unreachable!() // Asserted in `plan_explain_plan`.
154        };
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!() // We are parsing the `create_sql` of a `View` item.
165        };
166
167        // It is safe to assume that query optimization will always succeed, so
168        // for now we statically assume `broken = false`.
169        let broken = false;
170
171        // Create an OptimizerTrace instance to collect plans emitted when
172        // executing the optimizer pipeline.
173        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!() // Asserted in `sequence_explain_plan`.
204        };
205        let CatalogItem::View(view) = self.catalog().get_entry(&id).item() else {
206            unreachable!() // Asserted in `plan_explain_plan`.
207        };
208
209        let target_cluster = None; // Views don't have a target cluster.
210
211        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        // An optional context set iff the state machine is initiated from
252        // sequencing an EXPLAIN for this statement.
253        explain_ctx: ExplainContext,
254    ) -> Result<CreateViewStage, AdapterError> {
255        let plan::CreateViewPlan {
256            view: plan::View { expr, .. },
257            ambiguous_columns,
258            ..
259        } = &plan;
260
261        // Validate any references in the view's expression. We do this on the
262        // unoptimized plan to better reflect what the user typed. We want to
263        // reject queries that depend on a relation in the wrong timeline, for
264        // example, even if we can *technically* optimize that reference away.
265        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        // Track resolved dependencies so concurrent drops are caught between
271        // stages instead of panicking later when the persisted SQL is
272        // re-parsed during catalog application.
273        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        // Collect optimizer parameters.
302        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        // Build an optimizer for this VIEW.
307        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                            // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local)
320                            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                        // Internal optimizer errors are handled differently
348                        // depending on the caller.
349                        Err(err) => {
350                            let ExplainContext::Plan(explain_ctx) = explain_ctx else {
351                                // In `sequence_~` contexts, immediately return the error.
352                                return Err(err);
353                            };
354
355                            if explain_ctx.broken {
356                                // In `EXPLAIN BROKEN` contexts, just log the error
357                                // and move to the next stage with default
358                                // parameters.
359                                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                                // In regular `EXPLAIN` contexts, immediately return the error.
368                                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        // Populate the durable expression cache before the catalog transaction and await the write.
410        if !temporary {
411            self.catalog()
412                .cache_local_expression(global_id, optimized_expr.clone(), optimizer_features)
413                .await;
414        }
415        // Invalidate the entries of the views that `CREATE OR REPLACE` drops, as `DROP` does.
416        // The new view has fresh ids, so its entry stays.
417        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, // Views don't have a target cluster.
520                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}