use crate::util::Persistable;
use crate::{space, Link};
use data_bucket_codegen::Persistable;
use rkyv::{Archive, Deserialize, Serialize};
pub type SpaceName = String;
#[derive(Archive, Clone, Deserialize, Debug, PartialEq, Serialize, Persistable)]
pub(crate) struct SpaceInfoPageV1<Pk = ()> {
pub id: space::Id,
pub page_count: u32,
pub pk_gen_state: Pk,
pub name: SpaceName,
pub row_schema: Vec<(String, String)>,
pub primary_key_fields: Vec<String>,
pub secondary_index_types: Vec<(String, String)>,
pub empty_links_list: Vec<Link>,
}
#[derive(Archive, Clone, Deserialize, Debug, PartialEq, Serialize, Persistable)]
pub(crate) struct SpaceInfoPageV2<Pk = ()> {
pub id: space::Id,
pub page_count: u32,
pub pk_gen_state: Pk,
pub name: SpaceName,
pub version: u32,
pub row_schema: Vec<(String, String)>,
pub primary_key_fields: Vec<String>,
pub secondary_index_types: Vec<(String, String)>,
pub empty_links_list: Vec<Link>,
}
impl<Pk> From<SpaceInfoPageV1<Pk>> for SpaceInfoPage<Pk> {
fn from(v1: SpaceInfoPageV1<Pk>) -> Self {
SpaceInfoPage {
version: 0,
id: v1.id,
page_count: v1.page_count,
pk_gen_state: v1.pk_gen_state,
name: v1.name,
row_schema: v1.row_schema,
primary_key_fields: v1.primary_key_fields,
secondary_index_types: v1.secondary_index_types,
empty_links_list: v1.empty_links_list,
}
}
}
impl<Pk> From<SpaceInfoPageV2<Pk>> for SpaceInfoPage<Pk> {
fn from(v2: SpaceInfoPageV2<Pk>) -> Self {
SpaceInfoPage {
version: v2.version,
id: v2.id,
page_count: v2.page_count,
pk_gen_state: v2.pk_gen_state,
name: v2.name,
row_schema: v2.row_schema,
primary_key_fields: v2.primary_key_fields,
secondary_index_types: v2.secondary_index_types,
empty_links_list: v2.empty_links_list,
}
}
}
impl<Pk: Clone> From<SpaceInfoPage<Pk>> for SpaceInfoPageV2<Pk> {
fn from(page: SpaceInfoPage<Pk>) -> Self {
SpaceInfoPageV2 {
version: page.version,
id: page.id,
page_count: page.page_count,
pk_gen_state: page.pk_gen_state,
name: page.name,
row_schema: page.row_schema,
primary_key_fields: page.primary_key_fields,
secondary_index_types: page.secondary_index_types,
empty_links_list: page.empty_links_list,
}
}
}
#[derive(Archive, Clone, Deserialize, Debug, PartialEq, Serialize)]
pub struct SpaceInfoPage<Pk = ()> {
pub id: space::Id,
pub page_count: u32,
pub pk_gen_state: Pk,
pub name: SpaceName,
pub version: u32,
pub row_schema: Vec<(String, String)>,
pub primary_key_fields: Vec<String>,
pub secondary_index_types: Vec<(String, String)>,
pub empty_links_list: Vec<Link>,
}
impl<Pk> Persistable for SpaceInfoPage<Pk>
where
Pk: Archive
+ Clone
+ for<'a> rkyv::Serialize<
rkyv::rancor::Strategy<
rkyv::ser::Serializer<
rkyv::util::AlignedVec,
rkyv::ser::allocator::ArenaHandle<'a>,
rkyv::ser::sharing::Share,
>,
rkyv::rancor::Error,
>,
>,
<Pk as Archive>::Archived: rkyv::Deserialize<Pk, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
{
fn as_bytes(&self) -> impl AsRef<[u8]> + Send {
let v2 = SpaceInfoPageV2 {
version: self.version,
id: self.id,
page_count: self.page_count,
pk_gen_state: self.pk_gen_state.clone(),
name: self.name.clone(),
row_schema: self.row_schema.clone(),
primary_key_fields: self.primary_key_fields.clone(),
secondary_index_types: self.secondary_index_types.clone(),
empty_links_list: self.empty_links_list.clone(),
};
rkyv::to_bytes::<rkyv::rancor::Error>(&v2).unwrap()
}
fn from_bytes(bytes: &[u8], version: u32) -> Self {
match version {
1 => {
let v1 = SpaceInfoPageV1::from_bytes(bytes, version);
v1.into()
}
_ => {
let v2 = SpaceInfoPageV2::from_bytes(bytes, version);
v2.into()
}
}
}
}
#[derive(Archive, Clone, Deserialize, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Interval(pub usize, pub usize);
impl Interval {
pub fn contains(&self, interval: &Interval) -> bool {
self.0 <= interval.0 && self.1 >= interval.1
}
}
#[cfg(test)]
mod test {
use super::{SpaceInfoPage, SpaceInfoPageV1, SpaceInfoPageV2};
use crate::page::INNER_PAGE_SIZE;
use crate::util::Persistable;
use rkyv::Archive;
#[test]
fn test_as_bytes() {
let info: SpaceInfoPage = SpaceInfoPage {
id: 0.into(),
page_count: 0,
name: "Test".to_string(),
version: 1,
row_schema: vec![],
primary_key_fields: vec![],
pk_gen_state: (),
empty_links_list: vec![],
secondary_index_types: vec![],
};
let bytes = info.as_bytes();
assert!(bytes.as_ref().len() < INNER_PAGE_SIZE)
}
#[test]
fn test_migration_from_v1() {
let old_info: SpaceInfoPageV1 = SpaceInfoPageV1 {
id: 42.into(),
page_count: 5,
pk_gen_state: (),
name: "legacy_table".to_string(),
row_schema: vec![("col1".to_string(), "i32".to_string())],
primary_key_fields: vec!["col1".to_string()],
secondary_index_types: vec![],
empty_links_list: vec![],
};
let migrated: SpaceInfoPage = old_info.into();
assert_eq!(migrated.version, 0);
assert_eq!(migrated.id, 42.into());
assert_eq!(migrated.page_count, 5);
assert_eq!(migrated.name, "legacy_table");
assert_eq!(
migrated.row_schema,
vec![("col1".to_string(), "i32".to_string())]
);
assert_eq!(migrated.primary_key_fields, vec!["col1".to_string()]);
}
#[test]
fn test_v1_bytes_roundtrip_and_migration() {
let old_info: SpaceInfoPageV1 = SpaceInfoPageV1 {
id: 10.into(),
page_count: 3,
pk_gen_state: (),
name: "old_data".to_string(),
row_schema: vec![],
primary_key_fields: vec![],
secondary_index_types: vec![],
empty_links_list: vec![],
};
let bytes = old_info.as_bytes();
let archived = unsafe {
rkyv::access_unchecked::<<SpaceInfoPageV1 as Archive>::Archived>(bytes.as_ref())
};
let deserialized: SpaceInfoPageV1 =
rkyv::deserialize::<_, rkyv::rancor::Error>(archived).unwrap();
let migrated: SpaceInfoPage = deserialized.into();
assert_eq!(migrated.version, 0);
assert_eq!(migrated.id, 10.into());
assert_eq!(migrated.name, "old_data");
}
#[test]
fn test_v2_bytes_roundtrip() {
let info: SpaceInfoPageV2 = SpaceInfoPageV2 {
id: 20.into(),
page_count: 7,
pk_gen_state: (),
name: "new_table".to_string(),
version: 2,
row_schema: vec![("col2".to_string(), "String".to_string())],
primary_key_fields: vec!["col2".to_string()],
secondary_index_types: vec![],
empty_links_list: vec![],
};
let bytes = info.as_bytes();
let archived = unsafe {
rkyv::access_unchecked::<<SpaceInfoPageV2 as Archive>::Archived>(bytes.as_ref())
};
let deserialized: SpaceInfoPageV2 =
rkyv::deserialize::<_, rkyv::rancor::Error>(archived).unwrap();
assert_eq!(deserialized.version, 2);
assert_eq!(deserialized.id, 20.into());
assert_eq!(deserialized.name, "new_table");
}
#[test]
fn test_persistable_version_handling() {
let v1_info: SpaceInfoPageV1 = SpaceInfoPageV1 {
id: 100.into(),
page_count: 10,
pk_gen_state: (),
name: "v1_table".to_string(),
row_schema: vec![],
primary_key_fields: vec![],
secondary_index_types: vec![],
empty_links_list: vec![],
};
let v1_bytes = v1_info.as_bytes();
let page_from_v1: SpaceInfoPage = SpaceInfoPage::from_bytes(v1_bytes.as_ref(), 1);
assert_eq!(page_from_v1.version, 0);
assert_eq!(page_from_v1.id, 100.into());
let v2_info: SpaceInfoPageV2 = SpaceInfoPageV2 {
id: 200.into(),
page_count: 20,
pk_gen_state: (),
name: "v2_table".to_string(),
version: 5,
row_schema: vec![],
primary_key_fields: vec![],
secondary_index_types: vec![],
empty_links_list: vec![],
};
let v2_bytes = v2_info.as_bytes();
let page_from_v2: SpaceInfoPage = SpaceInfoPage::from_bytes(v2_bytes.as_ref(), 2);
assert_eq!(page_from_v2.version, 5);
assert_eq!(page_from_v2.id, 200.into());
}
}