refactor: optimize bulk delete and restore operations for ciphers
This commit is contained in:
parent
f5db9e400d
commit
eb6a1d8a63
1 changed files with 43 additions and 48 deletions
|
|
@ -367,23 +367,24 @@ pub async fn soft_delete_ciphers_bulk(
|
||||||
) -> Result<Json<()>, AppError> {
|
) -> Result<Json<()>, AppError> {
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
||||||
let batch_size = get_batch_size(&env);
|
let ids = payload.ids;
|
||||||
let mut statements: Vec<D1PreparedStatement> = Vec::with_capacity(payload.ids.len());
|
|
||||||
|
|
||||||
for id in payload.ids {
|
if ids.is_empty() {
|
||||||
let stmt = query!(
|
return Ok(Json(()));
|
||||||
&db,
|
|
||||||
"UPDATE ciphers SET deleted_at = ?1, updated_at = ?1 WHERE id = ?2 AND user_id = ?3",
|
|
||||||
now,
|
|
||||||
id,
|
|
||||||
claims.sub
|
|
||||||
)
|
|
||||||
.map_err(|_| AppError::Database)?;
|
|
||||||
|
|
||||||
statements.push(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db::execute_in_batches(&db, statements, batch_size).await?;
|
let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?;
|
||||||
|
|
||||||
|
query!(
|
||||||
|
&db,
|
||||||
|
"UPDATE ciphers SET deleted_at = ?1, updated_at = ?1 WHERE user_id = ?2 AND id IN (SELECT value FROM json_each(?3))",
|
||||||
|
now,
|
||||||
|
claims.sub,
|
||||||
|
ids_json
|
||||||
|
)
|
||||||
|
.map_err(|_| AppError::Database)?
|
||||||
|
.run()
|
||||||
|
.await?;
|
||||||
|
|
||||||
db::touch_user_updated_at(&db, &claims.sub).await?;
|
db::touch_user_updated_at(&db, &claims.sub).await?;
|
||||||
|
|
||||||
|
|
@ -431,9 +432,14 @@ pub async fn hard_delete_ciphers_bulk(
|
||||||
Json(payload): Json<CipherIdsData>,
|
Json(payload): Json<CipherIdsData>,
|
||||||
) -> Result<Json<()>, AppError> {
|
) -> Result<Json<()>, AppError> {
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
let batch_size = get_batch_size(&env);
|
|
||||||
let ids = payload.ids;
|
let ids = payload.ids;
|
||||||
|
|
||||||
|
if ids.is_empty() {
|
||||||
|
return Ok(Json(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?;
|
||||||
|
|
||||||
if attachments::attachments_enabled(env.as_ref()) {
|
if attachments::attachments_enabled(env.as_ref()) {
|
||||||
let bucket = attachments::require_bucket(env.as_ref())?;
|
let bucket = attachments::require_bucket(env.as_ref())?;
|
||||||
let keys =
|
let keys =
|
||||||
|
|
@ -441,21 +447,15 @@ pub async fn hard_delete_ciphers_bulk(
|
||||||
attachments::delete_r2_objects(&bucket, &keys).await?;
|
attachments::delete_r2_objects(&bucket, &keys).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut statements: Vec<D1PreparedStatement> = Vec::with_capacity(ids.len());
|
query!(
|
||||||
|
&db,
|
||||||
for id in ids {
|
"DELETE FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))",
|
||||||
let stmt = query!(
|
claims.sub,
|
||||||
&db,
|
ids_json
|
||||||
"DELETE FROM ciphers WHERE id = ?1 AND user_id = ?2",
|
)
|
||||||
id,
|
.map_err(|_| AppError::Database)?
|
||||||
claims.sub
|
.run()
|
||||||
)
|
.await?;
|
||||||
.map_err(|_| AppError::Database)?;
|
|
||||||
|
|
||||||
statements.push(stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
db::execute_in_batches(&db, statements, batch_size).await?;
|
|
||||||
|
|
||||||
db::touch_user_updated_at(&db, &claims.sub).await?;
|
db::touch_user_updated_at(&db, &claims.sub).await?;
|
||||||
|
|
||||||
|
|
@ -523,7 +523,6 @@ pub async fn restore_ciphers_bulk(
|
||||||
) -> Result<Json<BulkRestoreResponse>, AppError> {
|
) -> Result<Json<BulkRestoreResponse>, AppError> {
|
||||||
let db = db::get_db(&env)?;
|
let db = db::get_db(&env)?;
|
||||||
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
||||||
let batch_size = get_batch_size(&env);
|
|
||||||
let ids = payload.ids;
|
let ids = payload.ids;
|
||||||
|
|
||||||
if ids.is_empty() {
|
if ids.is_empty() {
|
||||||
|
|
@ -534,25 +533,21 @@ pub async fn restore_ciphers_bulk(
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch UPDATE operations
|
// Precompute JSON array for ids, used by both UPDATE and SELECT
|
||||||
let mut update_statements: Vec<D1PreparedStatement> = Vec::with_capacity(ids.len());
|
|
||||||
for id in ids.iter() {
|
|
||||||
let stmt = query!(
|
|
||||||
&db,
|
|
||||||
"UPDATE ciphers SET deleted_at = NULL, updated_at = ?1 WHERE id = ?2 AND user_id = ?3",
|
|
||||||
now,
|
|
||||||
id.clone(),
|
|
||||||
claims.sub
|
|
||||||
)
|
|
||||||
.map_err(|_| AppError::Database)?;
|
|
||||||
|
|
||||||
update_statements.push(stmt);
|
|
||||||
}
|
|
||||||
db::execute_in_batches(&db, update_statements, batch_size).await?;
|
|
||||||
|
|
||||||
// Batch SELECT using json_each() - avoid N+1 query problem
|
|
||||||
let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?;
|
let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?;
|
||||||
|
|
||||||
|
// Single bulk UPDATE using json_each()
|
||||||
|
query!(
|
||||||
|
&db,
|
||||||
|
"UPDATE ciphers SET deleted_at = NULL, updated_at = ?1 WHERE user_id = ?2 AND id IN (SELECT value FROM json_each(?3))",
|
||||||
|
now,
|
||||||
|
claims.sub,
|
||||||
|
ids_json.clone()
|
||||||
|
)
|
||||||
|
.map_err(|_| AppError::Database)?
|
||||||
|
.run()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut restored_ciphers: Vec<Cipher> = db
|
let mut restored_ciphers: Vec<Cipher> = db
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT * FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))",
|
"SELECT * FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue