aykroyd 0.3.1

Zero-overhead ergonomic data access for Rust.
Documentation
//! Traits and structs for handling result rows.

use crate::client::{Client, FromColumnIndexed, FromColumnNamed};
use crate::error::Error;

/// The columns of a result row by index.
#[derive(Debug)]
pub struct ColumnsIndexed<'a, 'b, C: Client> {
    row: &'a C::Row<'b>,
    offset: usize,
}

impl<'a, 'b, C: Client> ColumnsIndexed<'a, 'b, C> {
    /// Wrap the database client's row.
    pub fn new(row: &'a C::Row<'b>) -> Self {
        ColumnsIndexed { row, offset: 0 }
    }

    /// Get the mapped value of a column by index.
    pub fn get<T>(&self, index: usize) -> Result<T, Error<C::Error>>
    where
        T: FromColumnIndexed<C>,
    {
        FromColumnIndexed::from_column(self.row, self.offset + index)
    }

    /// Get a nested value mapped from columns starting at the given offset.
    pub fn get_nested<T>(&self, offset: usize) -> Result<T, Error<C::Error>>
    where
        T: FromColumnsIndexed<C>,
    {
        FromColumnsIndexed::from_columns(self.child(offset))
    }

    fn child(&self, offset: usize) -> Self {
        let offset = self.offset + offset;
        ColumnsIndexed {
            row: self.row,
            offset,
        }
    }
}

/// The columns of a result row by name.
#[derive(Debug)]
pub struct ColumnsNamed<'a, 'b, C: Client> {
    row: &'a C::Row<'b>,
    prefix: String,
}

impl<'a, 'b, C: Client> ColumnsNamed<'a, 'b, C> {
    /// Wrap the database client's row.
    pub fn new(row: &'a C::Row<'b>) -> Self {
        ColumnsNamed {
            row,
            prefix: String::new(),
        }
    }

    /// Get the mapped value of a column by name.
    pub fn get<T>(&self, name: &str) -> Result<T, Error<C::Error>>
    where
        T: FromColumnNamed<C>,
    {
        let name = {
            let mut s = self.prefix.clone();
            s.push_str(name);
            s
        };
        FromColumnNamed::from_column(self.row, name.as_ref())
    }

    /// Get a nested value mapped from columns with the given prefix.
    pub fn get_nested<T>(&self, prefix: &str) -> Result<T, Error<C::Error>>
    where
        T: FromColumnsNamed<C>,
    {
        FromColumnsNamed::from_columns(self.child(prefix))
    }

    fn child(&self, prefix: &str) -> Self {
        let prefix = {
            let mut s = self.prefix.clone();
            s.push_str(prefix);
            s
        };
        ColumnsNamed {
            row: self.row,
            prefix,
        }
    }
}

/// A type that can be produced from a result row by column index.
///
/// This is automatically generated by the [`FromRow`](crate::FromRow)
/// derive macro when the type is a tuple struct, it has the
/// attribute `#[aykroyd(by_index)]`, or the fields are annotated
/// with numeric `#[aykroyd(column)]` attributes.
///
/// You can also implement this additionally for any struct that
/// already implements [`FromColumnsNamed`] (perhaps through the
/// `FromRow` derive).  This is useful in cases where you want
/// to use this struct as part of queries that use both strategies.
///
/// When deriving this trait, struct fields will be matched up to
/// database columns in **source order**.  Field values are generally
/// loaded with the [`FromColumnIndexed`] implementation, however
/// by using the `#[aykroyd(nested)]` attribute you can load another
/// type that implements `FromColumnsIndexed`.
#[cfg_attr(
    feature = "derive",
    doc = r##"

