refactor: optimize sync and list operation by join

This commit is contained in:
qaz741wsd856 2025-12-10 09:55:53 +00:00
parent ae423c1a53
commit 96749af57f
3 changed files with 70 additions and 10 deletions

View file

@ -443,16 +443,33 @@ pub async fn hydrate_ciphers_attachments(
}
};
let mut map = load_attachment_map_json(db, body, path).await?;
let map = load_attachment_map_json(db, body, path).await?;
apply_attachment_map(ciphers, map);
for cipher in ciphers.iter_mut() {
if let Some(list) = map.remove(&cipher.id) {
if !list.is_empty() {
cipher.attachments = Some(list);
}
}
Ok(())
}
/// Batch hydrate attachments for all ciphers belonging to a user without serializing ids.
pub async fn hydrate_ciphers_attachments_for_user(
db: &D1Database,
env: &Env,
ciphers: &mut [Cipher],
user_id: &str,
) -> Result<(), AppError> {
if ciphers.is_empty() {
return Ok(());
}
if !attachments_enabled(env) {
for cipher in ciphers.iter_mut() {
cipher.attachments = None;
}
return Ok(());
}
let map = load_attachment_map_for_user(db, user_id).await?;
apply_attachment_map(ciphers, map);
Ok(())
}
@ -627,6 +644,32 @@ async fn load_attachment_map_json(
.results()
.map_err(|_| AppError::Database)?;
Ok(build_attachment_map(attachments))
}
async fn load_attachment_map_for_user(
db: &D1Database,
user_id: &str,
) -> Result<HashMap<String, Vec<AttachmentResponse>>, AppError> {
let attachments: Vec<AttachmentDB> = db
.prepare(
"SELECT a.* FROM attachments a \
JOIN ciphers c ON a.cipher_id = c.id \
WHERE c.user_id = ?1",
)
.bind(&[user_id.into()])?
.all()
.await
.map_err(|_| AppError::Database)?
.results()
.map_err(|_| AppError::Database)?;
Ok(build_attachment_map(attachments))
}
fn build_attachment_map(
attachments: Vec<AttachmentDB>,
) -> HashMap<String, Vec<AttachmentResponse>> {
let mut map: HashMap<String, Vec<AttachmentResponse>> = HashMap::new();
for attachment in attachments {
@ -636,7 +679,17 @@ async fn load_attachment_map_json(
.push(attachment.to_response(None));
}
Ok(map)
map
}
fn apply_attachment_map(ciphers: &mut [Cipher], mut map: HashMap<String, Vec<AttachmentResponse>>) {
for cipher in ciphers.iter_mut() {
if let Some(list) = map.remove(&cipher.id) {
if !list.is_empty() {
cipher.attachments = Some(list);
}
}
}
}
async fn upload_to_r2(

View file

@ -233,7 +233,13 @@ pub async fn list_ciphers(
let mut ciphers: Vec<Cipher> = ciphers_db.into_iter().map(|c| c.into()).collect();
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, None, None).await?;
attachments::hydrate_ciphers_attachments_for_user(
&db,
env.as_ref(),
&mut ciphers,
&claims.sub,
)
.await?;
Ok(Json(CipherListResponse {
data: ciphers,

View file

@ -65,7 +65,8 @@ pub async fn get_sync_data(
.collect::<Vec<Cipher>>();
let mut ciphers = ciphers;
attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, None, None).await?;
attachments::hydrate_ciphers_attachments_for_user(&db, env.as_ref(), &mut ciphers, &user_id)
.await?;
let profile = Profile::from_user(user)?;