361 lines
9.0 KiB
V
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
|
|
}
|