odatav4_parser/renderers/
postgresql.rs

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