use pgrx::pg_sys;
#[must_use]
pub fn quoted(name: &str) -> String {
format!("\"{}\"", name.replace('"', "\"\""))
}
#[must_use]
pub fn quote_if_needed(name: &str) -> String {
let Ok(c) = std::ffi::CString::new(name) else {
return quoted(name);
};
unsafe {
std::ffi::CStr::from_ptr(pg_sys::quote_identifier(c.as_ptr()))
.to_string_lossy()
.into_owned()
}
}
#[must_use]
pub fn split(name: &str) -> Option<Vec<String>> {
let mut parts = Vec::new();
let mut chars = name.chars().peekable();
loop {
let mut part = String::new();
if chars.peek() == Some(&'"') {
chars.next();
loop {
match chars.next()? {
'"' if chars.peek() == Some(&'"') => {
chars.next();
part.push('"');
}
'"' => break,
c => part.push(c),
}
}
if !matches!(chars.peek(), None | Some('.')) {
return None;
}
} else {
while let Some(&c) = chars.peek() {
if c == '.' {
break;
}
part.push(c);
chars.next();
}
}
if part.is_empty() {
return None;
}
parts.push(part);
if chars.next().is_none() {
return Some(parts);
}
}
}
#[must_use]
pub fn names(word: &str, quoted: bool, name: &str) -> bool {
if quoted {
word == name
} else {
word.to_lowercase() == name
}
}
#[cfg(test)]
mod tests {
use super::{names, quoted, split};
#[test]
fn quoted_doubles_quotes() {
assert_eq!(quoted("tv_post"), "\"tv_post\"");
assert_eq!(quoted("a\"b"), "\"a\"\"b\"");
}
#[test]
fn split_unquotes_and_refuses_malformed_names() {
assert_eq!(
split("app.tv_post"),
Some(vec!["app".into(), "tv_post".into()])
);
assert_eq!(
split("\"Odd.Schema\".tv_post"),
Some(vec!["Odd.Schema".into(), "tv_post".into()])
);
assert_eq!(split("\"a\"\"b\""), Some(vec!["a\"b".into()]));
for bad in ["", "app..tv_post", "\"app.tv_post", "\"app\"x.tv_post"] {
assert_eq!(split(bad), None, "{bad}");
}
}
#[test]
fn names_folds_unquoted_words() {
assert!(names("TV_POST", false, "tv_post"));
assert!(!names("TV_POST", true, "tv_post"));
assert!(names("TV_POST", true, "TV_POST"));
}
}