Skip to main content

rupg_func/
encoding.rs

1//! The names and the numbers of the encodings, as `encnames.c` gives them: `pg_char_to_encoding` and `pg_encoding_to_char`, and the SQL functions with these names. The conversions of `mbutils.c` and `conv.c` between UTF8 and the single-byte encodings: `convert`, `convert_from`, `convert_to` and `length(bytea, name)`. The function `to_ascii` of `ascii.c`.
2
3use std::borrow::Cow;
4
5use rupg_common::{Error, Result, SqlState};
6use rupg_types::Value;
7
8use crate::generated::single_byte::TABLES;
9use crate::{Call, Kernel, bad_value, not_yet};
10
11/// `NAMEDATALEN`: a name of this length or longer is not in the table.
12const NAMEDATALEN: usize = 64;
13
14/// `pg_enc2name_tbl`: the name of each encoding by its number. The number 7, which was `MULE_INTERNAL`, has no encoding.
15const NAMES: [&str; 42] = [
16    "SQL_ASCII",
17    "EUC_JP",
18    "EUC_CN",
19    "EUC_KR",
20    "EUC_TW",
21    "EUC_JIS_2004",
22    "UTF8",
23    "",
24    "LATIN1",
25    "LATIN2",
26    "LATIN3",
27    "LATIN4",
28    "LATIN5",
29    "LATIN6",
30    "LATIN7",
31    "LATIN8",
32    "LATIN9",
33    "LATIN10",
34    "WIN1256",
35    "WIN1258",
36    "WIN866",
37    "WIN874",
38    "KOI8R",
39    "WIN1251",
40    "WIN1252",
41    "ISO_8859_5",
42    "ISO_8859_6",
43    "ISO_8859_7",
44    "ISO_8859_8",
45    "WIN1250",
46    "WIN1253",
47    "WIN1254",
48    "WIN1255",
49    "WIN1257",
50    "KOI8U",
51    "SJIS",
52    "BIG5",
53    "GBK",
54    "UHC",
55    "GB18030",
56    "JOHAB",
57    "SHIFT_JIS_2004",
58];
59
60/// `pg_encname_tbl`: each name and alias of an encoding, cleaned as `clean_encoding_name` does, with the name of the encoding.
61const ALIASES: &[(&str, &str)] = &[
62    ("abc", "WIN1258"),
63    ("alt", "WIN866"),
64    ("big5", "BIG5"),
65    ("euccn", "EUC_CN"),
66    ("eucjis2004", "EUC_JIS_2004"),
67    ("eucjp", "EUC_JP"),
68    ("euckr", "EUC_KR"),
69    ("euctw", "EUC_TW"),
70    ("gb18030", "GB18030"),
71    ("gbk", "GBK"),
72    ("iso88591", "LATIN1"),
73    ("iso885910", "LATIN6"),
74    ("iso885913", "LATIN7"),
75    ("iso885914", "LATIN8"),
76    ("iso885915", "LATIN9"),
77    ("iso885916", "LATIN10"),
78    ("iso88592", "LATIN2"),
79    ("iso88593", "LATIN3"),
80    ("iso88594", "LATIN4"),
81    ("iso88595", "ISO_8859_5"),
82    ("iso88596", "ISO_8859_6"),
83    ("iso88597", "ISO_8859_7"),
84    ("iso88598", "ISO_8859_8"),
85    ("iso88599", "LATIN5"),
86    ("johab", "JOHAB"),
87    ("koi8", "KOI8R"),
88    ("koi8r", "KOI8R"),
89    ("koi8u", "KOI8U"),
90    ("latin1", "LATIN1"),
91    ("latin10", "LATIN10"),
92    ("latin2", "LATIN2"),
93    ("latin3", "LATIN3"),
94    ("latin4", "LATIN4"),
95    ("latin5", "LATIN5"),
96    ("latin6", "LATIN6"),
97    ("latin7", "LATIN7"),
98    ("latin8", "LATIN8"),
99    ("latin9", "LATIN9"),
100    ("mskanji", "SJIS"),
101    ("shiftjis", "SJIS"),
102    ("shiftjis2004", "SHIFT_JIS_2004"),
103    ("sjis", "SJIS"),
104    ("sqlascii", "SQL_ASCII"),
105    ("tcvn", "WIN1258"),
106    ("tcvn5712", "WIN1258"),
107    ("uhc", "UHC"),
108    ("unicode", "UTF8"),
109    ("utf8", "UTF8"),
110    ("vscii", "WIN1258"),
111    ("win", "WIN1251"),
112    ("win1250", "WIN1250"),
113    ("win1251", "WIN1251"),
114    ("win1252", "WIN1252"),
115    ("win1253", "WIN1253"),
116    ("win1254", "WIN1254"),
117    ("win1255", "WIN1255"),
118    ("win1256", "WIN1256"),
119    ("win1257", "WIN1257"),
120    ("win1258", "WIN1258"),
121    ("win866", "WIN866"),
122    ("win874", "WIN874"),
123    ("win932", "SJIS"),
124    ("win936", "GBK"),
125    ("win949", "UHC"),
126    ("win950", "BIG5"),
127    ("windows1250", "WIN1250"),
128    ("windows1251", "WIN1251"),
129    ("windows1252", "WIN1252"),
130    ("windows1253", "WIN1253"),
131    ("windows1254", "WIN1254"),
132    ("windows1255", "WIN1255"),
133    ("windows1256", "WIN1256"),
134    ("windows1257", "WIN1257"),
135    ("windows1258", "WIN1258"),
136    ("windows866", "WIN866"),
137    ("windows874", "WIN874"),
138    ("windows932", "SJIS"),
139    ("windows936", "GBK"),
140    ("windows949", "UHC"),
141    ("windows950", "BIG5"),
142];
143
144/// `pg_char_to_encoding` with the name of the encoding: the canonical name of an encoding name or alias, without regard to case and to the characters that are not letters or digits.
145#[must_use]
146pub fn encoding(name: &str) -> Option<&'static str> {
147    if name.len() >= NAMEDATALEN {
148        return None;
149    }
150    let key: String =
151        name.chars().filter(char::is_ascii_alphanumeric).map(|c| c.to_ascii_lowercase()).collect();
152    ALIASES.iter().find(|(alias, _)| *alias == key).map(|(_, name)| *name)
153}
154
155/// `pg_valid_server_encoding`: true for the name or alias of an encoding that a database can have. The encodings after `KOI8U` are only for a client.
156pub(crate) fn valid_server_encoding(name: &str) -> bool {
157    let last = NAMES.iter().position(|n| *n == "KOI8U").unwrap_or_default();
158    usize::try_from(char_to_encoding(name)).is_ok_and(|n| n <= last)
159}
160
161/// `pg_char_to_encoding`: the number of an encoding name or alias, or -1.
162#[must_use]
163pub fn char_to_encoding(name: &str) -> i32 {
164    encoding(name)
165        .and_then(|name| NAMES.iter().position(|n| *n == name))
166        .and_then(|n| i32::try_from(n).ok())
167        .unwrap_or(-1)
168}
169
170/// `pg_encoding_to_char`: the name of an encoding by its number, or an empty string for a number that has no encoding.
171#[must_use]
172pub fn encoding_to_char(number: i64) -> &'static str {
173    usize::try_from(number).ok().and_then(|n| NAMES.get(n)).copied().unwrap_or_default()
174}
175
176/// The encoding of each database, which is always UTF8.
177const DATABASE: &str = "UTF8";
178
179/// The conversions of `pg_conversion` between two encodings that are not UTF8. rupg does not have them yet.
180const DIRECT: [(&str, &str); 10] = [
181    ("KOI8R", "WIN1251"),
182    ("KOI8R", "WIN866"),
183    ("WIN866", "WIN1251"),
184    ("ISO_8859_5", "KOI8R"),
185    ("ISO_8859_5", "WIN1251"),
186    ("ISO_8859_5", "WIN866"),
187    ("EUC_JP", "SJIS"),
188    ("EUC_TW", "BIG5"),
189    ("LATIN2", "WIN1250"),
190    ("EUC_JIS_2004", "SHIFT_JIS_2004"),
191];
192
193/// The code points of the bytes from 0x80 to 0xFF of a single-byte encoding. LATIN1 has no table, because each of its bytes is the code point with the same number.
194fn table(encoding: &str) -> Option<&'static [u16; 128]> {
195    TABLES.iter().find(|(name, _)| *name == encoding).map(|(_, table)| table)
196}
197
198/// True for an encoding in which each character is one byte.
199fn single_byte(encoding: &str) -> bool {
200    matches!(encoding, "SQL_ASCII" | "LATIN1") || table(encoding).is_some()
201}
202
203/// The code point of a byte of a single-byte encoding, or None for a byte with no character.
204fn decode_byte(encoding: &str, b: u8) -> Option<char> {
205    if b < 0x80 || encoding == "LATIN1" {
206        return Some(char::from(b));
207    }
208    let c = table(encoding)?[usize::from(b - 0x80)];
209    if c == 0 { None } else { char::from_u32(u32::from(c)) }
210}
211
212/// The byte of a character in a single-byte encoding, or None for a character that the encoding does not have.
213fn encode_char(encoding: &str, c: char) -> Option<u8> {
214    if let Ok(b) = u8::try_from(c)
215        && (b < 0x80 || encoding == "LATIN1")
216    {
217        return Some(b);
218    }
219    let c = u16::try_from(u32::from(c)).ok().filter(|c| *c != 0)?;
220    let at = table(encoding)?.iter().position(|x| *x == c)?;
221    u8::try_from(at + 0x80).ok()
222}
223
224/// `pg_utf_mblen`: the length of a UTF-8 character from its first byte.
225fn utf8_length(b: u8) -> usize {
226    match b {
227        0x00..=0x7f => 1,
228        0xc0..=0xdf => 2,
229        0xe0..=0xef => 3,
230        0xf0..=0xf7 => 4,
231        _ => 1,
232    }
233}
234
235/// The bytes of a character as the error messages give them: at most 8 bytes, each as `0x` and two hex digits.
236fn byte_text(bytes: &[u8]) -> String {
237    bytes.iter().take(8).map(|b| format!("0x{b:02x}")).collect::<Vec<_>>().join(" ")
238}
239
240/// The length of the character at the start of `rest` in an encoding, as `pg_encoding_mblen_or_incomplete` gives it, but not more than the bytes in `rest`.
241fn char_length(encoding: &str, rest: &[u8]) -> usize {
242    let length = if encoding == "UTF8" { rest.first().map_or(1, |b| utf8_length(*b)) } else { 1 };
243    length.min(rest.len())
244}
245
246/// `report_invalid_encoding`: the error of the bad character at the start of `rest`.
247pub(crate) fn invalid(encoding: &str, rest: &[u8]) -> Error {
248    let bytes = &rest[..char_length(encoding, rest)];
249    Error::new(
250        SqlState::CHARACTER_NOT_IN_REPERTOIRE,
251        format!("invalid byte sequence for encoding \"{encoding}\": {}", byte_text(bytes)),
252    )
253}
254
255/// `report_untranslatable_char`: the error of the character at the start of `rest`, which the destination encoding does not have.
256fn untranslatable(from: &str, to: &str, rest: &[u8]) -> Error {
257    let bytes = &rest[..char_length(from, rest)];
258    Error::new(
259        SqlState::UNTRANSLATABLE_CHARACTER,
260        format!(
261            "character with byte sequence {} in encoding \"{from}\" has no equivalent in encoding \"{to}\"",
262            byte_text(bytes)
263        ),
264    )
265}
266
267/// The length of the valid UTF-8 at the start of `bytes`. A zero byte is not valid.
268fn utf8_valid_length(bytes: &[u8]) -> usize {
269    let end = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
270    match std::str::from_utf8(&bytes[..end]) {
271        Ok(_) => end,
272        Err(e) => e.valid_up_to(),
273    }
274}
275
276/// `pg_verify_mbstr_len`: the number of characters of a string in an encoding, or the error of its first bad character.
277fn verify(encoding: &str, bytes: &[u8]) -> Result<usize> {
278    if encoding == "UTF8" {
279        let ok = utf8_valid_length(bytes);
280        if ok < bytes.len() {
281            return Err(invalid(encoding, &bytes[ok..]));
282        }
283        return Ok(bytes.iter().filter(|b| (**b & 0xc0) != 0x80).count());
284    }
285    // Each encoding has the ASCII characters as single bytes.
286    if !single_byte(encoding) && !bytes.is_ascii() {
287        return Err(not_yet(format_args!("a string in the encoding {encoding}")));
288    }
289    match bytes.iter().position(|b| *b == 0) {
290        Some(at) => Err(invalid(encoding, &bytes[at..])),
291        None => Ok(bytes.len()),
292    }
293}
294
295/// `pg_do_encoding_conversion`: a string that `verify` checked in the encoding `from`, in the encoding `to`.
296fn conversion<'a>(bytes: &'a [u8], from: &str, to: &str) -> Result<Cow<'a, [u8]>> {
297    if bytes.is_empty() || from == to || to == "SQL_ASCII" {
298        return Ok(Cow::Borrowed(bytes));
299    }
300    if from == "SQL_ASCII" {
301        verify(to, bytes)?;
302        return Ok(Cow::Borrowed(bytes));
303    }
304    if from == "UTF8" && single_byte(to) {
305        let text = std::str::from_utf8(bytes).map_err(|_| invalid(from, bytes))?;
306        let mut out = Vec::with_capacity(bytes.len());
307        for (at, c) in text.char_indices() {
308            out.push(encode_char(to, c).ok_or_else(|| untranslatable(from, to, &bytes[at..]))?);
309        }
310        return Ok(Cow::Owned(out));
311    }
312    if to == "UTF8" && single_byte(from) {
313        let mut out = String::with_capacity(bytes.len());
314        for (at, b) in bytes.iter().enumerate() {
315            out.push(decode_byte(from, *b).ok_or_else(|| untranslatable(from, to, &bytes[at..]))?);
316        }
317        return Ok(Cow::Owned(out.into_bytes()));
318    }
319    let direct = DIRECT.iter().any(|&(a, b)| (a, b) == (from, to) || (b, a) == (from, to));
320    if direct || from == "UTF8" || to == "UTF8" {
321        // Each conversion keeps the ASCII characters as they are.
322        if bytes.is_ascii() {
323            return Ok(Cow::Borrowed(bytes));
324        }
325        return Err(not_yet(format_args!("the conversion from {from} to {to}")));
326    }
327    Err(Error::new(
328        SqlState::UNDEFINED_FUNCTION,
329        format!("default conversion function for encoding \"{from}\" to \"{to}\" does not exist"),
330    ))
331}
332
333/// `pg_convert`: a string in the encoding `from` in the encoding `to`, with the names of the encodings.
334fn convert_bytes<'a>(bytes: &'a [u8], from: &str, to: &str) -> Result<Cow<'a, [u8]>> {
335    let source = encoding(from).ok_or_else(|| {
336        Error::new(
337            SqlState::INVALID_PARAMETER_VALUE,
338            format!("invalid source encoding name \"{from}\""),
339        )
340    })?;
341    let destination = encoding(to).ok_or_else(|| {
342        Error::new(
343            SqlState::INVALID_PARAMETER_VALUE,
344            format!("invalid destination encoding name \"{to}\""),
345        )
346    })?;
347    verify(source, bytes)?;
348    conversion(bytes, source, destination)
349}
350
351/// The SQL function `convert(bytea, name, name)`.
352fn pg_convert(_: &Call<'_>, args: &[Value]) -> Result<Value> {
353    let [Value::Bytea(bytes), from, to] = args else { return Err(bad_value()) };
354    let (from, to) = (from.as_str().ok_or_else(bad_value)?, to.as_str().ok_or_else(bad_value)?);
355    Ok(Value::Bytea(convert_bytes(bytes, from, to)?.into_owned()))
356}
357
358/// The SQL function `convert_from(bytea, name)`: a string in an encoding as text of the database.
359fn pg_convert_from(_: &Call<'_>, args: &[Value]) -> Result<Value> {
360    let [Value::Bytea(bytes), from] = args else { return Err(bad_value()) };
361    let out = convert_bytes(bytes, from.as_str().ok_or_else(bad_value)?, DATABASE)?;
362    let text = String::from_utf8(out.into_owned()).map_err(|_| bad_value())?;
363    Ok(Value::Text(text))
364}
365
366/// The SQL function `convert_to(text, name)`: text of the database in an encoding.
367fn pg_convert_to(_: &Call<'_>, args: &[Value]) -> Result<Value> {
368    let [Value::Text(text), to] = args else { return Err(bad_value()) };
369    let out = convert_bytes(text.as_bytes(), DATABASE, to.as_str().ok_or_else(bad_value)?)?;
370    Ok(Value::Bytea(out.into_owned()))
371}
372
373/// The SQL function `length(bytea, name)`: the number of characters of a string in an encoding.
374fn length_in_encoding(_: &Call<'_>, args: &[Value]) -> Result<Value> {
375    let [Value::Bytea(bytes), name] = args else { return Err(bad_value()) };
376    let name = name.as_str().ok_or_else(bad_value)?;
377    let encoding = encoding(name).ok_or_else(|| {
378        Error::new(SqlState::INVALID_PARAMETER_VALUE, format!("invalid encoding name \"{name}\""))
379    })?;
380    let count = verify(encoding, bytes)?;
381    Ok(Value::Int4(i32::try_from(count).map_err(|_| bad_value())?))
382}
383
384/// `pg_to_ascii`: the text with each byte that is not ASCII changed to the ASCII character of the encoding for it. Only LATIN1, LATIN2, LATIN9 and WIN1250 have such characters.
385fn to_ascii(text: &str, encoding: &str) -> Result<Value> {
386    let (ascii, start): (&[u8], u8) = match encoding {
387        "LATIN1" => (b"  cL Y  \"Ca  -R     'u .,      ?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty", 160),
388        "LATIN2" => (b" A L LS \"SSTZ-ZZ a,l'ls ,sstz\"zzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt.", 160),
389        "LATIN9" => (b"  cL YS sCa  -R     Zu .z   EeY?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty", 160),
390        "WIN1250" => (b"  ' \"    %S<STZZ `'\"\".--  s>stzz   L A  \"CS  -RZ  ,l'u .,as L\"lzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt ", 128),
391        _ => {
392            return Err(Error::new(
393                SqlState::FEATURE_NOT_SUPPORTED,
394                format!("encoding conversion from {encoding} to ASCII not supported"),
395            ));
396        }
397    };
398    let out: String = text
399        .bytes()
400        .map(|b| match b {
401            0..0x80 => char::from(b),
402            b if b < start => ' ',
403            b => char::from(ascii[usize::from(b - start)]),
404        })
405        .collect();
406    Ok(Value::Text(out))
407}
408
409/// The SQL function `to_ascii(text)`, for the encoding of the database.
410fn to_ascii_default(_: &Call<'_>, args: &[Value]) -> Result<Value> {
411    to_ascii(args.first().and_then(Value::as_str).ok_or_else(bad_value)?, DATABASE)
412}
413
414/// The SQL function `to_ascii(text, name)`.
415fn to_ascii_encname(_: &Call<'_>, args: &[Value]) -> Result<Value> {
416    let [text, name] = args else { return Err(bad_value()) };
417    let name = name.as_str().ok_or_else(bad_value)?;
418    let encoding = encoding(name).ok_or_else(|| {
419        Error::new(SqlState::UNDEFINED_OBJECT, format!("{name} is not a valid encoding name"))
420    })?;
421    to_ascii(text.as_str().ok_or_else(bad_value)?, encoding)
422}
423
424/// The SQL function `to_ascii(text, integer)`.
425fn to_ascii_enc(_: &Call<'_>, args: &[Value]) -> Result<Value> {
426    let [text, number] = args else { return Err(bad_value()) };
427    let number = number.as_i64().ok_or_else(bad_value)?;
428    let encoding = encoding_to_char(number);
429    if encoding.is_empty() {
430        return Err(Error::new(
431            SqlState::UNDEFINED_OBJECT,
432            format!("{number} is not a valid encoding code"),
433        ));
434    }
435    to_ascii(text.as_str().ok_or_else(bad_value)?, encoding)
436}
437
438/// The SQL function `pg_char_to_encoding(name)`.
439fn pg_char_to_encoding(_: &Call<'_>, args: &[Value]) -> Result<Value> {
440    let name = args.first().and_then(Value::as_str).ok_or_else(bad_value)?;
441    Ok(Value::Int4(char_to_encoding(name)))
442}
443
444/// The SQL function `pg_encoding_to_char(integer)`.
445fn pg_encoding_to_char(_: &Call<'_>, args: &[Value]) -> Result<Value> {
446    let number = args.first().and_then(Value::as_i64).ok_or_else(bad_value)?;
447    Ok(Value::text(encoding_to_char(number)))
448}
449
450/// The kernel of a function of this module by its `prosrc`.
451pub(crate) fn by_src(src: &str) -> Option<Kernel> {
452    Some(match src {
453        "PG_char_to_encoding" => pg_char_to_encoding,
454        "PG_encoding_to_char" => pg_encoding_to_char,
455        "pg_convert" => pg_convert,
456        "pg_convert_from" => pg_convert_from,
457        "pg_convert_to" => pg_convert_to,
458        "length_in_encoding" => length_in_encoding,
459        "to_ascii_default" => to_ascii_default,
460        "to_ascii_encname" => to_ascii_encname,
461        "to_ascii_enc" => to_ascii_enc,
462        _ => return None,
463    })
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    fn names_and_numbers() {
472        assert_eq!(char_to_encoding("UTF8"), 6);
473        assert_eq!(char_to_encoding("latin-1"), 8);
474        assert_eq!(char_to_encoding("mule_internal"), -1);
475        assert_eq!(char_to_encoding(""), -1);
476        assert_eq!(encoding_to_char(6), "UTF8");
477        assert_eq!(encoding_to_char(7), "");
478        assert_eq!(encoding_to_char(41), "SHIFT_JIS_2004");
479        assert_eq!(encoding_to_char(42), "");
480        assert_eq!(encoding_to_char(-1), "");
481        for (number, name) in NAMES.iter().enumerate().filter(|(_, n)| !n.is_empty()) {
482            assert_eq!(usize::try_from(char_to_encoding(name)).ok(), Some(number));
483        }
484    }
485
486    #[test]
487    fn single_byte_tables() {
488        for (name, table) in &TABLES {
489            assert!(NAMES.contains(name), "{name}");
490            for (n, c) in table.iter().enumerate().filter(|(_, c)| **c != 0) {
491                let b = u8::try_from(n + 0x80).unwrap();
492                let c = char::from_u32(u32::from(*c)).unwrap();
493                assert_eq!(decode_byte(name, b), Some(c));
494                assert_eq!(encode_char(name, c), Some(b), "{name} {c}");
495            }
496        }
497    }
498
499    #[test]
500    fn conversions() {
501        let to = |s: &str, e: &str| convert_bytes(s.as_bytes(), "UTF8", e).map(Cow::into_owned);
502        assert_eq!(to("aé€", "LATIN9").unwrap(), b"a\xe9\xa4");
503        assert_eq!(to("é", "latin1").unwrap(), b"\xe9");
504        let message = to("€", "LATIN1").unwrap_err().to_string();
505        assert!(message.contains("0xe2 0x82 0xac in encoding \"UTF8\" has no"), "{message}");
506        let from = |b: &[u8], e: &str| convert_bytes(b, e, "UTF8").map(Cow::into_owned);
507        assert_eq!(from(b"\xa4", "LATIN9").unwrap(), "€".as_bytes());
508        assert!(from(b"\x81", "WIN1252").is_err());
509        assert!(from(b"a\x00", "LATIN1").is_err());
510        assert_eq!(utf8_valid_length(b"ab\xc3\x28"), 2);
511        assert_eq!(utf8_valid_length(b"a\x00b"), 1);
512        assert_eq!(verify("UTF8", "aé€".as_bytes()).unwrap(), 3);
513    }
514}