inline-postgres-impl 0.1.0

Implementation of the inline-postgres crate - do not use on its own
Documentation
use core::marker::PhantomData;

pub use async_trait::async_trait;
pub use schema::*;
pub use tokio_postgres::{self as postgres, *};

/// Re-exports traits such that the functions can be used without manually importing them.
pub mod prelude {
    pub use super::{Exec, Fetch, Table, TableHelper};
}

pub mod key;
pub mod schema;
pub mod untyped_key;

pub struct Statement<'a> {
    /// The SQL code with placeholders in the form of `$1`, `$2`, etc
    pub code: &'static str,
    /// The values being injected into the prepared statement
    pub vals: &'a [&'a (dyn types::ToSql + Sync)],
}

impl<'a> Statement<'a> {
    #[doc(hidden)]
    pub fn new(code: &'static str, vals: &'a [&'a (dyn types::ToSql + Sync)]) -> Self {
        Self { code, vals }
    }
}

/// The base type that is returned by the SQL query macros.
///
/// It is not intended for this type to be created manually.
pub struct Query<'a, O: From<Row>> {
    /// The SQL code with placeholders in the form of `$1`, `$2`, etc
    pub code: &'static str,
    /// The values being injected into the prepared statement
    pub vals: &'a [&'a (dyn types::ToSql + Sync)],
    /// Holding the generic argument to the output type.
    _phantom: PhantomData<O>,
}

impl<'a, O: From<Row> + Send + Sync> Query<'a, O> {
    #[doc(hidden)]
    /// Creates a new instance of a query.
    ///
    /// Do not call manually.
    pub fn new(code: &'static str, vals: &'a [&'a (dyn types::ToSql + Sync)]) -> Self {
        Self {
            code,
            vals,
            _phantom: PhantomData,
        }
    }

    /// executes this query on a client.
    pub async fn execute_on(self, client: &mut Client) -> Result<u64, Error> {
        client.execute(self.code, self.vals).await
    }
}

impl<'a, O: From<Row>> Query<'a, O> {
    #[doc(hidden)]
    pub fn casted<T: From<Row>>(self) -> Query<'a, T> {
        Query {
            code: self.code,
            vals: self.vals,
            _phantom: PhantomData,
        }
    }
}

#[async_trait]
/// Helper Trait to implement the `fetch` method on [`Client`](Client).
pub trait Fetch {
    /// fetches records from `self` and converts them to instances of `O`.
    async fn fetch<'a, O: From<row::Row> + Send + Sync>(
        &self,
        query: Query<'a, O>,
    ) -> Result<Vec<O>, Error>;
}
#[async_trait]
impl<C: GenericClient + Sync> Fetch for C {
    async fn fetch<'a, O: From<Row> + Send + Sync>(
        &self,
        query: Query<'a, O>,
    ) -> Result<Vec<O>, Error> {
        let res = self
            .query(query.code, query.vals)
            .await?
            .into_iter()
            .map(Into::into)
            .collect();
        Ok(res)
    }
}

#[async_trait]
/// Helper Trait to implement the `exec` method on [`Client`](Client).
pub trait Exec {
    /// executes `stmt` on `self`.
    ///
    /// returns the number of affected results
    async fn exec<'a>(&self, stmt: Statement<'a>) -> Result<u64, Error>;
}
#[async_trait]
impl<C: GenericClient + Sync> Exec for C {
    async fn exec<'a>(&self, stmt: Statement<'a>) -> Result<u64, Error> {
        self.execute(stmt.code, stmt.vals).await
    }
}

/// A where clause generated by [`iff!`]
pub struct WhereClause<'a> {
    /// The SQL code with placeholders in the form of `$1`, `$2`, etc
    pub clause: &'static str,
    /// The values being injected into the prepared statement
    pub vals: &'a [&'a (dyn types::ToSql + Sync)],
}

impl<'a> WhereClause<'a> {
    #[doc(hidden)]
    pub fn new(santa: &'static str, vals: &'a [&'a (dyn types::ToSql + Sync)]) -> Self {
        Self {
            clause: santa,
            vals,
        }
    }
}

#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct InsertOptions {
    pub on_conflict_do_nothing: bool,
    pub on_conflict_do_update: bool,
}

