use super::V3Backend;
use crate::SqliteGraphError;
use crate::backend::{EdgeSpec, NodeSpec};
use rusqlite::ToSql;
pub struct WriteBatchGuard<'a> {
backend: &'a V3Backend,
node_count: u64,
edge_count: u64,
staged_nodes: Vec<(i64, String, String, serde_json::Value)>,
staged_edges: Vec<EdgeSpec>,
committed: bool,
}
impl<'a> WriteBatchGuard<'a> {
pub(crate) fn new(backend: &'a V3Backend) -> Self {
{
let mut node_store = backend.node_store.write();
node_store.begin_batch();
}
Self {
backend,
node_count: 0,
edge_count: 0,
staged_nodes: Vec::new(),
staged_edges: Vec::new(),
committed: false,
}
}
pub fn insert_node(&mut self, node: NodeSpec) -> Result<i64, SqliteGraphError> {
let kind = node.kind.clone();
let name = node.name.clone();
let data = node.data.clone();
let node_id = self.backend.insert_node_inner(node)?;
self.node_count += 1;
self.staged_nodes.push((node_id, kind, name, data));
Ok(node_id)
}
pub fn insert_node_with_id(
&mut self,
mut node: NodeSpec,
node_id: i64,
) -> Result<i64, SqliteGraphError> {
if let Some(obj) = node.data.as_object_mut() {
obj.insert("id".to_string(), serde_json::Value::Number(node_id.into()));
} else {
let mut obj = serde_json::Map::new();
obj.insert("id".to_string(), serde_json::Value::Number(node_id.into()));
node.data = serde_json::Value::Object(obj);
}
let kind = node.kind.clone();
let name = node.name.clone();
let data = node.data.clone();
let node_id = self.backend.insert_node_inner(node)?;
self.node_count += 1;
self.staged_nodes.push((node_id, kind, name, data));
Ok(node_id)
}
pub fn insert_edge(&mut self, edge: EdgeSpec) -> Result<i64, SqliteGraphError> {
let edge_id = self.backend.insert_edge_inner(edge.clone())?;
self.edge_count += 1;
self.staged_edges.push(edge);
Ok(edge_id)
}
pub fn commit(mut self) -> Result<(), SqliteGraphError> {
if self.committed {
return Ok(());
}
if self.node_count > 0 {
let mut node_store = self.backend.node_store.write();
node_store
.commit_batch()
.map_err(|e| SqliteGraphError::connection(format!("Batch commit failed: {}", e)))?;
}
if self.node_count > 0 || self.edge_count > 0 {
self.backend.sync_header()?;
let next_version = self.backend.next_graph_version();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| {
SqliteGraphError::connection(format!(
"System clock before UNIX epoch during batch commit: {}",
e
))
})?
.as_secs() as i64;
if self.node_count > 0 {
let conn = self.backend.sqlite_conn.lock();
for (node_id, kind, name, data) in &self.staged_nodes {
let data_json = serde_json::to_string(data).unwrap_or_default();
conn.execute(
"INSERT INTO node_properties (node_id, kind, name, data, created_at, created_version) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
[
node_id as &dyn ToSql,
kind as &dyn ToSql,
name as &dyn ToSql,
&data_json as &dyn ToSql,
×tamp as &dyn ToSql,
&next_version as &dyn ToSql,
],
).map_err(|e| SqliteGraphError::connection(format!("Failed to insert node properties: {}", e)))?;
}
drop(conn);
}
if self.edge_count > 0 {
let conn = self.backend.sqlite_conn.lock();
for edge in &self.staged_edges {
let data_json = serde_json::to_string(&edge.data).unwrap_or_default();
conn.execute(
"INSERT OR REPLACE INTO edge_attributes (src, dst, attr_name, attr_value, created_version) VALUES (?1, ?2, ?3, ?4, ?5)",
[
&edge.from as &dyn ToSql,
&edge.to as &dyn ToSql,
&edge.edge_type as &dyn ToSql,
&data_json as &dyn ToSql,
&next_version as &dyn ToSql,
],
).map_err(|e| SqliteGraphError::connection(format!("Failed to insert edge attributes: {}", e)))?;
}
drop(conn);
self.backend.rebuild_csr_runtime_views(next_version)?;
}
self.backend.advance_snapshot_version();
self.backend.flush_to_disk()?;
}
self.committed = true;
Ok(())
}
pub fn node_count(&self) -> u64 {
self.node_count
}
pub fn edge_count(&self) -> u64 {
self.edge_count
}
}
impl<'a> Drop for WriteBatchGuard<'a> {
fn drop(&mut self) {
if !self.committed {
let mut node_store = self.backend.node_store.write();
node_store.rollback_batch();
}
}
}