pub use tokio_postgres;
#[cfg(feature = "sync")]
pub use postgres;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use sql_composer::composer::Composer;
use sql_composer::driver;
use sql_composer::types::Template;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Composer(#[from] sql_composer::Error),
#[error(transparent)]
Postgres(#[from] tokio_postgres::Error),
}
#[cfg(feature = "async")]
pub struct PgClient(pub tokio_postgres::Client);
#[cfg(feature = "async")]
impl PgClient {
pub fn from_client(client: tokio_postgres::Client) -> Self {
Self(client)
}
}
#[cfg(feature = "async")]
impl Deref for PgClient {
type Target = tokio_postgres::Client;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "async")]
impl DerefMut for PgClient {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "async")]
pub fn boxed_params(
params: &[Box<dyn tokio_postgres::types::ToSql + Sync + Send>],
) -> Vec<&(dyn tokio_postgres::types::ToSql + Sync)> {
params
.iter()
.map(|p| p.as_ref() as &(dyn tokio_postgres::types::ToSql + Sync))
.collect()
}
#[cfg(feature = "async")]
impl driver::ComposerConnectionAsync for PgClient {
type Value = Box<dyn tokio_postgres::types::ToSql + Sync + Send>;
type Statement = String;
type Error = Error;
async fn compose(
&self,
composer: &Composer,
template: &Template,
mut values: BTreeMap<String, Vec<Self::Value>>,
) -> Result<(String, Vec<Self::Value>), Error> {
let composed = composer.compose_with_values(template, &values)?;
let ordered = driver::resolve_values(&composed, &mut values)?;
Ok((composed.sql, ordered))
}
}
#[cfg(feature = "sync")]
pub struct PgConnection(pub postgres::Client);
#[cfg(feature = "sync")]
impl PgConnection {
pub fn from_client(client: postgres::Client) -> Self {
Self(client)
}
}
#[cfg(feature = "sync")]
impl Deref for PgConnection {
type Target = postgres::Client;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "sync")]
impl DerefMut for PgConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "sync")]
pub fn boxed_params_sync(
params: &[Box<dyn postgres::types::ToSql + Sync>],
) -> Vec<&(dyn postgres::types::ToSql + Sync)> {
params
.iter()
.map(|p| p.as_ref() as &(dyn postgres::types::ToSql + Sync))
.collect()
}
#[cfg(feature = "sync")]
impl driver::ComposerConnection for PgConnection {
type Value = Box<dyn postgres::types::ToSql + Sync>;
type Statement = String;
type Error = Error;
fn compose(
&self,
composer: &Composer,
template: &Template,
mut values: BTreeMap<String, Vec<Self::Value>>,
) -> Result<(String, Vec<Self::Value>), Error> {
let composed = composer.compose_with_values(template, &values)?;
let ordered = driver::resolve_values(&composed, &mut values)?;
Ok((composed.sql, ordered))
}
}
#[cfg(test)]
mod tests {
use sql_composer::composer::Composer;
use sql_composer::parser::parse_template;
use sql_composer::types::{Dialect, TemplateSource};
#[test]
fn test_compose_single_bind_postgres() {
let input = "SELECT * FROM users WHERE id = :bind(user_id)";
let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
let composer = Composer::new(Dialect::Postgres);
let result = composer.compose(&template).unwrap();
assert_eq!(result.sql, "SELECT * FROM users WHERE id = $1");
assert_eq!(result.bind_params, vec!["user_id"]);
}
#[test]
fn test_compose_multiple_binds_postgres() {
let input = "SELECT * FROM users WHERE name = :bind(name) AND active = :bind(active)";
let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
let composer = Composer::new(Dialect::Postgres);
let result = composer.compose(&template).unwrap();
assert_eq!(
result.sql,
"SELECT * FROM users WHERE name = $2 AND active = $1"
);
assert_eq!(result.bind_params, vec!["active", "name"]);
}
#[test]
fn test_compose_with_values_multi_bind_postgres() {
let input = "SELECT * FROM users WHERE id IN (:bind(ids))";
let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
let composer = Composer::new(Dialect::Postgres);
let values = sql_composer::bind_values!("ids" => [10, 20, 30]);
let result = composer.compose_with_values(&template, &values).unwrap();
assert_eq!(result.sql, "SELECT * FROM users WHERE id IN ($1, $2, $3)");
assert_eq!(result.bind_params, vec!["ids", "ids", "ids"]);
}
}