graphqlite 0.6.0

SQLite extension for graph queries using Cypher
Documentation
//! Node operations for Graph.

use super::Graph;
use crate::utils::{escape_string, PropertyValue};
use crate::{Result, Value};

impl Graph {
    /// Check if a node with the given ID exists.
    pub fn has_node(&self, node_id: &str) -> Result<bool> {
        let result = self
            .connection()
            .cypher_builder("MATCH (n {id: $id}) RETURN count(n) AS cnt")
            .param("id", node_id)
            .run()?;
        if result.is_empty() {
            return Ok(false);
        }
        let cnt: i64 = result[0].get("cnt").unwrap_or(0);
        Ok(cnt > 0)
    }

    /// Get a node by ID.
    ///
    /// Returns the node as a [`Value`], or `None` if not found.
    pub fn get_node(&self, node_id: &str) -> Result<Option<Value>> {
        let result = self
            .connection()
            .cypher_builder("MATCH (n {id: $id}) RETURN n")
            .param("id", node_id)
            .run()?;
        if result.is_empty() {
            return Ok(None);
        }
        Ok(result[0].get_value("n").cloned())
    }

    /// Create or update a node.
    ///
    /// If a node with the given id exists, its properties are updated.
    /// Otherwise, a new node is created.
    pub fn upsert_node<I, K, V>(&self, node_id: &str, props: I, label: &str) -> Result<()>
    where
        I: IntoIterator<Item = (K, V)>,
        K: AsRef<str>,
        V: Into<PropertyValue>,
    {
        let props: Vec<(String, PropertyValue)> = props
            .into_iter()
            .map(|(k, v)| (k.as_ref().to_string(), v.into()))
            .collect();

        if self.has_node(node_id)? {
            // Update existing node
            for (k, v) in props {
                let query = format!(
                    "MATCH (n {{id: $id}}) SET n.{} = {} RETURN n",
                    k,
                    v.to_cypher()
                );
                self.connection()
                    .cypher_builder(&query)
                    .param("id", node_id)
                    .run()?;
            }
        } else {
            // Create new node
            let mut prop_parts = vec![format!("id: '{}'", escape_string(node_id))];
            for (k, v) in props {
                prop_parts.push(format!("{}: {}", k, v.to_cypher()));
            }
            let prop_str = prop_parts.join(", ");
            let query = format!("CREATE (n:{} {{{}}})", label, prop_str);
            self.connection().cypher(&query)?;
        }
        Ok(())
    }

    /// Delete a node and all its relationships.
    pub fn delete_node(&self, node_id: &str) -> Result<()> {
        self.connection()
            .cypher_builder("MATCH (n {id: $id}) DETACH DELETE n")
            .param("id", node_id)
            .run()?;
        Ok(())
    }

    /// Get all nodes, optionally filtered by label.
    pub fn get_all_nodes(&self, label: Option<&str>) -> Result<Vec<Value>> {
        let query = match label {
            Some(l) => format!("MATCH (n:{}) RETURN n", l),
            None => "MATCH (n) RETURN n".to_string(),
        };
        let result = self.connection().cypher(&query)?;
        let mut nodes = Vec::new();
        for row in result.iter() {
            if let Some(n) = row.get_value("n") {
                nodes.push(n.clone());
            }
        }
        Ok(nodes)
    }
}