tag2upload_service_manager/
bsql_rows.rs

1
2use crate::prelude::*;
3use crate::bsql_queries::*;
4
5//---------- AsSqlRow ----------
6
7#[derive(Clone, Copy)]
8pub struct SkipRowId;
9
10pub struct AsBSqlColumnData<'r> {
11    pub name: &'r str,
12    pub value: DynToSql<'r>,
13    pub isnt_row_id: Result<(), SkipRowId>,
14}
15
16pub trait AsBSqlRow: Sync {
17    fn as_sql_row_columns(&self) -> impl Iterator<Item=AsBSqlColumnData<'_>>;
18}
19
20#[derive(Clone, Copy)]
21pub struct AsBSqlRowParams<'r, R: AsBSqlRow> {
22    pub row: &'r R, 
23    pub include_row_id: Result<(), bsql_rows::SkipRowId>,
24}
25impl<R: AsBSqlRow> AsBSqlRowParams<'_, R> {
26    fn cols(&self) -> impl Iterator<Item = AsBSqlColumnData> {
27        self.row.as_sql_row_columns()
28            .filter(|c| {
29                c.isnt_row_id
30                    .or_else(|_: SkipRowId| self.include_row_id)
31                    .is_ok()
32            })
33    }
34}
35
36impl<R: AsBSqlRow> IsFragment for AsBSqlRowParams<'_, R> {
37    fn bsql_extend_text(&self, s: &mut String) {
38        *s += "(";
39        extend_texts_sep_commas(s, self.cols().map(|c| c.name));
40        *s += ") VALUES (";
41        extend_texts_sep_commas(s, self.cols().map(|_c| "?"));
42        *s += ")";
43    }
44    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
45        for c in self.cols() {
46            p.push(c.value)
47        }
48    }
49    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
50}
51
52define_derive_deftly! {
53    export AsBSqlRow for struct, expect items:
54
55    impl $crate::bsql_rows::AsBSqlRow for $ttype {
56        fn as_sql_row_columns(&self) -> impl Iterator<
57            Item=$crate::bsql_rows::AsBSqlColumnData<'_>
58        > {
59            use $crate::prelude::*;
60            use bsql_rows::*;
61            chain!(
62          $(
63            ${if fmeta(bsql(flatten)) {
64                AsBSqlRow::as_sql_row_columns(&self.$fname)
65            } else {
66                [AsBSqlColumnData {
67                    name: stringify!($fname),
68                    value: &self.$fname,
69                    isnt_row_id: ${if fmeta(bsql(rowid)) {
70                        Err(SkipRowId)
71                    } else {
72                        Ok(())
73                    }},
74                }]
75            }}
76              ,
77          )
78            )
79        }
80    }
81} 
82
83//---------- UpdateSqlRow ----------
84
85pub struct BSqlUpdateColumnData<'u> {
86    pub name: &'u str,
87    pub value: DynToSql<'u>,
88}
89
90pub trait HasUpdateSqlRow {
91    type UpdateSqlRow<'u>: UpdateSqlRow + Default;
92}
93
94pub trait UpdateSqlRow: Sized + Sync {
95    /// In `bslq!`, if `Some`, provides `"col = .., .."`
96    fn bsql(&self) -> Option<impl AsFragment + '_> {
97        let _: BSqlUpdateColumnData = self.bsql_update_row_columns().next()?;
98        Some(Update(self))
99    }
100
101    fn bsql_update_row_columns(
102        &self
103    ) -> impl Iterator<Item=BSqlUpdateColumnData<'_>>;
104}
105
106struct Update<'u, U: UpdateSqlRow>(&'u U);
107
108/// Construct a `RowUpdate` or `impl IsFragment` for some `Row`, 
109///
110/// ```
111/// # use tag2upload_service_manager::{
112/// #     prelude::*,
113/// #     derive_deftly_template_UpdateSqlRow,
114/// #     bsql,
115/// #     bsql_update,
116/// # };
117/// # #[derive(Deftly)]
118/// # #[derive_deftly(UpdateSqlRow)]
119/// # struct Row { FIELD0: u32, FIELD1: u32 }
120/// # let VALUE = 1;
121/// # let FIELD1 = 1;
122/// #
123/// let _: RowUpdate = bsql_update! { Row {
124///     FIELD0: VALUE,
125///     FIELD1,
126///     // ..
127/// } };
128///
129/// bsql_update! { let update = Row {
130///     FIELD0: VALUE,
131///     FIELD1,
132///     // ..
133/// } };
134/// let _: BoundSql = bsql!("UPDATE table SET " update "");
135/// ```
136#[macro_export]
137macro_rules! bsql_update {
138    {
139        let $bind:ident = $tname:ident { $($body:tt)+ } $(;)?
140    } => {
141        let $bind = $crate::bsql_update! { $tname { $($body)+ } };
142        let $bind = $bind.bsql().expect("nonempty by construction");
143    };
144    {
145        $tname:ident { $(
146            $fname:ident $( : $value:expr )?
147        ),* $(,)?
148        }
149    } => { paste!{
150        [< $tname Update >] {
151            $(
152                $fname: $crate::bsql_update!(@ $fname $( : $value )? ),
153            )*
154            ..Default::default()
155        }
156    } };
157    { @ $fname:ident } => { $crate::bsql_update!(@ $fname: $fname ) };
158    { @ $fname:ident: $value:expr } => {
159        std::option::Option::Some(
160            std::convert::From::from(
161                $value
162            )
163        )
164    }
165}
166
167
168impl<'u, U: UpdateSqlRow> AsFragment for Update<'u, U> {
169    type F<'uu> = Self where Self: 'uu;
170    fn as_fragment(&self) -> Self::F<'_> { Update(self.0) }
171}
172
173impl<'u, U: UpdateSqlRow> IsFragment for Update<'u, U> {
174    fn bsql_extend_text(&self, s: &mut String) {
175        for (sep, c) in izip!(
176            chain!([""], iter::repeat(", ")),
177            self.0.bsql_update_row_columns(),
178        ) {
179            write!(s, "{sep}{} = ?", c.name).expect("write to string failed");
180        }
181    }
182    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
183        for c in self.0.bsql_update_row_columns() {
184            p.push(c.value)
185        }
186    }
187    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
188}
189
190define_derive_deftly! {
191    export UpdateSqlRow for struct, expect items:
192
193    #[derive(Clone, Default)]
194    $tvis struct $<$tname Update><'update_sql_row, $tdefgens> { $(
195        ${when not(fmeta(bsql(rowid)))}
196
197        $fvis $fname: std::option::Option<
198            ${if fmeta(bsql(flatten)) {
199                <$ftype as $crate::bsql_rows::HasUpdateSqlRow>
200                    ::UpdateSqlRow<'update_sql_row>
201            } else {
202                $crate::prelude::MaybeOwned<'update_sql_row, $ftype>
203            }}
204        >,
205    ) }
206
207    impl<$tgens> $crate::bsql_rows::HasUpdateSqlRow for $ttype {
208        type UpdateSqlRow<'update_sql_row> =
209            $<$tname Update><'update_sql_row, $tgens>;
210    }
211
212    impl<$tgens> $crate::bsql_rows::UpdateSqlRow
213        for $<$tname Update><'_, $tgens>
214    {
215        fn bsql_update_row_columns(
216            &self
217        ) -> impl Iterator<Item=$crate::bsql_rows::BSqlUpdateColumnData<'_>> {
218            #[allow(unused_imports)] // false positive
219            use $crate::bsql_rows::*;
220
221            chain!( $(
222                ${when not(fmeta(bsql(rowid)))}
223
224                self.$fname.as_ref().map(|v| {
225                    ${if fmeta(bsql(flatten)) {
226                        UpdateSqlRow::bsql_update_row_columns(v)
227                    } else {
228                        [BSqlUpdateColumnData {
229                            name: stringify!($fname),
230                            value: &**v as _,
231                        }]
232                    }}
233                }).into_iter().flatten(),
234            ) )
235        }
236    }
237
238    impl<'update_sql_row, $tgens> std::ops::BitOr
239        for $<$tname Update><'update_sql_row, $tgens>
240    {
241        type Output = $<$tname Update><'update_sql_row, $tgens>;
242        fn bitor(self, rhs: Self) -> Self::Output {
243            $<$tname Update> { $(
244                ${when not(fmeta(bsql(rowid)))}
245
246                ${if fmeta(bsql(flatten)) {
247                    $fname: Some(
248                        self.$fname.unwrap_or_default() |
249                        rhs.$fname.unwrap_or_default()
250                    )
251                } else {
252                    $fname: self.$fname.or(rhs.$fname)
253                }}
254                ,
255            ) }
256        }
257    }
258} 
259
260//---------- FromSqlRow ----------
261
262pub trait FromSqlRow: Sized {
263    fn from_sql_row(row: &rusqlite::Row) -> Result<Self, InternalError>;
264}
265
266define_derive_deftly! {
267    export FromSqlRow for struct, expect items:
268
269    impl FromSqlRow for $ttype {
270        fn from_sql_row(row: &rusqlite::Row) -> Result<Self, InternalError> {
271            Ok($ttype {
272              $(
273                $fname: 
274                    ${if fmeta(bsql(flatten)) {
275                        FromSqlRow::from_sql_row(row)?
276                    } else {
277                        row.get(stringify!($fname))
278                            .into_internal(concat!(
279                                "failed to convert ",
280                                stringify!($fname),
281                            ))?
282                    }},
283              )
284            })
285        }
286    }
287}