odatav4_parser/renderers/
postgresql.rs

1use crate::ast::QueryOptions;
2use crate::renderers::{filter::FilterRenderer, SqlRenderer};
3
4/// PostgreSQL renderer
5pub struct PostgresqlRenderer;
6
7impl PostgresqlRenderer {
8    pub fn new() -> Self {
9        Self
10    }
11}
12
13impl Default for PostgresqlRenderer {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl FilterRenderer for PostgresqlRenderer {
20    fn quote_identifier(&self, ident: &str) -> String {
21        format!("\"{}\"", ident)
22    }
23}
24
25impl SqlRenderer for PostgresqlRenderer {
26    fn render(&self, table_name: &str, options: &QueryOptions) -> String {
27        let mut parts = Vec::new();
28
29        // SELECT clause
30        parts.push("SELECT".to_string());
31
32        // Field selection
33        if let Some(ref fields) = options.select {
34            let field_list = fields
35                .iter()
36                .map(|f| self.quote_identifier(f))
37                .collect::<Vec<_>>()
38                .join(", ");
39            parts.push(field_list);
40        } else {
41            parts.push("*".to_string());
42        }
43
44        // FROM clause
45        parts.push("FROM".to_string());
46        parts.push(self.quote_identifier(table_name));
47
48        // Note: $expand requires schema knowledge for JOINs
49        if let Some(ref expand_fields) = options.expand {
50            let expand_list = expand_fields
51                .iter()
52                .map(|item| item.field.clone())
53                .collect::<Vec<_>>()
54                .join(", ");
55            parts.push(format!(
56                "/* TODO: JOIN {} */",
57                expand_list
58            ));
59        }
60
61        // WHERE clause (for $filter)
62        if let Some(ref filter) = options.filter {
63            parts.push("WHERE".to_string());
64            parts.push(self.render_filter(filter));
65        }
66
67        // GROUP BY clause (for $groupby)
68        if let Some(ref groupby_fields) = options.groupby {
69            let groupby_list = groupby_fields
70                .iter()
71                .map(|f| self.quote_identifier(f))
72                .collect::<Vec<_>>()
73                .join(", ");
74            parts.push(format!("GROUP BY {}", groupby_list));
75        }
76
77        // ORDER BY clause (for $orderby)
78        if let Some(ref orderby_items) = options.orderby {
79            let orderby_list = orderby_items
80                .iter()
81                .map(|item| {
82                    let dir = match item.direction {
83                        crate::ast::SortDirection::Asc => "ASC",
84                        crate::ast::SortDirection::Desc => "DESC",
85                    };
86                    format!("{} {}", self.quote_identifier(&item.field), dir)
87                })
88                .collect::<Vec<_>>()
89                .join(", ");
90            parts.push(format!("ORDER BY {}", orderby_list));
91        }
92
93        // LIMIT clause (for $top)
94        if let Some(top) = options.top {
95            parts.push(format!("LIMIT {}", top));
96        }
97
98        // OFFSET clause (for $skip)
99        if let Some(skip) = options.skip {
100            parts.push(format!("OFFSET {}", skip));
101        }
102
103        parts.join(" ")
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_postgresql_select_only() {
113        let renderer = PostgresqlRenderer::new();
114        let mut options = QueryOptions::new();
115        options.select = Some(vec!["id".to_string(), "name".to_string()]);
116
117        let sql = renderer.render("users", &options);
118        assert_eq!(sql, "SELECT \"id\", \"name\" FROM \"users\"");
119    }
120
121    #[test]
122    fn test_postgresql_limit() {
123        let renderer = PostgresqlRenderer::new();
124        let mut options = QueryOptions::new();
125        options.top = Some(10);
126
127        let sql = renderer.render("users", &options);
128        assert_eq!(sql, "SELECT * FROM \"users\" LIMIT 10");
129    }
130
131    #[test]
132    fn test_postgresql_offset() {
133        let renderer = PostgresqlRenderer::new();
134        let mut options = QueryOptions::new();
135        options.skip = Some(20);
136
137        let sql = renderer.render("users", &options);
138        assert_eq!(sql, "SELECT * FROM \"users\" OFFSET 20");
139    }
140
141    #[test]
142    fn test_postgresql_combined() {
143        let renderer = PostgresqlRenderer::new();
144        let mut options = QueryOptions::new();
145        options.select = Some(vec!["id".to_string(), "name".to_string()]);
146        options.top = Some(10);
147        options.skip = Some(20);
148
149        let sql = renderer.render("users", &options);
150        assert_eq!(sql, "SELECT \"id\", \"name\" FROM \"users\" LIMIT 10 OFFSET 20");
151    }
152}