Skip to main content

mz_transform/
fold_constants.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Replace operators on constants collections with constant collections.
11
12use std::cmp::Ordering;
13use std::collections::{BTreeMap, BTreeSet};
14use std::convert::TryInto;
15use std::iter;
16
17use mz_expr::visit::Visit;
18use mz_expr::{
19    AggregateExpr, ColumnOrder, Eval, EvalError, MirRelationExpr, MirScalarExpr, RowComparator,
20    TableFunc, UnaryFunc,
21};
22use mz_repr::{Datum, Diff, ReprRelationType, Row, RowArena};
23
24use crate::{TransformCtx, TransformError, any};
25
26/// Replace operators on constant collections with constant collections.
27#[derive(Debug)]
28pub struct FoldConstants {
29    /// An optional maximum size, after which optimization can cease.
30    ///
31    /// The `None` value here indicates no maximum size, but does not
32    /// currently guarantee that any constant expression will be reduced
33    /// to a `MirRelationExpr::Constant` variant.
34    pub limit: Option<usize>,
35}
36
37impl crate::Transform for FoldConstants {
38    fn name(&self) -> &'static str {
39        "FoldConstants"
40    }
41
42    #[mz_ore::instrument(
43        target = "optimizer",
44        level = "debug",
45        fields(path.segment = "fold_constants")
46    )]
47    fn actually_perform_transform(
48        &self,
49        relation: &mut MirRelationExpr,
50        _: &mut TransformCtx,
51    ) -> Result<(), TransformError> {
52        let mut type_stack = Vec::new();
53        let result = relation.try_visit_mut_post(&mut |e| -> Result<(), TransformError> {
54            let num_inputs = e.num_inputs();
55            let input_types = &type_stack[type_stack.len() - num_inputs..];
56            let mut relation_type = e.typ_with_input_types(input_types);
57            self.action(e, &mut relation_type)?;
58            type_stack.truncate(type_stack.len() - num_inputs);
59            type_stack.push(relation_type);
60            Ok(())
61        });
62        mz_repr::explain::trace_plan(&*relation);
63        result
64    }
65}
66
67impl FoldConstants {
68    /// Replace operators on constants collections with constant collections.
69    ///
70    /// This transform will cease optimization if it encounters constant collections
71    /// that are larger than `self.limit`, if that is set. It is not guaranteed that
72    /// a constant input within the limit will be reduced to a `Constant` variant.
73    pub fn action(
74        &self,
75        relation: &mut MirRelationExpr,
76        relation_type: &mut ReprRelationType,
77    ) -> Result<(), TransformError> {
78        match relation {
79            MirRelationExpr::Constant { .. } => { /* handled after match */ }
80            MirRelationExpr::Get { .. } => {}
81            MirRelationExpr::Let { .. } | MirRelationExpr::LetRec { .. } => {
82                // Constant propagation through bindings is currently handled by in NormalizeLets.
83                // Maybe we should move it / replicate it here (see database-issues#5346 for context)?
84            }
85            MirRelationExpr::Reduce {
86                input,
87                group_key,
88                aggregates,
89                monotonic: _,
90                expected_group_size: _,
91            } => {
92                // Guard against evaluating an expression that may contain
93                // unfoldable functions.
94                if group_key.iter().any(|e| e.contains_unfoldable())
95                    || aggregates.iter().any(|a| a.expr.contains_unfoldable())
96                {
97                    return Ok(());
98                }
99
100                if let Some((rows, ..)) = (**input).as_const() {
101                    let new_rows = match rows {
102                        Ok(rows) => {
103                            if let Some(rows) =
104                                Self::fold_reduce_constant(group_key, aggregates, rows, self.limit)
105                            {
106                                rows
107                            } else {
108                                return Ok(());
109                            }
110                        }
111                        Err(e) => Err(e.clone()),
112                    };
113                    *relation = MirRelationExpr::Constant {
114                        rows: new_rows,
115                        typ: relation_type.clone(),
116                    };
117                }
118            }
119            MirRelationExpr::TopK {
120                input,
121                group_key,
122                order_key,
123                limit,
124                offset,
125                ..
126            } => {
127                // Only fold constants when:
128                //
129                // 1. The `limit` value is not set, or
130                // 2. The `limit` value is set to a literal x such that x >= 0.
131                //
132                // We can improve this to arbitrary expressions, but it requires
133                // more typing.
134                if any![
135                    limit.is_none(),
136                    limit.as_ref().and_then(|l| l.as_literal_int64()) >= Some(0),
137                ] {
138                    let limit = limit
139                        .as_ref()
140                        .and_then(|l| l.as_literal_int64().map(Into::into));
141                    if let Some((rows, ..)) = (**input).as_const_mut() {
142                        if let Ok(rows) = rows {
143                            Self::fold_topk_constant(group_key, order_key, &limit, offset, rows);
144                        }
145                        *relation = input.take_dangerous();
146                    }
147                }
148            }
149            MirRelationExpr::Negate { input } => {
150                if let Some((rows, ..)) = (**input).as_const_mut() {
151                    if let Ok(rows) = rows {
152                        for (_row, diff) in rows {
153                            *diff = -*diff;
154                        }
155                    }
156                    *relation = input.take_dangerous();
157                }
158            }
159            MirRelationExpr::Threshold { input } => {
160                if let Some((rows, ..)) = (**input).as_const_mut() {
161                    if let Ok(rows) = rows {
162                        rows.retain(|(_, diff)| diff.is_positive());
163                    }
164                    *relation = input.take_dangerous();
165                }
166            }
167            MirRelationExpr::Map { input, scalars } => {
168                // Guard against evaluating expression that may contain
169                // unfoldable functions.
170                if scalars.iter().any(|e| e.contains_unfoldable()) {
171                    return Ok(());
172                }
173
174                if let Some((rows, ..)) = (**input).as_const() {
175                    // Do not evaluate calls if:
176                    // 1. The input consist of at least one row, and
177                    // 2. The scalars is a singleton mz_panic('forced panic') call.
178                    // Instead, indicate to the caller to panic.
179                    if rows.as_ref().map_or(0, |r| r.len()) > 0 && scalars.len() == 1 {
180                        if let MirScalarExpr::CallUnary {
181                            func: UnaryFunc::Panic(_),
182                            expr,
183                        } = &scalars[0]
184                        {
185                            if let Some("forced panic") = expr.as_literal_str() {
186                                let msg = "forced panic".to_string();
187                                return Err(TransformError::CallerShouldPanic(msg));
188                            }
189                        }
190                    }
191
192                    let new_rows = match rows {
193                        Ok(rows) => rows
194                            .iter()
195                            .map(|(input_row, diff)| {
196                                // TODO: reduce allocations to zero.
197                                let mut unpacked = input_row.unpack();
198                                let temp_storage = RowArena::new();
199                                for scalar in scalars.iter() {
200                                    unpacked.push(scalar.eval(&unpacked, &temp_storage)?)
201                                }
202                                Ok::<_, EvalError>((Row::pack_slice(&unpacked), *diff))
203                            })
204                            .collect::<Result<_, _>>(),
205                        Err(e) => Err(e.clone()),
206                    };
207                    *relation = MirRelationExpr::Constant {
208                        rows: new_rows,
209                        typ: relation_type.clone(),
210                    };
211                }
212            }
213            MirRelationExpr::FlatMap { input, func, exprs } => {
214                // Guard against evaluating expression that may contain unfoldable functions.
215                if exprs.iter().any(|e| e.contains_unfoldable()) {
216                    return Ok(());
217                }
218
219                if let Some((rows, ..)) = (**input).as_const() {
220                    let new_rows = match rows {
221                        Ok(rows) => Self::fold_flat_map_constant(func, exprs, rows, self.limit),
222                        Err(e) => Err(e.clone()),
223                    };
224                    match new_rows {
225                        Ok(None) => {}
226                        Ok(Some(rows)) => {
227                            *relation = MirRelationExpr::Constant {
228                                rows: Ok(rows),
229                                typ: relation_type.clone(),
230                            };
231                        }
232                        Err(err) => {
233                            *relation = MirRelationExpr::Constant {
234                                rows: Err(err),
235                                typ: relation_type.clone(),
236                            };
237                        }
238                    };
239                }
240            }
241            MirRelationExpr::Filter { input, predicates } => {
242                // Guard against evaluating expression that may contain
243                // unfoldable function calls.
244                if predicates.iter().any(|e| e.contains_unfoldable()) {
245                    return Ok(());
246                }
247
248                // If any predicate is false, reduce to the empty collection.
249                if predicates
250                    .iter()
251                    .any(|p| p.is_literal_false() || p.is_literal_null())
252                {
253                    relation.take_safely(Some(relation_type.clone()));
254                } else if let Some((rows, ..)) = (**input).as_const() {
255                    // Evaluate errors last, to reduce risk of spurious errors.
256                    predicates.sort_by_key(|p| p.is_literal_err());
257                    let new_rows = match rows {
258                        Ok(rows) => Self::fold_filter_constant(predicates, rows),
259                        Err(e) => Err(e.clone()),
260                    };
261                    *relation = MirRelationExpr::Constant {
262                        rows: new_rows,
263                        typ: relation_type.clone(),
264                    };
265                }
266            }
267            MirRelationExpr::Project { input, outputs } => {
268                if let Some((rows, ..)) = (**input).as_const() {
269                    let mut row_buf = Row::default();
270                    let new_rows = match rows {
271                        Ok(rows) => Ok(rows
272                            .iter()
273                            .map(|(input_row, diff)| {
274                                // TODO: reduce allocations to zero.
275                                let datums = input_row.unpack();
276                                row_buf.packer().extend(outputs.iter().map(|i| &datums[*i]));
277                                (row_buf.clone(), *diff)
278                            })
279                            .collect()),
280                        Err(e) => Err(e.clone()),
281                    };
282                    *relation = MirRelationExpr::Constant {
283                        rows: new_rows,
284                        typ: relation_type.clone(),
285                    };
286                }
287            }
288            MirRelationExpr::Join {
289                inputs,
290                equivalences,
291                ..
292            } => {
293                if inputs.iter().any(|e| e.is_empty()) {
294                    relation.take_safely(Some(relation_type.clone()));
295                } else if let Some(e) = inputs.iter().find_map(|i| i.as_const_err()) {
296                    *relation = MirRelationExpr::Constant {
297                        rows: Err(e.clone()),
298                        typ: relation_type.clone(),
299                    };
300                } else if inputs
301                    .iter()
302                    .all(|i| matches!(i.as_const(), Some((Ok(_), ..))))
303                {
304                    // Guard against evaluating expression that may contain unfoldable functions.
305                    if equivalences
306                        .iter()
307                        .any(|equiv| equiv.iter().any(|e| e.contains_unfoldable()))
308                    {
309                        return Ok(());
310                    }
311
312                    // We can fold all constant inputs together, but must apply the constraints to restrict them.
313                    // We start with a single 0-ary row.
314                    let mut old_rows = vec![(Row::pack::<_, Datum>(None), Diff::ONE)];
315                    let mut row_buf = Row::default();
316                    for input in inputs.iter() {
317                        if let Some((Ok(rows), ..)) = input.as_const() {
318                            if let Some(limit) = self.limit {
319                                if old_rows.len() * rows.len() > limit {
320                                    // Bail out if we have produced too many rows.
321                                    // TODO: progressively apply equivalences to narrow this count
322                                    // as we go, rather than at the end.
323                                    return Ok(());
324                                }
325                            }
326                            let mut next_rows = Vec::new();
327                            for (old_row, old_count) in old_rows {
328                                for (new_row, new_count) in rows.iter() {
329                                    let mut packer = row_buf.packer();
330                                    packer.extend_by_row(&old_row);
331                                    packer.extend_by_row(new_row);
332                                    next_rows.push((row_buf.clone(), old_count * *new_count));
333                                }
334                            }
335                            old_rows = next_rows;
336                        }
337                    }
338
339                    // Now throw away anything that doesn't satisfy the requisite constraints.
340                    let mut datum_vec = mz_repr::DatumVec::new();
341                    old_rows.retain(|(row, _count)| {
342                        let datums = datum_vec.borrow_with(row);
343                        let temp_storage = RowArena::new();
344                        equivalences.iter().all(|equivalence| {
345                            let mut values =
346                                equivalence.iter().map(|e| e.eval(&datums, &temp_storage));
347                            if let Some(value) = values.next() {
348                                values.all(|v| v == value)
349                            } else {
350                                true
351                            }
352                        })
353                    });
354
355                    *relation = MirRelationExpr::Constant {
356                        rows: Ok(old_rows),
357                        typ: relation_type.clone(),
358                    };
359                }
360                // TODO: General constant folding for all constant inputs.
361            }
362            MirRelationExpr::Union { base, inputs } => {
363                if let Some(e) = iter::once(&mut **base)
364                    .chain(&mut *inputs)
365                    .find_map(|i| i.as_const_err())
366                {
367                    *relation = MirRelationExpr::Constant {
368                        rows: Err(e.clone()),
369                        typ: relation_type.clone(),
370                    };
371                } else {
372                    let mut rows = vec![];
373                    let mut new_inputs = vec![];
374
375                    for input in iter::once(&mut **base).chain(&mut *inputs) {
376                        if let Some((Ok(rs), ..)) = input.as_const() {
377                            rows.extend(rs.clone());
378                        } else {
379                            new_inputs.push(input.clone())
380                        }
381                    }
382                    if !rows.is_empty() {
383                        new_inputs.push(MirRelationExpr::Constant {
384                            rows: Ok(rows),
385                            typ: relation_type.clone(),
386                        });
387                    }
388
389                    *relation = MirRelationExpr::union_many(new_inputs, relation_type.clone());
390                }
391            }
392            MirRelationExpr::ArrangeBy { .. } => {
393                // Don't fold ArrangeBys, because that could result in unarranged Delta join inputs.
394                // See also the comment on `MirRelationExpr::Constant`.
395            }
396        }
397
398        // This transformation maintains the invariant that all constant nodes
399        // will be consolidated. We have to make a separate check for constant
400        // nodes here, since the match arm above might install new constant
401        // nodes.
402        if let Some((Ok(rows), typ)) = relation.as_const_mut() {
403            // Reduce down to canonical representation.
404            differential_dataflow::consolidation::consolidate(rows);
405
406            // Re-establish nullability of each column.
407            for col_type in typ.column_types.iter_mut() {
408                col_type.nullable = false;
409            }
410            for (row, _) in rows.iter_mut() {
411                for (index, datum) in row.iter().enumerate() {
412                    if datum.is_null() {
413                        typ.column_types[index].nullable = true;
414                    }
415                }
416            }
417            *relation_type = typ.clone();
418        }
419
420        Ok(())
421    }
422
423    // TODO(benesch): remove this once this function no longer makes use of
424    // potentially dangerous `as` conversions.
425    #[allow(clippy::as_conversions)]
426    fn fold_reduce_constant(
427        group_key: &[MirScalarExpr],
428        aggregates: &[AggregateExpr],
429        rows: &[(Row, Diff)],
430        limit: Option<usize>,
431    ) -> Option<Result<Vec<(Row, Diff)>, EvalError>> {
432        // Build a map from `group_key` to `Vec<Vec<an, ..., a1>>)`,
433        // where `an` is the input to the nth aggregate function in
434        // `aggregates`.
435        let mut groups = BTreeMap::new();
436        let temp_storage2 = RowArena::new();
437        let mut row_buf = Row::default();
438        let mut limit_remaining =
439            limit.map_or(Diff::MAX, |limit| Diff::try_from(limit).expect("must fit"));
440        for (row, diff) in rows {
441            // We currently maintain the invariant that any negative
442            // multiplicities will be consolidated away before they
443            // arrive at a reduce.
444
445            if *diff <= Diff::ZERO {
446                return Some(Err(EvalError::InvalidParameterValue(
447                    "constant folding encountered reduce on collection with non-positive multiplicities".into()
448                )));
449            }
450
451            if limit_remaining < *diff {
452                return None;
453            }
454            limit_remaining -= diff;
455
456            let datums = row.unpack();
457            let temp_storage = RowArena::new();
458            let key = match group_key
459                .iter()
460                .map(|e| e.eval(&datums, &temp_storage2))
461                .collect::<Result<Vec<_>, _>>()
462            {
463                Ok(key) => key,
464                Err(e) => return Some(Err(e)),
465            };
466            let val = match aggregates
467                .iter()
468                .map(|agg| {
469                    row_buf
470                        .packer()
471                        .extend([agg.expr.eval(&datums, &temp_storage)?]);
472                    Ok::<_, EvalError>(row_buf.clone())
473                })
474                .collect::<Result<Vec<_>, _>>()
475            {
476                Ok(val) => val,
477                Err(e) => return Some(Err(e)),
478            };
479            // Store the value once alongside its multiplicity rather than
480            // expanding it into `diff` copies. The count-aware aggregate
481            // `eval` consumes the multiplicity directly, so this avoids
482            // materializing a number of rows proportional to the diff.
483            let entry = groups.entry(key).or_insert_with(Vec::new);
484            entry.push((val, *diff));
485        }
486
487        // For each group, apply the aggregate function to the rows
488        // in the group. The output is
489        // `Vec<Vec<k1, ..., kn, r1, ..., rn>>`
490        // where kn is the nth column of the key and rn is the
491        // result of the nth aggregate function for that group.
492        let new_rows = groups
493            .into_iter()
494            .map({
495                let mut row_buf = Row::default();
496                move |(key, vals)| {
497                    let temp_storage = RowArena::new();
498                    row_buf.packer().extend(key.into_iter().chain(
499                        aggregates.iter().enumerate().map(|(i, agg)| {
500                            if agg.distinct {
501                                // Distinct collapses to one copy per value, so
502                                // each distinct datum gets a unit multiplicity.
503                                agg.func.eval(
504                                    vals.iter()
505                                        .map(|(val, _diff)| val[i].unpack_first())
506                                        .collect::<BTreeSet<_>>()
507                                        .into_iter()
508                                        .map(|datum| (datum, Diff::ONE)),
509                                    &temp_storage,
510                                )
511                            } else {
512                                agg.func.eval(
513                                    vals.iter()
514                                        .map(|(val, diff)| (val[i].unpack_first(), *diff)),
515                                    &temp_storage,
516                                )
517                            }
518                        }),
519                    ));
520                    (row_buf.clone(), Diff::ONE)
521                }
522            })
523            .collect();
524        Some(Ok(new_rows))
525    }
526
527    fn fold_topk_constant<'a>(
528        group_key: &[usize],
529        order_key: &[ColumnOrder],
530        limit: &Option<Diff>,
531        offset: &usize,
532        rows: &'a mut [(Row, Diff)],
533    ) {
534        // helper functions for comparing elements by order_key and group_key
535        let comparator = RowComparator::new(order_key);
536
537        let mut cmp_order_key = |lhs: &(Row, Diff), rhs: &(Row, Diff)| {
538            comparator.compare_rows(&lhs.0, &rhs.0, || lhs.cmp(rhs))
539        };
540        let mut cmp_group_key = {
541            let group_key = group_key
542                .iter()
543                .map(|column| ColumnOrder {
544                    column: *column,
545                    // desc and nulls_last don't matter: the sorting by cmp_group_key is just to
546                    // make the elements of each group appear next to each other, but the order of
547                    // groups doesn't matter.
548                    desc: false,
549                    nulls_last: false,
550                })
551                .collect::<Vec<ColumnOrder>>();
552            let comparator = RowComparator::new(group_key);
553            move |lhs: &(Row, Diff), rhs: &(Row, Diff)| {
554                comparator.compare_rows(&lhs.0, &rhs.0, || Ordering::Equal)
555            }
556        };
557
558        // compute Ordering based on the sort_key, otherwise consider all rows equal
559        rows.sort_by(&mut cmp_order_key);
560
561        // sort by the grouping key if not empty, keeping order_key as a secondary sort
562        if !group_key.is_empty() {
563            rows.sort_by(&mut cmp_group_key);
564        };
565
566        let same_group_key =
567            |lhs: &(Row, Diff), rhs: &(Row, Diff)| cmp_group_key(lhs, rhs) == Ordering::Equal;
568
569        let mut cursor = 0;
570        while cursor < rows.len() {
571            // first, reset the remaining limit and offset for the current group
572            let mut offset_rem: Diff = offset.clone().try_into().unwrap();
573            let mut limit_rem: Option<Diff> = limit.clone();
574
575            let mut finger = cursor;
576            while finger < rows.len() && same_group_key(&rows[cursor], &rows[finger]) {
577                if rows[finger].1.is_negative() {
578                    // ignore elements with negative diff
579                    rows[finger].1 = Diff::ZERO;
580                } else {
581                    // determine how many of the leading rows to ignore,
582                    // then decrement the diff and remaining offset by that number
583                    let rows_to_ignore = std::cmp::min(offset_rem, rows[finger].1);
584                    rows[finger].1 -= rows_to_ignore;
585                    offset_rem -= rows_to_ignore;
586                    // determine how many of the remaining rows to retain,
587                    // then update the diff and decrement the remaining limit by that number
588                    if let Some(limit_rem) = &mut limit_rem {
589                        let rows_to_retain = std::cmp::min(*limit_rem, rows[finger].1);
590                        rows[finger].1 = rows_to_retain;
591                        *limit_rem -= rows_to_retain;
592                    }
593                }
594                finger += 1;
595            }
596            cursor = finger;
597        }
598    }
599
600    fn fold_flat_map_constant(
601        func: &TableFunc,
602        exprs: &[MirScalarExpr],
603        rows: &[(Row, Diff)],
604        limit: Option<usize>,
605    ) -> Result<Option<Vec<(Row, Diff)>>, EvalError> {
606        // We cannot exceed `usize::MAX` in any array, so this is a fine upper bound.
607        let limit = limit.unwrap_or(usize::MAX);
608        let mut new_rows = Vec::new();
609        let mut row_buf = Row::default();
610        let mut datum_vec = mz_repr::DatumVec::new();
611        for (input_row, diff) in rows {
612            let datums = datum_vec.borrow_with(input_row);
613            let temp_storage = RowArena::new();
614            let datums = exprs
615                .iter()
616                .map(|expr| expr.eval(&datums, &temp_storage))
617                .collect::<Result<Vec<_>, _>>()?;
618            let mut output_rows = func.eval(&datums, &temp_storage)?.fuse();
619            for (output_row, diff2) in (&mut output_rows).take(limit - new_rows.len()) {
620                let mut packer = row_buf.packer();
621                packer.extend_by_row(input_row);
622                packer.extend_by_row(&output_row);
623                new_rows.push((row_buf.clone(), diff2 * *diff))
624            }
625            // If we still have records to enumerate, but dropped out of the iteration,
626            // it means we have exhausted `limit` and should stop.
627            if output_rows.next() != None {
628                return Ok(None);
629            }
630        }
631        Ok(Some(new_rows))
632    }
633
634    fn fold_filter_constant(
635        predicates: &[MirScalarExpr],
636        rows: &[(Row, Diff)],
637    ) -> Result<Vec<(Row, Diff)>, EvalError> {
638        let mut new_rows = Vec::new();
639        let mut datum_vec = mz_repr::DatumVec::new();
640        'outer: for (row, diff) in rows {
641            let datums = datum_vec.borrow_with(row);
642            let temp_storage = RowArena::new();
643            for p in &*predicates {
644                if p.eval(&datums, &temp_storage)? != Datum::True {
645                    continue 'outer;
646                }
647            }
648            new_rows.push((row.clone(), *diff))
649        }
650        Ok(new_rows)
651    }
652}