# SochDB Rust Client

[![Crates.io](https://img.shields.io/crates/v/sochdb.svg)](https://crates.io/crates/sochdb)
[![Documentation](https://docs.rs/sochdb/badge.svg)](https://docs.rs/sochdb)
[![License](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Rust 1.70+](https://img.shields.io/badge/rust-1.70+-orange.svg)](https://www.rust-lang.org)

The official Rust client SDK for **SochDB** — a high-performance embedded document database with HNSW vector search, built-in multi-tenancy, and SQL support.

## Features

- ✅ **Zero-Copy Reads** — Direct access to memory-mapped data
- ✅ **Native Vector Search** — Built-in HNSW index for embeddings
- ✅ **SQL Support** — Full SQL via sochdb-query integration
- ✅ **IPC Client** — Connect to SochDB server (async)
- ✅ **Multi-Tenancy** — Efficient prefix scanning for data isolation
- ✅ **ACID Transactions** — Snapshot isolation with automatic commit/abort
- ✅ **Thread-Safe** — Safe concurrent access with MVCC
- ✅ **Columnar Storage** — Efficient for analytical queries

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
sochdb = "0.3"  # Or specific version like "0.3.0"
tokio = { version = "1", features = ["full"] }  # For async IPC
```

## What's New in Latest Release

### 🎯 Namespace Isolation
Logical database namespaces for true multi-tenancy without key prefixing:

```rust
use sochdb::{Database, NamespaceHandle};

let db = Database::open("./my_database")?;

// Create isolated namespaces
let user_db = db.namespace("users")?;
let orders_db = db.namespace("orders")?;

// Keys don't collide across namespaces
user_db.put(b"123", b r#"{"name":"Alice"}"#)?;
orders_db.put(b"123", b r#"{"total":500}"#)?;  // Different "123"!

// Each namespace has isolated collections
user_db.create_collection("profiles", CollectionConfig {
    vector_dim: 384,
    index_type: IndexType::HNSW,
    metric: DistanceMetric::Cosine,
    ..Default::default()
})?;
```

### 🔍 Hybrid Search
Combine dense vectors (HNSW) with sparse BM25 text search:

```rust
use sochdb_vector::{HybridSearchEngine, HybridQuery, RRFFusion};

// Create collection with hybrid search
let config = CollectionConfig {
    vector_dim: 384,
    index_type: IndexType::HNSW,
    enable_bm25: true,  // Enable text search
    ..Default::default()
};
let collection = db.create_collection("documents", config)?;

// Insert documents with text and vectors
let doc = Document {
    id: "doc1".to_string(),
    text: Some("Machine learning models for NLP tasks".to_string()),
    vector: vec![0.1, 0.2, 0.3, /* ... 384 dims */],
};
collection.insert(&doc)?;

// Hybrid search (vector + text)
let query = HybridQuery {
    vector: query_embedding,
    text: Some("NLP transformer".to_string()),
    k: 10,
    alpha: 0.7,      // 70% vector, 30% BM25
    rrf_fusion: true, // Reciprocal Rank Fusion
};
let results = collection.hybrid_search(&query)?;
```

### 📄 Multi-Vector Documents
Store multiple embeddings per document (e.g., title + content):

```rust
use sochdb_vector::MultiVectorDocument;
use std::collections::HashMap;

// Insert document with multiple vectors
let mut vectors = HashMap::new();
vectors.insert("title".to_string(), title_embedding);
vectors.insert("abstract".to_string(), abstract_embedding);
vectors.insert("content".to_string(), content_embedding);

let multi_doc = MultiVectorDocument {
    id: "article1".to_string(),
    text: Some("Deep Learning: A Survey".to_string()),
    vectors,
};
collection.insert_multi_vector(&multi_doc)?;

// Search with aggregation strategy
let mut query_vectors = HashMap::new();
query_vectors.insert("title".to_string(), query_title_embedding);
query_vectors.insert("content".to_string(), query_content_embedding);

let results = collection.multi_vector_search(
    &query_vectors,
    10,  // k
    AggregationStrategy::MaxPooling  // or MeanPooling, WeightedSum
)?;
```

### 🧩 Context-Aware Queries
Optimize retrieval for LLM context windows:

```rust
use sochdb::ContextQuery;

// Query with token budget
let config = ContextQueryConfig {
    vector: query_embedding,
    max_tokens: 4000,
    target_provider: Some("gpt-4".to_string()),
    dedup_strategy: DeduplicationStrategy::Semantic,
};
let results = collection.context_query(&config)?;

// Results fit within 4000 tokens, deduplicated for relevance
```

## Quick Start

### IPC Client (Async)

```rust
use sochdb::IpcClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to SochDB server
    let mut client = IpcClient::connect("./my_database/sochdb.sock").await?;
    
    // Put and Get
    client.put(b"user:123", b r#"{"name":"Alice","age":30}"#).await?;
    let value = client.get(b"user:123").await?;
    
    if let Some(data) = value {
        println!("{}", String::from_utf8_lossy(&data));
        // Output: {"name":"Alice","age":30}
    }
    
    Ok(())
}
```

**Start server first:**
```bash
sochdb-server --db ./my_database
# Output: [IpcServer] Listening on "./my_database/sochdb.sock"
```

### Embedded Mode (Direct FFI)

For single-process applications with maximum performance:

```rust
use sochdb_core::Database;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Open database
    let db = Database::open("./my_database")?;
    
    // Key-value operations
    db.put(b"key", b"value")?;
    let value = db.get(b"key")?;
    
    if let Some(data) = value {
        println!("{}", String::from_utf8_lossy(&data));
        // Output: value
    }
    
    Ok(())
}
```

## Core Operations

### Basic Key-Value

```rust
// Put
client.put(b"key", b"value").await?;

// Get
match client.get(b"key").await? {
    Some(value) => println!("{}", String::from_utf8_lossy(&value)),
    None => println!("Key not found"),
}

// Delete
client.delete(b"key").await?;
```

**Output:**
```
value
Key not found (after delete)
```

### Path Operations

```rust
// Hierarchical data storage
client.put_path("users/alice/email", b"alice@example.com").await?;
client.put_path("users/alice/age", b"30").await?;
client.put_path("users/bob/email", b"bob@example.com").await?;

// Retrieve by path
if let Some(email) = client.get_path("users/alice/email").await? {
    println!("Alice's email: {}", String::from_utf8_lossy(&email));
}
```

**Output:**
```
Alice's email: alice@example.com
```

### Prefix Scanning ⭐

The most efficient way to iterate keys with a common prefix:

```rust
use sochdb::IpcClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = IpcClient::connect("./db/sochdb.sock").await?;
    
    // Insert multi-tenant data
    client.put(b"tenants/acme/users/1", b r#"{"name":"Alice"}"#).await?;
    client.put(b"tenants/acme/users/2", b r#"{"name":"Bob"}"#).await?;
    client.put(b"tenants/acme/orders/1", b r#"{"total":100}"#).await?;
    client.put(b"tenants/globex/users/1", b r#"{"name":"Charlie"}"#).await?;
    
    // Scan only ACME Corp data (tenant isolation)
    let results = client.scan("tenants/acme/").await?;
    println!("ACME Corp has {} items:", results.len());
    
    for kv in results {
        println!("  {}: {}", 
            String::from_utf8_lossy(&kv.key),
            String::from_utf8_lossy(&kv.value)
        );
    }
    
    Ok(())
}
```

**Output:**
```
ACME Corp has 3 items:
  tenants/acme/orders/1: {"total":100}
  tenants/acme/users/1: {"name":"Alice"}
  tenants/acme/users/2: {"name":"Bob"}
```

**Why use scan():**
- **Fast**: O(|prefix|) performance
- **Isolated**: Perfect for multi-tenant apps
- **Efficient**: Zero-copy reads from storage

## Transactions

### Automatic Transactions

```rust
// Transaction with automatic commit/abort
client.with_transaction(|txn| async move {
    txn.put(b"account:1:balance", b"1000").await?;
    txn.put(b"account:2:balance", b"500").await?;
    Ok(())
}).await?;
```

**Output:**
```
✅ Transaction committed
```

### Manual Transaction Control

```rust
let txn = client.begin_transaction().await?;

txn.put(b"key1", b"value1").await?;
txn.put(b"key2", b"value2").await?;

// Commit or abort
if success {
    client.commit_transaction(txn).await?;
} else {
    client.abort_transaction(txn).await?;
}
```

## SQL Operations

SochDB supports full SQL via the `sochdb-query` crate:

```rust
use sochdb_query::QueryEngine;
use sochdb_core::Database;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Database::open("./sql_db")?;
    let query_engine = QueryEngine::new(db);
    
    // Create table
    query_engine.execute(r#"
        CREATE TABLE users (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT UNIQUE,
            age INTEGER
        )
    "#)?;
    
    // Insert data
    query_engine.execute(r#"
        INSERT INTO users (id, name, email, age)
        VALUES (1, 'Alice', 'alice@example.com', 30)
    "#)?;
    
    query_engine.execute(r#"
        INSERT INTO users (id, name, email, age)
        VALUES (2, 'Bob', 'bob@example.com', 25)
    "#)?;
    
    // Query
    let results = query_engine.execute("SELECT * FROM users WHERE age > 26")?;
    for row in results {
        println!("{:?}", row);
    }
    
    Ok(())
}
```

**Output:**
```
Row { id: 1, name: "Alice", email: "alice@example.com", age: 30 }
```

### Complex SQL Queries

```rust
// JOIN query
let results = query_engine.execute(r#"
    SELECT users.name, orders.product, orders.amount
    FROM users
    JOIN orders ON users.id = orders.user_id
    WHERE orders.amount > 50
    ORDER BY orders.amount DESC
"#)?;

for row in results {
    println!("{} bought {} for ${}", 
        row.get_str("name")?, 
        row.get_str("product")?, 
        row.get_f64("amount")?
    );
}
```

**Output:**
```
Alice bought Laptop for $999.99
Bob bought Keyboard for $75
```

### Aggregations

```rust
// GROUP BY with aggregations
let results = query_engine.execute(r#"
    SELECT users.name, COUNT(*) as order_count, SUM(orders.amount) as total
    FROM users
    JOIN orders ON users.id = orders.user_id
    GROUP BY users.name
    ORDER BY total DESC
"#)?;

for row in results {
    println!("{}: {} orders, ${} total",
        row.get_str("name")?,
        row.get_i64("order_count")?,
        row.get_f64("total")?
    );
}
```

**Output:**
```
Alice: 2 orders, $1024.99 total
Bob: 1 orders, $75 total
```

## Vector Search

### HNSW Index

```rust
use sochdb_index::{HnswIndex, DistanceMetric};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create HNSW index
    let mut index = HnswIndex::new(
        384,                      // dimension
        DistanceMetric::Cosine,   // metric
        16,                       // m
        100                       // ef_construction
    )?;
    
    // Build from embeddings
    let embeddings = vec![
        vec![0.1, 0.2, 0.3, /* ... 384 dims */],
        vec![0.4, 0.5, 0.6, /* ... 384 dims */],
    ];
    let labels = vec!["doc1", "doc2"];
    
    index.bulk_build(&embeddings, &labels)?;
    
    // Search
    let query = vec![0.15, 0.25, 0.35, /* ... 384 dims */];
    let results = index.query(&query, 10, 50)?; // k=10, ef_search=50
    
    for (i, result) in results.iter().enumerate() {
        println!("{}. {} (distance: {:.4})", 
            i + 1, 
            result.label, 
            result.distance
        );
    }
    
    Ok(())
}
```

**Output:**
```
1. doc1 (distance: 0.0234)
2. doc2 (distance: 0.1567)
```

## Complete Example: Multi-Tenant SaaS App

```rust
use sochdb::IpcClient;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = IpcClient::connect("./saas_db/sochdb.sock").await?;
    
    // Insert tenant data
    client.put(
        b"tenants/acme/users/alice",
        br#"{"role":"admin","email":"alice@acme.com"}"#
    ).await?;
    
    client.put(
        b"tenants/acme/users/bob",
        br#"{"role":"user","email":"bob@acme.com"}"#
    ).await?;
    
    client.put(
        b"tenants/globex/users/charlie",
        br#"{"role":"admin","email":"charlie@globex.com"}"#
    ).await?;
    
    // Scan ACME Corp data only (tenant isolation)
    let acme_data = client.scan("tenants/acme/").await?;
    println!("ACME Corp: {} users", acme_data.len());
    
    for kv in acme_data {
        let user: Value = serde_json::from_slice(&kv.value)?;
        println!("  {}: {} ({})",
            String::from_utf8_lossy(&kv.key),
            user["email"].as_str().unwrap(),
            user["role"].as_str().unwrap()
        );
    }
    
    // Scan Globex Corp data
    let globex_data = client.scan("tenants/globex/").await?;
    println!("\nGlobex Inc: {} users", globex_data.len());
    
    for kv in globex_data {
        let user: Value = serde_json::from_slice(&kv.value)?;
        println!("  {}: {} ({})",
            String::from_utf8_lossy(&kv.key),
            user["email"].as_str().unwrap(),
            user["role"].as_str().unwrap()
        );
    }
    
    Ok(())
}
```

**Output:**
```
ACME Corp: 2 users
  tenants/acme/users/alice: alice@acme.com (admin)
  tenants/acme/users/bob: bob@acme.com (user)

Globex Inc: 1 users
  tenants/globex/users/charlie: charlie@globex.com (admin)
```

## API Reference

### IpcClient (Async)

| Method | Description |
|--------|-------------|
| `IpcClient::connect(path)` | Connect to IPC server |
| `put(key, value)` | Store key-value pair |
| `get(key)` | Retrieve value (Option) |
| `delete(key)` | Delete a key |
| `put_path(path, value)` | Store at hierarchical path |
| `get_path(path)` | Retrieve by path |
| `scan(prefix)` | Scan keys with prefix |
| `begin_transaction()` | Start transaction |
| `commit_transaction(txn)` | Commit transaction |
| `abort_transaction(txn)` | Abort transaction |

### Database (Embedded)

| Method | Description |
|--------|-------------|
| `Database::open(path)` | Open/create database |
| `put(key, value)` | Store key-value pair |
| `get(key)` | Retrieve value (Option) |
| `delete(key)` | Delete a key |
| `scan(start, end)` | Iterate key range |
| `checkpoint()` | Force durability checkpoint |
| `stats()` | Get storage statistics |

## Configuration

```rust
use sochdb::Config;

let config = Config {
    create_if_missing: true,
    wal_enabled: true,
    sync_mode: SyncMode::Normal,  // Full, Normal, Off
    memtable_size_bytes: 64 * 1024 * 1024,  // 64MB
    ..Default::default()
};

let db = Database::open_with_config("./my_db", config)?;
```

## Error Handling

```rust
use sochdb::{IpcClient, Error};

match client.get(b"key").await {
    Ok(Some(value)) => {
        println!("Found: {}", String::from_utf8_lossy(&value));
    }
    Ok(None) => {
        println!("Key not found");
    }
    Err(Error::ConnectionFailed) => {
        eprintln!("Server not running!");
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}
```

## Best Practices

✅ **Use IPC for multi-process** — Better for microservices
✅ **Use embedded for single-process** — Maximum performance
✅ **Use scan() for multi-tenancy** — Efficient prefix-based isolation
✅ **Use transactions** — Atomic multi-key operations
✅ **Use async/await** — Non-blocking I/O for IPC
✅ **Handle errors properly** — Match on Error variants

## Crate Organization

| Crate | Purpose |
|-------|---------|
| `sochdb` | High-level client SDK (this crate) |
| `sochdb-core` | Core database engine |
| `sochdb-storage` | Storage layer with IPC server |
| `sochdb-index` | Vector search (HNSW) |
| `sochdb-query` | SQL query engine |
| `sochdb-client` | Low-level client bindings |

## Building from Source

```bash
# Clone repository
git clone https://github.com/sochdb/sochdb
cd sochdb

# Build all crates
cargo build --release

# Run tests
cargo test --all

# Build specific crate
cargo build --release -p sochdb-client
```

## Examples

See the [examples directory](../examples/rust) for more:

- `basic_operations.rs` - Simple key-value operations
- `multi_tenant.rs` - Multi-tenant data isolation
- `transactions.rs` - ACID transactions
- `vector_search.rs` - HNSW vector search
- `sql_queries.rs` - SQL operations

## Platform Support

- Linux (x86_64, aarch64)
- macOS (Intel, Apple Silicon)
- Windows (x64)

Requires Rust 1.70 or later.

## License

GNU AGPL v3.0 or later (AGPL-3.0-or-later)

## Links

- [Documentation](https://docs.rs/sochdb)
- [Crates.io](https://crates.io/crates/sochdb)
- [Python SDK](../sochdb-python-sdk)
- [Go SDK](../sochdb-go)
- [JavaScript SDK](../sochdb-js)
- [GitHub](https://github.com/sochdb/sochdb)

## Support

- GitHub Issues: https://github.com/sochdb/sochdb/issues
- Email: sushanth@sochdb.dev

## Author

**Sushanth** - [GitHub](https://github.com/sushanthpy)
