uqa_sql/ast/types/
display.rs1use std::cell::RefCell;
10use std::sync::Arc;
11use uqa_core::{
12 memory::{Produced, ProductionControl},
13 ValueRetentionError,
14};
15
16use super::ColumnType;
17
18thread_local! {
19 static SEARCH_PATH: RefCell<Option<Arc<[String]>>> = const { RefCell::new(None) };
20}
21
22pub struct TypeDisplayScope {
24 previous: Option<Arc<[String]>>,
25}
26
27impl TypeDisplayScope {
28 #[must_use]
29 pub fn enter(search_path: &[String]) -> Self {
30 let previous = SEARCH_PATH.with(|current| current.replace(Some(search_path.into())));
31 Self { previous }
32 }
33
34 pub fn refresh(&self, search_path: &[String]) {
36 SEARCH_PATH.with(|current| {
37 current.replace(Some(search_path.into()));
38 });
39 }
40}
41
42impl Drop for TypeDisplayScope {
43 fn drop(&mut self) {
44 SEARCH_PATH.with(|current| {
45 current.replace(self.previous.take());
46 });
47 }
48}
49
50fn schema_visible(schema: &str) -> bool {
51 schema == "pg_catalog"
52 || SEARCH_PATH.with(|current| {
53 current.borrow().as_ref().map_or_else(
54 || schema == "public",
55 |path| path.iter().any(|entry| entry == schema),
56 )
57 })
58}
59
60pub(super) fn visible_type_name(schema: &str, name: &str) -> String {
62 visible_type_name_with_control(schema, name, &ProductionControl::uncontrolled())
63 .expect("ordinary visible type name")
64 .into_uncontrolled()
65 .expect("ordinary type name has no reservation")
66}
67
68fn visible_type_name_with_control(
69 schema: &str,
70 name: &str,
71 control: &ProductionControl<'_>,
72) -> Result<Produced<String>, ValueRetentionError> {
73 let local = crate::expr::quote_ident_with_control(name, control)?;
74 if schema_visible(schema) {
75 Ok(local)
76 } else {
77 let schema = crate::expr::quote_ident_with_control(schema, control)?;
78 control.format(format_args!("{}.{}", schema.as_str(), local.as_str()))
79 }
80}
81
82impl ColumnType {
83 #[must_use]
85 pub fn display_name(&self) -> String {
86 self.display_name_with_control(&ProductionControl::uncontrolled())
87 .expect("ordinary diagnostic type name")
88 .into_uncontrolled()
89 .expect("ordinary type name has no reservation")
90 }
91
92 pub(crate) fn display_name_with_control(
93 &self,
94 control: &ProductionControl<'_>,
95 ) -> Result<Produced<String>, ValueRetentionError> {
96 control.check()?;
97 match self {
98 ColumnType::Enum(reference) => {
99 visible_type_name_with_control(&reference.schema, &reference.name, control)
100 }
101 ColumnType::Composite(reference) => {
102 visible_type_name_with_control(&reference.schema, &reference.name, control)
103 }
104 ColumnType::Domain { schema, name, .. } => {
105 visible_type_name_with_control(schema, name, control)
106 }
107 ColumnType::Array(element) => {
109 let mut leaf = element.as_ref();
110 while let ColumnType::Array(inner) = leaf {
111 control.check()?;
112 leaf = inner;
113 }
114 if matches!(
115 leaf,
116 ColumnType::Enum(_) | ColumnType::Composite(_) | ColumnType::Domain { .. }
117 ) {
118 let leaf = leaf.display_name_with_control(control)?;
119 control.format(format_args!("{}[]", leaf.as_str()))
120 } else {
121 self.regtype_name_with_control(control)
122 }
123 }
124 other => other.regtype_name_with_control(control),
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::TypeDisplayScope;
132 use crate::ast::{ColumnType, EnumTypeReference};
133
134 #[test]
135 fn hidden_user_types_are_qualified_in_diagnostics() {
136 let mood = ColumnType::Enum(EnumTypeReference {
137 schema: "hidden".into(),
138 name: "hmood".into(),
139 oid: 1,
140 array_oid: 2,
141 });
142 assert_eq!(mood.display_name(), "hidden.hmood");
144 let scope = TypeDisplayScope::enter(&["public".into()]);
145 assert_eq!(mood.display_name(), "hidden.hmood");
146 assert_eq!(
147 ColumnType::Array(Box::new(mood.clone())).display_name(),
148 "hidden.hmood[]"
149 );
150 scope.refresh(&["hidden".into(), "public".into()]);
151 assert_eq!(mood.display_name(), "hmood");
152 drop(scope);
153 assert_eq!(mood.display_name(), "hidden.hmood");
154 assert_eq!(ColumnType::Integer.display_name(), "integer");
155 }
156}