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