diff --git a/pmrmodel/.sqlx/query-94fec7ace28a793797fb5e54dd4f7e1f5e4959d7d1a099557a5ea596fbd5dfbb.json b/pmrmodel/.sqlx/query-94fec7ace28a793797fb5e54dd4f7e1f5e4959d7d1a099557a5ea596fbd5dfbb.json new file mode 100644 index 0000000..7c0c38c --- /dev/null +++ b/pmrmodel/.sqlx/query-94fec7ace28a793797fb5e54dd4f7e1f5e4959d7d1a099557a5ea596fbd5dfbb.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\nUPDATE exposure_file\nSET default_view_id = ?2\nWHERE id = ?1\n AND ?2 IN (\n SELECT id\n FROM exposure_file_view\n WHERE exposure_file_id = ?1\n )\n", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "94fec7ace28a793797fb5e54dd4f7e1f5e4959d7d1a099557a5ea596fbd5dfbb" +} diff --git a/pmrmodel/.sqlx/query-dc6e6039b0ca12759926690613cda7eba525df2e45b3bc9c8698331dfe0bc261.json b/pmrmodel/.sqlx/query-dc6e6039b0ca12759926690613cda7eba525df2e45b3bc9c8698331dfe0bc261.json new file mode 100644 index 0000000..738d4e6 --- /dev/null +++ b/pmrmodel/.sqlx/query-dc6e6039b0ca12759926690613cda7eba525df2e45b3bc9c8698331dfe0bc261.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\nUPDATE exposure\nSET default_file_id = ?2\nWHERE id = ?1\n AND ?2 IN (\n SELECT id\n FROM exposure_file\n WHERE exposure_id = ?1\n )\n", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "dc6e6039b0ca12759926690613cda7eba525df2e45b3bc9c8698331dfe0bc261" +} diff --git a/pmrmodel/src/model/db/sqlite/exposure.rs b/pmrmodel/src/model/db/sqlite/exposure.rs index 7a3f904..626202a 100644 --- a/pmrmodel/src/model/db/sqlite/exposure.rs +++ b/pmrmodel/src/model/db/sqlite/exposure.rs @@ -111,6 +111,30 @@ WHERE workspace_id = ?1 Ok(rec.into()) } +async fn set_default_file_sqlite( + sqlite: &SqliteBackend, + id: i64, + file_id: i64, +) -> Result { + let rows_affected = sqlx::query!(r#" +UPDATE exposure +SET default_file_id = ?2 +WHERE id = ?1 + AND ?2 IN ( + SELECT id + FROM exposure_file + WHERE exposure_id = ?1 + ) +"#, + id, + file_id, + ) + .execute(&*sqlite.pool) + .await? + .rows_affected(); + Ok(rows_affected > 0) +} + #[async_trait] impl ExposureBackend for SqliteBackend { async fn insert( @@ -148,6 +172,18 @@ impl ExposureBackend for SqliteBackend { id, ).await } + + async fn set_default_file( + &self, + id: i64, + file_id: i64, + ) -> Result { + set_default_file_sqlite( + &self, + id, + file_id, + ).await + } } #[cfg(test)] diff --git a/pmrmodel/src/model/db/sqlite/exposure_file.rs b/pmrmodel/src/model/db/sqlite/exposure_file.rs index c3e1e52..41aa3ab 100644 --- a/pmrmodel/src/model/db/sqlite/exposure_file.rs +++ b/pmrmodel/src/model/db/sqlite/exposure_file.rs @@ -91,6 +91,31 @@ WHERE exposure_id = ?1 Ok(rec.into()) } + +async fn set_default_view_sqlite( + sqlite: &SqliteBackend, + id: i64, + view_id: i64, +) -> Result { + let rows_affected = sqlx::query!(r#" +UPDATE exposure_file +SET default_view_id = ?2 +WHERE id = ?1 + AND ?2 IN ( + SELECT id + FROM exposure_file_view + WHERE exposure_file_id = ?1 + ) +"#, + id, + view_id, + ) + .execute(&*sqlite.pool) + .await? + .rows_affected(); + Ok(rows_affected > 0) +} + #[async_trait] impl ExposureFileBackend for SqliteBackend { async fn insert( @@ -126,6 +151,18 @@ impl ExposureFileBackend for SqliteBackend { id, ).await } + + async fn set_default_view( + &self, + id: i64, + view_id: i64 + ) -> Result { + set_default_view_sqlite( + &self, + id, + view_id, + ).await + } } #[cfg(test)] @@ -133,6 +170,7 @@ pub(crate) mod testing { use pmrmodel_base::{ exposure::{ ExposureFile, + traits::ExposureBackend, traits::ExposureFileBackend, }, }; @@ -182,20 +220,21 @@ pub(crate) mod testing { } #[async_std::test] - async fn test_list_exposure_file() -> anyhow::Result<()> { + async fn test_using_exposure_files() -> anyhow::Result<()> { let backend = SqliteBackend::from_url("sqlite::memory:") .await? .run_migration_profile(Profile::Pmrapp) .await?; let efb: &dyn ExposureFileBackend = &backend; + let eb: &dyn ExposureBackend = &backend; let w1 = make_example_workspace(&backend).await?; let e1 = make_example_exposure(&backend, w1).await?; - let _ = make_example_exposure_file( + let ef0 = make_example_exposure_file( &backend, e1, "README.md").await?; let e2 = make_example_exposure(&backend, w1).await?; - make_example_exposure_file(&backend, e2, "README.md").await?; + let ef1 = make_example_exposure_file(&backend, e2, "README.md").await?; make_example_exposure_file(&backend, e2, "model.cellml").await?; make_example_exposure_file(&backend, e2, "lib/units.cellml").await?; let results = efb.list_for_exposure(e2).await?; @@ -207,6 +246,13 @@ pub(crate) mod testing { .collect::>(), ); + // Matching pairing of exposure id and file + assert!(eb.set_default_file(e1, ef0).await?); + assert!(eb.set_default_file(e2, ef1).await?); + // Mismatching pairing of exposure id and file + assert!(!eb.set_default_file(e2, ef0).await?); + assert!(!eb.set_default_file(e1, ef1).await?); + Ok(()) } diff --git a/pmrmodel/src/model/db/sqlite/exposure_file_view.rs b/pmrmodel/src/model/db/sqlite/exposure_file_view.rs index 09ef6cf..2c15a61 100644 --- a/pmrmodel/src/model/db/sqlite/exposure_file_view.rs +++ b/pmrmodel/src/model/db/sqlite/exposure_file_view.rs @@ -117,11 +117,15 @@ impl ExposureFileViewBackend for SqliteBackend { } } +// TODO generalize the testing modules across related modules (actually +// all db access) and instantiate the test of all db implementations +// against all relevant tests. #[cfg(test)] pub(crate) mod testing { use pmrmodel_base::{ exposure::{ ExposureFileView, + traits::ExposureFileBackend, traits::ExposureFileViewBackend, }, }; @@ -175,18 +179,21 @@ pub(crate) mod testing { } #[async_std::test] - async fn test_list_exposure_file_view() -> anyhow::Result<()> { + async fn test_using_exposure_file_view() -> anyhow::Result<()> { let backend = SqliteBackend::from_url("sqlite::memory:") .await? .run_migration_profile(Profile::Pmrapp) .await?; let efvb: &dyn ExposureFileViewBackend = &backend; + let efb: &dyn ExposureFileBackend = &backend; let w1 = make_example_workspace(&backend).await?; let _ = make_example_exposure(&backend, w1).await?; let e2 = make_example_exposure(&backend, w1).await?; let e2f1 = make_example_exposure_file(&backend, e2, "README.md").await?; - make_example_exposure_file_view(&backend, e2f1, "view").await?; + let e2f1v1 = make_example_exposure_file_view( + &backend, e2f1, "view").await?; + let e2f2 = make_example_exposure_file( &backend, e2, "model.cellml").await?; make_example_exposure_file_view(&backend, e2f2, "model").await?; @@ -209,6 +216,14 @@ pub(crate) mod testing { views, ); + // Matching pairing of exposure file and view + assert!(efb.set_default_view(e2f1, e2f1v1).await?); + assert!(efb.set_default_view(e2f2, 2).await?); + assert!(efb.set_default_view(e2f2, 3).await?); + // Mismatching pairing of exposure file and view + assert!(!efb.set_default_view(e2f1, 2).await?); + assert!(!efb.set_default_view(e2f2, e2f1v1).await?); + Ok(()) } diff --git a/pmrmodel_base/src/exposure/traits.rs b/pmrmodel_base/src/exposure/traits.rs index 2228cdd..e4b1f9e 100644 --- a/pmrmodel_base/src/exposure/traits.rs +++ b/pmrmodel_base/src/exposure/traits.rs @@ -13,6 +13,9 @@ use crate::{ #[async_trait] pub trait ExposureBackend { + /// Inserts a new `Exposure` entry. + /// + /// Returns the id of the inserted entry. async fn insert( &self, workspace_id: i64, @@ -20,45 +23,79 @@ pub trait ExposureBackend { commit_id: &str, default_file_id: Option, ) -> Result; + + /// Returns all `Exposures` for the given `workspace_id`. async fn list_for_workspace( &self, workspace_id: i64, ) -> Result; + + /// Returns the `Exposure` for the given `id`. async fn get_id( &self, id: i64, ) -> Result; + + /// For the given `Exposure` identified by its `id`, set the default + /// `ExposureFile` via its `id`. + async fn set_default_file( + &self, + id: i64, + file_id: i64, + ) -> Result; } #[async_trait] pub trait ExposureFileBackend { + /// Inserts a new `ExposureFile` entry. + /// + /// Returns the id of the inserted entry. async fn insert( &self, exposure_id: i64, workspace_file_path: &str, default_view_id: Option, ) -> Result; + + /// Returns all `ExposureFiles` for the given `exposure_id`. async fn list_for_exposure( &self, exposure_id: i64, ) -> Result; + + /// Returns the `ExposureFile` for the given `id`. async fn get_id( &self, id: i64, ) -> Result; + + /// For the given `ExposureFile` identified by its `id`, set the + /// default `ExposureFileView` via its `id`. + async fn set_default_view( + &self, + id: i64, + file_id: i64, + ) -> Result; } #[async_trait] pub trait ExposureFileViewBackend { + /// Inserts a new `ExposureFileView` entry. + /// + /// Returns the id of the inserted entry. async fn insert( &self, exposure_file_id: i64, view_key: &str, ) -> Result; + + /// Returns all `ExposureFileViews` for the given `exposure_file_id`. async fn list_for_exposure_file( &self, exposure_file_id: i64, ) -> Result; + + /// Returns the `ExposureFileView` for the given `id`. async fn get_id( &self, id: i64,