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