diff options
-rw-r--r-- | rust/src/api_helpers.rs | 50 | ||||
-rw-r--r-- | rust/src/database_entity_crud.rs | 152 |
2 files changed, 158 insertions, 44 deletions
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs index 925a6073..8ab9dcb3 100644 --- a/rust/src/api_helpers.rs +++ b/rust/src/api_helpers.rs @@ -1,18 +1,20 @@ use data_encoding::BASE32_NOPAD; use database_models::*; use database_schema::*; +use fatcat_api::models::*; use diesel; use diesel::prelude::*; use errors::*; use regex::Regex; use std::str::FromStr; use uuid::Uuid; +use database_entity_crud::EntityCrud; pub type DbConn = diesel::r2d2::PooledConnection<diesel::r2d2::ConnectionManager<diesel::PgConnection>>; /// This function should always be run within a transaction -pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result<Uuid> { +pub fn get_or_create_editgroup(editor_id: Uuid, conn: &DbConn) -> Result<Uuid> { // check for current active let ed_row: EditorRow = editor::table.find(editor_id).first(conn)?; if let Some(current) = ed_row.active_editgroup_id { @@ -30,7 +32,7 @@ pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result<U } /// This function should always be run within a transaction -pub fn accept_editgroup(editgroup_id: Uuid, conn: &PgConnection) -> Result<ChangelogRow> { +pub fn accept_editgroup(editgroup_id: Uuid, conn: &DbConn) -> Result<ChangelogRow> { // check that we haven't accepted already (in changelog) // NB: could leave this to a UNIQUE constraint let count: i64 = changelog::table @@ -41,41 +43,13 @@ pub fn accept_editgroup(editgroup_id: Uuid, conn: &PgConnection) -> Result<Chang return Err(ErrorKind::EditgroupAlreadyAccepted(uuid2fcid(&editgroup_id)).into()); } - // for each entity type... - //for entity in (container_edit, creator_edit, file_edit, release_edit, work_edit) { - /* - // This would be the clean and efficient way, but see: - // https://github.com/diesel-rs/diesel/issues/1478 - diesel::update(container_ident::table) - .inner_join(container_edit::table.on( - container_ident::id.eq(container_edit::ident_id) - )) - .filter(container_edit::editgroup_id.eq(editgroup_id)) - .values(( - container_ident::is_live.eq(true), - container_ident::rev_id.eq(container_edit::rev_id), - container_ident::redirect_id.eq(container_edit::redirect_id), - )) - .execute()?; - */ - - // Sketchy... but fast? Only a few queries per accept. - for entity in &["container", "creator", "file", "work", "release"] { - diesel::sql_query(format!( - " - UPDATE {entity}_ident - SET - is_live = true, - rev_id = {entity}_edit.rev_id, - redirect_id = {entity}_edit.redirect_id - FROM {entity}_edit - WHERE - {entity}_ident.id = {entity}_edit.ident_id - AND {entity}_edit.editgroup_id = $1", - entity = entity - )).bind::<diesel::sql_types::Uuid, _>(editgroup_id) - .execute(conn)?; - } + // copy edit columns to ident table + let eg_id = FatCatId::from_uuid(&editgroup_id); + ContainerEntity::db_accept_edits(conn, eg_id)?; + CreatorEntity::db_accept_edits(conn, eg_id)?; + FileEntity::db_accept_edits(conn, eg_id)?; + ReleaseEntity::db_accept_edits(conn, eg_id)?; + WorkEntity::db_accept_edits(conn, eg_id)?; // append log/changelog row let entry: ChangelogRow = diesel::insert_into(changelog::table) @@ -91,7 +65,7 @@ pub fn accept_editgroup(editgroup_id: Uuid, conn: &PgConnection) -> Result<Chang Ok(entry) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct FatCatId(Uuid); impl ToString for FatCatId { diff --git a/rust/src/database_entity_crud.rs b/rust/src/database_entity_crud.rs index 88c89e84..01323637 100644 --- a/rust/src/database_entity_crud.rs +++ b/rust/src/database_entity_crud.rs @@ -3,7 +3,7 @@ use chrono; use database_models::*; use database_schema::*; use diesel::prelude::*; -use diesel::insert_into; +use diesel::{self, insert_into}; use errors::*; use fatcat_api::models::*; use serde_json; @@ -51,9 +51,19 @@ where fn parse_editgroup_id(&self) -> Result<Option<FatCatId>>; // Generic Methods - fn db_get(conn: &DbConn, ident: FatCatId) -> Result<Self>; - fn db_get_rev(conn: &DbConn, rev_id: Uuid) -> Result<Self>; - fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow>; + fn db_get( + conn: &DbConn, + ident: FatCatId + ) -> Result<Self>; + fn db_get_rev( + conn: &DbConn, + rev_id: Uuid + ) -> Result<Self>; + fn db_create( + &self, + conn: &DbConn, + edit_context: &EditContext + ) -> Result<Self::EditRow>; fn db_create_batch( conn: &DbConn, edit_context: &EditContext, @@ -75,6 +85,10 @@ where ident: FatCatId, limit: Option<i64>, ) -> Result<Vec<EntityHistoryEntry>>; + fn db_accept_edits( + conn: &DbConn, + editgroup_id: FatCatId + ) -> Result<u64>; // Entity-specific Methods fn db_from_row( @@ -82,8 +96,14 @@ where rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>, ) -> Result<Self>; - fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid>; - fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>>; + fn db_insert_rev( + &self, + conn: &DbConn + ) -> Result<Uuid>; + fn db_insert_revs( + conn: &DbConn, + models: &[&Self] + ) -> Result<Vec<Uuid>>; } // TODO: this could be a separate trait on all entities @@ -276,6 +296,116 @@ macro_rules! generic_db_get_history { }; } +/* +// This would be the clean and efficient way, but see: +// https://github.com/diesel-rs/diesel/issues/1478 +// + diesel::update(container_ident::table) + .inner_join(container_edit::table.on( + container_ident::id.eq(container_edit::ident_id) + )) + .filter(container_edit::editgroup_id.eq(editgroup_id)) + .values(( + container_ident::is_live.eq(true), + container_ident::rev_id.eq(container_edit::rev_id), + container_ident::redirect_id.eq(container_edit::redirect_id), + )) + .execute()?; + +// Was previously: + + for entity in &["container", "creator", "file", "work", "release"] { + diesel::sql_query(format!( + " + UPDATE {entity}_ident + SET + is_live = true, + rev_id = {entity}_edit.rev_id, + redirect_id = {entity}_edit.redirect_id + FROM {entity}_edit + WHERE + {entity}_ident.id = {entity}_edit.ident_id + AND {entity}_edit.editgroup_id = $1", + entity = entity + )).bind::<diesel::sql_types::Uuid, _>(editgroup_id) + .execute(conn)?; +*/ + +// UPDATE FROM version: single query for many rows +// Works with Postgres, not Cockroach +macro_rules! generic_db_accept_edits_batch { + ($entity_name_str:expr) => { + fn db_accept_edits( + conn: &DbConn, + editgroup_id: FatCatId, + ) -> Result<u64> { + + let count = diesel::sql_query(format!( + " + UPDATE {entity}_ident + SET + is_live = true, + rev_id = {entity}_edit.rev_id, + redirect_id = {entity}_edit.redirect_id + FROM {entity}_edit + WHERE + {entity}_ident.id = {entity}_edit.ident_id + AND {entity}_edit.editgroup_id = $1", + entity = $entity_name_str + )).bind::<diesel::sql_types::Uuid, _>(editgroup_id.to_uuid()) + .execute(conn)?; + Ok(count as u64) + } + } +} + +// UPDATE ROW version: single query per row +// CockroachDB version (slow, single query per row) +macro_rules! generic_db_accept_edits_each { + ($ident_table:ident, $edit_table:ident) => { + fn db_accept_edits( + conn: &DbConn, + editgroup_id: FatCatId, + ) -> Result<u64> { + + // 1. select edit rows (in sql) + let edit_rows: Vec<Self::EditRow> = $edit_table::table + .filter($edit_table::editgroup_id.eq(&editgroup_id.to_uuid())) + .get_results(conn)?; + // 2. create ident rows (in rust) + let ident_rows: Vec<Self::IdentRow> = edit_rows + .iter() + .map(|edit| + Self::IdentRow { + id: edit.ident_id, + is_live: true, + rev_id: edit.rev_id, + redirect_id: edit.redirect_id, + + } + ) + .collect(); + /* + // 3. upsert ident rows (in sql) + let count: u64 = diesel::insert_into($ident_table::table) + .values(ident_rows) + .on_conflict() + .do_update() + .set(ident_rows) + .execute(conn)?; + */ + // 3. update every row individually + let count = ident_rows.len() as u64; + for row in ident_rows { + diesel::update(&row) + .set(&row) + .execute(conn)?; + } + Ok(count) + } + }; +} + macro_rules! generic_db_insert_rev { () => { fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid> { @@ -299,6 +429,8 @@ impl EntityCrud for ContainerEntity { generic_db_update!(container_ident, container_edit); generic_db_delete!(container_ident, container_edit); generic_db_get_history!(container_edit); + generic_db_accept_edits_batch!("container"); + //generic_db_accept_edits_each!(container_ident, container_edit); generic_db_insert_rev!(); fn db_from_row( @@ -378,6 +510,8 @@ impl EntityCrud for CreatorEntity { generic_db_update!(creator_ident, creator_edit); generic_db_delete!(creator_ident, creator_edit); generic_db_get_history!(creator_edit); + generic_db_accept_edits_batch!("creator"); + //generic_db_accept_edits_each!(creator_ident, creator_edit); generic_db_insert_rev!(); fn db_from_row( @@ -454,6 +588,8 @@ impl EntityCrud for FileEntity { generic_db_update!(file_ident, file_edit); generic_db_delete!(file_ident, file_edit); generic_db_get_history!(file_edit); + generic_db_accept_edits_batch!("file"); + //generic_db_accept_edits_each!(file_ident, file_edit); generic_db_insert_rev!(); fn db_from_row( @@ -590,6 +726,8 @@ impl EntityCrud for ReleaseEntity { generic_db_update!(release_ident, release_edit); generic_db_delete!(release_ident, release_edit); generic_db_get_history!(release_edit); + generic_db_accept_edits_batch!("release"); + //generic_db_accept_edits_each!(release_ident, release_edit); generic_db_insert_rev!(); fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> { @@ -962,6 +1100,8 @@ impl EntityCrud for WorkEntity { generic_db_update!(work_ident, work_edit); generic_db_delete!(work_ident, work_edit); generic_db_get_history!(work_edit); + generic_db_accept_edits_batch!("work"); + //generic_db_accept_edits_each!(work_ident, work_edit); generic_db_insert_rev!(); fn db_from_row( |