Skip to main content

rupg_func/
text.rs

1//! The string functions of `varlena.c`, `oracle_compat.c`, `like.c` and `quote.c` for the C locale and the UTF8 encoding.
2
3use md5::{Digest, Md5};
4use rupg_catalog::names::{NAMEDATALEN, clip};
5use rupg_common::{Error, Result, SqlState};
6use rupg_sql::Category;
7use rupg_types::{Value, oid};
8
9use crate::io::base_type;
10use crate::{Call, Kernel, bad_value, output};
11
12/// `MaxAllocSize`: the largest value that PostgreSQL can allocate.
13const MAX_ALLOC_SIZE: i64 = 0x3fff_ffff;
14/// The 4 bytes of the header of a `varlena` value.
15const VARHDRSZ: i64 = 4;
16/// The longest character of UTF8 in bytes.
17const MAX_CHAR_BYTES: i64 = 4;
18
19/// The string of an argument.
20fn arg(args: &[Value], at: usize) -> Result<&str> {
21    args.get(at).and_then(Value::as_str).ok_or_else(bad_value)
22}
23
24/// The `int4` value of an argument.
25fn int_arg(args: &[Value], at: usize) -> Result<i64> {
26    args.get(at).and_then(Value::as_i64).ok_or_else(bad_value)
27}
28
29fn negative_length() -> Error {
30    Error::new(SqlState::SUBSTRING_ERROR, "negative substring length not allowed")
31}
32
33fn too_large() -> Error {
34    Error::new(SqlState::PROGRAM_LIMIT_EXCEEDED, "requested length too large")
35}
36
37/// The prefix of a string with this number of characters.
38fn prefix(s: &str, chars: usize) -> &str {
39    s.char_indices().nth(chars).map_or(s, |(at, _)| &s[..at])
40}
41
42/// The suffix of a string after this number of characters.
43fn after(s: &str, chars: usize) -> &str {
44    s.char_indices().nth(chars).map_or("", |(at, _)| &s[at..])
45}
46
47fn char_count(s: &str) -> usize {
48    s.chars().count()
49}
50
51/// An `int4` from a count of characters or bytes, which is never larger than a value can be.
52fn count(n: usize) -> Value {
53    Value::Int4(i32::try_from(n).unwrap_or(i32::MAX))
54}
55
56/// `textcat`: `||`.
57fn textcat(_: &Call<'_>, args: &[Value]) -> Result<Value> {
58    Ok(Value::Text(format!("{}{}", arg(args, 0)?, arg(args, 1)?)))
59}
60
61/// `textlen`: the number of characters.
62fn textlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
63    Ok(count(char_count(arg(args, 0)?)))
64}
65
66/// `bpcharlen`: the number of characters without the trailing spaces.
67fn bpcharlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
68    Ok(count(char_count(arg(args, 0)?.trim_end_matches(' '))))
69}
70
71/// `textoctetlen` and `bpcharoctetlen`: the number of bytes.
72fn octetlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
73    Ok(count(arg(args, 0)?.len()))
74}
75
76/// `byteaoctetlen`.
77fn byteaoctetlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
78    let Some(Value::Bytea(b)) = args.first() else { return Err(bad_value()) };
79    Ok(count(b.len()))
80}
81
82/// `lower` and `casefold` in the C locale: only the ASCII letters change.
83fn lower(_: &Call<'_>, args: &[Value]) -> Result<Value> {
84    Ok(Value::Text(arg(args, 0)?.to_ascii_lowercase()))
85}
86
87/// `upper` in the C locale.
88fn upper(_: &Call<'_>, args: &[Value]) -> Result<Value> {
89    Ok(Value::Text(arg(args, 0)?.to_ascii_uppercase()))
90}
91
92/// `initcap` in the C locale: an ASCII letter after a letter or a digit is in lower case, and the other ASCII letters are in upper case.
93fn initcap(_: &Call<'_>, args: &[Value]) -> Result<Value> {
94    let mut out = String::new();
95    let mut after_word = false;
96    for c in arg(args, 0)?.chars() {
97        out.push(if after_word { c.to_ascii_lowercase() } else { c.to_ascii_uppercase() });
98        after_word = c.is_ascii_alphanumeric();
99    }
100    Ok(Value::Text(out))
101}
102
103/// Which ends of a string a trim removes.
104#[derive(Clone, Copy)]
105enum Ends {
106    Left,
107    Right,
108    Both,
109}
110
111fn trim<'a>(s: &'a str, set: &str, ends: Ends) -> &'a str {
112    let strip = |c: char| set.contains(c);
113    match ends {
114        Ends::Left => s.trim_start_matches(strip),
115        Ends::Right => s.trim_end_matches(strip),
116        Ends::Both => s.trim_matches(strip),
117    }
118}
119
120fn trim_call(args: &[Value], ends: Ends) -> Result<Value> {
121    let set = if args.len() > 1 { arg(args, 1)? } else { " " };
122    Ok(Value::text(trim(arg(args, 0)?, set, ends)))
123}
124
125fn ltrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
126    trim_call(args, Ends::Left)
127}
128
129fn rtrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
130    trim_call(args, Ends::Right)
131}
132
133fn btrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
134    trim_call(args, Ends::Both)
135}
136
137/// `text_substr` and `text_substr_no_len`: the characters from a position, with a length or to the end.
138fn substr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
139    let s = arg(args, 0)?;
140    let start = int_arg(args, 1)?;
141    let first = start.max(1);
142    let end = if args.len() > 2 {
143        let length = int_arg(args, 2)?;
144        if length < 0 {
145            return Err(negative_length());
146        }
147        start + length
148    } else {
149        i64::MAX
150    };
151    if end <= first {
152        return Ok(Value::text(""));
153    }
154    let skip = usize::try_from(first - 1).unwrap_or(usize::MAX);
155    let take = usize::try_from(end - first).unwrap_or(usize::MAX);
156    Ok(Value::text(prefix(after(s, skip), take)))
157}
158
159/// `textpos`: the position of the first character of the first match, or 0.
160fn textpos(_: &Call<'_>, args: &[Value]) -> Result<Value> {
161    let (s, needle) = (arg(args, 0)?, arg(args, 1)?);
162    Ok(count(s.find(needle).map_or(0, |at| char_count(&s[..at]) + 1)))
163}
164
165/// `textoverlay` and `textoverlay_no_len`: `overlay`. The positions count characters, and the length is the length of the new text when it is not given.
166fn overlay(_: &Call<'_>, args: &[Value]) -> Result<Value> {
167    let (s, placing, from) = (arg(args, 0)?, arg(args, 1)?, int_arg(args, 2)?);
168    let length = if args.len() > 3 {
169        int_arg(args, 3)?
170    } else {
171        i64::try_from(char_count(placing)).unwrap_or(i64::MAX)
172    };
173    if from <= 0 {
174        return Err(negative_length());
175    }
176    let tail = i32::try_from(from + length)
177        .map_err(|_| Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "integer out of range"))?;
178    let head = prefix(s, usize::try_from(from - 1).unwrap_or(usize::MAX));
179    let rest = after(s, usize::try_from(tail.max(1) - 1).unwrap_or(0));
180    Ok(Value::Text(format!("{head}{placing}{rest}")))
181}
182
183/// `translate`: each character of the string that is in `from` becomes the character at the same place in `to`, or goes away when `to` is shorter. The first match in `from` is the one that counts.
184fn translate(_: &Call<'_>, args: &[Value]) -> Result<Value> {
185    let (s, from, to) = (arg(args, 0)?, arg(args, 1)?, arg(args, 2)?);
186    let bytes = i64::try_from(s.len()).unwrap_or(i64::MAX);
187    if bytes.saturating_mul(MAX_CHAR_BYTES).saturating_add(VARHDRSZ) > MAX_ALLOC_SIZE {
188        return Err(too_large());
189    }
190    let to: Vec<char> = to.chars().collect();
191    let mut out = String::with_capacity(s.len());
192    for c in s.chars() {
193        match from.chars().position(|f| f == c) {
194            Some(at) => out.extend(to.get(at)),
195            None => out.push(c),
196        }
197    }
198    Ok(Value::Text(out))
199}
200
201/// `replace_text`.
202fn replace(_: &Call<'_>, args: &[Value]) -> Result<Value> {
203    let (s, from, to) = (arg(args, 0)?, arg(args, 1)?, arg(args, 2)?);
204    if from.is_empty() {
205        return Ok(Value::text(s));
206    }
207    Ok(Value::Text(s.replace(from, to)))
208}
209
210/// `split_part`: a field by its position from the start, or from the end for a negative position.
211fn split_part(_: &Call<'_>, args: &[Value]) -> Result<Value> {
212    let (s, delimiter, n) = (arg(args, 0)?, arg(args, 1)?, int_arg(args, 2)?);
213    if n == 0 {
214        return Err(Error::new(
215            SqlState::INVALID_PARAMETER_VALUE,
216            "field position must not be zero",
217        ));
218    }
219    if s.is_empty() {
220        return Ok(Value::text(""));
221    }
222    let fields: Vec<&str> =
223        if delimiter.is_empty() { vec![s] } else { s.split(delimiter).collect() };
224    let len = i64::try_from(fields.len()).unwrap_or(i64::MAX);
225    let index = if n > 0 { n - 1 } else { len + n };
226    let field = usize::try_from(index).ok().and_then(|i| fields.get(i)).copied().unwrap_or("");
227    Ok(Value::text(field))
228}
229
230/// The bits of an `int4` or `int8` argument as an unsigned number, as `convert_to_base` takes them.
231#[allow(clippy::cast_sign_loss)]
232fn unsigned_arg(args: &[Value]) -> Result<u64> {
233    match args.first() {
234        Some(Value::Int4(v)) => Ok(u64::from(*v as u32)),
235        Some(Value::Int8(v)) => Ok(*v as u64),
236        _ => Err(bad_value()),
237    }
238}
239
240/// `to_hex32` and `to_hex64`: the bits of the number in hexadecimal.
241fn to_hex(_: &Call<'_>, args: &[Value]) -> Result<Value> {
242    Ok(Value::Text(format!("{:x}", unsigned_arg(args)?)))
243}
244
245/// `to_oct32` and `to_oct64`: the bits of the number in octal.
246fn to_oct(_: &Call<'_>, args: &[Value]) -> Result<Value> {
247    Ok(Value::Text(format!("{:o}", unsigned_arg(args)?)))
248}
249
250/// `to_bin32` and `to_bin64`: the bits of the number in binary.
251fn to_bin(_: &Call<'_>, args: &[Value]) -> Result<Value> {
252    Ok(Value::Text(format!("{:b}", unsigned_arg(args)?)))
253}
254
255/// `repeat`: a negative count is 0.
256fn repeat(_: &Call<'_>, args: &[Value]) -> Result<Value> {
257    let (s, n) = (arg(args, 0)?, int_arg(args, 1)?.max(0));
258    let bytes = i64::try_from(s.len()).unwrap_or(i64::MAX);
259    match bytes.checked_mul(n).and_then(|t| t.checked_add(VARHDRSZ)) {
260        Some(total) if total <= MAX_ALLOC_SIZE && i32::try_from(total).is_ok() => {}
261        _ => return Err(too_large()),
262    }
263    Ok(Value::Text(s.repeat(usize::try_from(n).unwrap_or(0))))
264}
265
266/// `text_reverse`.
267fn reverse(_: &Call<'_>, args: &[Value]) -> Result<Value> {
268    Ok(Value::Text(arg(args, 0)?.chars().rev().collect()))
269}
270
271/// `text_left`: the first characters, or all but the last characters for a negative count.
272fn left(_: &Call<'_>, args: &[Value]) -> Result<Value> {
273    let (s, n) = (arg(args, 0)?, int_arg(args, 1)?);
274    let keep = if n >= 0 {
275        usize::try_from(n).unwrap_or(usize::MAX)
276    } else {
277        char_count(s).saturating_sub(usize::try_from(n.unsigned_abs()).unwrap_or(usize::MAX))
278    };
279    Ok(Value::text(prefix(s, keep)))
280}
281
282/// `text_right`: the last characters, or all but the first characters for a negative count.
283fn right(_: &Call<'_>, args: &[Value]) -> Result<Value> {
284    let (s, n) = (arg(args, 0)?, int_arg(args, 1)?);
285    let skip = if n >= 0 {
286        char_count(s).saturating_sub(usize::try_from(n).unwrap_or(usize::MAX))
287    } else {
288        usize::try_from(n.unsigned_abs()).unwrap_or(usize::MAX)
289    };
290    Ok(Value::text(after(s, skip)))
291}
292
293/// `lpad` and `rpad`: the string cut or filled to a number of characters.
294fn pad(args: &[Value], fill: &str, at_left: bool) -> Result<Value> {
295    let (s, len) = (arg(args, 0)?, int_arg(args, 1)?.max(0));
296    if len * MAX_CHAR_BYTES + VARHDRSZ > MAX_ALLOC_SIZE {
297        return Err(too_large());
298    }
299    let len = usize::try_from(len).unwrap_or(0);
300    let have = char_count(s);
301    if have >= len || fill.is_empty() {
302        return Ok(Value::text(prefix(s, len)));
303    }
304    let filler: String = fill.chars().cycle().take(len - have).collect();
305    Ok(Value::Text(if at_left { filler + s } else { format!("{s}{filler}") }))
306}
307
308fn lpad(_: &Call<'_>, args: &[Value]) -> Result<Value> {
309    pad(args, arg(args, 2)?, true)
310}
311
312fn rpad(_: &Call<'_>, args: &[Value]) -> Result<Value> {
313    pad(args, arg(args, 2)?, false)
314}
315
316/// `lpad(text, int4)`, which is a function in SQL that fills with spaces.
317pub(crate) fn lpad_space(_: &Call<'_>, args: &[Value]) -> Result<Value> {
318    pad(args, " ", true)
319}
320
321/// `rpad(text, int4)`, which is a function in SQL that fills with spaces.
322pub(crate) fn rpad_space(_: &Call<'_>, args: &[Value]) -> Result<Value> {
323    pad(args, " ", false)
324}
325
326/// `ascii`: the code point of the first character, or 0.
327fn ascii(_: &Call<'_>, args: &[Value]) -> Result<Value> {
328    Ok(Value::Int4(
329        arg(args, 0)?.chars().next().map_or(0, |c| i32::try_from(u32::from(c)).unwrap_or(0)),
330    ))
331}
332
333/// `chr`: the character of a code point.
334#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
335fn chr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
336    let code = int_arg(args, 0)?;
337    if code < 0 {
338        return Err(Error::new(
339            SqlState::INVALID_PARAMETER_VALUE,
340            "character number must be positive",
341        ));
342    }
343    if code == 0 {
344        return Err(Error::new(SqlState::PROGRAM_LIMIT_EXCEEDED, "null character not permitted"));
345    }
346    let code = code as u32;
347    if code > 0x10_FFFF {
348        return Err(Error::new(
349            SqlState::PROGRAM_LIMIT_EXCEEDED,
350            format!("requested character too large for encoding: {code}"),
351        ));
352    }
353    char::from_u32(code).map(|c| Value::Text(c.to_string())).ok_or_else(|| {
354        Error::new(
355            SqlState::PROGRAM_LIMIT_EXCEEDED,
356            format!("requested character not valid for encoding: {code}"),
357        )
358    })
359}
360
361/// The result of a match of `MatchText`.
362#[derive(Clone, Copy, PartialEq, Eq)]
363enum Like {
364    True,
365    False,
366    /// No suffix of the text matches either.
367    Abort,
368}
369
370fn escape_at_end() -> Error {
371    Error::new(SqlState::INVALID_ESCAPE_SEQUENCE, "LIKE pattern must not end with escape character")
372}
373
374/// The length of the UTF8 character that starts with this byte.
375fn char_len(first: u8) -> usize {
376    match first {
377        0xf0.. => 4,
378        0xe0.. => 3,
379        0xc0.. => 2,
380        _ => 1,
381    }
382}
383
384/// The bytes of the first character of `t`: one byte for `bytea`, or the length of the UTF8 character for `text`.
385fn first_len(t: &[u8], utf8: bool) -> usize {
386    if utf8 { char_len(t[0]).min(t.len()) } else { 1 }
387}
388
389/// `MatchText` of `like_match.c` with `\` as the escape character, byte by byte as PostgreSQL does it for a deterministic collation. `_` matches one UTF8 character when `utf8` is true, else one byte, as `SB_MatchText` does it for `bytea`.
390fn match_text(mut t: &[u8], mut p: &[u8], utf8: bool) -> Result<Like> {
391    if p == b"%" {
392        return Ok(Like::True);
393    }
394    while !t.is_empty() && !p.is_empty() {
395        match p[0] {
396            b'%' => {
397                p = &p[1..];
398                while let Some(&c) = p.first() {
399                    if c == b'%' {
400                        p = &p[1..];
401                    } else if c == b'_' {
402                        if t.is_empty() {
403                            return Ok(Like::Abort);
404                        }
405                        t = &t[first_len(t, utf8)..];
406                        p = &p[1..];
407                    } else {
408                        break;
409                    }
410                }
411                if p.is_empty() {
412                    return Ok(Like::True);
413                }
414                let first = if p[0] == b'\\' { *p.get(1).ok_or_else(escape_at_end)? } else { p[0] };
415                while !t.is_empty() {
416                    if t[0] == first {
417                        let matched = match_text(t, p, utf8)?;
418                        if matched != Like::False {
419                            return Ok(matched);
420                        }
421                    }
422                    t = &t[first_len(t, utf8)..];
423                }
424                return Ok(Like::Abort);
425            }
426            b'_' => {
427                t = &t[first_len(t, utf8)..];
428                p = &p[1..];
429                continue;
430            }
431            b'\\' => {
432                p = &p[1..];
433                let Some(&c) = p.first() else { return Err(escape_at_end()) };
434                if c != t[0] {
435                    return Ok(Like::False);
436                }
437            }
438            c if c != t[0] => return Ok(Like::False),
439            _ => {}
440        }
441        t = &t[1..];
442        p = &p[1..];
443    }
444    if !t.is_empty() {
445        return Ok(Like::False);
446    }
447    while p.first() == Some(&b'%') {
448        p = &p[1..];
449    }
450    Ok(if p.is_empty() { Like::True } else { Like::Abort })
451}
452
453/// True when the `bytea` value matches the pattern of `LIKE`, byte by byte.
454pub(crate) fn like_bytes(value: &[u8], pattern: &[u8]) -> Result<bool> {
455    Ok(match_text(value, pattern, false)? == Like::True)
456}
457
458/// True when the text matches the pattern of `LIKE`.
459pub(crate) fn like(text: &str, pattern: &str) -> Result<bool> {
460    Ok(match_text(text.as_bytes(), pattern.as_bytes(), true)? == Like::True)
461}
462
463fn textlike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
464    Ok(Value::Bool(like(arg(args, 0)?, arg(args, 1)?)?))
465}
466
467fn textnlike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
468    Ok(Value::Bool(!like(arg(args, 0)?, arg(args, 1)?)?))
469}
470
471/// `ILIKE` in the C locale: both strings in ASCII lower case.
472fn ilike(args: &[Value]) -> Result<bool> {
473    like(&arg(args, 0)?.to_ascii_lowercase(), &arg(args, 1)?.to_ascii_lowercase())
474}
475
476fn texticlike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
477    Ok(Value::Bool(ilike(args)?))
478}
479
480fn texticnlike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
481    Ok(Value::Bool(!ilike(args)?))
482}
483
484/// `like_escape`: the pattern with `\` as its escape character.
485fn like_escape(_: &Call<'_>, args: &[Value]) -> Result<Value> {
486    let (pattern, escape) = (arg(args, 0)?, arg(args, 1)?);
487    let mut chars = escape.chars();
488    let Some(e) = chars.next() else { return Ok(Value::Text(pattern.replace('\\', "\\\\"))) };
489    if chars.next().is_some() {
490        return Err(Error::new(SqlState::INVALID_ESCAPE_SEQUENCE, "invalid escape string")
491            .with_hint("Escape string must be empty or one character."));
492    }
493    if e == '\\' {
494        return Ok(Value::text(pattern));
495    }
496    let mut out = String::with_capacity(pattern.len());
497    let mut after_escape = false;
498    for c in pattern.chars() {
499        if c == e && !after_escape {
500            out.push('\\');
501            after_escape = true;
502        } else if c == '\\' {
503            out.push('\\');
504            if !after_escape {
505                out.push('\\');
506            }
507            after_escape = false;
508        } else {
509            out.push(c);
510            after_escape = false;
511        }
512    }
513    Ok(Value::Text(out))
514}
515
516/// `text_starts_with`.
517fn starts_with(_: &Call<'_>, args: &[Value]) -> Result<Value> {
518    Ok(Value::Bool(arg(args, 0)?.starts_with(arg(args, 1)?)))
519}
520
521/// `quote_identifier` of `ruleutils.c`: the name in double quotes unless it is a simple name in lower case that is not a key word, or that is an unreserved key word.
522#[must_use]
523pub fn quote_identifier(name: &str) -> String {
524    let simple = name.bytes().next().is_some_and(|c| c.is_ascii_lowercase() || c == b'_')
525        && name.bytes().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'_')
526        && rupg_sql::keyword(name).is_none_or(|k| k.category == Category::Unreserved);
527    if simple { name.to_owned() } else { format!("\"{}\"", name.replace('"', "\"\"")) }
528}
529
530fn quote_ident(_: &Call<'_>, args: &[Value]) -> Result<Value> {
531    Ok(Value::Text(quote_identifier(arg(args, 0)?)))
532}
533
534/// `quote_literal_internal`: the string in single quotes, with `E` before it when it has a backslash.
535pub(crate) fn quote_literal_text(s: &str) -> String {
536    let mut out = String::with_capacity(s.len() + 3);
537    if s.contains('\\') {
538        out.push('E');
539    }
540    out.push('\'');
541    for c in s.chars() {
542        if c == '\'' || c == '\\' {
543            out.push(c);
544        }
545        out.push(c);
546    }
547    out.push('\'');
548    out
549}
550
551fn quote_literal(_: &Call<'_>, args: &[Value]) -> Result<Value> {
552    Ok(Value::Text(quote_literal_text(arg(args, 0)?)))
553}
554
555/// `quote_nullable`: `NULL` for a null value, else `quote_literal`. It is not strict.
556fn quote_nullable(_: &Call<'_>, args: &[Value]) -> Result<Value> {
557    match args.first() {
558        Some(Value::Null) => Ok(Value::text("NULL")),
559        _ => Ok(Value::Text(quote_literal_text(arg(args, 0)?))),
560    }
561}
562
563/// `quote_literal(anyelement)` and `quote_nullable(anyelement)`, which are functions in SQL that cast the value to `text`.
564pub(crate) fn quote_any(call: &Call<'_>, args: &[Value]) -> Result<Value> {
565    match args.first() {
566        Some(Value::Null) => Ok(Value::text("NULL")),
567        Some(value) => {
568            let ty = call.args.first().copied().ok_or_else(bad_value)?;
569            Ok(Value::Text(quote_literal_text(&crate::to_text(ty, value, call.session)?)))
570        }
571        None => Err(bad_value()),
572    }
573}
574
575/// The value of the first `n` bytes of `s` as hexadecimal digits, or `None` when they are not all hexadecimal digits.
576fn hex_digits(s: &[u8], n: usize) -> Option<u32> {
577    let digits = s.get(..n)?;
578    digits.iter().try_fold(0u32, |acc, &d| Some(acc << 4 | char::from(d).to_digit(16)?))
579}
580
581fn bad_surrogate_pair() -> Error {
582    Error::new(SqlState::SYNTAX_ERROR, "invalid Unicode surrogate pair")
583}
584
585/// `unistr`: the string with each escape `\XXXX`, `\+XXXXXX`, `\uXXXX` or `\UXXXXXXXX` replaced by its character, and `\\` replaced by `\`. Two escapes can be a surrogate pair of UTF-16.
586fn unistr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
587    let mut s = arg(args, 0)?.as_bytes();
588    let mut out = Vec::with_capacity(s.len());
589    let mut first: Option<u32> = None;
590    while let Some(&c) = s.first() {
591        if c != b'\\' {
592            if first.is_some() {
593                return Err(bad_surrogate_pair());
594            }
595            out.push(c);
596            s = &s[1..];
597            continue;
598        }
599        if s.get(1) == Some(&b'\\') {
600            if first.is_some() {
601                return Err(bad_surrogate_pair());
602            }
603            out.push(b'\\');
604            s = &s[2..];
605            continue;
606        }
607        let (code, used) = if let Some(code) = hex_digits(&s[1..], 4) {
608            (code, 5)
609        } else if let Some(code) =
610            (s.get(1) == Some(&b'u')).then(|| hex_digits(&s[2..], 4)).flatten()
611        {
612            (code, 6)
613        } else if let Some(code) =
614            (s.get(1) == Some(&b'+')).then(|| hex_digits(&s[2..], 6)).flatten()
615        {
616            (code, 8)
617        } else if let Some(code) =
618            (s.get(1) == Some(&b'U')).then(|| hex_digits(&s[2..], 8)).flatten()
619        {
620            (code, 10)
621        } else {
622            return Err(Error::new(SqlState::SYNTAX_ERROR, "invalid Unicode escape")
623                .with_hint("Unicode escapes must be \\XXXX, \\+XXXXXX, \\uXXXX, or \\UXXXXXXXX."));
624        };
625        if code == 0 || code > 0x10_FFFF {
626            return Err(Error::new(
627                SqlState::INVALID_PARAMETER_VALUE,
628                format!("invalid Unicode code point: {code:04X}"),
629            ));
630        }
631        let second = (0xDC00..=0xDFFF).contains(&code);
632        let code = match first.take() {
633            Some(high) if second => 0x1_0000 + ((high - 0xD800) << 10) + (code - 0xDC00),
634            Some(_) => return Err(bad_surrogate_pair()),
635            None if second => return Err(bad_surrogate_pair()),
636            None => code,
637        };
638        if (0xD800..=0xDBFF).contains(&code) {
639            first = Some(code);
640        } else {
641            let c = char::from_u32(code).ok_or_else(bad_value)?;
642            out.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes());
643        }
644        s = &s[used..];
645    }
646    if first.is_some() {
647        return Err(bad_surrogate_pair());
648    }
649    String::from_utf8(out).map(Value::Text).map_err(|_| bad_value())
650}
651
652/// `md5_text`: the MD5 hash of the bytes in lower case hexadecimal.
653fn md5_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
654    let digest = Md5::digest(arg(args, 0)?.as_bytes());
655    Ok(Value::Text(digest.iter().map(|b| format!("{b:02x}")).collect()))
656}
657
658/// The arguments of `concat` and `concat_ws` with their types: the elements of the array of a `VARIADIC` call, or the arguments. `None` when the array is null.
659pub(crate) fn variadic_values<'a>(
660    call: &Call<'_>,
661    args: &'a [Value],
662    skip: usize,
663) -> Result<Option<Vec<(u32, &'a Value)>>> {
664    if !call.variadic {
665        let types = call.args.iter().skip(skip).copied();
666        return Ok(Some(types.zip(args.iter().skip(skip)).collect()));
667    }
668    match args.get(skip) {
669        Some(Value::Null) => Ok(None),
670        Some(Value::Array(array)) => {
671            let ty = call.args.get(skip).copied().ok_or_else(bad_value)?;
672            let element = rupg_pgcatalog::builtin::type_by_oid(base_type(ty))
673                .map_or(oid::TEXT, |row| row.elem);
674            Ok(Some(
675                array
676                    .values
677                    .iter()
678                    .map(|v| (element, v.as_ref().unwrap_or(&Value::Null)))
679                    .collect(),
680            ))
681        }
682        _ => Err(bad_value()),
683    }
684}
685
686/// The text of the values that are not null, with a separator between them.
687fn join(call: &Call<'_>, values: &[(u32, &Value)], separator: &str) -> Result<String> {
688    let mut out = Vec::new();
689    let mut first = true;
690    for (ty, value) in values {
691        if value.is_null() {
692            continue;
693        }
694        if !first {
695            out.extend_from_slice(separator.as_bytes());
696        }
697        first = false;
698        output(*ty, value, call.session, &mut out)?;
699    }
700    String::from_utf8(out)
701        .map_err(|_| Error::internal("an output function gave bytes that are not UTF-8"))
702}
703
704/// `text_concat`: the text of the arguments that are not null. It is not strict.
705fn concat(call: &Call<'_>, args: &[Value]) -> Result<Value> {
706    let Some(values) = variadic_values(call, args, 0)? else { return Ok(Value::Null) };
707    Ok(Value::Text(join(call, &values, "")?))
708}
709
710/// `text_concat_ws`: the text of the arguments that are not null with a separator, or null for a null separator. It is not strict.
711fn concat_ws(call: &Call<'_>, args: &[Value]) -> Result<Value> {
712    let separator = match args.first() {
713        Some(Value::Null) => return Ok(Value::Null),
714        Some(Value::Text(s)) => s.as_str(),
715        _ => return Err(bad_value()),
716    };
717    let Some(values) = variadic_values(call, args, 1)? else { return Ok(Value::Null) };
718    Ok(Value::Text(join(call, &values, separator)?))
719}
720
721/// The error of a format string of `format` that ends in a conversion specifier.
722fn unterminated_format() -> Error {
723    Error::new(SqlState::INVALID_PARAMETER_VALUE, "unterminated format() type specifier")
724        .with_hint("For a single \"%\" use \"%%\".")
725}
726
727/// The error of a number in a format string of `format` that is too large.
728fn format_out_of_range() -> Error {
729    Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "number is out of range")
730}
731
732/// `ADVANCE_PARSE_POINTER`: the next byte of the format string, or an error when the string ends.
733fn format_advance(bytes: &[u8], at: &mut usize) -> Result<()> {
734    *at += 1;
735    if *at >= bytes.len() {
736        return Err(unterminated_format());
737    }
738    Ok(())
739}
740
741/// `text_format_parse_digits`: the number of the decimal digits at `at`, or `None` when there is no digit.
742fn format_digits(bytes: &[u8], at: &mut usize) -> Result<Option<i32>> {
743    let mut found = None;
744    while bytes[*at].is_ascii_digit() {
745        let digit = i32::from(bytes[*at] - b'0');
746        let value = found.unwrap_or(0i32);
747        let value = value.checked_mul(10).and_then(|v| v.checked_add(digit));
748        found = Some(value.ok_or_else(format_out_of_range)?);
749        format_advance(bytes, at)?;
750    }
751    Ok(found)
752}
753
754/// The parts of a conversion specifier of `format` before its type: `[argpos$][-][width]`, `[argpos$][-]*` or `[argpos$][-]*widthpos$`.
755#[derive(Debug, Default)]
756struct FormatSpec {
757    /// The number of the argument to show, or `None` for the next argument.
758    argpos: Option<usize>,
759    /// The number of the argument of the width, 0 for the next argument, or `None` when no argument gives the width.
760    widthpos: Option<usize>,
761    /// True for the flag `-`, which aligns the value to the left.
762    minus: bool,
763    /// The width that the specifier gives, or 0.
764    width: i32,
765}
766
767/// The number of an argument in a format string. The arguments are numbered from 1.
768fn format_position(n: i32) -> Result<usize> {
769    if n == 0 {
770        return Err(Error::new(
771            SqlState::INVALID_PARAMETER_VALUE,
772            "format specifies argument 0, but arguments are numbered from 1",
773        ));
774    }
775    usize::try_from(n).map_err(|_| format_out_of_range())
776}
777
778/// `text_format_parse_format`: the parts of a conversion specifier after the `%`. At the end, `at` is at the type of the specifier.
779fn format_spec(bytes: &[u8], at: &mut usize) -> Result<FormatSpec> {
780    let mut spec = FormatSpec::default();
781    if let Some(n) = format_digits(bytes, at)? {
782        if bytes[*at] != b'$' {
783            spec.width = n;
784            return Ok(spec);
785        }
786        spec.argpos = Some(format_position(n)?);
787        format_advance(bytes, at)?;
788    }
789    while bytes[*at] == b'-' {
790        spec.minus = true;
791        format_advance(bytes, at)?;
792    }
793    if bytes[*at] == b'*' {
794        format_advance(bytes, at)?;
795        match format_digits(bytes, at)? {
796            Some(n) => {
797                if bytes[*at] != b'$' {
798                    return Err(Error::new(
799                        SqlState::INVALID_PARAMETER_VALUE,
800                        "width argument position must be ended by \"$\"",
801                    ));
802                }
803                spec.widthpos = Some(format_position(n)?);
804                format_advance(bytes, at)?;
805            }
806            None => spec.widthpos = Some(0),
807        }
808    } else if let Some(n) = format_digits(bytes, at)? {
809        spec.width = n;
810    }
811    Ok(spec)
812}
813
814/// `text_format_append_string`: the string with spaces before it, or after it when `left` is true, up to `width` characters. A negative width aligns to the left.
815fn format_append(out: &mut String, s: &str, left: bool, width: i32) -> Result<()> {
816    let (left, width) = if width < 0 {
817        (true, width.checked_neg().ok_or_else(format_out_of_range)?)
818    } else {
819        (left, width)
820    };
821    let pad = usize::try_from(width).unwrap_or(0).saturating_sub(s.chars().count());
822    if !left {
823        out.extend(std::iter::repeat_n(' ', pad));
824    }
825    out.push_str(s);
826    if left {
827        out.extend(std::iter::repeat_n(' ', pad));
828    }
829    Ok(())
830}
831
832/// The text of the output function of the type, as `OutputFunctionCall` gives it. A `boolean` gives `t` or `f`, and not the `true` or `false` of a cast to `text`.
833fn output_text(ty: u32, value: &Value, session: &dyn crate::Session) -> Result<String> {
834    let mut out = Vec::new();
835    output(ty, value, session, &mut out)?;
836    String::from_utf8(out)
837        .map_err(|_| Error::internal("an output function gave bytes that are not UTF-8"))
838}
839
840/// The argument of `format` with this number, from 1, with its type.
841fn format_arg<'a>(values: &[(u32, &'a Value)], arg: usize) -> Result<(u32, &'a Value)> {
842    values.get(arg.wrapping_sub(1)).copied().ok_or_else(|| {
843        Error::new(SqlState::INVALID_PARAMETER_VALUE, "too few arguments for format()")
844    })
845}
846
847/// `text_format` and `text_format_nv`: `format(text)` and `format(text, VARIADIC "any")`. Each `%s`, `%I` or `%L` of the format string shows the text of an argument, as an identifier with `%I` and as a literal with `%L`. `%%` shows `%`. A null format string gives null, and the null array of a `VARIADIC` call has no elements. It is not strict.
848fn format(call: &Call<'_>, args: &[Value]) -> Result<Value> {
849    let fmt = match args.first() {
850        Some(Value::Null) => return Ok(Value::Null),
851        Some(Value::Text(s)) => s.as_str(),
852        _ => return Err(bad_value()),
853    };
854    let values = variadic_values(call, args, 1)?.unwrap_or_default();
855    let bytes = fmt.as_bytes();
856    let mut out = String::new();
857    let mut arg = 1;
858    let mut start = 0;
859    let mut at = 0;
860    while at < bytes.len() {
861        if bytes[at] != b'%' {
862            at += 1;
863            continue;
864        }
865        out.push_str(&fmt[start..at]);
866        format_advance(bytes, &mut at)?;
867        if bytes[at] == b'%' {
868            out.push('%');
869            at += 1;
870            start = at;
871            continue;
872        }
873        let spec = format_spec(bytes, &mut at)?;
874        let conversion = bytes[at];
875        if !matches!(conversion, b's' | b'I' | b'L') {
876            let found = fmt[at..].chars().next().unwrap_or_default();
877            return Err(Error::new(
878                SqlState::INVALID_PARAMETER_VALUE,
879                format!("unrecognized format() type specifier \"{found}\""),
880            )
881            .with_hint("For a single \"%\" use \"%%\"."));
882        }
883        let mut width = spec.width;
884        if let Some(widthpos) = spec.widthpos {
885            if widthpos > 0 {
886                arg = widthpos;
887            }
888            let (ty, value) = format_arg(&values, arg)?;
889            arg += 1;
890            width = match value {
891                Value::Null => 0,
892                Value::Int4(n) => *n,
893                Value::Int2(n) => i32::from(*n),
894                _ => {
895                    let text = output_text(ty, value, call.session)?;
896                    match crate::input(oid::INT4, &text, -1, call.session)? {
897                        Value::Int4(n) => n,
898                        _ => return Err(bad_value()),
899                    }
900                }
901            };
902        }
903        if let Some(argpos) = spec.argpos {
904            arg = argpos;
905        }
906        let (ty, value) = format_arg(&values, arg)?;
907        arg += 1;
908        let text = match (value, conversion) {
909            (Value::Null, b's') => String::new(),
910            (Value::Null, b'L') => "NULL".to_owned(),
911            (Value::Null, _) => {
912                return Err(Error::new(
913                    SqlState::NULL_VALUE_NOT_ALLOWED,
914                    "null values cannot be formatted as an SQL identifier",
915                ));
916            }
917            (_, b'I') => quote_identifier(&output_text(ty, value, call.session)?),
918            (_, b'L') => quote_literal_text(&output_text(ty, value, call.session)?),
919            _ => output_text(ty, value, call.session)?,
920        };
921        format_append(&mut out, &text, spec.minus, width)?;
922        at += 1;
923        start = at;
924    }
925    out.push_str(&fmt[start..]);
926    Ok(Value::Text(out))
927}
928
929/// `textanycat` and `anytextcat`: `||` with one argument of a type that is not a string, which is a function in SQL that casts that argument to `text`.
930pub(crate) fn any_concat(call: &Call<'_>, args: &[Value]) -> Result<Value> {
931    let mut out = String::new();
932    for (ty, value) in call.args.iter().zip(args) {
933        out.push_str(&crate::to_text(*ty, value, call.session)?);
934    }
935    Ok(Value::Text(out))
936}
937
938/// `bit_length(text)` and `bit_length(bytea)`, which are functions in SQL.
939pub(crate) fn bit_length(_: &Call<'_>, args: &[Value]) -> Result<Value> {
940    let bytes = match args.first() {
941        Some(Value::Text(s)) => s.len(),
942        Some(Value::Bytea(b)) => b.len(),
943        _ => return Err(bad_value()),
944    };
945    Ok(count(bytes.saturating_mul(8)))
946}
947
948/// `nameconcatoid`: the name, then `_` and the OID. The views of `information_schema` use it for names that must be unique. When the result is longer than `NAMEDATALEN - 1` bytes, the name loses characters at its end, and the OID stays whole.
949fn nameconcatoid(_: &Call<'_>, args: &[Value]) -> Result<Value> {
950    let Some(Value::Oid(oid)) = args.get(1) else {
951        return Err(bad_value());
952    };
953    let suffix = format!("_{oid}");
954    let name = clip(arg(args, 0)?, NAMEDATALEN - 1 - suffix.len());
955    Ok(Value::Text(format!("{name}{suffix}")))
956}
957
958/// The kernel of a string function by its `prosrc`.
959pub(crate) fn by_src(src: &str) -> Option<Kernel> {
960    Some(match src {
961        "textcat" => textcat,
962        "textlen" => textlen,
963        "bpcharlen" => bpcharlen,
964        "textoctetlen" | "bpcharoctetlen" => octetlen,
965        "byteaoctetlen" => byteaoctetlen,
966        "lower" | "casefold" => lower,
967        "upper" => upper,
968        "initcap" => initcap,
969        "ltrim1" | "ltrim" => ltrim,
970        "rtrim1" | "rtrim" => rtrim,
971        "btrim1" | "btrim" => btrim,
972        "text_substr" | "text_substr_no_len" => substr,
973        "textpos" => textpos,
974        "replace_text" => replace,
975        "split_part" => split_part,
976        "to_hex32" | "to_hex64" => to_hex,
977        "to_oct32" | "to_oct64" => to_oct,
978        "to_bin32" | "to_bin64" => to_bin,
979        "textoverlay" | "textoverlay_no_len" => overlay,
980        "translate" => translate,
981        "unistr" => unistr,
982        "repeat" => repeat,
983        "text_reverse" => reverse,
984        "text_left" => left,
985        "text_right" => right,
986        "lpad" => lpad,
987        "rpad" => rpad,
988        "ascii" => ascii,
989        "chr" => chr,
990        "textlike" | "namelike" => textlike,
991        "textnlike" | "namenlike" => textnlike,
992        "texticlike" | "nameiclike" => texticlike,
993        "texticnlike" | "nameicnlike" => texticnlike,
994        "like_escape" => like_escape,
995        "text_starts_with" => starts_with,
996        "quote_ident" => quote_ident,
997        "quote_literal" => quote_literal,
998        "quote_nullable" => quote_nullable,
999        "md5_text" => md5_text,
1000        "text_concat" => concat,
1001        "text_concat_ws" => concat_ws,
1002        "text_format" | "text_format_nv" => format,
1003        "nameconcatoid" => nameconcatoid,
1004        _ => return None,
1005    })
1006}