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}