#![allow(clippy::unwrap_used)]
use std::borrow::Cow;
use std::sync::Arc;
use surrealdb_kvs::TransactionType::{Read, Write};
use surrealdb_types::Value;
use crate::catalog::RecordType;
use crate::catalog::providers::{DatabaseProvider, TableProvider};
use crate::dbs::Session;
use crate::expr::Dir;
use crate::key::schema::{GraphDirPrefix, GraphKey, GraphPointerKey};
use crate::kvs::Datastore;
use crate::val::RecordId;
async fn ds() -> Arc<Datastore> {
Datastore::builder().without_maintenance_tasks().build_with_path("memory").await.unwrap()
}
async fn run(ds: &Datastore, ses: &Session, sql: &str) -> Vec<Value> {
ds.execute(sql, ses, None)
.await
.unwrap()
.into_iter()
.map(|response| response.result.unwrap())
.collect()
}
#[tokio::test]
async fn v1_edge_migration_deletes_both_legacy_vertex_keys() {
let ds = ds().await;
let ses = Session::owner().with_ns("test").with_db("test");
run(
&ds,
&ses,
"DEFINE NAMESPACE test;
DEFINE DATABASE test;
DEFINE TABLE person;
DEFINE TABLE likes TYPE RELATION;
CREATE person:a, person:b;
RELATE person:a->likes:e1->person:b;",
)
.await;
let baseline_out = run(&ds, &ses, "SELECT VALUE ->likes FROM person:a;").await;
let baseline_in = run(&ds, &ses, "SELECT VALUE <-likes FROM person:b;").await;
let a = RecordId::new("person".into(), "a".to_owned());
let b = RecordId::new("person".into(), "b".to_owned());
let e1 = RecordId::new("likes".into(), "e1".to_owned());
let (ns, db) = {
let txn = ds.transaction(Read).await.unwrap();
let db = txn.get_db_by_name("test", "test", None).await.unwrap().unwrap();
txn.cancel().await.unwrap();
(db.namespace_id, db.database_id)
};
let ltr_legacy = GraphKey {
ns,
db,
tb: Cow::Borrowed(&a.table),
id: Cow::Borrowed(&a.key),
dir: Dir::Out,
foreign_table: Cow::Borrowed(&e1.table),
foreign_key: Cow::Borrowed(&e1.key),
};
let rtl_legacy = GraphKey {
ns,
db,
tb: Cow::Borrowed(&b.table),
id: Cow::Borrowed(&b.key),
dir: Dir::In,
foreign_table: Cow::Borrowed(&e1.table),
foreign_key: Cow::Borrowed(&e1.key),
};
let ltr_pointer = GraphPointerKey {
ns,
db,
tb: Cow::Borrowed(&a.table),
id: Cow::Borrowed(&a.key),
dir: Dir::Out,
foreign_table: Cow::Borrowed(&e1.table),
foreign_key: Cow::Borrowed(&e1.key),
target_table: Cow::Borrowed(&b.table),
target_key: Cow::Borrowed(&b.key),
};
let rtl_pointer = GraphPointerKey {
ns,
db,
tb: Cow::Borrowed(&b.table),
id: Cow::Borrowed(&b.key),
dir: Dir::In,
foreign_table: Cow::Borrowed(&e1.table),
foreign_key: Cow::Borrowed(&e1.key),
target_table: Cow::Borrowed(&a.table),
target_key: Cow::Borrowed(&a.key),
};
{
let txn = ds.transaction(Write).await.unwrap();
txn.del_key(<r_pointer).await.unwrap();
txn.del_key(&rtl_pointer).await.unwrap();
txn.set_key(<r_legacy, &()).await.unwrap();
txn.set_key(&rtl_legacy, &()).await.unwrap();
let record = txn.get_record(ns, db, &e1.table, &e1.key, None).await.unwrap();
let mut record = (*record).clone();
record.set_record_type(RecordType::Edge {
variant: 1,
});
txn.set_record(ns, db, &e1.table, &e1.key, Arc::new(record)).await.unwrap();
txn.commit().await.unwrap();
}
run(&ds, &ses, "RELATE person:a->likes:e1->person:b;").await;
{
let txn = ds.transaction(Read).await.unwrap();
assert!(
!txn.exists_key(<r_legacy, None).await.unwrap(),
"stale legacy key left on the `in` vertex"
);
assert!(
!txn.exists_key(&rtl_legacy, None).await.unwrap(),
"stale legacy key left on the `out` vertex"
);
assert!(txn.exists_key(<r_pointer, None).await.unwrap());
assert!(txn.exists_key(&rtl_pointer, None).await.unwrap());
let a_out = GraphDirPrefix {
ns,
db,
tb: Cow::Borrowed(&a.table),
id: Cow::Borrowed(&a.key),
dir: Dir::Out,
};
let b_in = GraphDirPrefix {
ns,
db,
tb: Cow::Borrowed(&b.table),
id: Cow::Borrowed(&b.key),
dir: Dir::In,
};
assert_eq!(txn.count(a_out.range().unwrap(), None).await.unwrap(), 1);
assert_eq!(txn.count(b_in.range().unwrap(), None).await.unwrap(), 1);
txn.cancel().await.unwrap();
}
assert_eq!(run(&ds, &ses, "SELECT VALUE ->likes FROM person:a;").await, baseline_out);
assert_eq!(run(&ds, &ses, "SELECT VALUE <-likes FROM person:b;").await, baseline_in);
}