diff --git a/doc/developer/design/20240205_cluster_specific_optimization.md b/doc/developer/design/20240205_cluster_specific_optimization.md new file mode 100644 index 0000000000000..06869805e1eaf --- /dev/null +++ b/doc/developer/design/20240205_cluster_specific_optimization.md @@ -0,0 +1,319 @@ +# Cluster-specific optimization + +- Associated: + - [optimizer: Flag for controlling join + planning (#23318)](https://github.com/MaterializeInc/materialize/pull/23318) + - [DNM: Draft for variadic outer join + lowering (#24345)](https://github.com/MaterializeInc/materialize/pull/24345) + - [misc: add `mzexplore` command for catalog + exploration (#22892)](https://github.com/MaterializeInc/materialize/pull/22892) + - [explain: control outer join lowering via + `ExplainConfig` (#22744)](https://github.com/MaterializeInc/materialize/pull/22744) + - [design: A unified optimizer + interface (#20569)](https://github.com/MaterializeInc/materialize/pull/20569) + + + +## The Problem + + + +Optimizer changes are tricky to implement and deploy in a robust and predictable +manner. The main problem is depicted by the following diagram. + +![Problem](./static/20240205_cluster_specific_optimization/problem.png) + +Most of the time, a change to our optimizer will not only improve the +performance of some SQL queries (hopefully a majority), but also introduce +performance regressions to others. + +The expected plan regressions and improvements of an optimizer change _in +general_ can be identified by running random query workloads using one of the +fuzzers available in our CI pipeline (RQG, SQLSmith, SQLLancer). However, we +currently lack the right tooling to determine the _specific impact_ of such +changes to our current `production` environments. + +The latter is of particular practical importance when prototyping and validating +possible optimizer changes behind a feature flag: + +1. It will help us to quickly get a feeling of the potential improvements that + our customers will see. This in turn can help us determine whether it is + worthwhile to invest the necessary time to make the change production-ready. +2. It will help us to identify potential performance regressions that will be + observed by our customers once we roll out the change. This in turn can help + us to improve the robustness of our rollout strategy for optimizer changes. + +There are two dimensions of assessing the quality of SQL queries in production: + +1. **Assessing the quality of the optimized plans.** To a large extent this is + already supported with the introduction of the `mzexplore` tool + (MaterializeInc/materialize#22892) and with adding the ability to control + optimizer feature flags using the `EXPLAIN WITH(...)` syntax + (MaterializeInc/materialize#22744). There are some known issues with the + current state of the code, but we have fixes for those that should be merged + soon (pending review). +2. **Assessing the quality of the running dataflows.** This is needed because + sometimes the impact of an optimizer change is not directly evident in the + optimized plan. Instead, we need to deploy and run the modified dataflow and + compare its performance against its current version. + +The current document proposes tooling and workflows in aid of (2). + +## Success Criteria + + + +- Members of the compute team have agreed upon the developed the minimal amount of + tooling and code infrastructure required to enable the evaluation of proposed + optimizer changes in production. + +- The new feature has been used to asses the potential impact of the following + optimizer changes: + - [optimizer: Flag for controlling join + planning (#23318)](https://github.com/MaterializeInc/materialize/pull/23318) + - [DNM: Draft for variadic outer join + lowering (#24345)](https://github.com/MaterializeInc/materialize/pull/24345) + +## Out of Scope + + + +Things that will be nice to have, but are intentionally left out of scope from +the current proposal are listed below with a brief motivation for that decision. + +- **Not requiring `mz_system` access in order to do a runtime validation + experiment.** This doesn't seem urgent because we will always use + `mzexplore` to first identify a small set of candidate dataflows. For those, + we will in turn run a dedicated experiment together with a designated + engineer that can temporarily request `mz_system` privileges with Teleport + (similar to the `lgalloc` experiments). +- **Provide tooling to automatically duplicate dataflow-backed catalog items + from a reference cluster to a temporary cluster used to run an experiment.** + While this might be very helpful in general, we believe that most of our + customers deploy their DDL statements with a script. For the first iteration + of this feature it should be therefore sufficient to ask them to run a + modified version of this script (with changed catalog item names) against + the temporary cluster while running our experiments. +- **Support experimentation on unmanaged clusters.** Extending the syntax for + managed clusters should be sufficient for an MVP. Since the clusters will be + short-lived it doesn't make a huge difference which syntax the Materialize + employees will use to create them. Adding `FEATURES` support for unmanaged + clusters will require more code changes. Since the syntax is now deprecated, + there is no need to additionally complicate this code. +- **Support changes of `CLUSTER`-specific features with `ALTER CLUSTER`.** For + the same reasons as above—for experimentation purposes we only need + short-lived clusters. + +## Solution Proposal + +The suggested high-level workflow for assessing the potential impact of an +optimizer feature will be: + +1. Use `bin/mzexplore` to identify dataflows and clusters that might be affected + by an optimizer change gated behind a feature flag. +2. For each such cluster, run the following experiment: + +The suggested workflow for running an experiment on a specific customer +environment will be: + +1. Obtain `mz_system` privileges through Teleport. +2. Create a dedicated cluster for the experiment. Use the feature flag to + enable the tested feature in the `CREATE CLUSTER` definition. +3. Create an `UNBILLED` replica for that cluster. +4. Ask the customer to replicate (a subset of) dataflow-backed catalog items + defined on the the original cluster to the experiment cluster. +5. Monitor and record observed differences between the dataflows running in the + original cluster and the (modified) dataflows running in the experiment + cluster. + + + +In order to facilitate this workflow, we propose the the following changes +(discussed in detail below): + +- Extensions to the `CREATE CLUSTER` syntax. +- Extensions to the optimizer API. + +### Extensions to the `CREATE CLUSTER` syntax + +Extend the `CREATE CLUSTER SYNTAX` for managed cluster plans as follows: + +```sql +CREATE CLUSTER ... FEATURES (...) +``` + +The newly added `FEATURES` clause will be only visible by `mz_system`. We will +extend `CreateClusterManagedPlan` and `CreateClusterManaged` with a new +`ClusterFeatures` struct that models the feature flag configuration that can be +overridden on a per-cluster level. + +### Extensions to the `optimize` API + +Here we can benefit from the unified optimizer interface introduced with +MaterializeInc/materialize#20569. As part of the associated changes we +introduced an `mz_adapter::optimize::OptimizerConfig` struct that currently can +can already be configured in a layered way: + +1. A top layer of settings bound from `SystemVars`. +2. A layer of `EXPLAIN`-specific overrides bound from the `ExplainContext`, + which is available when the `Coordinator` methods that drive the optimization + process are initiated from `sequence_explain_plan`. + +Since all `Optimizer::new(...)` constructor calls in the `Coordinator` happen at +a time where the target cluster for the optimized statement is already resolved, +we can just add a new layer for cluster-specific overrides between (1) and (2). + +## Minimal Viable Prototype + +An sketch of the proposed design can be found in the MVP draft PR[^3]. + + + +Nothing is done yet, but once we agree on the SQL extensions the changes to get +something working end-to-end should be done quite quickly. + +## Alternatives + + + +### Managing cluster-specific parameters with LaunchDarkly + +There is prior art for this in PostgreSQL: you can set (most) system +parameters at the database level, and they take precedence in order of: + +``` +system < database < role < session +``` + +or something lke that. If we get more use cases for such layering we can invest +the time to teach the `SystemVars` about cluster-specific parameters. Once we do +that, we should be able to enable feature flags on a per-cluster basis through +LaunchDarkly. + +The approach is rejected (for now) because it requires more substantial changes +to the current state of our LaunchDarkly setup in `main`. Basically at the +moment we pull parameters from LaunchDarkly in a loop using a fixed +`ld::Context`[^1] that consists of: + +1. A context of `kind = environment` that models the current Materialize + environment. +2. A context of `kind = organization` that models the environment owner. + +If we want to provide cluster-specific configuration through LaunchDarkly, we +would need to extend the `system_parameter_sync` loop[^2] to run a +`frontend.pull(...)` call with a different `ld::Context` for each cluster. We +would then use the `CLUSTER`-specific `ALTER SYSTEM` extensions in `SystemVars` +in the `backend.push(...)` call. + +## Open questions + + + +N/A + +## Future work + +From @benesch: + +> As future work, it seems like it'd also be interesting to allow users to use the +> `FEATURES` flag, limited to a restricted set of features that are worth allowing +> users to control. +> +> We might also consider introducing +> +> ```sql +> CREATE TEMPORARY CLUSTER ... FEATURES(...) +> ``` +> +> and limit certain features for use only in temporary clusters. These clusters +> would only last for a single SQL session, and therefore wouldn't survive an +> `envd` restart, and therefore wouldn't prevent us from removing the feature +> flag in a future version of Materialize. + +--- + +[Automatically duplicate dataflow-backed catalog items for experimentation](https://github.com/MaterializeInc/materialize/issues/25166) + +--- + +[^1]: [`ld_ctx` definition](https://github.com/MaterializeInc/materialize/blob/d44bd11b02e0c4cf11aa8307e44ffc5c5132eb12/src/adapter/src/config/frontend.rs#L145C1-L208C2) +[^2]: [`system_parameter_sync` definition](https://github.com/MaterializeInc/materialize/blob/d44bd11b02e0c4cf11aa8307e44ffc5c5132eb12/src/adapter/src/config/sync.rs#L23-L78) +[^3]: [MVP sketch (draft PR)](https://github.com/MaterializeInc/materialize/pull/25165) diff --git a/doc/developer/design/static/20240205_cluster_specific_optimization/problem.png b/doc/developer/design/static/20240205_cluster_specific_optimization/problem.png new file mode 100644 index 0000000000000..33a683aab0287 Binary files /dev/null and b/doc/developer/design/static/20240205_cluster_specific_optimization/problem.png differ diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 7274c08cad293..0dd961114e49e 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -558,6 +558,7 @@ pub struct CreateIndexExplain { pub enum CreateViewStage { Optimize(CreateViewOptimize), Finish(CreateViewFinish), + Explain(CreateViewExplain), } #[derive(Debug)] @@ -565,6 +566,9 @@ pub struct CreateViewOptimize { validity: PlanValidity, plan: plan::CreateViewPlan, resolved_ids: ResolvedIds, + /// An optional context set iff the state machine is initiated from + /// sequencing an EXPALIN for this statement. + explain_ctx: ExplainContext, } #[derive(Debug)] @@ -576,6 +580,14 @@ pub struct CreateViewFinish { optimized_expr: OptimizedMirRelationExpr, } +#[derive(Debug)] +pub struct CreateViewExplain { + validity: PlanValidity, + id: GlobalId, + plan: plan::CreateViewPlan, + explain_ctx: ExplainPlanContext, +} + #[derive(Debug)] pub enum ExplainContext { /// The ordinary, non-explain variant of the statement. diff --git a/src/adapter/src/coord/sequencer/cluster.rs b/src/adapter/src/coord/sequencer/cluster.rs index dce65663140ce..7bd59d68d3949 100644 --- a/src/adapter/src/coord/sequencer/cluster.rs +++ b/src/adapter/src/coord/sequencer/cluster.rs @@ -68,6 +68,7 @@ impl Coordinator { idle_arrangement_merge_effort: plan.compute.idle_arrangement_merge_effort, replication_factor: plan.replication_factor, disk: plan.disk, + optimizer_feature_overrides: plan.optimizer_feature_overrides.clone(), }) } CreateClusterVariant::Unmanaged(_) => ClusterVariant::Unmanaged, @@ -576,6 +577,7 @@ impl Coordinator { idle_arrangement_merge_effort: None, replication_factor: 1, disk, + optimizer_feature_overrides: Default::default(), }); } } @@ -588,6 +590,7 @@ impl Coordinator { idle_arrangement_merge_effort, replication_factor, disk, + optimizer_feature_overrides: _, }) => { use AlterOptionParameter::*; match &options.size { @@ -707,6 +710,7 @@ impl Coordinator { logging, idle_arrangement_merge_effort, disk, + optimizer_feature_overrides: _, }, ClusterVariantManaged { size: new_size, @@ -715,6 +719,7 @@ impl Coordinator { logging: new_logging, idle_arrangement_merge_effort: new_idle_arrangement_merge_effort, disk: new_disk, + optimizer_feature_overrides: _, }, ) = (&config, &new_config); @@ -849,6 +854,7 @@ impl Coordinator { logging: _, idle_arrangement_merge_effort: _, disk: new_disk, + optimizer_feature_overrides: _, } = &mut new_config; // Validate replication factor parameter diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index f1fc0fc8baa5c..59a632c4c0150 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -33,7 +33,7 @@ use mz_repr::explain::json::json_string; use mz_repr::explain::{ExplainFormat, ExprHumanizer}; use mz_repr::role_id::RoleId; use mz_repr::{Datum, Diff, GlobalId, Row, RowArena, Timestamp}; -use mz_sql::ast::{ExplainStage, IndexOptionName}; +use mz_sql::ast::IndexOptionName; use mz_sql::catalog::{ CatalogCluster, CatalogClusterReplica, CatalogDatabase, CatalogError, CatalogItem as SqlCatalogItem, CatalogItemType, CatalogRole, CatalogSchema, CatalogTypeDetails, @@ -87,7 +87,6 @@ use crate::coord::{ RealTimeRecencyContext, StageResult, Staged, TargetCluster, }; use crate::error::AdapterError; -use crate::explain::explain_dataflow; use crate::notice::{AdapterNotice, DroppedInUseIndex}; use crate::optimize::dataflows::{prep_scalar_expr, EvalTime, ExprPrepStyle}; use crate::optimize::{self, Optimize}; @@ -1861,6 +1860,9 @@ impl Coordinator { ) { match &plan.explainee { plan::Explainee::Statement(stmt) => match stmt { + plan::ExplaineeStatement::CreateView { .. } => { + self.explain_create_view(ctx, plan).await; + } plan::ExplaineeStatement::CreateMaterializedView { .. } => { self.explain_create_materialized_view(ctx, plan).await; } @@ -1871,6 +1873,10 @@ impl Coordinator { self.explain_peek(ctx, plan, target_cluster).await; } }, + plan::Explainee::View(_) => { + let result = self.explain_view(&ctx, plan); + ctx.retire(result); + } plan::Explainee::MaterializedView(_) => { let result = self.explain_materialized_view(&ctx, plan); ctx.retire(result); @@ -1879,6 +1885,9 @@ impl Coordinator { let result = self.explain_index(&ctx, plan); ctx.retire(result); } + plan::Explainee::ReplanView(_) => { + self.explain_replan_view(ctx, plan).await; + } plan::Explainee::ReplanMaterializedView(_) => { self.explain_replan_materialized_view(ctx, plan).await; } @@ -1888,128 +1897,6 @@ impl Coordinator { }; } - fn explain_materialized_view( - &mut self, - ctx: &ExecuteContext, - plan::ExplainPlanPlan { - stage, - format, - config, - explainee, - }: plan::ExplainPlanPlan, - ) -> Result { - let plan::Explainee::MaterializedView(id) = explainee else { - unreachable!() // Asserted in `sequence_explain_plan`. - }; - let CatalogItem::MaterializedView(_) = self.catalog().get_entry(&id).item() else { - unreachable!() // Asserted in `plan_explain_plan`. - }; - - let Some(dataflow_metainfo) = self.catalog().try_get_dataflow_metainfo(&id) else { - tracing::error!( - "cannot find dataflow metainformation for materialized view {id} in catalog" - ); - coord_bail!( - "cannot find dataflow metainformation for materialized view {id} in catalog" - ); - }; - - let explain = match stage { - ExplainStage::OptimizedPlan => { - let Some(plan) = self.catalog().try_get_optimized_plan(&id).cloned() else { - tracing::error!("cannot find {stage} for materialized view {id} in catalog"); - coord_bail!("cannot find {stage} for materialized view in catalog"); - }; - explain_dataflow( - plan, - format, - &config, - &self.catalog().for_session(ctx.session()), - dataflow_metainfo, - )? - } - ExplainStage::PhysicalPlan => { - let Some(plan) = self.catalog().try_get_physical_plan(&id).cloned() else { - tracing::error!("cannot find {stage} for materialized view {id} in catalog"); - coord_bail!("cannot find {stage} for materialized view in catalog"); - }; - explain_dataflow( - plan, - format, - &config, - &self.catalog().for_session(ctx.session()), - dataflow_metainfo, - )? - } - _ => { - coord_bail!("cannot EXPLAIN {} FOR MATERIALIZED VIEW", stage); - } - }; - - let rows = vec![Row::pack_slice(&[Datum::from(explain.as_str())])]; - - Ok(Self::send_immediate_rows(rows)) - } - - fn explain_index( - &mut self, - ctx: &ExecuteContext, - plan::ExplainPlanPlan { - stage, - format, - config, - explainee, - }: plan::ExplainPlanPlan, - ) -> Result { - let plan::Explainee::Index(id) = explainee else { - unreachable!() // Asserted in `sequence_explain_plan`. - }; - let CatalogItem::Index(_) = self.catalog().get_entry(&id).item() else { - unreachable!() // Asserted in `plan_explain_plan`. - }; - - let Some(dataflow_metainfo) = self.catalog().try_get_dataflow_metainfo(&id) else { - tracing::error!("cannot find dataflow metainformation for index {id} in catalog"); - coord_bail!("cannot find dataflow metainformation for index {id} in catalog"); - }; - - let explain = match stage { - ExplainStage::OptimizedPlan => { - let Some(plan) = self.catalog().try_get_optimized_plan(&id).cloned() else { - tracing::error!("cannot find {stage} for index {id} in catalog"); - coord_bail!("cannot find {stage} for index in catalog"); - }; - explain_dataflow( - plan, - format, - &config, - &self.catalog().for_session(ctx.session()), - dataflow_metainfo, - )? - } - ExplainStage::PhysicalPlan => { - let Some(plan) = self.catalog().try_get_physical_plan(&id).cloned() else { - tracing::error!("cannot find {stage} for index {id} in catalog"); - coord_bail!("cannot find {stage} for index in catalog"); - }; - explain_dataflow( - plan, - format, - &config, - &self.catalog().for_session(ctx.session()), - dataflow_metainfo, - )? - } - _ => { - coord_bail!("cannot EXPLAIN {} FOR INDEX", stage); - } - }; - - let rows = vec![Row::pack_slice(&[Datum::from(explain.as_str())])]; - - Ok(Self::send_immediate_rows(rows)) - } - #[instrument] pub async fn sequence_explain_timestamp( &mut self, diff --git a/src/adapter/src/coord/sequencer/inner/create_index.rs b/src/adapter/src/coord/sequencer/inner/create_index.rs index c412cad4ab575..d5930afcf551d 100644 --- a/src/adapter/src/coord/sequencer/inner/create_index.rs +++ b/src/adapter/src/coord/sequencer/inner/create_index.rs @@ -13,6 +13,8 @@ use maplit::btreemap; use mz_catalog::memory::objects::{CatalogItem, Index}; use mz_ore::instrument; use mz_repr::explain::{ExprHumanizerExt, TransientItem}; +use mz_repr::{Datum, Row}; +use mz_sql::ast::ExplainStage; use mz_sql::catalog::CatalogError; use mz_sql::names::ResolvedIds; use mz_sql::plan; @@ -25,6 +27,7 @@ use crate::coord::{ ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged, }; use crate::error::AdapterError; +use crate::explain::explain_dataflow; use crate::explain::optimizer_trace::OptimizerTrace; use crate::optimize::dataflows::dataflow_import_id_bundle; use crate::optimize::{self, Optimize, OverrideFrom}; @@ -173,6 +176,66 @@ impl Coordinator { self.sequence_staged(ctx, Span::current(), stage).await; } + #[instrument] + pub(crate) fn explain_index( + &mut self, + ctx: &ExecuteContext, + plan::ExplainPlanPlan { + stage, + format, + config, + explainee, + }: plan::ExplainPlanPlan, + ) -> Result { + let plan::Explainee::Index(id) = explainee else { + unreachable!() // Asserted in `sequence_explain_plan`. + }; + let CatalogItem::Index(_) = self.catalog().get_entry(&id).item() else { + unreachable!() // Asserted in `plan_explain_plan`. + }; + + let Some(dataflow_metainfo) = self.catalog().try_get_dataflow_metainfo(&id) else { + tracing::error!("cannot find dataflow metainformation for index {id} in catalog"); + coord_bail!("cannot find dataflow metainformation for index {id} in catalog"); + }; + + let explain = match stage { + ExplainStage::GlobalPlan => { + let Some(plan) = self.catalog().try_get_optimized_plan(&id).cloned() else { + tracing::error!("cannot find {stage} for index {id} in catalog"); + coord_bail!("cannot find {stage} for index in catalog"); + }; + explain_dataflow( + plan, + format, + &config, + &self.catalog().for_session(ctx.session()), + dataflow_metainfo, + )? + } + ExplainStage::PhysicalPlan => { + let Some(plan) = self.catalog().try_get_physical_plan(&id).cloned() else { + tracing::error!("cannot find {stage} for index {id} in catalog"); + coord_bail!("cannot find {stage} for index in catalog"); + }; + explain_dataflow( + plan, + format, + &config, + &self.catalog().for_session(ctx.session()), + dataflow_metainfo, + )? + } + _ => { + coord_bail!("cannot EXPLAIN {} FOR INDEX", stage); + } + }; + + let rows = vec![Row::pack_slice(&[Datum::from(explain.as_str())])]; + + Ok(Self::send_immediate_rows(rows)) + } + // `explain_ctx` is an optional context set iff the state machine is initiated from // sequencing an EXPALIN for this statement. #[instrument] @@ -229,6 +292,7 @@ impl Coordinator { self.allocate_transient_id()? }; let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()) + .override_from(&self.catalog.get_cluster(*cluster_id).config.features()) .override_from(&explain_ctx); // Build an optimizer for this INDEX. diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index e1f716f6af9fd..9e28b786cef0e 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -16,6 +16,9 @@ use mz_ore::collections::CollectionExt; use mz_ore::instrument; use mz_ore::soft_panic_or_log; use mz_repr::explain::{ExprHumanizerExt, TransientItem}; +use mz_repr::Datum; +use mz_repr::Row; +use mz_sql::ast::ExplainStage; use mz_sql::catalog::CatalogError; use mz_sql::names::{ObjectId, ResolvedIds}; use mz_sql::plan; @@ -33,6 +36,8 @@ use crate::coord::{ ExplainPlanContext, Message, PlanValidity, StageResult, Staged, }; use crate::error::AdapterError; +use crate::explain::explain_dataflow; +use crate::explain::explain_plan; use crate::explain::optimizer_trace::OptimizerTrace; use crate::optimize::dataflows::dataflow_import_id_bundle; use crate::optimize::{self, Optimize, OverrideFrom}; @@ -137,7 +142,7 @@ impl Coordinator { optimizer_trace, }); let stage = return_if_err!( - self.create_materialized_view_validate(ctx.session(), plan, resolved_ids, explain_ctx,), + self.create_materialized_view_validate(ctx.session(), plan, resolved_ids, explain_ctx), ctx ); self.sequence_staged(ctx, Span::current(), stage).await; @@ -193,6 +198,82 @@ impl Coordinator { self.sequence_staged(ctx, Span::current(), stage).await; } + #[instrument] + pub(super) fn explain_materialized_view( + &mut self, + ctx: &ExecuteContext, + plan::ExplainPlanPlan { + stage, + format, + config, + explainee, + }: plan::ExplainPlanPlan, + ) -> Result { + let plan::Explainee::MaterializedView(id) = explainee else { + unreachable!() // Asserted in `sequence_explain_plan`. + }; + let CatalogItem::MaterializedView(view) = self.catalog().get_entry(&id).item() else { + unreachable!() // Asserted in `plan_explain_plan`. + }; + + let Some(dataflow_metainfo) = self.catalog().try_get_dataflow_metainfo(&id) else { + tracing::error!( + "cannot find dataflow metainformation for materialized view {id} in catalog" + ); + coord_bail!( + "cannot find dataflow metainformation for materialized view {id} in catalog" + ); + }; + + let explain = match stage { + ExplainStage::RawPlan => explain_plan( + view.raw_expr.clone(), + format, + &config, + &self.catalog().for_session(ctx.session()), + )?, + ExplainStage::LocalPlan => explain_plan( + view.optimized_expr.as_inner().clone(), + format, + &config, + &self.catalog().for_session(ctx.session()), + )?, + ExplainStage::GlobalPlan => { + let Some(plan) = self.catalog().try_get_optimized_plan(&id).cloned() else { + tracing::error!("cannot find {stage} for materialized view {id} in catalog"); + coord_bail!("cannot find {stage} for materialized view in catalog"); + }; + explain_dataflow( + plan, + format, + &config, + &self.catalog().for_session(ctx.session()), + dataflow_metainfo, + )? + } + ExplainStage::PhysicalPlan => { + let Some(plan) = self.catalog().try_get_physical_plan(&id).cloned() else { + tracing::error!("cannot find {stage} for materialized view {id} in catalog"); + coord_bail!("cannot find {stage} for materialized view in catalog"); + }; + explain_dataflow( + plan, + format, + &config, + &self.catalog().for_session(ctx.session()), + dataflow_metainfo, + )? + } + _ => { + coord_bail!("cannot EXPLAIN {} FOR MATERIALIZED VIEW", stage); + } + }; + + let rows = vec![Row::pack_slice(&[Datum::from(explain.as_str())])]; + + Ok(Self::send_immediate_rows(rows)) + } + #[instrument] fn create_materialized_view_validate( &mut self, @@ -316,6 +397,7 @@ impl Coordinator { let internal_view_id = self.allocate_transient_id()?; let debug_name = self.catalog().resolve_full_name(name, None).to_string(); let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()) + .override_from(&self.catalog.get_cluster(*cluster_id).config.features()) .override_from(&explain_ctx); // Build an optimizer for this MATERIALIZED VIEW. @@ -337,22 +419,22 @@ impl Coordinator { move || { span.in_scope(|| { let mut pipeline = || -> Result<( - optimize::materialized_view::LocalMirPlan, - optimize::materialized_view::GlobalMirPlan, - optimize::materialized_view::GlobalLirPlan, - ), AdapterError> { - let _dispatch_guard = explain_ctx.dispatch_guard(); + optimize::materialized_view::LocalMirPlan, + optimize::materialized_view::GlobalMirPlan, + optimize::materialized_view::GlobalLirPlan, + ), AdapterError> { + let _dispatch_guard = explain_ctx.dispatch_guard(); - let raw_expr = plan.materialized_view.expr.clone(); + let raw_expr = plan.materialized_view.expr.clone(); - // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local and global) - let local_mir_plan = optimizer.catch_unwind_optimize(raw_expr)?; - let global_mir_plan = optimizer.catch_unwind_optimize(local_mir_plan.clone())?; - // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global) - let global_lir_plan = optimizer.catch_unwind_optimize(global_mir_plan.clone())?; + // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local and global) + let local_mir_plan = optimizer.catch_unwind_optimize(raw_expr)?; + let global_mir_plan = optimizer.catch_unwind_optimize(local_mir_plan.clone())?; + // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global) + let global_lir_plan = optimizer.catch_unwind_optimize(global_mir_plan.clone())?; - Ok((local_mir_plan, global_mir_plan, global_lir_plan)) - }; + Ok((local_mir_plan, global_mir_plan, global_lir_plan)) + }; let stage = match pipeline() { Ok((local_mir_plan, global_mir_plan, global_lir_plan)) => { diff --git a/src/adapter/src/coord/sequencer/inner/create_view.rs b/src/adapter/src/coord/sequencer/inner/create_view.rs index eb35795a03273..2bb044449357e 100644 --- a/src/adapter/src/coord/sequencer/inner/create_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_view.rs @@ -7,10 +7,13 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. +use maplit::btreemap; use mz_catalog::memory::objects::{CatalogItem, View}; use mz_expr::CollectionPlan; use mz_ore::instrument; -use mz_repr::RelationDesc; +use mz_repr::explain::{ExprHumanizerExt, TransientItem}; +use mz_repr::{Datum, RelationDesc, Row}; +use mz_sql::ast::ExplainStage; use mz_sql::catalog::CatalogError; use mz_sql::names::{ObjectId, ResolvedIds}; use mz_sql::plan::{self}; @@ -19,11 +22,13 @@ use tracing::Span; use crate::command::ExecuteResponse; use crate::coord::sequencer::inner::return_if_err; use crate::coord::{ - Coordinator, CreateViewFinish, CreateViewOptimize, CreateViewStage, Message, PlanValidity, - StageResult, Staged, + Coordinator, CreateViewExplain, CreateViewFinish, CreateViewOptimize, CreateViewStage, + ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged, }; use crate::error::AdapterError; -use crate::optimize::{self, Optimize}; +use crate::explain::explain_plan; +use crate::explain::optimizer_trace::OptimizerTrace; +use crate::optimize::{self, Optimize, OverrideFrom}; use crate::session::Session; use crate::{catalog, AdapterNotice, ExecuteContext}; @@ -32,6 +37,7 @@ impl Staged for CreateViewStage { match self { Self::Optimize(stage) => &mut stage.validity, Self::Finish(stage) => &mut stage.validity, + Self::Explain(stage) => &mut stage.validity, } } @@ -43,6 +49,7 @@ impl Staged for CreateViewStage { match self { CreateViewStage::Optimize(stage) => coord.create_view_optimize(stage).await, CreateViewStage::Finish(stage) => coord.create_view_finish(ctx.session(), stage).await, + CreateViewStage::Explain(stage) => coord.create_view_explain(ctx.session(), stage), } } @@ -63,18 +70,157 @@ impl Coordinator { resolved_ids: ResolvedIds, ) { let stage = return_if_err!( - self.create_view_validate(ctx.session(), plan, resolved_ids), + self.create_view_validate(ctx.session(), plan, resolved_ids, ExplainContext::None), ctx ); self.sequence_staged(ctx, Span::current(), stage).await; } + #[instrument] + pub(crate) async fn explain_create_view( + &mut self, + ctx: ExecuteContext, + plan::ExplainPlanPlan { + stage, + format, + config, + explainee, + }: plan::ExplainPlanPlan, + ) { + let plan::Explainee::Statement(stmt) = explainee else { + // This is currently asserted in the `sequence_explain_plan` code that + // calls this method. + unreachable!() + }; + let plan::ExplaineeStatement::CreateView { broken, plan } = stmt else { + // This is currently asserted in the `sequence_explain_plan` code that + // calls this method. + unreachable!() + }; + + // Create an OptimizerTrace instance to collect plans emitted when + // executing the optimizer pipeline. + let optimizer_trace = OptimizerTrace::new(broken, stage.path()); + + // Not used in the EXPLAIN path so it's OK to generate a dummy value. + let resolved_ids = ResolvedIds(Default::default()); + + let explain_ctx = ExplainContext::Plan(ExplainPlanContext { + broken, + config, + format, + stage, + replan: None, + desc: None, + optimizer_trace, + }); + let stage = return_if_err!( + self.create_view_validate(ctx.session(), plan, resolved_ids, explain_ctx), + ctx + ); + self.sequence_staged(ctx, Span::current(), stage).await; + } + + #[instrument] + pub(crate) async fn explain_replan_view( + &mut self, + ctx: ExecuteContext, + plan::ExplainPlanPlan { + stage, + format, + config, + explainee, + }: plan::ExplainPlanPlan, + ) { + let plan::Explainee::ReplanView(id) = explainee else { + unreachable!() // Asserted in `sequence_explain_plan`. + }; + let CatalogItem::View(item) = self.catalog().get_entry(&id).item() else { + unreachable!() // Asserted in `plan_explain_plan`. + }; + + let state = self.catalog().state(); + let plan_result = state.deserialize_plan(id, item.create_sql.clone(), true); + let (plan, resolved_ids) = return_if_err!(plan_result, ctx); + + let plan::Plan::CreateView(plan) = plan else { + unreachable!() // We are parsing the `create_sql` of a `MaterializedView` item. + }; + + // It is safe to assume that query optimization will always succeed, so + // for now we statically assume `broken = false`. + let broken = false; + + // Create an OptimizerTrace instance to collect plans emitted when + // executing the optimizer pipeline. + let optimizer_trace = OptimizerTrace::new(broken, stage.path()); + + let explain_ctx = ExplainContext::Plan(ExplainPlanContext { + broken, + config, + format, + stage, + replan: Some(id), + desc: None, + optimizer_trace, + }); + let stage = return_if_err!( + self.create_view_validate(ctx.session(), plan, resolved_ids, explain_ctx), + ctx + ); + self.sequence_staged(ctx, Span::current(), stage).await; + } + + #[instrument] + pub(crate) fn explain_view( + &mut self, + ctx: &ExecuteContext, + plan::ExplainPlanPlan { + stage, + format, + config, + explainee, + }: plan::ExplainPlanPlan, + ) -> Result { + let plan::Explainee::View(id) = explainee else { + unreachable!() // Asserted in `sequence_explain_plan`. + }; + let CatalogItem::View(view) = self.catalog().get_entry(&id).item() else { + unreachable!() // Asserted in `plan_explain_plan`. + }; + + let explain = match stage { + ExplainStage::RawPlan => explain_plan( + view.raw_expr.clone(), + format, + &config, + &self.catalog().for_session(ctx.session()), + )?, + ExplainStage::LocalPlan => explain_plan( + view.optimized_expr.as_inner().clone(), + format, + &config, + &self.catalog().for_session(ctx.session()), + )?, + _ => { + coord_bail!("cannot EXPLAIN {} FOR MATERIALIZED VIEW", stage); + } + }; + + let rows = vec![Row::pack_slice(&[Datum::from(explain.as_str())])]; + + Ok(Self::send_immediate_rows(rows)) + } + #[instrument] fn create_view_validate( &mut self, session: &Session, plan: plan::CreateViewPlan, resolved_ids: ResolvedIds, + // An optional context set iff the state machine is initiated from + // sequencing an EXPALIN for this statement. + explain_ctx: ExplainContext, ) -> Result { let plan::CreateViewPlan { view: plan::View { expr, .. }, @@ -102,6 +248,7 @@ impl Coordinator { validity, plan, resolved_ids, + explain_ctx, })) } @@ -112,30 +259,80 @@ impl Coordinator { validity, plan, resolved_ids, + explain_ctx, }: CreateViewOptimize, ) -> Result>, AdapterError> { let id = self.catalog_mut().allocate_user_id().await?; // Collect optimizer parameters. - let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()); + let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()) + .override_from(&explain_ctx); + + // Build an optimizer for this VIEW. + let mut optimizer = optimize::view::Optimizer::new(optimizer_config); let span = Span::current(); Ok(StageResult::Handle(mz_ore::task::spawn_blocking( || "optimize create view", move || { span.in_scope(|| { - // Build an optimizer for this VIEW. - let mut optimizer = optimize::view::Optimizer::new(optimizer_config); - // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local) - let raw_expr = plan.view.expr.clone(); - let optimized_expr = optimizer.catch_unwind_optimize(raw_expr)?; - Ok(Box::new(CreateViewStage::Finish(CreateViewFinish { - validity, - id, - plan, - optimized_expr, - resolved_ids, - }))) + let mut pipeline = + || -> Result { + let _dispatch_guard = explain_ctx.dispatch_guard(); + + // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local) + let raw_expr = plan.view.expr.clone(); + let optimized_expr = optimizer.catch_unwind_optimize(raw_expr)?; + + Ok(optimized_expr) + }; + + let stage = match pipeline() { + Ok(optimized_expr) => { + if let ExplainContext::Plan(explain_ctx) = explain_ctx { + CreateViewStage::Explain(CreateViewExplain { + validity, + id, + plan, + explain_ctx, + }) + } else { + CreateViewStage::Finish(CreateViewFinish { + validity, + id, + plan, + optimized_expr, + resolved_ids, + }) + } + } + // Internal optimizer errors are handled differently + // depending on the caller. + Err(err) => { + let ExplainContext::Plan(explain_ctx) = explain_ctx else { + // In `sequence_~` contexts, immediately return the error. + return Err(err.into()); + }; + + if explain_ctx.broken { + // In `EXPLAIN BROKEN` contexts, just log the error + // and move to the next stage with default + // parameters. + tracing::error!("error while handling EXPLAIN statement: {}", err); + CreateViewStage::Explain(CreateViewExplain { + validity, + id, + plan, + explain_ctx, + }) + } else { + // In regular `EXPLAIN` contexts, immediately return the error. + return Err(err.into()); + } + } + }; + + Ok(Box::new(stage)) }) }, ))) @@ -206,4 +403,58 @@ impl Coordinator { Err(err) => Err(err), } } + + #[instrument] + fn create_view_explain( + &mut self, + session: &Session, + CreateViewExplain { + id, + plan: + plan::CreateViewPlan { + name, + view: plan::View { column_names, .. }, + .. + }, + explain_ctx: + ExplainPlanContext { + broken, + config, + format, + stage, + optimizer_trace, + .. + }, + .. + }: CreateViewExplain, + ) -> Result>, AdapterError> { + let session_catalog = self.catalog().for_session(session); + let expr_humanizer = { + let full_name = self.catalog().resolve_full_name(&name, None); + let transient_items = btreemap! { + id => TransientItem::new( + Some(full_name.to_string()), + Some(full_name.item.to_string()), + Some(column_names.iter().map(|c| c.to_string()).collect()), + ) + }; + ExprHumanizerExt::new(transient_items, &session_catalog) + }; + + let rows = optimizer_trace.into_rows( + format, + &config, + &expr_humanizer, + None, + Default::default(), + stage, + plan::ExplaineeStatementKind::CreateView, + )?; + + if broken { + tracing_core::callsite::rebuild_interest_cache(); + } + + Ok(StageResult::Response(Self::send_immediate_rows(rows))) + } } diff --git a/src/adapter/src/coord/sequencer/inner/peek.rs b/src/adapter/src/coord/sequencer/inner/peek.rs index a8ecd7694b711..5281175280992 100644 --- a/src/adapter/src/coord/sequencer/inner/peek.rs +++ b/src/adapter/src/coord/sequencer/inner/peek.rs @@ -279,6 +279,7 @@ impl Coordinator { .expect("compute instance does not exist"); let view_id = self.allocate_transient_id()?; let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()) + .override_from(&self.catalog.get_cluster(cluster.id()).config.features()) .override_from(&explain_ctx); let optimizer = match copy_to_ctx { diff --git a/src/adapter/src/coord/sequencer/inner/subscribe.rs b/src/adapter/src/coord/sequencer/inner/subscribe.rs index da5d96c9c1ef7..2750ff276cb11 100644 --- a/src/adapter/src/coord/sequencer/inner/subscribe.rs +++ b/src/adapter/src/coord/sequencer/inner/subscribe.rs @@ -21,7 +21,7 @@ use crate::coord::{ SubscribeStage, SubscribeTimestampOptimizeLir, TargetCluster, }; use crate::error::AdapterError; -use crate::optimize::Optimize; +use crate::optimize::{Optimize, OverrideFrom}; use crate::session::{Session, TransactionOps}; use crate::util::ResultExt; use crate::{optimize, AdapterNotice, ExecuteContext, TimelineContext}; @@ -162,8 +162,9 @@ impl Coordinator { } = &plan; // Collect optimizer parameters. + let cluster_id = validity.cluster_id.expect("cluser_id"); let compute_instance = self - .instance_snapshot(validity.cluster_id.expect("cluser_id")) + .instance_snapshot(cluster_id) .expect("compute instance does not exist"); let id = self.allocate_transient_id()?; let conn_id = session.conn_id().clone(); @@ -171,7 +172,8 @@ impl Coordinator { .as_ref() .map(|expr| Coordinator::evaluate_when(self.catalog().state(), expr.clone(), session)) .transpose()?; - let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()); + let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config()) + .override_from(&self.catalog.get_cluster(cluster_id).config.features()); // Build an optimizer for this SUBSCRIBE. let mut optimizer = optimize::subscribe::Optimizer::new( diff --git a/src/adapter/src/explain/mod.rs b/src/adapter/src/explain/mod.rs index b5d7f24e4a981..c3b7a82ea3433 100644 --- a/src/adapter/src/explain/mod.rs +++ b/src/adapter/src/explain/mod.rs @@ -74,3 +74,31 @@ where Ok(Explainable::new(&mut plan).explain(&format, &context)?) } + +/// Convenience method to explain a single plan. +/// +/// In the long term, this method and [`explain_dataflow`] should be unified. In +/// order to do that, however, we first need to generalize the role +/// [`DataflowMetainfo`] as a carrier of metainformation for the optimization +/// pass in general, and not for a specific strucutre representing an +/// intermediate result. +pub(crate) fn explain_plan( + mut plan: T, + format: ExplainFormat, + config: &ExplainConfig, + humanizer: &dyn ExprHumanizer, +) -> Result +where + for<'a> Explainable<'a, T>: Explain<'a, Context = ExplainContext<'a>>, +{ + let context = ExplainContext { + config, + humanizer, + used_indexes: Default::default(), + finishing: Default::default(), + duration: Default::default(), + optimizer_notices: Default::default(), + }; + + Ok(Explainable::new(&mut plan).explain(&format, &context)?) +} diff --git a/src/adapter/src/optimize/copy_to.rs b/src/adapter/src/optimize/copy_to.rs index c929b46d3eb52..01e9e0a83b497 100644 --- a/src/adapter/src/optimize/copy_to.rs +++ b/src/adapter/src/optimize/copy_to.rs @@ -218,7 +218,7 @@ impl<'s> Optimize>> for Optimizer { let mut df_desc = MirDataflowDescription::new(debug_name.to_string()); df_builder.import_view_into_dataflow(&self.select_id, &expr, &mut df_desc)?; - df_builder.reoptimize_imported_views(&mut df_desc, &self.config)?; + df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?; // Creating an S3 sink as currently only s3 sinks are supported. It // might be possible in the future for COPY TO to write to different diff --git a/src/adapter/src/optimize/dataflows.rs b/src/adapter/src/optimize/dataflows.rs index 3225a5e9e7b7d..cc7d51e12b87d 100644 --- a/src/adapter/src/optimize/dataflows.rs +++ b/src/adapter/src/optimize/dataflows.rs @@ -43,7 +43,7 @@ use tracing::warn; use crate::catalog::CatalogState; use crate::coord::id_bundle::CollectionIdBundle; -use crate::optimize::{view, Optimize, OptimizeMode, OptimizerConfig, OptimizerError}; +use crate::optimize::{view, Optimize, OptimizerConfig, OptimizerError}; use crate::session::{Session, SERVER_MAJOR_VERSION, SERVER_MINOR_VERSION}; use crate::util::viewable_variables; @@ -302,33 +302,35 @@ impl<'a> DataflowBuilder<'a> { } // Re-optimize the imported view plans using the current optimizer - // configuration if we are running in `EXPLAIN`. - pub fn reoptimize_imported_views( + // configuration if reoptimization is requested. + pub fn maybe_reoptimize_imported_views( &self, df_desc: &mut DataflowDesc, config: &OptimizerConfig, ) -> Result<(), OptimizerError> { - if config.mode == OptimizeMode::Explain { - for desc in df_desc.objects_to_build.iter_mut().rev() { - if matches!(desc.id, GlobalId::Explain | GlobalId::Transient(_)) { - // Skip descriptions that do not reference proper views. - continue; - } - if let CatalogItem::View(view) = &self.catalog.get_entry(&desc.id).item { - let _span = tracing::span!( - target: "optimizer", - tracing::Level::DEBUG, - "view", - path.segment = desc.id.to_string() - ) - .entered(); - - let mut view_optimizer = view::Optimizer::new(config.clone()); - desc.plan = view_optimizer.optimize(view.raw_expr.clone())?; + if !config.reoptimize_imported_views { + return Ok(()); // Do nothing is not explicitly requested. + } - // Report the optimized plan under this span. - trace_plan(desc.plan.as_inner()); - } + let mut view_optimizer = view::Optimizer::new(config.clone()); + for desc in df_desc.objects_to_build.iter_mut().rev() { + if matches!(desc.id, GlobalId::Explain | GlobalId::Transient(_)) { + continue; // Skip descriptions that do not reference proper views. + } + if let CatalogItem::View(view) = &self.catalog.get_entry(&desc.id).item { + let _span = tracing::span!( + target: "optimizer", + tracing::Level::DEBUG, + "view", + path.segment = desc.id.to_string() + ) + .entered(); + + // Reoptimize the view and update the resulting `desc.plan`. + desc.plan = view_optimizer.optimize(view.raw_expr.clone())?; + + // Report the optimized plan under this span. + trace_plan(desc.plan.as_inner()); } } diff --git a/src/adapter/src/optimize/index.rs b/src/adapter/src/optimize/index.rs index 9080ce9e6a7b9..f59f85766e634 100644 --- a/src/adapter/src/optimize/index.rs +++ b/src/adapter/src/optimize/index.rs @@ -151,7 +151,7 @@ impl Optimize for Optimizer { let mut df_desc = MirDataflowDescription::new(full_name.to_string()); df_builder.import_into_dataflow(&index.on, &mut df_desc)?; - df_builder.reoptimize_imported_views(&mut df_desc, &self.config)?; + df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?; for desc in df_desc.objects_to_build.iter_mut() { prep_relation_expr(&mut desc.plan, ExprPrepStyle::Index)?; diff --git a/src/adapter/src/optimize/materialized_view.rs b/src/adapter/src/optimize/materialized_view.rs index 8823ead177aa7..50cdb1e7cdb3e 100644 --- a/src/adapter/src/optimize/materialized_view.rs +++ b/src/adapter/src/optimize/materialized_view.rs @@ -203,7 +203,7 @@ impl Optimize for Optimizer { let mut df_desc = MirDataflowDescription::new(self.debug_name.clone()); df_builder.import_view_into_dataflow(&self.internal_view_id, &expr, &mut df_desc)?; - df_builder.reoptimize_imported_views(&mut df_desc, &self.config)?; + df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?; for BuildDesc { plan, .. } in &mut df_desc.objects_to_build { prep_relation_expr(plan, ExprPrepStyle::Index)?; diff --git a/src/adapter/src/optimize/mod.rs b/src/adapter/src/optimize/mod.rs index 2c41b71a484d9..064422637d74f 100644 --- a/src/adapter/src/optimize/mod.rs +++ b/src/adapter/src/optimize/mod.rs @@ -67,6 +67,7 @@ use mz_compute_types::plan::Plan; use mz_expr::{EvalError, MirRelationExpr, OptimizedMirRelationExpr, UnmaterializableFunc}; use mz_ore::stack::RecursionLimitError; use mz_repr::adt::timestamp::TimestampError; +use mz_repr::optimize::OptimizerFeatureOverrides; use mz_repr::GlobalId; use mz_sql::plan::PlanError; use mz_sql::session::vars::SystemVars; @@ -151,7 +152,39 @@ where // Optimizer configuration // ----------------------- -// Feature flags for the optimizer. +/// Feature flags for the optimizer. +/// +/// To add a new feature flag, do the following steps: +/// +/// 1. To make the flag available to all stages in our [`Optimize`] pipelines: +/// 1. Add the flag as an [`OptimizerConfig`] field. +/// +/// 2. To allow engineers to set a system-wide override for this feature flag: +/// 1. Add the flag to the `feature_flags!(...)` macro call. +/// 2. Extend the `From<&SystemVars>` implementation for [`OptimizerConfig`]. +/// +/// 3. To enable `EXPLAIN ... WITH(...)` overrides which will allow engineers to +/// inspect plan differences before deploying the optimizer changes: +/// 1. Add the flag as a [`mz_repr::explain::ExplainConfig`] field. +/// 2. Add the flag to the `ExplainPlanOptionName` definition. +/// 3. Add the flag to the `generate_extracted_config!(ExplainPlanOption, +/// ...)` macro call. +/// 4. Extend the `TryFrom` implementation for +/// [`mz_repr::explain::ExplainConfig`]. +/// 5. Extend the `OverrideFrom` implementation for +/// [`OptimizerConfig`]. +/// +/// 4. To enable `CLUSTER ... FEATURES(...)` overrides which will allow +/// engineers to experiment with runtime differences before deploying the +/// optimizer changes: +/// 1. Add the flag to the `optimizer_feature_flags!(...)` macro call. +/// 2. Add the flag to the `ClusterFeatureName` definition. +/// 3. Add the flag to the `generate_extracted_config!(ClusterFeature, ...)` +/// macro call. +/// 4. Extend the `let optimizer_feature_overrides = ...` call in +/// `plan_create_cluster`. +/// 4. Extend the `OverrideFrom` implementation +/// for [`OptimizerConfig`]. #[derive(Clone, Debug)] pub struct OptimizerConfig { /// The mode in which the optimizer runs. @@ -161,6 +194,9 @@ pub struct OptimizerConfig { /// This means that it will not consider catalog items (more specifically /// indexes) with [`GlobalId`] greater or equal than the one provided here. pub replan: Option, + /// Reoptimize imported views when building and optimizing a + /// [`DataflowDescription`] in the global MIR optimization phase. + pub reoptimize_imported_views: bool, /// Enable fast path optimization. pub enable_fast_path: bool, /// Enable consolidation of unions that happen immediately after negate. @@ -192,6 +228,7 @@ impl From<&SystemVars> for OptimizerConfig { Self { mode: OptimizeMode::Execute, replan: None, + reoptimize_imported_views: false, enable_fast_path: true, // Always enable fast path if available. enable_consolidate_after_union_negate: vars.enable_consolidate_after_union_negate(), persist_fast_path_limit: vars.persist_fast_path_limit(), @@ -209,6 +246,35 @@ pub trait OverrideFrom { fn override_from(self, layer: &T) -> Self; } +/// [`OptimizerConfig`] overrides coming from an optional `T`. +impl OverrideFrom> for OptimizerConfig +where + Self: OverrideFrom, +{ + fn override_from(self, layer: &Option<&T>) -> Self { + match layer { + Some(layer) => self.override_from(layer), + None => self, + } + } +} + +/// [`OptimizerConfig`] overrides coming from a [`OptimizerFeatureOverrides`]. +impl OverrideFrom for OptimizerConfig { + fn override_from(mut self, overrides: &OptimizerFeatureOverrides) -> Self { + if let Some(feature_value) = overrides.reoptimize_imported_views { + self.reoptimize_imported_views = feature_value; + } + if let Some(feature_value) = overrides.enable_new_outer_join_lowering { + self.enable_new_outer_join_lowering = feature_value; + } + if let Some(feature_value) = overrides.enable_eager_delta_joins { + self.enable_eager_delta_joins = feature_value; + } + self + } +} + /// [`OptimizerConfig`] overrides coming from an [`ExplainContext`]. impl OverrideFrom for OptimizerConfig { fn override_from(mut self, ctx: &ExplainContext) -> Self { @@ -222,6 +288,9 @@ impl OverrideFrom for OptimizerConfig { self.enable_fast_path = !ctx.config.no_fast_path; // Override feature flags that can be enabled in the EXPLAIN config. + if let Some(explain_flag) = ctx.config.reoptimize_imported_views { + self.reoptimize_imported_views = explain_flag; + } if let Some(explain_flag) = ctx.config.enable_new_outer_join_lowering { self.enable_new_outer_join_lowering = explain_flag; } @@ -229,7 +298,7 @@ impl OverrideFrom for OptimizerConfig { self.enable_eager_delta_joins = explain_flag; } - // Return final result. + // Return the final result. self } } diff --git a/src/adapter/src/optimize/peek.rs b/src/adapter/src/optimize/peek.rs index 6dfae4ac22397..10040127f7dd6 100644 --- a/src/adapter/src/optimize/peek.rs +++ b/src/adapter/src/optimize/peek.rs @@ -222,7 +222,7 @@ impl<'s> Optimize>> for Optimizer { let mut df_desc = MirDataflowDescription::new(debug_name.to_string()); df_builder.import_view_into_dataflow(&self.select_id, &expr, &mut df_desc)?; - df_builder.reoptimize_imported_views(&mut df_desc, &self.config)?; + df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?; // Resolve all unmaterializable function calls except mz_now(), because // we don't yet have a timestamp. diff --git a/src/adapter/src/optimize/subscribe.rs b/src/adapter/src/optimize/subscribe.rs index 9d8f3b1c158b5..3c96d3c67e091 100644 --- a/src/adapter/src/optimize/subscribe.rs +++ b/src/adapter/src/optimize/subscribe.rs @@ -236,7 +236,7 @@ impl Optimize for Optimizer { let mut df_desc = MirDataflowDescription::new(sink_name); df_builder.import_view_into_dataflow(&from_id, &expr, &mut df_desc)?; - df_builder.reoptimize_imported_views(&mut df_desc, &self.config)?; + df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?; let df_meta = df_builder.build_sink_dataflow_into(&mut df_desc, from_id, sink_desc)?; diff --git a/src/adapter/src/optimize/view.rs b/src/adapter/src/optimize/view.rs index 17cfca2ecff14..44606ceb2e725 100644 --- a/src/adapter/src/optimize/view.rs +++ b/src/adapter/src/optimize/view.rs @@ -13,7 +13,7 @@ use mz_expr::OptimizedMirRelationExpr; use mz_sql::plan::HirRelationExpr; use mz_transform::typecheck::{empty_context, SharedContext as TypecheckContext}; -use crate::optimize::{optimize_mir_local, Optimize, OptimizerConfig, OptimizerError}; +use crate::optimize::{optimize_mir_local, trace_plan, Optimize, OptimizerConfig, OptimizerError}; pub struct Optimizer { /// A typechecking context to use throughout the optimizer pipeline. @@ -35,6 +35,9 @@ impl Optimize for Optimizer { type To = OptimizedMirRelationExpr; fn optimize(&mut self, expr: HirRelationExpr) -> Result { + // Trace the pipeline input under `optimize/raw`. + trace_plan!(at: "raw", &expr); + // HIR ⇒ MIR lowering and decorrelation let expr = expr.lower(&self.config)?; diff --git a/src/buf.yaml b/src/buf.yaml index 0e373965ab370..a1db7969f8efa 100644 --- a/src/buf.yaml +++ b/src/buf.yaml @@ -28,6 +28,8 @@ breaking: # reason: does currently not require backward-compatibility - catalog/protos/objects_v46.proto # reason: does currently not require backward-compatibility + - catalog/protos/objects_v47.proto + # reason: does currently not require backward-compatibility - cluster-client/src/client.proto # reason: does currently not require backward-compatibility - compute-client/src/logging.proto diff --git a/src/catalog/protos/hashes.json b/src/catalog/protos/hashes.json index a999f177bc7df..d55339e62f7d8 100644 --- a/src/catalog/protos/hashes.json +++ b/src/catalog/protos/hashes.json @@ -1,7 +1,7 @@ [ { "name": "objects.proto", - "md5": "531d0dc14d10a018edd65096e75b42e3" + "md5": "05e754e13c48a5d9ed134930a40af6e3" }, { "name": "objects_v42.proto", @@ -22,5 +22,9 @@ { "name": "objects_v46.proto", "md5": "d5d7092a8001d81e73448c07b9c2717f" + }, + { + "name": "objects_v47.proto", + "md5": "5cd2d06907da6131b05c01dcbd2064f0" } ] diff --git a/src/catalog/protos/objects.proto b/src/catalog/protos/objects.proto index 74330e2cc5649..6e14dd72c004d 100644 --- a/src/catalog/protos/objects.proto +++ b/src/catalog/protos/objects.proto @@ -315,6 +315,11 @@ message ReplicaMergeEffort { uint32 effort = 1; } +message OptimizerFeatureOverride { + string name = 1; + string value = 2; +} + message ClusterConfig { message ManagedCluster { string size = 1; @@ -323,6 +328,7 @@ message ClusterConfig { ReplicaLogging logging = 4; ReplicaMergeEffort idle_arrangement_merge_effort = 5; bool disk = 6; + repeated OptimizerFeatureOverride optimizer_feature_overrides = 7; } oneof variant { diff --git a/src/catalog/protos/objects_v47.proto b/src/catalog/protos/objects_v47.proto new file mode 100644 index 0000000000000..32dcae1d036ee --- /dev/null +++ b/src/catalog/protos/objects_v47.proto @@ -0,0 +1,788 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +// This protobuf file defines the types we store in the Stash. +// +// Before and after modifying this file, make sure you have a snapshot of the before version, +// e.g. a copy of this file named 'objects_v{CATALOG_VERSION}.proto', and a snapshot of the file +// after your modifications, e.g. 'objects_v{CATALOG_VERSION + 1}.proto'. Then you can write a +// migration using these two files, and no matter how they types change in the future, we'll always +// have these snapshots to facilitate the migration. + +// buf breaking: ignore (does currently not require backward-compatibility) + +syntax = "proto3"; + +package objects_v47; + +message ConfigKey { + string key = 1; +} + +message ConfigValue { + uint64 value = 1; +} + +message SettingKey { + string name = 1; +} + +message SettingValue { + string value = 1; +} + +message IdAllocKey { + string name = 1; +} + +message IdAllocValue { + uint64 next_id = 1; +} + +message GidMappingKey { + string schema_name = 1; + CatalogItemType object_type = 2; + string object_name = 3; +} + +message GidMappingValue { + uint64 id = 1; + string fingerprint = 2; +} + +message ClusterKey { + ClusterId id = 1; +} + +message ClusterValue { + reserved 2; + string name = 1; + RoleId owner_id = 3; + repeated MzAclItem privileges = 4; + ClusterConfig config = 5; +} + +message ClusterIntrospectionSourceIndexKey { + ClusterId cluster_id = 1; + string name = 2; +} + +message ClusterIntrospectionSourceIndexValue { + uint64 index_id = 1; +} + +message ClusterReplicaKey { + ReplicaId id = 1; +} + +message ClusterReplicaValue { + ClusterId cluster_id = 1; + string name = 2; + ReplicaConfig config = 3; + RoleId owner_id = 4; +} + +message DatabaseKey { + DatabaseId id = 1; +} + +message DatabaseValue { + string name = 1; + RoleId owner_id = 2; + repeated MzAclItem privileges = 3; +} + +message SchemaKey { + SchemaId id = 1; +} + +message SchemaValue { + DatabaseId database_id = 1; + string name = 2; + RoleId owner_id = 3; + repeated MzAclItem privileges = 4; +} + +message ItemKey { + GlobalId gid = 1; +} + +message ItemValue { + SchemaId schema_id = 1; + string name = 2; + CatalogItem definition = 3; + RoleId owner_id = 4; + repeated MzAclItem privileges = 5; +} + +message RoleKey { + RoleId id = 1; +} + +message RoleValue { + string name = 1; + RoleAttributes attributes = 2; + RoleMembership membership = 3; + RoleVars vars = 4; +} + +message TimestampKey { + string id = 1; +} + +message TimestampValue { + Timestamp ts = 1; +} + +message ServerConfigurationKey { + string name = 1; +} + +message ServerConfigurationValue { + string value = 1; +} + +message AuditLogKey { + oneof event { + AuditLogEventV1 v1 = 1; + } +} + +message StorageUsageKey { + message StorageUsageV1 { + uint64 id = 1; + StringWrapper shard_id = 2; + uint64 size_bytes = 3; + EpochMillis collection_timestamp = 4; + } + + oneof usage { + StorageUsageV1 v1 = 1; + } +} + +message CommentKey { + oneof object { + GlobalId table = 1; + GlobalId view = 2; + GlobalId materialized_view = 4; + GlobalId source = 5; + GlobalId sink = 6; + GlobalId index = 7; + GlobalId func = 8; + GlobalId connection = 9; + GlobalId type = 10; + GlobalId secret = 11; + RoleId role = 12; + DatabaseId database = 13; + ResolvedSchema schema = 14; + ClusterId cluster = 15; + ClusterReplicaId cluster_replica = 16; + } + oneof sub_component { + uint64 column_pos = 3; + } +} + +message CommentValue { + string comment = 1; +} + +// ---- Common Types +// +// Note: Normally types like this would go in some sort of `common.proto` file, but we want to keep +// our proto definitions in a single file to make snapshotting easier, hence them living here. + +message Empty { /* purposefully empty */ } + +// In protobuf a "None" string is the same thing as an empty string. To get the same semantics of +// an `Option` from Rust, we need to wrap a string in a message. +message StringWrapper { + string inner = 1; +} + +message Duration { + uint64 secs = 1; + uint32 nanos = 2; +} + +message EpochMillis { + uint64 millis = 1; +} + +// Opaque timestamp type that is specific to Materialize. +message Timestamp { + uint64 internal = 1; +} + +enum CatalogItemType { + CATALOG_ITEM_TYPE_UNKNOWN = 0; + CATALOG_ITEM_TYPE_TABLE = 1; + CATALOG_ITEM_TYPE_SOURCE = 2; + CATALOG_ITEM_TYPE_SINK = 3; + CATALOG_ITEM_TYPE_VIEW = 4; + CATALOG_ITEM_TYPE_MATERIALIZED_VIEW = 5; + CATALOG_ITEM_TYPE_INDEX = 6; + CATALOG_ITEM_TYPE_TYPE = 7; + CATALOG_ITEM_TYPE_FUNC = 8; + CATALOG_ITEM_TYPE_SECRET = 9; + CATALOG_ITEM_TYPE_CONNECTION = 10; +} + +message CatalogItem { + message V1 { + string create_sql = 1; + } + + oneof value { + V1 v1 = 1; + } +} + +message GlobalId { + oneof value { + uint64 system = 1; + uint64 user = 2; + uint64 transient = 3; + Empty explain = 4; + } +} + +message ClusterId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message DatabaseId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message ResolvedDatabaseSpecifier { + oneof spec { + Empty ambient = 1; + DatabaseId id = 2; + } +} + +message SchemaId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message SchemaSpecifier { + oneof spec { + Empty temporary = 1; + SchemaId id = 2; + } +} + +message ResolvedSchema { + ResolvedDatabaseSpecifier database = 1; + SchemaSpecifier schema = 2; +} + +message ReplicaId { + oneof value { + uint64 system = 1; + uint64 user = 2; + } +} + +message ClusterReplicaId { + ClusterId cluster_id = 1; + ReplicaId replica_id = 2; +} + +message ReplicaLogging { + bool log_logging = 1; + Duration interval = 2; +} + +message ReplicaMergeEffort { + uint32 effort = 1; +} + +message OptimizerFeatureOverride { + string name = 1; + string value = 2; +} + +message ClusterConfig { + message ManagedCluster { + string size = 1; + uint32 replication_factor = 2; + repeated string availability_zones = 3; + ReplicaLogging logging = 4; + ReplicaMergeEffort idle_arrangement_merge_effort = 5; + bool disk = 6; + repeated OptimizerFeatureOverride optimizer_feature_overrides = 7; + } + + oneof variant { + Empty unmanaged = 1; + ManagedCluster managed = 2; + } +} + +message ReplicaConfig { + message UnmanagedLocation { + repeated string storagectl_addrs = 1; + repeated string storage_addrs = 2; + repeated string computectl_addrs = 3; + repeated string compute_addrs = 4; + uint64 workers = 5; + } + + message ManagedLocation { + string size = 1; + optional string availability_zone = 2; + bool disk = 4; + bool internal = 5; + optional string billed_as = 6; + } + + oneof location { + UnmanagedLocation unmanaged = 1; + ManagedLocation managed = 2; + } + ReplicaLogging logging = 3; + ReplicaMergeEffort idle_arrangement_merge_effort = 4; +} + +message RoleId { + oneof value { + uint64 system = 1; + uint64 user = 2; + Empty public = 3; + } +} + +message RoleAttributes { + bool inherit = 1; +} + +message RoleMembership { + message Entry { + RoleId key = 1; + RoleId value = 2; + } + + repeated Entry map = 1; +} + +message RoleVars { + message SqlSet { + repeated string entries = 1; + } + + message Entry { + string key = 1; + oneof val { + string flat = 2; + SqlSet sql_set = 3; + } + } + + repeated Entry entries = 1; +} + +message AclMode { + // A bit flag representing all the privileges that can be granted to a role. + uint64 bitflags = 1; +} + +message MzAclItem { + RoleId grantee = 1; + RoleId grantor = 2; + AclMode acl_mode = 3; +} + +enum ObjectType { + OBJECT_TYPE_UNKNOWN = 0; + OBJECT_TYPE_TABLE = 1; + OBJECT_TYPE_VIEW = 2; + OBJECT_TYPE_MATERIALIZED_VIEW = 3; + OBJECT_TYPE_SOURCE = 4; + OBJECT_TYPE_SINK = 5; + OBJECT_TYPE_INDEX = 6; + OBJECT_TYPE_TYPE = 7; + OBJECT_TYPE_ROLE = 8; + OBJECT_TYPE_CLUSTER = 9; + OBJECT_TYPE_CLUSTER_REPLICA = 10; + OBJECT_TYPE_SECRET = 11; + OBJECT_TYPE_CONNECTION = 12; + OBJECT_TYPE_DATABASE = 13; + OBJECT_TYPE_SCHEMA = 14; + OBJECT_TYPE_FUNC = 15; +} + +message DefaultPrivilegesKey { + RoleId role_id = 1; + DatabaseId database_id = 2; + SchemaId schema_id = 3; + ObjectType object_type = 4; + RoleId grantee = 5; +} + +message DefaultPrivilegesValue { + AclMode privileges = 1; +} + +message SystemPrivilegesKey { + RoleId grantee = 1; + RoleId grantor = 2; +} + +message SystemPrivilegesValue { + AclMode acl_mode = 1; +} + +message AuditLogEventV1 { + enum EventType { + EVENT_TYPE_UNKNOWN = 0; + EVENT_TYPE_CREATE = 1; + EVENT_TYPE_DROP = 2; + EVENT_TYPE_ALTER = 3; + EVENT_TYPE_GRANT = 4; + EVENT_TYPE_REVOKE = 5; + } + + enum ObjectType { + OBJECT_TYPE_UNKNOWN = 0; + OBJECT_TYPE_CLUSTER = 1; + OBJECT_TYPE_CLUSTER_REPLICA = 2; + OBJECT_TYPE_CONNECTION = 3; + OBJECT_TYPE_DATABASE = 4; + OBJECT_TYPE_FUNC = 5; + OBJECT_TYPE_INDEX = 6; + OBJECT_TYPE_MATERIALIZED_VIEW = 7; + OBJECT_TYPE_ROLE = 8; + OBJECT_TYPE_SECRET = 9; + OBJECT_TYPE_SCHEMA = 10; + OBJECT_TYPE_SINK = 11; + OBJECT_TYPE_SOURCE = 12; + OBJECT_TYPE_TABLE = 13; + OBJECT_TYPE_TYPE = 14; + OBJECT_TYPE_VIEW = 15; + OBJECT_TYPE_SYSTEM = 16; + } + + message IdFullNameV1 { + string id = 1; + FullNameV1 name = 2; + } + + message FullNameV1 { + string database = 1; + string schema = 2; + string item = 3; + } + + message IdNameV1 { + string id = 1; + string name = 2; + } + + message RenameClusterV1 { + string id = 1; + string old_name = 2; + string new_name = 3; + } + + message RenameClusterReplicaV1 { + string cluster_id = 1; + string replica_id = 2; + string old_name = 3; + string new_name = 4; + } + + message RenameItemV1 { + string id = 1; + FullNameV1 old_name = 2; + FullNameV1 new_name = 3; + } + + message CreateClusterReplicaV1 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + string logical_size = 5; + bool disk = 6; + optional string billed_as = 7; + bool internal = 8; + } + + message DropClusterReplicaV1 { + string cluster_id = 1; + string cluster_name = 2; + StringWrapper replica_id = 3; + string replica_name = 4; + } + + message CreateSourceSinkV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper size = 3; + } + + message CreateSourceSinkV2 { + string id = 1; + FullNameV1 name = 2; + StringWrapper size = 3; + string external_type = 4; + } + + message CreateSourceSinkV3 { + string id = 1; + FullNameV1 name = 2; + string external_type = 3; + } + + message AlterSourceSinkV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper old_size = 3; + StringWrapper new_size = 4; + } + + message AlterSetClusterV1 { + string id = 1; + FullNameV1 name = 2; + StringWrapper old_cluster = 3; + StringWrapper new_cluster = 4; + } + + message GrantRoleV1 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + } + + message GrantRoleV2 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + string executed_by = 4; + } + + message RevokeRoleV1 { + string role_id = 1; + string member_id = 2; + } + + message RevokeRoleV2 { + string role_id = 1; + string member_id = 2; + string grantor_id = 3; + string executed_by = 4; + } + + message UpdatePrivilegeV1 { + string object_id = 1; + string grantee_id = 2; + string grantor_id = 3; + string privileges = 4; + } + + message AlterDefaultPrivilegeV1 { + string role_id = 1; + StringWrapper database_id = 2; + StringWrapper schema_id = 3; + string grantee_id= 4; + string privileges = 5; + } + + message UpdateOwnerV1 { + string object_id = 1; + string old_owner_id = 2; + string new_owner_id = 3; + } + + message SchemaV1 { + string id = 1; + string name = 2; + string database_name = 3; + } + + message SchemaV2 { + string id = 1; + string name = 2; + StringWrapper database_name = 3; + } + + message RenameSchemaV1 { + string id = 1; + optional string database_name = 2; + string old_name = 3; + string new_name = 4; + } + + message UpdateItemV1 { + string id = 1; + FullNameV1 name = 2; + } + + uint64 id = 1; + EventType event_type = 2; + ObjectType object_type = 3; + StringWrapper user = 4; + EpochMillis occurred_at = 5; + + // next-id: 29 + oneof details { + CreateClusterReplicaV1 create_cluster_replica_v1 = 6; + DropClusterReplicaV1 drop_cluster_replica_v1 = 7; + CreateSourceSinkV1 create_source_sink_v1 = 8; + CreateSourceSinkV2 create_source_sink_v2 = 9; + AlterSourceSinkV1 alter_source_sink_v1 = 10; + AlterSetClusterV1 alter_set_cluster_v1 = 25; + GrantRoleV1 grant_role_v1 = 11; + GrantRoleV2 grant_role_v2 = 12; + RevokeRoleV1 revoke_role_v1 = 13; + RevokeRoleV2 revoke_role_v2 = 14; + UpdatePrivilegeV1 update_privilege_v1 = 22; + AlterDefaultPrivilegeV1 alter_default_privilege_v1 = 23; + UpdateOwnerV1 update_owner_v1 = 24; + IdFullNameV1 id_full_name_v1 = 15; + RenameClusterV1 rename_cluster_v1 = 20; + RenameClusterReplicaV1 rename_cluster_replica_v1 = 21; + RenameItemV1 rename_item_v1 = 16; + IdNameV1 id_name_v1 = 17; + SchemaV1 schema_v1 = 18; + SchemaV2 schema_v2 = 19; + RenameSchemaV1 rename_schema_v1 = 27; + UpdateItemV1 update_item_v1 = 26; + CreateSourceSinkV3 create_source_sink_v3 = 29; + } +} + +// Wrapper of key-values used by the persist implementation to serialize the catalog. +message StateUpdateKind { + message AuditLog { + AuditLogKey key = 1; + } + + message Cluster { + ClusterKey key = 1; + ClusterValue value = 2; + } + + message ClusterReplica { + ClusterReplicaKey key = 1; + ClusterReplicaValue value = 2; + } + + message Comment { + CommentKey key = 1; + CommentValue value = 2; + } + + message Config { + ConfigKey key = 1; + ConfigValue value = 2; + } + + message Database { + DatabaseKey key = 1; + DatabaseValue value = 2; + } + + message DefaultPrivileges { + DefaultPrivilegesKey key = 1; + DefaultPrivilegesValue value = 2; + } + + message Epoch { + int64 epoch = 1; + } + + message IdAlloc { + IdAllocKey key = 1; + IdAllocValue value = 2; + } + + message ClusterIntrospectionSourceIndex { + ClusterIntrospectionSourceIndexKey key = 1; + ClusterIntrospectionSourceIndexValue value = 2; + } + + message Item { + ItemKey key = 1; + ItemValue value = 2; + } + + message Role { + RoleKey key = 1; + RoleValue value = 2; + } + + message Schema { + SchemaKey key = 1; + SchemaValue value = 2; + } + + message Setting { + SettingKey key = 1; + SettingValue value = 2; + } + + message StorageUsage { + StorageUsageKey key = 1; + } + + message ServerConfiguration { + ServerConfigurationKey key = 1; + ServerConfigurationValue value = 2; + } + + message GidMapping { + GidMappingKey key = 1; + GidMappingValue value = 2; + } + + message SystemPrivileges { + SystemPrivilegesKey key = 1; + SystemPrivilegesValue value = 2; + } + + message Timestamp { + TimestampKey key = 1; + TimestampValue value = 2; + } + + oneof kind { + AuditLog audit_log = 1; + Cluster cluster = 2; + ClusterReplica cluster_replica = 3; + Comment comment = 4; + Config config = 5; + Database database = 6; + DefaultPrivileges default_privileges = 7; + Epoch epoch = 8; + IdAlloc id_alloc = 9; + ClusterIntrospectionSourceIndex cluster_introspection_source_index = 10; + Item item = 11; + Role role = 12; + Schema schema = 13; + Setting setting = 14; + StorageUsage storage_usage = 15; + ServerConfiguration server_configuration = 16; + GidMapping gid_mapping = 17; + SystemPrivileges system_privileges = 18; + Timestamp timestamp = 19; + } +} diff --git a/src/catalog/src/durable/initialize.rs b/src/catalog/src/durable/initialize.rs index 0fffb105c1069..42dcc96951805 100644 --- a/src/catalog/src/durable/initialize.rs +++ b/src/catalog/src/durable/initialize.rs @@ -628,6 +628,7 @@ fn default_cluster_config(args: &BootstrapArgs) -> ClusterConfig { }, idle_arrangement_merge_effort: None, disk: false, + optimizer_feature_overrides: Default::default(), }), } } diff --git a/src/catalog/src/durable/objects.rs b/src/catalog/src/durable/objects.rs index a48833012bf25..bdd9d74bcf1ba 100644 --- a/src/catalog/src/durable/objects.rs +++ b/src/catalog/src/durable/objects.rs @@ -198,6 +198,7 @@ pub struct ClusterVariantManaged { pub idle_arrangement_merge_effort: Option, pub replication_factor: u32, pub disk: bool, + pub optimizer_feature_overrides: BTreeMap, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/catalog/src/durable/objects/serialization.rs b/src/catalog/src/durable/objects/serialization.rs index ce43d288be4d3..561c8eb59653d 100644 --- a/src/catalog/src/durable/objects/serialization.rs +++ b/src/catalog/src/durable/objects/serialization.rs @@ -20,7 +20,7 @@ use mz_audit_log::{ use mz_compute_client::controller::ComputeReplicaLogging; use mz_controller_types::ReplicaId; use mz_ore::cast::CastFrom; -use mz_proto::{IntoRustIfSome, ProtoType, RustType, TryFromProtoError}; +use mz_proto::{IntoRustIfSome, ProtoMapEntry, ProtoType, RustType, TryFromProtoError}; use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem}; use mz_repr::role_id::RoleId; use mz_repr::{GlobalId, Timestamp}; @@ -64,6 +64,19 @@ impl TryFrom for proto::StateUpdateKind { } } +impl ProtoMapEntry for proto::OptimizerFeatureOverride { + fn from_rust<'a>(entry: (&'a String, &'a String)) -> Self { + proto::OptimizerFeatureOverride { + name: entry.0.into_proto(), + value: entry.1.into_proto(), + } + } + + fn into_rust(self) -> Result<(String, String), TryFromProtoError> { + Ok((self.name.into_rust()?, self.value.into_rust()?)) + } +} + impl RustType for ClusterConfig { fn into_proto(&self) -> proto::ClusterConfig { proto::ClusterConfig { @@ -88,6 +101,7 @@ impl RustType for ClusterVariant { idle_arrangement_merge_effort, replication_factor, disk, + optimizer_feature_overrides, }) => proto::cluster_config::Variant::Managed(proto::cluster_config::ManagedCluster { size: size.to_string(), availability_zones: availability_zones.clone(), @@ -96,6 +110,7 @@ impl RustType for ClusterVariant { .map(|effort| proto::ReplicaMergeEffort { effort }), replication_factor: *replication_factor, disk: *disk, + optimizer_feature_overrides: optimizer_feature_overrides.into_proto(), }), ClusterVariant::Unmanaged => proto::cluster_config::Variant::Unmanaged(proto::Empty {}), } @@ -116,6 +131,7 @@ impl RustType for ClusterVariant { .map(|e| e.effort), replication_factor: managed.replication_factor, disk: managed.disk, + optimizer_feature_overrides: managed.optimizer_feature_overrides.into_rust()?, })) } } diff --git a/src/catalog/src/durable/upgrade.rs b/src/catalog/src/durable/upgrade.rs index 68f854a35d782..44d8bf8d8b000 100644 --- a/src/catalog/src/durable/upgrade.rs +++ b/src/catalog/src/durable/upgrade.rs @@ -161,14 +161,14 @@ macro_rules! objects { } } -objects!(v42, v43, v44, v45, v46); +objects!(v42, v43, v44, v45, v46, v47); /// The current version of the `Catalog`. /// /// We will initialize new `Catalog`es with this version, and migrate existing `Catalog`es to this /// version. Whenever the `Catalog` changes, e.g. the protobufs we serialize in the `Catalog` /// change, we need to bump this version. -pub const CATALOG_VERSION: u64 = 46; +pub const CATALOG_VERSION: u64 = 47; /// The minimum `Catalog` version number that we support migrating from. /// @@ -196,6 +196,7 @@ pub(crate) mod stash { mod v43_to_v44; mod v44_to_v45; mod v45_to_v46; + mod v46_to_v47; #[mz_ore::instrument(name = "stash::upgrade", level = "debug")] pub(crate) async fn upgrade(stash: &mut Stash) -> Result<(), StashError> { @@ -223,6 +224,7 @@ pub(crate) mod stash { 43 => v43_to_v44::upgrade(), 44 => v44_to_v45::upgrade(&tx).await?, 45 => v45_to_v46::upgrade(&tx).await?, + 46 => v46_to_v47::upgrade(&tx).await?, // Up-to-date, no migration needed! CATALOG_VERSION => return Ok(CATALOG_VERSION), @@ -302,6 +304,7 @@ pub(crate) mod persist { mod v43_to_v44; mod v44_to_v45; mod v45_to_v46; + mod v46_to_v47; /// Describes a single action to take during a migration from `V1` to `V2`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -413,6 +416,15 @@ pub(crate) mod persist { ) .await } + 46 => { + run_versioned_upgrade( + unopened_catalog_state, + mode, + version, + v46_to_v47::upgrade, + ) + .await + } // Up-to-date, no migration needed! CATALOG_VERSION => Ok(CATALOG_VERSION), diff --git a/src/catalog/src/durable/upgrade/persist/v46_to_v47.rs b/src/catalog/src/durable/upgrade/persist/v46_to_v47.rs new file mode 100644 index 0000000000000..1dfbdd9ccbe50 --- /dev/null +++ b/src/catalog/src/durable/upgrade/persist/v46_to_v47.rs @@ -0,0 +1,205 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use mz_stash::upgrade::WireCompatible; + +use crate::durable::upgrade::persist::MigrationAction; +use crate::durable::upgrade::{objects_v46 as v46, objects_v47 as v47}; + +/// Introduce empty `optimizer_feature_overrides` in `ManagedCluster`'s. +pub fn upgrade( + snapshot: Vec, +) -> Vec> { + snapshot + .into_iter() + .filter_map(|update| { + let update = update.kind.as_ref().expect("missing field"); + let v46::state_update_kind::Kind::Cluster(update) = update else { + return None; + }; + if !update.is_managed() { + return None; + }; + + let old = v46::StateUpdateKind { + kind: Some(v46::state_update_kind::Kind::Cluster( + v46::state_update_kind::Cluster { + key: update.key.clone(), + value: update.value.clone(), + }, + )), + }; + + let new = v47::StateUpdateKind { + kind: Some(v47::state_update_kind::Kind::Cluster( + v47::state_update_kind::Cluster { + key: update.key.as_ref().map(WireCompatible::convert), + value: update.value.as_ref().map(|old_val| v47::ClusterValue { + name: old_val.name.clone(), + owner_id: old_val.owner_id.as_ref().map(WireCompatible::convert), + privileges: old_val + .privileges + .iter() + .map(WireCompatible::convert) + .collect(), + config: old_val.config.as_ref().map(|config| v47::ClusterConfig { + variant: config.variant.as_ref().map(|variant| match variant { + v46::cluster_config::Variant::Unmanaged(_) => { + v47::cluster_config::Variant::Unmanaged(v47::Empty {}) + } + v46::cluster_config::Variant::Managed(c) => { + v47::cluster_config::Variant::Managed( + v47::cluster_config::ManagedCluster { + size: c.size.clone(), + replication_factor: c.replication_factor, + availability_zones: c.availability_zones.clone(), + logging: c + .logging + .as_ref() + .map(WireCompatible::convert), + idle_arrangement_merge_effort: c + .idle_arrangement_merge_effort + .as_ref() + .map(WireCompatible::convert), + disk: c.disk, + optimizer_feature_overrides: Vec::new(), + }, + ) + } + }), + }), + }), + }, + )), + }; + + Some(MigrationAction::Update(old, new)) + }) + .collect() +} + +impl v46::state_update_kind::Cluster { + fn is_managed(&self) -> bool { + let Some(cluster) = self.value.as_ref() else { + return false; + }; + let Some(config) = cluster.config.as_ref() else { + return false; + }; + match config.variant.as_ref() { + Some(v46::cluster_config::Variant::Managed(_)) => true, + _ => false, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` on OS `linux` + async fn smoke_test_migration() { + let v46 = v46::StateUpdateKind { + kind: Some(v46::state_update_kind::Kind::Cluster( + v46::state_update_kind::Cluster { + key: Some(v46::ClusterKey { + id: Some(v46::ClusterId { + value: Some(v46::cluster_id::Value::User(Default::default())), + }), + }), + value: Some(v46::ClusterValue { + name: Default::default(), + owner_id: Some(v46::RoleId { + value: Some(v46::role_id::Value::Public(Default::default())), + }), + privileges: vec![], + config: Some(v46::ClusterConfig { + variant: Some(v46::cluster_config::Variant::Managed( + v46::cluster_config::ManagedCluster { + size: String::from("1cc"), + replication_factor: 2, + availability_zones: vec![ + String::from("az1"), + String::from("az2"), + ], + logging: Some(v46::ReplicaLogging { + log_logging: true, + interval: Some(v46::Duration { + secs: 3600, + nanos: 747, + }), + }), + idle_arrangement_merge_effort: Some(v46::ReplicaMergeEffort { + effort: 42, + }), + disk: true, + }, + )), + }), + }), + }, + )), + }; + + let v47 = v47::StateUpdateKind { + kind: Some(v47::state_update_kind::Kind::Cluster( + v47::state_update_kind::Cluster { + key: Some(v47::ClusterKey { + id: Some(v47::ClusterId { + value: Some(v47::cluster_id::Value::User(Default::default())), + }), + }), + value: Some(v47::ClusterValue { + name: Default::default(), + owner_id: Some(v47::RoleId { + value: Some(v47::role_id::Value::Public(Default::default())), + }), + privileges: vec![], + config: Some(v47::ClusterConfig { + variant: Some(v47::cluster_config::Variant::Managed( + v47::cluster_config::ManagedCluster { + size: String::from("1cc"), + replication_factor: 2, + availability_zones: vec![ + String::from("az1"), + String::from("az2"), + ], + logging: Some(v47::ReplicaLogging { + log_logging: true, + interval: Some(v47::Duration { + secs: 3600, + nanos: 747, + }), + }), + idle_arrangement_merge_effort: Some(v47::ReplicaMergeEffort { + effort: 42, + }), + disk: true, + optimizer_feature_overrides: Vec::new(), + }, + )), + }), + }), + }, + )), + }; + + let actions = upgrade(vec![v46.clone()]); + + match &actions[..] { + [MigrationAction::Update(old, new)] => { + assert_eq!(old, &v46); + assert_eq!(new, &v47); + } + o => panic!("expected single MigrationAction::Update, got {:?}", o), + } + } +} diff --git a/src/catalog/src/durable/upgrade/snapshots/objects_v47.txt b/src/catalog/src/durable/upgrade/snapshots/objects_v47.txt new file mode 100644 index 0000000000000..9d4accaf4f468 --- /dev/null +++ b/src/catalog/src/durable/upgrade/snapshots/objects_v47.txt @@ -0,0 +1,100 @@ +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 +Ck0KS7oBSAoJCgNrZXkSAggEChsKBGtpbmQSE0IRRGVmYXVsdFByaXZpbGVnZXMKHgoFdmFsdWUSFboBEgoQCgpwcml2aWxlZ2VzEgIIBA== +CjAKLroBKwoYCgVlcG9jaBIPwgEMCgpQhyQ1kYMROAIdCg8KBGtpbmQSB0IFRXBvY2g= +CjIKMLoBLQoXCgNrZXkSELoBDQoLCgVldmVudBICCAQKEgoEa2luZBIKQghBdWRpdExvZw== +CkoKSLoBRQoJCgNrZXkSAggEChAKBGtpbmQSCEIGQ29uZmlnCiYKBXZhbHVlEh26ARoKGAoFdmFsdWUSD8IBDAoKNGI0NheFhUckLA== +CmUKY7oBYAo+CgNrZXkSN7oBNAoyCgJpZBIsugEpCicKBXZhbHVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCldJNFCARIAiY5wKEQoEa2luZBIJQgdDbHVzdGVyCgsKBXZhbHVlEgIIBA== +CogBCoUBugGBAQoiCgNrZXkSG7oBGAoWCgJpZBIQugENCgsKBXZhbHVlEgIIBAoOCgRraW5kEgZCBFJvbGUKSwoFdmFsdWUSQroBPwoQCgphdHRyaWJ1dGVzEgIIBAoQCgptZW1iZXJzaGlwEgIIBAoNCgRuYW1lEgVCA15YKAoKCgR2YXJzEgIIBA== +CmcKZboBYgpACgNrZXkSOboBNgo0CgRuYW1lEixCKmBMI/CflbREJyZ7dVkl6qyFRfCWqaE/dFw6wqVVXe++gzw6cuqVmeCyhgoRCgRraW5kEglCB1NldHRpbmcKCwoFdmFsdWUSAggE +CmgKZroBYwoJCgNrZXkSAggEChoKBGtpbmQSEkIQU3lzdGVtUHJpdmlsZWdlcwo6CgV2YWx1ZRIxugEuCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKMFI3YwUIhJI1XA== +Cm8KbboBagoJCgNrZXkSAggEChEKBGtpbmQSCUIHQ29tbWVudApKCgV2YWx1ZRJBugE+CjwKB2NvbW1lbnQSMUIvJvCWq4Yn77+9JdS+8J2qrSzhsod077ex4K6qMPCQjJrDu+GkiVjIuvCflbQuPSc= +CuoBCucBugHjAQqDAQoDa2V5Eny6AXkKGwoLb2JqZWN0X25hbWUSDEIKOci6dSR84aGBKgokCgtvYmplY3RfdHlwZRIVwgESCgUCGFYjfBD///////////8BCjQKC3NjaGVtYV9uYW1lEiVCI+CoiXdlKvCQlbXwn5W08J+VtOOIh+GltFlxJirgsbl54KaQChQKBGtpbmQSDEIKR2lkTWFwcGluZwpFCgV2YWx1ZRI8ugE5CiAKC2ZpbmdlcnByaW50EhFCDz/gqpFa4oCJyLohZuCroQoVCgJpZBIPwgEMCgqSVWGDWUEIFYVs +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KCGFjbF9tb2RlEgIIBAobCgdncmFudGVlEhC6AQ0KCwoFdmFsdWUSAggECisKB2dyYW50b3ISILoBHQobCgV2YWx1ZRISugEPCg0KBlB1YmxpYxIDugEACnK6AW8KDgoIYWNsX21vZGUSAggEChsKB2dyYW50ZWUSELoBDQoLCgV2YWx1ZRICCAQKQAoHZ3JhbnRvchI1ugEyCjAKBXZhbHVlEie6ASQKIgoEVXNlchIawgEXCgoIBpcZZTkDUXdsEP///////////wEKaLoBZQoOCghhY2xfbW9kZRICCAQKNgoHZ3JhbnRlZRIrugEoCiYKBXZhbHVlEh26ARoKGAoEVXNlchIQwgENCgsBhChiZyBVIWVUbAobCgdncmFudG9yEhC6AQ0KCwoFdmFsdWUSAggECnu6AXgKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBQEUlYUKRAJcDLAo4CgdncmFudGVlEi26ASoKKAoFdmFsdWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLASZ3FnYokiZVRnwKDQoHZ3JhbnRvchICCAQKgQG6AX4KNgoIYWNsX21vZGUSKroBJwolCghiaXRmbGFncxIZwgEWCgmHEhSCJAmRCWwQ/v//////////AQoNCgdncmFudGVlEgIIBAo1CgdncmFudG9yEiq6AScKJQoFdmFsdWUSHLoBGQoXCgRVc2VyEg/CAQwKCiJVADaBEhgJYkw= +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHSWRBbGxvYwoLCgV2YWx1ZRICCAQ= +CpwBCpkBugGVAQpHCgNrZXkSQLoBPQo7CgJpZBI1QjMqMPCdgow8evCQrofwlquzeyo4a1cm6p2lWuC6gi7wlry58J2NrvCflbTwkKCIL/CdhooKEwoEa2luZBILQglUaW1lc3RhbXAKNQoFdmFsdWUSLLoBKQonCgJ0cxIhugEeChwKCGludGVybmFsEhDCAQ0KCwEBNHczgWV3UVWM +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHSWRBbGxvYwoLCgV2YWx1ZRICCAQ= +CjIKMLoBLQoJCgNrZXkSAggEChMKBGtpbmQSC0IJVGltZXN0YW1wCgsKBXZhbHVlEgIIBA== +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHU2V0dGluZwoLCgV2YWx1ZRICCAQ= +CtUBCtIBugHOAQqoAQoDa2V5EqABugGcAQpBCgtvYmplY3RfbmFtZRIyQjAsUS5D4KedW+KvqvCfrLJg6qmJP+KAtynwkKO78JG2kWw94Kqywr3wkLOeIuqfk3sKGQoLb2JqZWN0X3R5cGUSCsIBBwoFlJNlIBwKPAoLc2NoZW1hX25hbWUSLUIrOuCxoeCogu+/vSlbdOC3ni9uL04q4KeX4aSzN2jwnZSiwqU6cjpf8J+VtAoUCgRraW5kEgxCCkdpZE1hcHBpbmcKCwoFdmFsdWUSAggE +CpwBCpkBugGVAQpLCgNrZXkSRLoBQQo/CgRuYW1lEjdCNeC3rHvgsJDwkJa8WnZ9KvCRnKEnVuCnneCqiGIh8KyJhWLbufCflbTgqrfwnamUYCXgu4lXChEKBGtpbmQSCUIHSWRBbGxvYwozCgV2YWx1ZRIqugEnCiUKB25leHRfaWQSGsIBFwoKEDQEGElkWQF5HBD///////////8B +ClkKV7oBVAoJCgNrZXkSAggECh0KBGtpbmQSFUITU2VydmVyQ29uZmlndXJhdGlvbgooCgV2YWx1ZRIfugEcChoKBXZhbHVlEhFCD/CbsbhOfvCRg7PwkZmRSg== +CjAKLroBKwoYCgVlcG9jaBIPwgEMCgoVCUBGl0Vgc2hMCg8KBGtpbmQSB0IFRXBvY2g= +Ci8KLboBKgoJCgNrZXkSAggEChAKBGtpbmQSCEIGQ29uZmlnCgsKBXZhbHVlEgIIBA== +CjAKLroBKwoYCgVlcG9jaBIPwgEMCgoEZkIJSIhGFGlMCg8KBGtpbmQSB0IFRXBvY2g= +CjoKOLoBNQoJCgNrZXkSAggEChsKBGtpbmQSE0IRRGVmYXVsdFByaXZpbGVnZXMKCwoFdmFsdWUSAggE +CjsKOboBNgojCgVlcG9jaBIawgEXCgoCECiHWWmWNmlsEP///////////wEKDwoEa2luZBIHQgVFcG9jaA== +CokBCoYBugGCAQooCgNrZXkSIboBHgoNCgdncmFudGVlEgIIBAoNCgdncmFudG9yEgIIBAoaCgRraW5kEhJCEFN5c3RlbVByaXZpbGVnZXMKOgoFdmFsdWUSMboBLgosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCmcgGIMFJ2hjWYw= +CmsKaboBZgoJCgNrZXkSAggEChsKBGtpbmQSE0IRRGVmYXVsdFByaXZpbGVnZXMKPAoFdmFsdWUSM7oBMAouCgpwcml2aWxlZ2VzEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKMVGECGZURpOWjA== +CjkKN7oBNAoVCgNrZXkSDroBCwoJCgNnaWQSAggECg4KBGtpbmQSBkIESXRlbQoLCgV2YWx1ZRICCAQ= +Cm4KbLoBaQoJCgNrZXkSAggEChEKBGtpbmQSCUIHQ29tbWVudApJCgV2YWx1ZRJAugE9CjsKB2NvbW1lbnQSMEIuOe+/vcOSIicxPHQm8J64g+C1jDXhn6UxMUfwkaaleuCssy894Kaqwrrgp4FvaQ== +CnIKcLoBbQoJCgNrZXkSAggEChEKBGtpbmQSCUIHU2V0dGluZwpNCgV2YWx1ZRJEugFBCj8KBXZhbHVlEjZCNPCQqZZyWkjwkYyyZvCQlLDguoEiyLrguIhR8JCetS/CpSrwn4mE8K6SlSfwkr+WKfCRjbQ= +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHQ2x1c3RlcgoLCgV2YWx1ZRICCAQ= +CjMKMboBLgoJCgNrZXkSAggEChQKBGtpbmQSDEIKR2lkTWFwcGluZwoLCgV2YWx1ZRICCAQ= +CjsKOboBNgojCgVlcG9jaBIawgEXCgoDgnBDAjRlJnF8EP///////////wEKDwoEa2luZBIHQgVFcG9jaA== +Ci0KK7oBKAoJCgNrZXkSAggECg4KBGtpbmQSBkIESXRlbQoLCgV2YWx1ZRICCAQ= +CjwKOroBNwoJCgNrZXkSAggECh0KBGtpbmQSFUITU2VydmVyQ29uZmlndXJhdGlvbgoLCgV2YWx1ZRICCAQ= +CoBHCv1GugH5RgoJCgNrZXkSAggEChAKBGtpbmQSCEIGU2NoZW1hCtlGCgV2YWx1ZRLPRroBy0YKOwoLZGF0YWJhc2VfaWQSLLoBKQonCgV2YWx1ZRIeugEbChkKBlN5c3RlbRIPwgEMCgonZAZWA0VgCZIcChwKBG5hbWUSFEIS4KaQ4KqDJOCyo/CepZNgezw+Cg4KCG93bmVyX2lkEgIIBArdRQoKcHJpdmlsZWdlcxLORbIBykUKT7oBTAoOCghhY2xfbW9kZRICCAQKKwoHZ3JhbnRlZRIgugEdChsKBXZhbHVlEhK6AQ8KDQoGUHVibGljEgO6AQAKDQoHZ3JhbnRvchICCAQKhAG6AYABCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKCCIkA3EilmaCHBD///////////8BCg0KB2dyYW50ZWUSAggECjYKB2dyYW50b3ISK7oBKAomCgV2YWx1ZRIdugEaChgKBFVzZXISEMIBDQoLATFxJJmXllmQeYwKXboBWgosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCilgYDRoKZOEAkwKDQoHZ3JhbnRlZRICCAQKGwoHZ3JhbnRvchIQugENCgsKBXZhbHVlEgIIBApPugFMCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKEHMRc0GVk2YgbAoNCgdncmFudGVlEgIIBAoNCgdncmFudG9yEgIIBApQugFNCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWCSdZdGQYcneGwKDQoHZ3JhbnRlZRICCAQKDQoHZ3JhbnRvchICCAQKP7oBPAoOCghhY2xfbW9kZRICCAQKDQoHZ3JhbnRlZRICCAQKGwoHZ3JhbnRvchIQugENCgsKBXZhbHVlEgIIBAo/ugE8Cg4KCGFjbF9tb2RlEgIIBAoNCgdncmFudGVlEgIIBAobCgdncmFudG9yEhC6AQ0KCwoFdmFsdWUSAggECk+6AUwKDgoIYWNsX21vZGUSAggECg0KB2dyYW50ZWUSAggECisKB2dyYW50b3ISILoBHQobCgV2YWx1ZRISugEPCg0KBlB1YmxpYxIDugEAClC6AU0KLQoIYWNsX21vZGUSIboBHgocCghiaXR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mbGFncxIPwgEMCgomVJZHAhYANQQcCg0KB2dyYW50ZWUSAggECjUKB2dyYW50b3ISKroBJwolCgV2YWx1ZRIcugEZChcKBFVzZXISD8IBDAoKUDCTEQgZZ3FnjA== +Ci0KK7oBKAoJCgNrZXkSAggECg4KBGtpbmQSBkIEUm9sZQoLCgV2YWx1ZRICCAQ= +ClwKWroBVwoJCgNrZXkSAggECh0KBGtpbmQSFUITU2VydmVyQ29uZmlndXJhdGlvbgorCgV2YWx1ZRIiugEfCh0KBXZhbHVlEhRCEjt3ceG9rOCxnT/vtIsv8JCokA== +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHQ2x1c3RlcgoLCgV2YWx1ZRICCAQ= +CjIKMLoBLQoXCgNrZXkSELoBDQoLCgVldmVudBICCAQKEgoEa2luZBIKQghBdWRpdExvZw== +CigKJroBIwoJCgNrZXkSAggEChYKBGtpbmQSDkIMU3RvcmFnZVVzYWdl +Ck0KS7oBSAomCgNrZXkSH7oBHAoaCgRuYW1lEhJCEPCWq57gtL8nwqXgt5zvrYQKEQoEa2luZBIJQgdJZEFsbG9jCgsKBXZhbHVlEgIIBA== +CksKSboBRgoJCgNrZXkSAggEChAKBGtpbmQSCEIGQ29uZmlnCicKBXZhbHVlEh66ARsKGQoFdmFsdWUSEMIBDQoLAXYEV3UWAWEFCDw= +CqACCp0CugGZAgqpAQoDa2V5EqEBugGdAQpbCgtvYmplY3RfbmFtZRJMQkrRqPCflbQq8JCilz3wlr+w4ri18K+ogT8n8J6Fj/CdiYQuXNGo8JGCksKlcCUyPPCQhIDwnZGY4rWv0ajIuiNc8Jq/scKl8JCnhwoZCgtvYmplY3RfdHlwZRIKwgEHCgWUUkgoPAojCgtzY2hlbWFfbmFtZRIUQhJ78J+VtC/bgeCwsyc3by5rIigKFAoEa2luZBIMQgpHaWRNYXBwaW5nClUKBXZhbHVlEky6AUkKLwoLZmluZ2VycHJpbnQSIEIeO/CfoqDgsZXhv5rgs4hY8JGkk/CfqobwkKKq4KCwChYKAmlkEhDCAQ0KCwEUh3SZgVEZJDWc +Cm0Ka7oBaAo/CgNrZXkSOLoBNQozCgJpZBItugEqCigKBXZhbHVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwE1KIiAgSVDgGJcChgKBGtpbmQSEEIOQ2x1c3RlclJlcGxpY2EKCwoFdmFsdWUSAggE +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XYCR9M34lS++/m/CQnaUKNUIzbeCwi+GKtF7wnri76qmR77+9IiTwkLyAdeKDp+CzsfCflbTwkKuY8Ja8iFx2Ljrvv70lCgpCCHteL3pCdFw3Cj9CPS3CrOGqh3Pqr4gn8JCsktGoOmAq8JGxo/CRpLg/RD/hvJwl4Z+n4LK2POGgmeG+hzAmfOC+scOE4b+a0agKNUIz6p+z8KS8jD3hsp0/8J+VtO+/vSRK8J65pO+/mj8/MXk/dC8u4b+ZIjp68JCtrkk8PlhcChNCESXwnaqc4qK7LTc7JT/vv717CjpCOOChqkPwn5W0ck46yLotMfCQioLwkr6rJDQl8J+imPCRsqA8K/CQlpVd4Kuj8JCDl+Cov3Qu77+SCkJCQDzDr/Ceo5bwnYSJL33CpSLwkKut4rS3UeK+ruKCpSfwm7G2w4xX8J2coCbwkKeBZ8K7TzxgZ1tjL9GoI/CRpIkKBkIEXOGtkAoTQhE98JCtqfCQvbd1yLpgZnZFdwoxQi9g4Y+58JCuqiVieD3IusOtPVwvcOCwj0/Dq/CRnLrCsD9YMSVzXFNCdPCQrZs/SgokQiJo0ah8772D8JSTtiYkOivwnp+0JD864K6c76ygJfCQrb9cCjNCMeqslj064aWwUGTwkbyAatGo4LGd8J64uSLgqI/gs4TgsYPwlJCW8J+VtHPRqOCzszwKCkII77iH762GJF0KM0IxPPCfgaLhv5rhorDwn5W0YC5xzozwn5W0NSrGjE7RqNGo8Ja8rSZc8JCMiTty4KytPwoGQgTwn4G8CiFCHyTRqNGoQuCuoy/wnrinVPCQgIHgrodQ8J2SqnPhnJEKQkJAZHvwn5W0JfCWq7Hwn66bIuqsg1jwkKmV76y48J+gvMKlJlhb8JCkv+CsuPCfkrQkKi/hv5LcmTzvv73wkLqrJgo7QjngtJBSw6nwm7KD77+9T3vgsaBrO3vwkaeg8JCAl8K78JCol++/vXFZM0x14r+0wqUiQCcmJ+KvmiIKF0IVKtaNXHpgS/Cdkrsq8J+rgi/gqrlICj5CPCTitqPqrJbtnrLwkbyQJuG9iyfwlqu04YC2InslQ+GKtPCegIUuwqUqw7IkVPCRiLvwn5W0PcK24K6IcQooQiYu0ag6e9Go8J+JkPCeuINZUU46wqpgbfCQqJXvrYE28J2UveCuowo1QjPIuuGkt8i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ncmFudGVlEhC6AQ0KCwoFdmFsdWUSAggECg0KB2dyYW50b3ISAggECg8KCXNjaGVtYV9pZBICCAQ= +Cj0KO7oBOAoJCgNrZXkSAggEChMKBGtpbmQSC0IJVGltZXN0YW1wChYKBXZhbHVlEg26AQoKCAoCdHMSAggE +CpYBCpMBugGPAQpKCgNrZXkSQ7oBQAo+CgRuYW1lEjZCNDbgu47wlq2Y4KyB4L2S776LL9Go4KGCwqUxQGDRqPCRpIkm8JGksPCdlYbqrJbCpfCQgZIKHQoEa2luZBIVQhNTZXJ2ZXJDb25maWd1cmF0aW9uCiIKBXZhbHVlEhm6ARYKFAoFdmFsdWUSC0IJwqUyZCXhgqY8 +CpQBCpEBugGNAQpJCgNrZXkSQroBPwo9CgRuYW1lEjVCMzki77+9eyk3WcKlT0HwnYSzJPCRjZcg8JGNsvCRg4IkaSUu4K6/Lsi677iV8J2Rq+C/hAoRCgRraW5kEglCB1NldHRpbmcKLQoFdmFsdWUSJLoBIQofCgV2YWx1ZRIWQhRg4LK8M0XwnZWA4LeG4KyQYHQkKQ== +CpwBCpkBugGVAQpWCgNrZXkST7oBTApKCgRuYW1lEkJCQOCmkDxk8JC+sE/gv4p78JuykeCys2fgqLUmTi7wkaC0Pe+5q+G9tHvhnYDDn+qfk+CnueCvjWLhs4VtIuG9jCcKEQoEa2luZBIJQgdJZEFsbG9jCigKBXZhbHVlEh+6ARwKGgoHbmV4dF9pZBIPwgEMCgqAOZAWIFYwSJks +CnAKbroBawoJCgNrZXkSAggEChQKBGtpbmQSDEIKR2lkTWFwcGluZwpICgV2YWx1ZRI/ugE8CiIKC2ZpbmdlcnByaW50EhNCEeCyvTrigq/vv71uKmAn4LOKChYKAmlkEhDCAQ0KCwExdHAlhwNJAxKM +CigKJroBIwoJCgNrZXkSAggEChYKBGtpbmQSDkIMU3RvcmFnZVVzYWdl +CigKJroBIwoJCgNrZXkSAggEChYKBGtpbmQSDkIMU3RvcmFnZVVzYWdl +CmYKZLoBYQo+CgNrZXkSN7oBNAoyCgJpZBIsugEpCicKBXZhbHVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCiciaCmUiXkkSZwKEgoEa2luZBIKQghEYXRhYmFzZQoLCgV2YWx1ZRICCAQ= +Cj0KO7oBOAoUCgNrZXkSDboBCgoICgJpZBICQgAKEwoEa2luZBILQglUaW1lc3RhbXAKCwoFdmFsdWUSAggE +CiQKIroBHwoJCgNrZXkSAggEChIKBGtpbmQSCkIIQXVkaXRMb2c= +CjIKMLoBLQoJCgNrZXkSAggEChMKBGtpbmQSC0IJVGltZXN0YW1wCgsKBXZhbHVlEgIIBA== +CkgKRroBQwoJCgNrZXkSAggECikKBGtpbmQSIUIfQ2x1c3RlckludHJvc3BlY3Rpb25Tb3VyY2VJbmRleAoLCgV2YWx1ZRICCAQ= +CjAKLroBKwoJCgNrZXkSAggEChEKBGtpbmQSCUIHU2V0dGluZwoLCgV2YWx1ZRICCAQ= diff --git a/src/catalog/src/durable/upgrade/stash/v46_to_v47.rs b/src/catalog/src/durable/upgrade/stash/v46_to_v47.rs new file mode 100644 index 0000000000000..c74742c39f530 --- /dev/null +++ b/src/catalog/src/durable/upgrade/stash/v46_to_v47.rs @@ -0,0 +1,75 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use mz_stash::upgrade::{wire_compatible, MigrationAction, WireCompatible}; +use mz_stash::{Transaction, TypedCollection}; +use mz_stash_types::StashError; + +use crate::durable::upgrade::{objects_v46 as v46, objects_v47 as v47}; + +wire_compatible!(v46::ClusterKey with v47::ClusterKey); +wire_compatible!(v46::MzAclItem with v47::MzAclItem); +wire_compatible!(v46::RoleId with v47::RoleId); +wire_compatible!(v46::ReplicaLogging with v47::ReplicaLogging); +wire_compatible!(v46::ReplicaMergeEffort with v47::ReplicaMergeEffort); + +const CLUSTER_COLLECTION: TypedCollection = + TypedCollection::new("clusters"); + +/// Introduce empty `optimizer_feature_overrides` in `ManagedCluster`'s. +pub async fn upgrade(tx: &Transaction<'_>) -> Result<(), StashError> { + CLUSTER_COLLECTION + .migrate_to::(tx, |entries| { + entries + .iter() + .map(|(old_key, old_val)| { + let new_key = WireCompatible::convert(old_key); + let new_val = v47::ClusterValue { + name: old_val.name.clone(), + owner_id: old_val.owner_id.as_ref().map(WireCompatible::convert), + privileges: old_val + .privileges + .iter() + .map(WireCompatible::convert) + .collect(), + config: old_val.config.as_ref().map(|config| v47::ClusterConfig { + variant: config.variant.as_ref().map(|variant| match variant { + v46::cluster_config::Variant::Unmanaged(_) => { + v47::cluster_config::Variant::Unmanaged(v47::Empty {}) + } + v46::cluster_config::Variant::Managed(c) => { + v47::cluster_config::Variant::Managed( + v47::cluster_config::ManagedCluster { + size: c.size.clone(), + replication_factor: c.replication_factor, + availability_zones: c.availability_zones.clone(), + logging: c + .logging + .as_ref() + .map(WireCompatible::convert), + idle_arrangement_merge_effort: c + .idle_arrangement_merge_effort + .as_ref() + .map(WireCompatible::convert), + disk: c.disk, + optimizer_feature_overrides: Vec::new(), + }, + ) + } + }), + }), + }; + + MigrationAction::Update(old_key.clone(), (new_key, new_val)) + }) + .collect() + }) + .await?; + Ok(()) +} diff --git a/src/catalog/src/memory/objects.rs b/src/catalog/src/memory/objects.rs index e6cba55981093..f2aead5d831cd 100644 --- a/src/catalog/src/memory/objects.rs +++ b/src/catalog/src/memory/objects.rs @@ -30,6 +30,7 @@ use mz_expr::refresh_schedule::RefreshSchedule; use mz_expr::{CollectionPlan, MirScalarExpr, OptimizedMirRelationExpr}; use mz_ore::collections::CollectionExt; use mz_repr::adt::mz_acl_item::{AclMode, PrivilegeMap}; +use mz_repr::optimize::OptimizerFeatureOverrides; use mz_repr::role_id::RoleId; use mz_repr::{GlobalId, RelationDesc}; use mz_sql::ast::display::AstDisplay; @@ -1729,6 +1730,15 @@ pub struct ClusterConfig { pub variant: ClusterVariant, } +impl ClusterConfig { + pub fn features(&self) -> Option<&OptimizerFeatureOverrides> { + match &self.variant { + ClusterVariant::Managed(managed) => Some(&managed.optimizer_feature_overrides), + ClusterVariant::Unmanaged => None, + } + } +} + impl From for durable::ClusterConfig { fn from(config: ClusterConfig) -> Self { Self { @@ -1753,6 +1763,7 @@ pub struct ClusterVariantManaged { pub idle_arrangement_merge_effort: Option, pub replication_factor: u32, pub disk: bool, + pub optimizer_feature_overrides: OptimizerFeatureOverrides, } impl From for durable::ClusterVariantManaged { @@ -1764,6 +1775,7 @@ impl From for durable::ClusterVariantManaged { idle_arrangement_merge_effort: managed.idle_arrangement_merge_effort, replication_factor: managed.replication_factor, disk: managed.disk, + optimizer_feature_overrides: managed.optimizer_feature_overrides.into(), } } } @@ -1777,6 +1789,7 @@ impl From for ClusterVariantManaged { idle_arrangement_merge_effort: managed.idle_arrangement_merge_effort, replication_factor: managed.replication_factor, disk: managed.disk, + optimizer_feature_overrides: managed.optimizer_feature_overrides.into(), } } } diff --git a/src/catalog/tests/snapshots/debug__persist_opened_trace.snap b/src/catalog/tests/snapshots/debug__persist_opened_trace.snap index 4cf57968aa5f8..4c2d011d96d3c 100644 --- a/src/catalog/tests/snapshots/debug__persist_opened_trace.snap +++ b/src/catalog/tests/snapshots/debug__persist_opened_trace.snap @@ -516,6 +516,7 @@ Trace { ), idle_arrangement_merge_effort: None, disk: false, + optimizer_feature_overrides: [], }, ), ), diff --git a/src/catalog/tests/snapshots/debug__stash_opened_trace.snap b/src/catalog/tests/snapshots/debug__stash_opened_trace.snap index 80b05fdfd5970..68f0cd56f0e41 100644 --- a/src/catalog/tests/snapshots/debug__stash_opened_trace.snap +++ b/src/catalog/tests/snapshots/debug__stash_opened_trace.snap @@ -516,6 +516,7 @@ Trace { ), idle_arrangement_merge_effort: None, disk: false, + optimizer_feature_overrides: [], }, ), ), diff --git a/src/catalog/tests/snapshots/open__initial_snapshot.snap b/src/catalog/tests/snapshots/open__initial_snapshot.snap index b0d6dd3ce5f56..2bc218cff4b28 100644 --- a/src/catalog/tests/snapshots/open__initial_snapshot.snap +++ b/src/catalog/tests/snapshots/open__initial_snapshot.snap @@ -978,6 +978,7 @@ Snapshot { ), idle_arrangement_merge_effort: None, disk: false, + optimizer_feature_overrides: [], }, ), ), diff --git a/src/repr/src/explain/mod.rs b/src/repr/src/explain/mod.rs index eebd45eef4548..45881ff2cd90b 100644 --- a/src/repr/src/explain/mod.rs +++ b/src/repr/src/explain/mod.rs @@ -195,6 +195,8 @@ pub struct ExplainConfig { // ------------- // Feature flags // ------------- + /// Re-optimize view imported directly in DataflowDescriptions. + pub reoptimize_imported_views: Option, /// Enable outer join lowering implemented in #22347 and #22348. pub enable_new_outer_join_lowering: Option, /// Enable the eager delta join planning implemented in #23318. @@ -223,6 +225,7 @@ impl Default for ExplainConfig { subtree_size: false, timing: false, types: false, + reoptimize_imported_views: None, enable_new_outer_join_lowering: None, enable_eager_delta_joins: None, } @@ -897,6 +900,7 @@ mod tests { subtree_size: false, timing: true, types: false, + reoptimize_imported_views: None, enable_new_outer_join_lowering: None, enable_eager_delta_joins: None, }; diff --git a/src/repr/src/explain/tracing.rs b/src/repr/src/explain/tracing.rs index 007f7ae440351..42f7de093f8e4 100644 --- a/src/repr/src/explain/tracing.rs +++ b/src/repr/src/explain/tracing.rs @@ -321,8 +321,8 @@ impl PlanTrace { // Compute the path from which we are going to lookup the `UsedIndexes` // instance from the requested path. let path = match NamedPlan::of_path(plan_path) { - Some(NamedPlan::Optimized) => Some(NamedPlan::Optimized), - Some(NamedPlan::Physical) => Some(NamedPlan::Optimized), + Some(NamedPlan::Global) => Some(NamedPlan::Global), + Some(NamedPlan::Physical) => Some(NamedPlan::Global), Some(NamedPlan::FastPath) => Some(NamedPlan::FastPath), _ => None, }; diff --git a/src/repr/src/optimize.rs b/src/repr/src/optimize.rs index 82c53eb0d3e5d..c5af6126aac92 100644 --- a/src/repr/src/optimize.rs +++ b/src/repr/src/optimize.rs @@ -84,6 +84,7 @@ macro_rules! optimizer_feature_flags { optimizer_feature_flags!({ enable_consolidate_after_union_negate: bool, persist_fast_path_limit: usize, + reoptimize_imported_views: bool, enable_new_outer_join_lowering: bool, enable_eager_delta_joins: bool, enable_reduce_mfp_fusion: bool, diff --git a/src/sql-lexer/src/keywords.txt b/src/sql-lexer/src/keywords.txt index 15c0f7b0aa4f9..d8853065ed78d 100644 --- a/src/sql-lexer/src/keywords.txt +++ b/src/sql-lexer/src/keywords.txt @@ -190,6 +190,7 @@ If Ignore Ilike Implementations +Imported In Include Index @@ -231,6 +232,7 @@ Linear List Load Local +Locally Log Logical Login @@ -329,6 +331,7 @@ Regex Region Registry Rename +Reoptimize Repeatable Replace Replan diff --git a/src/sql-parser/src/ast/defs/statement.rs b/src/sql-parser/src/ast/defs/statement.rs index 5b04cfc901500..28d726f8b96f1 100644 --- a/src/sql-parser/src/ast/defs/statement.rs +++ b/src/sql-parser/src/ast/defs/statement.rs @@ -1729,6 +1729,7 @@ impl AstDisplay for ClusterOption { // enum are generated automatically by this crate's `build.rs`. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum ClusterFeatureName { + ReoptimizeImportedViews, EnableNewOuterJoinLowering, EnableEagerDeltaJoins, } @@ -3089,6 +3090,7 @@ pub enum ExplainPlanOptionName { SubtreeSize, Timing, Types, + ReoptimizeImportedViews, EnableNewOuterJoinLowering, EnableEagerDeltaJoins, } @@ -3550,8 +3552,10 @@ pub enum ExplainStage { RawPlan, /// The mz_expr::MirRelationExpr after decorrelation DecorrelatedPlan, - /// The mz_expr::MirRelationExpr after optimization - OptimizedPlan, + /// The mz_expr::MirRelationExpr after local optimization + LocalPlan, + /// The mz_expr::MirRelationExpr after global optimization + GlobalPlan, /// The mz_compute_types::plan::Plan PhysicalPlan, /// The complete trace of the plan through the optimizer @@ -3565,7 +3569,8 @@ impl ExplainStage { match self { Self::RawPlan => Some(Raw.path()), Self::DecorrelatedPlan => Some(Decorrelated.path()), - Self::OptimizedPlan => Some(Optimized.path()), + Self::LocalPlan => Some(Local.path()), + Self::GlobalPlan => Some(Global.path()), Self::PhysicalPlan => Some(Physical.path()), Self::Trace => None, } @@ -3577,7 +3582,8 @@ impl ExplainStage { match self { Self::RawPlan => false, Self::DecorrelatedPlan => false, - Self::OptimizedPlan => true, + Self::LocalPlan => false, + Self::GlobalPlan => true, Self::PhysicalPlan => true, Self::Trace => false, } @@ -3589,7 +3595,8 @@ impl AstDisplay for ExplainStage { match self { Self::RawPlan => f.write_str("RAW PLAN"), Self::DecorrelatedPlan => f.write_str("DECORRELATED PLAN"), - Self::OptimizedPlan => f.write_str("OPTIMIZED PLAN"), + Self::LocalPlan => f.write_str("LOCALLY OPTIMIZED PLAN"), + Self::GlobalPlan => f.write_str("OPTIMIZED PLAN"), Self::PhysicalPlan => f.write_str("PHYSICAL PLAN"), Self::Trace => f.write_str("OPTIMIZER TRACE"), } @@ -3602,7 +3609,8 @@ impl_display!(ExplainStage); pub enum NamedPlan { Raw, Decorrelated, - Optimized, + Local, + Global, Physical, FastPath, } @@ -3613,7 +3621,8 @@ impl NamedPlan { match value { "optimize/raw" => Some(Self::Raw), "optimize/hir_to_mir" => Some(Self::Decorrelated), - "optimize/global" => Some(Self::Optimized), + "optimize/local" => Some(Self::Local), + "optimize/global" => Some(Self::Global), "optimize/finalize_dataflow" => Some(Self::Physical), "optimize/fast_path" => Some(Self::FastPath), _ => None, @@ -3626,7 +3635,8 @@ impl NamedPlan { match self { Self::Raw => "optimize/raw", Self::Decorrelated => "optimize/hir_to_mir", - Self::Optimized => "optimize/global", + Self::Local => "optimize/local", + Self::Global => "optimize/global", Self::Physical => "optimize/finalize_dataflow", Self::FastPath => "optimize/fast_path", } @@ -3644,10 +3654,20 @@ pub enum Explainee { ReplanMaterializedView(T::ItemName), ReplanIndex(T::ItemName), Select(Box>, bool), + CreateView(Box>, bool), CreateMaterializedView(Box>, bool), CreateIndex(Box>, bool), } +impl Explainee { + pub fn default_stage(&self) -> ExplainStage { + match self { + Self::View(_) | Self::ReplanView(_) | Self::CreateView(_, _) => ExplainStage::LocalPlan, + _ => ExplainStage::GlobalPlan, + } + } +} + impl AstDisplay for Explainee { fn fmt(&self, f: &mut AstFormatter) { match self { @@ -3681,6 +3701,12 @@ impl AstDisplay for Explainee { } f.write_node(select); } + Self::CreateView(statement, broken) => { + if *broken { + f.write_str("BROKEN "); + } + f.write_node(statement); + } Self::CreateMaterializedView(statement, broken) => { if *broken { f.write_str("BROKEN "); diff --git a/src/sql-parser/src/parser.rs b/src/sql-parser/src/parser.rs index 942d2705a15a5..51b0b539f4a96 100644 --- a/src/sql-parser/src/parser.rs +++ b/src/sql-parser/src/parser.rs @@ -7295,7 +7295,18 @@ impl<'a> Parser<'a> { } else { let broken = self.parse_keyword(BROKEN); - if self.peek_keywords(&[CREATE, MATERIALIZED, VIEW]) + if self.peek_keywords(&[CREATE, VIEW]) + || self.peek_keywords(&[CREATE, OR, REPLACE, VIEW]) + { + // Parse: `BROKEN? CREATE [OR REPLACE] VIEW ...` + let _ = self.parse_keyword(CREATE); // consume CREATE token + let stmt = match self.parse_create_view()? { + Statement::CreateView(stmt) => stmt, + _ => panic!("Unexpected statement type return after parsing"), + }; + + Explainee::CreateView(Box::new(stmt), broken) + } else if self.peek_keywords(&[CREATE, MATERIALIZED, VIEW]) || self.peek_keywords(&[CREATE, OR, REPLACE, MATERIALIZED, VIEW]) { // Parse: `BROKEN? CREATE [OR REPLACE] MATERIALIZED VIEW ...` @@ -7329,39 +7340,47 @@ impl<'a> Parser<'a> { /// Parse an `EXPLAIN ... PLAN` statement, assuming that the `EXPLAIN` token /// has already been consumed. fn parse_explain_plan(&mut self) -> Result, ParserError> { - let stage = match self.parse_one_of_keywords(&[ - PLAN, + let (expect_for, stage) = match self.parse_one_of_keywords(&[ RAW, DECORRELATED, + LOCALLY, OPTIMIZED, PHYSICAL, OPTIMIZER, + PLAN, ]) { - Some(PLAN) => { - // EXPLAIN PLAN = EXPLAIN OPTIMIZED PLAN - Some(ExplainStage::OptimizedPlan) - } Some(RAW) => { self.expect_keyword(PLAN)?; - Some(ExplainStage::RawPlan) + (true, Some(ExplainStage::RawPlan)) } Some(DECORRELATED) => { self.expect_keyword(PLAN)?; - Some(ExplainStage::DecorrelatedPlan) + (true, Some(ExplainStage::DecorrelatedPlan)) + } + Some(LOCALLY) => { + self.expect_keywords(&[OPTIMIZED, PLAN])?; + (true, Some(ExplainStage::LocalPlan)) } Some(OPTIMIZED) => { self.expect_keyword(PLAN)?; - Some(ExplainStage::OptimizedPlan) + (true, Some(ExplainStage::GlobalPlan)) } Some(PHYSICAL) => { self.expect_keyword(PLAN)?; - Some(ExplainStage::PhysicalPlan) + (true, Some(ExplainStage::PhysicalPlan)) } Some(OPTIMIZER) => { self.expect_keyword(TRACE)?; - Some(ExplainStage::Trace) + (true, Some(ExplainStage::Trace)) + } + Some(PLAN) => { + // Use the default plan for the explainee. + (true, None) + } + None => { + // Use the default plan for the explainee. + (false, None) } - None => None, _ => unreachable!(), }; @@ -7390,14 +7409,14 @@ impl<'a> Parser<'a> { ExplainFormat::Text }; - if stage.is_some() { + if expect_for { self.expect_keyword(FOR)?; } let explainee = self.parse_explainee()?; Ok(Statement::ExplainPlan(ExplainPlanStatement { - stage: stage.unwrap_or(ExplainStage::OptimizedPlan), + stage: stage.unwrap_or_else(|| explainee.default_stage()), with_options, format, explainee, diff --git a/src/sql-parser/tests/testdata/explain b/src/sql-parser/tests/testdata/explain index 90085127b6bee..7509c7349989c 100644 --- a/src/sql-parser/tests/testdata/explain +++ b/src/sql-parser/tests/testdata/explain @@ -23,7 +23,7 @@ EXPLAIN SELECT 665 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR SELECT 665 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN RAW PLAN FOR SELECT 665 @@ -44,7 +44,7 @@ EXPLAIN OPTIMIZED PLAN FOR SELECT 665 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR SELECT 665 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN PHYSICAL PLAN FOR SELECT 665 @@ -58,77 +58,91 @@ EXPLAIN SELECT 665 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR SELECT 665 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR VIEW foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR MATERIALIZED VIEW foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR MATERIALIZED VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: MaterializedView(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: MaterializedView(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR INDEX foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR INDEX foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Index(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Index(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR REPLAN VIEW foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR REPLAN VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: ReplanView(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: ReplanView(Name(UnresolvedItemName([Ident("foo")]))) }) + +parse-statement +EXPLAIN PLAN FOR REPLAN VIEW foo +---- +EXPLAIN LOCALLY OPTIMIZED PLAN AS TEXT FOR REPLAN VIEW foo +=> +ExplainPlan(ExplainPlanStatement { stage: LocalPlan, with_options: [], format: Text, explainee: ReplanView(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR REPLAN MATERIALIZED VIEW foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR REPLAN MATERIALIZED VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: ReplanMaterializedView(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: ReplanMaterializedView(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR REPLAN INDEX foo ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR REPLAN INDEX foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: ReplanIndex(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: ReplanIndex(Name(UnresolvedItemName([Ident("foo")]))) }) + +parse-statement +EXPLAIN PLAN FOR VIEW foo +---- +EXPLAIN LOCALLY OPTIMIZED PLAN AS TEXT FOR VIEW foo +=> +ExplainPlan(ExplainPlanStatement { stage: LocalPlan, with_options: [], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN WITH(types) FOR VIEW foo ---- EXPLAIN OPTIMIZED PLAN WITH (TYPES) AS TEXT FOR VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [ExplainPlanOption { name: Types, value: None }], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [ExplainPlanOption { name: Types, value: None }], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN OPTIMIZED PLAN WITH(arity, types) FOR VIEW foo ---- EXPLAIN OPTIMIZED PLAN WITH (ARITY, TYPES) AS TEXT FOR VIEW foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [ExplainPlanOption { name: Arity, value: None }, ExplainPlanOption { name: Types, value: None }], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [ExplainPlanOption { name: Arity, value: None }, ExplainPlanOption { name: Types, value: None }], format: Text, explainee: View(Name(UnresolvedItemName([Ident("foo")]))) }) parse-statement EXPLAIN ((SELECT 1)) ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR SELECT 1 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN OPTIMIZED PLAN AS TEXT FOR WITH a AS (SELECT 1) SELECT * FROM a ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR WITH a AS (SELECT 1) SELECT * FROM a => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, id: (), query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } }]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("a")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, id: (), query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } }]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("a")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) # regression test for #16029 parse-statement @@ -136,7 +150,7 @@ EXPLAIN WITH a AS (SELECT 1) SELECT * FROM a ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR WITH a AS (SELECT 1) SELECT * FROM a => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, id: (), query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } }]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("a")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([Cte { alias: TableAlias { name: Ident("a"), columns: [], strict: false }, id: (), query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("1")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } }]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("a")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN TIMESTAMP FOR SELECT 1 @@ -150,7 +164,7 @@ EXPLAIN AS JSON SELECT * FROM foo ---- EXPLAIN OPTIMIZED PLAN AS JSON FOR SELECT * FROM foo => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Json, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("foo")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Json, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Wildcard], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("foo")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None }, false) }) parse-statement EXPLAIN OPTIMIZER TRACE WITH (types) AS TEXT FOR BROKEN SELECT 1 + 1 @@ -161,33 +175,47 @@ ExplainPlan(ExplainPlanStatement { stage: Trace, with_options: [ExplainPlanOptio # TODO (aalexandrov): Add negative tests for new explain API. +parse-statement +EXPLAIN CREATE VIEW mv AS SELECT 665 +---- +EXPLAIN LOCALLY OPTIMIZED PLAN AS TEXT FOR CREATE VIEW mv AS SELECT 665 +=> +ExplainPlan(ExplainPlanStatement { stage: LocalPlan, with_options: [], format: Text, explainee: CreateView(CreateViewStatement { if_exists: Error, temporary: false, definition: ViewDefinition { name: UnresolvedItemName([Ident("mv")]), columns: [], query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } } }, false) }) + +parse-statement +EXPLAIN CREATE OR REPLACE VIEW mv AS SELECT 665 +---- +EXPLAIN LOCALLY OPTIMIZED PLAN AS TEXT FOR CREATE OR REPLACE VIEW mv AS SELECT 665 +=> +ExplainPlan(ExplainPlanStatement { stage: LocalPlan, with_options: [], format: Text, explainee: CreateView(CreateViewStatement { if_exists: Replace, temporary: false, definition: ViewDefinition { name: UnresolvedItemName([Ident("mv")]), columns: [], query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None } } }, false) }) + parse-statement EXPLAIN WITH (humanized expressions) CREATE MATERIALIZED VIEW mv AS SELECT 665 ---- EXPLAIN OPTIMIZED PLAN WITH (HUMANIZED EXPRESSIONS) AS TEXT FOR CREATE MATERIALIZED VIEW mv AS SELECT 665 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [ExplainPlanOption { name: HumanizedExpressions, value: None }], format: Text, explainee: CreateMaterializedView(CreateMaterializedViewStatement { if_exists: Error, name: UnresolvedItemName([Ident("mv")]), columns: [], in_cluster: None, query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None, with_options: [] }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [ExplainPlanOption { name: HumanizedExpressions, value: None }], format: Text, explainee: CreateMaterializedView(CreateMaterializedViewStatement { if_exists: Error, name: UnresolvedItemName([Ident("mv")]), columns: [], in_cluster: None, query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None, with_options: [] }, false) }) parse-statement EXPLAIN BROKEN CREATE MATERIALIZED VIEW mv AS SELECT 665 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR BROKEN CREATE MATERIALIZED VIEW mv AS SELECT 665 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: CreateMaterializedView(CreateMaterializedViewStatement { if_exists: Error, name: UnresolvedItemName([Ident("mv")]), columns: [], in_cluster: None, query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None, with_options: [] }, true) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: CreateMaterializedView(CreateMaterializedViewStatement { if_exists: Error, name: UnresolvedItemName([Ident("mv")]), columns: [], in_cluster: None, query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None, with_options: [] }, true) }) parse-statement EXPLAIN BROKEN CREATE DEFAULT INDEX ON q1 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR BROKEN CREATE DEFAULT INDEX ON q1 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: CreateIndex(CreateIndexStatement { name: None, in_cluster: None, on_name: Name(UnresolvedItemName([Ident("q1")])), key_parts: None, with_options: [], if_not_exists: false }, true) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: CreateIndex(CreateIndexStatement { name: None, in_cluster: None, on_name: Name(UnresolvedItemName([Ident("q1")])), key_parts: None, with_options: [], if_not_exists: false }, true) }) parse-statement EXPLAIN OPTIMIZED PLAN FOR CREATE INDEX ON v(auction_id) ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR CREATE INDEX ON v (auction_id) => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: CreateIndex(CreateIndexStatement { name: None, in_cluster: None, on_name: Name(UnresolvedItemName([Ident("v")])), key_parts: Some([Identifier([Ident("auction_id")])]), with_options: [], if_not_exists: false }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: CreateIndex(CreateIndexStatement { name: None, in_cluster: None, on_name: Name(UnresolvedItemName([Ident("v")])), key_parts: Some([Identifier([Ident("auction_id")])]), with_options: [], if_not_exists: false }, false) }) parse-statement EXPLAIN VALUE SCHEMA AS TEXT FOR CREATE SINK foo FROM bar INTO KAFKA CONNECTION baz (TOPIC 'topic') FORMAT AVRO USING CONFLUENT SCHEMA REGISTRY CONNECTION conn2 ENVELOPE UPSERT @@ -222,7 +250,7 @@ EXPLAIN SELECT 665 AS OF 3 ---- EXPLAIN OPTIMIZED PLAN AS TEXT FOR SELECT 665 AS OF 3 => -ExplainPlan(ExplainPlanStatement { stage: OptimizedPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: Some(At(Value(Number("3")))) }, false) }) +ExplainPlan(ExplainPlanStatement { stage: GlobalPlan, with_options: [], format: Text, explainee: Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Value(Number("665")), alias: None }], from: [], selection: None, group_by: [], having: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: Some(At(Value(Number("3")))) }, false) }) parse-statement EXPLAIN FILTER PUSHDOWN FOR SELECT * FROM numbers where value > 10 diff --git a/src/sql/src/plan.rs b/src/sql/src/plan.rs index 202defe94d3b0..1325508eaa928 100644 --- a/src/sql/src/plan.rs +++ b/src/sql/src/plan.rs @@ -605,7 +605,7 @@ pub struct CreateTablePlan { pub if_not_exists: bool, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CreateViewPlan { pub name: QualifiedItemName, pub view: View, @@ -806,10 +806,14 @@ pub struct ExplainPlanPlan { /// The type of object to be explained #[derive(Clone, Debug)] pub enum Explainee { + /// Lookup and explain a plan saved for an view. + View(GlobalId), /// Lookup and explain a plan saved for an existing materialized view. MaterializedView(GlobalId), /// Lookup and explain a plan saved for an existing index. Index(GlobalId), + /// Replan an existing view. + ReplanView(GlobalId), /// Replan an existing materialized view. ReplanMaterializedView(GlobalId), /// Replan an existing index. @@ -829,6 +833,12 @@ pub enum ExplaineeStatement { plan: plan::SelectPlan, desc: RelationDesc, }, + /// The object to be explained is a CREATE VIEW. + CreateView { + /// Broken flag (see [`ExplaineeStatement::broken()`]). + broken: bool, + plan: plan::CreateViewPlan, + }, /// The object to be explained is a CREATE MATERIALIZED VIEW. CreateMaterializedView { /// Broken flag (see [`ExplaineeStatement::broken()`]). @@ -847,6 +857,7 @@ impl ExplaineeStatement { pub fn depends_on(&self) -> BTreeSet { match self { Self::Select { plan, .. } => plan.source.depends_on(), + Self::CreateView { plan, .. } => plan.view.expr.depends_on(), Self::CreateMaterializedView { plan, .. } => plan.materialized_view.expr.depends_on(), Self::CreateIndex { plan, .. } => btreeset! {plan.index.on}, } @@ -865,6 +876,7 @@ impl ExplaineeStatement { pub fn broken(&self) -> bool { match self { Self::Select { broken, .. } => *broken, + Self::CreateView { broken, .. } => *broken, Self::CreateMaterializedView { broken, .. } => *broken, Self::CreateIndex { broken, .. } => *broken, } @@ -876,8 +888,9 @@ impl ExplaineeStatementKind { use ExplainStage::*; match self { Self::Select => true, + Self::CreateView => ![GlobalPlan, PhysicalPlan].contains(stage), Self::CreateMaterializedView => true, - Self::CreateIndex => ![RawPlan, DecorrelatedPlan].contains(stage), + Self::CreateIndex => ![RawPlan, DecorrelatedPlan, LocalPlan].contains(stage), } } } @@ -886,6 +899,7 @@ impl std::fmt::Display for ExplaineeStatementKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Select => write!(f, "SELECT"), + Self::CreateView => write!(f, "CREATE VIEW"), Self::CreateMaterializedView => write!(f, "CREATE MATERIALIZED VIEW"), Self::CreateIndex => write!(f, "CREATE INDEX"), } diff --git a/src/sql/src/plan/statement/ddl.rs b/src/sql/src/plan/statement/ddl.rs index cdc5e4b3d202f..1b95a1c40b8bc 100644 --- a/src/sql/src/plan/statement/ddl.rs +++ b/src/sql/src/plan/statement/ddl.rs @@ -3280,6 +3280,7 @@ generate_extracted_config!( generate_extracted_config!( ClusterFeature, + (ReoptimizeImportedViews, Option, Default(None)), (EnableEagerDeltaJoins, Option, Default(None)), (EnableNewOuterJoinLowering, Option, Default(None)) ); @@ -3355,11 +3356,13 @@ pub fn plan_create_cluster( // Plan OptimizerFeatureOverrides. let ClusterFeatureExtracted { + reoptimize_imported_views, enable_eager_delta_joins, enable_new_outer_join_lowering, seen: _, } = ClusterFeatureExtracted::try_from(features)?; let optimizer_feature_overrides = OptimizerFeatureOverrides { + reoptimize_imported_views, enable_eager_delta_joins, enable_new_outer_join_lowering, ..Default::default() diff --git a/src/sql/src/plan/statement/dml.rs b/src/sql/src/plan/statement/dml.rs index 71529084a811e..fec18713f7f63 100644 --- a/src/sql/src/plan/statement/dml.rs +++ b/src/sql/src/plan/statement/dml.rs @@ -255,20 +255,24 @@ pub fn describe_explain_plan( match stage { ExplainStage::RawPlan => { - relation_desc = - relation_desc.with_column("Raw Plan", ScalarType::String.nullable(false)); + let name = "Raw Plan"; + relation_desc = relation_desc.with_column(name, ScalarType::String.nullable(false)); } ExplainStage::DecorrelatedPlan => { - relation_desc = - relation_desc.with_column("Decorrelated Plan", ScalarType::String.nullable(false)); + let name = "Decorrelated Plan"; + relation_desc = relation_desc.with_column(name, ScalarType::String.nullable(false)); } - ExplainStage::OptimizedPlan => { - relation_desc = - relation_desc.with_column("Optimized Plan", ScalarType::String.nullable(false)); + ExplainStage::LocalPlan => { + let name = "Locally Optimized Plan"; + relation_desc = relation_desc.with_column(name, ScalarType::String.nullable(false)); + } + ExplainStage::GlobalPlan => { + let name = "Optimized Plan"; + relation_desc = relation_desc.with_column(name, ScalarType::String.nullable(false)); } ExplainStage::PhysicalPlan => { - relation_desc = - relation_desc.with_column("Physical Plan", ScalarType::String.nullable(false)); + let name = "Physical Plan"; + relation_desc = relation_desc.with_column(name, ScalarType::String.nullable(false)); } ExplainStage::Trace => { relation_desc = relation_desc @@ -346,6 +350,7 @@ generate_extracted_config!( (SubtreeSize, bool, Default(false)), (Timing, bool, Default(false)), (Types, bool, Default(false)), + (ReoptimizeImportedViews, Option, Default(None)), (EnableNewOuterJoinLowering, Option, Default(None)), (EnableEagerDeltaJoins, Option, Default(None)) ); @@ -387,6 +392,7 @@ impl TryFrom for ExplainConfig { subtree_size: v.subtree_size, timing: v.timing, types: v.types, + reoptimize_imported_views: v.reoptimize_imported_views, enable_eager_delta_joins: v.enable_eager_delta_joins, enable_new_outer_join_lowering: v.enable_new_outer_join_lowering, }) @@ -402,16 +408,20 @@ fn plan_explainee( let is_replan = matches!( explainee, - Explainee::ReplanView(_) | Explainee::ReplanMaterializedView(_) | Explainee::ReplanIndex(_), + Explainee::ReplanView(_) | Explainee::ReplanMaterializedView(_) | Explainee::ReplanIndex(_) ); let explainee = match explainee { - Explainee::View(_) | Explainee::ReplanView(_) => { - bail_never_supported!( - "EXPLAIN ... VIEW ", - "sql/explain-plan", - "Use `EXPLAIN ... SELECT * FROM ` (if the view is not indexed) or `EXPLAIN ... INDEX ` (if the view is indexed) instead." - ); + Explainee::View(name) | Explainee::ReplanView(name) => { + let item = scx.get_item_by_resolved_name(&name)?; + let item_type = item.item_type(); + if item_type != CatalogItemType::View { + sql_bail!("Expected {name} to be a view, not a {item_type}"); + } + match is_replan { + true => crate::plan::Explainee::ReplanView(item.id()), + false => crate::plan::Explainee::View(item.id()), + } } Explainee::MaterializedView(name) | Explainee::ReplanMaterializedView(name) => { let item = scx.get_item_by_resolved_name(&name)?; @@ -436,15 +446,32 @@ fn plan_explainee( } } Explainee::Select(select, broken) => { - let copy_to = None; - let (plan, desc) = plan_select_inner(scx, *select, params, copy_to)?; - + let (plan, desc) = plan_select_inner(scx, *select, params, None)?; if broken { scx.require_feature_flag(&vars::ENABLE_EXPLAIN_BROKEN)?; } - crate::plan::Explainee::Statement(ExplaineeStatement::Select { broken, plan, desc }) } + Explainee::CreateView(mut stmt, broken) => { + if stmt.if_exists != IfExistsBehavior::Skip { + // If we don't force this parameter to Skip planning will + // fail for names that already exist in the catalog. This + // can happen even in `Replace` mode if the existing item + // has dependencies. + stmt.if_exists = IfExistsBehavior::Skip; + } else { + sql_bail!( + "Cannot EXPLAIN a CREATE VIEW that explictly sets IF NOT EXISTS \ + (the behavior is implied within the scope of an enclosing EXPLAIN)" + ); + } + + let Plan::CreateView(plan) = ddl::plan_create_view(scx, *stmt, params)? else { + sql_bail!("expected CreateViewPlan plan"); + }; + + crate::plan::Explainee::Statement(ExplaineeStatement::CreateView { broken, plan }) + } Explainee::CreateMaterializedView(mut stmt, broken) => { if stmt.if_exists != IfExistsBehavior::Skip { // If we don't force this parameter to Skip planning will diff --git a/src/sql/src/rbac.rs b/src/sql/src/rbac.rs index 7a2593cde274c..5d747ad7405aa 100644 --- a/src/sql/src/rbac.rs +++ b/src/sql/src/rbac.rs @@ -767,8 +767,10 @@ fn generate_rbac_requirements( }) | Plan::ExplainPushdown(plan::ExplainPushdownPlan { explainee }) => RbacRequirements { privileges: match explainee { - Explainee::MaterializedView(id) + Explainee::View(id) + | Explainee::MaterializedView(id) | Explainee::Index(id) + | Explainee::ReplanView(id) | Explainee::ReplanMaterializedView(id) | Explainee::ReplanIndex(id) => { let item = catalog.get_item(id); @@ -786,8 +788,10 @@ fn generate_rbac_requirements( .collect(), }, item_usage: match explainee { - Explainee::MaterializedView(..) + Explainee::View(..) + | Explainee::MaterializedView(..) | Explainee::Index(..) + | Explainee::ReplanView(..) | Explainee::ReplanMaterializedView(..) | Explainee::ReplanIndex(..) => &EMPTY_ITEM_USAGE, Explainee::Statement(_) => &DEFAULT_ITEM_USAGE, diff --git a/test/sqllogictest/cluster_features.slt b/test/sqllogictest/cluster_features.slt index b577f63a7d7c2..a7ff5c9f002ca 100644 --- a/test/sqllogictest/cluster_features.slt +++ b/test/sqllogictest/cluster_features.slt @@ -18,4 +18,526 @@ mode cockroach # Regular users cannot create clusters with FEATURES yet. statement error db error: ERROR: FEATURES not supported for non\-system users -CREATE CLUSTER foo SIZE = '1' FEATURES (enable eager delta joins = true); +CREATE CLUSTER FOO SIZE = '1' FEATURES (ENABLE EAGER DELTA JOINS = TRUE); + +# Cluster and system config for the test DDL statements below +# ----------------------------------------------------------- + +simple conn=mz_system,user=mz_system +CREATE CLUSTER c1 SIZE = '1' FEATURES (ENABLE EAGER DELTA JOINS = TRUE); +---- +COMPLETE 0 + +simple conn=mz_system,user=mz_system +CREATE CLUSTER c2 SIZE = '1' FEATURES (ENABLE EAGER DELTA JOINS = FALSE); +---- +COMPLETE 0 + +simple conn=mz_system,user=mz_system +GRANT ALL ON CLUSTER c1 TO materialize; +---- +COMPLETE 0 + +simple conn=mz_system,user=mz_system +GRANT ALL ON CLUSTER c2 TO materialize; +---- +COMPLETE 0 + +# Schema for the test DDL statements below +# ---------------------------------------- + +statement ok +CREATE TABLE t1 ( + x int, + y int +); + +statement ok +CREATE TABLE t2 ( + x int, + y int +); + +statement ok +CREATE TABLE t3 ( + x int, + y int +); + +# Test materialized views +# ----------------------- + +# Should be created with the feature flag turned on. +statement ok +CREATE MATERIALIZED VIEW mv1 IN CLUSTER c1 AS +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; + +# Should be created with the feature flag turned off. +statement ok +CREATE MATERIALIZED VIEW mv2 IN CLUSTER c2 AS +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; + +# EXPLAIN mv1 in c1 (should be running with the feature flag turned on). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +MATERIALIZED VIEW mv1; +---- +materialize.public.mv1: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=delta + implementation + %0:t1 » %1:t2[#0]K » %2:t3[#0]K + %1:t2 » %0:t1[#0]K » %2:t3[#0]K + %2:t3 » %1:t2[#1]K » %0:t1[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0], [#1]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN mv2 in c2 (should be running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +MATERIALIZED VIEW mv2; +---- +materialize.public.mv2: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN REPLAN mv1 in c1 (should be running with the feature flag turned on). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +REPLAN MATERIALIZED VIEW mv1; +---- +materialize.public.mv1: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=delta + implementation + %0:t1 » %1:t2[#0]K » %2:t3[#0]K + %1:t2 » %0:t1[#0]K » %2:t3[#0]K + %2:t3 » %1:t2[#1]K » %0:t1[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0], [#1]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN REPLAN mv1 in c1 with an explain-level feature override (should be +# running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS, ENABLE EAGER DELTA JOINS = FALSE) +REPLAN MATERIALIZED VIEW mv1; +---- +materialize.public.mv1: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN CREATE in c1 with an explain-level feature override (should be +# running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS, ENABLE EAGER DELTA JOINS = FALSE) +CREATE MATERIALIZED VIEW mv1 IN CLUSTER c1 AS +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; +---- +materialize.public.mv1: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# Test indexed views +# ------------------ + +# Same as the mv1 / mv2 definitions above. +statement ok +CREATE VIEW v AS +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; + +statement ok +CREATE INDEX v_idx_in_c1 IN CLUSTER c1 ON v(f1); + +statement ok +CREATE INDEX v_idx_in_c2 IN CLUSTER c2 ON v(f1); + +# EXPLAIN v in c2 (should be running with the feature flag turned on). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +INDEX v_idx_in_c1; +---- +materialize.public.v_idx_in_c1: + ArrangeBy keys=[[#0]] + ReadGlobalFromSameDataflow materialize.public.v + +materialize.public.v: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=delta + implementation + %0:t1 » %1:t2[#0]K » %2:t3[#0]K + %1:t2 » %0:t1[#0]K » %2:t3[#0]K + %2:t3 » %1:t2[#1]K » %0:t1[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0], [#1]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN v in c2 (should be running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +INDEX v_idx_in_c2; +---- +materialize.public.v_idx_in_c2: + ArrangeBy keys=[[#0]] + ReadGlobalFromSameDataflow materialize.public.v + +materialize.public.v: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN REPLAN v in c1 (should be running with the feature flag turned on). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS) +REPLAN INDEX v_idx_in_c1; +---- +materialize.public.v_idx_in_c1: + ArrangeBy keys=[[#0]] + ReadGlobalFromSameDataflow materialize.public.v + +materialize.public.v: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=delta + implementation + %0:t1 » %1:t2[#0]K » %2:t3[#0]K + %1:t2 » %0:t1[#0]K » %2:t3[#0]K + %2:t3 » %1:t2[#1]K » %0:t1[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0], [#1]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# EXPLAIN REPLAN v in c1 with an explain-level feature override (should be +# running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS, ENABLE EAGER DELTA JOINS = FALSE) +REPLAN INDEX v_idx_in_c1; +---- +materialize.public.v_idx_in_c1: + ArrangeBy keys=[[#0]] + ReadGlobalFromSameDataflow materialize.public.v + +materialize.public.v: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# Delete the existing index in order to get the expected output in the next +# test. +statement ok +DROP INDEX v_idx_in_c1; + +# EXPLAIN CREATE in c1 with an explain-level feature override (should be +# running with the feature flag turned off). +query T multiline +EXPLAIN WITH(JOIN IMPLEMENTATIONS, ENABLE EAGER DELTA JOINS = FALSE) +CREATE INDEX v_idx_in_c1 IN CLUSTER c1 ON v(f1); +---- +materialize.public.v_idx_in_c1: + ArrangeBy keys=[[#0]] + ReadGlobalFromSameDataflow materialize.public.v + +materialize.public.v: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + implementation + %0:t1[#0]K » %1:t2[#0]K » %2:t3[#0]K + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +# Test peeks +# ---------- + +statement ok +SET cluster = c1; + +# EXPLAIN in c1 (should be running with the feature flag turned on). +query T multiline +EXPLAIN +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; +---- +Explained Query: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=delta + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0], [#1]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF + +statement ok +SET cluster = c2; + +# EXPLAIN in c2 (should be running with the feature flag turned off). +query T multiline +EXPLAIN +SELECT + t1.y as f1, + t2.y as f2, + t3.y as f3 +FROM + t1, t2, t3 +where + t1.x = t2.x AND + t2.y = t3.y; +---- +Explained Query: + Project (#1, #3, #3) + Join on=(#0 = #2 AND #3 = #4) type=differential + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL + ReadStorage materialize.public.t1 + ArrangeBy keys=[[#0]] + Filter (#0) IS NOT NULL AND (#1) IS NOT NULL + ReadStorage materialize.public.t2 + ArrangeBy keys=[[#0]] + Project (#1) + Filter (#1) IS NOT NULL + ReadStorage materialize.public.t3 + +Source materialize.public.t1 + filter=((#0) IS NOT NULL) +Source materialize.public.t2 + filter=((#0) IS NOT NULL AND (#1) IS NOT NULL) +Source materialize.public.t3 + filter=((#1) IS NOT NULL) + +EOF diff --git a/test/sqllogictest/explain/view.slt b/test/sqllogictest/explain/view.slt new file mode 100644 index 0000000000000..c7b26956df484 --- /dev/null +++ b/test/sqllogictest/explain/view.slt @@ -0,0 +1,249 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. + +simple conn=mz_system,user=mz_system +ALTER SYSTEM SET enable_new_outer_join_lowering TO false; +---- +COMPLETE 0 + +statement ok +CREATE TABLE accounts(id int, balance int); + +# Use `id bigint` instead of `id int` to force differences in planning based on +# the `enable_new_outer_join_lowering` feature flag value. +statement ok +CREATE TABLE account_details(id bigint, address string); + +statement ok +CREATE OR REPLACE VIEW v AS +SELECT + * +FROM + accounts a + LEFT JOIN account_details ad USING(id) +WHERE + balance = 100; + +mode cockroach + +# Must explain the "Raw Plan". +query T multiline +EXPLAIN RAW PLAN FOR VIEW v; +---- +Project (#0, #1, #3) + Filter (#1 = 100) + LeftOuterJoin (true AND (integer_to_bigint(#0) = #2)) + Get materialize.public.accounts + Get materialize.public.account_details + +EOF + +# Must explain the "Locally Optimized Plan". +query T multiline +EXPLAIN VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Get l0 + Project (#0, #3..=#5) + Map (100, null, null) + Join on=(#0 = #1) + Union + Negate + Distinct project=[#0] + Get l0 + Distinct project=[#0] + Get l1 + Get l1 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL AND (#1 = 100) + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF + +# Must explain the "Locally Optimized Plan" (same as above). +query T multiline +EXPLAIN LOCALLY OPTIMIZED PLAN FOR VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Get l0 + Project (#0, #3..=#5) + Map (100, null, null) + Join on=(#0 = #1) + Union + Negate + Distinct project=[#0] + Get l0 + Distinct project=[#0] + Get l1 + Get l1 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL AND (#1 = 100) + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF + +# Must explain the "Locally Optimized Plan" (same as above). +query T multiline +EXPLAIN PLAN FOR REPLAN VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Get l0 + Project (#0, #3..=#5) + Map (100, null, null) + Join on=(#0 = #1) + Union + Negate + Distinct project=[#0] + Get l0 + Distinct project=[#0] + Get l1 + Get l1 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL AND (#1 = 100) + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF + +# Must explain the "Locally Optimized Plan" after changing the feature flag +# (same as below). +query T multiline +EXPLAIN PLAN WITH(ENABLE NEW OUTER JOIN LOWERING = TRUE) FOR REPLAN VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Map (null, null) + Union + Project (#0, #1) + Negate + Join on=(#2 = integer_to_bigint(#0)) + Get l1 + Distinct project=[integer_to_bigint(#0)] + Get l0 + Get l1 + Filter (#1 = 100) + Get l0 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF + +# Change the feature flag value +simple conn=mz_system,user=mz_system +ALTER SYSTEM SET enable_new_outer_join_lowering TO true; +---- +COMPLETE 0 + +# Must be planning with the feature flag turned on. +statement ok +CREATE OR REPLACE VIEW v AS +SELECT + * +FROM + accounts a + LEFT JOIN account_details ad USING(id) +WHERE + balance = 100; + +# Ensure that the index is now used by the view +query T multiline +EXPLAIN VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Map (null, null) + Union + Project (#0, #1) + Negate + Join on=(#2 = integer_to_bigint(#0)) + Get l1 + Distinct project=[integer_to_bigint(#0)] + Get l0 + Get l1 + Filter (#1 = 100) + Get l0 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF + +# Must be re-planning with the feature flag turned off. +query T multiline +EXPLAIN PLAN WITH(ENABLE NEW OUTER JOIN LOWERING = FALSE) FOR REPLAN VIEW v; +---- +Return + Project (#0, #1, #3) + Union + Get l0 + Project (#0, #3..=#5) + Map (100, null, null) + Join on=(#0 = #1) + Union + Negate + Distinct project=[#0] + Get l0 + Distinct project=[#0] + Get l1 + Get l1 +With + cte l1 = + Filter (#1 = 100) + Get materialize.public.accounts + cte l0 = + Join on=(#2 = integer_to_bigint(#0)) + Filter (#0) IS NOT NULL AND (#1 = 100) + Get materialize.public.accounts + Filter (#0) IS NOT NULL + Get materialize.public.account_details + +EOF diff --git a/test/sqllogictest/outer_join_lowering.slt b/test/sqllogictest/outer_join_lowering.slt index ba45c2229d3d3..1e76edf2ae759 100644 --- a/test/sqllogictest/outer_join_lowering.slt +++ b/test/sqllogictest/outer_join_lowering.slt @@ -827,7 +827,7 @@ FROM # EXPLAIN a SELECT * FROM with the feature turned in the EXPLAIN config. query T multiline -EXPLAIN OPTIMIZED PLAN WITH(enable new outer join lowering, humanized expressions, arity) FOR +EXPLAIN OPTIMIZED PLAN WITH(enable new outer join lowering, reoptimize imported views, humanized expressions, arity) FOR SELECT * FROM v; ---- Explained Query: @@ -868,7 +868,7 @@ EOF # EXPLAIN a CREATE INDEX with the feature turned in the EXPLAIN config. query T multiline -EXPLAIN OPTIMIZED PLAN WITH(enable new outer join lowering, humanized expressions, arity) FOR +EXPLAIN OPTIMIZED PLAN WITH(enable new outer join lowering, reoptimize imported views, humanized expressions, arity) FOR CREATE INDEX ON v(facts_k01); ---- materialize.public.v_facts_k01_idx: