Files
herolib/lib/data/graphdb/graphdb.v
2025-01-31 15:39:44 +03:00

361 lines
9.0 KiB
V

module graphdb
import freeflowuniverse.herolib.data.ourdb
import freeflowuniverse.herolib.data.cache { Cache, CacheConfig, new_cache }
// Node represents a vertex in the graph with properties and edge references
@[heap]
pub struct Node {
pub mut:
id u32 // Unique identifier
properties map[string]string // Key-value properties
node_type string // Type of node can e.g. refer to a object implementation e.g. a User, ...
edges_out []EdgeRef // Outgoing edge references
edges_in []EdgeRef // Incoming edge references
}
// Edge represents a connection between nodes with properties
@[heap]
pub struct Edge {
pub mut:
id u32 // Unique identifier
from_node u32 // Source node ID
to_node u32 // Target node ID
edge_type string // Type of relationship
properties map[string]string // Key-value properties
weight u16 // weight of the connection between the objects
}
// EdgeRef is a lightweight reference to an edge
@[heap]
pub struct EdgeRef {
pub mut:
edge_id u32 // Database ID of the edge
edge_type string // Type of the edge relationship
}
// GraphDB represents the graph database
pub struct GraphDB {
mut:
db &ourdb.OurDB // Database for persistent storage
node_cache &Cache[Node] // Cache for nodes
edge_cache &Cache[Edge] // Cache for edges
}
pub struct NewArgs {
pub mut:
path string
reset bool
cache_config CacheConfig = CacheConfig{} // Default cache configuration
}
// Creates a new graph database instance
pub fn new(args NewArgs) !&GraphDB {
mut db := ourdb.new(
path: args.path
record_size_max: 1024 * 4 // 4KB max record size
incremental_mode: true
reset: args.reset
)!
// Create type-specific caches with provided config
node_cache := new_cache[Node](args.cache_config)
edge_cache := new_cache[Edge](args.cache_config)
return &GraphDB{
db: &db
node_cache: node_cache
edge_cache: edge_cache
}
}
// Creates a new node with the given properties
pub fn (mut gdb GraphDB) create_node(properties map[string]string) !u32 {
mut node := Node{
properties: properties
edges_out: []EdgeRef{}
edges_in: []EdgeRef{}
}
// Let OurDB assign the ID in incremental mode
node_id := gdb.db.set(data: serialize_node(node))!
// Update node with assigned ID and cache it
node.id = node_id
gdb.node_cache.set(node_id, &node)
return node_id
}
// Creates an edge between two nodes
pub fn (mut gdb GraphDB) create_edge(from_id u32, to_id u32, edge_type string, properties map[string]string) !u32 {
// Create the edge
mut edge := Edge{
from_node: from_id
to_node: to_id
edge_type: edge_type
properties: properties
}
// Let OurDB assign the ID in incremental mode
edge_id := gdb.db.set(data: serialize_edge(edge))!
// Update edge with assigned ID and cache it
edge.id = edge_id
gdb.edge_cache.set(edge_id, &edge)
// Update source node's outgoing edges
mut from_node := deserialize_node(gdb.db.get(from_id)!)!
from_node.edges_out << EdgeRef{
edge_id: edge_id
edge_type: edge_type
}
gdb.db.set(id: from_id, data: serialize_node(from_node))!
gdb.node_cache.set(from_id, &from_node)
// Update target node's incoming edges
mut to_node := deserialize_node(gdb.db.get(to_id)!)!
to_node.edges_in << EdgeRef{
edge_id: edge_id
edge_type: edge_type
}
gdb.db.set(id: to_id, data: serialize_node(to_node))!
gdb.node_cache.set(to_id, &to_node)
return edge_id
}
// Gets a node by its ID
pub fn (mut gdb GraphDB) get_node(id u32) !Node {
// Try cache first
if cached_node := gdb.node_cache.get(id) {
return *cached_node
}
// Load from database
node_data := gdb.db.get(id)!
node := deserialize_node(node_data)!
// Cache the node
gdb.node_cache.set(id, &node)
return node
}
// Gets an edge by its ID
pub fn (mut gdb GraphDB) get_edge(id u32) !Edge {
// Try cache first
if cached_edge := gdb.edge_cache.get(id) {
return *cached_edge
}
// Load from database
edge_data := gdb.db.get(id)!
edge := deserialize_edge(edge_data)!
// Cache the edge
gdb.edge_cache.set(id, &edge)
return edge
}
// Updates a node's properties
pub fn (mut gdb GraphDB) update_node(id u32, properties map[string]string) ! {
mut node := deserialize_node(gdb.db.get(id)!)!
node.properties = properties.clone()
// Update database
gdb.db.set(id: id, data: serialize_node(node))!
// Update cache
gdb.node_cache.set(id, &node)
}
// Updates an edge's properties
pub fn (mut gdb GraphDB) update_edge(id u32, properties map[string]string) ! {
mut edge := deserialize_edge(gdb.db.get(id)!)!
edge.properties = properties.clone()
// Update database
gdb.db.set(id: id, data: serialize_edge(edge))!
// Update cache
gdb.edge_cache.set(id, &edge)
}
// Deletes a node and all its edges
pub fn (mut gdb GraphDB) delete_node(id u32) ! {
node := deserialize_node(gdb.db.get(id)!)!
// Delete outgoing edges
for edge_ref in node.edges_out {
gdb.delete_edge(edge_ref.edge_id)!
}
// Delete incoming edges
for edge_ref in node.edges_in {
gdb.delete_edge(edge_ref.edge_id)!
}
// Delete from database
gdb.db.delete(id)!
// Remove from cache
gdb.node_cache.remove(id)
}
// Deletes an edge and updates connected nodes
pub fn (mut gdb GraphDB) delete_edge(id u32) ! {
edge := deserialize_edge(gdb.db.get(id)!)!
// Update source node
mut from_node := deserialize_node(gdb.db.get(edge.from_node)!)!
for i, edge_ref in from_node.edges_out {
if edge_ref.edge_id == id {
from_node.edges_out.delete(i)
break
}
}
gdb.db.set(id: edge.from_node, data: serialize_node(from_node))!
gdb.node_cache.set(edge.from_node, &from_node)
// Update target node
mut to_node := deserialize_node(gdb.db.get(edge.to_node)!)!
for i, edge_ref in to_node.edges_in {
if edge_ref.edge_id == id {
to_node.edges_in.delete(i)
break
}
}
gdb.db.set(id: edge.to_node, data: serialize_node(to_node))!
gdb.node_cache.set(edge.to_node, &to_node)
// Delete from database and cache
gdb.db.delete(id)!
gdb.edge_cache.remove(id)
}
// Queries nodes by property value
pub fn (mut gdb GraphDB) query_nodes_by_property(key string, value string) ![]Node {
mut nodes := []Node{}
mut next_id := gdb.db.get_next_id()!
// Process each ID up to next_id
for id := u32(0); id < next_id; id++ {
// Try to get from cache first
if cached := gdb.node_cache.get(id) {
if prop_value := cached.properties[key] {
if prop_value == value {
nodes << *cached
}
}
continue
}
// Not in cache, try to get from database
raw_data := gdb.db.get(id) or { continue }
mut node := deserialize_node(raw_data) or { continue }
// Cache the node for future use
gdb.node_cache.set(id, &node)
// Check if this node matches the query
if prop_value := node.properties[key] {
if prop_value == value {
nodes << node
}
}
}
return nodes
}
// Gets all edges between two nodes
pub fn (mut gdb GraphDB) get_edges_between(from_id u32, to_id u32) ![]Edge {
mut from_node := if cached := gdb.node_cache.get(from_id) {
*cached
} else {
node := deserialize_node(gdb.db.get(from_id)!)!
gdb.node_cache.set(from_id, &node)
node
}
mut edges := []Edge{}
for edge_ref in from_node.edges_out {
edge_data := if cached := gdb.edge_cache.get(edge_ref.edge_id) {
*cached
} else {
mut edge := deserialize_edge(gdb.db.get(edge_ref.edge_id)!)!
gdb.edge_cache.set(edge_ref.edge_id, &edge)
edge
}
if edge_data.to_node == to_id {
edges << edge_data
}
}
return edges
}
// Gets all nodes connected to a given node by edge type
pub fn (mut gdb GraphDB) get_connected_nodes(id u32, edge_type string, direction string) ![]Node {
mut start_node := if cached := gdb.node_cache.get(id) {
*cached
} else {
node := deserialize_node(gdb.db.get(id)!)!
gdb.node_cache.set(id, &node)
node
}
mut connected_nodes := []Node{}
if direction in ['out', 'both'] {
for edge_ref in start_node.edges_out {
if edge_ref.edge_type == edge_type {
edge_data := if cached := gdb.edge_cache.get(edge_ref.edge_id) {
*cached
} else {
mut edge := deserialize_edge(gdb.db.get(edge_ref.edge_id)!)!
gdb.edge_cache.set(edge_ref.edge_id, &edge)
edge
}
mut target_node := if cached := gdb.node_cache.get(edge_data.to_node) {
*cached
} else {
node := deserialize_node(gdb.db.get(edge_data.to_node)!)!
gdb.node_cache.set(edge_data.to_node, &node)
node
}
connected_nodes << target_node
}
}
}
if direction in ['in', 'both'] {
for edge_ref in start_node.edges_in {
if edge_ref.edge_type == edge_type {
edge_data := if cached := gdb.edge_cache.get(edge_ref.edge_id) {
*cached
} else {
mut edge := deserialize_edge(gdb.db.get(edge_ref.edge_id)!)!
gdb.edge_cache.set(edge_ref.edge_id, &edge)
edge
}
mut source_node := if cached := gdb.node_cache.get(edge_data.from_node) {
*cached
} else {
node := deserialize_node(gdb.db.get(edge_data.from_node)!)!
gdb.node_cache.set(edge_data.from_node, &node)
node
}
connected_nodes << source_node
}
}
}
return connected_nodes
}