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