From d17ab2dda6cc274eec55d23794375b0143a41256 Mon Sep 17 00:00:00 2001 From: Mara Schulke Date: Sat, 4 Nov 2023 17:07:55 +0100 Subject: [PATCH] Implement dynamic binding for various query impls --- atmosphere-core/src/bind.rs | 38 ++++++++++-- atmosphere-core/src/lib.rs | 2 +- atmosphere-core/src/schema.rs | 84 ++++++++++++++------------- atmosphere-macros/src/schema/table.rs | 17 ++++-- 4 files changed, 91 insertions(+), 50 deletions(-) diff --git a/atmosphere-core/src/bind.rs b/atmosphere-core/src/bind.rs index c60cb3f..1689eed 100644 --- a/atmosphere-core/src/bind.rs +++ b/atmosphere-core/src/bind.rs @@ -1,18 +1,46 @@ use crate::{Column, Result, Table}; +use sqlx::query::QueryAs; +use sqlx::Encode; +use sqlx::Type; use sqlx::{database::HasArguments, Database}; type Query<'q, DB> = sqlx::query::Query<'q, DB, >::Arguments>; +pub trait Bindable<'q, DB> +where + DB: Database + for<'a> HasArguments<'a>, +{ + fn dyn_bind + Type>(self, value: T) -> Self; +} + +impl<'q, DB> Bindable<'q, DB> for Query<'q, DB> +where + DB: Database + for<'a> HasArguments<'a>, +{ + fn dyn_bind + Type>(self, value: T) -> Self { + self.bind(value) + } +} + +impl<'q, E, DB> Bindable<'q, DB> for QueryAs<'q, DB, E, >::Arguments> +where + DB: Database + for<'a> HasArguments<'a>, +{ + fn dyn_bind + Type>(self, value: T) -> Self { + self.bind(value) + } +} + /// Bind columns to SQL Queries pub trait Bind: Table where DB: Database, { /// Bind a single column to the query - fn bind<'q>(&'q self, c: &'q Column, query: Query<'q, DB>) -> Result>; + fn bind<'q, Q: Bindable<'q, DB>>(&'q self, c: &'q Column, query: Q) -> Result; /// Bind a all columns to the query - fn bind_all<'q>(&'q self, mut query: Query<'q, DB>) -> Result> { + fn bind_all<'q, Q: Bindable<'q, DB>>(&'q self, mut query: Q) -> Result { query = self.bind_primary_key(query)?; query = self.bind_foreign_keys(query)?; query = self.bind_data(query)?; @@ -21,12 +49,12 @@ where } /// Bind the primary key column to the query - fn bind_primary_key<'q>(&'q self, query: Query<'q, DB>) -> Result> { + fn bind_primary_key<'q, Q: Bindable<'q, DB>>(&'q self, query: Q) -> Result { self.bind(&Self::PRIMARY_KEY, query) } /// Bind the foreign keys columns to the query - fn bind_foreign_keys<'q>(&'q self, mut query: Query<'q, DB>) -> Result> { + fn bind_foreign_keys<'q, Q: Bindable<'q, DB>>(&'q self, mut query: Q) -> Result { for ref fk in Self::FOREIGN_KEYS { query = self.bind(fk, query)?; } @@ -35,7 +63,7 @@ where } /// Bind the data columns to the query - fn bind_data<'q>(&'q self, mut query: Query<'q, DB>) -> Result> { + fn bind_data<'q, Q: Bindable<'q, DB>>(&'q self, mut query: Q) -> Result { for ref data in Self::DATA { query = self.bind(data, query)?; } diff --git a/atmosphere-core/src/lib.rs b/atmosphere-core/src/lib.rs index 724076c..7b53cc4 100644 --- a/atmosphere-core/src/lib.rs +++ b/atmosphere-core/src/lib.rs @@ -7,5 +7,5 @@ pub mod schema; /// Automated testing of SQL interactions pub mod testing; -pub use bind::Bind; +pub use bind::*; pub use schema::*; diff --git a/atmosphere-core/src/schema.rs b/atmosphere-core/src/schema.rs index b93055e..9869866 100644 --- a/atmosphere-core/src/schema.rs +++ b/atmosphere-core/src/schema.rs @@ -62,11 +62,19 @@ where } #[async_trait] -pub trait Read: Table { +pub trait Read: Table + Send + Sync + Unpin + 'static { /// Find a row by its primary key async fn find<'e, E>(pk: &Self::PrimaryKey, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static + Unpin, + Self: Bind, + E: sqlx::Executor<'e, Database = sqlx::Postgres>, + for<'q> >::Arguments: + Send + sqlx::IntoArguments<'q, sqlx::Postgres>; + + /// Reload from database + async fn reload<'e, E>(&mut self, executor: E) -> sqlx::Result<()> + where + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>; @@ -87,35 +95,48 @@ pub trait Read: Table { #[async_trait] impl Read for T where - T: Table, + T: Table + Send + Sync + Unpin + 'static, { async fn find<'e, E>(pk: &Self::PrimaryKey, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static + Unpin, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>, { let query = crate::runtime::sql::SQL::::select(); - Ok(sqlx::query_as::(&query.into_sql()) + sqlx::query_as::(&query.into_sql()) .bind(pk) .fetch_one(executor) .await - .unwrap()) + } + + async fn reload<'e, E>(&mut self, executor: E) -> sqlx::Result<()> + where + Self: Bind + Sync + Unpin + 'static, + E: sqlx::Executor<'e, Database = sqlx::Postgres>, + for<'q> >::Arguments: + Send + sqlx::IntoArguments<'q, sqlx::Postgres>, + { + let sql = crate::runtime::sql::SQL::::select().into_sql(); + + let query = sqlx::query_as::(&sql); + + let new = self + .bind_primary_key(query) + .unwrap() + .fetch_one(executor) + .await?; + + *self = new; + + Ok(()) } } #[async_trait] -pub trait Update: Table { - // Reload from database - //async fn reload<'e, E>(&mut self, executor: E) -> sqlx::Result<()> - //where - //Self: Bind + Sync + 'static, - //E: sqlx::Executor<'e, Database = sqlx::Postgres>, - //for<'q> >::Arguments: - //Send + sqlx::IntoArguments<'q, sqlx::Postgres>; - +pub trait Update: Table + Send + Sync + Unpin + 'static { /// Update the row in the database async fn update<'e, E>(&mut self, executor: E) -> sqlx::Result where @@ -136,26 +157,11 @@ pub trait Update: Table { #[async_trait] impl Update for T where - T: Table, + T: Table + Send + Sync + Unpin + 'static, { - //async fn reload<'e, E>(&self, executor: E) -> Result - //where - //Self: Bind + Sync + 'static, - //E: sqlx::Executor<'e, Database = sqlx::Postgres>, - //for<'q> >::Arguments: - //Send + sqlx::IntoArguments<'q, sqlx::Postgres>, - //{ - //let query = crate::runtime::sql::SQL::::delete(); - - //self.bind_all(sqlx::query::(&query.into_sql()))? - //.execute(executor) - //.await - //.map_err(|_| ()) - //} - async fn update<'e, E>(&mut self, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>, @@ -168,7 +174,7 @@ where async fn save<'e, E>(&mut self, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>, @@ -181,11 +187,11 @@ where } #[async_trait] -pub trait Delete: Table { +pub trait Delete: Table + Send + Sync + Unpin + 'static { /// Delete row in database async fn delete<'e, E>(&self, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>; @@ -193,7 +199,7 @@ pub trait Delete: Table { /// Delete row in database by primary key async fn delete_by<'e, E>(pk: &Self::PrimaryKey, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>; @@ -210,11 +216,11 @@ pub trait Delete: Table { #[async_trait] impl Delete for T where - T: Table, + T: Table + Send + Sync + Unpin + 'static, { async fn delete<'e, E>(&self, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>, @@ -229,7 +235,7 @@ where async fn delete_by<'e, E>(pk: &Self::PrimaryKey, executor: E) -> sqlx::Result where - Self: Bind + Sync + 'static, + Self: Bind, E: sqlx::Executor<'e, Database = sqlx::Postgres>, for<'q> >::Arguments: Send + sqlx::IntoArguments<'q, sqlx::Postgres>, diff --git a/atmosphere-macros/src/schema/table.rs b/atmosphere-macros/src/schema/table.rs index 26408c2..82c9f23 100644 --- a/atmosphere-macros/src/schema/table.rs +++ b/atmosphere-macros/src/schema/table.rs @@ -123,7 +123,9 @@ impl Table { quote!( if #col.name == Self::PRIMARY_KEY.name { - return Ok(#query.bind(&self.#name)); + use ::atmosphere_core::Bindable; + + return Ok(#query.dyn_bind(&self.#name)); } ) }; @@ -137,7 +139,9 @@ impl Table { stream.extend(quote!( if #col.name == #name { - return Ok(#query.bind(&self.#ident)); + use ::atmosphere_core::Bindable; + + return Ok(#query.dyn_bind(&self.#ident)); } )); } @@ -154,7 +158,9 @@ impl Table { stream.extend(quote!( if #col.name == #name { - return Ok(#query.bind(&self.#ident)); + use ::atmosphere_core::Bindable; + + return Ok(#query.dyn_bind(&self.#ident)); } )); } @@ -168,11 +174,12 @@ impl Table { impl ::atmosphere::Bind<#databases> for #ident { fn bind< 'q, + Q: ::atmosphere::Bindable<'q, #databases> >( &'q self, #col: &'q ::atmosphere::Column, - #query: ::sqlx::query::Query<'q, #databases, <#databases as ::sqlx::database::HasArguments<'q>>::Arguments>, - ) -> ::atmosphere::Result<::sqlx::query::Query<'q, #databases, <#databases as ::sqlx::database::HasArguments<'q>>::Arguments>> { + #query: Q + ) -> ::atmosphere::Result { #primary_key_bind #foreign_key_binds #data_binds