use topodb::*;
#[test]
#[ignore]
fn regenerate_v8_fixture() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v8.redb");
let _ = std::fs::remove_file(&path);
let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
{
let db = Db::open_with(&path, spec).unwrap();
let s = ScopeId::from_u128(1);
let n1 = NodeId::from_u128(10);
let n2 = NodeId::from_u128(11);
let mut p1 = Props::new();
p1.insert("name".into(), PropValue::Str("ada".into()));
let mut p2 = Props::new();
p2.insert(
"content".into(),
PropValue::Str("fixture memory about databases".into()),
);
db.submit(vec![
Op::CreateNode {
id: n1,
scope: Scope::Id(s),
label: "Entity".into(),
props: p1,
},
Op::CreateNode {
id: n2,
scope: Scope::Id(s),
label: "Memory".into(),
props: p2,
},
])
.unwrap();
db.submit(vec![Op::SetEmbedding {
id: n2,
model: "m1".into(),
vector: vec![1.0, 0.0],
}])
.unwrap();
}
{
let mut raw = redb::Database::open(&path).unwrap();
raw.compact().unwrap();
}
assert!(
path.exists(),
"regenerate_v8_fixture: fixture file was not created at {path:?}"
);
}
#[test]
fn v5_fixture_migrates_to_v6_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v5.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v5.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(
db.nodes_by_prop_normalized(&scopes, "Entity", "name", &PropValue::Str(" ADA ".into()))
.unwrap()
.len(),
1,
"v5 normalized equality keys must match case/whitespace variants"
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(
db.nodes_by_label(&scopes, "Entity").len(),
1,
"recipe's known corpus: exactly one Entity node (n1)"
);
assert_eq!(
db.nodes_by_label(&scopes, "Memory").len(),
1,
"recipe's known corpus: exactly one Memory node (n2)"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn v6_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v6.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v6.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn v6_fixture_migrates_to_v7_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v6.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v6.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
assert!(db.debug_dump_hnsw_meta().unwrap().is_empty());
assert!(db.debug_dump_hnsw_links().unwrap().is_empty());
}
#[test]
fn v7_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v7.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v7.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
assert!(db.debug_dump_hnsw_meta().unwrap().is_empty());
assert!(db.debug_dump_hnsw_links().unwrap().is_empty());
}
fn quantize(v: &[f32]) -> (f32, Vec<i8>) {
let mut maxabs = 0.0f32;
for &x in v {
let a = x.abs();
if a > maxabs {
maxabs = a;
}
}
if maxabs == 0.0 || !maxabs.is_finite() {
return (0.0, vec![0i8; v.len()]);
}
let s = 127.0f32 / maxabs;
let codes = v
.iter()
.map(|&x| ((x * s).round() as i32).clamp(-127, 127) as i8)
.collect();
(maxabs, codes)
}
fn dequantize(scale: f32, codes: &[i8]) -> Vec<f32> {
codes.iter().map(|&c| c as f32 * scale / 127.0).collect()
}
#[test]
fn v8_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v8.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v8.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
assert!(db.debug_dump_hnsw_meta().unwrap().is_empty());
assert!(db.debug_dump_hnsw_links().unwrap().is_empty());
let rows = db.debug_dump_vectors().unwrap();
assert_eq!(
rows.len(),
1,
"recipe's known corpus: exactly one embedding"
);
let (scale, codes) = quantize(&[1.0f32, 0.0]);
let expected = dequantize(scale, &codes);
assert_eq!(
rows[0].3, expected,
"stored embedding must dequantize to exactly dequantize(quantize([1.0, 0.0]))"
);
}
#[test]
fn v1_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v1.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v1.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let s = ScopeId::from_u128(1);
let scopes = ScopeSet::of(&[s]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(
db.nodes_by_label(&scopes, "Entity").len(),
1,
"v1->v6 migration: recipe's known corpus has exactly one Entity node (n1)"
);
assert_eq!(
db.nodes_by_label(&scopes, "Memory").len(),
1,
"v1->v6 migration: recipe's known corpus has exactly one Memory node (n2)"
);
assert_eq!(
db.debug_dump_label_index().unwrap().len(),
2,
"v1->v6 migration: LABEL_INDEX must have exactly one row per node (2 total), not be empty"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
drop(db);
let reopened = Db::open_with(
&path,
IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
},
)
.unwrap();
assert_eq!(reopened.current_seq().unwrap(), 3);
}
#[test]
fn v2_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v2.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v2.redb");
std::fs::copy(&src, &path).unwrap();
let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(
db.debug_dump_label_index().unwrap().len(),
2,
"v2->v6 migration: LABEL_INDEX must have exactly one row per node (2 total), not be empty"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn v3_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v3.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v3.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(
db.debug_dump_label_index().unwrap().len(),
2,
"v3->v6 migration: LABEL_INDEX must have exactly one row per node (2 total), not be empty"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn v3_legacy_fixture_migrates_and_reads() {
let src =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v3-legacy.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v3-legacy.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(
db.debug_dump_label_index().unwrap().len(),
2,
"v3-legacy->v6 migration: LABEL_INDEX must have exactly one row per node (2 total), not be empty"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn v4_fixture_opens_and_reads() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v4.redb");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("v4.redb");
std::fs::copy(&src, &path).unwrap(); let spec = IndexSpec {
equality: vec![PropIndex {
label: "Entity".into(),
prop: "name".into(),
}],
text: vec![PropIndex {
label: "Memory".into(),
prop: "content".into(),
}],
};
let db = Db::open_with(&path, spec).unwrap();
let scopes = ScopeSet::of(&[ScopeId::from_u128(1)]);
assert_eq!(
db.nodes_by_prop(&scopes, "Entity", "name", &PropValue::Str("ada".into()))
.unwrap()
.len(),
1
);
assert_eq!(db.search_text(&scopes, "databases", 10).unwrap().len(), 1);
assert_eq!(
db.search_vector(&VectorQuery {
scopes: scopes.clone(),
model: "m1".into(),
vector: vec![1.0, 0.0],
k: 1,
candidates: None,
})
.unwrap()
.len(),
1
);
assert_eq!(db.nodes_by_label(&scopes, "Entity").len(), 1);
assert_eq!(db.nodes_by_label(&scopes, "Memory").len(), 1);
assert_eq!(
db.debug_dump_label_index().unwrap().len(),
2,
"v4->v6 migration: LABEL_INDEX must have exactly one row per node (2 total), not be empty"
);
assert_eq!(db.current_seq().unwrap(), 3);
assert_eq!(db.format_version(), 8);
}
#[test]
fn corrupt_hnsw_params_stamp_fails_open_with_encoding_error() {
use redb::TableDefinition;
const META: TableDefinition<&str, &[u8]> = TableDefinition::new("meta");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("t.redb");
let spec = IndexSpec {
equality: vec![],
text: vec![],
};
{
let db = Db::open_with(&path, spec.clone()).unwrap();
drop(db);
}
{
let raw = redb::Database::open(&path).unwrap();
let tx = raw.begin_write().unwrap();
{
let mut meta = tx.open_table(META).unwrap();
meta.insert("hnsw_params", [0xFFu8].as_slice()).unwrap();
}
tx.commit().unwrap();
}
let err = Db::open_with(&path, spec).unwrap_err();
assert!(
matches!(err, TopoError::Encoding(ref msg) if msg.contains("hnsw_params")),
"corrupt hnsw_params stamp must fail open with an Encoding error, got: {err:?}"
);
}