1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
// 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.
//! Harvests information about non-nullability of columns from sources.
//!
//! This transformation can simplify logic when columns are known to be non-nullable.
//! Predicates that tests for null can be elided, and aggregations can be simplified.
// TODO(frank): evaluate for redundancy with `column_knowledge`, or vice-versa.
use mz_expr::{func, AggregateExpr, AggregateFunc, MirRelationExpr, MirScalarExpr, UnaryFunc};
use mz_repr::RelationType;
use crate::{TransformCtx, TransformError};
/// Harvests information about non-nullability of columns from sources.
#[derive(Debug)]
pub struct NonNullable;
impl crate::Transform for NonNullable {
#[mz_ore::instrument(
target = "optimizer",
level = "debug",
fields(path.segment = "non_nullable")
)]
fn transform(
&self,
relation: &mut MirRelationExpr,
_: &mut TransformCtx,
) -> Result<(), TransformError> {
let result = relation.visit_pre_mut(|e| self.action(e));
mz_repr::explain::trace_plan(&*relation);
Ok(result)
}
}
impl NonNullable {
/// Harvests information about non-nullability of columns from sources.
pub fn action(&self, relation: &mut MirRelationExpr) {
match relation {
MirRelationExpr::Map { input, scalars } => {
let contains_isnull = scalars
.iter()
.map(scalar_contains_isnull)
.fold(false, |b1, b2| b1 || b2);
if contains_isnull {
let mut metadata = input.typ();
for scalar in scalars.iter_mut() {
scalar_nonnullable(scalar, &metadata);
let typ = scalar.typ(&metadata.column_types);
metadata.column_types.push(typ);
}
}
}
MirRelationExpr::Filter { input, predicates } => {
let contains_isnull = predicates
.iter()
.map(scalar_contains_isnull)
.fold(false, |b1, b2| b1 || b2);
if contains_isnull {
let metadata = input.typ();
for predicate in predicates.iter_mut() {
scalar_nonnullable(predicate, &metadata);
}
}
}
MirRelationExpr::Reduce {
input,
group_key: _,
aggregates,
monotonic: _,
expected_group_size: _,
} => {
let contains_isnull_or_count = aggregates
.iter()
.map(|a| {
let contains_null = scalar_contains_isnull(&(a).expr);
let matches_count = matches!(&(a).func, AggregateFunc::Count);
contains_null || matches_count
})
.fold(false, |b1, b2| b1 || b2);
if contains_isnull_or_count {
let metadata = input.typ();
for aggregate in aggregates.iter_mut() {
scalar_nonnullable(&mut aggregate.expr, &metadata);
aggregate_nonnullable(aggregate, &metadata);
}
}
}
_ => {}
}
}
}
/// True if the expression contains a "is null" test.
fn scalar_contains_isnull(expr: &MirScalarExpr) -> bool {
let mut result = false;
expr.visit_pre(|e| {
if let MirScalarExpr::CallUnary {
func: UnaryFunc::IsNull(func::IsNull),
..
} = e
{
result = true;
}
});
result
}
/// Transformations to scalar functions, based on nonnullability of columns.
fn scalar_nonnullable(expr: &mut MirScalarExpr, metadata: &RelationType) {
// Tests for null can be replaced by "false" for non-nullable columns.
expr.visit_pre_mut(|e| {
if let MirScalarExpr::CallUnary {
func: UnaryFunc::IsNull(func::IsNull),
expr,
} = e
{
if let MirScalarExpr::Column(c) = &**expr {
if !metadata.column_types[*c].nullable {
*e = MirScalarExpr::literal_false();
}
}
}
});
}
/// Transformations to aggregation functions, based on nonnullability of columns.
fn aggregate_nonnullable(expr: &mut AggregateExpr, metadata: &RelationType) {
// An aggregate that is a count of non-nullable data can be replaced by
// count(true).
if let (AggregateFunc::Count, MirScalarExpr::Column(c)) = (&expr.func, &expr.expr) {
if !metadata.column_types[*c].nullable && !expr.distinct {
expr.expr = MirScalarExpr::literal_true();
}
}
}