1pub use crate::generated::nodes::*;
10
11pub type Str = Box<str>;
13
14pub type List = Vec<Option<Node>>;
16
17pub trait Equal {
19 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
29impl<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
46pub trait Out {
48 fn out(&self, s: &mut String);
50
51 fn fields(&self, s: &mut String, prefix: &str) {
53 let _ = (s, prefix);
54 }
55}
56
57pub trait NodeType: Out + Sized {
59 fn into_node(self: Box<Self>) -> Node;
61
62 fn from_node(node: Node) -> Result<Box<Self>, Node>;
64
65 fn peek(node: &Node) -> Option<&Self>;
67
68 fn peek_mut(node: &mut Node) -> Option<&mut Self>;
70}
71
72impl Node {
73 pub fn to_text(&self) -> String {
75 let mut s = String::new();
76 self.write(&mut s);
77 s
78 }
79}
80
81pub 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
144pub(crate) mod w {
146 use std::fmt::Display;
147
148 use super::{List, Node, Out};
149
150 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 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 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 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 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 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 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 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 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 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 pub(crate) fn integer(s: &mut String, value: i32) {
239 let _ = std::fmt::Write::write_fmt(s, format_args!("{value}"));
240 }
241
242 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 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 #[test]
313 fn select() {
314 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 #[test]
401 fn embedded() {
402 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}