1use rupg_common::SqlState;
8
9use crate::error::TypeError;
10use crate::number::is_space;
11
12pub const NAME_MAX_BYTES: usize = 63;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ByteaOutput {
18 #[default]
20 Hex,
21 Escape,
23}
24
25pub fn bool_in(s: &str) -> Result<bool, TypeError> {
27 let b = s.as_bytes();
28 let start = b.iter().position(|&c| !is_space(c)).unwrap_or(b.len());
29 let end = b.iter().rposition(|&c| !is_space(c)).map_or(start, |i| i + 1);
30 let word = &b[start..end];
31 let prefix = |of: &[u8], min: usize| {
32 word.len() >= min && word.len() <= of.len() && word.eq_ignore_ascii_case(&of[..word.len()])
33 };
34 let value = match word.first() {
35 Some(b't' | b'T') if prefix(b"true", 1) => Some(true),
36 Some(b'f' | b'F') if prefix(b"false", 1) => Some(false),
37 Some(b'y' | b'Y') if prefix(b"yes", 1) => Some(true),
38 Some(b'n' | b'N') if prefix(b"no", 1) => Some(false),
39 Some(b'o' | b'O') if prefix(b"on", 2) => Some(true),
40 Some(b'o' | b'O') if prefix(b"off", 2) => Some(false),
41 Some(b'1') if word.len() == 1 => Some(true),
42 Some(b'0') if word.len() == 1 => Some(false),
43 _ => None,
44 };
45 value.ok_or_else(|| TypeError::syntax("boolean", s))
46}
47
48pub fn bool_out(v: bool, out: &mut Vec<u8>) {
50 out.push(if v { b't' } else { b'f' });
51}
52
53pub fn char_in(s: &str) -> u8 {
55 match s.as_bytes() {
56 [b'\\', a @ b'0'..=b'7', b @ b'0'..=b'7', c @ b'0'..=b'7'] => {
57 ((a - b'0') << 6).wrapping_add((b - b'0') << 3).wrapping_add(c - b'0')
58 }
59 [first, ..] => *first,
60 [] => 0,
61 }
62}
63
64pub fn char_out(v: u8, out: &mut Vec<u8>) {
66 if v >= 0x80 {
67 out.extend_from_slice(&[b'\\', b'0' + (v >> 6), b'0' + ((v >> 3) & 7), b'0' + (v & 7)]);
68 } else if v != 0 {
69 out.push(v);
70 }
71}
72
73pub fn name_in(s: &str) -> &str {
75 if s.len() <= NAME_MAX_BYTES {
76 return s;
77 }
78 let mut end = NAME_MAX_BYTES;
79 while !s.is_char_boundary(end) {
80 end -= 1;
81 }
82 &s[..end]
83}
84
85pub fn bytea_in(s: &str) -> Result<Vec<u8>, TypeError> {
87 let b = s.as_bytes();
88 if let Some(hex) = b.strip_prefix(b"\\x") {
89 let mut out = Vec::with_capacity(hex.len() / 2);
90 let digit = |i: usize| {
91 (hex[i] as char).to_digit(16).map(|d| d as u8).ok_or_else(|| {
92 let rest = &s[s.len() - (hex.len() - i)..];
94 let c = rest.chars().next().unwrap_or_default();
95 TypeError::new(
96 SqlState::INVALID_PARAMETER_VALUE,
97 format!("invalid hexadecimal digit: \"{c}\""),
98 )
99 })
100 };
101 let mut i = 0;
102 while i < hex.len() {
103 if matches!(hex[i], b' ' | b'\n' | b'\t' | b'\r') {
104 i += 1;
105 continue;
106 }
107 let high = digit(i)?;
108 if i + 1 >= hex.len() {
109 return Err(TypeError::new(
110 SqlState::INVALID_PARAMETER_VALUE,
111 "invalid hexadecimal data: odd number of digits".to_string(),
112 ));
113 }
114 out.push(high << 4 | digit(i + 1)?);
115 i += 2;
116 }
117 return Ok(out);
118 }
119
120 let mut out = Vec::with_capacity(b.len());
121 let mut i = 0;
122 while i < b.len() {
123 match b[i..] {
124 [b'\\', a @ b'0'..=b'3', c @ b'0'..=b'7', d @ b'0'..=b'7', ..] => {
125 out.push((a - b'0') << 6 | (c - b'0') << 3 | (d - b'0'));
126 i += 4;
127 }
128 [b'\\', b'\\', ..] => {
129 out.push(b'\\');
130 i += 2;
131 }
132 [b'\\', ..] => {
133 return Err(TypeError::new(
134 SqlState::INVALID_TEXT_REPRESENTATION,
135 "invalid input syntax for type bytea".to_string(),
136 ));
137 }
138 [c, ..] => {
139 out.push(c);
140 i += 1;
141 }
142 [] => break,
143 }
144 }
145 Ok(out)
146}
147
148const HEX: &[u8; 16] = b"0123456789abcdef";
149
150pub fn bytea_out(v: &[u8], style: ByteaOutput, out: &mut Vec<u8>) {
152 match style {
153 ByteaOutput::Hex => {
154 out.reserve(2 + v.len() * 2);
155 out.extend_from_slice(b"\\x");
156 for &byte in v {
157 out.extend_from_slice(&[HEX[usize::from(byte >> 4)], HEX[usize::from(byte & 15)]]);
158 }
159 }
160 ByteaOutput::Escape => {
161 for &byte in v {
162 match byte {
163 b'\\' => out.extend_from_slice(b"\\\\"),
164 0x20..=0x7e => out.push(byte),
165 _ => out.extend_from_slice(&[
166 b'\\',
167 b'0' + (byte >> 6),
168 b'0' + ((byte >> 3) & 7),
169 b'0' + (byte & 7),
170 ]),
171 }
172 }
173 }
174 }
175}
176
177pub fn uuid_in(s: &str) -> Result<[u8; 16], TypeError> {
179 let syntax = || TypeError::syntax("uuid", s);
180 let b = s.as_bytes();
181 let braces = b.first() == Some(&b'{');
182 let mut i = usize::from(braces);
183 let mut uuid = [0u8; 16];
184 for (n, byte) in uuid.iter_mut().enumerate() {
185 let pair = b.get(i..i + 2).ok_or_else(syntax)?;
186 let high = (pair[0] as char).to_digit(16).ok_or_else(syntax)?;
187 let low = (pair[1] as char).to_digit(16).ok_or_else(syntax)?;
188 *byte = (high << 4 | low) as u8;
189 i += 2;
190 if b.get(i) == Some(&b'-') && n % 2 == 1 && n < 15 {
191 i += 1;
192 }
193 }
194 if braces {
195 if b.get(i) != Some(&b'}') {
196 return Err(syntax());
197 }
198 i += 1;
199 }
200 if i != b.len() {
201 return Err(syntax());
202 }
203 Ok(uuid)
204}
205
206pub fn uuid_out(v: &[u8; 16], out: &mut Vec<u8>) {
208 for (i, &byte) in v.iter().enumerate() {
209 if matches!(i, 4 | 6 | 8 | 10) {
210 out.push(b'-');
211 }
212 out.extend_from_slice(&[HEX[usize::from(byte >> 4)], HEX[usize::from(byte & 15)]]);
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn text(f: impl Fn(&mut Vec<u8>)) -> Vec<u8> {
221 let mut out = Vec::new();
222 f(&mut out);
223 out
224 }
225
226 #[test]
227 fn a_boolean_takes_a_prefix_that_is_not_ambiguous() {
228 for input in ["t", "TRUE", " tr ", "y", "yes", "on", "ON", "1", "\ttrue\n"] {
229 assert_eq!(bool_in(input), Ok(true), "{input:?}");
230 }
231 for input in ["f", "fAlSe", "n", "no", "of", "off", "0"] {
232 assert_eq!(bool_in(input), Ok(false), "{input:?}");
233 }
234 for input in ["", " ", "o", "truex", "yess", "onn", "01", "2", "t r"] {
235 assert_eq!(
236 bool_in(input).unwrap_err().message,
237 format!("invalid input syntax for type boolean: \"{input}\""),
238 "{input:?}"
239 );
240 }
241 assert_eq!(text(|out| bool_out(true, out)), b"t");
242 }
243
244 #[test]
245 fn a_char_is_one_byte_or_an_octal_escape() {
246 assert_eq!(char_in("a"), b'a');
247 assert_eq!(char_in("abc"), b'a');
248 assert_eq!(char_in(""), 0);
249 assert_eq!(char_in("\\101"), b'A');
250 assert_eq!(char_in("\\377"), 0xff);
251 assert_eq!(char_in("\\777"), 0xff);
252 assert_eq!(char_in("\\18"), b'\\');
253 assert_eq!(char_in("é"), 0xc3);
254 assert_eq!(text(|out| char_out(0xc3, out)), b"\\303");
255 assert_eq!(text(|out| char_out(b'r', out)), b"r");
256 assert_eq!(text(|out| char_out(0, out)), b"");
257 }
258
259 #[test]
260 fn a_name_is_cut_at_a_character() {
261 let long = "a".repeat(70);
262 assert_eq!(name_in(&long).len(), 63);
263 let wide = format!("{}é", "a".repeat(62));
264 assert_eq!(name_in(&wide), "a".repeat(62));
265 assert_eq!(name_in("pg_class"), "pg_class");
266 }
267
268 #[test]
269 fn bytea_reads_and_writes_both_formats() {
270 assert_eq!(bytea_in("\\x0aFF"), Ok(vec![0x0a, 0xff]));
271 assert_eq!(bytea_in("\\x 0a\n ff "), Ok(vec![0x0a, 0xff]));
272 assert_eq!(bytea_in("\\x"), Ok(vec![]));
273 assert_eq!(bytea_in("ab\\\\c\\001\\377"), Ok(b"ab\\c\x01\xff".to_vec()));
274 assert!(bytea_in("\\X0a").is_err());
276 let odd = bytea_in("\\x0").unwrap_err();
277 assert_eq!(
278 (odd.sqlstate.as_str(), odd.message.as_str()),
279 ("22023", "invalid hexadecimal data: odd number of digits")
280 );
281 assert_eq!(bytea_in("\\x0g").unwrap_err().message, "invalid hexadecimal digit: \"g\"");
282 assert_eq!(bytea_in("\\xé0").unwrap_err().message, "invalid hexadecimal digit: \"é\"");
283 assert_eq!(bytea_in("\\x0 1").unwrap_err().message, "invalid hexadecimal digit: \" \"");
284 for input in ["\\", "a\\4", "\\400", "\\12"] {
285 let error = bytea_in(input).unwrap_err();
286 assert_eq!(
287 (error.sqlstate.as_str(), error.message.as_str()),
288 ("22P02", "invalid input syntax for type bytea"),
289 "{input:?}"
290 );
291 }
292 let value = b"a\\\x00\x7f\xff ~";
293 assert_eq!(text(|out| bytea_out(value, ByteaOutput::Hex, out)), b"\\x615c007fff207e");
294 assert_eq!(
295 text(|out| bytea_out(value, ByteaOutput::Escape, out)),
296 b"a\\\\\\000\\177\\377 ~"
297 );
298 }
299
300 #[test]
301 fn a_uuid_takes_braces_and_hyphens_after_groups_of_four() {
302 let uuid = [
303 0xa0, 0xee, 0xbc, 0x99, 0x9c, 0x0b, 0x4e, 0xf8, 0xbb, 0x6d, 0x6b, 0xb9, 0xbd, 0x38,
304 0x0a, 0x11,
305 ];
306 for input in [
307 "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
308 "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
309 "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
310 "a0eebc999c0b4ef8bb6d6bb9bd380a11",
311 "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11",
312 "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}",
313 ] {
314 assert_eq!(uuid_in(input), Ok(uuid), "{input:?}");
315 }
316 for input in [
317 "",
318 "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1",
319 "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a111",
320 "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11-",
321 "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
322 "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
323 "a0e-ebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
324 "a0eebc99--9c0b-4ef8-bb6d-6bb9bd380a11",
325 " a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
326 ] {
327 assert_eq!(
328 uuid_in(input).unwrap_err().message,
329 format!("invalid input syntax for type uuid: \"{input}\""),
330 "{input:?}"
331 );
332 }
333 assert_eq!(text(|out| uuid_out(&uuid, out)), b"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
334 }
335}