graph_d 1.3.2

A native graph database implementation in Rust with built-in JSON support and SQLite-like simplicity
Documentation
//! GQL (Graph Query Language) demonstration.
//!
//! This example shows how to use the GQL interface to query the graph database
//! using the standardized ISO/IEC 39075:2024 GQL syntax.

use graph_d::{gql::Gql, Graph, Result};
use serde_json::json;
use std::cell::RefCell;

fn main() -> Result<()> {
    println!("=== GQL (Graph Query Language) Demo ===\n");

    // Create a new graph database
    let mut graph = Graph::new()?;

    // Set up sample data
    setup_sample_data(&mut graph)?;

    // Create GQL interface (requires RefCell for interior mutability)
    let graph = RefCell::new(graph);
    let gql = Gql::new(&graph);

    // Demo 1: Basic node queries
    demo_basic_queries(&gql)?;

    // Demo 2: Path traversal queries
    demo_path_queries(&gql)?;

    // Demo 3: Property filtering
    demo_property_filtering(&gql)?;

    // Demo 4: Complex pattern matching
    demo_pattern_matching(&gql)?;

    println!("🎉 GQL demo completed successfully!");

    Ok(())
}

fn setup_sample_data(graph: &mut Graph) -> Result<()> {
    println!("Setting up sample social network data...\n");

    // Create users
    let alice_id = graph.create_node(
        [
            ("type".to_string(), json!("Person")),
            ("name".to_string(), json!("Alice")),
            ("age".to_string(), json!(30)),
            ("city".to_string(), json!("San Francisco")),
            ("occupation".to_string(), json!("Engineer")),
        ]
        .into(),
    )?;

    let bob_id = graph.create_node(
        [
            ("type".to_string(), json!("Person")),
            ("name".to_string(), json!("Bob")),
            ("age".to_string(), json!(25)),
            ("city".to_string(), json!("Seattle")),
            ("occupation".to_string(), json!("Designer")),
        ]
        .into(),
    )?;

    let charlie_id = graph.create_node(
        [
            ("type".to_string(), json!("Person")),
            ("name".to_string(), json!("Charlie")),
            ("age".to_string(), json!(35)),
            ("city".to_string(), json!("San Francisco")),
            ("occupation".to_string(), json!("Manager")),
        ]
        .into(),
    )?;

    let diana_id = graph.create_node(
        [
            ("type".to_string(), json!("Person")),
            ("name".to_string(), json!("Diana")),
            ("age".to_string(), json!(28)),
            ("city".to_string(), json!("Portland")),
            ("occupation".to_string(), json!("Artist")),
        ]
        .into(),
    )?;

    // Create companies
    let techcorp_id = graph.create_node(
        [
            ("type".to_string(), json!("Company")),
            ("name".to_string(), json!("TechCorp")),
            ("industry".to_string(), json!("Technology")),
            ("size".to_string(), json!("Large")),
        ]
        .into(),
    )?;

    let designstudio_id = graph.create_node(
        [
            ("type".to_string(), json!("Company")),
            ("name".to_string(), json!("Design Studio")),
            ("industry".to_string(), json!("Creative")),
            ("size".to_string(), json!("Small")),
        ]
        .into(),
    )?;

    // Create relationships
    graph.create_relationship(
        alice_id,
        bob_id,
        "KNOWS".to_string(),
        [("since".to_string(), json!("2020"))].into(),
    )?;

    graph.create_relationship(
        bob_id,
        charlie_id,
        "KNOWS".to_string(),
        [("since".to_string(), json!("2019"))].into(),
    )?;

    graph.create_relationship(
        charlie_id,
        diana_id,
        "KNOWS".to_string(),
        [("since".to_string(), json!("2021"))].into(),
    )?;

    graph.create_relationship(
        alice_id,
        diana_id,
        "KNOWS".to_string(),
        [("since".to_string(), json!("2022"))].into(),
    )?;

    graph.create_relationship(
        alice_id,
        techcorp_id,
        "WORKS_FOR".to_string(),
        [("position".to_string(), json!("Senior Engineer"))].into(),
    )?;

    graph.create_relationship(
        bob_id,
        designstudio_id,
        "WORKS_FOR".to_string(),
        [("position".to_string(), json!("Lead Designer"))].into(),
    )?;

    graph.create_relationship(
        charlie_id,
        techcorp_id,
        "WORKS_FOR".to_string(),
        [("position".to_string(), json!("Engineering Manager"))].into(),
    )?;

    println!(
        "✓ Created {} users and {} companies with relationships",
        4, 2
    );

    Ok(())
}