```
# use aykroyd::{FromRow, Query};
# use aykroyd::row::FromColumnsIndexed;
# struct Color;
#[derive(FromColumnsIndexed)]
struct Person {
    name: String,
    favorite_color: Color,
}

#[derive(FromRow)]
#[aykroyd(by_index)]
struct Pet {
    name: String,
    #[aykroyd(nested)]
    owner: Person,
}

#[derive(Query)]
#[aykroyd(row(Pet), text = "
    SELECT pet.name, owner.name, owner.fav_color FROM pets
")]
struct GetPets;
```
"##
)]
pub trait FromColumnsIndexed<C: Client>: Sized {
    const NUM_COLUMNS: usize;
    fn from_columns(columns: ColumnsIndexed<C>) -> Result<Self, Error<C::Error>>;
}

impl<C: Client, T: FromColumnsIndexed<C>> FromColumnsIndexed<C> for Option<T> {
    const NUM_COLUMNS: usize = T::NUM_COLUMNS;
    fn from_columns(columns: ColumnsIndexed<C>) -> Result<Self, Error<C::Error>> {
        T::from_columns(columns).map(Some).or(Ok(None)) // TODO: this is terrible!
    }
}

/// A type that can be produced from a result row by column name.
///
/// This is automatically generated by the [`FromRow`](crate::FromRow)
/// derive macro when type is a struct with named fields, it has the
/// attribute `#[aykroyd(by_name)]`, or the fields are annotated
/// with string `#[aykroyd(column)]` attributes.
///
/// You can also implement this additionally for any struct that
/// already implements [`FromColumnsIndexed`] (perhaps through the
/// `FromRow` derive).  This is useful in cases where you want
/// to use this struct as part of queries that use both strategies.
///
/// When deriving this trait, struct fields will be matched up to
/// database columns by **source name**.  Field values are generally
/// loaded with the [`FromColumnNamed`] implementation, however
/// by using the `#[aykroyd(nested)]` attribute you can load another
/// type that implements `FromColumnsNamed`.
#[cfg_attr(
    feature = "derive",
    doc = r##"

```
# use aykroyd::{FromRow, Query};
# use aykroyd::row::FromColumnsNamed;
# struct Color;
#[derive(FromColumnsNamed)]
struct Person {
    name: String,
    favorite_color: Color,
}

#[derive(FromRow)]
struct Pet {
    name: String,
    #[aykroyd(nested)]
    owner: Person,
}

#[derive(Query)]
#[aykroyd(row(Pet), text = "
    SELECT pet.name, owner.name AS owner_name, owner.fav_color AS owner_favorite_color FROM pets
")]
struct GetPets;
```
"##
)]
pub trait FromColumnsNamed<C: Client>: Sized {
    fn from_columns(columns: ColumnsNamed<C>) -> Result<Self, Error<C::Error>>;
}

#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use aykroyd_derive::{FromColumnsIndexed, FromColumnsNamed};

macro_rules! impl_tuple_from_columns_indexed {
    (
        $num_columns:literal $(:)?
        $(
            $name:ident $index:literal
        ),*
        $(,)?
    ) => {
        impl<
            C,
            $(
                $name,
            )*
        > FromColumnsIndexed<C> for ($($name,)*)
        where
            C: Client,
            $(
                $name: FromColumnIndexed<C>,
            )*
        {
            const NUM_COLUMNS: usize = $num_columns;

            #[allow(unused_variables)]
            fn from_columns(
                columns: ColumnsIndexed<C>,
            ) -> Result<Self, Error<C::Error>> {
                Ok(($(
                    columns.get($index)?,
                )*))
            }
        }
    };
}

impl_tuple_from_columns_indexed!(0);
impl_tuple_from_columns_indexed!(1: T0 0);
impl_tuple_from_columns_indexed!(2: T0 0, T1 1);
impl_tuple_from_columns_indexed!(3: T0 0, T1 1, T2 2);
impl_tuple_from_columns_indexed!(4: T0 0, T1 1, T2 2, T3 3);
impl_tuple_from_columns_indexed!(5: T0 0, T1 1, T2 2, T3 3, T4 4);
impl_tuple_from_columns_indexed!(6: T0 0, T1 1, T2 2, T3 3, T4 4, T5 5);
impl_tuple_from_columns_indexed!(7: T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6);
impl_tuple_from_columns_indexed!(8: T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7);

