use crate::embedded::c_api::error::{linkrs_error_code_t, set_last_error_message};
use crate::embedded::c_api::session::LinkrsSessionHandle;
use crate::embedded::c_api::types::{linkrs_batch_t, linkrs_session_t, linkrs_value_t};
use linkrs_core::types::VertexId;
use linkrs_core::vertex_edge_path::Tag;
use linkrs_core::{Edge, Vertex};
use std::collections::HashMap;
use std::ffi::{c_char, c_int, CStr};
enum BatchItem {
Vertex(Vertex),
Edge(Edge),
}
pub struct LinkrsBatchHandle {
session_ptr: *mut LinkrsSessionHandle,
batch_size: usize,
buffer: Vec<BatchItem>,
vertices_inserted: usize,
edges_inserted: usize,
errors: Vec<String>,
}
impl LinkrsBatchHandle {
fn is_session_valid(&self) -> bool {
!self.session_ptr.is_null()
}
fn get_session(&self) -> Option<&LinkrsSessionHandle> {
if self.is_session_valid() {
Some(unsafe { &*self.session_ptr })
} else {
None
}
}
fn flush_vertices(&mut self) -> Result<(), String> {
let mut vertices = Vec::new();
let mut remaining = Vec::new();
for item in self.buffer.drain(..) {
match item {
BatchItem::Vertex(v) => vertices.push(v),
_ => remaining.push(item),
}
}
self.buffer.extend(remaining);
if vertices.is_empty() {
return Ok(());
}
let vertex_count = vertices.len();
let result = {
let session = self
.get_session()
.ok_or_else(|| "Session invalid or closed".to_string())?;
session.inner.batch_insert_vertices(vertices)
};
match result {
Ok(_) => {
self.vertices_inserted += vertex_count;
Ok(())
}
Err(e) => {
let err_msg = format!("Batch insert vertices failed: {}", e);
self.errors.push(err_msg.clone());
Err(err_msg)
}
}
}
fn flush_edges(&mut self) -> Result<(), String> {
let mut edges = Vec::new();
let mut remaining = Vec::new();
for item in self.buffer.drain(..) {
match item {
BatchItem::Edge(e) => edges.push(e),
_ => remaining.push(item),
}
}
self.buffer.extend(remaining);
if edges.is_empty() {
return Ok(());
}
let edge_count = edges.len();
let result = {
let session = self
.get_session()
.ok_or_else(|| "Session invalid or closed".to_string())?;
session.inner.batch_insert_edges(edges)
};
match result {
Ok(_) => {
self.edges_inserted += edge_count;
Ok(())
}
Err(e) => {
let err_msg = format!("Batch insert edges failed: {}", e);
self.errors.push(err_msg.clone());
Err(err_msg)
}
}
}
fn execute(&mut self) -> Result<(), String> {
self.flush_vertices()?;
self.flush_edges()?;
Ok(())
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_inserter_create(
session: *mut linkrs_session_t,
batch_size: c_int,
batch: *mut *mut linkrs_batch_t,
) -> c_int {
if session.is_null() || batch.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let size = if batch_size <= 0 {
100
} else {
batch_size as usize
};
let handle = Box::new(LinkrsBatchHandle {
session_ptr: session as *mut LinkrsSessionHandle,
batch_size: size,
buffer: Vec::new(),
vertices_inserted: 0,
edges_inserted: 0,
errors: Vec::new(),
});
*batch = Box::into_raw(handle) as *mut linkrs_batch_t;
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_free(batch: *mut linkrs_batch_t) -> c_int {
if batch.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let _ = Box::from_raw(batch as *mut LinkrsBatchHandle);
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_add_vertex(
batch: *mut linkrs_batch_t,
vid: *const linkrs_value_t,
tags: *const c_char,
) -> c_int {
if batch.is_null() || vid.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(batch as *mut LinkrsBatchHandle);
if !handle.is_session_valid() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let vid_value = unsafe { super::value::linkrs_value_to_core(vid) };
let vid_id = match value_to_vertex_id(&vid_value) {
Some(id) => id,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
if tags.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let tags_str = match unsafe { CStr::from_ptr(tags).to_str() } {
Ok(s) => s,
Err(_) => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let mut tag_names = tags_str
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty());
let single_tag = match (tag_names.next(), tag_names.next()) {
(Some(name), None) => name.to_string(),
_ => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let vertex = Vertex::new(vid_id, Tag::new(single_tag, HashMap::new()));
handle.buffer.push(BatchItem::Vertex(vertex));
if handle.buffer.len() >= handle.batch_size {
if let Err(e) = handle.execute() {
set_last_error_message(e);
return linkrs_error_code_t::LINKRS_ERROR as c_int;
}
}
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_add_edge(
batch: *mut linkrs_batch_t,
src_vid: *const linkrs_value_t,
dst_vid: *const linkrs_value_t,
edge_type: *const c_char,
) -> c_int {
if batch.is_null() || src_vid.is_null() || dst_vid.is_null() || edge_type.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(batch as *mut LinkrsBatchHandle);
if !handle.is_session_valid() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let src_value = unsafe { super::value::linkrs_value_to_core(src_vid) };
let dst_value = unsafe { super::value::linkrs_value_to_core(dst_vid) };
let src_id = match value_to_vertex_id(&src_value) {
Some(id) => id,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let dst_id = match value_to_vertex_id(&dst_value) {
Some(id) => id,
None => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let edge_type_str = match CStr::from_ptr(edge_type).to_str() {
Ok(s) => s.to_string(),
Err(_) => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
};
let edge = Edge::new(src_id, dst_id, edge_type_str, 0, HashMap::new());
handle.buffer.push(BatchItem::Edge(edge));
if handle.buffer.len() >= handle.batch_size {
if let Err(e) = handle.execute() {
set_last_error_message(e);
return linkrs_error_code_t::LINKRS_ERROR as c_int;
}
}
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_execute(batch: *mut linkrs_batch_t) -> c_int {
if batch.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &mut *(batch as *mut LinkrsBatchHandle);
if !handle.is_session_valid() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
if let Err(e) = handle.execute() {
set_last_error_message(e);
return linkrs_error_code_t::LINKRS_ERROR as c_int;
}
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_vertices_inserted(
batch: *mut linkrs_batch_t,
count: *mut c_int,
) -> c_int {
if batch.is_null() || count.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &*(batch as *mut LinkrsBatchHandle);
*count = handle.vertices_inserted as c_int;
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_edges_inserted(
batch: *mut linkrs_batch_t,
count: *mut c_int,
) -> c_int {
if batch.is_null() || count.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let handle = &*(batch as *mut LinkrsBatchHandle);
*count = handle.edges_inserted as c_int;
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_buffered_vertices(batch: *mut linkrs_batch_t) -> c_int {
if batch.is_null() {
return -1;
}
let handle = &*(batch as *mut LinkrsBatchHandle);
handle
.buffer
.iter()
.filter(|item| matches!(item, BatchItem::Vertex(_)))
.count() as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_batch_buffered_edges(batch: *mut linkrs_batch_t) -> c_int {
if batch.is_null() {
return -1;
}
let handle = &*(batch as *mut LinkrsBatchHandle);
handle
.buffer
.iter()
.filter(|item| matches!(item, BatchItem::Edge(_)))
.count() as c_int
}
fn value_to_vertex_id(value: &linkrs_core::Value) -> Option<VertexId> {
use linkrs_core::Value;
match value {
Value::Int(i) => VertexId::try_from_int64(*i as i64).ok(),
Value::BigInt(i) => VertexId::try_from_int64(*i).ok(),
Value::String(s) => VertexId::try_from_string(s.as_str()).ok(),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_inserter_create_null_params() {
let result = unsafe {
linkrs_batch_inserter_create(std::ptr::null_mut(), 100, std::ptr::null_mut())
};
assert_eq!(result, linkrs_error_code_t::LINKRS_MISUSE as c_int);
}
#[test]
fn test_batch_free_null() {
unsafe { linkrs_batch_free(std::ptr::null_mut()) };
}
}