#[async_trait]
pub trait Table: From<Row> + Sync + Send {
    fn key(&self) -> key::Key<Self>;
    async fn fetch_by_key<C: postgres::GenericClient + Sync>(
        key: key::Key<Self>,
        client: &C,
    ) -> Result<Option<Self>, postgres::Error>;
    async fn fetch_where<C: postgres::GenericClient + Sync>(
        client: &C,
        where_clause: WhereClause<'_>,
    ) -> Result<Vec<Self>, postgres::Error>;
    async fn delete_by_key<C: postgres::GenericClient + Sync>(
        key: key::Key<Self>,
        client: &C,
    ) -> Result<u64, postgres::Error>;
    async fn delete<C: postgres::GenericClient + Sync>(
        &self,
        client: &C,
    ) -> Result<u64, postgres::Error> {
        Self::delete_by_key(self.key(), client).await
    }
    async fn insert<C: postgres::GenericClient + Sync>(
        &self,
        client: &C,
    ) -> Result<u64, postgres::Error>;
    async fn update<C: postgres::GenericClient + Sync>(
        &self,
        client: &C,
    ) -> Result<u64, postgres::Error>;
    async fn upsert<C: postgres::GenericClient + Sync>(
        &self,
        client: &C,
    ) -> Result<u64, postgres::Error>;
    async fn create_table<C: postgres::GenericClient + Sync>(
        client: &C,
    ) -> Result<(), postgres::Error>;
    async fn bulk_insert<C: postgres::GenericClient + Sync>(
        client: &C,
        values: &[Self],
    ) -> Result<(), postgres::Error> {
        Self::bulk_insert_opt(client, values, InsertOptions::default()).await
    }
    async fn bulk_insert_opt<C: postgres::GenericClient + Sync>(
        client: &C,
        values: &[Self],
        options: InsertOptions,
    ) -> Result<(), postgres::Error>;
}

#[async_trait]
pub trait TableHelper<T: 'static + Table>: Sync + Send {
    /// Delete the record associated to the given key
    async fn delete_by_key(&self, key: key::Key<T>) -> Result<u64, postgres::Error>;

    async fn delete(&self, record: T) -> Result<u64, postgres::Error> {
        self.delete_by_key(record.key()).await
    }
    async fn insert(&self, record: &T) -> Result<u64, postgres::Error>;
    async fn update(&self, record: &T) -> Result<u64, postgres::Error>;
    async fn upsert(&self, record: &T) -> Result<u64, postgres::Error>;

    /// Fetch records of the given table with a specific `WHERE` clause.
    ///
    /// # Example
    /// ```
    /// use inline_postgres as pg;
    /// use pg::{key::Key, prelude::*, Table, sql};
    ///
    /// fn main() -> Result<(), pg::Error> {
    ///
    ///     #[derive(Debug, Table)]
    ///     struct Celebrity {
    ///         id: Key<Self>,
    ///         first_name: String,
    ///         last_name: String,
    ///     }
    ///
    /// #   client.exec(sql! {
    /// #       CREATE TABLE IF NOT EXISTS CELEBRITY (
    /// #           ID UUID PRIMARY KEY,
    /// #           FIRST_NAME VARCHAR(100),
    /// #           LAST_NAME VARCHAR(100)
    /// #       )
    /// #   })?;
    ///     let mut client = pg::Client::connect("host=localhost, user=postgres", pg::NoTls)?;
    ///     let client = &mut client;
    ///
    ///     client.insert(Celebrity::new("Tony", "Hawk"))?;
    ///
    ///     let people: Vec<Celebrity> = client.fetch_where(sql!{ FIRST_NAME = "Tony" })?;
    ///
    ///     assert_eq!(people.len(), 1);
    ///     assert_eq!(people[0].first_name, "Tony");
    ///     assert_eq!(people[0].last_name, "Hawk");
    ///
    /// #   client.exec(sql! {
    /// #       DROP TABLE CELEBRITY
    /// #   })?;
    ///     Ok(())
    /// }
    /// ```
    async fn fetch_table(&self, where_clause: WhereClause<'_>) -> Result<Vec<T>, postgres::Error>;

    async fn bulk_insert(&self, records: &[T]) -> Result<(), postgres::Error>;
}

#[async_trait]
impl<T: 'static + Table, C: GenericClient + Send + Sync> TableHelper<T> for C {
    async fn delete_by_key(&self, key: key::Key<T>) -> Result<u64, postgres::Error> {
        key.delete(self).await
    }

    async fn insert(&self, record: &T) -> Result<u64, postgres::Error> {
        record.insert(self).await
    }

    async fn update(&self, record: &T) -> Result<u64, postgres::Error> {
        record.update(self).await
    }

    async fn upsert(&self, record: &T) -> Result<u64, postgres::Error> {
        record.upsert(self).await
    }

    async fn fetch_table(&self, where_clause: WhereClause<'_>) -> Result<Vec<T>, postgres::Error> {
        T::fetch_where(self, where_clause).await
    }

    async fn bulk_insert(&self, records: &[T]) -> Result<(), postgres::Error> {
        T::bulk_insert(self, records).await
    }
}

#[derive(Debug)]
pub struct Ref<T: Table + std::fmt::Debug> {
    /// the foreign key referencing the entry in the table `T`
    pub fk: untyped_key::Key,
    _phantom: PhantomData<T>,
}