use plugmem_arena::{Arena, ArenaCfg, ShardMode, Slot, TermId, key};
use crate::config::Config;
use crate::error::Error;
use crate::id::{EdgeId, EntityId, FactId};
use crate::index::bm25::DocLenSlot;
use crate::model::{EdgeHistorySlot, VALID_TO_OPEN};
use crate::snapshot::Snapshot;
use super::Memory;
pub(super) mod legacy_kind {
pub const EDGES_OUT_META: u16 = 9;
pub const EDGES_OUT_POOL: u16 = 10;
pub const EDGES_IN_META: u16 = 11;
pub const EDGES_IN_POOL: u16 = 12;
pub const BM25_DOCLEN_META: u16 = 26;
pub const BM25_DOCLEN_POOL: u16 = 27;
pub const EDGE_HIST_OUT_META: u16 = 46;
pub const EDGE_HIST_OUT_POOL: u16 = 47;
pub const EDGE_HIST_IN_META: u16 = 48;
pub const EDGE_HIST_IN_POOL: u16 = 49;
}
const STATE_V1_LEN: usize = 24;
const STATE_V2_LEN: usize = 32;
pub(super) const STATE_LEN: usize = 40;
mod state_at {
use core::mem::size_of;
pub(super) const NEXT_FACT: usize = 0;
pub(super) const NEXT_ENTITY: usize = NEXT_FACT + size_of::<u32>();
pub(super) const BM25_DOCS: usize = NEXT_ENTITY + size_of::<u32>();
pub(super) const BM25_TOTAL_LEN: usize = BM25_DOCS + size_of::<u64>();
pub(super) const TOKENIZER_VERSION: usize = BM25_TOTAL_LEN + size_of::<u64>();
pub(super) const RESERVED: usize = TOKENIZER_VERSION + size_of::<u32>();
pub(super) const NEXT_EDGE: usize = RESERVED + size_of::<u32>();
}
pub(super) struct EngineState {
pub(super) next_fact: u32,
pub(super) next_entity: u32,
pub(super) bm25_tokenizer_version: u32,
pub(super) next_edge: u32,
pub(super) predates_edge_versions: bool,
}
pub(super) fn decode_engine_state(bytes: &[u8]) -> Result<EngineState, Error> {
if bytes.len() != STATE_V1_LEN && bytes.len() != STATE_V2_LEN && bytes.len() != STATE_LEN {
return Err(Error::Corrupt("engine state section has a wrong length"));
}
let at = |off: usize| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
Ok(EngineState {
next_fact: at(state_at::NEXT_FACT),
next_entity: at(state_at::NEXT_ENTITY),
bm25_tokenizer_version: if bytes.len() >= STATE_V2_LEN {
at(state_at::TOKENIZER_VERSION)
} else {
super::maintain::TOKENIZER_INDEX_VERSION
},
next_edge: if bytes.len() >= STATE_LEN {
at(state_at::NEXT_EDGE)
} else {
0
},
predates_edge_versions: bytes.len() < STATE_LEN,
})
}
#[derive(Clone, Copy)]
struct LegacyEdgeSlot {
a: EntityId,
rel: TermId,
b: EntityId,
fact: FactId,
}
impl Slot for LegacyEdgeSlot {
const SIZE: usize = 16;
const KEY_LEN: usize = 12;
fn write(&self, out: &mut [u8]) {
key::write_u32(out, self.a.0);
key::write_u32(&mut out[4..], self.rel.0);
key::write_u32(&mut out[8..], self.b.0);
key::write_u32(&mut out[12..], self.fact.0);
}
fn read(bytes: &[u8]) -> Self {
Self {
a: EntityId(key::read_u32(bytes)),
rel: TermId(key::read_u32(&bytes[4..])),
b: EntityId(key::read_u32(&bytes[8..])),
fact: FactId(key::read_u32(&bytes[12..])),
}
}
}
#[derive(Clone, Copy)]
struct LegacyEdgeHistorySlot {
a: EntityId,
rel: TermId,
b: EntityId,
edge: EdgeId,
fact: FactId,
flags: u16,
kind: u16,
recorded_at: u64,
valid_from: u64,
valid_to: u64,
}
impl Slot for LegacyEdgeHistorySlot {
const SIZE: usize = 48;
const KEY_LEN: usize = 16;
fn write(&self, out: &mut [u8]) {
key::write_u32(out, self.a.0);
key::write_u32(&mut out[4..], self.rel.0);
key::write_u32(&mut out[8..], self.b.0);
key::write_u32(&mut out[12..], self.edge.0);
key::write_u32(&mut out[16..], self.fact.0);
out[20..22].copy_from_slice(&self.flags.to_be_bytes());
out[22..24].copy_from_slice(&self.kind.to_be_bytes());
key::write_u64(&mut out[24..], self.recorded_at);
key::write_u64(&mut out[32..], self.valid_from);
key::write_u64(&mut out[40..], self.valid_to);
}
fn read(bytes: &[u8]) -> Self {
Self {
a: EntityId(key::read_u32(bytes)),
rel: TermId(key::read_u32(&bytes[4..])),
b: EntityId(key::read_u32(&bytes[8..])),
edge: EdgeId(key::read_u32(&bytes[12..])),
fact: FactId(key::read_u32(&bytes[16..])),
flags: u16::from_be_bytes(bytes[20..22].try_into().unwrap()),
kind: u16::from_be_bytes(bytes[22..24].try_into().unwrap()),
recorded_at: key::read_u64(&bytes[24..]),
valid_from: key::read_u64(&bytes[32..]),
valid_to: key::read_u64(&bytes[40..]),
}
}
}
impl Memory<'_> {
pub(super) fn migrate_edges(
&mut self,
snap: &Snapshot<'_>,
cfg: &Config,
) -> Result<bool, Error> {
let Some(current) = legacy_current_edges(snap, cfg)? else {
return Ok(false);
};
match legacy_history(snap, cfg)? {
Some(history) => {
for old in history.iter() {
self.adopt_history_version(EdgeHistorySlot {
a: old.a,
rel: old.rel,
b: old.b,
edge: old.edge,
fact: old.fact,
flags: old.flags,
kind: old.kind,
recorded_at: old.recorded_at,
valid_from: old.valid_from,
valid_to: old.valid_to,
})?;
}
if self.edges_out.len() != current.len() {
return Err(Error::Corrupt(
"legacy edge history does not cover every current edge",
));
}
}
None => {
for old in current.iter() {
let edge = EdgeId(self.next_edge);
self.next_edge = self.next_edge.saturating_add(1);
self.adopt_history_version(EdgeHistorySlot {
a: old.a,
rel: old.rel,
b: old.b,
edge,
fact: old.fact,
flags: 0,
kind: 0,
recorded_at: 0,
valid_from: 0,
valid_to: VALID_TO_OPEN,
})?;
}
}
}
Ok(true)
}
fn adopt_history_version(&mut self, version: EdgeHistorySlot) -> Result<(), Error> {
self.insert_history_edge(version)?;
if version.valid_to == VALID_TO_OPEN {
self.insert_current_edge(
version.a,
version.rel,
version.b,
version.fact,
version.edge,
version.valid_from,
)?;
}
Ok(())
}
}
fn legacy_current_edges(
snap: &Snapshot<'_>,
cfg: &Config,
) -> Result<Option<Arena<'static, LegacyEdgeSlot>>, Error> {
let Some(pair) = section_pair(
snap,
legacy_kind::EDGES_OUT_META,
legacy_kind::EDGES_OUT_POOL,
)?
else {
return Ok(None);
};
if section_pair(snap, legacy_kind::EDGES_IN_META, legacy_kind::EDGES_IN_POOL)?.is_none() {
return Err(Error::Corrupt("snapshot has incomplete edge sections"));
}
Ok(Some(Arena::load(ordered(cfg), pair.0, pair.1)?))
}
fn legacy_history(
snap: &Snapshot<'_>,
cfg: &Config,
) -> Result<Option<Arena<'static, LegacyEdgeHistorySlot>>, Error> {
let out = section_pair(
snap,
legacy_kind::EDGE_HIST_OUT_META,
legacy_kind::EDGE_HIST_OUT_POOL,
)?;
let has_in = section_pair(
snap,
legacy_kind::EDGE_HIST_IN_META,
legacy_kind::EDGE_HIST_IN_POOL,
)?
.is_some();
match (out, has_in) {
(Some(pair), true) => Ok(Some(Arena::load(ordered(cfg), pair.0, pair.1)?)),
(None, false) => Ok(None),
_ => Err(Error::Corrupt(
"snapshot has incomplete edge history sections",
)),
}
}
#[derive(Clone, Copy)]
struct LegacyDocLenSlot {
fact: FactId,
len: u16,
}
impl Slot for LegacyDocLenSlot {
const SIZE: usize = 8;
const KEY_LEN: usize = 4;
fn write(&self, out: &mut [u8]) {
key::write_u32(out, self.fact.0);
out[4..6].copy_from_slice(&self.len.to_be_bytes());
out[6..8].copy_from_slice(&[0, 0]);
}
fn read(bytes: &[u8]) -> Self {
Self {
fact: FactId(key::read_u32(bytes)),
len: u16::from_be_bytes(bytes[4..6].try_into().unwrap()),
}
}
}
pub(super) fn legacy_doc_len(
snap: &Snapshot<'_>,
cfg: &Config,
) -> Result<Option<Arena<'static, DocLenSlot>>, Error> {
let Some((meta, pool)) = section_pair(
snap,
legacy_kind::BM25_DOCLEN_META,
legacy_kind::BM25_DOCLEN_POOL,
)?
else {
return Ok(None);
};
let old = Arena::<LegacyDocLenSlot>::load(doc_len_cfg(cfg), meta, pool)?;
let mut out = Arena::new(doc_len_cfg(cfg))?;
for doc in old.iter() {
out.insert(&DocLenSlot {
fact: doc.fact,
len: doc.len,
distinct: 0,
sig: 0,
})?;
}
Ok(Some(out))
}
pub(super) fn doc_len_cfg(cfg: &Config) -> ArenaCfg {
ArenaCfg::new(cfg.shards_postings, ShardMode::Uniform).with_max_bytes(cfg.max_bytes)
}
type ArenaImage<'s> = (&'s [u8], &'s [u8]);
fn section_pair<'s>(
snap: &Snapshot<'s>,
meta: u16,
pool: u16,
) -> Result<Option<ArenaImage<'s>>, Error> {
match (snap.section(meta), snap.section(pool)) {
(Some(m), Some(p)) => Ok(Some((m, p))),
(None, None) => Ok(None),
_ => Err(Error::Corrupt("snapshot section pair is incomplete")),
}
}
fn ordered(cfg: &Config) -> ArenaCfg {
ArenaCfg::new(cfg.shards_edges, ShardMode::Ordered).with_max_bytes(cfg.max_bytes)
}