1use std::borrow::Cow;
8
9use rupg_common::SqlState;
10
11use crate::error::TypeError;
12use crate::generated::oids;
13use crate::number::{is_space, oid_in};
14use crate::scalar::name_in;
15use crate::types::Oid;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum RegKind {
20 Proc,
22 Procedure,
24 Oper,
26 Operator,
28 Class,
30 Type,
32 Role,
34 Namespace,
36 Collation,
38 Config,
40 Dictionary,
42 Database,
44}
45
46impl RegKind {
47 pub const ALL: [RegKind; 12] = [
49 RegKind::Proc,
50 RegKind::Procedure,
51 RegKind::Oper,
52 RegKind::Operator,
53 RegKind::Class,
54 RegKind::Type,
55 RegKind::Config,
56 RegKind::Dictionary,
57 RegKind::Namespace,
58 RegKind::Role,
59 RegKind::Collation,
60 RegKind::Database,
61 ];
62
63 pub fn from_oid(oid: Oid) -> Option<RegKind> {
65 RegKind::ALL.into_iter().find(|kind| kind.oid() == oid)
66 }
67
68 pub fn oid(self) -> Oid {
70 match self {
71 RegKind::Proc => oids::REGPROC,
72 RegKind::Procedure => oids::REGPROCEDURE,
73 RegKind::Oper => oids::REGOPER,
74 RegKind::Operator => oids::REGOPERATOR,
75 RegKind::Class => oids::REGCLASS,
76 RegKind::Type => oids::REGTYPE,
77 RegKind::Role => oids::REGROLE,
78 RegKind::Namespace => oids::REGNAMESPACE,
79 RegKind::Collation => oids::REGCOLLATION,
80 RegKind::Config => oids::REGCONFIG,
81 RegKind::Dictionary => oids::REGDICTIONARY,
82 RegKind::Database => oids::REGDATABASE,
83 }
84 }
85
86 pub fn type_name(self) -> &'static str {
88 match self {
89 RegKind::Proc => "regproc",
90 RegKind::Procedure => "regprocedure",
91 RegKind::Oper => "regoper",
92 RegKind::Operator => "regoperator",
93 RegKind::Class => "regclass",
94 RegKind::Type => "regtype",
95 RegKind::Role => "regrole",
96 RegKind::Namespace => "regnamespace",
97 RegKind::Collation => "regcollation",
98 RegKind::Config => "regconfig",
99 RegKind::Dictionary => "regdictionary",
100 RegKind::Database => "regdatabase",
101 }
102 }
103
104 pub fn zero_text(self) -> &'static str {
106 match self {
107 RegKind::Oper | RegKind::Operator => "0",
108 _ => "-",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum RegInput<'a> {
116 Oid(u32),
118 Name(&'a str),
120}
121
122pub fn reg_in(kind: RegKind, s: &str) -> Result<RegInput<'_>, TypeError> {
126 if s == "-" && kind.zero_text() == "-" {
127 return Ok(RegInput::Oid(0));
128 }
129 if !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) {
130 return oid_in(s).map(RegInput::Oid);
131 }
132 Ok(RegInput::Name(s))
133}
134
135pub fn reg_out_oid(kind: RegKind, oid: u32, out: &mut Vec<u8>) {
137 if oid == 0 {
138 out.extend_from_slice(kind.zero_text().as_bytes());
139 } else {
140 crate::number::oid_out(oid, out);
141 }
142}
143
144fn identifier(token: &str, quoted: bool) -> Cow<'_, str> {
146 let name: Cow<'_, str> = if quoted {
147 if token.contains("\"\"") {
148 Cow::Owned(token.replace("\"\"", "\""))
149 } else {
150 Cow::Borrowed(token)
151 }
152 } else if token.bytes().any(|b| b.is_ascii_uppercase()) {
153 Cow::Owned(token.to_ascii_lowercase())
155 } else {
156 Cow::Borrowed(token)
157 };
158 match name {
159 Cow::Borrowed(s) => Cow::Borrowed(name_in(s)),
160 Cow::Owned(mut s) => {
161 s.truncate(name_in(&s).len());
162 Cow::Owned(s)
163 }
164 }
165}
166
167pub fn split_identifier_string(s: &str, separator: u8) -> Option<Vec<Cow<'_, str>>> {
171 let b = s.as_bytes();
172 let skip_space = |mut at: usize| {
173 while at < b.len() && is_space(b[at]) {
174 at += 1;
175 }
176 at
177 };
178 let mut names = Vec::new();
179 let mut at = skip_space(0);
180 if at == b.len() {
181 return Some(names);
182 }
183 loop {
184 if b[at] == b'"' {
185 let start = at + 1;
187 let mut end = start;
188 loop {
189 end += b[end..].iter().position(|&c| c == b'"')?;
190 if b.get(end + 1) != Some(&b'"') {
191 break;
192 }
193 end += 2;
194 }
195 names.push(identifier(&s[start..end], true));
196 at = end + 1;
197 } else {
198 let start = at;
199 while at < b.len() && b[at] != separator && !is_space(b[at]) {
200 at += 1;
201 }
202 if at == start {
203 return None;
204 }
205 names.push(identifier(&s[start..at], false));
206 }
207 at = skip_space(at);
208 if at == b.len() {
209 return Some(names);
210 }
211 if b[at] != separator {
212 return None;
213 }
214 at = skip_space(at + 1);
215 if at == b.len() {
216 return None;
218 }
219 }
220}
221
222pub fn qualified_name_list(s: &str) -> Result<Vec<Cow<'_, str>>, TypeError> {
224 match split_identifier_string(s, b'.') {
225 Some(names) if !names.is_empty() => Ok(names),
226 _ => Err(TypeError::new(SqlState::INVALID_NAME, "invalid name syntax".to_owned())),
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn the_input_takes_a_number_or_a_dash() {
236 assert_eq!(reg_in(RegKind::Class, "-"), Ok(RegInput::Oid(0)));
237 assert_eq!(reg_in(RegKind::Oper, "-"), Ok(RegInput::Name("-")));
238 assert_eq!(reg_in(RegKind::Type, "010"), Ok(RegInput::Oid(8)));
239 assert_eq!(reg_in(RegKind::Type, "4294967295"), Ok(RegInput::Oid(u32::MAX)));
240 assert_eq!(reg_in(RegKind::Type, " 1"), Ok(RegInput::Name(" 1")));
241 assert_eq!(reg_in(RegKind::Type, ""), Ok(RegInput::Name("")));
242 let error = reg_in(RegKind::Class, "09").unwrap_err();
243 assert_eq!(error.message, "invalid input syntax for type oid: \"09\"");
244 let error = reg_in(RegKind::Class, "4294967296").unwrap_err();
245 assert_eq!(error.message, "value \"4294967296\" is out of range for type oid");
246 }
247
248 #[test]
249 fn the_output_of_zero_depends_on_the_kind() {
250 let text = |kind, oid| {
251 let mut out = Vec::new();
252 reg_out_oid(kind, oid, &mut out);
253 String::from_utf8(out).unwrap()
254 };
255 assert_eq!(text(RegKind::Class, 0), "-");
256 assert_eq!(text(RegKind::Operator, 0), "0");
257 assert_eq!(text(RegKind::Type, 70000), "70000");
258 for kind in RegKind::ALL {
259 assert_eq!(RegKind::from_oid(kind.oid()), Some(kind));
260 }
261 assert_eq!(RegKind::from_oid(oids::OID), None);
262 }
263
264 #[test]
265 fn name_lists_split_at_the_separator() {
266 let list = |s| qualified_name_list(s).map(|names| names.join("|"));
267 assert_eq!(list("Ab . \"C\"\"d\""), Ok("ab|C\"d".to_owned()));
268 assert_eq!(list("\"\""), Ok(String::new()));
269 assert_eq!(list("a\"b.c"), Ok("a\"b|c".to_owned()));
270 assert_eq!(list("ÉA"), Ok("Éa".to_owned()));
271 for bad in ["", " ", "a.", ".a", "a..b", "a b", "\"a", "\"a\"b", "a.\"b\"\""] {
272 assert_eq!(list(bad).unwrap_err().message, "invalid name syntax", "{bad:?}");
273 }
274 let long = format!("{}é", "a".repeat(62));
275 assert_eq!(list(&long), Ok("a".repeat(62)));
276 assert_eq!(split_identifier_string(" a , B ", b',').unwrap(), ["a", "b"]);
277 }
278}