#[cfg(test)]
mod test {
    use super::*;
    use crate::test::sync_client::{self, TestClient};

    #[test]
    fn columns_indexed_get() {
        fn test(columns: &ColumnsIndexed<TestClient>, index: usize, expected: &str) {
            let actual: String = columns.get(index).unwrap();
            assert_eq!(expected, actual);
        }

        let mut client = TestClient::new();
        let row = client.row(sync_client::RowInner {
            names: vec!["name".into(), "age".into(), "superpower".into()],
            values: vec!["Hermes".into(), "42".into(), "Filing".into()],
        });
        let columns = ColumnsIndexed::new(&row);

        test(&columns, 0, "Hermes");
        test(&columns, 1, "42");
        test(&columns, 2, "Filing");
    }

    #[test]
    fn columns_indexed_get_nested() {
        #[derive(PartialEq, Eq, Debug)]
        struct Nested(String, String, String);
        impl FromColumnsIndexed<TestClient> for Nested {
            const NUM_COLUMNS: usize = 3;

            fn from_columns(columns: ColumnsIndexed<TestClient>) -> sync_client::Result<Self> {
                Ok(Nested(columns.get(0)?, columns.get(1)?, columns.get(2)?))
            }
        }

        fn test(columns: &ColumnsIndexed<TestClient>, expected: Nested) {
            let actual: Nested = columns.get_nested(1).unwrap();
            assert_eq!(expected, actual);
        }

        let mut client = TestClient::new();
        let row = client.row(sync_client::RowInner {
            names: vec![
                "something_else".into(),
                "character_name".into(),
                "character_age".into(),
                "character_superpower".into(),
            ],
            values: vec![
                "Hello".into(),
                "Hermes".into(),
                "42".into(),
                "Filing".into(),
            ],
        });
        let columns = ColumnsIndexed::new(&row);

        test(
            &columns,
            Nested("Hermes".into(), "42".into(), "Filing".into()),
        );
    }

    #[test]
    fn columns_named_get() {
        fn test(columns: &ColumnsNamed<TestClient>, name: &str, expected: &str) {
            let actual: String = columns.get(name).unwrap();
            assert_eq!(expected, actual);
        }

        let mut client = TestClient::new();
        let row = client.row(sync_client::RowInner {
            names: vec!["name".into(), "age".into(), "superpower".into()],
            values: vec!["Hermes".into(), "42".into(), "Filing".into()],
        });
        let columns = ColumnsNamed::new(&row);

        test(&columns, "name", "Hermes");
        test(&columns, "age", "42");
        test(&columns, "superpower", "Filing");
    }

    #[test]
    fn columns_named_get_nested() {
        #[derive(PartialEq, Eq, Debug)]
        struct Nested(String, String, String);
        impl FromColumnsNamed<TestClient> for Nested {
            fn from_columns(columns: ColumnsNamed<TestClient>) -> sync_client::Result<Self> {
                Ok(Nested(
                    columns.get("name")?,
                    columns.get("age")?,
                    columns.get("superpower")?,
                ))
            }
        }

        fn test(columns: &ColumnsNamed<TestClient>, expected: Nested) {
            let actual: Nested = columns.get_nested("character_").unwrap();
            assert_eq!(expected, actual);
        }

        let mut client = TestClient::new();
        let row = client.row(sync_client::RowInner {
            names: vec![
                "something_else".into(),
                "character_name".into(),
                "character_age".into(),
                "character_superpower".into(),
            ],
            values: vec![
                "Hello".into(),
                "Hermes".into(),
                "42".into(),
                "Filing".into(),
            ],
        });
        let columns = ColumnsNamed::new(&row);

        test(
            &columns,
            Nested("Hermes".into(), "42".into(), "Filing".into()),
        );
    }
}