mz_expr/scalar/func/impls/
int32.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
10use std::fmt;
11
12use mz_lowertest::MzReflect;
13use mz_ore::cast::ReinterpretCast;
14use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
15use mz_repr::adt::system::{Oid, PgLegacyChar};
16use mz_repr::{ColumnType, ScalarType, strconv};
17use serde::{Deserialize, Serialize};
18
19use crate::EvalError;
20use crate::scalar::func::EagerUnaryFunc;
21
22sqlfunc!(
23    #[sqlname = "-"]
24    #[preserves_uniqueness = true]
25    #[inverse = to_unary!(NegInt32)]
26    #[is_monotone = true]
27    fn neg_int32(a: i32) -> Result<i32, EvalError> {
28        a.checked_neg()
29            .ok_or_else(|| EvalError::Int32OutOfRange(a.to_string().into()))
30    }
31);
32
33sqlfunc!(
34    #[sqlname = "~"]
35    #[preserves_uniqueness = true]
36    #[inverse = to_unary!(BitNotInt32)]
37    fn bit_not_int32(a: i32) -> i32 {
38        !a
39    }
40);
41
42sqlfunc!(
43    #[sqlname = "abs"]
44    fn abs_int32(a: i32) -> Result<i32, EvalError> {
45        a.checked_abs()
46            .ok_or_else(|| EvalError::Int32OutOfRange(a.to_string().into()))
47    }
48);
49
50sqlfunc!(
51    #[sqlname = "integer_to_boolean"]
52    #[preserves_uniqueness = false]
53    #[inverse = to_unary!(super::CastBoolToInt32)]
54    fn cast_int32_to_bool(a: i32) -> bool {
55        a != 0
56    }
57);
58
59sqlfunc!(
60    #[sqlname = "integer_to_real"]
61    #[preserves_uniqueness = false]
62    #[inverse = to_unary!(super::CastFloat32ToInt32)]
63    #[is_monotone = true]
64    fn cast_int32_to_float32(a: i32) -> f32 {
65        // TODO(benesch): remove potentially dangerous usage of `as`.
66        #[allow(clippy::as_conversions)]
67        {
68            a as f32
69        }
70    }
71);
72
73sqlfunc!(
74    #[sqlname = "integer_to_double"]
75    #[preserves_uniqueness = true]
76    #[inverse = to_unary!(super::CastFloat64ToInt32)]
77    #[is_monotone = true]
78    fn cast_int32_to_float64(a: i32) -> f64 {
79        f64::from(a)
80    }
81);
82
83sqlfunc!(
84    #[sqlname = "integer_to_smallint"]
85    #[preserves_uniqueness = true]
86    #[inverse = to_unary!(super::CastInt16ToInt32)]
87    #[is_monotone = true]
88    fn cast_int32_to_int16(a: i32) -> Result<i16, EvalError> {
89        i16::try_from(a).or_else(|_| Err(EvalError::Int16OutOfRange(a.to_string().into())))
90    }
91);
92
93sqlfunc!(
94    #[sqlname = "integer_to_bigint"]
95    #[preserves_uniqueness = true]
96    #[inverse = to_unary!(super::CastInt64ToInt32)]
97    #[is_monotone = true]
98    fn cast_int32_to_int64(a: i32) -> i64 {
99        i64::from(a)
100    }
101);
102
103sqlfunc!(
104    #[sqlname = "integer_to_text"]
105    #[preserves_uniqueness = true]
106    #[inverse = to_unary!(super::CastStringToInt32)]
107    fn cast_int32_to_string(a: i32) -> String {
108        let mut buf = String::new();
109        strconv::format_int32(&mut buf, a);
110        buf
111    }
112);
113
114sqlfunc!(
115    #[sqlname = "integer_to_uint2"]
116    #[preserves_uniqueness = true]
117    #[inverse = to_unary!(super::CastUint16ToInt32)]
118    #[is_monotone = true]
119    fn cast_int32_to_uint16(a: i32) -> Result<u16, EvalError> {
120        u16::try_from(a).or_else(|_| Err(EvalError::UInt16OutOfRange(a.to_string().into())))
121    }
122);
123
124sqlfunc!(
125    #[sqlname = "integer_to_uint4"]
126    #[preserves_uniqueness = true]
127    #[inverse = to_unary!(super::CastUint32ToInt32)]
128    #[is_monotone = true]
129    fn cast_int32_to_uint32(a: i32) -> Result<u32, EvalError> {
130        u32::try_from(a).or_else(|_| Err(EvalError::UInt32OutOfRange(a.to_string().into())))
131    }
132);
133
134sqlfunc!(
135    #[sqlname = "integer_to_uint8"]
136    #[preserves_uniqueness = true]
137    #[inverse = to_unary!(super::CastUint64ToInt32)]
138    #[is_monotone = true]
139    fn cast_int32_to_uint64(a: i32) -> Result<u64, EvalError> {
140        u64::try_from(a).or_else(|_| Err(EvalError::UInt64OutOfRange(a.to_string().into())))
141    }
142);
143
144#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
145pub struct CastInt32ToNumeric(pub Option<NumericMaxScale>);
146
147impl<'a> EagerUnaryFunc<'a> for CastInt32ToNumeric {
148    type Input = i32;
149    type Output = Result<Numeric, EvalError>;
150
151    fn call(&self, a: i32) -> Result<Numeric, EvalError> {
152        let mut a = Numeric::from(a);
153        if let Some(scale) = self.0 {
154            if numeric::rescale(&mut a, scale.into_u8()).is_err() {
155                return Err(EvalError::NumericFieldOverflow);
156            }
157        }
158        // Besides `rescale`, cast is infallible.
159        Ok(a)
160    }
161
162    fn output_type(&self, input: ColumnType) -> ColumnType {
163        ScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
164    }
165
166    fn could_error(&self) -> bool {
167        self.0.is_some()
168    }
169
170    fn inverse(&self) -> Option<crate::UnaryFunc> {
171        to_unary!(super::CastNumericToInt32)
172    }
173
174    fn is_monotone(&self) -> bool {
175        true
176    }
177}
178
179impl fmt::Display for CastInt32ToNumeric {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        f.write_str("integer_to_numeric")
182    }
183}
184
185sqlfunc!(
186    #[sqlname = "integer_to_oid"]
187    #[preserves_uniqueness = true]
188    #[inverse = to_unary!(super::CastOidToInt32)]
189    fn cast_int32_to_oid(a: i32) -> Oid {
190        // For historical reasons in PostgreSQL, the bytes of the `i32` are
191        // reinterpreted as a `u32` without bounds checks, so negative `i32`s
192        // become very large positive OIDs.
193        //
194        // Do not use this as a model for behavior in other contexts. OIDs
195        // should not in general be thought of as freely convertible from
196        // `i32`s.
197        Oid(u32::reinterpret_cast(a))
198    }
199);
200
201sqlfunc!(
202    #[sqlname = "integer_to_\"char\""]
203    #[preserves_uniqueness = true]
204    #[inverse = to_unary!(super::CastPgLegacyCharToInt32)]
205    fn cast_int32_to_pg_legacy_char(a: i32) -> Result<PgLegacyChar, EvalError> {
206        // Per PostgreSQL, casts to `PgLegacyChar` are performed as if
207        // `PgLegacyChar` is signed.
208        // See: https://github.com/postgres/postgres/blob/791b1b71da35d9d4264f72a87e4078b85a2fcfb4/src/backend/utils/adt/char.c#L91-L96
209        let a = i8::try_from(a).map_err(|_| EvalError::CharOutOfRange)?;
210        Ok(PgLegacyChar(u8::reinterpret_cast(a)))
211    }
212);
213
214sqlfunc!(
215    fn chr(a: i32) -> Result<String, EvalError> {
216        // This error matches the behavior of Postgres 13/14 (and potentially earlier versions)
217        // Postgres 15 will have a different error message for negative values
218        let codepoint = u32::try_from(a).map_err(|_| EvalError::CharacterTooLargeForEncoding(a))?;
219        if codepoint == 0 {
220            Err(EvalError::NullCharacterNotPermitted)
221        } else if 0xd800 <= codepoint && codepoint < 0xe000 {
222            // Postgres returns a different error message for inputs in this range
223            Err(EvalError::CharacterNotValidForEncoding(a))
224        } else {
225            char::from_u32(codepoint)
226                .map(|u| u.to_string())
227                .ok_or(EvalError::CharacterTooLargeForEncoding(a))
228        }
229    }
230);