Skip to main content

rust_ef_postgres/
sql_generator.rs

1//! PostgreSQL-specific SQL dialect generator.
2
3use rust_ef::provider::ISqlGenerator;
4
5/// SQL generator that produces PostgreSQL-compatible SQL.
6#[derive(Debug, Clone)]
7pub struct PostgresSqlGenerator;
8
9impl PostgresSqlGenerator {
10    pub fn new() -> Self {
11        Self
12    }
13}
14
15impl Default for PostgresSqlGenerator {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl ISqlGenerator for PostgresSqlGenerator {
22    fn select(&self, table: &str, columns: &[&str]) -> String {
23        let cols = if columns.is_empty() {
24            "*".to_string()
25        } else {
26            columns
27                .iter()
28                .map(|c| self.quote_identifier(c))
29                .collect::<Vec<_>>()
30                .join(", ")
31        };
32        format!("SELECT {} FROM {}", cols, self.quote_identifier(table))
33    }
34
35    fn insert(&self, table: &str, columns: &[&str], returning: bool) -> String {
36        let cols = columns
37            .iter()
38            .map(|c| self.quote_identifier(c))
39            .collect::<Vec<_>>()
40            .join(", ");
41        let placeholders: Vec<String> = (1..=columns.len())
42            .map(|i| self.parameter_placeholder(i))
43            .collect();
44        let returning_clause = if returning {
45            " RETURNING *".to_string()
46        } else {
47            String::new()
48        };
49        format!(
50            "INSERT INTO {} ({}) VALUES ({}){}",
51            self.quote_identifier(table),
52            cols,
53            placeholders.join(", "),
54            returning_clause
55        )
56    }
57
58    fn insert_batch(&self, table: &str, columns: &[&str], row_count: usize) -> String {
59        let cols = columns
60            .iter()
61            .map(|c| self.quote_identifier(c))
62            .collect::<Vec<_>>()
63            .join(", ");
64        let cols_per_row = columns.len();
65        let mut idx = 1usize;
66        let rows: Vec<String> = (0..row_count)
67            .map(|_| {
68                let ph: Vec<String> = (0..cols_per_row)
69                    .map(|_| {
70                        let p = self.parameter_placeholder(idx);
71                        idx += 1;
72                        p
73                    })
74                    .collect();
75                format!("({})", ph.join(", "))
76            })
77            .collect();
78        format!(
79            "INSERT INTO {} ({}) VALUES {} RETURNING *",
80            self.quote_identifier(table),
81            cols,
82            rows.join(", "),
83        )
84    }
85
86    fn upsert_batch(
87        &self,
88        table: &str,
89        columns: &[&str],
90        conflict_cols: &[&str],
91        row_count: usize,
92    ) -> String {
93        let cols = columns
94            .iter()
95            .map(|c| self.quote_identifier(c))
96            .collect::<Vec<_>>()
97            .join(", ");
98        let cols_per_row = columns.len();
99        let mut idx = 1usize;
100        let rows: Vec<String> = (0..row_count)
101            .map(|_| {
102                let ph: Vec<String> = (0..cols_per_row)
103                    .map(|_| {
104                        let p = self.parameter_placeholder(idx);
105                        idx += 1;
106                        p
107                    })
108                    .collect();
109                format!("({})", ph.join(", "))
110            })
111            .collect();
112        let conflict = conflict_cols
113            .iter()
114            .map(|c| self.quote_identifier(c))
115            .collect::<Vec<_>>()
116            .join(", ");
117        let update_cols: Vec<&str> = columns
118            .iter()
119            .filter(|c| !conflict_cols.contains(c))
120            .copied()
121            .collect();
122        let sets = update_cols
123            .iter()
124            .map(|c| {
125                format!(
126                    "{} = EXCLUDED.{}",
127                    self.quote_identifier(c),
128                    self.quote_identifier(c)
129                )
130            })
131            .collect::<Vec<_>>()
132            .join(", ");
133        format!(
134            "INSERT INTO {} ({}) VALUES {} ON CONFLICT({}) DO UPDATE SET {} RETURNING *",
135            self.quote_identifier(table),
136            cols,
137            rows.join(", "),
138            conflict,
139            sets,
140        )
141    }
142
143    fn update(&self, table: &str, set_columns: &[&str], where_clause: &str) -> String {
144        let sets: Vec<String> = set_columns
145            .iter()
146            .enumerate()
147            .map(|(i, c)| {
148                format!(
149                    "{} = {}",
150                    self.quote_identifier(c),
151                    self.parameter_placeholder(i + 1)
152                )
153            })
154            .collect();
155        format!(
156            "UPDATE {} SET {} WHERE {}",
157            self.quote_identifier(table),
158            sets.join(", "),
159            where_clause
160        )
161    }
162
163    fn delete(&self, table: &str, where_clause: &str) -> String {
164        format!(
165            "DELETE FROM {} WHERE {}",
166            self.quote_identifier(table),
167            where_clause
168        )
169    }
170
171    fn create_table(&self, table: &str, columns: &[(String, String)]) -> String {
172        let col_defs: Vec<String> = columns
173            .iter()
174            .map(|(name, type_def)| format!("{} {}", self.quote_identifier(name), type_def))
175            .collect();
176        format!(
177            "CREATE TABLE {} (\n    {}\n)",
178            self.quote_identifier(table),
179            col_defs.join(",\n    ")
180        )
181    }
182
183    fn drop_table(&self, table: &str) -> String {
184        format!("DROP TABLE IF EXISTS {}", self.quote_identifier(table))
185    }
186
187    fn pagination(&self, skip: Option<usize>, take: Option<usize>) -> String {
188        match (skip, take) {
189            (Some(s), Some(t)) => format!("OFFSET {} LIMIT {}", s, t),
190            (Some(s), None) => format!("OFFSET {}", s),
191            (None, Some(t)) => format!("LIMIT {}", t),
192            (None, None) => String::new(),
193        }
194    }
195
196    fn parameter_placeholder(&self, index: usize) -> String {
197        format!("${}", index)
198    }
199
200    fn quote_identifier(&self, identifier: &str) -> String {
201        format!("\"{}\"", identifier)
202    }
203
204    fn auto_increment_syntax(&self) -> &'static str {
205        "SERIAL"
206    }
207
208    fn supports_returning(&self) -> bool {
209        true
210    }
211
212    fn uses_numbered_placeholders(&self) -> bool {
213        true
214    }
215}