feat: dup doc in db row (#806)

* feat: support doc in duplicated database row

* fix: use db row collab

* fix: use meta

* fix: set correct values

* feat: allow insertion of standalone view

* fix: use correct id for duplicating doc in db row

* feat: add attachment count

* fix: compile
This commit is contained in:
Zack 2024-09-11 10:16:56 +08:00 committed by GitHub
parent 07d6001cf8
commit 5bf08621de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 161 additions and 63 deletions

View File

@ -13,14 +13,14 @@ pub struct PublishViewMeta {
pub publish_name: String, pub publish_name: String,
} }
#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
pub struct PublishViewMetaData { pub struct PublishViewMetaData {
pub view: PublishViewInfo, pub view: PublishViewInfo,
pub child_views: Vec<PublishViewInfo>, pub child_views: Vec<PublishViewInfo>,
pub ancestor_views: Vec<PublishViewInfo>, pub ancestor_views: Vec<PublishViewInfo>,
} }
#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] #[derive(Default, Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
pub struct PublishViewInfo { pub struct PublishViewInfo {
pub view_id: String, pub view_id: String,
pub name: String, pub name: String,

View File

@ -6,6 +6,8 @@ use collab::preclude::Collab;
use collab::preclude::MapExt; use collab::preclude::MapExt;
use collab_database::database::DatabaseBody; use collab_database::database::DatabaseBody;
use collab_database::entity::FieldType; use collab_database::entity::FieldType;
use collab_database::rows::meta_id_from_row_id;
use collab_database::rows::RowMetaKey;
use collab_database::workspace_database::WorkspaceDatabaseBody; use collab_database::workspace_database::WorkspaceDatabaseBody;
use collab_document::blocks::DocumentData; use collab_document::blocks::DocumentData;
use collab_document::document::Document; use collab_document::document::Document;
@ -24,8 +26,8 @@ use sqlx::PgPool;
use std::collections::HashSet; use std::collections::HashSet;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use workspace_template::gen_view_id; use workspace_template::gen_view_id;
use yrs::any;
use yrs::updates::encoder::Encode; use yrs::updates::encoder::Encode;
use yrs::Any;
use yrs::Array; use yrs::Array;
use yrs::ArrayRef; use yrs::ArrayRef;
use yrs::Out; use yrs::Out;
@ -258,7 +260,10 @@ impl PublishCollabDuplicator {
let mut inserted = vec![]; let mut inserted = vec![];
for (view_id, view) in views_to_add.iter() { for (view_id, view) in views_to_add.iter() {
if duplicated_view_ids.contains(&view.parent_view_id) { // allow to insert if parent view is already inserted
// or if view is standalone (view_id == parent_view_id)
if duplicated_view_ids.contains(&view.parent_view_id) || *view_id == view.parent_view_id
{
folder folder
.body .body
.views .views
@ -372,12 +377,12 @@ impl PublishCollabDuplicator {
async fn deep_copy_doc<'a>( async fn deep_copy_doc<'a>(
&mut self, &mut self,
pub_view_id: &str, pub_view_id: &str,
new_view_id: String, dup_view_id: String,
doc: Document, doc: Document,
metadata: PublishViewMetaData, metadata: PublishViewMetaData,
) -> Result<View, AppError> { ) -> Result<View, AppError> {
let mut ret_view = let mut ret_view =
self.new_folder_view(new_view_id.clone(), &metadata.view, ViewLayout::Document); self.new_folder_view(dup_view_id.clone(), &metadata.view, ViewLayout::Document);
let mut doc_data = doc let mut doc_data = doc
.get_document_data() .get_document_data()
@ -396,7 +401,7 @@ impl PublishCollabDuplicator {
{ {
// write modified doc_data back to storage // write modified doc_data back to storage
let empty_collab = collab_from_doc_state(vec![], &new_view_id)?; let empty_collab = collab_from_doc_state(vec![], &dup_view_id)?;
let new_doc = tokio::task::spawn_blocking(move || { let new_doc = tokio::task::spawn_blocking(move || {
Document::create_with_data(empty_collab, doc_data) Document::create_with_data(empty_collab, doc_data)
.map_err(|e| AppError::Unhandled(e.to_string())) .map_err(|e| AppError::Unhandled(e.to_string()))
@ -689,7 +694,7 @@ impl PublishCollabDuplicator {
for (key, type_option_value) in field.type_options.iter_mut() { for (key, type_option_value) in field.type_options.iter_mut() {
if *key == FieldType::Relation.type_id() { if *key == FieldType::Relation.type_id() {
if let Some(pub_db_id) = type_option_value.get_mut("database_id") { if let Some(pub_db_id) = type_option_value.get_mut("database_id") {
if let any::Any::String(pub_db_id_str) = pub_db_id { if let Any::String(pub_db_id_str) = pub_db_id {
if let Some(related_db_view) = if let Some(related_db_view) =
published_db.database_relations.get(pub_db_id_str.as_ref()) published_db.database_relations.get(pub_db_id_str.as_ref())
{ {
@ -703,7 +708,7 @@ impl PublishCollabDuplicator {
.cloned() .cloned()
.flatten() .flatten()
{ {
*pub_db_id = any::Any::String(dup_db_id.into()); *pub_db_id = Any::String(dup_db_id.into());
db_body.fields.update_field(&mut txn, &field.id, |f| { db_body.fields.update_field(&mut txn, &field.id, |f| {
f.set_type_option( f.set_type_option(
FieldType::Relation.into(), FieldType::Relation.into(),
@ -721,10 +726,10 @@ impl PublishCollabDuplicator {
} }
// duplicate db collab rows // duplicate db collab rows
for (old_id, row_bin_data) in &published_db.database_row_collabs { for (pub_row_id, row_bin_data) in &published_db.database_row_collabs {
// assign a new id for the row // assign a new id for the row
let new_row_id = gen_view_id(); let dup_row_id = gen_view_id();
let mut db_row_collab = collab_from_doc_state(row_bin_data.clone(), &new_row_id)?; let mut db_row_collab = collab_from_doc_state(row_bin_data.clone(), &dup_row_id)?;
{ {
// update database_id and row_id in data // update database_id and row_id in data
@ -737,64 +742,157 @@ impl PublishCollabDuplicator {
})? })?
.cast::<MapRef>() .cast::<MapRef>()
.map_err(|err| AppError::Unhandled(format!("data not map: {:?}", err)))?; .map_err(|err| AppError::Unhandled(format!("data not map: {:?}", err)))?;
data.insert(&mut txn, "id", new_row_id.clone()); data.insert(&mut txn, "id", dup_row_id.clone());
data.insert(&mut txn, "database_id", new_db_id.clone()); data.insert(&mut txn, "database_id", new_db_id.clone());
// update relation cells {
// db_row_collb: Object { // update relation cells
// "data": Object { // db_row_collb: Object {
// "cells": Object { // "data": Object {
// "MBaTsr": Object { // "cells": Object {
// "data": Array [ // "MBaTsr": Object {
// String("eefb5700-8cf7-411e-9596-f60b9a51916e"), // "data": Array [
// String("23d5e054-42c8-4754-ad69-527e4ffc1e46"), // String("eefb5700-8cf7-411e-9596-f60b9a51916e"),
// // above are published row ids of related database // String("23d5e054-42c8-4754-ad69-527e4ffc1e46"),
// // we need to replace them with respective duplicated row ids // // above are published row ids of related database
// ], // // we need to replace them with respective duplicated row ids
// "field_type": Number(10), // ],
// // use this condition to filter out relation cells // "field_type": Number(10),
// }, // // use this condition to filter out relation cells
// }, // },
// }, // },
// } // },
let cells: MapRef = db_row_collab // }
.data let cells: MapRef = db_row_collab
.get_with_path(&txn, ["data", "cells"]) .data
.ok_or_else(|| { .get_with_path(&txn, ["data", "cells"])
AppError::RecordNotFound("no cells found in database row collab".to_string()) .ok_or_else(|| {
})?; AppError::RecordNotFound("no cells found in database row collab".to_string())
})?;
let mut rel_row_idss = vec![]; let mut rel_row_idss = vec![];
for (_, out) in cells.iter(&txn) { for (_, out) in cells.iter(&txn) {
if let Ok(m) = out.cast::<MapRef>() { if let Ok(m) = out.cast::<MapRef>() {
if let Some(Out::Any(any::Any::BigInt(n))) = m.get(&txn, "field_type") { if let Some(Out::Any(Any::BigInt(n))) = m.get(&txn, "field_type") {
if n == FieldType::Relation as i64 { if n == FieldType::Relation as i64 {
let relation_data = m.get(&txn, "data").ok_or_else(|| { let relation_data = m.get(&txn, "data").ok_or_else(|| {
AppError::RecordNotFound("no data found in relation cell".to_string()) AppError::RecordNotFound("no data found in relation cell".to_string())
})?; })?;
if let Ok(arr) = relation_data.cast::<ArrayRef>() { if let Ok(arr) = relation_data.cast::<ArrayRef>() {
rel_row_idss.push(arr) rel_row_idss.push(arr)
}; };
}
} }
} }
} }
} for rel_row_ids in rel_row_idss {
for rel_row_ids in rel_row_idss { let num_refs = rel_row_ids.len(&txn);
let num_refs = rel_row_ids.len(&txn);
let mut pub_row_ids = Vec::with_capacity(num_refs as usize); let mut pub_row_ids = Vec::with_capacity(num_refs as usize);
for rel_row_id in rel_row_ids.iter(&txn) { for rel_row_id in rel_row_ids.iter(&txn) {
if let Out::Any(any::Any::String(s)) = rel_row_id { if let Out::Any(Any::String(s)) = rel_row_id {
pub_row_ids.push(s); pub_row_ids.push(s);
}
}
rel_row_ids.remove_range(&mut txn, 0, num_refs);
for pub_row_id in pub_row_ids {
let dup_row_id =
self
.duplicated_db_row
.get(pub_row_id.as_ref())
.ok_or_else(|| {
AppError::RecordNotFound(format!("row not found: {}", pub_row_id))
})?;
let _ = rel_row_ids.push_back(&mut txn, dup_row_id.as_str());
} }
} }
rel_row_ids.remove_range(&mut txn, 0, num_refs); }
for pub_row_id in pub_row_ids {
let dup_row_id = self {
.duplicated_db_row // handle row meta
.get(pub_row_id.as_ref()) let row_meta: MapRef = db_row_collab
.ok_or_else(|| AppError::RecordNotFound(format!("row not found: {}", pub_row_id)))?; .data
let _ = rel_row_ids.push_back(&mut txn, dup_row_id.as_str()); .get(&txn, "meta")
.ok_or_else(|| {
AppError::RecordNotFound(format!(
"no meta found in database row collab: {}",
pub_row_id
))
})?
.cast()
.map_err(|err| AppError::Unhandled(format!("not a map: {:?}", err)))?;
{
// handle document in database row
let pub_is_doc_empty_key =
meta_id_from_row_id(&pub_row_id.parse()?, RowMetaKey::IsDocumentEmpty);
let pub_is_doc_empty = row_meta.get(&txn, &pub_is_doc_empty_key);
if let Some(Out::Any(Any::Bool(is_doc_empty))) = pub_is_doc_empty {
row_meta.remove(&mut txn, &pub_is_doc_empty_key);
let dup_row_doc_id =
meta_id_from_row_id(&dup_row_id.parse()?, RowMetaKey::DocumentId);
row_meta.insert(&mut txn, dup_row_doc_id.clone(), Any::Bool(is_doc_empty));
if !is_doc_empty {
let pub_row_doc_id =
meta_id_from_row_id(&pub_row_id.parse()?, RowMetaKey::DocumentId);
let row_doc_doc_state = published_db
.database_row_document_collabs
.get(&pub_row_doc_id)
.ok_or_else(|| {
AppError::RecordNotFound(format!("doc not found: {}", pub_row_doc_id))
})?;
let doc_collab =
collab_from_doc_state(row_doc_doc_state.to_vec(), &pub_row_doc_id)?;
let doc =
Document::open(doc_collab).map_err(|e| AppError::Unhandled(e.to_string()))?;
let mut new_doc_view = Box::pin(self.deep_copy_doc(
&pub_row_doc_id,
dup_row_doc_id.clone(),
doc,
PublishViewMetaData::default(),
))
.await?;
new_doc_view.parent_view_id.clone_from(&dup_row_doc_id); // orphan folder view
self
.views_to_add
.insert(dup_row_doc_id.clone(), new_doc_view);
}
}
}
{
// handle icon id
let pub_icon_id_key = meta_id_from_row_id(&pub_row_id.parse()?, RowMetaKey::IconId);
let pub_icon_id = row_meta.get(&txn, &pub_icon_id_key);
if let Some(Out::Any(Any::String(icon_id))) = pub_icon_id {
row_meta.remove(&mut txn, &pub_icon_id_key);
let dup_icon_id_key = meta_id_from_row_id(&dup_row_id.parse()?, RowMetaKey::IconId);
row_meta.insert(&mut txn, dup_icon_id_key, icon_id);
}
}
{
// handle cover id
let pub_cover_id_key = meta_id_from_row_id(&pub_row_id.parse()?, RowMetaKey::CoverId);
let pub_cover_id = row_meta.get(&txn, &pub_cover_id_key);
if let Some(Out::Any(Any::String(cover_id))) = pub_cover_id {
row_meta.remove(&mut txn, &pub_cover_id_key);
let dup_cover_id_key = meta_id_from_row_id(&dup_row_id.parse()?, RowMetaKey::CoverId);
row_meta.insert(&mut txn, dup_cover_id_key, cover_id);
}
}
{
// handle attachment_count
let pub_attach_count_key =
meta_id_from_row_id(&pub_row_id.parse()?, RowMetaKey::CoverId);
let pub_attach_count = row_meta.get(&txn, &pub_attach_count_key);
if let Some(Out::Any(Any::BigInt(attachment_count))) = pub_attach_count {
row_meta.remove(&mut txn, &pub_attach_count_key);
let dup_attach_count_key =
meta_id_from_row_id(&dup_row_id.parse()?, RowMetaKey::AttachmentCount);
row_meta.insert(&mut txn, dup_attach_count_key, attachment_count);
}
} }
} }
} }
@ -804,12 +902,12 @@ impl PublishCollabDuplicator {
tokio::task::spawn_blocking(move || collab_to_bin(&db_row_collab, CollabType::DatabaseRow)) tokio::task::spawn_blocking(move || collab_to_bin(&db_row_collab, CollabType::DatabaseRow))
.await?; .await?;
self.collabs_to_insert.insert( self.collabs_to_insert.insert(
new_row_id.clone(), dup_row_id.clone(),
(CollabType::DatabaseRow, db_row_ec_bytes?), (CollabType::DatabaseRow, db_row_ec_bytes?),
); );
self self
.duplicated_db_row .duplicated_db_row
.insert(old_id.clone(), new_row_id.clone()); .insert(pub_row_id.clone(), dup_row_id.clone());
} }
// accumulate list of database views (Board, Cal, ...) to be linked to the database // accumulate list of database views (Board, Cal, ...) to be linked to the database