fn demo_basic_queries(gql: &Gql) -> Result<()> {
    println!("=== Demo 1: Basic Node Queries ===\n");

    // Query 1: Find all people
    println!("🔍 Query: Find all people");
    println!("MATCH (p:Person) RETURN p.name, p.age");

    match gql.execute("MATCH (p:Person) RETURN p.name, p.age") {
        Ok(result) => {
            println!("✓ Results: {} rows", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    // Query 2: Find all companies
    println!("🔍 Query: Find all companies");
    println!("MATCH (c:Company) RETURN c.name, c.industry");

    match gql.execute("MATCH (c:Company) RETURN c.name, c.industry") {
        Ok(result) => {
            println!("✓ Results: {} rows", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    Ok(())
}

fn demo_path_queries(gql: &Gql) -> Result<()> {
    println!("=== Demo 2: Path Traversal Queries ===\n");

    // Query 1: Find who knows whom
    println!("🔍 Query: Find friendship connections");
    println!("MATCH (p1:Person)-[:KNOWS]->(p2:Person) RETURN p1.name, p2.name");

    match gql.execute("MATCH (p1:Person)-[:KNOWS]->(p2:Person) RETURN p1.name, p2.name") {
        Ok(result) => {
            println!("✓ Results: {} relationships", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    // Query 2: Find employment relationships
    println!("🔍 Query: Find employment relationships");
    println!("MATCH (p:Person)-[:WORKS_FOR]->(c:Company) RETURN p.name, c.name");

    match gql.execute("MATCH (p:Person)-[:WORKS_FOR]->(c:Company) RETURN p.name, c.name") {
        Ok(result) => {
            println!("✓ Results: {} employment relationships", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    Ok(())
}

fn demo_property_filtering(gql: &Gql) -> Result<()> {
    println!("=== Demo 3: Property Filtering ===\n");

    // Query 1: Find people over 30
    println!("🔍 Query: Find people over 30");
    println!("MATCH (p:Person) WHERE p.age > 30 RETURN p.name, p.age");

    match gql.execute("MATCH (p:Person) WHERE p.age > 30 RETURN p.name, p.age") {
        Ok(result) => {
            println!("✓ Results: {} people", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    // Query 2: Find people in San Francisco
    println!("🔍 Query: Find people in San Francisco");
    println!("MATCH (p:Person) WHERE p.city = 'San Francisco' RETURN p.name, p.occupation");

    match gql.execute("MATCH (p:Person) WHERE p.city = 'San Francisco' RETURN p.name, p.occupation")
    {
        Ok(result) => {
            println!("✓ Results: {} people", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    Ok(())
}

fn demo_pattern_matching(gql: &Gql) -> Result<()> {
    println!("=== Demo 4: Complex Pattern Matching ===\n");

    // Query 1: Find coworkers (people who work at the same company)
    println!("🔍 Query: Find coworkers");
    println!("MATCH (p1:Person)-[:WORKS_FOR]->(c:Company)<-[:WORKS_FOR]-(p2:Person) RETURN p1.name, p2.name, c.name");

    match gql.execute("MATCH (p1:Person)-[:WORKS_FOR]->(c:Company)<-[:WORKS_FOR]-(p2:Person) RETURN p1.name, p2.name, c.name") {
        Ok(result) => {
            println!("✓ Results: {} coworker relationships", result.row_count());
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    // Query 2: Find friends of friends
    println!("🔍 Query: Find friends of friends");
    println!("MATCH (p:Person)-[:KNOWS]->()-[:KNOWS]->(fof:Person) RETURN p.name, fof.name");

    match gql
        .execute("MATCH (p:Person)-[:KNOWS]->()-[:KNOWS]->(fof:Person) RETURN p.name, fof.name")
    {
        Ok(result) => {
            println!(
                "✓ Results: {} friend-of-friend relationships",
                result.row_count()
            );
            print_query_result(&result);
        }
        Err(e) => println!("❌ Error: {e}"),
    }

    println!();

    Ok(())
}

fn print_query_result(result: &graph_d::gql::QueryResult) {
    if result.is_empty() {
        println!("  (no results)");
        return;
    }

    // Print column headers
    print!("  ");
    for (i, column) in result.columns.iter().enumerate() {
        if i > 0 {
            print!(" | ");
        }
        print!("{column:<15}");
    }
    println!();

    // Print separator
    print!("  ");
    for (i, _) in result.columns.iter().enumerate() {
        if i > 0 {
            print!("-+-");
        }
        print!("{}", "-".repeat(15));
    }
    println!();

    // Print data rows
    for row in &result.rows {
        print!("  ");
        for (i, value) in row.iter().enumerate() {
            if i > 0 {
                print!(" | ");
            }
            let display_value = format_query_value(value);
            print!("{display_value:<15}");
        }
        println!();
    }

    println!();
}

fn format_query_value(value: &graph_d::gql::QueryValue) -> String {
    match value {
        graph_d::gql::QueryValue::Null => "NULL".to_string(),
        graph_d::gql::QueryValue::Boolean(b) => b.to_string(),
        graph_d::gql::QueryValue::Integer(i) => i.to_string(),
        graph_d::gql::QueryValue::Float(f) => format!("{f:.2}"),
        graph_d::gql::QueryValue::String(s) => s.clone(),
        graph_d::gql::QueryValue::Node { id, .. } => format!("Node({id})"),
        graph_d::gql::QueryValue::Relationship { id, .. } => format!("Rel({id})"),
        graph_d::gql::QueryValue::Path(_) => "Path".to_string(),
        graph_d::gql::QueryValue::List(l) => format!("List[{}]", l.len()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_gql_basic_functionality() -> Result<()> {
        let mut graph = Graph::new()?;
        setup_sample_data(&mut graph)?;

        let gql = Gql::new(&graph);

        // Test basic node query
        let result = gql.execute("MATCH (p:Person) RETURN p.name")?;
        assert!(result.row_count() > 0);
        assert_eq!(result.columns.len(), 1);
        assert_eq!(result.columns[0], "p.name");

        Ok(())
    }

    #[test]
    fn test_gql_parsing() -> Result<()> {
        let graph = Graph::new()?;
        let gql = Gql::new(&graph);

        // Test query parsing without execution
        let ast = gql.parse("MATCH (n:Person) WHERE n.age > 25 RETURN n.name")?;

        // Verify the AST structure
        if let graph_d::gql::GqlStatement::LinearQuery(query) = ast {
            assert_eq!(query.clauses.len(), 3); // MATCH, WHERE, RETURN
        } else {
            panic!("Expected LinearQuery");
        }

        Ok(())
    }
}