Skip to main content

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