use std::sync::Arc;
use anyhow::Result;
use revision::revisioned;
use surrealdb_strand::Strand;
use crate::catalog::aggregation::AggregationStat;
use crate::kvs::KVValue;
use crate::val::{RecordId, Value};
#[revisioned(revision(1), revision(2, optimised, indexed_struct))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Record {
pub(crate) metadata: Option<Metadata>,
pub(crate) data: Value,
}
const ID_KEY: Strand = Strand::new_static("id");
impl KVValue for Record {
type KeyContext = RecordId;
fn kv_encode_value(&self) -> Result<Vec<u8>> {
Ok(revision::to_vec(self)?)
}
fn kv_decode_value(bytes: &[u8], rid: RecordId) -> Result<Record> {
let mut record: Record = revision::from_slice(bytes)?;
if let Value::Object(obj) = &mut record.data {
obj.0.insert(ID_KEY, Value::RecordId(rid));
}
Ok(record)
}
}
impl Record {
pub(crate) fn new(data: Value) -> Self {
Self {
metadata: None,
data,
}
}
pub const fn is_edge(&self) -> bool {
matches!(
&self.metadata,
Some(Metadata {
record_type: RecordType::Edge { .. },
..
})
)
}
pub const fn edge_variant(&self) -> Option<u16> {
match &self.metadata {
Some(Metadata {
record_type: RecordType::Edge {
variant,
},
..
}) => Some(*variant),
_ => None,
}
}
pub(crate) fn into_read_only(self) -> Arc<Self> {
Arc::new(self)
}
pub(crate) fn set_record_type(&mut self, rtype: RecordType) {
match &mut self.metadata {
Some(metadata) => {
metadata.record_type = rtype;
}
metadata => {
*metadata = Some(Metadata {
record_type: rtype,
aggregation_stats: Vec::new(),
});
}
}
}
}
pub(crate) const LATEST_EDGE_VARIANT: u16 = 2;
#[revisioned(revision = 2)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Hash)]
pub(crate) enum RecordType {
#[default]
Table,
#[revision(end = 2, convert_fn = "upgrade_edge_v1", fields_name = "EdgeV1")]
Edge,
#[revision(start = 2)]
Edge {
variant: u16,
},
}
impl RecordType {
fn upgrade_edge_v1(_fields: EdgeV1, _revision: u16) -> Result<Self, revision::Error> {
Ok(Self::Edge {
variant: 1,
})
}
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Metadata {
pub(crate) record_type: RecordType,
pub(crate) aggregation_stats: Vec<AggregationStat>,
}
#[cfg(test)]
mod tests {
use surrealdb_strand::Strand;
use super::*;
use crate::val::{Array, Object, RecordIdKey, TableName};
fn make_rid(table: &str, key: &str) -> RecordId {
RecordId {
table: TableName::new(table),
key: RecordIdKey::String(Strand::new(key)),
}
}
#[test]
fn legacy_unit_edge_decodes_to_variant_one() {
use revision::{DeserializeRevisioned, SerializeRevisioned, revisioned};
#[revisioned(revision = 1)]
#[derive(Debug, PartialEq)]
enum LegacyRecordType {
Table,
Edge,
}
let mut bytes = Vec::new();
LegacyRecordType::Edge.serialize_revisioned(&mut bytes).unwrap();
let decoded =
<RecordType as DeserializeRevisioned>::deserialize_revisioned(&mut bytes.as_slice())
.unwrap();
assert_eq!(
decoded,
RecordType::Edge {
variant: 1,
}
);
}
#[test]
fn current_edge_round_trips_through_metadata() {
use revision::{DeserializeRevisioned, SerializeRevisioned};
let original = Metadata {
record_type: RecordType::Edge {
variant: 7,
},
aggregation_stats: Vec::new(),
};
let mut bytes = Vec::new();
original.serialize_revisioned(&mut bytes).unwrap();
let decoded =
<Metadata as DeserializeRevisioned>::deserialize_revisioned(&mut bytes.as_slice())
.unwrap();
assert_eq!(decoded, original);
}
#[test]
fn bool_record_encoded_size_is_stable() {
assert_eq!(Record::new(Value::Bool(true)).kv_encode_value().unwrap().len(), 17);
}
fn legacy_stripped_bytes(obj_without_id: Object) -> Vec<u8> {
revision::to_vec(&Record::new(Value::Object(obj_without_id))).unwrap()
}
#[test]
fn encode_decode_round_trips_with_id() {
let rid = make_rid("user", "alice");
let mut obj = Object::default();
obj.0.insert(Strand::new("id"), Value::RecordId(rid.clone()));
obj.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
let original = Record::new(Value::Object(obj));
let bytes = original.kv_encode_value().unwrap();
let decoded = Record::kv_decode_value(&bytes, rid.clone()).unwrap();
assert_eq!(decoded, original);
match &decoded.data {
Value::Object(o) => assert_eq!(o.0.get("id"), Some(&Value::RecordId(rid))),
_ => panic!("expected Value::Object"),
}
}
#[test]
fn encode_stores_id_inline() {
let rid = make_rid("user", "alice");
let mut obj = Object::default();
obj.0.insert(Strand::new("id"), Value::RecordId(rid));
obj.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
let record = Record::new(Value::Object(obj));
assert_eq!(record.kv_encode_value().unwrap(), revision::to_vec(&record).unwrap());
}
#[test]
fn decode_splices_id_for_legacy_stripped_data() {
let rid = make_rid("user", "alice");
let mut middle = Object::default();
middle.0.insert(Strand::new("age"), Value::from(30i64));
middle.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
let decoded = Record::kv_decode_value(&legacy_stripped_bytes(middle), rid.clone()).unwrap();
match &decoded.data {
Value::Object(o) => {
let keys: Vec<&str> = o.0.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["age", "id", "name"]);
assert_eq!(o.0.get("id"), Some(&Value::RecordId(rid.clone())));
}
_ => panic!("expected Value::Object"),
}
let mut tail = Object::default();
tail.0.insert(Strand::new("address"), Value::String(Strand::new("123 main")));
tail.0.insert(Strand::new("age"), Value::from(30i64));
let decoded = Record::kv_decode_value(&legacy_stripped_bytes(tail), rid.clone()).unwrap();
match &decoded.data {
Value::Object(o) => {
let keys: Vec<&str> = o.0.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["address", "age", "id"]);
}
_ => panic!("expected Value::Object"),
}
let mut head = Object::default();
head.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
head.0.insert(Strand::new("zip"), Value::from(12345i64));
let decoded = Record::kv_decode_value(&legacy_stripped_bytes(head), rid).unwrap();
match &decoded.data {
Value::Object(o) => {
let keys: Vec<&str> = o.0.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["id", "name", "zip"]);
}
_ => panic!("expected Value::Object"),
}
}
#[test]
fn decode_round_trips_matching_id() {
let rid = make_rid("user", "alice");
let mut obj = Object::default();
obj.0.insert(Strand::new("id"), Value::RecordId(rid.clone()));
obj.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
let record = Record::new(Value::Object(obj));
let bytes = revision::to_vec(&record).unwrap();
let decoded = Record::kv_decode_value(&bytes, rid).unwrap();
assert_eq!(decoded, record);
}
#[test]
fn decode_overwrites_id_with_storage_key() {
let view_key = make_rid("high_scores", "2");
let source_id = make_rid("src", "2");
let mut obj = Object::default();
obj.0.insert(Strand::new("id"), Value::RecordId(source_id));
obj.0.insert(Strand::new("name"), Value::String(Strand::new("b")));
obj.0.insert(Strand::new("score"), Value::from(20i64));
let stored = Record::new(Value::Object(obj));
let bytes = revision::to_vec(&stored).unwrap();
let decoded = Record::kv_decode_value(&bytes, view_key.clone()).unwrap();
match &decoded.data {
Value::Object(o) => {
assert_eq!(o.0.get("id"), Some(&Value::RecordId(view_key)));
}
_ => panic!("expected Value::Object"),
}
}
#[test]
fn nested_id_is_preserved() {
let rid = make_rid("user", "alice");
let mut profile = Object::default();
profile.0.insert(Strand::new("id"), Value::String(Strand::new("profile:7")));
profile.0.insert(Strand::new("bio"), Value::String(Strand::new("hello")));
let mut friend1 = Object::default();
friend1.0.insert(Strand::new("id"), Value::String(Strand::new("user:bob")));
let mut friend2 = Object::default();
friend2.0.insert(Strand::new("id"), Value::String(Strand::new("user:carol")));
let friends = Array(vec![Value::Object(friend1.clone()), Value::Object(friend2.clone())]);
let mut top = Object::default();
top.0.insert(Strand::new("id"), Value::RecordId(rid.clone()));
top.0.insert(Strand::new("profile"), Value::Object(profile.clone()));
top.0.insert(Strand::new("friends"), Value::Array(friends));
let record = Record::new(Value::Object(top));
let bytes = record.kv_encode_value().unwrap();
let decoded = Record::kv_decode_value(&bytes, rid.clone()).unwrap();
match &decoded.data {
Value::Object(o) => {
assert_eq!(o.0.get("id"), Some(&Value::RecordId(rid)));
match o.0.get("profile") {
Some(Value::Object(p)) => {
assert_eq!(p.0.get("id"), Some(&Value::String(Strand::new("profile:7"))));
}
_ => panic!("expected nested profile object"),
}
match o.0.get("friends") {
Some(Value::Array(arr)) => {
let v: Vec<_> = arr.iter().collect();
match v[0] {
Value::Object(f) => {
assert_eq!(
f.0.get("id"),
Some(&Value::String(Strand::new("user:bob")))
);
}
_ => panic!("expected nested friend object"),
}
match v[1] {
Value::Object(f) => {
assert_eq!(
f.0.get("id"),
Some(&Value::String(Strand::new("user:carol")))
);
}
_ => panic!("expected nested friend object"),
}
}
_ => panic!("expected friends array"),
}
}
_ => panic!("expected Value::Object"),
}
}
#[test]
fn encode_round_trips_with_divergent_key_orderings() {
let rid = make_rid("user", "alice");
let mut obj = Object::default();
obj.0.insert(Strand::new("id"), Value::RecordId(rid.clone()));
obj.0.insert(Strand::new("name"), Value::String(Strand::new("Alice")));
obj.0.insert(Strand::new("zip"), Value::from(12345i64));
let original = Record::new(Value::Object(obj));
let bytes = original.kv_encode_value().unwrap();
let decoded = Record::kv_decode_value(&bytes, rid).unwrap();
assert_eq!(decoded, original);
match decoded.data {
Value::Object(o) => {
let keys: Vec<&str> = o.0.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["id", "name", "zip"]);
}
_ => panic!("expected Value::Object"),
}
}
}