uni-db 1.1.0

Embedded graph database with OpenCypher queries, vector search, and columnar storage
Documentation
// Micro-benchmark: String interpolation vs Parameterized queries
// Run with: cargo run --release --example query_param_bench

use serde_json::json;
use std::time::Instant;
use uni_db::{Uni, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("๐Ÿ”ฌ Query Parameter Benchmark: String Interpolation vs Parameterized\n");
    println!("Testing: 50 CREATE queries with 128-dim embeddings\n");

    const ITERATIONS: usize = 50;

    // Setup
    let embedding: Vec<f32> = (0..128).map(|x| x as f32).collect();

    // Create two separate databases for clean comparison
    let db1 = Uni::open("/tmp/uni_bench1").build().await?;
    let db2 = Uni::open("/tmp/uni_bench2").build().await?;

    // Schema is created dynamically on first insert

    println!("๐Ÿ“Š Approach 1: String Interpolation (current)\n");
    println!("   Query example:");
    let embedding_str = json!(embedding).to_string();
    let example = format!(
        "CREATE (n:Person {{name: 'Bench_0', age: 30, embedding: {}}})",
        &embedding_str[..60] // Show first 60 chars
    );
    println!("   {}", example);
    println!(
        "   Query length: ~{} bytes\n",
        format!(
            "CREATE (n:Person {{name: 'Bench_0', age: 30, embedding: {}}})",
            embedding_str
        )
        .len()
    );

    // Benchmark 1: String interpolation (current approach)
    let start = Instant::now();
    for i in 0..ITERATIONS {
        let embedding_str = json!(embedding).to_string();
        let cypher = format!(
            "CREATE (n:Person {{name: 'Bench_{}', age: 30, embedding: {}}})",
            i, embedding_str
        );
        let s = db1.session();
        let tx = s.tx().await?;
        tx.execute(&cypher).await?;
        tx.commit().await?;
    }
    let duration1 = start.elapsed();
    let per_query1 = duration1.as_micros() as f64 / ITERATIONS as f64;

    println!("โœ… Completed: {} queries in {:?}", ITERATIONS, duration1);
    println!("   Average: {:.1}ยตs per query\n", per_query1);

    println!("---\n");

    println!("๐Ÿ“Š Approach 2: Parameterized Queries\n");
    println!("   Query example:");
    println!("   CREATE (n:Person {{name: $name, age: $age, embedding: $embedding}})");
    println!("   Query length: ~70 bytes");
    println!("   + Parameters passed as structured data\n");

    // Benchmark 2: Parameterized queries
    let embedding_value = Value::List(embedding.iter().map(|&f| Value::Float(f as f64)).collect());

    let start = Instant::now();
    let cypher = "CREATE (n:Person {name: $name, age: $age, embedding: $embedding})";

    for i in 0..ITERATIONS {
        db2.session()
            .query_with(cypher)
            .param("name", Value::String(format!("Bench_{}", i)))
            .param("age", Value::Int(30))
            .param("embedding", embedding_value.clone())
            .fetch_all()
            .await?;
    }
    let duration2 = start.elapsed();
    let per_query2 = duration2.as_micros() as f64 / ITERATIONS as f64;

    println!("โœ… Completed: {} queries in {:?}", ITERATIONS, duration2);
    println!("   Average: {:.1}ยตs per query\n", per_query2);

    println!("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
    println!("\n๐Ÿ“ˆ RESULTS\n");

    println!("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
    println!("โ”‚ Approach                โ”‚ Total Time   โ”‚ Per Query    โ”‚");
    println!("โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค");
    println!(
        "โ”‚ String Interpolation    โ”‚ {:>9.2}ms โ”‚ {:>9.1}ยตs โ”‚",
        duration1.as_micros() as f64 / 1000.0,
        per_query1
    );
    println!(
        "โ”‚ Parameterized Queries   โ”‚ {:>9.2}ms โ”‚ {:>9.1}ยตs โ”‚",
        duration2.as_micros() as f64 / 1000.0,
        per_query2
    );
    println!("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜");

    let speedup = per_query1 / per_query2;
    let savings_us = per_query1 - per_query2;
    let savings_pct = (savings_us / per_query1) * 100.0;

    println!("\n๐ŸŽฏ Performance Improvement:");
    println!("   Speedup: {:.2}x faster", speedup);
    println!(
        "   Savings: {:.1}ยตs per query ({:.1}% reduction)",
        savings_us, savings_pct
    );

    if savings_pct > 5.0 {
        println!("\nโœ… Parameterized queries are significantly faster!");
    } else {
        println!("\nโš ๏ธ  Difference is small - other factors may dominate.");
    }

    println!("\nโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");

    // Detailed breakdown
    println!("\n๐Ÿ” Overhead Breakdown (String Interpolation):\n");

    // Measure just JSON serialization
    let start = Instant::now();
    for _ in 0..ITERATIONS {
        let _embedding_str = json!(embedding).to_string();
    }
    let json_overhead = start.elapsed().as_micros() as f64 / ITERATIONS as f64;
    println!("   JSON serialization:     {:.1}ยตs", json_overhead);

    // Measure string formatting
    let embedding_str = json!(embedding).to_string();
    let start = Instant::now();
    for i in 0..ITERATIONS {
        let _cypher = format!(
            "CREATE (n:Person {{name: 'Bench_{}', age: 30, embedding: {}}})",
            i, embedding_str
        );
    }
    let format_overhead = start.elapsed().as_micros() as f64 / ITERATIONS as f64;
    println!("   String concatenation:   {:.1}ยตs", format_overhead);

    let parse_execute_overhead = per_query1 - json_overhead - format_overhead;
    println!("   Parse + Execute:        {:.1}ยตs", parse_execute_overhead);
    println!("   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
    println!("   Total:                  {:.1}ยตs\n", per_query1);

    println!("๐Ÿ” Overhead Breakdown (Parameterized):\n");

    // Measure parameter preparation
    let start = Instant::now();
    for i in 0..ITERATIONS {
        let _name = Value::String(format!("Bench_{}", i));
        let _age = Value::Int(30);
        let _embedding = embedding_value.clone();
    }
    let param_overhead = start.elapsed().as_micros() as f64 / ITERATIONS as f64;
    println!("   Parameter preparation:  {:.1}ยตs", param_overhead);

    let parse_execute_overhead2 = per_query2 - param_overhead;
    println!(
        "   Parse + Execute:        {:.1}ยตs",
        parse_execute_overhead2
    );
    println!("   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
    println!("   Total:                  {:.1}ยตs\n", per_query2);

    println!("๐Ÿ’ก Key Insight:");
    let parsing_savings = parse_execute_overhead - parse_execute_overhead2;
    if parsing_savings > 0.0 {
        println!(
            "   Parsing is {:.1}ยตs faster with parameters ({:.1}% reduction)",
            parsing_savings,
            (parsing_savings / parse_execute_overhead) * 100.0
        );
        println!("   This confirms that parsing large array literals is expensive!");
    }

    Ok(())
}