diesel_dynamic_schema/
dynamic_value.rs

1//! This module provides a container that allows to receive a dynamically
2//! specified number of fields from the database.
3//!
4//!
5//! ```rust
6//! # mod connection_setup {
7//! #     include!("../tests/connection_setup.rs");
8//! # }
9//! # use diesel::prelude::*;
10//! # use diesel::sql_types::{Untyped};
11//! # use diesel_dynamic_schema::{table, DynamicSelectClause};
12//! # use diesel_dynamic_schema::dynamic_value::*;
13//! # use diesel::dsl::sql_query;
14//! # use diesel::deserialize::{self, FromSql};
15//! #
16//! # #[derive(PartialEq, Debug)]
17//! # enum MyDynamicValue {
18//! #     String(String),
19//! #     Integer(i32),
20//! # }
21//! #
22//! # #[cfg(feature = "postgres")]
23//! # impl FromSql<Any, diesel::pg::Pg> for MyDynamicValue {
24//! #     fn from_sql(value: diesel::pg::PgValue) -> deserialize::Result<Self> {
25//! #         use diesel::pg::Pg;
26//! #         use std::num::NonZeroU32;
27//! #
28//! #         const VARCHAR_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1043) };
29//! #         const TEXT_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(25) };
30//! #         const INTEGER_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(23) };
31//! #
32//! #         match value.get_oid() {
33//! #             VARCHAR_OID | TEXT_OID => {
34//! #                 <String as FromSql<diesel::sql_types::Text, Pg>>::from_sql(value)
35//! #                     .map(MyDynamicValue::String)
36//! #             }
37//! #             INTEGER_OID => <i32 as FromSql<diesel::sql_types::Integer, Pg>>::from_sql(value)
38//! #                 .map(MyDynamicValue::Integer),
39//! #             e => Err(format!("Unknown type: {}", e).into()),
40//! #         }
41//! #     }
42//! # }
43//! #
44//! # #[cfg(feature = "sqlite")]
45//! # impl FromSql<Any, diesel::sqlite::Sqlite> for MyDynamicValue {
46//! #     fn from_sql(value: diesel::sqlite::SqliteValue) -> deserialize::Result<Self> {
47//! #         use diesel::sqlite::{Sqlite, SqliteType};
48//! #         match value.value_type() {
49//! #             Some(SqliteType::Text) => {
50//! #                 <String as FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)
51//! #                     .map(MyDynamicValue::String)
52//! #             }
53//! #             Some(SqliteType::Long) => {
54//! #                 <i32 as FromSql<diesel::sql_types::Integer, Sqlite>>::from_sql(value)
55//! #                     .map(MyDynamicValue::Integer)
56//! #             }
57//! #             _ => Err("Unknown data type".into()),
58//! #         }
59//! #     }
60//! # }
61//! #
62//! # #[cfg(feature = "mysql")]
63//! # impl FromSql<Any, diesel::mysql::Mysql> for MyDynamicValue {
64//! #    fn from_sql(value: diesel::mysql::MysqlValue) -> deserialize::Result<Self> {
65//! #         use diesel::mysql::{Mysql, MysqlType};
66//! #         match value.value_type() {
67//! #              MysqlType::String => {
68//! #                  <String as FromSql<diesel::sql_types::Text, Mysql>>::from_sql(value)
69//! #                      .map(MyDynamicValue::String)
70//! #              }
71//! #              MysqlType::Long => <i32 as FromSql<diesel::sql_types::Integer, Mysql>>::from_sql(value)
72//! #                 .map(MyDynamicValue::Integer),
73//! #             e => Err(format!("Unknown data type: {:?}", e).into()),
74//! #         }
75//! #     }
76//! # }
77//! #
78//! # fn result_main() -> QueryResult<()> {
79//! #
80//! # let conn = &mut connection_setup::establish_connection();
81//! #
82//! # // Create some example data by using typical SQL statements.
83//! # connection_setup::create_user_table(conn);
84//! # sql_query("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").execute(conn)?;
85//!
86//!     let users = diesel_dynamic_schema::table("users");
87//!     let id = users.column::<Untyped, _>("id");
88//!     let name = users.column::<Untyped, _>("name");
89//!
90//!     let mut select = DynamicSelectClause::new();
91//!
92//!     select.add_field(id);
93//!     select.add_field(name);
94//!
95//!     let actual_data: Vec<DynamicRow<NamedField<MyDynamicValue>>> =
96//!         users.select(select).load(conn)?;
97//!
98//!     assert_eq!(
99//!         actual_data[0]["name"],
100//!         MyDynamicValue::String("Sean".into())
101//!     );
102//!     assert_eq!(
103//!         actual_data[0][1],
104//!         NamedField {
105//!             name: "name".into(),
106//!             value: MyDynamicValue::String("Sean".into())
107//!         }
108//!     );
109//!
110//! # Ok(())
111//! # }
112//! # result_main().unwrap()
113//! ```
114//!
115//! It is required to provide your own inner type to hold the actual database value.
116//!
117//! ```rust
118//! # use diesel_dynamic_schema::dynamic_value::Any;
119//! # use diesel::deserialize::{self, FromSql};
120//! #
121//! #[derive(PartialEq, Debug)]
122//! enum MyDynamicValue {
123//!    String(String),
124//!    Integer(i32),
125//! }
126//!
127//! # #[cfg(feature = "postgres")]
128//! impl FromSql<Any, diesel::pg::Pg> for MyDynamicValue {
129//!    fn from_sql(value: diesel::pg::PgValue) -> deserialize::Result<Self> {
130//!        use diesel::pg::Pg;
131//!        use std::num::NonZeroU32;
132//!
133//!        const VARCHAR_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1043) };
134//!        const TEXT_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(25) };
135//!        const INTEGER_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(23) };
136//!
137//!        match value.get_oid() {
138//!            VARCHAR_OID | TEXT_OID => {
139//!                <String as FromSql<diesel::sql_types::Text, Pg>>::from_sql(value)
140//!                    .map(MyDynamicValue::String)
141//!            }
142//!            INTEGER_OID => <i32 as FromSql<diesel::sql_types::Integer, Pg>>::from_sql(value)
143//!                .map(MyDynamicValue::Integer),
144//!            e => Err(format!("Unknown type: {}", e).into()),
145//!        }
146//!    }
147//! }
148//! ```
149
150use diesel::backend::Backend;
151use diesel::deserialize::{self, FromSql};
152use diesel::expression::TypedExpressionType;
153use diesel::row::{Field, NamedRow, Row};
154use diesel::QueryableByName;
155use std::iter::FromIterator;
156use std::ops::Index;
157
158/// A marker type used to indicate that
159/// the provided `FromSql` impl does handle
160/// any passed database value, independently
161/// from the actual value kind
162pub struct Any;
163
164impl TypedExpressionType for Any {}
165
166#[cfg(feature = "postgres")]
167impl diesel::expression::QueryMetadata<Any> for diesel::pg::Pg {
168    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
169        out.push(None)
170    }
171}
172
173#[cfg(feature = "sqlite")]
174impl diesel::expression::QueryMetadata<Any> for diesel::sqlite::Sqlite {
175    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
176        out.push(None)
177    }
178}
179
180#[cfg(feature = "mysql")]
181impl diesel::expression::QueryMetadata<Any> for diesel::mysql::Mysql {
182    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
183        out.push(None)
184    }
185}
186
187/// A dynamically sized container that allows to receive
188/// a not at compile time known number of columns from the database
189#[derive(Debug)]
190pub struct DynamicRow<I> {
191    values: Vec<I>,
192}
193
194/// A helper struct used as field type in `DynamicRow`
195/// to also return the name of the field along with the
196/// value
197#[derive(Debug, PartialEq, Eq)]
198pub struct NamedField<I> {
199    /// Name of the field
200    pub name: String,
201    /// Actual field value
202    pub value: I,
203}
204
205impl<I> FromIterator<I> for DynamicRow<I> {
206    fn from_iter<T>(iter: T) -> Self
207    where
208        T: IntoIterator<Item = I>,
209    {
210        DynamicRow {
211            values: iter.into_iter().collect(),
212        }
213    }
214}
215
216impl<I> DynamicRow<I> {
217    /// Get the field value at the provided row index
218    ///
219    /// Returns `None` if the index is outside the bounds of the row
220    pub fn get(&self, index: usize) -> Option<&I> {
221        self.values.get(index)
222    }
223
224    /// Get the number of fields in the current row
225    pub fn len(&self) -> usize {
226        self.values.len()
227    }
228
229    /// Check if the current row is empty
230    pub fn is_empty(&self) -> bool {
231        self.values.is_empty()
232    }
233
234    /// Create a new dynamic row from an existing database row
235    ///
236    /// This function is mostly useful for third party backends adding
237    /// support for `diesel_dynamic_schema`
238    pub fn from_row<'a, DB>(row: &impl Row<'a, DB>) -> deserialize::Result<Self>
239    where
240        DB: Backend,
241        I: FromSql<Any, DB>,
242    {
243        let data = (0..row.field_count())
244            .map(|i| {
245                let field = Row::get(row, i).expect("We checked the field count above");
246
247                I::from_nullable_sql(field.value())
248            })
249            .collect::<deserialize::Result<_>>()?;
250
251        Ok(Self { values: data })
252    }
253}
254
255impl<I> DynamicRow<NamedField<I>> {
256    /// Get the field value by the provided field name
257    ///
258    /// Returns `None` if the field with the specified name is not found.
259    /// If there are multiple fields with the same name, the behaviour
260    /// of this function is unspecified.
261    pub fn get_by_name<S: AsRef<str>>(&self, name: S) -> Option<&I> {
262        self.values
263            .iter()
264            .find(|f| f.name == name.as_ref())
265            .map(|f| &f.value)
266    }
267}
268
269impl<I> DynamicRow<NamedField<Option<I>>> {
270    /// Create a new dynamic row instance with corresponding field information from the given
271    /// database row
272    ///
273    /// This function is mostly useful for third party backends adding
274    /// support for `diesel_dynamic_schema`
275    pub fn from_nullable_row<'a, DB>(row: &impl Row<'a, DB>) -> deserialize::Result<Self>
276    where
277        DB: Backend,
278        I: FromSql<Any, DB>,
279    {
280        let data = (0..row.field_count())
281            .map(|i| {
282                let field = Row::get(row, i).expect("We checked the field count above");
283
284                let value = match I::from_nullable_sql(field.value()) {
285                    Ok(o) => Some(o),
286                    Err(e) if e.is::<diesel::result::UnexpectedNullError>() => None,
287                    Err(e) => return Err(e),
288                };
289
290                Ok(NamedField {
291                    name: field
292                        .field_name()
293                        .ok_or("Try to load an unnamed field")?
294                        .to_owned(),
295                    value,
296                })
297            })
298            .collect::<deserialize::Result<Vec<_>>>()?;
299        Ok(DynamicRow { values: data })
300    }
301}
302
303#[cfg(feature = "postgres")]
304impl<I> QueryableByName<diesel::pg::Pg> for DynamicRow<I>
305where
306    I: FromSql<Any, diesel::pg::Pg>,
307{
308    fn build<'a>(row: &impl NamedRow<'a, diesel::pg::Pg>) -> deserialize::Result<Self> {
309        Self::from_row(row)
310    }
311}
312
313#[cfg(feature = "mysql")]
314impl<I> QueryableByName<diesel::mysql::Mysql> for DynamicRow<I>
315where
316    I: FromSql<Any, diesel::mysql::Mysql>,
317{
318    fn build<'a>(row: &impl NamedRow<'a, diesel::mysql::Mysql>) -> deserialize::Result<Self> {
319        Self::from_row(row)
320    }
321}
322
323#[cfg(feature = "sqlite")]
324impl<I> QueryableByName<diesel::sqlite::Sqlite> for DynamicRow<I>
325where
326    I: FromSql<Any, diesel::sqlite::Sqlite>,
327{
328    fn build<'a>(row: &impl NamedRow<'a, diesel::sqlite::Sqlite>) -> deserialize::Result<Self> {
329        Self::from_row(row)
330    }
331}
332
333impl<I, DB> QueryableByName<DB> for DynamicRow<Option<I>>
334where
335    DB: Backend,
336    I: FromSql<Any, DB>,
337{
338    fn build<'a>(row: &impl NamedRow<'a, DB>) -> deserialize::Result<Self> {
339        let data = (0..row.field_count())
340            .map(|i| {
341                let field = Row::get(row, i).expect("We checked the field count above");
342
343                match I::from_nullable_sql(field.value()) {
344                    Ok(o) => Ok(Some(o)),
345                    Err(e) if e.is::<diesel::result::UnexpectedNullError>() => Ok(None),
346                    Err(e) => Err(e),
347                }
348            })
349            .collect::<deserialize::Result<_>>()?;
350
351        Ok(Self { values: data })
352    }
353}
354
355impl<I, DB> QueryableByName<DB> for DynamicRow<NamedField<I>>
356where
357    DB: Backend,
358    I: FromSql<Any, DB>,
359{
360    fn build<'a>(row: &impl NamedRow<'a, DB>) -> deserialize::Result<Self> {
361        let data = (0..row.field_count())
362            .map(|i| {
363                let field = Row::get(row, i).expect("We checked the field count above");
364
365                let value = I::from_nullable_sql(field.value())?;
366
367                Ok(NamedField {
368                    name: field
369                        .field_name()
370                        .ok_or("Try to load an unnamed field")?
371                        .to_owned(),
372                    value,
373                })
374            })
375            .collect::<deserialize::Result<Vec<_>>>()?;
376        Ok(DynamicRow { values: data })
377    }
378}
379
380#[cfg(feature = "postgres")]
381impl<I> QueryableByName<diesel::pg::Pg> for DynamicRow<NamedField<Option<I>>>
382where
383    I: FromSql<Any, diesel::pg::Pg>,
384{
385    fn build<'a>(row: &impl NamedRow<'a, diesel::pg::Pg>) -> deserialize::Result<Self> {
386        Self::from_nullable_row(row)
387    }
388}
389
390#[cfg(feature = "mysql")]
391impl<I> QueryableByName<diesel::mysql::Mysql> for DynamicRow<NamedField<Option<I>>>
392where
393    I: FromSql<Any, diesel::mysql::Mysql>,
394{
395    fn build<'a>(row: &impl NamedRow<'a, diesel::mysql::Mysql>) -> deserialize::Result<Self> {
396        Self::from_nullable_row(row)
397    }
398}
399
400#[cfg(feature = "sqlite")]
401impl<I> QueryableByName<diesel::sqlite::Sqlite> for DynamicRow<NamedField<Option<I>>>
402where
403    I: FromSql<Any, diesel::sqlite::Sqlite>,
404{
405    fn build<'a>(row: &impl NamedRow<'a, diesel::sqlite::Sqlite>) -> deserialize::Result<Self> {
406        Self::from_nullable_row(row)
407    }
408}
409
410impl<I> Index<usize> for DynamicRow<I> {
411    type Output = I;
412
413    fn index(&self, index: usize) -> &Self::Output {
414        &self.values[index]
415    }
416}
417
418impl<'a, I> Index<&'a str> for DynamicRow<NamedField<I>> {
419    type Output = I;
420
421    fn index(&self, field_name: &'a str) -> &Self::Output {
422        self.values
423            .iter()
424            .find(|f| f.name == field_name)
425            .map(|f| &f.value)
426            .expect("Field not found")
427    }
428}
429
430impl<'a, I> Index<&'a String> for DynamicRow<NamedField<I>> {
431    type Output = I;
432
433    fn index(&self, field_name: &'a String) -> &Self::Output {
434        self.index(field_name as &str)
435    }
436}
437
438impl<I> Index<String> for DynamicRow<NamedField<I>> {
439    type Output = I;
440
441    fn index(&self, field_name: String) -> &Self::Output {
442        self.index(&field_name)
443    }
444}
445
446impl<V> IntoIterator for DynamicRow<V> {
447    type Item = V;
448    type IntoIter = <Vec<V> as IntoIterator>::IntoIter;
449
450    fn into_iter(self) -> Self::IntoIter {
451        self.values.into_iter()
452    }
453}
454
455impl<'a, V> IntoIterator for &'a DynamicRow<V> {
456    type Item = &'a V;
457    type IntoIter = <&'a Vec<V> as IntoIterator>::IntoIter;
458
459    fn into_iter(self) -> Self::IntoIter {
460        self.values.iter()
461    }
462}