uqa_sql/catalog/
node_tree.rs1use std::fmt;
10
11use crate::SQLError;
12
13mod read;
14pub use read::parse;
15pub mod deparse;
16pub mod expressions;
17mod values;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Node {
21 pub kind: String,
22 pub fields: Vec<(String, Field)>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum Field {
27 Null,
28 Atom(String),
29 String(String),
30 Node(Node),
31 List(Vec<Field>),
32 Datum {
34 length: usize,
35 bytes: Vec<u8>,
36 },
37}
38
39impl Node {
40 pub fn new(
41 kind: impl Into<String>,
42 fields: impl IntoIterator<Item = (&'static str, Field)>,
43 ) -> Self {
44 Self {
45 kind: kind.into(),
46 fields: fields
47 .into_iter()
48 .map(|(name, value)| (name.into(), value))
49 .collect(),
50 }
51 }
52
53 pub fn field(&self, name: &str) -> Result<&Field, SQLError> {
54 self.fields
55 .iter()
56 .find_map(|(field, value)| (field == name).then_some(value))
57 .ok_or_else(|| invalid(format!("missing {name} in {} node", self.kind)))
58 }
59
60 pub fn integer(&self, name: &str) -> Result<i64, SQLError> {
61 match self.field(name)? {
62 Field::Atom(value) => value
63 .parse()
64 .map_err(|_| invalid(format!("invalid integer field {name}"))),
65 _ => Err(invalid(format!("invalid integer field {name}"))),
66 }
67 }
68
69 pub fn boolean(&self, name: &str) -> Result<bool, SQLError> {
70 match self.field(name)? {
71 Field::Atom(value) if value == "true" => Ok(true),
72 Field::Atom(value) if value == "false" => Ok(false),
73 _ => Err(invalid(format!("invalid boolean field {name}"))),
74 }
75 }
76}
77
78impl From<i64> for Field {
79 fn from(value: i64) -> Self {
80 Self::Atom(value.to_string())
81 }
82}
83
84impl From<bool> for Field {
85 fn from(value: bool) -> Self {
86 Self::Atom(value.to_string())
87 }
88}
89
90impl From<Node> for Field {
91 fn from(value: Node) -> Self {
92 Self::Node(value)
93 }
94}
95
96impl fmt::Display for Node {
97 fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(output, "{{{}", self.kind)?;
99 for (name, value) in &self.fields {
100 write!(output, " :{name} {value}")?;
101 }
102 output.write_str("}")
103 }
104}
105
106impl fmt::Display for Field {
107 fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 Self::Null => output.write_str("<>"),
110 Self::Atom(value) => output.write_str(value),
111 Self::String(value) => write_token(output, value),
112 Self::Node(node) => node.fmt(output),
113 Self::List(values) => {
114 output.write_str("(")?;
115 for (index, value) in values.iter().enumerate() {
116 if index != 0 {
117 output.write_str(" ")?;
118 }
119 value.fmt(output)?;
120 }
121 output.write_str(")")
122 }
123 Self::Datum { length, bytes } => {
124 write!(output, "{length} [")?;
125 for byte in bytes {
126 write!(output, " {}", std::ffi::c_char::from_ne_bytes([*byte]))?;
128 }
129 output.write_str(" ]")
130 }
131 }
132 }
133}
134
135fn write_token(output: &mut fmt::Formatter<'_>, value: &str) -> fmt::Result {
136 if value.is_empty() {
137 return output.write_str("\"\"");
138 }
139 let mut bytes = value.bytes();
140 let first = bytes.next().expect("nonempty token");
141 if matches!(first, b'<' | b'"')
142 || first.is_ascii_digit()
143 || (matches!(first, b'+' | b'-')
144 && bytes
145 .next()
146 .is_some_and(|byte| byte.is_ascii_digit() || byte == b'.'))
147 {
148 output.write_str("\\")?;
149 }
150 for character in value.chars() {
151 if matches!(character, ' ' | '\n' | '\t' | '(' | ')' | '{' | '}' | '\\') {
152 output.write_str("\\")?;
153 }
154 write!(output, "{character}")?;
155 }
156 Ok(())
157}
158
159pub(super) fn invalid(message: impl Into<String>) -> SQLError {
160 SQLError::Routine {
161 sqlstate: "XX000".into(),
162 message: message.into(),
163 }
164}
165
166#[cfg(test)]
167mod tests;