mz_expr/scalar/func/impls/
pg_legacy_char.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::str;
11
12use mz_ore::fmt::FormatBuffer;
13use mz_repr::adt::char::Char;
14use mz_repr::adt::system::PgLegacyChar;
15use mz_repr::adt::varchar::{VarChar, VarCharMaxLength};
16
17use crate::EvalError;
18
19pub fn format_pg_legacy_char<B>(buf: &mut B, c: u8) -> Result<(), EvalError>
20where
21    B: FormatBuffer,
22{
23    // PostgreSQL is willing to hold invalid UTF-8 in a `Datum::String`, but
24    // we are not.
25    match str::from_utf8(&[c]) {
26        Ok(s) => {
27            buf.write_str(s);
28            Ok(())
29        }
30        Err(_) => Err(EvalError::InvalidByteSequence {
31            byte_sequence: format!("{:#02x}", c).into(),
32            encoding_name: "UTF8".into(),
33        }),
34    }
35}
36
37sqlfunc!(
38    #[sqlname = "\"char\"_to_text"]
39    #[preserves_uniqueness = true]
40    #[inverse = to_unary!(super::CastStringToPgLegacyChar)]
41    fn cast_pg_legacy_char_to_string(a: PgLegacyChar) -> Result<String, EvalError> {
42        let mut buf = String::new();
43        format_pg_legacy_char(&mut buf, a.0)?;
44        Ok(buf)
45    }
46);
47
48sqlfunc!(
49    #[sqlname = "\"char\"_to_char"]
50    #[preserves_uniqueness = true]
51    #[inverse = to_unary!(super::CastStringToPgLegacyChar)]
52    fn cast_pg_legacy_char_to_char(a: PgLegacyChar) -> Result<Char<String>, EvalError> {
53        let mut buf = String::new();
54        format_pg_legacy_char(&mut buf, a.0)?;
55        Ok(Char(buf))
56    }
57);
58
59sqlfunc!(
60    #[sqlname = "\"char\"_to_varchar"]
61    #[preserves_uniqueness = true]
62    #[inverse = to_unary!(super::CastStringToVarChar{fail_on_len: false, length: Some(VarCharMaxLength::try_from(1).unwrap())})]
63    fn cast_pg_legacy_char_to_var_char(a: PgLegacyChar) -> Result<VarChar<String>, EvalError> {
64        let mut buf = String::new();
65        format_pg_legacy_char(&mut buf, a.0)?;
66        Ok(VarChar(buf))
67    }
68);
69
70sqlfunc!(
71    #[sqlname = "\"char\"_to_integer"]
72    #[preserves_uniqueness = true]
73    #[inverse = to_unary!(super::CastInt32ToPgLegacyChar)]
74    fn cast_pg_legacy_char_to_int32(a: PgLegacyChar) -> i32 {
75        // Per PostgreSQL, casts to `i32` are performed as if `PgLegacyChar` is
76        // signed.
77        // See: https://github.com/postgres/postgres/blob/791b1b71da35d9d4264f72a87e4078b85a2fcfb4/src/backend/utils/adt/char.c#L91-L96
78        i32::from(i8::from_ne_bytes([a.0]))
79    }
80);