Skip to main content

qail_core/transpiler/sql/
postgres.rs

1use crate::transpiler::escape_identifier;
2use crate::transpiler::traits::SqlGenerator;
3
4/// PostgreSQL-specific SQL generator.
5pub struct PostgresGenerator;
6
7impl Default for PostgresGenerator {
8    fn default() -> Self {
9        Self::new()
10    }
11}
12
13impl PostgresGenerator {
14    /// Create a new PostgreSQL generator.
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl SqlGenerator for PostgresGenerator {
21    fn quote_identifier(&self, name: &str) -> String {
22        escape_identifier(name)
23    }
24
25    fn placeholder(&self, index: usize) -> String {
26        format!("${}", index)
27    }
28
29    fn fuzzy_operator(&self) -> &str {
30        "ILIKE"
31    }
32
33    fn bool_literal(&self, val: bool) -> String {
34        if val {
35            "true".to_string()
36        } else {
37            "false".to_string()
38        }
39    }
40
41    fn string_concat(&self, parts: &[&str]) -> String {
42        parts.join(" || ")
43    }
44
45    fn limit_offset(&self, limit: Option<usize>, offset: Option<usize>) -> String {
46        let mut sql = String::new();
47        if let Some(n) = limit {
48            sql.push_str(&format!(" LIMIT {}", n));
49        }
50        if let Some(n) = offset {
51            sql.push_str(&format!(" OFFSET {}", n));
52        }
53        sql
54    }
55
56    fn json_access(&self, col: &str, path: &[&str]) -> String {
57        let mut sql = self.quote_identifier(col);
58
59        for (i, key) in path.iter().enumerate() {
60            let is_last = i == path.len() - 1;
61            // Use -> (json) for intermediates, ->> (text) for last
62            // Note: If the column is not text, an explicit cast may be required.
63            // Postgres ->> returns text, suitable for comparisons.
64            let op = if is_last { "->>" } else { "->" };
65            sql.push_str(&format!("{}'{}'", op, key.replace('\'', "''")));
66        }
67        sql
68    }
69}