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.
910//! Defines constraints that can be imposed on variables.
1112use std::fmt::Debug;
13use std::ops::{RangeBounds, RangeFrom, RangeInclusive};
1415use mz_repr::adt::numeric::Numeric;
16use mz_repr::bytes::ByteSize;
1718use super::{Value, Var, VarError};
1920pub static NUMERIC_NON_NEGATIVE: NumericNonNegNonNan = NumericNonNegNonNan;
2122pub static NUMERIC_BOUNDED_0_1_INCLUSIVE: NumericInRange<RangeInclusive<f64>> =
23 NumericInRange(0.0f64..=1.0);
2425pub static BYTESIZE_AT_LEAST_1MB: ByteSizeInRange<RangeFrom<ByteSize>> =
26 ByteSizeInRange(ByteSize::mb(1)..);
2728#[derive(Debug)]
29pub enum ValueConstraint {
30/// Variable is read-only and cannot be updated.
31ReadOnly,
32/// The variables value can be updated, but only to a fixed value.
33Fixed,
34// Arbitrary constraints over values.
35Domain(&'static dyn DynDomainConstraint),
36}
3738impl ValueConstraint {
39pub fn check_constraint(
40&self,
41 var: &dyn Var,
42 cur_value: &dyn Value,
43 new_value: &dyn Value,
44 ) -> Result<(), VarError> {
45match self {
46 ValueConstraint::ReadOnly => return Err(VarError::ReadOnlyParameter(var.name())),
47 ValueConstraint::Fixed => {
48if cur_value != new_value {
49return Err(VarError::FixedValueParameter {
50 name: var.name(),
51 value: cur_value.format(),
52 });
53 }
54 }
55 ValueConstraint::Domain(check) => check.check(var, new_value)?,
56 }
5758Ok(())
59 }
60}
6162impl Clone for ValueConstraint {
63fn clone(&self) -> Self {
64match self {
65 ValueConstraint::Fixed => ValueConstraint::Fixed,
66 ValueConstraint::ReadOnly => ValueConstraint::ReadOnly,
67 ValueConstraint::Domain(c) => ValueConstraint::Domain(*c),
68 }
69 }
70}
7172/// A type erased version of [`DomainConstraint`] that we can reference on a [`VarDefinition`].
73///
74/// [`VarDefinition`]: crate::session::vars::definitions::VarDefinition
75pub trait DynDomainConstraint: Debug + Send + Sync + 'static {
76fn check(&self, var: &dyn Var, v: &dyn Value) -> Result<(), VarError>;
77}
7879impl<D> DynDomainConstraint for D
80where
81D: DomainConstraint + Send + Sync + 'static,
82 D::Value: Value,
83{
84fn check(&self, var: &dyn Var, v: &dyn Value) -> Result<(), VarError> {
85let val = v
86 .as_any()
87 .downcast_ref::<D::Value>()
88 .expect("type should match");
89self.check(var, val)
90 }
91}
92pub trait DomainConstraint: Debug + Send + Sync + 'static {
93type Value;
9495fn check(&self, var: &dyn Var, v: &Self::Value) -> Result<(), VarError>;
96}
9798#[derive(Debug, Clone, Eq, PartialEq)]
99pub struct NumericNonNegNonNan;
100101impl DomainConstraint for NumericNonNegNonNan {
102type Value = Numeric;
103104fn check(&self, var: &dyn Var, n: &Numeric) -> Result<(), VarError> {
105if n.is_nan() || n.is_negative() {
106Err(VarError::InvalidParameterValue {
107 name: var.name(),
108 invalid_values: vec![n.to_string()],
109 reason: "only supports non-negative, non-NaN numeric values".to_string(),
110 })
111 } else {
112Ok(())
113 }
114 }
115}
116117#[derive(Debug, Clone, Eq, PartialEq)]
118pub struct NumericInRange<R>(pub R);
119120impl<R> DomainConstraint for NumericInRange<R>
121where
122R: RangeBounds<f64> + std::fmt::Debug + Send + Sync + 'static,
123{
124type Value = Numeric;
125126fn check(&self, var: &dyn Var, n: &Numeric) -> Result<(), VarError> {
127let n: f64 = (*n)
128 .try_into()
129 .map_err(|_e| VarError::InvalidParameterValue {
130 name: var.name(),
131 invalid_values: vec![n.to_string()],
132// This first check can fail if the value is NaN, out of range,
133 // OR if it underflows (i.e. is very close to 0 without actually being 0, and the closest
134 // representable float is 0).
135 //
136 // The underflow case is very unlikely to be accidentally hit by a user, so let's
137 // not make the error message more confusing by talking about it, even though that makes
138 // the error message slightly inaccurate.
139 //
140 // If the user tries to set the paramater to 0.000<hundreds more zeros>001
141 // and gets the message "only supports values in range [0.0..=1.0]", I think they will
142 // understand, or at least accept, what's going on.
143reason: format!("only supports values in range {:?}", self.0),
144 })?;
145if !self.0.contains(&n) {
146Err(VarError::InvalidParameterValue {
147 name: var.name(),
148 invalid_values: vec![n.to_string()],
149 reason: format!("only supports values in range {:?}", self.0),
150 })
151 } else {
152Ok(())
153 }
154 }
155}
156157#[derive(Debug, Clone, Eq, PartialEq)]
158pub struct ByteSizeInRange<R>(pub R);
159160impl<R> DomainConstraint for ByteSizeInRange<R>
161where
162R: RangeBounds<ByteSize> + std::fmt::Debug + Send + Sync + 'static,
163{
164type Value = ByteSize;
165166fn check(&self, var: &dyn Var, size: &ByteSize) -> Result<(), VarError> {
167if self.0.contains(size) {
168Ok(())
169 } else {
170Err(VarError::InvalidParameterValue {
171 name: var.name(),
172 invalid_values: vec![size.to_string()],
173 reason: format!("only supports values in range {:?}", self.0),
174 })
175 }
176 }
177}