sql_composer_postgres/
lib.rs1pub use tokio_postgres;
57
58#[cfg(feature = "sync")]
59pub use postgres;
60
61use std::collections::BTreeMap;
62use std::ops::{Deref, DerefMut};
63
64use sql_composer::composer::Composer;
65use sql_composer::driver;
66use sql_composer::types::Template;
67
68#[derive(Debug, thiserror::Error)]
70pub enum Error {
71 #[error(transparent)]
73 Composer(#[from] sql_composer::Error),
74
75 #[error(transparent)]
77 Postgres(#[from] tokio_postgres::Error),
78}
79
80#[cfg(feature = "async")]
90pub struct PgClient(pub tokio_postgres::Client);
91
92#[cfg(feature = "async")]
93impl PgClient {
94 pub fn from_client(client: tokio_postgres::Client) -> Self {
96 Self(client)
97 }
98}
99
100#[cfg(feature = "async")]
101impl Deref for PgClient {
102 type Target = tokio_postgres::Client;
103
104 fn deref(&self) -> &Self::Target {
105 &self.0
106 }
107}
108
109#[cfg(feature = "async")]
110impl DerefMut for PgClient {
111 fn deref_mut(&mut self) -> &mut Self::Target {
112 &mut self.0
113 }
114}
115
116#[cfg(feature = "async")]
119pub fn boxed_params(
120 params: &[Box<dyn tokio_postgres::types::ToSql + Sync + Send>],
121) -> Vec<&(dyn tokio_postgres::types::ToSql + Sync)> {
122 params
123 .iter()
124 .map(|p| p.as_ref() as &(dyn tokio_postgres::types::ToSql + Sync))
125 .collect()
126}
127
128#[cfg(feature = "async")]
129impl driver::ComposerConnectionAsync for PgClient {
130 type Value = Box<dyn tokio_postgres::types::ToSql + Sync + Send>;
131 type Statement = String;
132 type Error = Error;
133
134 async fn compose(
135 &self,
136 composer: &Composer,
137 template: &Template,
138 mut values: BTreeMap<String, Vec<Self::Value>>,
139 ) -> Result<(String, Vec<Self::Value>), Error> {
140 let composed = composer.compose_with_values(template, &values)?;
141 let ordered = driver::resolve_values(&composed, &mut values)?;
142 Ok((composed.sql, ordered))
143 }
144}
145
146#[cfg(feature = "sync")]
155pub struct PgConnection(pub postgres::Client);
156
157#[cfg(feature = "sync")]
158impl PgConnection {
159 pub fn from_client(client: postgres::Client) -> Self {
161 Self(client)
162 }
163}
164
165#[cfg(feature = "sync")]
166impl Deref for PgConnection {
167 type Target = postgres::Client;
168
169 fn deref(&self) -> &Self::Target {
170 &self.0
171 }
172}
173
174#[cfg(feature = "sync")]
175impl DerefMut for PgConnection {
176 fn deref_mut(&mut self) -> &mut Self::Target {
177 &mut self.0
178 }
179}
180
181#[cfg(feature = "sync")]
184pub fn boxed_params_sync(
185 params: &[Box<dyn postgres::types::ToSql + Sync>],
186) -> Vec<&(dyn postgres::types::ToSql + Sync)> {
187 params
188 .iter()
189 .map(|p| p.as_ref() as &(dyn postgres::types::ToSql + Sync))
190 .collect()
191}
192
193#[cfg(feature = "sync")]
194impl driver::ComposerConnection for PgConnection {
195 type Value = Box<dyn postgres::types::ToSql + Sync>;
196 type Statement = String;
197 type Error = Error;
198
199 fn compose(
200 &self,
201 composer: &Composer,
202 template: &Template,
203 mut values: BTreeMap<String, Vec<Self::Value>>,
204 ) -> Result<(String, Vec<Self::Value>), Error> {
205 let composed = composer.compose_with_values(template, &values)?;
206 let ordered = driver::resolve_values(&composed, &mut values)?;
207 Ok((composed.sql, ordered))
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use sql_composer::composer::Composer;
214 use sql_composer::parser::parse_template;
215 use sql_composer::types::{Dialect, TemplateSource};
216
217 #[test]
218 fn test_compose_single_bind_postgres() {
219 let input = "SELECT * FROM users WHERE id = :bind(user_id)";
220 let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
221 let composer = Composer::new(Dialect::Postgres);
222 let result = composer.compose(&template).unwrap();
223 assert_eq!(result.sql, "SELECT * FROM users WHERE id = $1");
224 assert_eq!(result.bind_params, vec!["user_id"]);
225 }
226
227 #[test]
228 fn test_compose_multiple_binds_postgres() {
229 let input = "SELECT * FROM users WHERE name = :bind(name) AND active = :bind(active)";
230 let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
231 let composer = Composer::new(Dialect::Postgres);
232 let result = composer.compose(&template).unwrap();
233 assert_eq!(
235 result.sql,
236 "SELECT * FROM users WHERE name = $2 AND active = $1"
237 );
238 assert_eq!(result.bind_params, vec!["active", "name"]);
239 }
240
241 #[test]
242 fn test_compose_with_values_multi_bind_postgres() {
243 let input = "SELECT * FROM users WHERE id IN (:bind(ids))";
244 let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
245 let composer = Composer::new(Dialect::Postgres);
246 let values = sql_composer::bind_values!("ids" => [10, 20, 30]);
247 let result = composer.compose_with_values(&template, &values).unwrap();
248 assert_eq!(result.sql, "SELECT * FROM users WHERE id IN ($1, $2, $3)");
249 assert_eq!(result.bind_params, vec!["ids", "ids", "ids"]);
250 }
251}