Skip to main content

quickstart/
quickstart.rs

1//! Recern Vector from Rust: create a database, insert records with metadata,
2//! search with a filter, see how the search ran, then save and reopen.
3//!
4//!     cargo run --release --example quickstart
5
6use recern_vector::{CollectionConfig, Database, Filter, Metric, RecallOptions, SearchOptions};
7use serde_json::json;
8
9fn main() -> recern_vector::Result<()> {
10    let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11    let _ = std::fs::remove_file(&path);
12
13    let mut db = Database::create(&path)?;
14    let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
15
16    // Batch insert: (id, vector, metadata). Built on all cores, atomic.
17    let records: Vec<_> = (0..10_000)
18        .map(|i| {
19            let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
20            let lang = ["en", "de", "fr"][i % 3];
21            (
22                format!("doc-{i}"),
23                vector,
24                Some(json!({"lang": lang, "year": 2015 + i % 10})),
25            )
26        })
27        .collect();
28    let query = records[42].1.clone();
29    docs.upsert_many(records)?;
30    println!("{} records", docs.len());
31
32    // Search with a filter; explain() returns the hits and how they were found.
33    let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
34    // The same filter, built in code:
35    let _same = Filter::And(vec![
36        Filter::eq("lang", "en"),
37        Filter::between("year", Some(2020.0), None),
38    ]);
39    let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
40    for hit in &report.hits {
41        println!(
42            "  {:<10} distance={:.4} {}",
43            hit.id,
44            hit.distance,
45            hit.metadata.as_ref().unwrap()
46        );
47    }
48    println!(
49        "strategy={} visited={} selectivity={:.1}% time={:?}",
50        report.strategy,
51        report.visited,
52        report.filter_selectivity.unwrap_or(1.0) * 100.0,
53        report.elapsed
54    );
55
56    // Index health and recall against exact search.
57    let stats = docs.stats();
58    println!(
59        "layers={:?} unreachable={}",
60        stats.nodes_per_layer, stats.unreachable
61    );
62    let recall = docs.estimate_recall(&RecallOptions {
63        ef_values: vec![16, 64],
64        ..Default::default()
65    })?;
66    for point in &recall.points {
67        println!(
68            "  ef={:<3} recall@{}={:.3}",
69            point.ef, recall.k, point.recall
70        );
71    }
72
73    db.save()?;
74    let reopened = Database::open(&path)?;
75    println!("reopened: {} records", reopened.collection("docs")?.len());
76    Ok(())
77}
78
79/// A tiny deterministic generator so the example has no dependencies.
80fn pseudo_random(seed: usize) -> f32 {
81    let mut x = seed as u64 ^ 0x9E37_79B9_7F4A_7C15;
82    x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
83    x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
84    ((x ^ (x >> 31)) as f64 / u64::MAX as f64 * 2.0 - 1.0) as f32
85}