use std::collections::BTreeMap;
use std::sync::Arc;
use surrealdb_kvs::TransactionType::Read;
use crate::dbs::{Capabilities, Session};
use crate::key::RawRange;
use crate::key::schema::{AnyKey, KeyKind, describe};
use crate::kvs::Datastore;
async fn mem_ds() -> Arc<Datastore> {
Datastore::builder()
.with_capabilities(Capabilities::all())
.build_with_path("memory")
.await
.unwrap()
}
async fn all_keys(ds: &Datastore) -> Vec<Vec<u8>> {
let tx = ds.transaction(Read).await.unwrap();
let res = tx.getr_raw(RawRange::every_key(), None).await.unwrap();
let _ = tx.cancel().await;
res.into_iter().map(|(k, _)| k).collect()
}
async fn populate(ds: &Datastore) {
let session = Session::owner().with_ns("test").with_db("test");
for statement in [
"DEFINE NAMESPACE test;",
"DEFINE DATABASE test;",
"DEFINE TABLE person SCHEMAFULL;",
"DEFINE FIELD name ON person TYPE string;",
"DEFINE FIELD best ON person TYPE option<record<person>> REFERENCE;",
"DEFINE INDEX name_idx ON person FIELDS name;",
"DEFINE INDEX name_unique ON person FIELDS name UNIQUE;",
"DEFINE ANALYZER simple TOKENIZERS blank;",
"DEFINE PARAM $threshold VALUE 10;",
"DEFINE FUNCTION fn::double($n: int) { RETURN $n * 2; };",
"DEFINE SEQUENCE seq_a;",
"DEFINE EVENT log ON person WHEN $event = 'CREATE' THEN { RETURN 1; };",
"DEFINE USER alice ON DATABASE PASSWORD 'secret' ROLES OWNER;",
"CREATE person:alice SET name = 'alice';",
"CREATE person:bob SET name = 'bob', best = person:alice;",
"RELATE person:alice->knows->person:bob;",
] {
let res = ds.execute(statement, &session, None).await.unwrap();
for response in res {
response.result.unwrap_or_else(|e| panic!("{statement} failed: {e}"));
}
}
}
#[tokio::test]
async fn every_key_the_engine_writes_is_recognised() {
let ds = mem_ds().await;
populate(&ds).await;
let keys = all_keys(&ds).await;
assert!(keys.len() > 20, "expected the engine to have written a populated store");
let unrecognised: Vec<String> = keys
.iter()
.filter(|k| AnyKey::decode(k).is_none())
.map(|k| describe(k).to_string())
.collect();
assert!(
unrecognised.is_empty(),
"{} of {} keys are not covered by the declared keyspace:\n{}",
unrecognised.len(),
keys.len(),
unrecognised.join("\n")
);
}
#[tokio::test]
async fn recognised_keys_are_attributed_to_the_right_route() {
let ds = mem_ds().await;
populate(&ds).await;
let mut by_kind: BTreeMap<KeyKind, usize> = BTreeMap::new();
for key in all_keys(&ds).await {
if let Some(decoded) = AnyKey::decode(&key) {
*by_kind.entry(decoded.kind()).or_default() += 1;
}
}
for expected in [
KeyKind::Namespace,
KeyKind::Database,
KeyKind::Table,
KeyKind::Field,
KeyKind::IndexDef,
KeyKind::IndexName,
KeyKind::Event,
KeyKind::Analyzer,
KeyKind::Param,
KeyKind::Function,
KeyKind::DbUser,
KeyKind::Sequence,
KeyKind::Record,
KeyKind::Entry,
KeyKind::Unique,
KeyKind::Graph,
KeyKind::GraphPointer,
KeyKind::Reference,
KeyKind::DocKey,
KeyKind::DocLookup,
KeyKind::BuildState,
KeyKind::BuildTicket,
] {
assert!(
by_kind.contains_key(&expected),
"no key was attributed to {expected:?}; got {by_kind:?}"
);
}
assert_eq!(by_kind.get(&KeyKind::Record), Some(&3), "got {by_kind:?}");
}
#[tokio::test]
async fn values_decode_under_the_type_their_key_binds() {
let ds = mem_ds().await;
populate(&ds).await;
let tx = ds.transaction(Read).await.unwrap();
let entries = tx.getr_raw(RawRange::every_key(), None).await.unwrap();
let _ = tx.cancel().await;
let mut decoded = 0usize;
let mut failures = Vec::new();
for (key, value) in &entries {
let Some(any) = AnyKey::decode(key) else {
continue;
};
match any.decode_value(value) {
Ok(_) => decoded += 1,
Err(e) => failures.push(format!("{}: {e}", describe(key))),
}
}
assert!(failures.is_empty(), "values failed to decode:\n{}", failures.join("\n"));
assert!(decoded > 20, "expected to have decoded a populated store, got {decoded}");
}
#[tokio::test]
async fn a_decoded_value_carries_its_contents() {
use crate::key::schema::AnyValue;
use crate::val::{RecordId, Value};
let ds = mem_ds().await;
populate(&ds).await;
let tx = ds.transaction(Read).await.unwrap();
let entries = tx.getr_raw(RawRange::every_key(), None).await.unwrap();
let _ = tx.cancel().await;
let mut namespaces = Vec::new();
let mut record_ids = Vec::new();
for (key, value) in &entries {
let Some(any) = AnyKey::decode(key) else {
continue;
};
let expected = match &any {
AnyKey::Record(record) => {
Some(RecordId::new(record.tb.as_ref().clone(), record.id.as_ref().clone()))
}
_ => None,
};
match any.decode_value(value) {
Ok(AnyValue::NamespaceDefinition(ns)) => namespaces.push(ns.name.to_string()),
Ok(AnyValue::Record(record)) => {
let expected = expected.expect("a record key names a record");
let Value::Object(data) = &record.data else {
panic!("a record's data is an object, got {:?}", record.data);
};
assert_eq!(
data.0.get("id"),
Some(&Value::RecordId(expected.clone())),
"a record's id comes from its key"
);
record_ids.push(format!("{}:{:?}", expected.table, expected.key));
}
_ => {}
}
}
assert_eq!(namespaces, ["test"], "the namespace definition decoded to its name");
record_ids.sort();
assert_eq!(record_ids.len(), 3, "every record decoded, got {record_ids:?}");
assert!(
record_ids.iter().any(|id| id.contains("person") && id.contains("alice")),
"got {record_ids:?}"
);
assert!(
record_ids.iter().any(|id| id.contains("person") && id.contains("bob")),
"got {record_ids:?}"
);
assert!(
record_ids.iter().any(|id| id.starts_with("knows:")),
"the edge record decoded, got {record_ids:?}"
);
}
#[tokio::test]
async fn a_dropped_namespace_leaves_only_queued_work() {
let ds = mem_ds().await;
populate(&ds).await;
let session = Session::owner().with_ns("test").with_db("test");
ds.execute("REMOVE NAMESPACE test;", &session, None).await.unwrap();
let mut remaining: BTreeMap<KeyKind, usize> = BTreeMap::new();
for key in all_keys(&ds).await {
if let Some(decoded) = AnyKey::decode(&key) {
*remaining.entry(decoded.kind()).or_default() += 1;
}
}
assert!(!remaining.contains_key(&KeyKind::Namespace), "got {remaining:?}");
assert!(remaining.contains_key(&KeyKind::Reclaim), "reclaim work should be queued");
}