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