Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rename Read trait functions for alignment #27

Merged
merged 1 commit into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
members = [".", "atmosphere-core", "atmosphere-macros"]

[workspace.package]
version = "0.2.0"
version = "0.3.0"
license = "Apache-2.0"
edition = "2021"
exclude = ["/.github", "/tests"]
Expand All @@ -16,8 +16,8 @@ repository = "https://github.com/helsing-ai/atmosphere"
keywords = ["sqlx", "postgres", "database", "orm", "backend"]

[workspace.dependencies]
atmosphere-core = { version = "=0.2.0", path = "atmosphere-core" }
atmosphere-macros = { version = "=0.2.0", path = "atmosphere-macros" }
atmosphere-core = { version = "=0.3.0", path = "atmosphere-core" }
atmosphere-macros = { version = "=0.3.0", path = "atmosphere-macros" }
async-trait = "0.1"
lazy_static = "1"
sqlx = { version = "0.7", features = ["chrono"] }
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

[![SQLx](https://img.shields.io/badge/sqlx-framework-blueviolet.svg)](https://github.com/launchbadge/sqlx)
[![Crate](https://img.shields.io/crates/v/atmosphere.svg)](https://crates.io/crates/atmosphere)
[![Book](https://img.shields.io/badge/book-latest-0f5225.svg)](https://bmc-labs.github.io/atmosphere)
[![Book](https://img.shields.io/badge/book-latest-0f5225.svg)](https://helsing-ai.github.io/atmosphere)
[![Docs](https://img.shields.io/badge/docs-latest-153f66.svg)](https://docs.rs/atmosphere)

</div>
Expand Down Expand Up @@ -70,7 +70,7 @@ async fn main() -> sqlx::Result<()> {
// Field Queries

assert_eq!(
User::find(&pool, &0).await?,
User::read(&pool, &0).await?,
User::find_by_email(&pool, "[email protected]").await?.unwrap()
);

Expand Down Expand Up @@ -167,14 +167,15 @@ Atmosphere is able to derive and generate the following queries:

#### `atmosphere::Read`

- `Model::find`
- `Model::find_all`
- `Model::read`: read a `Model` by its primary key, returning a `Model`.
- `Model::find`: find a `Model` by its primary key, returning an `Option<Model>`.
- `Model::read_all`: read all `Model`s, returning a `Vec<Model>`.
- `Model::reload`

#### `atmosphere::Update`

- `Model::update`
- `Model::save`
- `Model::upsert`

#### `atmosphere::Delete`

Expand All @@ -187,8 +188,8 @@ Each struct field that is marked with `#[sql(unique)]` becomes queryable.

In the above example `b` was marked as unique so atmosphere implements:

- `Model::find_by_b`
- `Model::delete_by_b`
- `Model::find_by_b`: find a `Model` by its `b` field, returning an `Option<Model>`.
- `Model::delete_by_b`: delete a `Model` by its `b` field.

### Relationships & Inter-Table Queries

Expand Down
8 changes: 4 additions & 4 deletions atmosphere-core/src/schema/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use sqlx::{database::HasArguments, Database, Executor, IntoArguments};

/// Trait for deleting rows from a database.
///
/// Provides functionality for deleting rows from a table in the database. Implementors of this trait can delete
/// entities either by their instance or by their primary key. The trait ensures proper execution of hooks at
/// various stages of the delete operation, enhancing flexibility and allowing for custom behavior during the
/// deletion process.
/// Provides functionality for deleting rows from a table in the database. Implementors of this
/// trait can delete entities either by their instance or by their primary key. The trait ensures
/// proper execution of hooks at various stages of the delete operation, enhancing flexibility and
/// allowing for custom behavior during the deletion process.
#[async_trait]
pub trait Delete: Table + Bind + Hooks + Send + Sync + Unpin + 'static {
/// Deletes the row represented by the instance from the database. Builds and executes a delete
Expand Down
56 changes: 28 additions & 28 deletions atmosphere-core/src/schema/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub trait Read: Table + Bind + Hooks + Send + Sync + Unpin + 'static {
/// Finds and retrieves a row by its primary key. This method constructs a query to fetch
/// a single row based on the primary key, executes it, and returns the result, optionally
/// triggering hooks before and after execution.
async fn find<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Self>
async fn read<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Self>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
Expand All @@ -28,15 +28,15 @@ pub trait Read: Table + Bind + Hooks + Send + Sync + Unpin + 'static {
/// Finds and retrieves a row by its primary key. This method constructs a query to fetch
/// a single row based on the primary key, executes it, and returns the result, optionally
/// triggering hooks before and after execution.
async fn find_optional<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Option<Self>>
async fn find<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Option<Self>>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
IntoArguments<'q, crate::Driver> + Send;

/// Retrieves all rows from the table. This method is useful for fetching the complete
/// dataset of a table, executing a query to return all rows, and applying hooks as needed.
async fn find_all<'e, E>(executor: E) -> Result<Vec<Self>>
async fn read_all<'e, E>(executor: E) -> Result<Vec<Self>>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
Expand All @@ -57,7 +57,7 @@ impl<T> Read for T
where
T: Table + Bind + Hooks + Send + Sync + Unpin + 'static,
{
async fn find<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Self>
async fn read<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Self>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
Expand Down Expand Up @@ -91,7 +91,7 @@ where
res
}

async fn find_optional<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Option<Self>>
async fn find<'e, E>(executor: E, pk: &Self::PrimaryKey) -> Result<Option<Self>>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
Expand Down Expand Up @@ -125,68 +125,68 @@ where
res
}

async fn reload<'e, E>(&mut self, executor: E) -> Result<()>
async fn read_all<'e, E>(executor: E) -> Result<Vec<Self>>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
IntoArguments<'q, crate::Driver> + Send,
{
let query = crate::runtime::sql::select_by::<T>(T::PRIMARY_KEY.as_col());

hooks::execute(HookStage::PreBind, &query, HookInput::Row(self)).await?;

let mut sql = sqlx::query_as(query.sql());

for c in query.bindings().columns() {
sql = self.bind(c, sql).unwrap();
}
let query = crate::runtime::sql::select_all::<T>();

hooks::execute(HookStage::PreBind, &query, HookInput::None).await?;
hooks::execute(HookStage::PreExec, &query, HookInput::None).await?;

let res = sql
let res = sqlx::query_as(query.sql())
.persistent(false)
.fetch_one(executor)
.fetch_all(executor)
.await
.map_err(QueryError::from)
.map_err(Error::Query);

hooks::execute(
hooks::HookStage::PostExec,
&query,
QueryResult::One(&res).into(),
QueryResult::Many(&res).into(),
)
.await?;

*self = res?;

Ok(())
res
}

async fn find_all<'e, E>(executor: E) -> Result<Vec<Self>>
async fn reload<'e, E>(&mut self, executor: E) -> Result<()>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
IntoArguments<'q, crate::Driver> + Send,
{
let query = crate::runtime::sql::select_all::<T>();
let query = crate::runtime::sql::select_by::<T>(T::PRIMARY_KEY.as_col());

hooks::execute(HookStage::PreBind, &query, HookInput::Row(self)).await?;

let mut sql = sqlx::query_as(query.sql());

for c in query.bindings().columns() {
sql = self.bind(c, sql).unwrap();
}

hooks::execute(HookStage::PreBind, &query, HookInput::None).await?;
hooks::execute(HookStage::PreExec, &query, HookInput::None).await?;

let res = sqlx::query_as(query.sql())
let res = sql
.persistent(false)
.fetch_all(executor)
.fetch_one(executor)
.await
.map_err(QueryError::from)
.map_err(Error::Query);

hooks::execute(
hooks::HookStage::PostExec,
&query,
QueryResult::Many(&res).into(),
QueryResult::One(&res).into(),
)
.await?;

res
*self = res?;

Ok(())
}
}
19 changes: 11 additions & 8 deletions atmosphere-core/src/schema/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use sqlx::{database::HasArguments, Database, Executor, IntoArguments};

/// Update rows in a database.
///
/// Provides functionality for updating data in tables within a SQL database. This trait defines asynchronous methods
/// for modifying existing rows in the database, either through direct updates or upserts (update or insert if not exists).
/// It ensures that hooks are executed at various stages, enabling custom logic to be integrated into the update process.
/// Provides functionality for updating data in tables within a SQL database. This trait defines
/// asynchronous methods for modifying existing rows in the database, either through direct updates
/// or upserts (update or insert if not exists). It ensures that hooks are executed at various
/// stages, enabling custom logic to be integrated into the update process.
#[async_trait]
pub trait Update: Table + Bind + Hooks + Send + Sync + Unpin + 'static {
/// Updates an existing row in the database. This method constructs an update query, binds the
Expand All @@ -27,10 +28,9 @@ pub trait Update: Table + Bind + Hooks + Send + Sync + Unpin + 'static {
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
IntoArguments<'q, crate::Driver> + Send;

/// Similar to `update`, but uses an upsert approach. It either updates an existing row or
/// inserts a new one if it does not exist, depending on the primary key's presence and
/// uniqueness.
async fn save<'e, E>(
/// Similar to `update`, but either updates an existing row or inserts a new one if it does not
/// exist, depending on the primary key's presence and uniqueness.
async fn upsert<'e, E>(
&mut self,
executor: E,
) -> Result<<crate::Driver as Database>::QueryResult>
Expand Down Expand Up @@ -83,7 +83,10 @@ where
res
}

async fn save<'e, E>(&mut self, executor: E) -> Result<<crate::Driver as Database>::QueryResult>
async fn upsert<'e, E>(
&mut self,
executor: E,
) -> Result<<crate::Driver as Database>::QueryResult>
where
E: Executor<'e, Database = crate::Driver>,
for<'q> <crate::Driver as HasArguments<'q>>::Arguments:
Expand Down
49 changes: 20 additions & 29 deletions atmosphere-core/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,18 @@ where
E: Entity + Clone + Debug + Eq + Send,
{
assert!(
E::find(pool, instance.pk()).await.is_err(),
"instance was found (find) before it was created"
E::read(pool, instance.pk()).await.is_err(),
"instance was found (read) before it was created"
);

assert!(
E::find_optional(pool, instance.pk())
.await
.unwrap()
.is_none(),
"instance was found (find_optional) before it was created"
E::find(pool, instance.pk()).await.unwrap().is_none(),
"instance was found (find) before it was created"
);

instance.create(pool).await.expect("insertion did not work");

let retrieved = E::find(pool, instance.pk())
let retrieved = E::read(pool, instance.pk())
.await
.expect("instance not found after insertion");

Expand All @@ -47,32 +44,29 @@ where
E: Entity + Clone + Debug + Eq + Send,
{
assert!(
E::find(pool, instance.pk()).await.is_err(),
"instance was found (find) after deletion"
E::read(pool, instance.pk()).await.is_err(),
"instance was found (read) after deletion"
);

assert!(
E::find_optional(pool, instance.pk())
.await
.unwrap()
.is_none(),
"instance was found (find_optional) after deletion"
E::find(pool, instance.pk()).await.unwrap().is_none(),
"instance was found (find) after deletion"
);

assert!(
E::find_all(pool).await.unwrap().is_empty(),
E::read_all(pool).await.unwrap().is_empty(),
"there was an instance found in the database before creating"
);

instance.create(pool).await.expect("insertion did not work");

let retrieved = E::find(pool, instance.pk())
let retrieved = E::read(pool, instance.pk())
.await
.expect("instance not found after insertion");

assert_eq!(instance, retrieved);

assert_eq!(E::find_all(pool).await.unwrap(), vec![instance.clone()]);
assert_eq!(E::read_all(pool).await.unwrap(), vec![instance.clone()]);
}

/// Tests updating of an entity in the database.
Expand All @@ -83,7 +77,7 @@ pub async fn update<E>(pool: &crate::Pool, mut instance: E, updates: Vec<E>)
where
E: Entity + Clone + Debug + Eq + Send,
{
instance.save(pool).await.expect("insertion did not work");
instance.upsert(pool).await.expect("insertion did not work");

for mut update in updates {
update
Expand All @@ -98,16 +92,16 @@ where

assert_eq!(instance, update);

let retrieved = E::find(pool, instance.pk())
let retrieved = E::read(pool, instance.pk())
.await
.expect("instance not found after update");

assert_eq!(instance, retrieved);

let retrieved = E::find_optional(pool, instance.pk())
let retrieved = E::find(pool, instance.pk())
.await
.unwrap()
.expect("instance not found (find_optional) after update");
.expect("instance not found (find) after update");

assert_eq!(instance, retrieved);
}
Expand All @@ -131,16 +125,13 @@ where
.expect_err("instance could be reloaded from db after deletion");

assert!(
E::find(pool, instance.pk()).await.is_err(),
"instance was found (find) after deletion"
E::read(pool, instance.pk()).await.is_err(),
"instance was found (read) after deletion"
);

assert!(
E::find_optional(pool, instance.pk())
.await
.unwrap()
.is_none(),
"instance was found (find_optional) after deletion"
E::find(pool, instance.pk()).await.unwrap().is_none(),
"instance was found (find) after deletion"
);

instance.create(pool).await.expect("insertion did not work");
Expand Down
5 changes: 4 additions & 1 deletion atmosphere-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ proc-macro = true
atmosphere-core.workspace = true
sqlx.workspace = true
proc-macro2 = { version = "1.0.36", default-features = false }
syn = { version = "2.0.39", default-features = false, features = ["parsing", "proc-macro"] }
syn = { version = "2.0.39", default-features = false, features = [
"parsing",
"proc-macro",
] }
quote = { version = "1.0.14", default-features = false }
lazy_static = "1.4.0"

Expand Down
Loading
Loading