use std::cell::RefCell;
use std::sync::Arc;
use super::ColumnType;
thread_local! {
static SEARCH_PATH: RefCell<Option<Arc<[String]>>> = const { RefCell::new(None) };
}
pub struct TypeDisplayScope {
previous: Option<Arc<[String]>>,
}
impl TypeDisplayScope {
#[must_use]
pub fn enter(search_path: &[String]) -> Self {
let previous = SEARCH_PATH.with(|current| current.replace(Some(search_path.into())));
Self { previous }
}
pub fn refresh(&self, search_path: &[String]) {
SEARCH_PATH.with(|current| {
current.replace(Some(search_path.into()));
});
}
}
impl Drop for TypeDisplayScope {
fn drop(&mut self) {
SEARCH_PATH.with(|current| {
current.replace(self.previous.take());
});
}
}
fn schema_visible(schema: &str) -> bool {
schema == "pg_catalog"
|| SEARCH_PATH.with(|current| {
current.borrow().as_ref().map_or_else(
|| schema == "public",
|path| path.iter().any(|entry| entry == schema),
)
})
}
pub(super) fn visible_type_name(schema: &str, name: &str) -> String {
let local = crate::expr::quote_ident(name);
if schema_visible(schema) {
local
} else {
format!("{}.{local}", crate::expr::quote_ident(schema))
}
}
impl ColumnType {
#[must_use]
pub fn display_name(&self) -> String {
match self {
ColumnType::Enum(reference) => visible_type_name(&reference.schema, &reference.name),
ColumnType::Composite(reference) => {
visible_type_name(&reference.schema, &reference.name)
}
ColumnType::Domain { schema, name, .. } => visible_type_name(schema, name),
ColumnType::Array(element) => {
let mut leaf = element.as_ref();
while let ColumnType::Array(inner) = leaf {
leaf = inner;
}
if matches!(
leaf,
ColumnType::Enum(_) | ColumnType::Composite(_) | ColumnType::Domain { .. }
) {
format!("{}[]", leaf.display_name())
} else {
self.regtype_name()
}
}
other => other.regtype_name(),
}
}
}
#[cfg(test)]
mod tests {
use super::TypeDisplayScope;
use crate::ast::{ColumnType, EnumTypeReference};
#[test]
fn hidden_user_types_are_qualified_in_diagnostics() {
let mood = ColumnType::Enum(EnumTypeReference {
schema: "hidden".into(),
name: "hmood".into(),
oid: 1,
array_oid: 2,
});
assert_eq!(mood.display_name(), "hidden.hmood");
let scope = TypeDisplayScope::enter(&["public".into()]);
assert_eq!(mood.display_name(), "hidden.hmood");
assert_eq!(
ColumnType::Array(Box::new(mood.clone())).display_name(),
"hidden.hmood[]"
);
scope.refresh(&["hidden".into(), "public".into()]);
assert_eq!(mood.display_name(), "hmood");
drop(scope);
assert_eq!(mood.display_name(), "hidden.hmood");
assert_eq!(ColumnType::Integer.display_name(), "integer");
}
}