use std::borrow::Cow;
use rupg_common::SqlState;
use crate::error::TypeError;
use crate::generated::oids;
use crate::number::{is_space, oid_in};
use crate::scalar::name_in;
use crate::types::Oid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegKind {
Proc,
Procedure,
Oper,
Operator,
Class,
Type,
Role,
Namespace,
Collation,
Config,
Dictionary,
Database,
}
impl RegKind {
pub const ALL: [RegKind; 12] = [
RegKind::Proc,
RegKind::Procedure,
RegKind::Oper,
RegKind::Operator,
RegKind::Class,
RegKind::Type,
RegKind::Config,
RegKind::Dictionary,
RegKind::Namespace,
RegKind::Role,
RegKind::Collation,
RegKind::Database,
];
pub fn from_oid(oid: Oid) -> Option<RegKind> {
RegKind::ALL.into_iter().find(|kind| kind.oid() == oid)
}
pub fn oid(self) -> Oid {
match self {
RegKind::Proc => oids::REGPROC,
RegKind::Procedure => oids::REGPROCEDURE,
RegKind::Oper => oids::REGOPER,
RegKind::Operator => oids::REGOPERATOR,
RegKind::Class => oids::REGCLASS,
RegKind::Type => oids::REGTYPE,
RegKind::Role => oids::REGROLE,
RegKind::Namespace => oids::REGNAMESPACE,
RegKind::Collation => oids::REGCOLLATION,
RegKind::Config => oids::REGCONFIG,
RegKind::Dictionary => oids::REGDICTIONARY,
RegKind::Database => oids::REGDATABASE,
}
}
pub fn type_name(self) -> &'static str {
match self {
RegKind::Proc => "regproc",
RegKind::Procedure => "regprocedure",
RegKind::Oper => "regoper",
RegKind::Operator => "regoperator",
RegKind::Class => "regclass",
RegKind::Type => "regtype",
RegKind::Role => "regrole",
RegKind::Namespace => "regnamespace",
RegKind::Collation => "regcollation",
RegKind::Config => "regconfig",
RegKind::Dictionary => "regdictionary",
RegKind::Database => "regdatabase",
}
}
pub fn zero_text(self) -> &'static str {
match self {
RegKind::Oper | RegKind::Operator => "0",
_ => "-",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegInput<'a> {
Oid(u32),
Name(&'a str),
}
pub fn reg_in(kind: RegKind, s: &str) -> Result<RegInput<'_>, TypeError> {
if s == "-" && kind.zero_text() == "-" {
return Ok(RegInput::Oid(0));
}
if !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) {
return oid_in(s).map(RegInput::Oid);
}
Ok(RegInput::Name(s))
}
pub fn reg_out_oid(kind: RegKind, oid: u32, out: &mut Vec<u8>) {
if oid == 0 {
out.extend_from_slice(kind.zero_text().as_bytes());
} else {
crate::number::oid_out(oid, out);
}
}
fn identifier(token: &str, quoted: bool) -> Cow<'_, str> {
let name: Cow<'_, str> = if quoted {
if token.contains("\"\"") {
Cow::Owned(token.replace("\"\"", "\""))
} else {
Cow::Borrowed(token)
}
} else if token.bytes().any(|b| b.is_ascii_uppercase()) {
Cow::Owned(token.to_ascii_lowercase())
} else {
Cow::Borrowed(token)
};
match name {
Cow::Borrowed(s) => Cow::Borrowed(name_in(s)),
Cow::Owned(mut s) => {
s.truncate(name_in(&s).len());
Cow::Owned(s)
}
}
}
pub fn split_identifier_string(s: &str, separator: u8) -> Option<Vec<Cow<'_, str>>> {
let b = s.as_bytes();
let skip_space = |mut at: usize| {
while at < b.len() && is_space(b[at]) {
at += 1;
}
at
};
let mut names = Vec::new();
let mut at = skip_space(0);
if at == b.len() {
return Some(names);
}
loop {
if b[at] == b'"' {
let start = at + 1;
let mut end = start;
loop {
end += b[end..].iter().position(|&c| c == b'"')?;
if b.get(end + 1) != Some(&b'"') {
break;
}
end += 2;
}
names.push(identifier(&s[start..end], true));
at = end + 1;
} else {
let start = at;
while at < b.len() && b[at] != separator && !is_space(b[at]) {
at += 1;
}
if at == start {
return None;
}
names.push(identifier(&s[start..at], false));
}
at = skip_space(at);
if at == b.len() {
return Some(names);
}
if b[at] != separator {
return None;
}
at = skip_space(at + 1);
if at == b.len() {
return None;
}
}
}
pub fn qualified_name_list(s: &str) -> Result<Vec<Cow<'_, str>>, TypeError> {
match split_identifier_string(s, b'.') {
Some(names) if !names.is_empty() => Ok(names),
_ => Err(TypeError::new(SqlState::INVALID_NAME, "invalid name syntax".to_owned())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_input_takes_a_number_or_a_dash() {
assert_eq!(reg_in(RegKind::Class, "-"), Ok(RegInput::Oid(0)));
assert_eq!(reg_in(RegKind::Oper, "-"), Ok(RegInput::Name("-")));
assert_eq!(reg_in(RegKind::Type, "010"), Ok(RegInput::Oid(8)));
assert_eq!(reg_in(RegKind::Type, "4294967295"), Ok(RegInput::Oid(u32::MAX)));
assert_eq!(reg_in(RegKind::Type, " 1"), Ok(RegInput::Name(" 1")));
assert_eq!(reg_in(RegKind::Type, ""), Ok(RegInput::Name("")));
let error = reg_in(RegKind::Class, "09").unwrap_err();
assert_eq!(error.message, "invalid input syntax for type oid: \"09\"");
let error = reg_in(RegKind::Class, "4294967296").unwrap_err();
assert_eq!(error.message, "value \"4294967296\" is out of range for type oid");
}
#[test]
fn the_output_of_zero_depends_on_the_kind() {
let text = |kind, oid| {
let mut out = Vec::new();
reg_out_oid(kind, oid, &mut out);
String::from_utf8(out).unwrap()
};
assert_eq!(text(RegKind::Class, 0), "-");
assert_eq!(text(RegKind::Operator, 0), "0");
assert_eq!(text(RegKind::Type, 70000), "70000");
for kind in RegKind::ALL {
assert_eq!(RegKind::from_oid(kind.oid()), Some(kind));
}
assert_eq!(RegKind::from_oid(oids::OID), None);
}
#[test]
fn name_lists_split_at_the_separator() {
let list = |s| qualified_name_list(s).map(|names| names.join("|"));
assert_eq!(list("Ab . \"C\"\"d\""), Ok("ab|C\"d".to_owned()));
assert_eq!(list("\"\""), Ok(String::new()));
assert_eq!(list("a\"b.c"), Ok("a\"b|c".to_owned()));
assert_eq!(list("ÉA"), Ok("Éa".to_owned()));
for bad in ["", " ", "a.", ".a", "a..b", "a b", "\"a", "\"a\"b", "a.\"b\"\""] {
assert_eq!(list(bad).unwrap_err().message, "invalid name syntax", "{bad:?}");
}
let long = format!("{}é", "a".repeat(62));
assert_eq!(list(&long), Ok("a".repeat(62)));
assert_eq!(split_identifier_string(" a , B ", b',').unwrap(), ["a", "b"]);
}
}