1use 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
12const MAX_ALLOC_SIZE: i64 = 0x3fff_ffff;
14const VARHDRSZ: i64 = 4;
16const MAX_CHAR_BYTES: i64 = 4;
18
19fn arg(args: &[Value], at: usize) -> Result<&str> {
21 args.get(at).and_then(Value::as_str).ok_or_else(bad_value)
22}
23
24fn 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
37fn prefix(s: &str, chars: usize) -> &str {
39 s.char_indices().nth(chars).map_or(s, |(at, _)| &s[..at])
40}
41
42fn 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
51fn count(n: usize) -> Value {
53 Value::Int4(i32::try_from(n).unwrap_or(i32::MAX))
54}
55
56fn textcat(_: &Call<'_>, args: &[Value]) -> Result<Value> {
58 Ok(Value::Text(format!("{}{}", arg(args, 0)?, arg(args, 1)?)))
59}
60
61fn textlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
63 Ok(count(char_count(arg(args, 0)?)))
64}
65
66fn bpcharlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
68 Ok(count(char_count(arg(args, 0)?.trim_end_matches(' '))))
69}
70
71fn octetlen(_: &Call<'_>, args: &[Value]) -> Result<Value> {
73 Ok(count(arg(args, 0)?.len()))
74}
75
76fn 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
82fn lower(_: &Call<'_>, args: &[Value]) -> Result<Value> {
84 Ok(Value::Text(arg(args, 0)?.to_ascii_lowercase()))
85}
86
87fn upper(_: &Call<'_>, args: &[Value]) -> Result<Value> {
89 Ok(Value::Text(arg(args, 0)?.to_ascii_uppercase()))
90}
91
92fn 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#[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
137fn 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
159fn 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
165fn 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
183fn 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
201fn 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
210fn 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#[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
240fn to_hex(_: &Call<'_>, args: &[Value]) -> Result<Value> {
242 Ok(Value::Text(format!("{:x}", unsigned_arg(args)?)))
243}
244
245fn to_oct(_: &Call<'_>, args: &[Value]) -> Result<Value> {
247 Ok(Value::Text(format!("{:o}", unsigned_arg(args)?)))
248}
249
250fn to_bin(_: &Call<'_>, args: &[Value]) -> Result<Value> {
252 Ok(Value::Text(format!("{:b}", unsigned_arg(args)?)))
253}
254
255fn 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
266fn reverse(_: &Call<'_>, args: &[Value]) -> Result<Value> {
268 Ok(Value::Text(arg(args, 0)?.chars().rev().collect()))
269}
270
271fn 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
282fn 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
293fn 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
316pub(crate) fn lpad_space(_: &Call<'_>, args: &[Value]) -> Result<Value> {
318 pad(args, " ", true)
319}
320
321pub(crate) fn rpad_space(_: &Call<'_>, args: &[Value]) -> Result<Value> {
323 pad(args, " ", false)
324}
325
326fn 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#[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#[derive(Clone, Copy, PartialEq, Eq)]
363enum Like {
364 True,
365 False,
366 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
374fn char_len(first: u8) -> usize {
376 match first {
377 0xf0.. => 4,
378 0xe0.. => 3,
379 0xc0.. => 2,
380 _ => 1,
381 }
382}
383
384fn first_len(t: &[u8], utf8: bool) -> usize {
386 if utf8 { char_len(t[0]).min(t.len()) } else { 1 }
387}
388
389fn 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
453pub(crate) fn like_bytes(value: &[u8], pattern: &[u8]) -> Result<bool> {
455 Ok(match_text(value, pattern, false)? == Like::True)
456}
457
458pub(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
471fn 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
484fn 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
516fn starts_with(_: &Call<'_>, args: &[Value]) -> Result<Value> {
518 Ok(Value::Bool(arg(args, 0)?.starts_with(arg(args, 1)?)))
519}
520
521#[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
534pub(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
555fn 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
563pub(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
575fn 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
585fn 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
652fn 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
658pub(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
686fn 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
704fn 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
710fn 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
721fn unterminated_format() -> Error {
723 Error::new(SqlState::INVALID_PARAMETER_VALUE, "unterminated format() type specifier")
724 .with_hint("For a single \"%\" use \"%%\".")
725}
726
727fn format_out_of_range() -> Error {
729 Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "number is out of range")
730}
731
732fn 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
741fn 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#[derive(Debug, Default)]
756struct FormatSpec {
757 argpos: Option<usize>,
759 widthpos: Option<usize>,
761 minus: bool,
763 width: i32,
765}
766
767fn 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
778fn 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
814fn 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
832fn 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
840fn 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
847fn 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
929pub(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
938pub(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
948fn 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
958pub(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}