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            values.push(self.value(depth)?);
106        }
107    }
108
109    fn datum(&mut self, length: &str) -> Result<Field, SQLError> {
110        let length = length
111            .parse()
112            .map_err(|_| invalid("invalid Datum length"))?;
113        self.offset += 1;
114        let mut bytes = Vec::new();
115        loop {
116            if self.peek() == Some(b']') {
117                self.offset += 1;
118                return Ok(Field::Datum { length, bytes });
119            }
120            let (token, escaped) = self.token()?;
121            let byte = token
122                .parse::<i16>()
123                .ok()
124                .filter(|byte| (-128..=255).contains(byte))
125                .ok_or_else(|| invalid("invalid Datum byte"))?;
126            if escaped {
127                return Err(invalid("escaped Datum byte"));
128            }
129            bytes.push(byte.to_le_bytes()[0]);
130        }
131    }
132
133    fn token(&mut self) -> Result<(String, bool), SQLError> {
134        self.peek();
135        let mut bytes = Vec::new();
136        let mut escaped = false;
137        while let Some(&byte) = self.bytes.get(self.offset) {
138            if byte.is_ascii_whitespace() || matches!(byte, b'{' | b'}' | b'(' | b')' | b'[' | b']')
139            {
140                break;
141            }
142            self.offset += 1;
143            if byte == b'\\' {
144                escaped = true;
145                let &next = self
146                    .bytes
147                    .get(self.offset)
148                    .ok_or_else(|| invalid("unfinished node token escape"))?;
149                bytes.push(next);
150                self.offset += 1;
151            } else {
152                bytes.push(byte);
153            }
154        }
155        if bytes.is_empty() {
156            return Err(invalid("missing node token"));
157        }
158        String::from_utf8(bytes)
159            .map(|token| (token, escaped))
160            .map_err(|_| invalid("invalid UTF-8 node token"))
161    }
162}