1use std::collections::{BTreeMap, BTreeSet};
16use std::fmt;
17use std::num::NonZeroUsize;
18use std::ops::Deref;
19use std::sync::Arc;
20
21use differential_dataflow::consolidation::consolidate;
22use itertools::Itertools;
23use mz_adapter_types::connection::ConnectionId;
24use mz_cluster_client::ReplicaId;
25use mz_compute_client::controller::PeekNotification;
26use mz_compute_client::protocol::command::PeekTarget;
27use mz_compute_client::protocol::response::PeekResponse;
28use mz_compute_types::ComputeInstanceId;
29use mz_compute_types::dataflows::{DataflowDescription, IndexImport};
30use mz_controller_types::ClusterId;
31use mz_expr::explain::{HumanizedExplain, HumanizerMode, fmt_text_constant_rows};
32use mz_expr::row::RowCollection;
33use mz_expr::{
34 EvalError, Id, MirRelationExpr, MirScalarExpr, OptimizedMirRelationExpr, RowSetFinishing,
35 RowSetFinishingIncremental, permutation_for_arrangement,
36};
37use mz_ore::cast::CastFrom;
38use mz_ore::collections::CollectionExt;
39use mz_ore::soft_assert_eq_or_log;
40use mz_ore::str::{StrExt, separated};
41use mz_ore::task;
42use mz_ore::tracing::OpenTelemetryContext;
43use mz_persist_client::Schemas;
44use mz_persist_types::codec_impls::UnitSchema;
45use mz_repr::explain::text::DisplayText;
46use mz_repr::explain::{CompactScalars, IndexUsageType, PlanRenderingContext, UsedIndexes};
47use mz_repr::{
48 Diff, GlobalId, IntoRowIterator, RelationDesc, Row, RowIterator, SqlRelationType,
49 preserves_order,
50};
51use mz_storage_types::sources::SourceData;
52use serde::{Deserialize, Serialize};
53use timely::progress::Antichain;
54use tokio::sync::oneshot;
55use tracing::{Instrument, Span};
56use uuid::Uuid;
57
58use crate::active_compute_sink::{ActiveComputeSink, ActiveCopyTo};
59use crate::catalog::Catalog;
60use crate::coord::timestamp_selection::TimestampDetermination;
61use crate::optimize::OptimizerError;
62use crate::peek_client::CoordinatorClient;
63use crate::statement_logging::WatchSetCreation;
64use crate::statement_logging::{StatementEndedExecutionReason, StatementExecutionStrategy};
65use crate::{AdapterError, ExecuteContextGuard, ExecuteResponse, PeekClient};
66
67#[derive(Debug)]
69pub(crate) struct PendingPeek {
70 pub(crate) conn_id: ConnectionId,
72 pub(crate) cluster_id: ClusterId,
74 pub(crate) depends_on: BTreeSet<GlobalId>,
76 pub(crate) ctx_extra: ExecuteContextGuard,
79 pub(crate) is_fast_path: bool,
81}
82
83#[derive(Debug)]
88pub enum PeekResponseUnary {
89 Rows(Box<dyn RowIterator + Send + Sync>),
90 Error(AdapterError),
91 Canceled,
92 DependencyDropped(DroppedDependency),
94}
95
96#[derive(Clone, Debug)]
102pub enum DroppedDependency {
103 Relation { name: String },
104 Cluster { name: String },
105}
106
107impl fmt::Display for DroppedDependency {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::Relation { name } => write!(f, "relation {}", name.quoted()),
111 Self::Cluster { name } => write!(f, "cluster {}", name.quoted()),
112 }
113 }
114}
115
116impl DroppedDependency {
117 pub fn query_terminated_error(&self) -> String {
120 format!("query could not complete because {self} was dropped")
121 }
122
123 pub fn to_concurrent_dependency_drop(&self) -> AdapterError {
125 let (kind, name) = match self {
126 Self::Relation { name } => ("relation", name.clone()),
127 Self::Cluster { name } => ("cluster", name.clone()),
128 };
129 AdapterError::ConcurrentDependencyDrop {
130 dependency_kind: kind,
131 dependency_id: name,
132 }
133 }
134}
135
136#[derive(Clone, Debug)]
137pub struct PeekDataflowPlan {
138 pub(crate) desc: mz_compute_types::dataflows::LirDataflowDescription,
139 pub(crate) id: GlobalId,
140 key: Vec<MirScalarExpr>,
141 permutation: Vec<usize>,
142 thinned_arity: usize,
143}
144
145impl PeekDataflowPlan {
146 pub fn new(
147 desc: mz_compute_types::dataflows::LirDataflowDescription,
148 id: GlobalId,
149 typ: &SqlRelationType,
150 ) -> Self {
151 let arity = typ.arity();
152 let key = typ
153 .default_key()
154 .into_iter()
155 .map(MirScalarExpr::column)
156 .collect::<Vec<_>>();
157 let (permutation, thinning) = permutation_for_arrangement(&key, arity);
158 Self {
159 desc,
160 id,
161 key,
162 permutation,
163 thinned_arity: thinning.len(),
164 }
165 }
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd)]
169pub enum FastPathPlan {
170 Constant(Result<Vec<(Row, Diff)>, EvalError>, SqlRelationType),
175 PeekExisting(GlobalId, GlobalId, Option<Vec<Row>>, mz_expr::SafeMfpPlan),
178 PeekPersist(GlobalId, Option<Row>, mz_expr::SafeMfpPlan),
180}
181
182impl<'a, T: 'a> DisplayText<PlanRenderingContext<'a, T>> for FastPathPlan {
183 fn fmt_text(
184 &self,
185 f: &mut fmt::Formatter<'_>,
186 ctx: &mut PlanRenderingContext<'a, T>,
187 ) -> fmt::Result {
188 if ctx.config.verbose_syntax {
189 self.fmt_verbose_text(f, ctx)
190 } else {
191 self.fmt_default_text(f, ctx)
192 }
193 }
194}
195
196impl FastPathPlan {
197 pub fn fmt_default_text<'a, T>(
198 &self,
199 f: &mut fmt::Formatter<'_>,
200 ctx: &mut PlanRenderingContext<'a, T>,
201 ) -> fmt::Result {
202 let mode = HumanizedExplain::new(ctx.config.redacted);
203
204 match self {
205 FastPathPlan::Constant(rows, _) => {
206 write!(f, "{}→Constant ", ctx.indent)?;
207
208 match rows {
209 Ok(rows) => writeln!(f, "({} rows)", rows.len())?,
210 Err(err) => {
211 if mode.redacted() {
212 writeln!(f, "(error: █)")?;
213 } else {
214 writeln!(f, "(error: {})", err.to_string().quoted(),)?;
215 }
216 }
217 }
218 }
219 FastPathPlan::PeekExisting(coll_id, idx_id, literal_constraints, mfp) => {
220 let coll = ctx
221 .humanizer
222 .humanize_id(*coll_id)
223 .unwrap_or_else(|| coll_id.to_string());
224 let idx = ctx
225 .humanizer
226 .humanize_id(*idx_id)
227 .unwrap_or_else(|| idx_id.to_string());
228 writeln!(f, "{}→Map/Filter/Project", ctx.indent)?;
229 ctx.indent.set();
230
231 ctx.indent += 1;
232
233 mode.expr(mfp.deref(), None).fmt_default_text(f, ctx)?;
234 let printed = !mfp.expressions.is_empty() || !mfp.predicates.is_empty();
235
236 if printed {
237 ctx.indent += 1;
238 }
239 if let Some(literal_constraints) = literal_constraints {
240 writeln!(f, "{}→Index Lookup on {coll} (using {idx})", ctx.indent)?;
241 ctx.indent += 1;
242 let values = separated("; ", mode.seq(literal_constraints, None));
243 writeln!(f, "{}Lookup values: {values}", ctx.indent)?;
244 } else {
245 writeln!(f, "{}→Indexed {coll} (using {idx})", ctx.indent)?;
246 }
247
248 ctx.indent.reset();
249 }
250 FastPathPlan::PeekPersist(global_id, literal_constraint, mfp) => {
251 let coll = ctx
252 .humanizer
253 .humanize_id(*global_id)
254 .unwrap_or_else(|| global_id.to_string());
255 writeln!(f, "{}→Map/Filter/Project", ctx.indent)?;
256 ctx.indent.set();
257
258 ctx.indent += 1;
259
260 mode.expr(mfp.deref(), None).fmt_default_text(f, ctx)?;
261 let printed = !mfp.expressions.is_empty() || !mfp.predicates.is_empty();
262
263 if printed {
264 ctx.indent += 1;
265 }
266 if let Some(literal_constraint) = literal_constraint {
267 writeln!(f, "{}→ReadStorage Lookup on {coll}", ctx.indent)?;
268 ctx.indent += 1;
269 let value = mode.expr(literal_constraint, None);
270 writeln!(f, "{}Lookup value: {value}", ctx.indent)?;
271 } else {
272 writeln!(f, "{}→ReadStorage {coll}", ctx.indent)?;
273 }
274
275 ctx.indent.reset();
276 }
277 }
278
279 Ok(())
280 }
281
282 pub fn fmt_verbose_text<'a, T>(
283 &self,
284 f: &mut fmt::Formatter<'_>,
285 ctx: &mut PlanRenderingContext<'a, T>,
286 ) -> fmt::Result {
287 let redacted = ctx.config.redacted;
288 let mode = HumanizedExplain::new(redacted);
289
290 match self {
293 FastPathPlan::Constant(Ok(rows), _) => {
294 if !rows.is_empty() {
295 writeln!(f, "{}Constant", ctx.indent)?;
296 *ctx.as_mut() += 1;
297 fmt_text_constant_rows(
298 f,
299 rows.iter().map(|(row, diff)| (row, diff)),
300 ctx.as_mut(),
301 redacted,
302 )?;
303 *ctx.as_mut() -= 1;
304 } else {
305 writeln!(f, "{}Constant <empty>", ctx.as_mut())?;
306 }
307 Ok(())
308 }
309 FastPathPlan::Constant(Err(err), _) => {
310 if redacted {
311 writeln!(f, "{}Error █", ctx.as_mut())
312 } else {
313 writeln!(f, "{}Error {}", ctx.as_mut(), err.to_string().escaped())
314 }
315 }
316 FastPathPlan::PeekExisting(coll_id, idx_id, literal_constraints, mfp) => {
317 ctx.as_mut().set();
318 let (map, filter, project) = mfp.as_map_filter_project();
319
320 let cols = if !ctx.config.humanized_exprs {
321 None
322 } else if let Some(cols) = ctx.humanizer.column_names_for_id(*idx_id) {
323 let cols = itertools::chain(
327 cols.iter().cloned(),
328 std::iter::repeat(String::new()).take(map.len()),
329 )
330 .collect();
331 Some(cols)
332 } else {
333 None
334 };
335
336 if project.len() != mfp.input_arity + map.len()
337 || !project.iter().enumerate().all(|(i, o)| i == *o)
338 {
339 let outputs = mode.seq(&project, cols.as_ref());
340 let outputs = CompactScalars(outputs);
341 writeln!(f, "{}Project ({})", ctx.as_mut(), outputs)?;
342 *ctx.as_mut() += 1;
343 }
344 if !filter.is_empty() {
345 let predicates = separated(" AND ", mode.seq(&filter, cols.as_ref()));
346 writeln!(f, "{}Filter {}", ctx.as_mut(), predicates)?;
347 *ctx.as_mut() += 1;
348 }
349 if !map.is_empty() {
350 let scalars = mode.seq(&map, cols.as_ref());
351 let scalars = CompactScalars(scalars);
352 writeln!(f, "{}Map ({})", ctx.as_mut(), scalars)?;
353 *ctx.as_mut() += 1;
354 }
355 MirRelationExpr::fmt_indexed_filter(
356 f,
357 ctx,
358 coll_id,
359 idx_id,
360 literal_constraints.clone(),
361 None,
362 )?;
363 writeln!(f)?;
364 ctx.as_mut().reset();
365 Ok(())
366 }
367 FastPathPlan::PeekPersist(gid, literal_constraint, mfp) => {
368 ctx.as_mut().set();
369 let (map, filter, project) = mfp.as_map_filter_project();
370
371 let cols = if !ctx.config.humanized_exprs {
372 None
373 } else if let Some(cols) = ctx.humanizer.column_names_for_id(*gid) {
374 let cols = itertools::chain(
375 cols.iter().cloned(),
376 std::iter::repeat(String::new()).take(map.len()),
377 )
378 .collect::<Vec<_>>();
379 Some(cols)
380 } else {
381 None
382 };
383
384 if project.len() != mfp.input_arity + map.len()
385 || !project.iter().enumerate().all(|(i, o)| i == *o)
386 {
387 let outputs = mode.seq(&project, cols.as_ref());
388 let outputs = CompactScalars(outputs);
389 writeln!(f, "{}Project ({})", ctx.as_mut(), outputs)?;
390 *ctx.as_mut() += 1;
391 }
392 if !filter.is_empty() {
393 let predicates = separated(" AND ", mode.seq(&filter, cols.as_ref()));
394 writeln!(f, "{}Filter {}", ctx.as_mut(), predicates)?;
395 *ctx.as_mut() += 1;
396 }
397 if !map.is_empty() {
398 let scalars = mode.seq(&map, cols.as_ref());
399 let scalars = CompactScalars(scalars);
400 writeln!(f, "{}Map ({})", ctx.as_mut(), scalars)?;
401 *ctx.as_mut() += 1;
402 }
403 let human_id = ctx
404 .humanizer
405 .humanize_id(*gid)
406 .unwrap_or_else(|| gid.to_string());
407 write!(f, "{}PeekPersist {human_id}", ctx.as_mut())?;
408 if let Some(literal) = literal_constraint {
409 let value = mode.expr(literal, None);
410 writeln!(f, " [value={}]", value)?;
411 } else {
412 writeln!(f, "")?;
413 }
414 ctx.as_mut().reset();
415 Ok(())
416 }
417 }?;
418 Ok(())
419 }
420}
421
422#[derive(Debug)]
423pub struct PlannedPeek {
424 pub plan: PeekPlan,
425 pub determination: TimestampDetermination,
426 pub conn_id: ConnectionId,
427 pub intermediate_result_type: SqlRelationType,
434 pub source_arity: usize,
435 pub source_ids: BTreeSet<GlobalId>,
436}
437
438#[derive(Clone, Debug)]
440pub enum PeekPlan {
441 FastPath(FastPathPlan),
442 SlowPath(PeekDataflowPlan),
444}
445
446fn mfp_to_safe_plan(
451 mfp: mz_expr::MapFilterProject,
452) -> Result<mz_expr::SafeMfpPlan, OptimizerError> {
453 mfp.into_plan()
454 .map_err(OptimizerError::InternalUnsafeMfpPlan)?
455 .into_nontemporal()
456 .map_err(|e| OptimizerError::InternalUnsafeMfpPlan(format!("{:?}", e)))
457}
458
459fn permute_oneshot_mfp_around_index(
461 mfp: mz_expr::MapFilterProject,
462 key: &[MirScalarExpr],
463) -> Result<mz_expr::SafeMfpPlan, OptimizerError> {
464 let input_arity = mfp.input_arity;
465 let mut safe_mfp = mfp_to_safe_plan(mfp)?;
466 let (permute, thinning) = permutation_for_arrangement(key, input_arity);
467 safe_mfp.permute_fn(|c| permute[c], key.len() + thinning.len());
468 Ok(safe_mfp)
469}
470
471pub fn create_fast_path_plan(
477 dataflow_plan: &mut DataflowDescription<OptimizedMirRelationExpr>,
478 view_id: GlobalId,
479 finishing: Option<&RowSetFinishing>,
480 persist_fast_path_limit: usize,
481 persist_fast_path_order: bool,
482) -> Result<Option<FastPathPlan>, OptimizerError> {
483 if dataflow_plan.objects_to_build.len() >= 1 && dataflow_plan.objects_to_build[0].id == view_id
489 {
490 let mut mir = &*dataflow_plan.objects_to_build[0].plan.as_inner_mut();
491 if let Some((rows, found_typ)) = mir.as_const() {
492 let plan = FastPathPlan::Constant(
494 rows.clone(),
495 mz_repr::SqlRelationType::from_repr(found_typ),
496 );
497 return Ok(Some(plan));
498 } else {
499 if let MirRelationExpr::TopK {
502 input,
503 group_key,
504 order_key,
505 limit,
506 offset,
507 monotonic: _,
508 expected_group_size: _,
509 } = mir
510 {
511 if let Some(finishing) = finishing {
512 if group_key.is_empty() && *order_key == finishing.order_by && *offset == 0 {
513 let finishing_limits_at_least_as_topk = match (limit, finishing.limit) {
516 (None, _) => true,
517 (Some(..), None) => false,
518 (Some(topk_limit), Some(finishing_limit)) => {
519 if let Some(l) = topk_limit.as_literal_int64() {
520 i128::cast_from(l)
521 >= i128::cast_from(*finishing_limit)
522 + i128::cast_from(finishing.offset)
523 } else {
524 false
525 }
526 }
527 };
528 if finishing_limits_at_least_as_topk {
529 mir = input;
530 }
531 }
532 }
533 }
534 let (mfp, mir) = mz_expr::MapFilterProject::extract_from_expression(mir);
538 match mir {
539 MirRelationExpr::Get {
540 id: Id::Global(get_id),
541 typ: repr_typ,
542 ..
543 } => {
544 for (index_id, IndexImport { desc, .. }) in dataflow_plan.index_imports.iter() {
546 if desc.on_id == *get_id {
547 return Ok(Some(FastPathPlan::PeekExisting(
548 *get_id,
549 *index_id,
550 None,
551 permute_oneshot_mfp_around_index(mfp, &desc.key)?,
552 )));
553 }
554 }
555
556 let safe_mfp = mfp_to_safe_plan(mfp)?;
560 let (_maps, filters, projection) = safe_mfp.as_map_filter_project();
561
562 let persist_fast_path_order_relation_typ = if persist_fast_path_order {
563 Some(
564 dataflow_plan
565 .source_imports
566 .get(get_id)
567 .expect("Get's ID is also imported")
568 .desc
569 .typ
570 .clone(),
571 )
572 } else {
573 None
574 };
575
576 let literal_constraint =
577 if let Some(relation_typ) = &persist_fast_path_order_relation_typ {
578 let mut row = Row::default();
579 let mut packer = row.packer();
580 for (idx, col) in relation_typ.column_types.iter().enumerate() {
581 if !preserves_order(&col.scalar_type) {
582 break;
583 }
584 let col_expr = MirScalarExpr::column(idx);
585
586 let Some((literal, _)) = filters
587 .iter()
588 .filter_map(|f| f.expr_eq_literal(&col_expr))
589 .next()
590 else {
591 break;
592 };
593 packer.extend_by_row(&literal);
594 }
595 if row.is_empty() { None } else { Some(row) }
596 } else {
597 None
598 };
599
600 let finish_ok = match &finishing {
601 None => false,
602 Some(RowSetFinishing {
603 order_by,
604 limit,
605 offset,
606 ..
607 }) => {
608 let order_ok =
609 if let Some(relation_typ) = &persist_fast_path_order_relation_typ {
610 order_by.iter().enumerate().all(|(idx, order)| {
611 let column_idx = projection[order.column];
614 if column_idx >= safe_mfp.input_arity {
615 return false;
616 }
617 let column_type = &relation_typ.column_types[column_idx];
618 let index_ok = idx == column_idx;
619 let nulls_ok = !column_type.nullable || order.nulls_last;
620 let asc_ok = !order.desc;
621 let type_ok = preserves_order(&column_type.scalar_type);
622 index_ok && nulls_ok && asc_ok && type_ok
623 })
624 } else {
625 order_by.is_empty()
626 };
627 let limit_ok = limit.map_or(false, |l| {
628 usize::cast_from(l) + *offset < persist_fast_path_limit
629 });
630 order_ok && limit_ok
631 }
632 };
633
634 let key_constraint = if let Some(literal) = &literal_constraint {
635 let prefix_len = literal.iter().count();
636 repr_typ
637 .keys
638 .iter()
639 .any(|k| k.iter().all(|idx| *idx < prefix_len))
640 } else {
641 false
642 };
643
644 if key_constraint || (filters.is_empty() && finish_ok) {
648 return Ok(Some(FastPathPlan::PeekPersist(
649 *get_id,
650 literal_constraint,
651 safe_mfp,
652 )));
653 }
654 }
655 MirRelationExpr::Join { implementation, .. } => {
656 if let mz_expr::JoinImplementation::IndexedFilter(coll_id, idx_id, key, vals) =
657 implementation
658 {
659 return Ok(Some(FastPathPlan::PeekExisting(
660 *coll_id,
661 *idx_id,
662 Some(vals.clone()),
663 permute_oneshot_mfp_around_index(mfp, key)?,
664 )));
665 }
666 }
667 _ => {}
669 }
670 }
671 }
672 Ok(None)
673}
674
675impl FastPathPlan {
676 pub fn used_indexes(&self, finishing: Option<&RowSetFinishing>) -> UsedIndexes {
677 match self {
678 FastPathPlan::Constant(..) => UsedIndexes::default(),
679 FastPathPlan::PeekExisting(_coll_id, idx_id, literal_constraints, _mfp) => {
680 if literal_constraints.is_some() {
681 UsedIndexes::new([(*idx_id, vec![IndexUsageType::Lookup(*idx_id)])].into())
682 } else if finishing.map_or(false, |f| f.limit.is_some() && f.order_by.is_empty()) {
683 UsedIndexes::new([(*idx_id, vec![IndexUsageType::FastPathLimit])].into())
684 } else {
685 UsedIndexes::new([(*idx_id, vec![IndexUsageType::FullScan])].into())
686 }
687 }
688 FastPathPlan::PeekPersist(..) => UsedIndexes::default(),
689 }
690 }
691}
692
693impl crate::coord::Coordinator {
694 #[mz_ore::instrument(level = "debug")]
705 pub async fn implement_peek_plan(
706 &mut self,
707 ctx_extra: &mut ExecuteContextGuard,
708 plan: PlannedPeek,
709 finishing: RowSetFinishing,
710 compute_instance: ComputeInstanceId,
711 target_replica: Option<ReplicaId>,
712 max_result_size: u64,
713 max_returned_query_size: Option<u64>,
714 ) -> Result<ExecuteResponse, AdapterError> {
715 let PlannedPeek {
716 plan: fast_path,
717 determination,
718 conn_id,
719 intermediate_result_type,
720 source_arity,
721 source_ids,
722 } = plan;
723
724 if let PeekPlan::FastPath(FastPathPlan::Constant(rows, _)) = fast_path {
726 let mut rows = match rows {
727 Ok(rows) => rows,
728 Err(e) => return Err(e.into()),
729 };
730 consolidate(&mut rows);
732
733 let mut results = Vec::new();
734 for (row, count) in rows {
735 if count.is_negative() {
736 Err(EvalError::InvalidParameterValue(
737 format!("Negative multiplicity in constant result: {}", count).into(),
738 ))?
739 };
740 if count.is_positive() {
741 let count = usize::cast_from(
742 u64::try_from(count.into_inner())
743 .expect("known to be positive from check above"),
744 );
745 results.push((
746 row,
747 NonZeroUsize::new(count).expect("known to be non-zero from check above"),
748 ));
749 }
750 }
751 let row_collection = RowCollection::new(results, &finishing.order_by);
752 let duration_histogram = self.metrics.row_set_finishing_seconds();
753
754 let (ret, reason) = match finishing.finish(
755 row_collection,
756 max_result_size,
757 max_returned_query_size,
758 &duration_histogram,
759 ) {
760 Ok((rows, row_size_bytes)) => {
761 let result_size = u64::cast_from(row_size_bytes);
762 let rows_returned = u64::cast_from(rows.count());
763 (
764 Ok(Self::send_immediate_rows(rows)),
765 StatementEndedExecutionReason::Success {
766 result_size: Some(result_size),
767 rows_returned: Some(rows_returned),
768 execution_strategy: Some(StatementExecutionStrategy::Constant),
769 },
770 )
771 }
772 Err(error) => (
773 Err(AdapterError::ResultSize(error.clone())),
774 StatementEndedExecutionReason::Errored { error },
775 ),
776 };
777 self.retire_execution(reason, std::mem::take(ctx_extra).defuse());
778 return ret;
779 }
780
781 let timestamp = determination.timestamp_context.timestamp_or_default();
782 if let Some(id) = ctx_extra.contents() {
783 self.set_statement_execution_timestamp(id, timestamp)
784 }
785
786 let (peek_command, drop_dataflow, is_fast_path, peek_target, strategy, read_hold) =
796 match fast_path {
797 PeekPlan::FastPath(FastPathPlan::PeekExisting(
798 _coll_id,
799 idx_id,
800 literal_constraints,
801 map_filter_project,
802 )) => {
803 let read_hold = self
804 .controller
805 .compute
806 .acquire_read_hold(compute_instance, idx_id)
807 .map_err(
808 AdapterError::concurrent_dependency_drop_from_collection_update_error,
809 )?;
810 (
811 (literal_constraints, timestamp, map_filter_project),
812 None,
813 true,
814 PeekTarget::Index { id: idx_id },
815 StatementExecutionStrategy::FastPath,
816 read_hold,
817 )
818 }
819 PeekPlan::FastPath(FastPathPlan::PeekPersist(
820 coll_id,
821 literal_constraint,
822 map_filter_project,
823 )) => {
824 let peek_command = (
825 literal_constraint.map(|r| vec![r]),
826 timestamp,
827 map_filter_project,
828 );
829 let metadata = self
830 .controller
831 .storage
832 .collection_metadata(coll_id)
833 .expect("storage collection for fast-path peek")
834 .clone();
835 let read_hold = self
836 .controller
837 .storage_collections
838 .acquire_read_holds(vec![coll_id])
839 .map_err(AdapterError::concurrent_dependency_drop_from_collection_missing)?
840 .into_element();
841 (
842 peek_command,
843 None,
844 true,
845 PeekTarget::Persist {
846 id: coll_id,
847 metadata,
848 },
849 StatementExecutionStrategy::PersistFastPath,
850 read_hold,
851 )
852 }
853 PeekPlan::SlowPath(PeekDataflowPlan {
854 desc: dataflow,
855 id: index_id,
859 key: index_key,
860 permutation: index_permutation,
861 thinned_arity: index_thinned_arity,
862 }) => {
863 let exports: Vec<GlobalId> = dataflow.export_ids().collect();
869 soft_assert_eq_or_log!(
870 exports.as_slice(),
871 &[index_id],
872 "slow-path peek dataflow must export exactly [index_id]",
873 );
874 if exports.as_slice() != [index_id] {
875 return Err(AdapterError::internal(
876 "peek error",
877 format!(
878 "slow-path peek dataflow exports {exports:?}, expected [{index_id}]",
879 ),
880 ));
881 }
882
883 self.controller
885 .compute
886 .create_dataflow(compute_instance, dataflow, None)
887 .map_err(
888 AdapterError::concurrent_dependency_drop_from_dataflow_creation_error,
889 )?;
890
891 let acquire_result = self
894 .controller
895 .compute
896 .acquire_read_hold(compute_instance, index_id)
897 .map_err(
898 AdapterError::concurrent_dependency_drop_from_collection_update_error,
899 );
900 let read_hold = match acquire_result {
901 Ok(hold) => hold,
902 Err(e) => {
903 self.drop_compute_collections(vec![(compute_instance, index_id)]);
904 return Err(e);
905 }
906 };
907
908 let mut map_filter_project = mz_expr::MapFilterProject::new(source_arity);
910 map_filter_project.permute_fn(
911 |c| index_permutation[c],
912 index_key.len() + index_thinned_arity,
913 );
914 let map_filter_project = mfp_to_safe_plan(map_filter_project)?;
915
916 (
917 (None, timestamp, map_filter_project),
918 Some(index_id),
919 false,
920 PeekTarget::Index { id: index_id },
921 StatementExecutionStrategy::Standard,
922 read_hold,
923 )
924 }
925 PeekPlan::FastPath(_) => {
926 unreachable!()
927 }
928 };
929
930 let (rows_tx, rows_rx) = tokio::sync::oneshot::channel();
932
933 let mut uuid = Uuid::new_v4();
936 while self.pending_peeks.contains_key(&uuid) {
937 uuid = Uuid::new_v4();
938 }
939
940 let (literal_constraints, timestamp, map_filter_project) = peek_command;
941
942 let peek_result_column_names =
945 (0..intermediate_result_type.arity()).map(|i| format!("peek_{i}"));
946 let peek_result_desc =
947 RelationDesc::new(intermediate_result_type, peek_result_column_names);
948
949 let peek_result = self
950 .controller
951 .compute
952 .peek(
953 compute_instance,
954 peek_target,
955 literal_constraints,
956 uuid,
957 timestamp,
958 peek_result_desc,
959 finishing.clone(),
960 map_filter_project,
961 read_hold,
962 target_replica,
963 rows_tx,
964 )
965 .map_err(AdapterError::concurrent_dependency_drop_from_peek_error);
966 if let Err(e) = peek_result {
967 if let Some(index_id) = drop_dataflow {
969 self.drop_compute_collections(vec![(compute_instance, index_id)]);
970 }
971 return Err(e);
972 }
973
974 self.pending_peeks.insert(
978 uuid,
979 PendingPeek {
980 conn_id: conn_id.clone(),
981 cluster_id: compute_instance,
982 depends_on: source_ids,
983 ctx_extra: std::mem::take(ctx_extra),
984 is_fast_path,
985 },
986 );
987 self.client_pending_peeks
988 .entry(conn_id)
989 .or_default()
990 .insert(uuid, compute_instance);
991
992 let duration_histogram = self.metrics.row_set_finishing_seconds();
993
994 if let Some(index_id) = drop_dataflow {
1001 self.drop_compute_collections(vec![(compute_instance, index_id)]);
1002 }
1003
1004 let persist_client = self.persist_client.clone();
1005 let peek_stash_read_batch_size_bytes =
1006 mz_compute_types::dyncfgs::PEEK_RESPONSE_STASH_READ_BATCH_SIZE_BYTES
1007 .get(self.catalog().system_config().dyncfgs());
1008 let peek_stash_read_memory_budget_bytes =
1009 mz_compute_types::dyncfgs::PEEK_RESPONSE_STASH_READ_MEMORY_BUDGET_BYTES
1010 .get(self.catalog().system_config().dyncfgs());
1011
1012 let peek_response_stream = Self::create_peek_response_stream(
1013 rows_rx,
1014 finishing,
1015 max_result_size,
1016 max_returned_query_size,
1017 duration_histogram,
1018 persist_client,
1019 peek_stash_read_batch_size_bytes,
1020 peek_stash_read_memory_budget_bytes,
1021 );
1022
1023 Ok(crate::ExecuteResponse::SendingRowsStreaming {
1024 rows: Box::pin(peek_response_stream),
1025 instance_id: compute_instance,
1026 strategy,
1027 })
1028 }
1029
1030 pub(crate) fn background_peek_client(&self, catalog: &Arc<Catalog>) -> PeekClient {
1036 let build_version = catalog.state().config().build_info.human_version(None);
1037 PeekClient::new(
1038 CoordinatorClient::Background {
1039 tx: self.internal_cmd_tx.clone(),
1040 metrics: self.metrics.clone(),
1041 },
1042 catalog,
1043 Arc::clone(&self.controller.storage_collections),
1044 Arc::clone(&self.transient_id_gen),
1045 self.optimizer_metrics.clone(),
1046 self.persist_client.clone(),
1047 self.statement_logging.create_frontend(build_version),
1048 Arc::clone(&self.occ_write_semaphore),
1049 self.group_commit_tx.clone(),
1050 self.controller.read_only(),
1051 )
1052 }
1053
1054 #[mz_ore::instrument(level = "debug")]
1058 pub(crate) fn create_peek_response_stream(
1059 rows_rx: tokio::sync::oneshot::Receiver<PeekResponse>,
1060 finishing: RowSetFinishing,
1061 max_result_size: u64,
1062 max_returned_query_size: Option<u64>,
1063 duration_histogram: prometheus::Histogram,
1064 mut persist_client: mz_persist_client::PersistClient,
1065 peek_stash_read_batch_size_bytes: usize,
1066 peek_stash_read_memory_budget_bytes: usize,
1067 ) -> impl futures::Stream<Item = PeekResponseUnary> {
1068 async_stream::stream!({
1069 let result = rows_rx.await;
1070
1071 let rows = match result {
1072 Ok(rows) => rows,
1073 Err(e) => {
1074 yield PeekResponseUnary::Error(AdapterError::Unstructured(anyhow::anyhow!(e)));
1075 return;
1076 }
1077 };
1078
1079 match rows {
1080 PeekResponse::Rows(rows) => {
1081 let rows = RowCollection::merge_sorted(&rows, &finishing.order_by);
1082 match finishing.finish(
1083 rows,
1084 max_result_size,
1085 max_returned_query_size,
1086 &duration_histogram,
1087 ) {
1088 Ok((rows, _size_bytes)) => yield PeekResponseUnary::Rows(Box::new(rows)),
1089 Err(e) => {
1090 yield PeekResponseUnary::Error(AdapterError::Unstructured(
1091 anyhow::Error::msg(e),
1092 ))
1093 }
1094 }
1095 }
1096 PeekResponse::Stashed(response) => {
1097 let response = *response;
1098
1099 let shard_id = response.shard_id;
1100
1101 let mut batches = Vec::new();
1102 for proto_batch in response.batches.into_iter() {
1103 let batch =
1104 persist_client.batch_from_transmittable_batch(&shard_id, proto_batch);
1105
1106 batches.push(batch);
1107 }
1108 tracing::trace!(?batches, "stashed peek response");
1109
1110 let as_of = Antichain::from_elem(mz_repr::Timestamp::default());
1111 let read_schemas: Schemas<SourceData, ()> = Schemas {
1112 id: None,
1113 key: Arc::new(response.relation_desc.clone()),
1114 val: Arc::new(UnitSchema),
1115 };
1116
1117 let mut row_cursor = persist_client
1118 .read_batches_consolidated::<_, _, _, i64>(
1119 response.shard_id,
1120 as_of,
1121 read_schemas,
1122 batches,
1123 |_stats| true,
1124 peek_stash_read_memory_budget_bytes,
1125 )
1126 .await
1127 .expect("invalid usage");
1128
1129 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
1148 mz_ore::task::spawn(|| "read_peek_batches", async move {
1149 for rows in response.inline_rows {
1159 let result = tx.send(rows).await;
1160 if result.is_err() {
1161 tracing::debug!("receiver went away");
1162 }
1163 }
1164
1165 let mut current_batch = Vec::new();
1166 let mut current_batch_size: usize = 0;
1167
1168 'outer: while let Some(rows) = row_cursor.next().await {
1169 for ((source_data, _val), _ts, diff) in rows {
1170 let row = source_data
1171 .0
1172 .expect("we are not sending errors on this code path");
1173
1174 let diff = usize::try_from(diff)
1175 .expect("peek responses cannot have negative diffs");
1176
1177 if diff > 0 {
1178 let diff =
1179 NonZeroUsize::new(diff).expect("checked to be non-zero");
1180 current_batch_size =
1181 current_batch_size.saturating_add(row.byte_len());
1182 current_batch.push((row, diff));
1183 }
1184
1185 if current_batch_size > peek_stash_read_batch_size_bytes {
1186 let result = tx
1192 .send(RowCollection::new(
1193 current_batch.drain(..).collect_vec(),
1194 &[],
1195 ))
1196 .await;
1197 if result.is_err() {
1198 tracing::debug!("receiver went away");
1199 break 'outer;
1202 }
1203
1204 current_batch_size = 0;
1205 }
1206 }
1207 }
1208
1209 if current_batch.len() > 0 {
1210 let result = tx.send(RowCollection::new(current_batch, &[])).await;
1211 if result.is_err() {
1212 tracing::debug!("receiver went away");
1213 }
1214 }
1215
1216 let batches = row_cursor.into_lease();
1217 tracing::trace!(?response.shard_id, "cleaning up batches of peek result");
1218 for batch in batches {
1219 batch.delete().await;
1220 }
1221 });
1222
1223 assert!(
1224 finishing.is_streamable(response.relation_desc.arity()),
1225 "can only get stashed responses when the finishing is streamable"
1226 );
1227
1228 tracing::trace!("query result is streamable!");
1229
1230 assert!(finishing.is_streamable(response.relation_desc.arity()));
1231 let mut incremental_finishing = RowSetFinishingIncremental::new(
1232 finishing.offset,
1233 finishing.limit,
1234 finishing.project,
1235 max_returned_query_size,
1236 );
1237
1238 let mut got_zero_rows = true;
1239 while let Some(rows) = rx.recv().await {
1240 got_zero_rows = false;
1241
1242 let result_rows = incremental_finishing.finish_incremental(
1243 rows,
1244 max_result_size,
1245 &duration_histogram,
1246 );
1247
1248 match result_rows {
1249 Ok(result_rows) => yield PeekResponseUnary::Rows(Box::new(result_rows)),
1250 Err(e) => {
1251 yield PeekResponseUnary::Error(AdapterError::Unstructured(
1252 anyhow::Error::msg(e),
1253 ))
1254 }
1255 }
1256 }
1257
1258 if got_zero_rows {
1261 let row_iter = vec![].into_row_iter();
1262 yield PeekResponseUnary::Rows(Box::new(row_iter));
1263 }
1264 }
1265 PeekResponse::Canceled => {
1266 yield PeekResponseUnary::Canceled;
1267 }
1268 PeekResponse::Error(e) => {
1269 yield PeekResponseUnary::Error(e.into());
1270 }
1271 }
1272 })
1273 }
1274
1275 #[mz_ore::instrument(level = "debug")]
1277 pub(crate) fn cancel_pending_peeks(&mut self, conn_id: &ConnectionId) {
1278 if let Some(uuids) = self.client_pending_peeks.remove(conn_id) {
1279 self.metrics
1280 .canceled_peeks
1281 .inc_by(u64::cast_from(uuids.len()));
1282
1283 let mut inverse: BTreeMap<ComputeInstanceId, BTreeSet<Uuid>> = Default::default();
1284 for (uuid, compute_instance) in &uuids {
1285 inverse.entry(*compute_instance).or_default().insert(*uuid);
1286 }
1287 for (compute_instance, uuids) in inverse {
1288 for uuid in uuids {
1293 let _ = self.controller.compute.cancel_peek(
1294 compute_instance,
1295 uuid,
1296 PeekResponse::Canceled,
1297 );
1298 }
1299 }
1300
1301 let peeks = uuids
1302 .iter()
1303 .filter_map(|(uuid, _)| self.pending_peeks.remove(uuid))
1304 .collect::<Vec<_>>();
1305 for peek in peeks {
1306 self.retire_execution(
1307 StatementEndedExecutionReason::Canceled,
1308 peek.ctx_extra.defuse(),
1309 );
1310 }
1311 }
1312 }
1313
1314 pub(crate) fn handle_peek_notification(
1317 &mut self,
1318 uuid: Uuid,
1319 notification: PeekNotification,
1320 otel_ctx: OpenTelemetryContext,
1321 ) {
1322 if let Some(PendingPeek {
1325 conn_id: _,
1326 cluster_id: _,
1327 depends_on: _,
1328 ctx_extra,
1329 is_fast_path,
1330 }) = self.remove_pending_peek(&uuid)
1331 {
1332 let reason = match notification {
1333 PeekNotification::Success {
1334 rows: num_rows,
1335 result_size,
1336 } => {
1337 let strategy = if is_fast_path {
1338 StatementExecutionStrategy::FastPath
1339 } else {
1340 StatementExecutionStrategy::Standard
1341 };
1342 StatementEndedExecutionReason::Success {
1343 result_size: Some(result_size),
1344 rows_returned: Some(num_rows),
1345 execution_strategy: Some(strategy),
1346 }
1347 }
1348 PeekNotification::Error(error) => StatementEndedExecutionReason::Errored { error },
1349 PeekNotification::Canceled => StatementEndedExecutionReason::Canceled,
1350 };
1351 otel_ctx.attach_as_parent();
1352 self.retire_execution(reason, ctx_extra.defuse());
1353 }
1354 }
1357
1358 pub(crate) fn remove_pending_peek(&mut self, uuid: &Uuid) -> Option<PendingPeek> {
1360 let pending_peek = self.pending_peeks.remove(uuid);
1361 if let Some(pending_peek) = &pending_peek {
1362 let uuids = self
1363 .client_pending_peeks
1364 .get_mut(&pending_peek.conn_id)
1365 .expect("coord peek state is inconsistent");
1366 uuids.remove(uuid);
1367 if uuids.is_empty() {
1368 self.client_pending_peeks.remove(&pending_peek.conn_id);
1369 }
1370 }
1371 pending_peek
1372 }
1373
1374 pub(crate) async fn implement_slow_path_peek(
1380 &mut self,
1381 dataflow_plan: PeekDataflowPlan,
1382 determination: TimestampDetermination,
1383 finishing: RowSetFinishing,
1384 compute_instance: ComputeInstanceId,
1385 target_replica: Option<ReplicaId>,
1386 intermediate_result_type: SqlRelationType,
1387 source_ids: BTreeSet<GlobalId>,
1388 conn_id: ConnectionId,
1389 max_result_size: u64,
1390 max_query_result_size: Option<u64>,
1391 watch_set: Option<WatchSetCreation>,
1392 ) -> Result<ExecuteResponse, AdapterError> {
1393 let statement_logging_id = watch_set.as_ref().map(|ws| ws.logging_id);
1398 if let Some(ws) = watch_set {
1399 self.install_peek_watch_sets(conn_id.clone(), ws)
1400 .map_err(|e| {
1401 AdapterError::concurrent_dependency_drop_from_compute_lookup_error(e)
1402 })?;
1403 }
1404
1405 let source_arity = intermediate_result_type.arity();
1406
1407 let planned_peek = PlannedPeek {
1408 plan: PeekPlan::SlowPath(dataflow_plan),
1409 determination,
1410 conn_id,
1411 intermediate_result_type,
1412 source_arity,
1413 source_ids,
1414 };
1415
1416 let mut ctx_guard =
1420 ExecuteContextGuard::new(statement_logging_id, self.internal_cmd_tx.clone());
1421 let result = self
1422 .implement_peek_plan(
1423 &mut ctx_guard,
1424 planned_peek,
1425 finishing,
1426 compute_instance,
1427 target_replica,
1428 max_result_size,
1429 max_query_result_size,
1430 )
1431 .await;
1432 if result.is_err() {
1436 let _ = ctx_guard.defuse();
1437 }
1438 result
1439 }
1440
1441 pub(crate) async fn implement_copy_to(
1454 &mut self,
1455 df_desc: mz_compute_types::dataflows::LirDataflowDescription,
1456 compute_instance: ComputeInstanceId,
1457 target_replica: Option<ReplicaId>,
1458 source_ids: BTreeSet<GlobalId>,
1459 conn_id: ConnectionId,
1460 watch_set: Option<WatchSetCreation>,
1461 tx: oneshot::Sender<Result<ExecuteResponse, AdapterError>>,
1462 ) {
1463 let send_err = |tx: oneshot::Sender<Result<ExecuteResponse, AdapterError>>,
1465 e: AdapterError| {
1466 let _ = tx.send(Err(e));
1467 };
1468
1469 if let Some(ws) = watch_set {
1473 if let Err(e) = self.install_peek_watch_sets(conn_id.clone(), ws) {
1474 let err = AdapterError::concurrent_dependency_drop_from_compute_lookup_error(e);
1475 send_err(tx, err);
1476 return;
1477 }
1478 }
1479
1480 let sink_id = df_desc.sink_id();
1484
1485 let (sink_tx, sink_rx) = oneshot::channel();
1489 let active_copy_to = ActiveCopyTo {
1490 conn_id: conn_id.clone(),
1491 tx: sink_tx,
1492 cluster_id: compute_instance,
1493 depends_on: source_ids,
1494 };
1495
1496 drop(self.add_active_compute_sink(sink_id, ActiveComputeSink::CopyTo(active_copy_to)));
1498
1499 if let Err(e) = self
1502 .try_ship_dataflow(df_desc, compute_instance, target_replica)
1503 .await
1504 .map_err(AdapterError::concurrent_dependency_drop_from_dataflow_creation_error)
1505 {
1506 self.remove_active_compute_sink(sink_id).await;
1510 send_err(tx, e);
1511 return;
1512 }
1513
1514 let span = Span::current();
1519 task::spawn(
1520 || "copy to completion",
1521 async move {
1522 let res = sink_rx.await;
1523 let result = match res {
1524 Ok(res) => res,
1525 Err(_) => Err(AdapterError::Internal("copy to sender dropped".into())),
1526 };
1527
1528 let _ = tx.send(result);
1529 }
1530 .instrument(span),
1531 );
1532 }
1533
1534 pub(crate) fn send_immediate_rows<I>(rows: I) -> ExecuteResponse
1538 where
1539 I: IntoRowIterator,
1540 I::Iter: Send + Sync + 'static,
1541 {
1542 let rows = Box::new(rows.into_row_iter());
1543 ExecuteResponse::SendingRowsImmediate { rows }
1544 }
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549 use mz_expr::func::IsNull;
1550 use mz_expr::{MapFilterProject, UnaryFunc};
1551 use mz_ore::str::Indent;
1552 use mz_repr::explain::text::text_string_at;
1553 use mz_repr::explain::{DummyHumanizer, ExplainConfig, PlanRenderingContext};
1554 use mz_repr::{Datum, SqlColumnType, SqlScalarType};
1555
1556 use super::*;
1557
1558 #[mz_ore::test]
1559 #[cfg_attr(miri, ignore)] fn test_fast_path_plan_as_text() {
1561 let typ = SqlRelationType::new(vec![SqlColumnType {
1562 scalar_type: SqlScalarType::String,
1563 nullable: false,
1564 }]);
1565 let constant_err = FastPathPlan::Constant(Err(EvalError::DivisionByZero), typ.clone());
1566 let no_lookup = FastPathPlan::PeekExisting(
1567 GlobalId::User(8),
1568 GlobalId::User(10),
1569 None,
1570 MapFilterProject::new(4)
1571 .map(Some(MirScalarExpr::column(0).or(MirScalarExpr::column(2))))
1572 .project([1, 4])
1573 .into_plan()
1574 .expect("invalid plan")
1575 .into_nontemporal()
1576 .expect("invalid nontemporal"),
1577 );
1578 let lookup = FastPathPlan::PeekExisting(
1579 GlobalId::User(9),
1580 GlobalId::User(11),
1581 Some(vec![Row::pack(Some(Datum::Int32(5)))]),
1582 MapFilterProject::new(3)
1583 .filter(Some(
1584 MirScalarExpr::column(0).call_unary(UnaryFunc::IsNull(IsNull)),
1585 ))
1586 .into_plan()
1587 .expect("invalid plan")
1588 .into_nontemporal()
1589 .expect("invalid nontemporal"),
1590 );
1591
1592 let humanizer = DummyHumanizer;
1593 let config = ExplainConfig {
1594 redacted: false,
1595 verbose_syntax: true,
1596 ..Default::default()
1597 };
1598 let ctx_gen = || {
1599 let indent = Indent::default();
1600 let annotations = BTreeMap::new();
1601 PlanRenderingContext::<FastPathPlan>::new(
1602 indent,
1603 &humanizer,
1604 annotations,
1605 &config,
1606 BTreeSet::default(),
1607 )
1608 };
1609
1610 let constant_err_exp = "Error \"division by zero\"\n";
1611 let no_lookup_exp = "Project (#1, #4)\n Map ((#0 OR #2))\n ReadIndex on=u8 [DELETED INDEX]=[*** full scan ***]\n";
1612 let lookup_exp =
1613 "Filter (#0) IS NULL\n ReadIndex on=u9 [DELETED INDEX]=[lookup value=(5)]\n";
1614
1615 assert_eq!(text_string_at(&constant_err, ctx_gen), constant_err_exp);
1616 assert_eq!(text_string_at(&no_lookup, ctx_gen), no_lookup_exp);
1617 assert_eq!(text_string_at(&lookup, ctx_gen), lookup_exp);
1618
1619 let mut constant_rows = vec![
1620 (Row::pack(Some(Datum::String("hello"))), Diff::ONE),
1621 (Row::pack(Some(Datum::String("world"))), 2.into()),
1622 (Row::pack(Some(Datum::String("star"))), 500.into()),
1623 ];
1624 let constant_exp1 =
1625 "Constant\n - (\"hello\")\n - ((\"world\") x 2)\n - ((\"star\") x 500)\n";
1626 assert_eq!(
1627 text_string_at(
1628 &FastPathPlan::Constant(Ok(constant_rows.clone()), typ.clone()),
1629 ctx_gen
1630 ),
1631 constant_exp1
1632 );
1633 constant_rows
1634 .extend((0..20).map(|i| (Row::pack(Some(Datum::String(&i.to_string()))), Diff::ONE)));
1635 let constant_exp2 = "Constant\n total_rows (diffs absed): 523\n first_rows:\n - (\"hello\")\
1636 \n - ((\"world\") x 2)\n - ((\"star\") x 500)\n - (\"0\")\n - (\"1\")\
1637 \n - (\"2\")\n - (\"3\")\n - (\"4\")\n - (\"5\")\n - (\"6\")\
1638 \n - (\"7\")\n - (\"8\")\n - (\"9\")\n - (\"10\")\n - (\"11\")\
1639 \n - (\"12\")\n - (\"13\")\n - (\"14\")\n - (\"15\")\n - (\"16\")\n";
1640 assert_eq!(
1641 text_string_at(&FastPathPlan::Constant(Ok(constant_rows), typ), ctx_gen),
1642 constant_exp2
1643 );
1644 }
1645}