Skip to main content

uqa_sql/catalog/node_tree/
read.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Read node fields without executing an expression or consulting a catalog.
8
9use super::{invalid, Field, Node};
10use crate::SQLError;
11
12pub fn parse(text: &str) -> Result<Field, SQLError> {
13    let mut input = Reader {
14        bytes: text.as_bytes(),
15        offset: 0,
16    };
17    let value = input.value(0)?;
18    if input.peek().is_some() {
19        return Err(invalid("trailing data in node tree"));
20    }
21    Ok(value)
22}
23
24struct Reader<'a> {
25    bytes: &'a [u8],
26    offset: usize,
27}
28
29impl Reader<'_> {
30    fn peek(&mut self) -> Option<u8> {
31        while self
32            .bytes
33            .get(self.offset)
34            .is_some_and(u8::is_ascii_whitespace)
35        {
36            self.offset += 1;
37        }
38        self.bytes.get(self.offset).copied()
39    }
40
41    fn value(&mut self, depth: usize) -> Result<Field, SQLError> {
42        if depth > 256 {
43            return Err(SQLError::Routine {
44                sqlstate: "54001".into(),
45                message: "node tree nesting is too deep".into(),
46            });
47        }
48        match self.peek() {
49            Some(b'{') => self.node(depth + 1).map(Field::Node),
50            Some(b'(') => self.list(depth + 1).map(Field::List),
51            Some(b')' | b'}' | b'[' | b']') | None => Err(invalid("unexpected end of node field")),
52            _ => {
53                let (token, escaped) = self.token()?;
54                if escaped {
55                    return Ok(Field::String(token));
56                }
57                match token.as_str() {
58                    "<>" => Ok(Field::Null),
59                    "\"\"" => Ok(Field::String(String::new())),
60                    _ if self.peek() == Some(b'[') => self.datum(&token),
61                    _ => Ok(Field::Atom(token)),
62                }
63            }
64        }
65    }
66
67    fn node(&mut self, depth: usize) -> Result<Node, SQLError> {
68        self.offset += 1;
69        let (kind, escaped) = self.token()?;
70        if escaped
71            || kind.is_empty()
72            || !kind
73                .bytes()
74                .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')
75        {
76            return Err(invalid("invalid node tag"));
77        }
78        let mut fields = Vec::new();
79        loop {
80            if self.peek() == Some(b'}') {
81                self.offset += 1;
82                break;
83            }
84            let (field, escaped) = self.token()?;
85            let name = field
86                .strip_prefix(':')
87                .filter(|name| !name.is_empty())
88                .ok_or_else(|| invalid("expected a named node field"))?;
89            if escaped || fields.iter().any(|(field, _)| field == name) {
90                return Err(invalid("invalid or duplicate node field"));
91            }
92            fields.push((name.to_owned(), self.value(depth)?));
93        }
94        Ok(Node { kind, fields })
95    }
96
97    fn list(&mut self, depth: usize) -> Result<Vec<Field>, SQLError> {
98        self.offset += 1;
99        let mut values = Vec::new();
100        loop {
101            if self.peek() == Some(b')') {
102                self.offset += 1;
103                return Ok(values);
104            }
105            if self.peek() == Some(b'"') {
106                let (token, _) = self.token()?;
107                let value = token
108                    .strip_prefix('"')
109                    .and_then(|token| token.strip_suffix('"'))
110                    .ok_or_else(|| invalid("unfinished String value in node list"))?;
111                values.push(Field::StringValue(value.into()));
112            } else {
113                values.push(self.value(depth)?);
114            }
115        }
116    }
117
118    fn datum(&mut self, length: &str) -> Result<Field, SQLError> {
119        let length = length
120            .parse()
121            .map_err(|_| invalid("invalid Datum length"))?;
122        self.offset += 1;
123        let mut bytes = Vec::new();
124        loop {
125            if self.peek() == Some(b']') {
126                self.offset += 1;
127                return Ok(Field::Datum { length, bytes });
128            }
129            let (token, escaped) = self.token()?;
130            let byte = token
131                .parse::<i16>()
132                .ok()
133                .filter(|byte| (-128..=255).contains(byte))
134                .ok_or_else(|| invalid("invalid Datum byte"))?;
135            if escaped {
136                return Err(invalid("escaped Datum byte"));
137            }
138            bytes.push(byte.to_le_bytes()[0]);
139        }
140    }
141
142    fn token(&mut self) -> Result<(String, bool), SQLError> {
143        self.peek();
144        let mut bytes = Vec::new();
145        let mut escaped = false;
146        while let Some(&byte) = self.bytes.get(self.offset) {
147            if byte.is_ascii_whitespace() || matches!(byte, b'{' | b'}' | b'(' | b')' | b'[' | b']')
148            {
149                break;
150            }
151            self.offset += 1;
152            if byte == b'\\' {
153                escaped = true;
154                let &next = self
155                    .bytes
156                    .get(self.offset)
157                    .ok_or_else(|| invalid("unfinished node token escape"))?;
158                bytes.push(next);
159                self.offset += 1;
160            } else {
161                bytes.push(byte);
162            }
163        }
164        if bytes.is_empty() {
165            return Err(invalid("missing node token"));
166        }
167        String::from_utf8(bytes)
168            .map(|token| (token, escaped))
169            .map_err(|_| invalid("invalid UTF-8 node token"))
170    }
171}