pinakes-core: wrap save_markdown_links in transactions for atomicity

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I44eaeda5bc1d6894137ee9a3c902cdac6a6a6964
This commit is contained in:
raf 2026-02-09 14:26:02 +03:00
commit 4ed61bc62e
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 43 additions and 27 deletions

View file

@ -6047,7 +6047,7 @@ impl StorageBackend for PostgresBackend {
media_id: MediaId,
links: &[crate::model::MarkdownLink],
) -> Result<()> {
let client = self
let mut client = self
.pool
.get()
.await
@ -6055,40 +6055,49 @@ impl StorageBackend for PostgresBackend {
let media_id_str = media_id.0.to_string();
// Delete existing links for this source
client
.execute(
"DELETE FROM markdown_links WHERE source_media_id = $1",
&[&media_id_str],
)
// Wrap DELETE + INSERT in transaction to ensure atomicity
let tx = client
.transaction()
.await
.map_err(|e| PinakesError::Database(e.to_string()))?;
// Delete existing links for this source
tx.execute(
"DELETE FROM markdown_links WHERE source_media_id = $1",
&[&media_id_str],
)
.await
.map_err(|e| PinakesError::Database(e.to_string()))?;
// Insert new links
for link in links {
let target_media_id = link.target_media_id.map(|id| id.0.to_string());
client
.execute(
"INSERT INTO markdown_links (
tx.execute(
"INSERT INTO markdown_links (
id, source_media_id, target_path, target_media_id,
link_type, link_text, line_number, context, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
&[
&link.id.to_string(),
&media_id_str,
&link.target_path,
&target_media_id,
&link.link_type.to_string(),
&link.link_text,
&link.line_number,
&link.context,
&link.created_at,
],
)
.await
.map_err(|e| PinakesError::Database(e.to_string()))?;
&[
&link.id.to_string(),
&media_id_str,
&link.target_path,
&target_media_id,
&link.link_type.to_string(),
&link.link_text,
&link.line_number,
&link.context,
&link.created_at,
],
)
.await
.map_err(|e| PinakesError::Database(e.to_string()))?;
}
// Commit transaction - if this fails, all changes are rolled back
tx.commit()
.await
.map_err(|e| PinakesError::Database(e.to_string()))?;
Ok(())
}

View file

@ -6400,16 +6400,19 @@ impl StorageBackend for SqliteBackend {
let links: Vec<_> = links.to_vec();
tokio::task::spawn_blocking(move || {
let conn = conn.lock().unwrap();
let mut conn = conn.lock().unwrap();
// Wrap DELETE + INSERT in transaction to ensure atomicity
let tx = conn.transaction()?;
// Delete existing links for this source
conn.execute(
tx.execute(
"DELETE FROM markdown_links WHERE source_media_id = ?1",
[&media_id_str],
)?;
// Insert new links
let mut stmt = conn.prepare(
let mut stmt = tx.prepare(
"INSERT INTO markdown_links (
id, source_media_id, target_path, target_media_id,
link_type, link_text, line_number, context, created_at
@ -6430,6 +6433,10 @@ impl StorageBackend for SqliteBackend {
])?;
}
// Commit transaction - if this fails, all changes are rolled back
drop(stmt);
tx.commit()?;
Ok::<_, rusqlite::Error>(())
})
.await