From da10d0e0cebc1142852b959cea93c8b80801d565 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Sun, 9 Sep 2018 12:26:37 -0700 Subject: generic edit accept, and per-row variant The per-row variant is for use with cockroach. --- rust/src/api_helpers.rs | 50 ++++--------- 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>; /// This function should always be run within a transaction -pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result { +pub fn get_or_create_editgroup(editor_id: Uuid, conn: &DbConn) -> Result { // 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 Result { +pub fn accept_editgroup(editgroup_id: Uuid, conn: &DbConn) -> Result { // 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(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 Result>; // Generic Methods - fn db_get(conn: &DbConn, ident: FatCatId) -> Result; - fn db_get_rev(conn: &DbConn, rev_id: Uuid) -> Result; - fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result; + fn db_get( + conn: &DbConn, + ident: FatCatId + ) -> Result; + fn db_get_rev( + conn: &DbConn, + rev_id: Uuid + ) -> Result; + fn db_create( + &self, + conn: &DbConn, + edit_context: &EditContext + ) -> Result; fn db_create_batch( conn: &DbConn, edit_context: &EditContext, @@ -75,6 +85,10 @@ where ident: FatCatId, limit: Option, ) -> Result>; + fn db_accept_edits( + conn: &DbConn, + editgroup_id: FatCatId + ) -> Result; // Entity-specific Methods fn db_from_row( @@ -82,8 +96,14 @@ where rev_row: Self::RevRow, ident_row: Option, ) -> Result; - fn db_insert_rev(&self, conn: &DbConn) -> Result; - fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result>; + fn db_insert_rev( + &self, + conn: &DbConn + ) -> Result; + fn db_insert_revs( + conn: &DbConn, + models: &[&Self] + ) -> Result>; } // 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::(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 { + + 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::(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 { + + // 1. select edit rows (in sql) + let edit_rows: Vec = $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 = 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 { @@ -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 { @@ -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( -- cgit v1.2.3