postgres_extras/
lib.rs

1use std::{iter::Map, marker::PhantomData};
2
3use postgres::{types::{ToSql, FromSqlOwned}, Row, CopyInWriter};
4pub use postgres_extras_macros::sql;
5
6#[derive(Debug)]
7pub struct Sql<'a> {
8    pub raw: &'a str,
9    pub bindings: &'a [&'a(dyn ToSql + Sync) ]
10}
11
12impl<'a> Sql<'a> {
13    pub fn fragment(&self) -> SqlFragment<'_> {
14        SqlFragment::Lucky(self)
15    }
16    pub fn execute(self, tx: &mut impl postgres::GenericClient) -> Result<u64, postgres::error::Error> {
17        tx.execute(self.raw, self.bindings)
18    }
19    pub fn batch_execute(self, tx: &mut impl postgres::GenericClient) -> Result<(), postgres::error::Error> {
20        tx.batch_execute(self.raw)
21    }
22    pub fn query(self, tx: &mut impl postgres::GenericClient)-> Result<Vec<Row>, postgres::error::Error> {
23        tx.query(self.raw, self.bindings)
24    }
25    pub fn query_one(self, tx: &mut impl postgres::GenericClient)-> Result<Row, postgres::error::Error> {
26        tx.query_one(self.raw, self.bindings)
27    }
28    pub fn query_into<T: FromSqlOwned>(self, tx: &mut impl postgres::GenericClient)-> Result<T, postgres::error::Error> {
29        Ok(tx.query_one(self.raw, self.bindings)?.get(0))
30    }
31    pub fn query_opt(self, tx: &mut impl postgres::GenericClient)-> Result<Option<Row>, postgres::error::Error> {
32        tx.query_opt(self.raw, self.bindings)
33    }
34    pub fn copy_in(self, tx: &mut impl postgres::GenericClient) -> Result<CopyInWriter<'_>, postgres::error::Error> {
35        tx.copy_in(self.raw)
36    }
37}
38#[derive(Clone, Copy)]
39pub enum SqlFragment<'a> {
40    Raw(&'a str),
41    Bindings(&'a [ &'a (dyn ToSql + Sync) ]),
42    Binding(&'a (dyn ToSql + Sync)),
43    Fn(&'a dyn SqlSnippet<'a>),
44    Lucky(&'a Sql<'a>),
45}
46
47impl<'a> SqlFragment<'a> {
48    pub fn borrow_into_sql_fragment(self) -> Self {
49        self
50    }
51}
52pub trait SqlSnippet<'a> {
53    fn render_sql(&self, raw: &mut SqlBuilder<'a>);
54}
55pub type Binding<'a> = &'a (dyn ToSql+Sync);
56#[derive(Clone)]
57pub struct MapIntoRow<'a, const N: usize, T>{
58    iter: T,
59    s: PhantomData<&'a ()>
60}
61impl<'a, const N: usize, T> Iterator for MapIntoRow<'a, N, T>
62where T: Iterator,
63      <T as Iterator>::Item: IntoTupleSlice<'a, N>
64{
65    type Item = [Binding<'a>; N];
66
67    fn next(&mut self) -> Option<Self::Item> {
68        Some(self.iter.next()?.sql_tuple())
69    }
70
71}
72
73pub trait SqlIterExt: Iterator + Sized {
74    fn map_into_row<'a, const N: usize, F: FnMut(Self::Item) -> [Binding<'a>; N]>(self, fnx: F) -> Map<Self, F> {
75        self.map(fnx)
76    }
77    fn into_sql_tuples<'a, const N: usize>(self) -> MapIntoRow<'a, N, Self>
78        where Self::Item: IntoTupleSlice<'a, N>
79    {
80        MapIntoRow{
81            iter: self,
82            s: PhantomData
83        }
84    }
85
86    fn borrow_into_sql_fragment<'a>(&'a self) -> SqlFragment<'a>
87    where Self: IntoIterator + Clone, <Self as IntoIterator>::Item : SqlSnippet<'a>
88    {
89        SqlFragment::Fn(unsafe {std::mem::transmute::<&'a Self, &'a IterValues<Self>>(self)})
90    }
91}
92
93impl<T: Iterator + Sized> SqlIterExt for T{}
94
95pub trait IntoTupleSlice<'a, const N: usize> {
96    fn sql_tuple(&self) -> [Binding<'a>; N];
97}
98
99macro_rules! impl_into_tuple_slice {
100    ($count: literal; $($T:ident : $N: tt),*) => {
101        impl<'a, $($T: ToSql + Sync,)*> IntoTupleSlice<'a, $count> for ($(&'a $T,)*) {
102            fn sql_tuple(&self) -> [Binding<'a>; $count] {
103                [$(self.$N),*]
104            }
105        }
106        impl<'a, $($T: ToSql + Sync,)*> IntoTupleSlice<'a, $count> for &'a ($($T,)*) {
107            fn sql_tuple(&self) -> [Binding<'a>; $count] {
108                [$(&self.$N),*]
109            }
110        }
111    };
112}
113
114impl_into_tuple_slice!(1; T0: 0);
115impl_into_tuple_slice!(2; T0: 0, T1: 1);
116impl_into_tuple_slice!(3; T0: 0, T1: 1, T2: 2);
117impl_into_tuple_slice!(4; T0: 0, T1: 1, T2: 2, T3: 3);
118impl_into_tuple_slice!(5; T0: 0, T1: 1, T2: 2, T3: 3, T4: 4);
119impl_into_tuple_slice!(6; T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5);
120impl_into_tuple_slice!(7; T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6);
121impl_into_tuple_slice!(8; T0: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7);
122
123
124
125#[repr(transparent)]
126pub struct IterValues<T> { pub iter: T}
127impl <'a ,T: IntoIterator + Clone> SqlSnippet<'a> for IterValues<T>
128    where <T as IntoIterator>::Item : SqlSnippet<'a>
129{
130    fn render_sql(&self, sql: &mut SqlBuilder<'a>) {
131        for (x,i) in self.iter.clone().into_iter().enumerate() {
132            if x > 0 {
133                sql.raw.push(',');
134            } 
135            i.render_sql(sql)
136        }
137    }
138}
139impl <'a, const N: usize,> SqlSnippet<'a> for [ Binding<'a>;N ] {
140    fn render_sql(&self, sql: &mut SqlBuilder<'a>) {
141        sql.raw.push('(');
142        for (x, i)in self.iter().enumerate() {
143            if x > 0 {
144                sql.raw.push(',');
145            } 
146            sql.push_binding(*i);
147        }
148        sql.raw.push(')');
149    }
150}
151
152#[derive(Default)]
153pub struct SqlBuilder<'a> {
154    pub raw: String,
155    pub bindings: Vec<&'a(dyn ToSql + Sync)>
156}
157
158impl<'a> SqlBuilder<'a> {
159    //todo very broken due to dynamic bindings and static binds fixed ids :( SAD
160    pub fn push_binding(&mut self, bindings: &'a (dyn ToSql + Sync)) {
161        use std::fmt::Write;
162        self.bindings.push(bindings);
163        write!(self.raw, "${} ", self.bindings.len()).ok();
164    }
165
166    pub fn push(&mut self, frag: &SqlFragment<'a>) {
167        match frag {
168            SqlFragment::Raw(raw) => {
169                self.raw.push_str(raw);
170            },
171            SqlFragment::Bindings(binds) => {
172                self.bindings.extend_from_slice(binds);
173            },
174            SqlFragment::Lucky(s) => {
175                self.raw.push_str(s.raw);
176                self.bindings.extend_from_slice(s.bindings);
177            },
178            SqlFragment::Fn(fnx) => {
179                fnx.render_sql(self);
180            },
181            SqlFragment::Binding(binding) => {
182                self.push_binding(*binding)
183            },
184        }
185    }
186}
187pub struct GeneratedSql<'a> {
188    pub raw: String,
189    pub bindings: Vec<&'a(dyn ToSql + Sync)>
190}
191
192impl<'a> GeneratedSql<'a> {
193    pub fn execute(self, tx: &mut impl postgres::GenericClient) -> Result<u64, postgres::error::Error> {
194        tx.execute(&self.raw, &self.bindings)
195    }
196    pub fn batch_execute(self, tx: &mut impl postgres::GenericClient) -> Result<(), postgres::error::Error> {
197        tx.batch_execute(&self.raw)
198    }
199    pub fn query(self, tx: &mut impl postgres::GenericClient)-> Result<Vec<Row>, postgres::error::Error> {
200        tx.query(&self.raw, &self.bindings)
201    }
202    pub fn query_one(self, tx: &mut impl postgres::GenericClient)-> Result<Row, postgres::error::Error> {
203        tx.query_one(&self.raw, &self.bindings)
204    }
205    pub fn query_into<T: FromSqlOwned>(self, tx: &mut impl postgres::GenericClient)-> Result<T, postgres::error::Error> {
206        Ok(tx.query_one(&self.raw, &self.bindings)?.get(0))
207    }
208    pub fn query_opt(self, tx: &mut impl postgres::GenericClient)-> Result<Option<Row>, postgres::error::Error> {
209        tx.query_opt(&self.raw, &self.bindings)
210    }
211    pub fn copy_in(self, tx: &mut impl postgres::GenericClient) -> Result<CopyInWriter<'_>, postgres::error::Error> {
212        tx.copy_in(&self.raw)
213    }
214}
215impl<'a> GeneratedSql<'a> {
216    pub fn generated(fragments: &[ SqlFragment<'a>]) -> GeneratedSql<'a> {
217        let mut builder = SqlBuilder::default();
218        for frag in fragments {
219            builder.push(frag)
220        }
221        GeneratedSql { raw: builder.raw, bindings: builder.bindings }
222    }
223}