Skip to main content

rupg_sql/
nodes.rs

1//! The raw parse tree: the node types of PostgreSQL and their text.
2//!
3//! The types are generated from the vendored headers into `generated/nodes.rs`, with the names and the fields that PostgreSQL gives them, so an action of `gram.y` ports to Rust line by line. This module has what the generator does not write: the names of the field types, the helpers of the writers, the writers of the three node types that `outfuncs.c` writes by hand, and the [`Equal`] of the pointers and the lists.
4//!
5//! The text of a node is the text of `nodeToStringWithLocations`. PostgreSQL logs that text for the raw parse tree of each statement when `debug_print_raw_parse` is on, so a test can compare the tree of this parser with the tree of PostgreSQL for the same statement.
6//!
7//! Lifted from `crates/rudb-pgparse/src/nodes.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
8
9pub use crate::generated::nodes::*;
10
11/// The text of a `char *` field.
12pub type Str = Box<str>;
13
14/// A `List *` of nodes. `NIL` is the empty list. An element can be `NULL`, as in the `list_make1(NIL)` of `DISTINCT`, and `outfuncs.c` writes it as `<>`.
15pub type List = Vec<Option<Node>>;
16
17/// `equal()` of `equalfuncs.c`: two trees are equal when all their fields are equal, except the locations. The derived `PartialEq` compares the locations too, so it is not `equal()`.
18pub trait Equal {
19    /// The two values are equal, without a look at the locations.
20    fn equal(&self, other: &Self) -> bool;
21}
22
23impl<T: Equal + ?Sized> Equal for Box<T> {
24    fn equal(&self, other: &Self) -> bool {
25        (**self).equal(other)
26    }
27}
28
29/// Two `NULL` pointers are equal, and a `NULL` pointer is not equal to a node.
30impl<T: Equal> Equal for Option<T> {
31    fn equal(&self, other: &Self) -> bool {
32        match (self, other) {
33            (None, None) => true,
34            (Some(a), Some(b)) => a.equal(b),
35            _ => false,
36        }
37    }
38}
39
40impl<T: Equal> Equal for Vec<T> {
41    fn equal(&self, other: &Self) -> bool {
42        self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a.equal(b))
43    }
44}
45
46/// A node type that `outNode` writes in braces.
47pub trait Out {
48    /// Writes the name of the node type and its fields, without the braces.
49    fn out(&self, s: &mut String);
50
51    /// Writes the fields only, each name with `prefix` before it. A node type that is the first field of another, as `CreateStmt` is in `CreateForeignTableStmt`, writes its fields so.
52    fn fields(&self, s: &mut String, prefix: &str) {
53        let _ = (s, prefix);
54    }
55}
56
57/// A node type that is a variant of [`Node`], with the casts of `castNode` and `IsA`.
58pub trait NodeType: Out + Sized {
59    /// The node of this type as a [`Node`].
60    fn into_node(self: Box<Self>) -> Node;
61
62    /// The node as this type, or the node back when it has another type.
63    fn from_node(node: Node) -> Result<Box<Self>, Node>;
64
65    /// The node as this type, or `None`.
66    fn peek(node: &Node) -> Option<&Self>;
67
68    /// The node as this type, or `None`.
69    fn peek_mut(node: &mut Node) -> Option<&mut Self>;
70}
71
72impl Node {
73    /// The text of `nodeToStringWithLocations` for the node.
74    pub fn to_text(&self) -> String {
75        let mut s = String::new();
76        self.write(&mut s);
77        s
78    }
79}
80
81/// The text of `nodeToStringWithLocations` for a list, for example the list of `RawStmt` nodes that the parser gives for a query.
82pub fn list_text(list: &List) -> String {
83    let mut s = String::new();
84    w::items(&mut s, list);
85    s
86}
87
88impl Out for A_Const {
89    fn out(&self, s: &mut String) {
90        s.push_str("A_CONST");
91        if self.isnull {
92            s.push_str(" NULL");
93        } else {
94            w::node(s, "", "val", self.val.as_ref());
95        }
96        w::int(s, "", "location", self.location);
97    }
98}
99
100impl Out for A_Expr {
101    fn out(&self, s: &mut String) {
102        s.push_str("A_EXPR");
103        let kind = match self.kind {
104            A_Expr_Kind::AEXPR_OP => "",
105            A_Expr_Kind::AEXPR_OP_ANY => " ANY",
106            A_Expr_Kind::AEXPR_OP_ALL => " ALL",
107            A_Expr_Kind::AEXPR_DISTINCT => " DISTINCT",
108            A_Expr_Kind::AEXPR_NOT_DISTINCT => " NOT_DISTINCT",
109            A_Expr_Kind::AEXPR_NULLIF => " NULLIF",
110            A_Expr_Kind::AEXPR_IN => " IN",
111            A_Expr_Kind::AEXPR_LIKE => " LIKE",
112            A_Expr_Kind::AEXPR_ILIKE => " ILIKE",
113            A_Expr_Kind::AEXPR_SIMILAR => " SIMILAR",
114            A_Expr_Kind::AEXPR_BETWEEN => " BETWEEN",
115            A_Expr_Kind::AEXPR_NOT_BETWEEN => " NOT_BETWEEN",
116            A_Expr_Kind::AEXPR_BETWEEN_SYM => " BETWEEN_SYM",
117            A_Expr_Kind::AEXPR_NOT_BETWEEN_SYM => " NOT_BETWEEN_SYM",
118            _ => " ?",
119        };
120        s.push_str(kind);
121        w::list(s, "", "name", &self.name);
122        w::node(s, "", "lexpr", self.lexpr.as_ref());
123        w::node(s, "", "rexpr", self.rexpr.as_ref());
124        w::int(s, "", "rexpr_list_start", self.rexpr_list_start);
125        w::int(s, "", "rexpr_list_end", self.rexpr_list_end);
126        w::int(s, "", "location", self.location);
127    }
128}
129
130impl Out for BoolExpr {
131    fn out(&self, s: &mut String) {
132        s.push_str("BOOLEXPR :boolop ");
133        s.push_str(match self.boolop {
134            BoolExprType::AND_EXPR => "and",
135            BoolExprType::OR_EXPR => "or",
136            BoolExprType::NOT_EXPR => "not",
137            _ => "<>",
138        });
139        w::list(s, "", "args", &self.args);
140        w::int(s, "", "location", self.location);
141    }
142}
143
144/// The helpers of the writers, one for each `WRITE_..._FIELD` macro of `outfuncs.c`.
145pub(crate) mod w {
146    use std::fmt::Display;
147
148    use super::{List, Node, Out};
149
150    /// Writes ` :name `, with the prefix of an embedded node type.
151    fn label(s: &mut String, prefix: &str, name: &str) {
152        s.push_str(" :");
153        s.push_str(prefix);
154        s.push_str(name);
155        s.push(' ');
156    }
157
158    /// `WRITE_BOOL_FIELD`.
159    pub(crate) fn bool(s: &mut String, prefix: &str, name: &str, value: bool) {
160        label(s, prefix, name);
161        s.push_str(if value { "true" } else { "false" });
162    }
163
164    /// `WRITE_INT_FIELD`, `WRITE_UINT_FIELD`, `WRITE_ENUM_FIELD` and `WRITE_LOCATION_FIELD`.
165    pub(crate) fn int(s: &mut String, prefix: &str, name: &str, value: impl Display) {
166        label(s, prefix, name);
167        let _ = std::fmt::Write::write_fmt(s, format_args!("{value}"));
168    }
169
170    /// `WRITE_CHAR_FIELD`: `<>` for the zero character.
171    pub(crate) fn char(s: &mut String, prefix: &str, name: &str, value: u8) {
172        label(s, prefix, name);
173        if value == 0 {
174            s.push_str("<>");
175        } else {
176            let mut buffer = [0; 4];
177            token(s, Some(char::from(value).encode_utf8(&mut buffer)));
178        }
179    }
180
181    /// `WRITE_STRING_FIELD`.
182    pub(crate) fn text(s: &mut String, prefix: &str, name: &str, value: Option<&str>) {
183        label(s, prefix, name);
184        token(s, value);
185    }
186
187    /// `WRITE_NODE_FIELD` for a `List *`.
188    pub(crate) fn list(s: &mut String, prefix: &str, name: &str, value: &List) {
189        label(s, prefix, name);
190        items(s, value);
191    }
192
193    /// `WRITE_NODE_FIELD` for a `Node *`.
194    pub(crate) fn node(s: &mut String, prefix: &str, name: &str, value: Option<&Node>) {
195        label(s, prefix, name);
196        match value {
197            Some(node) => node.write(s),
198            None => s.push_str("<>"),
199        }
200    }
201
202    /// `WRITE_NODE_FIELD` for a pointer to a node type.
203    pub(crate) fn boxed<T: Out>(s: &mut String, prefix: &str, name: &str, value: Option<&T>) {
204        label(s, prefix, name);
205        match value {
206            Some(node) => braced(s, node),
207            None => s.push_str("<>"),
208        }
209    }
210
211    /// A node type in braces.
212    pub(crate) fn braced<T: Out + ?Sized>(s: &mut String, node: &T) {
213        s.push('{');
214        node.out(s);
215        s.push('}');
216    }
217
218    /// `_outList`: the nodes in parentheses with a space between two, or `<>` for `NIL`.
219    pub(crate) fn items(s: &mut String, list: &List) {
220        if list.is_empty() {
221            s.push_str("<>");
222            return;
223        }
224        s.push('(');
225        for (i, node) in list.iter().enumerate() {
226            if i > 0 {
227                s.push(' ');
228            }
229            match node {
230                Some(node) => node.write(s),
231                None => s.push_str("<>"),
232            }
233        }
234        s.push(')');
235    }
236
237    /// `_outInteger`.
238    pub(crate) fn integer(s: &mut String, value: i32) {
239        let _ = std::fmt::Write::write_fmt(s, format_args!("{value}"));
240    }
241
242    /// `_outString`: the text in double quotes, with the escapes of `outToken`.
243    pub(crate) fn string(s: &mut String, value: &str) {
244        s.push('"');
245        if !value.is_empty() {
246            token(s, Some(value));
247        }
248        s.push('"');
249    }
250
251    /// `outToken`: `<>` for `NULL`, `""` for the empty string, and else the text with a backslash before each character that `read.c` treats in a special way.
252    pub(crate) fn token(s: &mut String, value: Option<&str>) {
253        let Some(value) = value else {
254            s.push_str("<>");
255            return;
256        };
257        let bytes = value.as_bytes();
258        let Some(&first) = bytes.first() else {
259            s.push_str("\"\"");
260            return;
261        };
262        let second = bytes.get(1).copied().unwrap_or(0);
263        if first == b'<'
264            || first == b'"'
265            || first.is_ascii_digit()
266            || (matches!(first, b'+' | b'-') && (second.is_ascii_digit() || second == b'.'))
267        {
268            s.push('\\');
269        }
270        for c in value.chars() {
271            if matches!(c, ' ' | '\n' | '\t' | '(' | ')' | '{' | '}' | '\\') {
272                s.push('\\');
273            }
274            s.push(c);
275        }
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn tokens() {
285        let token = |value: Option<&str>| {
286            let mut s = String::new();
287            w::token(&mut s, value);
288            s
289        };
290        assert_eq!(token(None), "<>");
291        assert_eq!(token(Some("")), "\"\"");
292        assert_eq!(token(Some("a b(c)")), "a\\ b\\(c\\)");
293        assert_eq!(token(Some("1x")), "\\1x");
294        assert_eq!(token(Some("-.5")), "\\-.5");
295        assert_eq!(token(Some("-a")), "-a");
296        assert_eq!(token(Some("<x")), "\\<x");
297    }
298
299    fn string(text: &str) -> Option<Node> {
300        Some(Node::String(text.into()))
301    }
302
303    fn column(name: &str, location: i32) -> Option<Node> {
304        Some(ColumnRef { fields: vec![string(name)], location }.into())
305    }
306
307    fn integer(value: i32, location: i32) -> Option<Node> {
308        Some(A_Const { val: Some(Node::Integer(value)), isnull: false, location }.into())
309    }
310
311    /// The tree that PostgreSQL 19 logs with `debug_print_raw_parse` for a query, built by hand.
312    #[test]
313    fn select() {
314        // select a+1 as x, 'q'::text from t where b between 1 and 2 and not c
315        let plus = A_Expr {
316            kind: A_Expr_Kind::AEXPR_OP,
317            name: vec![string("+")],
318            lexpr: column("a", 7),
319            rexpr: integer(1, 9),
320            location: 8,
321            ..A_Expr::default()
322        };
323        let cast = TypeCast {
324            arg: Some(A_Const { val: string("q"), isnull: false, location: 17 }.into()),
325            typeName: Some(Box::new(TypeName {
326                names: vec![string("text")],
327                typemod: -1,
328                location: 22,
329                ..TypeName::default()
330            })),
331            location: 20,
332        };
333        let between = A_Expr {
334            kind: A_Expr_Kind::AEXPR_BETWEEN,
335            name: vec![string("BETWEEN")],
336            lexpr: column("b", 40),
337            rexpr: Some(Node::List(vec![integer(1, 50), integer(2, 56)])),
338            location: 42,
339            ..A_Expr::default()
340        };
341        let not =
342            BoolExpr { boolop: BoolExprType::NOT_EXPR, args: vec![column("c", 66)], location: 62 };
343        let select = SelectStmt {
344            targetList: vec![
345                Some(
346                    ResTarget {
347                        name: Some("x".into()),
348                        val: Some(plus.into()),
349                        location: 7,
350                        ..ResTarget::default()
351                    }
352                    .into(),
353                ),
354                Some(
355                    ResTarget { val: Some(cast.into()), location: 17, ..ResTarget::default() }
356                        .into(),
357                ),
358            ],
359            fromClause: vec![Some(
360                RangeVar {
361                    relname: Some("t".into()),
362                    inh: true,
363                    relpersistence: b'p',
364                    location: 32,
365                    ..RangeVar::default()
366                }
367                .into(),
368            )],
369            whereClause: Some(
370                BoolExpr {
371                    boolop: BoolExprType::AND_EXPR,
372                    args: vec![Some(between.into()), Some(not.into())],
373                    location: 58,
374                }
375                .into(),
376            ),
377            ..SelectStmt::default()
378        };
379        let raw = RawStmt { stmt: Some(select.into()), stmt_location: 0, stmt_len: 0 };
380        let expected = "({RAWSTMT :stmt {SELECTSTMT :distinctClause <> :intoClause <> :targetList \
381            ({RESTARGET :name x :indirection <> :val {A_EXPR :name (\"+\") :lexpr {COLUMNREF \
382            :fields (\"a\") :location 7} :rexpr {A_CONST :val 1 :location 9} :rexpr_list_start 0 \
383            :rexpr_list_end 0 :location 8} :location 7} {RESTARGET :name <> :indirection <> :val \
384            {TYPECAST :arg {A_CONST :val \"q\" :location 17} :typeName {TYPENAME :names \
385            (\"text\") :typeOid 0 :setof false :pct_type false :typmods <> :typemod -1 \
386            :arrayBounds <> :location 22} :location 20} :location 17}) :fromClause ({RANGEVAR \
387            :catalogname <> :schemaname <> :relname t :inh true :relpersistence p :alias <> \
388            :location 32}) :whereClause {BOOLEXPR :boolop and :args ({A_EXPR BETWEEN :name \
389            (\"BETWEEN\") :lexpr {COLUMNREF :fields (\"b\") :location 40} :rexpr ({A_CONST :val \
390            1 :location 50} {A_CONST :val 2 :location 56}) :rexpr_list_start 0 :rexpr_list_end 0 \
391            :location 42} {BOOLEXPR :boolop not :args ({COLUMNREF :fields (\"c\") :location 66}) \
392            :location 62}) :location 58} :groupClause <> :groupDistinct false :havingClause <> \
393            :windowClause <> :valuesLists <> :sortClause <> :limitOffset <> :limitCount <> \
394            :limitOption 0 :lockingClause <> :withClause <> :op 0 :all false :larg <> :rarg <>} \
395            :stmt_location 0 :stmt_len 0})";
396        assert_eq!(list_text(&vec![Some(raw.into())]), expected);
397    }
398
399    /// A node type that is the first field of another writes its fields with `base.` before them.
400    #[test]
401    fn embedded() {
402        // create foreign table f (a int) server s
403        let column = ColumnDef {
404            colname: Some("a".into()),
405            typeName: Some(Box::new(TypeName {
406                names: vec![string("pg_catalog"), string("int4")],
407                typemod: -1,
408                location: 26,
409                ..TypeName::default()
410            })),
411            is_local: true,
412            location: 24,
413            ..ColumnDef::default()
414        };
415        let create = CreateForeignTableStmt {
416            base: CreateStmt {
417                relation: Some(Box::new(RangeVar {
418                    relname: Some("f".into()),
419                    inh: true,
420                    relpersistence: b'p',
421                    location: 21,
422                    ..RangeVar::default()
423                })),
424                tableElts: vec![Some(column.into())],
425                ..CreateStmt::default()
426            },
427            servername: Some("s".into()),
428            options: Vec::new(),
429        };
430        let expected = "{CREATEFOREIGNTABLESTMT :base.relation {RANGEVAR :catalogname <> :schemaname <> \
431            :relname f :inh true :relpersistence p :alias <> :location 21} :base.tableElts \
432            ({COLUMNDEF :colname a :typeName {TYPENAME :names (\"pg_catalog\" \"int4\") :typeOid 0 \
433            :setof false :pct_type false :typmods <> :typemod -1 :arrayBounds <> :location 26} \
434            :compression <> :inhcount 0 :is_local true :is_not_null false :is_from_type false \
435            :storage <> :storage_name <> :raw_default <> :cooked_default <> :identity <> \
436            :identitySequence <> :generated <> :collClause <> :collOid 0 :constraints <> \
437            :fdwoptions <> :location 24}) :base.inhRelations <> :base.partbound <> :base.partspec \
438            <> :base.ofTypename <> :base.constraints <> :base.nnconstraints <> :base.options <> \
439            :base.oncommit 0 :base.tablespacename <> :base.accessMethod <> :base.if_not_exists \
440            false :servername s :options <>}";
441        assert_eq!(Node::from(create).to_text(), expected);
442    }
443}