use arrow::record_batch::RecordBatch;
use arrow::datatypes::{DataType, Field, Schema};
use std::sync::Arc;
use std::path::Path;
use crate::error::{GraphError, Result};
use crate::graph::GraphIndexes;
#[derive(Debug, Clone)]
pub struct ArrowGraph {
pub nodes: RecordBatch,
pub edges: RecordBatch,
pub indexes: GraphIndexes,
}
impl ArrowGraph {
pub fn new(nodes: RecordBatch, edges: RecordBatch) -> Result<Self> {
let indexes = GraphIndexes::build(&nodes, &edges)?;
Ok(ArrowGraph {
nodes,
edges,
indexes,
})
}
pub fn from_edges(edges: RecordBatch) -> Result<Self> {
let nodes_schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
]));
let empty_nodes = RecordBatch::new_empty(nodes_schema);
Self::new(empty_nodes, edges)
}
pub fn empty() -> Result<Self> {
let nodes_schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
]));
let empty_nodes = RecordBatch::new_empty(nodes_schema);
let edges_schema = Arc::new(Schema::new(vec![
Field::new("source", DataType::Utf8, false),
Field::new("target", DataType::Utf8, false),
Field::new("weight", DataType::Float64, true),
]));
let empty_edges = RecordBatch::new_empty(edges_schema);
Self::new(empty_nodes, empty_edges)
}
pub async fn from_files<P: AsRef<Path>>(
_nodes_path: P,
_edges_path: P,
) -> Result<Self> {
todo!("Implement loading from files - will read Arrow/Parquet files")
}
pub fn from_tables(
nodes: RecordBatch,
edges: RecordBatch,
) -> Result<Self> {
Self::new(nodes, edges)
}
pub async fn sql(&self, _query: &str) -> Result<RecordBatch> {
todo!("Implement SQL execution using DataFusion with graph functions")
}
pub fn node_count(&self) -> usize {
self.indexes.node_count
}
pub fn edge_count(&self) -> usize {
self.indexes.edge_count
}
pub fn density(&self) -> f64 {
let n = self.node_count() as f64;
let m = self.edge_count() as f64;
if n <= 1.0 {
0.0
} else {
m / (n * (n - 1.0))
}
}
pub fn neighbors(&self, node_id: &str) -> Option<&Vec<String>> {
self.indexes.neighbors(node_id)
}
pub fn predecessors(&self, node_id: &str) -> Option<&Vec<String>> {
self.indexes.predecessors(node_id)
}
pub fn has_node(&self, node_id: &str) -> bool {
self.indexes.has_node(node_id)
}
pub fn edge_weight(&self, source: &str, target: &str) -> Option<f64> {
self.indexes.edge_weight(source, target)
}
pub fn node_ids(&self) -> impl Iterator<Item = &String> {
self.indexes.all_nodes()
}
pub fn add_node(&mut self, node_id: String) -> Result<()> {
if self.has_node(&node_id) {
return Err(GraphError::invalid_parameter(
&format!("Node '{}' already exists in the graph", node_id)
));
}
let mut node_ids: Vec<String> = self.node_ids().cloned().collect();
node_ids.push(node_id);
let nodes_schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
]));
let new_nodes = RecordBatch::try_new(
nodes_schema,
vec![Arc::new(arrow::array::StringArray::from(node_ids))],
).map_err(GraphError::from)?;
self.nodes = new_nodes;
self.indexes = GraphIndexes::build(&self.nodes, &self.edges)?;
Ok(())
}
pub fn remove_node(&mut self, node_id: &str) -> Result<()> {
if !self.has_node(node_id) {
return Err(GraphError::invalid_parameter(
&format!("Node '{}' does not exist in the graph", node_id)
));
}
let remaining_nodes: Vec<String> = self.node_ids()
.filter(|&id| id != node_id)
.cloned()
.collect();
let nodes_schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
]));
let new_nodes = RecordBatch::try_new(
nodes_schema,
vec![Arc::new(arrow::array::StringArray::from(remaining_nodes))],
).map_err(GraphError::from)?;
let source_array = self.edges.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid source column type"))?;
let target_array = self.edges.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid target column type"))?;
let mut remaining_sources = Vec::new();
let mut remaining_targets = Vec::new();
let mut remaining_weights = Vec::new();
for i in 0..self.edges.num_rows() {
let source = source_array.value(i);
let target = target_array.value(i);
if source != node_id && target != node_id {
remaining_sources.push(source.to_string());
remaining_targets.push(target.to_string());
if self.edges.num_columns() > 2 {
if let Some(weight_array) = self.edges.column(2)
.as_any()
.downcast_ref::<arrow::array::Float64Array>()
{
remaining_weights.push(weight_array.value(i));
}
}
}
}
let mut edge_fields = vec![
Field::new("source", DataType::Utf8, false),
Field::new("target", DataType::Utf8, false),
];
let mut edge_columns: Vec<Arc<dyn arrow::array::Array>> = vec![
Arc::new(arrow::array::StringArray::from(remaining_sources)),
Arc::new(arrow::array::StringArray::from(remaining_targets)),
];
if !remaining_weights.is_empty() {
edge_fields.push(Field::new("weight", DataType::Float64, true));
edge_columns.push(Arc::new(arrow::array::Float64Array::from(remaining_weights)));
}
let edges_schema = Arc::new(Schema::new(edge_fields));
let new_edges = RecordBatch::try_new(edges_schema, edge_columns)
.map_err(GraphError::from)?;
self.nodes = new_nodes;
self.edges = new_edges;
self.indexes = GraphIndexes::build(&self.nodes, &self.edges)?;
Ok(())
}
pub fn add_edge(&mut self, source: String, target: String, weight: Option<f64>) -> Result<()> {
if !self.has_node(&source) {
self.add_node(source.clone())?;
}
if !self.has_node(&target) {
self.add_node(target.clone())?;
}
if self.edge_weight(&source, &target).is_some() {
return Err(GraphError::invalid_parameter(
&format!("Edge from '{}' to '{}' already exists", source, target)
));
}
let source_array = self.edges.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid source column type"))?;
let target_array = self.edges.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid target column type"))?;
let mut new_sources: Vec<String> = source_array.iter()
.map(|s| s.unwrap_or("").to_string())
.collect();
let mut new_targets: Vec<String> = target_array.iter()
.map(|t| t.unwrap_or("").to_string())
.collect();
new_sources.push(source);
new_targets.push(target);
let mut edge_fields = vec![
Field::new("source", DataType::Utf8, false),
Field::new("target", DataType::Utf8, false),
];
let mut edge_columns: Vec<Arc<dyn arrow::array::Array>> = vec![
Arc::new(arrow::array::StringArray::from(new_sources)),
Arc::new(arrow::array::StringArray::from(new_targets)),
];
if self.edges.num_columns() > 2 || weight.is_some() {
let mut new_weights = Vec::new();
if self.edges.num_columns() > 2 {
if let Some(weight_array) = self.edges.column(2)
.as_any()
.downcast_ref::<arrow::array::Float64Array>()
{
for i in 0..weight_array.len() {
new_weights.push(Some(weight_array.value(i)));
}
}
} else {
for _ in 0..self.edges.num_rows() {
new_weights.push(None);
}
}
new_weights.push(weight);
edge_fields.push(Field::new("weight", DataType::Float64, true));
edge_columns.push(Arc::new(arrow::array::Float64Array::from(new_weights)));
}
let edges_schema = Arc::new(Schema::new(edge_fields));
let new_edges = RecordBatch::try_new(edges_schema, edge_columns)
.map_err(GraphError::from)?;
self.edges = new_edges;
self.indexes = GraphIndexes::build(&self.nodes, &self.edges)?;
Ok(())
}
pub fn remove_edge(&mut self, source: &str, target: &str) -> Result<()> {
if self.edge_weight(source, target).is_none() {
return Err(GraphError::invalid_parameter(
&format!("Edge from '{}' to '{}' does not exist", source, target)
));
}
let source_array = self.edges.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid source column type"))?;
let target_array = self.edges.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| GraphError::invalid_parameter("Invalid target column type"))?;
let mut remaining_sources = Vec::new();
let mut remaining_targets = Vec::new();
let mut remaining_weights = Vec::new();
for i in 0..self.edges.num_rows() {
let edge_source = source_array.value(i);
let edge_target = target_array.value(i);
if !(edge_source == source && edge_target == target) {
remaining_sources.push(edge_source.to_string());
remaining_targets.push(edge_target.to_string());
if self.edges.num_columns() > 2 {
if let Some(weight_array) = self.edges.column(2)
.as_any()
.downcast_ref::<arrow::array::Float64Array>()
{
remaining_weights.push(Some(weight_array.value(i)));
}
}
}
}
let mut edge_fields = vec![
Field::new("source", DataType::Utf8, false),
Field::new("target", DataType::Utf8, false),
];
let mut edge_columns: Vec<Arc<dyn arrow::array::Array>> = vec![
Arc::new(arrow::array::StringArray::from(remaining_sources)),
Arc::new(arrow::array::StringArray::from(remaining_targets)),
];
if !remaining_weights.is_empty() {
edge_fields.push(Field::new("weight", DataType::Float64, true));
edge_columns.push(Arc::new(arrow::array::Float64Array::from(remaining_weights)));
}
let edges_schema = Arc::new(Schema::new(edge_fields));
let new_edges = RecordBatch::try_new(edges_schema, edge_columns)
.map_err(GraphError::from)?;
self.edges = new_edges;
self.indexes = GraphIndexes::build(&self.nodes, &self.edges)?;
Ok(())
}
}