1use std::fmt::Debug;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15
16use mz_compute_types::plan::LirRelationExpr;
17use mz_compute_types::sinks::{
18 ComputeSinkConnection, ComputeSinkDesc, CopyToS3OneshotSinkConnection,
19};
20use mz_expr::{MirRelationExpr, OptimizedMirRelationExpr};
21use mz_repr::GlobalId;
22use mz_repr::explain::trace_plan;
23use mz_sql::optimizer_metrics::OptimizerMetrics;
24use mz_sql::plan::HirRelationExpr;
25use mz_sql::session::metadata::SessionMetadata;
26use mz_storage_types::connections::Connection;
27use mz_storage_types::sinks::S3UploadInfo;
28use mz_transform::dataflow::DataflowMetainfo;
29use mz_transform::normalize_lets::normalize_lets;
30use mz_transform::typecheck::{SharedTypecheckingContext, empty_typechecking_context};
31use mz_transform::{StatisticsOracle, TransformCtx};
32use timely::progress::Antichain;
33use tracing::warn;
34
35use crate::TimestampContext;
36use crate::catalog::Catalog;
37use crate::coord::CopyToContext;
38use crate::optimize::dataflows::{
39 ComputeInstanceSnapshot, DataflowBuilder, EvalTime, ExprPrep, ExprPrepOneShot,
40};
41use crate::optimize::{
42 LirDataflowDescription, MirDataflowDescription, Optimize, OptimizeMode, OptimizerConfig,
43 OptimizerError, optimize_mir_local, trace_plan,
44};
45
46pub struct Optimizer {
47 typecheck_ctx: SharedTypecheckingContext,
49 catalog: Arc<Catalog>,
51 compute_instance: ComputeInstanceSnapshot,
53 select_id: GlobalId,
55 copy_to_context: CopyToContext,
57 config: OptimizerConfig,
59 metrics: OptimizerMetrics,
61 duration: Duration,
63}
64
65impl Optimizer {
66 pub fn new(
67 catalog: Arc<Catalog>,
68 compute_instance: ComputeInstanceSnapshot,
69 select_id: GlobalId,
70 copy_to_context: CopyToContext,
71 config: OptimizerConfig,
72 metrics: OptimizerMetrics,
73 ) -> Self {
74 Self {
75 typecheck_ctx: empty_typechecking_context(),
76 catalog,
77 compute_instance,
78 select_id,
79 copy_to_context,
80 config,
81 metrics,
82 duration: Default::default(),
83 }
84 }
85}
86
87impl Debug for Optimizer {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 f.debug_struct("OptimizePeek")
95 .field("config", &self.config)
96 .finish_non_exhaustive()
97 }
98}
99
100pub struct Unresolved;
103
104#[derive(Clone)]
107pub struct LocalMirPlan<T = Unresolved> {
108 expr: MirRelationExpr,
109 df_meta: DataflowMetainfo,
110 context: T,
111}
112
113pub struct Resolved<'s> {
116 timestamp_ctx: TimestampContext,
117 stats: Box<dyn StatisticsOracle>,
118 session: &'s dyn SessionMetadata,
119}
120
121#[derive(Debug)]
130pub struct GlobalLirPlan {
131 df_desc: LirDataflowDescription,
132 df_meta: DataflowMetainfo,
133}
134
135impl GlobalLirPlan {
136 pub fn df_desc(&self) -> &LirDataflowDescription {
137 &self.df_desc
138 }
139}
140
141impl Optimize<HirRelationExpr> for Optimizer {
142 type To = LocalMirPlan;
143
144 fn optimize(&mut self, expr: HirRelationExpr) -> Result<Self::To, OptimizerError> {
145 let time = Instant::now();
146
147 trace_plan!(at: "raw", &expr);
149
150 let expr = expr.lower(&self.config, Some(&self.metrics))?;
152
153 let mut df_meta = DataflowMetainfo::default();
155 let mut transform_ctx = TransformCtx::local(
156 &self.config.features,
157 &self.typecheck_ctx,
158 &mut df_meta,
159 Some(&mut self.metrics),
160 Some(self.select_id),
161 );
162 let expr = optimize_mir_local(expr, &mut transform_ctx)?.into_inner();
163
164 self.duration += time.elapsed();
165
166 Ok(LocalMirPlan {
168 expr,
169 df_meta,
170 context: Unresolved,
171 })
172 }
173}
174
175impl LocalMirPlan<Unresolved> {
176 pub fn resolve(
179 self,
180 timestamp_ctx: TimestampContext,
181 session: &dyn SessionMetadata,
182 stats: Box<dyn StatisticsOracle>,
183 ) -> LocalMirPlan<Resolved<'_>> {
184 LocalMirPlan {
185 expr: self.expr,
186 df_meta: self.df_meta,
187 context: Resolved {
188 timestamp_ctx,
189 session,
190 stats,
191 },
192 }
193 }
194}
195
196impl<'s> Optimize<LocalMirPlan<Resolved<'s>>> for Optimizer {
197 type To = GlobalLirPlan;
198
199 fn optimize(&mut self, plan: LocalMirPlan<Resolved<'s>>) -> Result<Self::To, OptimizerError> {
200 let time = Instant::now();
201
202 let LocalMirPlan {
203 expr,
204 mut df_meta,
205 context:
206 Resolved {
207 timestamp_ctx,
208 stats,
209 session,
210 },
211 } = plan;
212
213 let expr = OptimizedMirRelationExpr(expr);
214
215 let mut df_builder = {
217 let catalog = self.catalog.state();
218 let compute = self.compute_instance.clone();
219 DataflowBuilder::new(catalog, compute).with_config(&self.config)
220 };
221
222 let debug_name = format!("copy-to-{}", self.select_id);
223 let mut df_desc = MirDataflowDescription::new(debug_name.to_string());
224
225 df_builder.import_view_into_dataflow(
226 &self.select_id,
227 &expr,
228 &mut df_desc,
229 &self.config.features,
230 )?;
231 df_builder.maybe_reoptimize_imported_views(&mut df_desc, &self.config)?;
232
233 let connection = match &self.copy_to_context.connection {
237 Connection::Aws(aws_connection) => {
238 ComputeSinkConnection::CopyToS3Oneshot(CopyToS3OneshotSinkConnection {
239 upload_info: S3UploadInfo {
240 uri: self.copy_to_context.uri.to_string(),
241 max_file_size: self.copy_to_context.max_file_size,
242 desc: self.copy_to_context.desc.clone(),
243 format: self.copy_to_context.format.clone(),
244 },
245 aws_connection: aws_connection.clone(),
246 connection_id: self.copy_to_context.connection_id,
247 output_batch_count: self
248 .copy_to_context
249 .output_batch_count
250 .expect("output_batch_count should be set in sequencer"),
251 })
252 }
253 _ => {
254 let msg = "only aws connection is supported in COPY TO";
257 return Err(OptimizerError::Internal(msg.to_string()));
258 }
259 };
260 let sink_description = ComputeSinkDesc {
261 from_desc: self.copy_to_context.desc.clone(),
262 from: self.select_id,
263 connection,
264 with_snapshot: true,
265 up_to: Default::default(),
267 non_null_assertions: Vec::new(),
269 refresh_schedule: None,
271 from_arrangement: None,
273 };
274 df_desc.export_sink(self.select_id, sink_description);
275
276 df_desc.set_as_of(timestamp_ctx.antichain());
278
279 let as_of = df_desc
281 .as_of
282 .clone()
283 .expect("as_of antichain")
284 .into_option()
285 .expect("unique as_of element");
286
287 let style = ExprPrepOneShot {
289 logical_time: EvalTime::Time(as_of),
290 session,
291 catalog_state: self.catalog.state(),
292 };
293 df_desc.visit_children(
294 |r| style.prep_relation_expr(r),
295 |s| style.prep_scalar_expr(s),
296 )?;
297
298 if let Some(as_of) = timestamp_ctx.timestamp() {
306 if let Some(until) = as_of.checked_add(1) {
307 df_desc.until = Antichain::from_elem(until);
308 for (_, sink) in &mut df_desc.sink_exports {
310 sink.up_to.clone_from(&df_desc.until);
311 }
312 } else {
313 warn!(as_of = %as_of, "as_of + 1 overflow");
314 }
315 }
316
317 let mut transform_ctx = TransformCtx::global(
319 &df_builder,
320 &*stats,
321 &self.config.features,
322 &self.typecheck_ctx,
323 &mut df_meta,
324 Some(&mut self.metrics),
325 );
326 mz_transform::optimize_dataflow(&mut df_desc, &mut transform_ctx, false)?;
328
329 if self.config.mode == OptimizeMode::Explain {
330 trace_plan!(at: "global", &df_meta.used_indexes(&df_desc));
332 }
333
334 for build in df_desc.objects_to_build.iter_mut() {
336 normalize_lets(&mut build.plan.0, &self.config.features)?
337 }
338
339 let df_desc = LirRelationExpr::finalize_dataflow(
343 df_desc,
344 &self.config.features,
345 Some(self.metrics.lowering()),
346 )?;
347
348 trace_plan(&df_desc);
350
351 self.duration += time.elapsed();
352 self.metrics
353 .observe_e2e_optimization_time("copy_to", self.duration);
354
355 Ok(GlobalLirPlan { df_desc, df_meta })
356 }
357}
358
359impl GlobalLirPlan {
360 pub fn unapply(self) -> (LirDataflowDescription, DataflowMetainfo) {
362 (self.df_desc, self.df_meta)
363 }
364}