mod limited;
mod root_exact_cache;
#[cfg(test)]
#[path = "root_exact_profile.rs"]
pub(crate) mod root_exact_profile;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::mem::size_of;
use std::ops::Range;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use crate::storage_adapter::ValueSemantics;
use bytes::Bytes;
use smallvec::SmallVec;
use tracing::Instrument as _;
use super::*;
use crate::row_payload::CertifiedRowBatch as WasmCertifiedRowBatch;
use crate::storage_adapter::{BufferRange, EncodedMutationBatch, EncodedPut};
use crate::tracked_state::TrackedStateReadColumns;
pub(crate) const ROW_NAMESPACE: &str = "hot_state.row.v21";
pub(crate) const FILE_NAMESPACE: &str = "hot_state.file_schema.v18";
pub(crate) const DIFF_NAMESPACE: &str = "hot_state.diff.v17";
pub(crate) const COLLECTION_CONTROL_NAMESPACE: &str = "hot_state.collection_control.v1";
pub(crate) const INDEX_NAMESPACE: &str = "hot_state.index.v1";
pub(crate) const ROW_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_001b),
ROW_NAMESPACE,
ValueSemantics::Mutable,
);
pub(crate) const FILE_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_001c),
FILE_NAMESPACE,
ValueSemantics::Mutable,
);
pub(crate) const DIFF_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_001d),
DIFF_NAMESPACE,
ValueSemantics::Mutable,
);
pub(crate) const INDEX_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0033),
INDEX_NAMESPACE,
ValueSemantics::Mutable,
);
pub(crate) const COLLECTION_CONTROL_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0023),
COLLECTION_CONTROL_NAMESPACE,
ValueSemantics::Mutable,
);
pub(crate) const DETERMINISTIC_IDENTITY_WITNESS_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0034),
"hot_state.deterministic_identity_witness.v1",
ValueSemantics::Mutable,
);
#[derive(musli::Encode, musli::Decode)]
#[musli(packed)]
struct DeterministicIdentityWitness {
collection_control: Vec<u8>,
presence: u8,
}
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static BROAD_CANONICAL_CREATED_AT_LOOKUPS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static BROAD_CANONICAL_CREATED_AT_KEYS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static BROAD_CANONICAL_CREATED_AT_HITS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static HOT_SCAN_DECODED_ENTRIES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static HOT_SCAN_MATCHED_ENTRIES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static HOT_SCAN_TOMBSTONE_ENTRIES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static INTERVAL_LOCAL_TOMBSTONE_ROUTES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static INTERVAL_LOCAL_TOMBSTONE_OFFERED: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static INTERVAL_LOCAL_TOMBSTONE_CANDIDATES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static INTERVAL_LOCAL_TOMBSTONE_ELIDED: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static COMPACTED_TOMBSTONE_ROUTES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static COMPACTED_TOMBSTONE_OFFERED: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static COMPACTED_TOMBSTONE_CANDIDATES: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) static COMPACTED_TOMBSTONE_COMPACTED: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub(crate) const PACKED_CURRENT_BASE_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0024),
"hot_state.packed_current_base.v1",
ValueSemantics::Mutable,
);
pub(crate) const PACKED_CURRENT_BASE_CONTROL_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0025),
"hot_state.packed_current_base_control.v1",
ValueSemantics::Mutable,
);
pub(crate) const PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0027),
"hot_state.packed_current_exclusive_schema_base.v1",
ValueSemantics::Mutable,
);
pub(crate) const ROOT_CURRENT_BASE_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0028),
"hot_state.root_current_base.v1",
ValueSemantics::Mutable,
);
const HOT_DENSE_SCAN_MIN_IDENTITIES: usize = 64;
const HOT_DENSE_SCAN_MAX_OVERREAD: usize = 2;
const HOT_DIFF_SEGMENT_VERSION: u8 = 1;
const HOT_DIFF_SEGMENT_HEADER_BYTES: usize = 5;
const HOT_DIFF_SEGMENT_MAX_BYTES: usize = 256 * 1024;
const HOT_DIFF_SEGMENT_MAX_IDENTITIES: u32 = 4_096;
const HOT_DIFF_PACK_MIN_IDENTITIES: usize = 64;
const FILE_DESCRIPTOR_SCHEMA_KEY: &str = "lix_file_descriptor";
const CERTIFIED_ROW_BATCH_MAGIC_V2: &[u8; 4] = b"CEB2";
pub(crate) const CERTIFIED_ROW_BATCH_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_001f),
"hot_state.certified_row_batch.v1",
ValueSemantics::Mutable,
);
pub(crate) const CERTIFIED_ROW_BATCH_MANIFEST_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0021),
"hot_state.certified_row_batch_manifest.v2",
ValueSemantics::Mutable,
);
pub(crate) const CERTIFIED_ROW_BATCH_PAGE_SPACE: StorageSpace = StorageSpace::declare(
StorageSpaceId(0x0004_0022),
"hot_state.certified_row_batch_page.v1",
ValueSemantics::Mutable,
);
pub(crate) struct CertifiedRowBatchFileRef<'a> {
pub(crate) branch_id: &'a str,
pub(crate) file_id: &'a str,
pub(crate) batches: &'a [WasmCertifiedRowBatch],
}
pub(crate) async fn stage_certified_row_batches(
read: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
file_writes: &[CertifiedRowBatchFileRef<'_>],
controls: &BTreeMap<String, BranchHeadControl>,
observations: &BTreeMap<String, crate::branch::BranchHeadControlObservation>,
commit_created_at: &BTreeMap<CommitId, LixTimestamp>,
root_backed_branch_publications: &BTreeSet<String>,
) -> Result<(), LixError> {
let mut content_owners = BTreeSet::new();
for file in file_writes {
for batch in file.batches {
if !content_owners.insert((file.branch_id, file.file_id, batch.format)) {
return Err(head_value_error(format!(
"certified row batches duplicate branch '{}', file '{}', format {}",
file.branch_id, file.file_id, batch.format
)));
}
}
}
let mut complete_manifest_suffixes = BTreeMap::<String, Vec<Vec<u8>>>::new();
for file in file_writes {
for batch in file
.batches
.iter()
.filter(|batch| batch.complete_file_state)
{
let mut suffix = Vec::new();
append_batch_text(&mut suffix, file.file_id)?;
suffix.extend_from_slice(&batch.format.to_le_bytes());
complete_manifest_suffixes
.entry(file.branch_id.to_owned())
.or_default()
.push(suffix);
}
}
let needs_branch_creation_donor = controls.keys().any(|branch_id| {
!root_backed_branch_publications.contains(branch_id)
&& observations
.get(branch_id)
.is_none_or(|observation| observation.control.is_none())
});
let durable_controls = if needs_branch_creation_donor {
BranchHeadControlContext::new().reader(read).scan().await?
} else {
Vec::new()
};
let mut inherited_manifests = BTreeMap::new();
for (branch_id, control) in controls {
if root_backed_branch_publications.contains(branch_id) {
continue;
}
let source_generations = observations
.get(branch_id)
.and_then(|observation| observation.control)
.map(|previous| BTreeSet::from([previous.tracked_generation]))
.unwrap_or_else(|| {
durable_controls
.iter()
.filter(|(_, candidate)| candidate.head_commit_id == control.head_commit_id)
.map(|(_, candidate)| candidate.tracked_generation)
.collect()
});
for source_generation in source_generations
.into_iter()
.filter(|generation| *generation != control.tracked_generation)
{
let previous_prefix = source_generation.as_uuid().as_bytes().to_vec();
let range = StoragePrefix {
bytes: Bytes::from(previous_prefix.clone()),
}
.to_range()?;
let mut cursor = read
.begin_scan(
CERTIFIED_ROW_BATCH_MANIFEST_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
let manifests = cursor.collect_all().await?;
for entry in manifests {
let suffix = entry
.key
.0
.get(previous_prefix.len()..)
.ok_or_else(|| head_value_error("truncated certified manifest key"))?;
if complete_manifest_suffixes
.get(branch_id)
.is_some_and(|prefixes| {
prefixes.iter().any(|prefix| suffix.starts_with(prefix))
})
{
continue;
}
let mut key = control.tracked_generation.as_uuid().as_bytes().to_vec();
key.extend_from_slice(suffix);
let value = full_value_bytes(entry.value)?;
match inherited_manifests.entry(key) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(value);
}
std::collections::btree_map::Entry::Occupied(entry)
if entry.get() == &value => {}
std::collections::btree_map::Entry::Occupied(_) => {
return Err(head_value_error(
"certified manifests disagree for the same inherited key",
));
}
}
}
}
}
for (key, value) in inherited_manifests {
writes.put(
CERTIFIED_ROW_BATCH_MANIFEST_SPACE,
StorageKey(Bytes::from(key)),
StorageValue { bytes: value },
);
}
for file in file_writes {
if file.batches.is_empty() {
continue;
}
let control = controls.get(file.branch_id).ok_or_else(|| {
head_value_error("certified row batch has no published branch control")
})?;
let created_at = commit_created_at
.get(&control.head_commit_id)
.copied()
.ok_or_else(|| head_value_error("certified row batch has no commit timestamp"))?;
for batch in file.batches {
if batch.complete_file_state {
let mut manifest_prefix = control.tracked_generation.as_uuid().as_bytes().to_vec();
append_batch_text(&mut manifest_prefix, file.file_id)?;
manifest_prefix.extend_from_slice(&batch.format.to_le_bytes());
let range = StoragePrefix {
bytes: Bytes::from(manifest_prefix),
}
.to_range()?;
let mut cursor = read
.begin_scan(
CERTIFIED_ROW_BATCH_MANIFEST_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
let prior_manifests = cursor.collect_all().await?;
for entry in prior_manifests {
writes.delete(CERTIFIED_ROW_BATCH_MANIFEST_SPACE, entry.key);
}
}
let mut content_key = control.head_commit_id.as_uuid().as_bytes().to_vec();
append_batch_text(&mut content_key, file.file_id)?;
content_key.extend_from_slice(&batch.format.to_le_bytes());
let schema_bytes = batch
.schema_keys
.iter()
.try_fold(2usize, |total, schema| total.checked_add(2 + schema.len()))
.ok_or_else(|| head_value_error("certified schema list size overflowed"))?;
let mut value = Vec::with_capacity(
4 + schema_bytes
+ 2
+ file.file_id.len()
+ 16
+ 2
+ created_at.to_string().len()
+ 2
+ 8
+ 8
+ 4
+ 4
+ batch.pages.len().saturating_mul(12),
);
value.extend_from_slice(CERTIFIED_ROW_BATCH_MAGIC_V2);
value.extend_from_slice(
&u16::try_from(batch.schema_keys.len())
.map_err(|_| head_value_error("certified batch has too many schemas"))?
.to_le_bytes(),
);
for schema_key in &batch.schema_keys {
append_batch_text(&mut value, schema_key)?;
}
append_batch_text(&mut value, file.file_id)?;
value.extend_from_slice(control.head_commit_id.as_uuid().as_bytes());
append_batch_text(&mut value, &created_at.to_string())?;
value.extend_from_slice(&batch.format.to_le_bytes());
value.extend_from_slice(&batch.row_count.to_le_bytes());
value.extend_from_slice(&batch.creates.high.to_le_bytes());
value.extend_from_slice(&batch.creates.low.to_le_bytes());
value.extend_from_slice(
&u32::try_from(batch.pages.len())
.map_err(|_| head_value_error("certified row batch has too many pages"))?
.to_le_bytes(),
);
for (page_index, page) in batch.pages.iter().enumerate() {
let (first_local_ref, last_local_ref) = match batch.format {
crate::row_payload::HOST_CERTIFIED_PACKET_FORMAT => {
certified_packet_page_local_ref_range(page)?.unwrap_or((0, u32::MAX))
}
crate::row_payload::HOST_CERTIFIED_ZSTD_PACKET_FORMAT => {
certified_zstd_packet_page_header(page)?.0
}
format => {
return Err(head_value_error(format!(
"unsupported v69 certified row batch format {format}"
)));
}
};
value.extend_from_slice(&first_local_ref.to_le_bytes());
value.extend_from_slice(&last_local_ref.to_le_bytes());
value.extend_from_slice(
&u32::try_from(page.len())
.map_err(|_| head_value_error("certified row batch page exceeds 4GiB"))?
.to_le_bytes(),
);
writes.put(
CERTIFIED_ROW_BATCH_PAGE_SPACE,
certified_row_batch_page_key(
&content_key,
u32::try_from(page_index).map_err(|_| {
head_value_error("certified row batch has too many pages")
})?,
),
StorageValue {
bytes: page.clone(),
},
);
}
writes.put(
CERTIFIED_ROW_BATCH_SPACE,
StorageKey(Bytes::from(content_key.clone())),
StorageValue {
bytes: Bytes::from(value),
},
);
let mut manifest_key = control.tracked_generation.as_uuid().as_bytes().to_vec();
append_batch_text(&mut manifest_key, file.file_id)?;
manifest_key.extend_from_slice(&batch.format.to_le_bytes());
manifest_key.extend_from_slice(control.head_commit_id.as_uuid().as_bytes());
writes.put(
CERTIFIED_ROW_BATCH_MANIFEST_SPACE,
StorageKey(Bytes::from(manifest_key)),
StorageValue {
bytes: Bytes::from(encode_certified_manifest_value(
&batch.schema_keys,
&content_key,
)?),
},
);
}
}
Ok(())
}
fn encode_certified_manifest_value(
schema_keys: &[String],
content_key: &[u8],
) -> Result<Vec<u8>, LixError> {
let schema_bytes = schema_keys
.iter()
.try_fold(2usize, |total, schema| total.checked_add(2 + schema.len()))
.ok_or_else(|| head_value_error("certified manifest schema list size overflowed"))?;
let mut value = Vec::with_capacity(schema_bytes + content_key.len());
value.extend_from_slice(
&u16::try_from(schema_keys.len())
.map_err(|_| head_value_error("certified manifest has too many schemas"))?
.to_le_bytes(),
);
for schema_key in schema_keys {
append_batch_text(&mut value, schema_key)?;
}
value.extend_from_slice(content_key);
Ok(value)
}
fn append_batch_text(output: &mut Vec<u8>, value: &str) -> Result<(), LixError> {
output.extend_from_slice(
&u16::try_from(value.len())
.map_err(|_| head_value_error("certified row batch text exceeds 64KiB"))?
.to_le_bytes(),
);
output.extend_from_slice(value.as_bytes());
Ok(())
}
fn certified_row_batch_page_key(content_key: &[u8], page_index: u32) -> StorageKey {
let mut key = Vec::with_capacity(content_key.len() + 4);
key.extend_from_slice(content_key);
key.extend_from_slice(&page_index.to_be_bytes());
StorageKey(Bytes::from(key))
}
struct CertifiedPacketReader<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> CertifiedPacketReader<'a> {
fn bytes(&mut self, length: usize) -> Result<&'a [u8], LixError> {
let end = self
.offset
.checked_add(length)
.filter(|end| *end <= self.bytes.len())
.ok_or_else(|| head_value_error("truncated certified packet page"))?;
let value = &self.bytes[self.offset..end];
self.offset = end;
Ok(value)
}
fn u8(&mut self) -> Result<u8, LixError> {
Ok(self.bytes(1)?[0])
}
fn u32(&mut self) -> Result<u32, LixError> {
Ok(u32::from_le_bytes(
self.bytes(4)?.try_into().expect("fixed packet u32 width"),
))
}
fn u64(&mut self) -> Result<u64, LixError> {
Ok(u64::from_le_bytes(
self.bytes(8)?.try_into().expect("fixed packet u64 width"),
))
}
}
fn certified_packet_page_local_ref_range(page: &[u8]) -> Result<Option<(u32, u32)>, LixError> {
let mut rows = CertifiedPacketReader {
bytes: page,
offset: 0,
};
let mut first = None;
let mut last = None;
while rows.offset < rows.bytes.len() {
let record_len = rows.u32()? as usize;
let record_bytes = rows.bytes(record_len)?;
let mut record = CertifiedPacketReader {
bytes: record_bytes,
offset: 0,
};
if record.u8()? != 2 {
return Ok(None);
}
let schema_len = record.u32()? as usize;
let _schema = record.bytes(schema_len)?;
let local_ref = u32::try_from(record.u64()?)
.map_err(|_| head_value_error("certified packet local reference exceeds u32"))?;
if last.is_some_and(|previous| previous >= local_ref) {
return Ok(None);
}
first.get_or_insert(local_ref);
last = Some(local_ref);
}
Ok(first.zip(last))
}
fn certified_zstd_packet_page_header(page: &[u8]) -> Result<((u32, u32), usize, &[u8]), LixError> {
let (header, compressed) = page
.split_at_checked(12)
.ok_or_else(|| head_value_error("compressed certified packet page is truncated"))?;
let first_local_ref = u32::from_le_bytes(
header[..4]
.try_into()
.expect("compressed packet first local ref"),
);
let last_local_ref = u32::from_le_bytes(
header[4..8]
.try_into()
.expect("compressed packet last local ref"),
);
if first_local_ref > last_local_ref {
return Err(head_value_error(
"compressed certified packet page has an inverted local-ref range",
));
}
let uncompressed_len = u32::from_le_bytes(
header[8..12]
.try_into()
.expect("compressed packet uncompressed length"),
) as usize;
if uncompressed_len == 0 || uncompressed_len > 64 * 1024 * 1024 {
return Err(head_value_error(
"compressed certified packet page has an invalid uncompressed length",
));
}
Ok((
(first_local_ref, last_local_ref),
uncompressed_len,
compressed,
))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, musli::Encode, musli::Decode)]
#[musli(packed)]
struct HotCollectionControl {
active_generation: CommitId,
live_count: u64,
ordered_identity_digest: Option<[u8; 32]>,
}
#[cfg(any(test, feature = "storage-benches"))]
fn test_snapshot(delta: &TrackedHeadDeltaRef<'_>) -> Result<Option<Vec<u8>>, LixError> {
match (delta.deleted, delta.snapshot) {
(true, _) => Ok(None),
(false, Some(snapshot)) => {
let value = serde_json::from_str(snapshot).map_err(|error| {
head_value_error(&format!(
"test current-state snapshot is invalid JSON: {error}"
))
})?;
WasmTypedRow::from_builtin_json(delta.schema_key, delta.row_pk, &value)
.or_else(|_| WasmTypedRow::from_test_json_unchecked(delta.row_pk, &value))
.and_then(|row| encode_snapshot(&row))
.map(Some)
}
(false, None) => Err(head_value_error(
"live test current-state row is missing its payload",
)),
}
}
#[cfg(any(test, feature = "storage-benches"))]
fn test_current_delta<'a>(
delta: &'a TrackedHeadDeltaRef<'a>,
snapshot: Option<&'a [u8]>,
) -> CurrentStateDeltaRef<'a> {
CurrentStateDeltaRef {
schema_key: delta.schema_key,
file_id: delta.file_id,
row_pk: delta.row_pk,
change_id: Some(delta.change_id),
commit_id: Some(delta.commit_id),
untracked: false,
deleted: delta.deleted,
created_at: delta.created_at,
updated_at: delta.updated_at,
snapshot,
metadata: delta.metadata.as_ref(),
columnar_base_coordinate: None,
}
}
const COMPLETE_HOT_COLLECTION_DIGEST_DOMAIN: &[u8] = b"lix.complete-hot-collection-identities.v1";
struct CompleteHotCollectionDigest {
canonical: blake3::Hasher,
single_string: blake3::Hasher,
single_string_compatible: bool,
previous_key: Vec<u8>,
}
impl CompleteHotCollectionDigest {
fn new(
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Self {
let mut canonical = blake3::Hasher::new();
canonical.update(COMPLETE_HOT_COLLECTION_DIGEST_DOMAIN);
let scope_key = hot_collection_control_key(branch_id, branch_generation, scope);
canonical.update(&(scope_key.len() as u64).to_le_bytes());
canonical.update(&scope_key);
Self {
canonical,
single_string: blake3::Hasher::new(),
single_string_compatible: scope.file_id.is_none(),
previous_key: Vec::new(),
}
}
fn push(&mut self, identity: &HeadRowIdentity, canonical_key: &[u8]) -> Result<(), LixError> {
self.push_parts(&identity.row_pk, identity.file_id.as_deref(), canonical_key)
}
fn push_parts(
&mut self,
row_pk: &RowPk,
file_id: Option<&str>,
canonical_key: &[u8],
) -> Result<(), LixError> {
if !self.previous_key.is_empty() {
match self.previous_key.as_slice().cmp(canonical_key) {
Ordering::Less => {}
Ordering::Equal => {
return Err(head_value_error(
"complete collection contains a duplicate canonical identity",
));
}
Ordering::Greater => {
return Err(head_value_error(
"complete collection identities are not in canonical order",
));
}
}
}
self.previous_key.clear();
self.previous_key.extend_from_slice(canonical_key);
self.canonical
.update(&(canonical_key.len() as u64).to_le_bytes());
self.canonical.update(canonical_key);
if self.single_string_compatible {
match (file_id, row_pk.as_single_string()) {
(None, Ok(value)) => {
self.single_string
.update(&(value.len() as u64).to_le_bytes());
self.single_string.update(value.as_bytes());
}
_ => self.single_string_compatible = false,
}
}
Ok(())
}
fn finish(self) -> [u8; 32] {
if self.single_string_compatible {
*self.single_string.finalize().as_bytes()
} else {
*self.canonical.finalize().as_bytes()
}
}
}
const DEFERRED_ROOT_LIVE_COUNT: u64 = crate::collection_generation::DEFERRED_LIVE_COUNT;
const TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES: usize = 64;
const TRANSACTION_PACKED_POINT_CACHE_MIN_OBSERVATIONS: u8 = 16;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct HotCollectionCacheKey {
branch_id: String,
generation: CommitId,
schema_key: String,
file_id: Option<String>,
}
#[derive(Default)]
pub(crate) struct HotStateTransactionCache {
collection_controls: StdMutex<BTreeMap<HotCollectionCacheKey, HotCollectionControl>>,
packed_point_generation_observations: StdMutex<SmallVec<[(CommitId, u8); 4]>>,
packed_current_base_refs: StdMutex<BTreeMap<(String, CommitId), Vec<PackedCurrentBaseRef>>>,
commit_delta_points: crate::tracked_state::CommitDeltaPointReadCache,
}
impl HotStateTransactionCache {
fn should_reuse_packed_points(&self, generation: CommitId) -> Result<bool, LixError> {
let mut generations = self
.packed_point_generation_observations
.lock()
.map_err(|_| hot_state_cache_lock_error())?;
if let Some((_, observations)) = generations
.iter_mut()
.find(|(candidate, _)| *candidate == generation)
{
*observations = observations.saturating_add(1);
return Ok(*observations >= TRANSACTION_PACKED_POINT_CACHE_MIN_OBSERVATIONS);
}
if generations.len() < TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES {
generations.push((generation, 1));
}
Ok(false)
}
fn packed_current_base_refs(
&self,
branch_id: &str,
generation: CommitId,
) -> Result<Option<Vec<PackedCurrentBaseRef>>, LixError> {
Ok(self
.packed_current_base_refs
.lock()
.map_err(|_| hot_state_cache_lock_error())?
.get(&(branch_id.to_owned(), generation))
.cloned())
}
fn remember_packed_current_base_refs(
&self,
branch_id: &str,
generation: CommitId,
refs: &[PackedCurrentBaseRef],
) -> Result<(), LixError> {
let mut entries = self
.packed_current_base_refs
.lock()
.map_err(|_| hot_state_cache_lock_error())?;
if entries.len() < TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES {
entries
.entry((branch_id.to_owned(), generation))
.or_insert_with(|| refs.to_vec());
}
Ok(())
}
fn collection_control(
&self,
key: &HotCollectionCacheKey,
) -> Result<Option<HotCollectionControl>, LixError> {
Ok(self
.collection_controls
.lock()
.map_err(|_| hot_state_cache_lock_error())?
.get(key)
.copied())
}
fn remember_collection_control(
&self,
key: HotCollectionCacheKey,
control: HotCollectionControl,
) -> Result<(), LixError> {
let mut entries = self
.collection_controls
.lock()
.map_err(|_| hot_state_cache_lock_error())?;
if entries.len() < TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES {
entries.entry(key).or_insert(control);
}
Ok(())
}
}
pub(crate) struct PackedIdentityMembership {
cache: Arc<HotStateTransactionCache>,
cursor: crate::tracked_state::CommitDeltaLiveMembershipCursor,
schema_key: String,
live_count: u64,
ordered_identity_digest: [u8; 32],
encoded_key: Vec<u8>,
}
impl PackedIdentityMembership {
pub(crate) async fn contains_single_string(
&mut self,
store: &(impl StorageAdapterRead + ?Sized),
row_pk: &str,
) -> Result<Option<bool>, LixError> {
self.encoded_key.clear();
let encoded = crate::tracked_state::encode_single_string_key_ref_into(
&mut self.encoded_key,
&self.schema_key,
None,
row_pk,
);
self.cursor
.live_member(
store,
&self.cache.commit_delta_points,
&self.encoded_key[encoded],
)
.await
}
pub(crate) fn complete_generation(&self) -> (u64, [u8; 32]) {
(self.live_count, self.ordered_identity_digest)
}
}
fn hot_state_cache_lock_error() -> LixError {
LixError::new(
LixError::CODE_INTERNAL_ERROR,
"transaction hot-state metadata cache lock is poisoned",
)
}
struct PackedCollectionIncrement {
live_count: u64,
ordered_identity_digest: Option<[u8; 32]>,
}
fn hot_collection_control_key(
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Vec<u8> {
let mut key = hot_scope_prefix(branch_id, branch_generation);
write_key_string(&mut key, scope.schema_key, KEY_PART_FINAL);
write_file_id(&mut key, scope.file_id);
key
}
async fn load_root_current_base_commit(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
) -> Result<Option<CommitId>, LixError> {
let key = StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation)));
let value = PointReadPlan::new(ROOT_CURRENT_BASE_SPACE, &[key])
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.next()
.flatten();
let Some(value) = value else {
return Ok(None);
};
let StorageProjectedValue::FullValue(bytes) = value else {
return Err(head_value_error(
"root current-base read unexpectedly omitted its value",
));
};
if bytes.len() != 16 {
return Err(head_value_error(
"root current-base reference must contain one commit UUID",
));
}
Ok(Some(CommitId::new(
uuid::Uuid::from_slice(&bytes).map_err(|error| head_value_error(error.to_string()))?,
)))
}
async fn load_hot_collection_control(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<HotCollectionControl, LixError> {
if let Some(control) =
load_stored_hot_collection_control(store, branch_id, branch_generation, scope).await?
{
return Ok(control);
}
if let Some(base_commit_id) =
load_root_current_base_commit(store, branch_id, branch_generation).await?
{
load_root_collection_control_from_base(store, base_commit_id, branch_generation, scope)
.await
} else {
Ok(HotCollectionControl {
active_generation: branch_generation,
live_count: 0,
ordered_identity_digest: None,
})
}
}
async fn load_stored_hot_collection_control(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<Option<HotCollectionControl>, LixError> {
let key = StorageKey(Bytes::from(hot_collection_control_key(
branch_id,
branch_generation,
scope,
)));
let value = PointReadPlan::new(COLLECTION_CONTROL_SPACE, &[key])
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.next()
.flatten();
match value {
Some(value) => {
let StorageProjectedValue::FullValue(bytes) = value else {
return Err(head_value_error(
"hot collection-control read unexpectedly omitted its value",
));
};
storage_codec::decode("hot collection control", &bytes).map(Some)
}
None => Ok(None),
}
}
async fn load_hot_collection_visibility_control(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<HotCollectionControl, LixError> {
let key = StorageKey(Bytes::from(hot_collection_control_key(
branch_id,
branch_generation,
scope,
)));
let value = PointReadPlan::new(COLLECTION_CONTROL_SPACE, &[key])
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.next()
.flatten();
let Some(value) = value else {
return Ok(HotCollectionControl {
active_generation: branch_generation,
live_count: 1,
ordered_identity_digest: None,
});
};
let StorageProjectedValue::FullValue(bytes) = value else {
return Err(head_value_error(
"hot collection-control visibility read unexpectedly omitted its value",
));
};
storage_codec::decode("hot collection control", &bytes)
}
async fn load_root_collection_control_from_base(
store: &(impl StorageAdapterRead + ?Sized),
base_commit_id: CommitId,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<HotCollectionControl, LixError> {
let active_generation = load_root_active_collection_generations(store, base_commit_id, [scope])
.await?
.get(&(
scope.schema_key.to_owned(),
scope.file_id.map(str::to_owned),
))
.map(|generation| generation.commit_id)
.unwrap_or(branch_generation);
Ok(HotCollectionControl {
active_generation,
live_count: DEFERRED_ROOT_LIVE_COUNT,
ordered_identity_digest: None,
})
}
async fn load_hot_collection_controls(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
scopes: &[crate::collection_generation::CollectionScopeRef<'_>],
) -> Result<Vec<HotCollectionControl>, LixError> {
if scopes.is_empty() {
return Ok(Vec::new());
}
let values =
load_stored_hot_collection_controls(store, branch_id, branch_generation, scopes).await?;
let missing_scopes = scopes
.iter()
.copied()
.zip(&values)
.filter_map(|(scope, value)| value.is_none().then_some(scope))
.collect::<Vec<_>>();
let root_generations = if missing_scopes.is_empty() {
None
} else if let Some(base_commit_id) =
load_root_current_base_commit(store, branch_id, branch_generation).await?
{
Some(
load_root_active_collection_generations(
store,
base_commit_id,
missing_scopes.iter().copied(),
)
.await?,
)
} else {
None
};
let mut controls = Vec::with_capacity(scopes.len());
for (scope, value) in scopes.iter().copied().zip(values) {
controls.push(match (value, root_generations.as_ref()) {
(Some(control), _) => control,
(None, Some(generations)) => HotCollectionControl {
active_generation: generations
.get(&(
scope.schema_key.to_owned(),
scope.file_id.map(str::to_owned),
))
.map(|generation| generation.commit_id)
.unwrap_or(branch_generation),
live_count: DEFERRED_ROOT_LIVE_COUNT,
ordered_identity_digest: None,
},
(None, None) => HotCollectionControl {
active_generation: branch_generation,
live_count: 0,
ordered_identity_digest: None,
},
});
}
Ok(controls)
}
async fn load_stored_hot_collection_controls(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
scopes: &[crate::collection_generation::CollectionScopeRef<'_>],
) -> Result<Vec<Option<HotCollectionControl>>, LixError> {
let keys = scopes
.iter()
.copied()
.map(|scope| {
StorageKey(Bytes::from(hot_collection_control_key(
branch_id,
branch_generation,
scope,
)))
})
.collect::<Vec<_>>();
let values = PointReadPlan::new(COLLECTION_CONTROL_SPACE, &keys)
.materialize(store, StorageGetOptions::default())
.await?
.value;
values
.into_iter()
.map(|value| match value {
Some(value) => {
let StorageProjectedValue::FullValue(bytes) = value else {
return Err(head_value_error(
"hot collection-control batch read unexpectedly omitted its value",
));
};
storage_codec::decode("hot collection control", &bytes).map(Some)
}
None => Ok(None),
})
.collect()
}
fn stage_hot_collection_control(
writes: &mut StorageWriteSet,
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
control: HotCollectionControl,
) -> Result<(), LixError> {
writes.put(
COLLECTION_CONTROL_SPACE,
StorageKey(Bytes::from(hot_collection_control_key(
branch_id,
branch_generation,
scope,
))),
StorageValue {
bytes: Bytes::from(storage_codec::encode("hot collection control", &control)?),
},
);
Ok(())
}
async fn load_incremental_collection_controls(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
branch_generation: CommitId,
deltas: &[&CurrentStateDeltaRef<'_>],
) -> Result<BTreeMap<(String, Option<String>), HotCollectionControl>, LixError> {
use crate::collection_generation::{
COLLECTION_GENERATION_SCHEMA_KEY, CollectionScopeRef, collection_scope_from_row_pk,
};
let mut owned_scopes = BTreeSet::<(String, Option<String>)>::new();
for delta in deltas {
if delta.schema_key == COLLECTION_GENERATION_SCHEMA_KEY {
owned_scopes.insert(collection_scope_from_row_pk(delta.row_pk)?);
continue;
}
owned_scopes.insert((delta.schema_key.to_string(), None));
if let Some(file_id) = delta.file_id {
owned_scopes.insert((delta.schema_key.to_string(), Some(file_id.to_string())));
}
}
if owned_scopes.is_empty() {
return Ok(BTreeMap::new());
}
let scopes = owned_scopes
.iter()
.map(|(schema_key, file_id)| CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
})
.collect::<Vec<_>>();
let controls =
load_hot_collection_controls(store, branch_id, branch_generation, &scopes).await?;
Ok(scopes
.iter()
.copied()
.zip(controls)
.map(|(scope, control)| {
(
(
scope.schema_key.to_string(),
scope.file_id.map(str::to_string),
),
control,
)
})
.collect())
}
fn stage_incremental_collection_controls(
writes: &mut StorageWriteSet,
branch_id: &str,
branch_generation: CommitId,
deltas: &[&CurrentStateDeltaRef<'_>],
previous_values: &[Option<CertifiedCurrentStatePredecessor>],
mut controls: BTreeMap<(String, Option<String>), HotCollectionControl>,
certified_live_increments: &BTreeMap<(String, Option<String>), u64>,
) -> Result<(), LixError> {
use crate::collection_generation::{
COLLECTION_GENERATION_SCHEMA_KEY, CollectionScopeRef, collection_scope_from_row_pk,
};
let mut dirty_scopes = BTreeSet::new();
for (delta, previous) in deltas.iter().zip(previous_values) {
if delta.schema_key == COLLECTION_GENERATION_SCHEMA_KEY {
let scope = collection_scope_from_row_pk(delta.row_pk)?;
dirty_scopes.insert(scope);
continue;
}
for scope in [
Some((delta.schema_key.to_string(), None)),
delta
.file_id
.map(|file_id| (delta.schema_key.to_string(), Some(file_id.to_string()))),
]
.into_iter()
.flatten()
{
let control = controls
.get_mut(&scope)
.expect("row collection scope was loaded above");
control.ordered_identity_digest = None;
dirty_scopes.insert(scope);
}
let previous_live = previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.is_some_and(|value| {
!value.deleted
&& row_belongs_to_active_collection_generation(
&controls,
branch_generation,
delta.schema_key,
delta.file_id,
value.untracked,
value.commit_id,
)
});
let belongs_to_active_generation = row_belongs_to_active_collection_generation(
&controls,
branch_generation,
delta.schema_key,
delta.file_id,
delta.untracked,
delta.commit_id,
);
let next_live = !delta.deleted && belongs_to_active_generation;
if previous_live == next_live {
continue;
}
for scope in [
Some((delta.schema_key.to_string(), None)),
delta
.file_id
.map(|file_id| (delta.schema_key.to_string(), Some(file_id.to_string()))),
]
.into_iter()
.flatten()
{
let control = controls
.get_mut(&scope)
.expect("row collection scope was loaded above");
if control.live_count == DEFERRED_ROOT_LIVE_COUNT {
dirty_scopes.insert(scope);
continue;
}
control.live_count = if next_live {
control
.live_count
.checked_add(1)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?
} else {
control
.live_count
.checked_sub(1)
.ok_or_else(|| head_value_error("hot collection live count underflow"))?
};
dirty_scopes.insert(scope);
}
}
for (scope, increment) in certified_live_increments {
let control = controls
.get_mut(scope)
.expect("certified collection scope was loaded above");
if control.live_count != DEFERRED_ROOT_LIVE_COUNT {
control.live_count = control
.live_count
.checked_add(*increment)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?;
}
control.ordered_identity_digest = None;
dirty_scopes.insert(scope.clone());
}
for ((schema_key, file_id), control) in controls {
if !dirty_scopes.contains(&(schema_key.clone(), file_id.clone())) {
continue;
}
if scope_requires_exact_closure(branch_id, &schema_key, file_id.as_deref()) {
continue;
}
stage_hot_collection_control(
writes,
branch_id,
branch_generation,
CollectionScopeRef {
schema_key: &schema_key,
file_id: file_id.as_deref(),
},
control,
)?;
}
Ok(())
}
fn apply_incremental_collection_generation_deltas(
controls: &mut BTreeMap<(String, Option<String>), HotCollectionControl>,
deltas: &[&CurrentStateDeltaRef<'_>],
) -> Result<(), LixError> {
use crate::collection_generation::{
COLLECTION_GENERATION_SCHEMA_KEY, collection_scope_from_row_pk,
};
for delta in deltas {
if delta.schema_key != COLLECTION_GENERATION_SCHEMA_KEY {
continue;
}
if delta.deleted {
return Err(head_value_error(
"collection-generation controls cannot be tombstoned",
));
}
let scope = collection_scope_from_row_pk(delta.row_pk)?;
let control = controls
.get_mut(&scope)
.expect("collection marker target was loaded above");
control.active_generation = delta
.commit_id
.ok_or_else(|| head_value_error("tracked collection-generation row lacks commit_id"))?;
control.live_count = 0;
control.ordered_identity_digest = None;
}
Ok(())
}
fn survives_collection_generation_fence(
untracked: bool,
commit_id: Option<CommitId>,
active_generation: CommitId,
inclusive: bool,
) -> bool {
if untracked {
return true;
}
commit_id.is_some_and(|commit_id| {
if inclusive {
commit_id >= active_generation
} else {
commit_id > active_generation
}
})
}
fn row_belongs_to_active_collection_generation(
controls: &BTreeMap<(String, Option<String>), HotCollectionControl>,
branch_generation: CommitId,
schema_key: &str,
file_id: Option<&str>,
untracked: bool,
commit_id: Option<CommitId>,
) -> bool {
[
Some((schema_key.to_string(), None)),
file_id.map(|file_id| (schema_key.to_string(), Some(file_id.to_string()))),
]
.into_iter()
.flatten()
.all(|scope| {
let control = controls
.get(&scope)
.expect("row collection scope was loaded above");
control.active_generation == branch_generation
|| survives_collection_generation_fence(
untracked,
commit_id,
control.active_generation,
false,
)
})
}
fn scope_requires_exact_closure(branch_id: &str, schema_key: &str, file_id: Option<&str>) -> bool {
branch_id == crate::GLOBAL_BRANCH_ID
&& schema_key == EXACT_CLOSURE_SCHEMA_KEY
&& file_id.is_none()
}
const EXACT_CLOSURE_SCHEMA_KEY: &str = "lix_key_value";
pub(crate) async fn stage_deterministic_identity_witness_migration(
read: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
generation: CommitId,
current_state_revision: u64,
max_rows: usize,
max_bytes: usize,
) -> Result<Vec<crate::storage_adapter::StoragePrecondition>, LixError> {
use crate::storage_adapter::StoragePrecondition;
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let key = StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
generation,
scope,
)));
let values = PointReadPlan::new(COLLECTION_CONTROL_SPACE, std::slice::from_ref(&key))
.materialize(read, StorageGetOptions::default())
.await?
.value;
let Some(Some(StorageProjectedValue::FullValue(control_bytes))) = values.first() else {
if current_state_revision != 0 {
return Err(head_value_error(
"migration deterministic collection control is missing",
));
}
return Ok(vec![StoragePrecondition::KeyAbsent {
space: COLLECTION_CONTROL_SPACE,
key,
}]);
};
let control: HotCollectionControl =
storage_codec::decode("hot collection control", control_bytes)?;
if control.live_count == DEFERRED_ROOT_LIVE_COUNT || control.ordered_identity_digest.is_none() {
return Err(head_value_error(
"migration requires a complete deterministic collection control",
));
}
let prefix = hot_scope_prefix(crate::GLOBAL_BRANCH_ID, generation);
let mut selected = prefix.clone();
write_key_string(&mut selected, EXACT_CLOSURE_SCHEMA_KEY, KEY_PART_FINAL);
let mut cursor = read
.begin_scan(
ROW_SPACE,
StoragePrefix {
bytes: Bytes::from(selected),
}
.to_range()?,
StorageBeginScanOptions::default(),
)
.await?;
let mut digest = CompleteHotCollectionDigest::new(crate::GLOBAL_BRANCH_ID, generation, scope);
let (mut count, mut rows, mut bytes, mut presence) = (0_u64, 0_usize, 0_usize, 0_u8);
let mut file_controls = BTreeMap::new();
loop {
let (page, more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let raw = full_value_bytes(entry.value)?;
rows = rows
.checked_add(1)
.ok_or_else(|| head_value_error("witness migration row budget overflow"))?;
bytes = bytes
.checked_add(entry.key.0.len())
.and_then(|n| n.checked_add(raw.len()))
.ok_or_else(|| head_value_error("witness migration byte budget overflow"))?;
if rows > max_rows || bytes > max_bytes {
return Err(LixError::new(
"LIX_ERROR_MIGRATION_LIMIT_EXCEEDED",
"witness migration exceeds caller budget",
));
}
let identity = decode_hot_row_key_in_scope(&entry.key.0, &prefix)?;
if identity.schema_key != EXACT_CLOSURE_SCHEMA_KEY {
return Err(head_value_error("witness migration escaped exact scope"));
}
let canonical = encode_hot_row_key_parts(
crate::GLOBAL_BRANCH_ID,
generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
validate_canonical_exact_collection_key(&entry.key.0, &canonical)?;
let value = decode_head_value(&raw)?;
if identity.file_id.is_none() {
presence |= deterministic_identity_bit(&identity.row_pk).unwrap_or(0);
}
let schema_active = control.active_generation == generation
|| survives_collection_generation_fence(
value.untracked,
value.commit_id,
control.active_generation,
false,
);
let file_active = if let Some(file_id) = identity.file_id.as_deref() {
let file_control = if let Some(cached) = file_controls.get(file_id) {
*cached
} else {
let loaded = load_hot_collection_control(
read,
crate::GLOBAL_BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: Some(file_id),
},
)
.await?;
file_controls.insert(file_id.to_owned(), loaded);
loaded
};
file_control.active_generation == generation
|| survives_collection_generation_fence(
value.untracked,
value.commit_id,
file_control.active_generation,
false,
)
} else {
true
};
if !value.deleted && schema_active && file_active {
digest.push(&identity, &entry.key.0)?;
count += 1;
}
}
if !more {
break;
}
}
if count != control.live_count || Some(digest.finish()) != control.ordered_identity_digest {
return Err(head_value_error(
"witness migration collection identity closure mismatch",
));
}
let prior = PointReadPlan::new(
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
std::slice::from_ref(&key),
)
.materialize(read, StorageGetOptions::default())
.await?
.value;
let witness_guard = match prior.into_iter().next().flatten() {
Some(StorageProjectedValue::FullValue(expected)) => StoragePrecondition::KeyValueEquals {
space: DETERMINISTIC_IDENTITY_WITNESS_SPACE,
key: key.clone(),
expected,
},
None => StoragePrecondition::KeyAbsent {
space: DETERMINISTIC_IDENTITY_WITNESS_SPACE,
key: key.clone(),
},
_ => return Err(head_value_error("witness migration omitted native value")),
};
let witness = DeterministicIdentityWitness {
collection_control: control_bytes.to_vec(),
presence,
};
writes.put(
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
key.clone(),
StorageValue {
bytes: Bytes::from(storage_codec::encode(
"deterministic identity witness",
&witness,
)?),
},
);
Ok(vec![
StoragePrecondition::KeyValueEquals {
space: COLLECTION_CONTROL_SPACE,
key,
expected: control_bytes.clone(),
},
witness_guard,
])
}
pub(crate) async fn verify_migrated_deterministic_witness(
source: &(impl StorageAdapterRead + ?Sized),
target: &(impl StorageAdapterRead + ?Sized),
generation: CommitId,
max_rows: usize,
max_bytes: usize,
) -> Result<(), LixError> {
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let key = StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
generation,
scope,
)));
let control = PointReadPlan::new(COLLECTION_CONTROL_SPACE, std::slice::from_ref(&key))
.materialize(source, Default::default())
.await?
.value
.pop()
.flatten();
let mut witnesses = target
.begin_scan(
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
StoragePrefix {
bytes: Bytes::new(),
}
.to_range()?,
Default::default(),
)
.await?;
let (mut witness_rows, mut witness_bytes) = (0usize, 0usize);
while let Some(page) = witnesses.next_chunk().await? {
for entry in page {
let value = full_value_bytes(entry.value)?;
witness_rows = witness_rows.saturating_add(1);
witness_bytes = witness_bytes
.saturating_add(entry.key.0.len())
.saturating_add(value.len());
if witness_rows > max_rows || witness_bytes > max_bytes {
return Err(head_value_error(
"witness inventory exceeds qualification bounds",
));
}
if entry.key == key && control.is_some() {
continue;
}
let old = PointReadPlan::new(DETERMINISTIC_IDENTITY_WITNESS_SPACE, &[entry.key])
.materialize(source, Default::default())
.await?
.value
.pop()
.flatten();
if !matches!(old, Some(StorageProjectedValue::FullValue(old)) if old == value) {
return Err(head_value_error(
"migration invented an unrelated deterministic witness",
));
}
}
}
let Some(StorageProjectedValue::FullValue(control)) = control else {
return Ok(());
};
let prefix = hot_scope_prefix(crate::GLOBAL_BRANCH_ID, generation);
let mut selected = prefix.clone();
write_key_string(&mut selected, EXACT_CLOSURE_SCHEMA_KEY, KEY_PART_FINAL);
let mut cursor = source
.begin_scan(
ROW_SPACE,
StoragePrefix {
bytes: Bytes::from(selected),
}
.to_range()?,
Default::default(),
)
.await?;
let (mut rows, mut bytes, mut presence) = (0usize, 0usize, 0u8);
while let Some(page) = cursor.next_chunk().await? {
for entry in page {
let value = full_value_bytes(entry.value)?;
rows = rows.saturating_add(1);
bytes = bytes
.saturating_add(entry.key.0.len())
.saturating_add(value.len());
if rows > max_rows || bytes > max_bytes {
return Err(head_value_error(
"deterministic witness qualification exceeds bounds",
));
}
let identity = decode_hot_row_key_in_scope(&entry.key.0, &prefix)?;
let canonical = encode_hot_row_key_parts(
crate::GLOBAL_BRANCH_ID,
generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
validate_canonical_exact_collection_key(&entry.key.0, &canonical)?;
if identity.schema_key != EXACT_CLOSURE_SCHEMA_KEY {
return Err(head_value_error(
"deterministic witness qualification escaped scope",
));
}
if identity.file_id.is_none() {
if identity.row_pk == RowPk::single("lix_deterministic_mode") {
presence |= 1;
}
if identity.row_pk == RowPk::single("lix_deterministic_sequence_number") {
presence |= 2;
}
}
}
}
let value = PointReadPlan::new(DETERMINISTIC_IDENTITY_WITNESS_SPACE, &[key])
.materialize(target, Default::default())
.await?
.value
.pop()
.flatten();
let Some(StorageProjectedValue::FullValue(value)) = value else {
return Err(head_value_error("migrated deterministic witness absent"));
};
let witness: DeterministicIdentityWitness =
storage_codec::decode("migrated deterministic witness", &value)?;
if witness.collection_control != control.as_ref() || witness.presence != presence {
return Err(head_value_error(
"migrated deterministic witness differs from source identities/control",
));
}
Ok(())
}
fn deterministic_identity_bit(pk: &RowPk) -> Option<u8> {
if pk == &RowPk::single("lix_deterministic_mode") {
Some(1)
} else if pk == &RowPk::single("lix_deterministic_sequence_number") {
Some(2)
} else {
None
}
}
async fn restage_exact_closure_collection_control(
store: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
staged: &BTreeMap<HeadRowIdentity, Option<Bytes>>,
) -> Result<(), LixError> {
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let marker_row_pk = RowPk::single(crate::collection_generation::collection_scope_key(scope));
let filter = TrackedStateFilter {
schema_keys: vec![
EXACT_CLOSURE_SCHEMA_KEY.to_owned(),
crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
],
include_tombstones: true,
..TrackedStateFilter::default()
};
let HotScanEntries::Decoded(entries) =
hot_scan_entries(store, branch_id, generation, &filter, None, None)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget")
else {
unreachable!("an unconstrained HOT scan cannot select the finite point-read route");
};
let mut rows: HotRowMap = BTreeMap::new();
for (identity, bytes) in entries {
let identity = identity.into_row_identity();
if identity.schema_key == crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
&& identity.row_pk != marker_row_pk
{
continue;
}
rows.insert(identity, bytes);
}
for (identity, value) in staged {
match value {
Some(bytes) => {
rows.insert(identity.clone(), bytes.clone());
}
None => {
rows.remove(identity);
}
}
}
stage_complete_collection_controls(writes, branch_id, generation, &rows)
}
fn complete_collection_generation_controls(
branch_generation: CommitId,
rows: &HotRowMap,
) -> Result<BTreeMap<(String, Option<String>), HotCollectionControl>, LixError> {
use crate::collection_generation::{
COLLECTION_GENERATION_SCHEMA_KEY, collection_scope_from_row_pk,
};
let mut controls = BTreeMap::<(String, Option<String>), HotCollectionControl>::new();
for (identity, bytes) in rows {
if identity.schema_key == COLLECTION_GENERATION_SCHEMA_KEY {
let target = collection_scope_from_row_pk(&identity.row_pk)?;
let marker = decode_head_value(bytes)?;
let active_generation = marker.commit_id.ok_or_else(|| {
head_value_error("tracked collection-generation row lacks commit_id")
})?;
controls.insert(
target,
HotCollectionControl {
active_generation,
live_count: 0,
ordered_identity_digest: None,
},
);
continue;
}
controls
.entry((identity.schema_key.clone(), None))
.or_insert(HotCollectionControl {
active_generation: branch_generation,
live_count: 0,
ordered_identity_digest: None,
});
if let Some(file_id) = &identity.file_id {
controls
.entry((identity.schema_key.clone(), Some(file_id.clone())))
.or_insert(HotCollectionControl {
active_generation: branch_generation,
live_count: 0,
ordered_identity_digest: None,
});
}
}
Ok(controls)
}
fn stage_complete_collection_controls(
writes: &mut StorageWriteSet,
branch_id: &str,
branch_generation: CommitId,
rows: &HotRowMap,
) -> Result<(), LixError> {
use crate::collection_generation::{COLLECTION_GENERATION_SCHEMA_KEY, CollectionScopeRef};
let mut controls = complete_collection_generation_controls(branch_generation, rows)?;
let mut physical_buckets = BTreeMap::<(String, Option<String>), Vec<&HeadRowIdentity>>::new();
for (identity, bytes) in rows {
if identity.schema_key == COLLECTION_GENERATION_SCHEMA_KEY {
continue;
}
let value = decode_head_value(bytes)?;
if value.deleted {
continue;
}
let schema_scope = (identity.schema_key.clone(), None);
let file_scope = identity
.file_id
.as_ref()
.map(|file_id| (identity.schema_key.clone(), Some(file_id.clone())));
if !row_belongs_to_active_collection_generation(
&controls,
branch_generation,
&identity.schema_key,
identity.file_id.as_deref(),
value.untracked,
value.commit_id,
) {
continue;
}
physical_buckets
.entry((identity.schema_key.clone(), identity.file_id.clone()))
.or_default()
.push(identity);
for scope in [Some(schema_scope), file_scope].into_iter().flatten() {
let control = controls
.get_mut(&scope)
.expect("complete row collection control was initialized above");
control.live_count = control
.live_count
.checked_add(1)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?;
}
}
let mut digests = controls
.keys()
.map(|(schema_key, file_id)| {
(
(schema_key.clone(), file_id.clone()),
CompleteHotCollectionDigest::new(
branch_id,
branch_generation,
CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
),
)
})
.collect::<BTreeMap<_, _>>();
for ((schema_key, file_id), identities) in physical_buckets {
for identity in identities {
let canonical_key = encode_hot_row_key_parts(
branch_id,
branch_generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
digests
.get_mut(&(schema_key.clone(), None))
.expect("complete row schema digest was initialized above")
.push(identity, &canonical_key)?;
if file_id.is_some() {
digests
.get_mut(&(schema_key.clone(), file_id.clone()))
.expect("complete row file digest was initialized above")
.push(identity, &canonical_key)?;
}
}
}
for ((schema_key, file_id), mut control) in controls {
control.ordered_identity_digest = Some(
digests
.remove(&(schema_key.clone(), file_id.clone()))
.expect("complete collection digest was initialized above")
.finish(),
);
if scope_requires_exact_closure(branch_id, &schema_key, file_id.as_deref()) {
let presence = rows
.keys()
.filter(|identity| {
identity.schema_key == EXACT_CLOSURE_SCHEMA_KEY && identity.file_id.is_none()
})
.fold(0, |mask, identity| {
mask | deterministic_identity_bit(&identity.row_pk).unwrap_or(0)
});
let witness = DeterministicIdentityWitness {
collection_control: storage_codec::encode("hot collection control", &control)?,
presence,
};
writes.put(
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
StorageKey(Bytes::from(hot_collection_control_key(
branch_id,
branch_generation,
CollectionScopeRef {
schema_key: &schema_key,
file_id: file_id.as_deref(),
},
))),
StorageValue {
bytes: Bytes::from(storage_codec::encode(
"deterministic identity witness",
&witness,
)?),
},
);
}
stage_hot_collection_control(
writes,
branch_id,
branch_generation,
CollectionScopeRef {
schema_key: &schema_key,
file_id: file_id.as_deref(),
},
control,
)?;
}
Ok(())
}
#[derive(Clone)]
struct PackedCurrentBaseRef {
commit_id: CommitId,
checkpoint_commit_id: Option<CommitId>,
file_id: Option<String>,
coverage_key: Bytes,
}
const PACKED_CURRENT_BASE_FILE_SCOPE_MAGIC: &[u8; 4] = b"PBF1";
fn packed_current_base_value(
checkpoint_commit_id: Option<CommitId>,
file_id: Option<&str>,
) -> Result<Bytes, LixError> {
let checkpoint = checkpoint_commit_id.unwrap_or_default();
let Some(file_id) = file_id else {
return Ok(Bytes::copy_from_slice(checkpoint.as_uuid().as_bytes()));
};
let file_len = u32::try_from(file_id.len())
.map_err(|_| head_value_error("packed current-base file id exceeds u32"))?;
let mut value = Vec::with_capacity(4 + 16 + 4 + file_id.len());
value.extend_from_slice(PACKED_CURRENT_BASE_FILE_SCOPE_MAGIC);
value.extend_from_slice(checkpoint.as_uuid().as_bytes());
value.extend_from_slice(&file_len.to_be_bytes());
value.extend_from_slice(file_id.as_bytes());
Ok(Bytes::from(value))
}
fn decode_packed_current_base_value(
bytes: &[u8],
) -> Result<(Option<CommitId>, Option<String>), LixError> {
let (checkpoint, file_id) = if bytes.len() == 16 {
(bytes, None)
} else {
if bytes.len() < 24 || &bytes[..4] != PACKED_CURRENT_BASE_FILE_SCOPE_MAGIC {
return Err(head_value_error(
"packed current-base manifest has invalid scope metadata",
));
}
let file_len = u32::from_be_bytes(
bytes[20..24]
.try_into()
.expect("packed file scope length is fixed width"),
) as usize;
if bytes.len() != 24 + file_len {
return Err(head_value_error(
"packed current-base manifest has a truncated file scope",
));
}
let file_id = std::str::from_utf8(&bytes[24..])
.map_err(|_| head_value_error("packed current-base file scope is not UTF-8"))?
.to_owned();
(&bytes[4..20], Some(file_id))
};
let checkpoint_uuid =
uuid::Uuid::from_slice(checkpoint).map_err(|error| head_value_error(error.to_string()))?;
Ok((
(!checkpoint_uuid.is_nil()).then(|| CommitId::new(checkpoint_uuid)),
file_id,
))
}
fn packed_base_matches_file_filter(
base_ref: &PackedCurrentBaseRef,
file_ids: &[NullableKeyFilter<String>],
) -> bool {
let Some(file_id) = base_ref.file_id.as_deref() else {
return true;
};
file_ids.is_empty()
|| file_ids.iter().any(|filter| match filter {
NullableKeyFilter::Any => true,
NullableKeyFilter::Null => false,
NullableKeyFilter::Value(requested) => requested == file_id,
})
}
struct PackedExclusiveSchemaBaseRef {
commit_id: CommitId,
index_key: Bytes,
}
#[derive(Clone, Debug)]
pub(crate) struct RowColumnarOverlayRow {
pub(crate) row_pk: RowPk,
pub(crate) snapshot_content: Option<Bytes>,
pub(crate) decoded_snapshot: Option<Arc<WasmTypedRow>>,
pub(crate) raw_snapshot: Option<Bytes>,
pub(crate) deleted: bool,
pub(crate) columnar_base_coordinate: Option<ColumnarBaseCoordinate>,
}
const ROW_COLUMNAR_OVERLAY_INPUT_ADMISSION_BYTES: usize = 128 * 1024 * 1024;
const ROW_COLUMNAR_OVERLAY_OUTPUT_ADMISSION_BYTES: usize = 128 * 1024 * 1024;
fn materialized_columnar_overlay_admission_bytes(
rows: &MaterializedHotStateBatch,
) -> Result<usize, LixError> {
rows.iter().try_fold(0_usize, |bytes, row| {
bytes
.checked_add(size_of::<MaterializedHotStateRow>())
.and_then(|bytes| bytes.checked_add(row.schema_key().len()))
.and_then(|bytes| bytes.checked_add(row.file_id().map_or(0, str::len)))
.and_then(|bytes| bytes.checked_add(row.branch_id().len()))
.and_then(|bytes| bytes.checked_add(row.row_pk().estimated_heap_bytes()))
.and_then(|bytes| {
bytes.checked_add(row.snapshot_content().map_or(0, |value| value.len()))
})
.and_then(|bytes| {
bytes.checked_add(row.decoded_snapshot().map_or(0, |typed| {
usize::try_from(typed.estimated_size()).unwrap_or(usize::MAX)
}))
})
.and_then(|bytes| bytes.checked_add(row.raw_snapshot().map_or(0, Bytes::len)))
.and_then(|bytes| bytes.checked_add(row.metadata().map_or(0, |value| value.len())))
.ok_or_else(|| head_value_error("row columnar overlay byte size overflow"))
})
}
fn packed_exclusive_schema_base_prefix(
branch_id: &str,
generation: CommitId,
schema_key: &str,
) -> Vec<u8> {
let mut prefix = hot_scope_prefix(branch_id, generation);
write_key_string(&mut prefix, schema_key, KEY_PART_MORE);
prefix
}
fn packed_exclusive_schema_base_key(
branch_id: &str,
generation: CommitId,
schema_key: &str,
commit_id: CommitId,
) -> Vec<u8> {
let mut key = packed_exclusive_schema_base_prefix(branch_id, generation, schema_key);
key.reserve(16);
key.extend_from_slice(commit_id.as_uuid().as_bytes());
key
}
async fn packed_exclusive_schema_base_refs(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
schema_key: &str,
) -> Result<Vec<PackedExclusiveSchemaBaseRef>, LixError> {
let prefix = packed_exclusive_schema_base_prefix(branch_id, generation, schema_key);
let range = StoragePrefix {
bytes: Bytes::copy_from_slice(&prefix),
}
.to_range()?;
let mut refs = Vec::new();
let mut cursor = store
.begin_scan(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
loop {
let (page, has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let bytes = entry.key.0.as_ref();
if bytes.len() != prefix.len() + 16 || bytes[..prefix.len()] != prefix {
return Err(head_value_error(
"packed exclusive-schema base index has an invalid key",
));
}
refs.push(PackedExclusiveSchemaBaseRef {
commit_id: CommitId::new(
uuid::Uuid::from_slice(&bytes[prefix.len()..])
.map_err(|error| head_value_error(error.to_string()))?,
),
index_key: entry.key.0,
});
}
if !has_more {
break;
}
}
Ok(refs)
}
fn stage_packed_exclusive_schema_base_ref(
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
schema_key: &str,
commit_id: CommitId,
) {
writes.put(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
StorageKey(Bytes::from(packed_exclusive_schema_base_key(
branch_id, generation, schema_key, commit_id,
))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
}
async fn packed_current_base_refs(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
) -> Result<Vec<PackedCurrentBaseRef>, LixError> {
let prefix = hot_scope_prefix(branch_id, generation);
let marker = PointReadPlan::new(
PACKED_CURRENT_BASE_CONTROL_SPACE,
&[StorageKey(Bytes::copy_from_slice(&prefix))],
)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.next()
.flatten();
if marker.is_none() {
return Ok(Vec::new());
}
let range = StoragePrefix {
bytes: Bytes::copy_from_slice(&prefix),
}
.to_range()?;
let mut refs = Vec::new();
let mut cursor = store
.begin_scan(
PACKED_CURRENT_BASE_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let bytes = entry.key.0.as_ref();
if bytes.len() != prefix.len() + 16 || bytes[..prefix.len()] != prefix {
return Err(head_value_error(
"packed current-base manifest has an invalid key",
));
}
let commit_id = CommitId::new(
uuid::Uuid::from_slice(&bytes[prefix.len()..])
.map_err(|error| head_value_error(error.to_string()))?,
);
let manifest_value = full_value_bytes(entry.value)?;
let (checkpoint_commit_id, file_id) =
decode_packed_current_base_value(&manifest_value)?;
refs.push(PackedCurrentBaseRef {
commit_id,
checkpoint_commit_id,
file_id,
coverage_key: entry.key.0,
});
}
if !page_has_more {
break;
}
}
Ok(refs)
}
async fn stage_retire_packed_current_bases(
store: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
) -> Result<(), LixError> {
let control_key = StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation)));
let control = PointReadPlan::new(
PACKED_CURRENT_BASE_CONTROL_SPACE,
std::slice::from_ref(&control_key),
)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.next()
.flatten();
if control.is_none() {
return Ok(());
}
for base_ref in packed_current_base_refs(store, branch_id, generation).await? {
writes.delete(PACKED_CURRENT_BASE_SPACE, StorageKey(base_ref.coverage_key));
}
let range = StoragePrefix {
bytes: Bytes::copy_from_slice(&control_key.0),
}
.to_range()?;
let mut cursor = store
.begin_scan(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
writes.delete(PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE, entry.key);
}
if !page_has_more {
break;
}
}
writes.delete(PACKED_CURRENT_BASE_CONTROL_SPACE, control_key);
Ok(())
}
async fn packed_current_base_has_schema(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
schema_key: &str,
) -> Result<bool, LixError> {
for base_ref in packed_current_base_refs(store, branch_id, generation).await? {
if crate::tracked_state::commit_delta_contains_schema(store, base_ref.commit_id, schema_key)
.await?
{
return Ok(true);
}
}
Ok(false)
}
fn packed_member_matches_filter(
member: &crate::tracked_state::CommitDeltaMember,
filter: &TrackedStateFilter,
) -> bool {
packed_identity_matches_filter(
&member.key.schema_key,
&member.key.row_pk,
member.key.file_id.as_deref(),
filter,
)
}
fn packed_identity_matches_filter(
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
filter: &TrackedStateFilter,
) -> bool {
(filter.schema_keys.is_empty()
|| filter
.schema_keys
.iter()
.any(|requested| requested == schema_key))
&& filter.matches_row_pk(row_pk)
&& (filter.file_ids.is_empty()
|| filter.file_ids.iter().any(|filter| match filter {
NullableKeyFilter::Any => true,
NullableKeyFilter::Null => file_id.is_none(),
NullableKeyFilter::Value(value) => file_id == Some(value.as_str()),
}))
}
fn packed_exact_keys_for_filter(filter: &TrackedStateFilter) -> Option<Vec<TrackedStateKey>> {
if filter.schema_keys.is_empty() || filter.row_pks.is_empty() {
return None;
}
if filter.file_ids.is_empty()
|| filter
.file_ids
.iter()
.any(|file_id| matches!(file_id, NullableKeyFilter::Any))
{
return None;
}
let file_ids = filter
.file_ids
.iter()
.map(|file_id| match file_id {
NullableKeyFilter::Null => None,
NullableKeyFilter::Value(file_id) => Some(file_id.clone()),
NullableKeyFilter::Any => unreachable!("Any returned above"),
})
.collect::<Vec<_>>();
let mut keys = Vec::with_capacity(
filter
.schema_keys
.len()
.saturating_mul(filter.row_pks.len())
.saturating_mul(file_ids.len()),
);
for schema_key in &filter.schema_keys {
for row_pk in &filter.row_pks {
if !crate::tracked_state::row_pk_satisfies_bounds(
row_pk,
filter.row_pk_lower.as_ref(),
filter.row_pk_upper.as_ref(),
) {
continue;
}
for file_id in &file_ids {
keys.push(TrackedStateKey {
schema_key: schema_key.clone(),
file_id: file_id.clone(),
row_pk: row_pk.clone(),
});
}
}
}
keys.sort_unstable();
keys.dedup();
Some(keys)
}
fn packed_current_base_working_diff_baseline(
active_checkpoint_commit_id: Option<CommitId>,
base_checkpoint_commit_id: Option<CommitId>,
) -> PackedWorkingDiffBaseline {
match active_checkpoint_commit_id {
Some(checkpoint_commit_id) if base_checkpoint_commit_id == Some(checkpoint_commit_id) => {
PackedWorkingDiffBaseline::AbsentAtCheckpoint {
checkpoint_commit_id,
}
}
Some(_) => PackedWorkingDiffBaseline::CleanAtCheckpoint,
None => PackedWorkingDiffBaseline::Disabled,
}
}
fn push_root_current_base_row(
rows: &mut MaterializedHotStateBatchBuilder,
row: crate::tracked_state::MaterializedTrackedStateRowRef<'_>,
branch_id: &str,
active_checkpoint_commit_id: Option<CommitId>,
) {
let ordinal = rows.push_materialized_ref(
row.row_pk(),
row.schema_key(),
row.file_id(),
row.snapshot_content().cloned(),
row.metadata().cloned(),
row.deleted(),
row.created_at(),
row.updated_at(),
branch_id == crate::GLOBAL_BRANCH_ID || row.schema_key() == "lix_account",
Some(row.change_id()),
Some(row.commit_id()),
false,
branch_id,
);
rows.set_decoded_snapshot(ordinal, row.decoded_snapshot().cloned());
if let Some(snapshot) = row.decoded_snapshot() {
rows.set_raw_snapshot(
ordinal,
Some(Bytes::from_owner(snapshot.durable_payload().expect(
"materialized root row retains its validated durable payload",
))),
);
}
rows.set_durable_predecessor(
ordinal,
CertifiedCurrentStatePredecessor::Packed(PackedHeadValue {
change_id: row.change_id(),
commit_id: row.commit_id(),
deleted: row.deleted(),
created_at: row.created_at(),
updated_at: row.updated_at(),
working_diff_baseline: match active_checkpoint_commit_id {
Some(_) => PackedWorkingDiffBaseline::CleanAtCheckpoint,
None => PackedWorkingDiffBaseline::Disabled,
},
columnar_base_coordinate: None,
}),
);
}
async fn scan_root_current_base_rows(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
request: &TrackedStateScanRequest,
root_base_cache: Option<&RootBaseBatchCache>,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
let Some(base_commit_id) = load_root_current_base_commit(store, branch_id, generation)
.await?
.or(fallback_base_commit_id)
else {
return Ok(MaterializedHotStateBatch::default());
};
let cache = root_base_cache.filter(|_| request.limit.is_none());
let tracked = match cache.and_then(|cache| cache.get(base_commit_id, request)) {
Some(cached) => {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_root_base_batch_cache_hit();
cached
}
None => {
#[cfg(feature = "storage-benches")]
if cache.is_some() {
crate::storage_bench::record_root_base_batch_cache_miss();
}
let context = cache
.map(|cache| cache.tracked_state.clone())
.unwrap_or_else(crate::tracked_state::TrackedStateContext::new);
let mut reader = context.reader(store);
let produced = Arc::new(
Box::pin(reader.scan_batch_at_commit(&base_commit_id.to_string(), request)).await?,
);
if let Some(cache) = cache {
cache.insert(base_commit_id, request.clone(), Arc::clone(&produced));
}
produced
}
};
let mut scopes = BTreeSet::<(String, Option<String>)>::new();
let mut previous_scope: Option<(String, Option<String>)> = None;
for row in tracked.iter() {
if row.schema_key() == crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY {
continue;
}
if previous_scope
.as_ref()
.is_some_and(|(schema_key, file_id)| {
schema_key == row.schema_key() && file_id.as_deref() == row.file_id()
})
{
continue;
}
let scope = (
row.schema_key().to_owned(),
row.file_id().map(str::to_owned),
);
scopes.insert((scope.0.clone(), None));
if scope.1.is_some() {
scopes.insert(scope.clone());
}
previous_scope = Some(scope);
}
let scope_refs = scopes
.iter()
.map(
|(schema_key, file_id)| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
)
.collect::<Vec<_>>();
let active_generations =
load_root_active_collection_generations(store, base_commit_id, scope_refs.iter().copied())
.await?;
let stored_control_values =
load_stored_hot_collection_controls(store, branch_id, generation, &scope_refs).await?;
let stored_controls = scopes
.iter()
.cloned()
.zip(stored_control_values)
.filter_map(|(scope, control)| control.map(|control| (scope, control)))
.collect::<BTreeMap<_, _>>();
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(tracked.len());
let mut scope_memo = RootScopeMemo::default();
for row in tracked.iter() {
if !root_tracked_row_is_active(
row,
generation,
&active_generations,
&stored_controls,
&mut scope_memo,
) {
continue;
}
push_root_current_base_row(&mut rows, row, branch_id, active_checkpoint_commit_id);
}
Ok(rows.finish())
}
async fn scan_root_current_base_rows_for_merge(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
request: &TrackedStateScanRequest,
other_candidate_count: usize,
root_base_cache: Option<&RootBaseBatchCache>,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
let Some(base_commit_id) = load_root_current_base_commit(store, branch_id, generation)
.await?
.or(fallback_base_commit_id)
else {
return Ok(MaterializedHotStateBatch::default());
};
let exact_scopes = (!request.filter.schema_keys.is_empty()
&& !request.filter.file_ids.is_empty()
&& request
.filter
.file_ids
.iter()
.all(|file_id| !matches!(file_id, NullableKeyFilter::Any)))
.then(|| {
request
.filter
.schema_keys
.iter()
.flat_map(|schema_key| {
std::iter::once(crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
})
.chain(request.filter.file_ids.iter().filter_map(|file_id| {
if let NullableKeyFilter::Value(file_id) = file_id {
Some(crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: Some(file_id),
})
} else {
None
}
}))
})
.collect::<Vec<_>>()
});
let has_local_collection_replacement = if let Some(scopes) = exact_scopes.as_deref() {
load_stored_hot_collection_controls(store, branch_id, generation, scopes)
.await?
.into_iter()
.flatten()
.any(|control| control.active_generation != generation)
} else {
let mut control_entries = Vec::new();
if request.filter.schema_keys.is_empty() {
let range = StoragePrefix {
bytes: Bytes::from(hot_scope_prefix(branch_id, generation)),
}
.to_range()?;
let mut cursor = store
.begin_scan(
COLLECTION_CONTROL_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
control_entries = cursor.collect_all().await?;
} else {
for schema_key in &request.filter.schema_keys {
let mut prefix = hot_scope_prefix(branch_id, generation);
write_key_string(&mut prefix, schema_key, KEY_PART_FINAL);
let range = StoragePrefix {
bytes: Bytes::from(prefix),
}
.to_range()?;
let mut cursor = store
.begin_scan(
COLLECTION_CONTROL_SPACE,
range,
StorageBeginScanOptions::default(),
)
.await?;
control_entries.extend(cursor.collect_all().await?);
}
}
control_entries
.into_iter()
.try_fold(false, |found, entry| -> Result<_, LixError> {
let value = full_value_bytes(entry.value)?;
let control: HotCollectionControl =
storage_codec::decode("hot collection control", &value)?;
Ok(found || control.active_generation != generation)
})?
};
let root_has_collection_replacement = if let Some(scopes) = exact_scopes {
!load_root_active_collection_generations(store, base_commit_id, scopes)
.await?
.is_empty()
} else {
let mut marker_reader = crate::tracked_state::TrackedStateContext::new().reader(store);
let root_collection_markers = Box::pin(marker_reader.scan_batch_at_commit(
&base_commit_id.to_string(),
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![
crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["change_id".to_owned()],
},
limit: Some(1),
},
))
.await?;
root_collection_markers.iter().next().is_some()
};
let mut root_request = request.clone();
if has_local_collection_replacement || root_has_collection_replacement {
root_request.limit = None;
} else if let Some(limit) = root_request.limit.as_mut() {
*limit = limit.saturating_add(other_candidate_count);
}
scan_root_current_base_rows(
store,
branch_id,
generation,
active_checkpoint_commit_id,
&root_request,
root_base_cache,
None,
)
.await
}
async fn load_root_current_base_exact(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
keys: &[TrackedStateKeyRef<'_>],
projection: ChangeRecordProjection,
cache: Option<&RootBaseBatchCache>,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateExactBatch, LixError> {
#[cfg(test)]
let profile_read = root_exact_profile::Read::new(store, keys.len());
#[cfg(test)]
let store = &profile_read;
let Some(base_commit_id) = load_root_current_base_commit(store, branch_id, generation)
.await?
.or(fallback_base_commit_id)
else {
return MaterializedHotStateExactBatch::new(
MaterializedHotStateBatch::default(),
vec![None; keys.len()],
);
};
let tracked = load_cached_root_exact(store, base_commit_id, keys, projection, cache).await?;
let scopes = keys
.iter()
.filter(|key| {
key.schema_key != crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
})
.flat_map(|key| {
[
Some((key.schema_key.to_owned(), None)),
key.file_id
.map(|file_id| (key.schema_key.to_owned(), Some(file_id.to_owned()))),
]
.into_iter()
.flatten()
})
.collect::<BTreeSet<_>>();
let scope_refs = scopes
.iter()
.map(
|(schema_key, file_id)| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
)
.collect::<Vec<_>>();
let active_generations = load_root_active_collection_generations_cached(
store,
base_commit_id,
scope_refs.iter().copied(),
cache,
)
.await?;
let stored_control_values =
load_stored_hot_collection_controls(store, branch_id, generation, &scope_refs).await?;
let stored_controls = scopes
.iter()
.cloned()
.zip(stored_control_values)
.filter_map(|(scope, control)| control.map(|control| (scope, control)))
.collect::<BTreeMap<_, _>>();
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(keys.len());
let mut slots = Vec::with_capacity(keys.len());
let mut scope_memo = RootScopeMemo::default();
for index in 0..tracked.len() {
slots.push(
tracked
.row(index)
.filter(|row| {
root_tracked_row_is_active(
*row,
generation,
&active_generations,
&stored_controls,
&mut scope_memo,
)
})
.map(|row| {
let ordinal = u32::try_from(rows.len())
.expect("root current-base exact result exceeds u32 rows");
push_root_current_base_row(
&mut rows,
row,
branch_id,
active_checkpoint_commit_id,
);
ordinal
}),
);
}
MaterializedHotStateExactBatch::new(rows.finish(), slots)
}
async fn load_cached_root_exact(
store: &(impl StorageAdapterRead + ?Sized),
base: CommitId,
keys: &[TrackedStateKeyRef<'_>],
projection: ChangeRecordProjection,
cache: Option<&RootBaseBatchCache>,
) -> Result<Arc<crate::tracked_state::MaterializedTrackedStateExactBatch>, LixError> {
if let Some(batch) = cache.and_then(|cache| cache.exact.get(base, keys, projection)) {
return Ok(batch);
}
let context = cache
.map(|cache| cache.tracked_state.clone())
.unwrap_or_else(crate::tracked_state::TrackedStateContext::new);
let mut reader = context.reader(store);
let batch = Arc::new(
Box::pin(reader.load_projected_batch_at_commit_refs(&base.to_string(), keys, &projection))
.await?,
);
if let Some(cache) = cache {
cache.exact.insert(base, keys, projection, batch.clone());
}
Ok(batch)
}
async fn load_root_active_collection_generations<'a>(
store: &(impl StorageAdapterRead + ?Sized),
base_commit_id: CommitId,
scopes: impl IntoIterator<Item = crate::collection_generation::CollectionScopeRef<'a>>,
) -> Result<BTreeMap<(String, Option<String>), RootCollectionGeneration>, LixError> {
load_root_active_collection_generations_cached(store, base_commit_id, scopes, None).await
}
async fn load_root_active_collection_generations_cached<'a>(
store: &(impl StorageAdapterRead + ?Sized),
base_commit_id: CommitId,
scopes: impl IntoIterator<Item = crate::collection_generation::CollectionScopeRef<'a>>,
cache: Option<&RootBaseBatchCache>,
) -> Result<BTreeMap<(String, Option<String>), RootCollectionGeneration>, LixError> {
let scopes = scopes
.into_iter()
.map(|scope| {
(
scope.schema_key.to_owned(),
scope.file_id.map(str::to_owned),
)
})
.collect::<BTreeSet<_>>();
if scopes.is_empty() {
return Ok(BTreeMap::new());
}
let marker_keys = scopes
.iter()
.map(|(schema_key, file_id)| TrackedStateKey {
schema_key: crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
file_id: None,
row_pk: RowPk::single(crate::collection_generation::collection_scope_key(
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
)),
})
.collect::<Vec<_>>();
let marker_refs = marker_keys
.iter()
.map(|key| TrackedStateKeyRef {
schema_key: &key.schema_key,
file_id: key.file_id.as_deref(),
row_pk: &key.row_pk,
})
.collect::<Vec<_>>();
let markers = load_cached_root_exact(
store,
base_commit_id,
&marker_refs,
ChangeRecordProjection::identity_only(),
cache,
)
.await?;
Ok(scopes
.into_iter()
.enumerate()
.filter_map(|(index, scope)| {
markers.row(index).map(|row| {
(
scope,
RootCollectionGeneration {
commit_id: row.commit_id(),
created_at: row.created_at(),
},
)
})
})
.collect())
}
#[derive(Clone, Copy)]
struct RootCollectionGeneration {
commit_id: CommitId,
created_at: LixTimestamp,
}
#[derive(Clone, Copy, Default)]
struct RootScopeVerdict {
disqualified: bool,
floor: Option<LixTimestamp>,
}
#[derive(Default)]
struct RootScopeMemo {
schema_key: String,
file_id: Option<String>,
primed: bool,
verdict: RootScopeVerdict,
}
impl RootScopeMemo {
fn verdict(
&mut self,
schema_key: &str,
file_id: Option<&str>,
branch_generation: CommitId,
active_generations: &BTreeMap<(String, Option<String>), RootCollectionGeneration>,
stored_controls: &BTreeMap<(String, Option<String>), HotCollectionControl>,
) -> RootScopeVerdict {
if self.primed && self.schema_key == schema_key && self.file_id.as_deref() == file_id {
return self.verdict;
}
let mut verdict = RootScopeVerdict::default();
for scope in [
Some((schema_key.to_owned(), None)),
file_id.map(|file_id| (schema_key.to_owned(), Some(file_id.to_owned()))),
]
.into_iter()
.flatten()
{
let root_generation = active_generations
.get(&scope)
.map_or(branch_generation, |generation| generation.commit_id);
if stored_controls
.get(&scope)
.is_some_and(|control| control.active_generation != root_generation)
{
verdict.disqualified = true;
}
if let Some(generation) = active_generations.get(&scope) {
verdict.floor = Some(match verdict.floor {
Some(floor) if floor >= generation.created_at => floor,
_ => generation.created_at,
});
}
}
self.schema_key.clear();
self.schema_key.push_str(schema_key);
match file_id {
Some(file_id) => {
let buffer = self.file_id.get_or_insert_with(String::new);
buffer.clear();
buffer.push_str(file_id);
}
None => self.file_id = None,
}
self.primed = true;
self.verdict = verdict;
verdict
}
}
fn root_tracked_row_is_active(
row: crate::tracked_state::MaterializedTrackedStateRowRef<'_>,
branch_generation: CommitId,
active_generations: &BTreeMap<(String, Option<String>), RootCollectionGeneration>,
stored_controls: &BTreeMap<(String, Option<String>), HotCollectionControl>,
memo: &mut RootScopeMemo,
) -> bool {
if row.schema_key() == crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY {
return true;
}
let verdict = memo.verdict(
row.schema_key(),
row.file_id(),
branch_generation,
active_generations,
stored_controls,
);
if verdict.disqualified {
return false;
}
verdict.floor.is_none_or(|floor| row.created_at() >= floor)
}
fn materialize_packed_slot(include: bool, slot: Option<lix_schema::Jsonb>) -> Option<SharedStr> {
if !include {
return None;
}
slot.map(|metadata| {
SharedStr::from(
metadata
.to_json_string()
.expect("validated native JSONB metadata must serialize"),
)
})
}
async fn scan_packed_current_base_rows(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
request: &TrackedStateScanRequest,
limit: Option<usize>,
) -> Result<MaterializedHotStateBatch, LixError> {
if matches!(limit, Some(0)) {
return Ok(MaterializedHotStateBatch::default());
}
let mut base_refs = packed_current_base_refs(store, branch_id, generation).await?;
base_refs
.retain(|base_ref| packed_base_matches_file_filter(base_ref, &request.filter.file_ids));
if base_refs.is_empty() {
return Ok(MaterializedHotStateBatch::default());
}
if request.read_columns.columns.as_slice() == ["commit_id"] {
return scan_packed_current_base_provenance_rows(
store, branch_id, base_refs, request, limit,
)
.await;
}
let single_base = base_refs.len() == 1;
let mut winners = BTreeMap::new();
let mut ordered_winners = None;
let payload_projection = ChangeRecordProjection::from_columns(&request.read_columns.columns);
for base_ref in base_refs {
let scan_members_with_payloads = request.filter.schema_keys.len() == 1
&& request.filter.row_pks.is_empty()
&& request.filter.file_ids.is_empty()
&& request.limit.is_none()
&& (payload_projection.snapshot_content
|| payload_projection.snapshot
|| payload_projection.raw_snapshot);
if scan_members_with_payloads {
let members =
crate::tracked_state::load_commit_delta_members_with_payloads_for_schemas(
store,
base_ref.commit_id,
&request.filter.schema_keys,
&[],
usize::MAX,
)
.await?;
if single_base {
if let Some(members) = members {
let mut ordered = Vec::with_capacity(members.len());
for member in members {
if member.value.deleted
|| !packed_member_matches_filter(&member, &request.filter)
{
continue;
}
ordered.push((member.key, member.value, member.change));
}
if ordered.iter().any(|row| row.0.file_id.is_some()) {
ordered.sort_unstable_by(|left, right| {
(&left.0.schema_key, &left.0.row_pk, &left.0.file_id).cmp(&(
&right.0.schema_key,
&right.0.row_pk,
&right.0.file_id,
))
});
}
ordered_winners = Some(ordered);
break;
}
} else if let Some(members) = members {
for member in members {
if member.value.deleted
|| !packed_member_matches_filter(&member, &request.filter)
{
continue;
}
let key = member.key;
let identity = (
key.schema_key.clone(),
key.row_pk.clone(),
key.file_id.clone(),
);
match winners.entry(identity) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert((key, member.value, member.change));
}
std::collections::btree_map::Entry::Occupied(mut entry)
if entry.get().1.commit_id < member.value.commit_id =>
{
entry.insert((key, member.value, member.change));
}
std::collections::btree_map::Entry::Occupied(_) => {}
}
}
continue;
}
}
let catalog_lookup =
!request.filter.schema_keys.is_empty() && !request.filter.row_pks.is_empty();
let keys = if catalog_lookup {
let mut keys = BTreeSet::new();
let mut next_owner = Some(base_ref.commit_id);
let mut visited = BTreeSet::new();
while let Some(owner) = next_owner {
if !visited.insert(owner) {
return Err(head_value_error(
"packed current-base selected-source cycle",
));
}
let manifest = crate::tracked_state::load_commit_state_manifest(store, owner)
.await?
.ok_or_else(|| {
head_value_error("packed current-base has no commit-state manifest")
})?;
if let Some(scope) = manifest.mutations.single_partition.as_ref() {
for row_pk in &request.filter.row_pks {
let key = TrackedStateKey {
schema_key: scope.schema_key.clone(),
file_id: scope.file_id.clone(),
row_pk: row_pk.clone(),
};
if packed_identity_matches_filter(
&key.schema_key,
&key.row_pk,
key.file_id.as_deref(),
&request.filter,
) {
keys.insert(key);
}
}
} else {
let mut reader = crate::tracked_state::TrackedStateContext::new().reader(store);
for schema_key in &request.filter.schema_keys {
for key in reader
.enumerate_schema_row_pk_keys_at_commit(
owner,
schema_key,
&request.filter.row_pks,
)
.await?
{
if packed_identity_matches_filter(
&key.schema_key,
&key.row_pk,
key.file_id.as_deref(),
&request.filter,
) {
keys.insert(key);
}
}
}
}
next_owner = manifest.mutations.selected_source_commit_id();
}
keys.into_iter().collect::<Vec<_>>()
} else {
let compact = crate::tracked_state::scan_commit_delta_values(
store,
base_ref.commit_id,
&request.filter.schema_keys,
)
.await?;
let mut keys = Vec::new();
for row in compact.iter() {
let key = row.key_ref();
if row.value().deleted
|| !packed_identity_matches_filter(
key.schema_key,
key.row_pk,
key.file_id,
&request.filter,
)
{
continue;
}
keys.push(TrackedStateKey {
schema_key: key.schema_key.to_owned(),
row_pk: key.row_pk.clone(),
file_id: key.file_id.map(str::to_owned),
});
if single_base && limit.is_some_and(|limit| keys.len() >= limit) {
break;
}
}
keys
};
let requests = keys
.iter()
.cloned()
.map(|key| (base_ref.commit_id, key))
.collect::<Vec<_>>();
let loaded =
crate::tracked_state::load_owned_commit_delta_entries(store, &requests).await?;
for (key, loaded_entry) in keys.into_iter().zip(loaded) {
let Some(loaded_entry) = loaded_entry else {
if catalog_lookup {
continue;
}
return Err(head_value_error(
"packed current-base manifest lost an indexed commit member",
));
};
if loaded_entry.value.deleted {
continue;
}
let identity = (
key.schema_key.clone(),
key.row_pk.clone(),
key.file_id.clone(),
);
match winners.entry(identity) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert((key, loaded_entry.value, loaded_entry.change_record));
}
std::collections::btree_map::Entry::Occupied(mut entry)
if entry.get().1.commit_id < loaded_entry.value.commit_id =>
{
entry.insert((key, loaded_entry.value, loaded_entry.change_record));
}
std::collections::btree_map::Entry::Occupied(_) => {}
}
}
}
let projection = ChangeRecordProjection::from_columns(&request.read_columns.columns);
let winner_rows = ordered_winners.unwrap_or_else(|| winners.into_values().collect::<Vec<_>>());
let row_capacity = limit.map_or(winner_rows.len(), |limit| limit.min(winner_rows.len()));
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(row_capacity);
let global = branch_id == crate::GLOBAL_BRANCH_ID;
for (key, value, mut change) in winner_rows.into_iter().take(row_capacity) {
let row_index = rows.len();
let decoded_snapshot = if projection.snapshot_content || projection.snapshot {
change
.snapshot
.as_deref()
.map(|payload| {
WasmTypedRow::decode_durable_payload(
Arc::from(payload),
&key.schema_key,
&key.row_pk,
)
})
.transpose()?
.map(Arc::new)
} else {
None
};
let raw_snapshot = if projection.raw_snapshot {
change.snapshot.take().map(Bytes::from)
} else {
None
};
let snapshot = if projection.snapshot_content {
decoded_snapshot
.as_deref()
.map(WasmTypedRow::to_json_shared)
.transpose()?
} else {
None
};
let metadata = materialize_packed_slot(projection.metadata, change.metadata);
rows.push_materialized(
key.row_pk,
key.schema_key,
key.file_id,
snapshot,
metadata,
false,
value.created_at,
value.updated_at,
global,
Some(value.change_id),
Some(value.commit_id),
false,
branch_id,
);
if decoded_snapshot.is_some() {
rows.set_decoded_snapshot(row_index, decoded_snapshot);
}
if raw_snapshot.is_some() {
rows.set_raw_snapshot(row_index, raw_snapshot);
}
}
Ok(rows.finish())
}
async fn scan_packed_current_base_provenance_rows(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
base_refs: Vec<PackedCurrentBaseRef>,
request: &TrackedStateScanRequest,
limit: Option<usize>,
) -> Result<MaterializedHotStateBatch, LixError> {
let mut winners = BTreeMap::new();
for base_ref in base_refs {
let compact = crate::tracked_state::scan_commit_delta_values(
store,
base_ref.commit_id,
&request.filter.schema_keys,
)
.await?;
for row in compact.iter() {
let key = row.key_ref();
let value = row.value();
if value.deleted
|| !packed_identity_matches_filter(
key.schema_key,
key.row_pk,
key.file_id,
&request.filter,
)
{
continue;
}
let identity = (
key.schema_key.to_owned(),
key.row_pk.clone(),
key.file_id.map(str::to_owned),
);
match winners.entry(identity) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(value.clone());
}
std::collections::btree_map::Entry::Occupied(mut entry)
if entry.get().commit_id < value.commit_id =>
{
entry.insert(value.clone());
}
std::collections::btree_map::Entry::Occupied(_) => {}
}
}
}
let row_capacity = limit.map_or(winners.len(), |limit| limit.min(winners.len()));
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(row_capacity);
let global = branch_id == crate::GLOBAL_BRANCH_ID;
for ((schema_key, row_pk, file_id), value) in winners.into_iter().take(row_capacity) {
rows.push_materialized(
row_pk,
schema_key,
file_id,
None,
None,
false,
value.created_at,
value.updated_at,
global,
Some(value.change_id),
Some(value.commit_id),
false,
branch_id,
);
}
Ok(rows.finish())
}
fn packed_current_base_shadow_from_rows(
keys: &[TrackedStateKeyRef<'_>],
rows: &MaterializedHotStateBatch,
) -> Vec<Option<CommitId>> {
let mut resolved = BTreeMap::<(&str, Option<&str>, &RowPk), Option<CommitId>>::new();
for row in rows.iter() {
let commit_id = row.commit_id();
let slot = resolved
.entry((row.schema_key(), row.file_id(), row.row_pk()))
.or_insert(commit_id);
*slot = match (*slot, commit_id) {
(Some(current), Some(candidate)) => Some(current.max(candidate)),
_ => None,
};
}
keys.iter()
.map(|key| {
resolved
.get(&(key.schema_key, key.file_id, key.row_pk))
.copied()
.flatten()
})
.collect()
}
async fn load_packed_current_base_exact(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
keys: &[TrackedStateKeyRef<'_>],
shadow: PackedCurrentBaseShadow<'_>,
projection: ChangeRecordProjection,
transaction_cache: Option<&HotStateTransactionCache>,
) -> Result<MaterializedHotStateExactBatch, LixError> {
if keys.is_empty() {
return MaterializedHotStateExactBatch::new(
MaterializedHotStateBatch::default(),
Vec::new(),
);
}
let winners = load_packed_current_base_exact_entries(
store,
branch_id,
generation,
keys,
shadow,
transaction_cache,
)
.await?;
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(keys.len());
let mut slots = Vec::with_capacity(keys.len());
let global = branch_id == crate::GLOBAL_BRANCH_ID;
for (key, entry) in keys.iter().zip(winners) {
let Some((value, mut change_record, base_coordinate, base_checkpoint_commit_id)) = entry
else {
slots.push(None);
continue;
};
if value.deleted {
slots.push(None);
continue;
}
let row_index = rows.len();
let decoded_snapshot =
if projection.snapshot_content || projection.snapshot || projection.raw_snapshot {
change_record
.snapshot
.as_deref()
.map(|payload| {
WasmTypedRow::decode_durable_payload(
Arc::from(payload),
key.schema_key,
key.row_pk,
)
})
.transpose()?
.map(Arc::new)
} else {
None
};
let columnar_base_coordinate = base_coordinate.map(|coordinate| ColumnarBaseCoordinate {
base_commit_id: coordinate.base_commit_id,
group_index: coordinate.group_index,
row_index: coordinate.row_index,
});
let durable_predecessor = CertifiedCurrentStatePredecessor::Packed(PackedHeadValue {
change_id: value.change_id,
commit_id: value.commit_id,
deleted: false,
created_at: value.created_at,
updated_at: value.updated_at,
working_diff_baseline: packed_current_base_working_diff_baseline(
active_checkpoint_commit_id,
base_checkpoint_commit_id,
),
columnar_base_coordinate,
});
let snapshot = if projection.snapshot_content {
decoded_snapshot
.as_deref()
.map(WasmTypedRow::to_json_shared)
.transpose()?
} else {
None
};
let raw_snapshot = if projection.raw_snapshot {
change_record.snapshot.take().map(Bytes::from)
} else {
None
};
let metadata = materialize_packed_slot(projection.metadata, change_record.metadata);
slots.push(Some(u32::try_from(row_index).map_err(|_| {
head_value_error("packed exact row count exceeds u32")
})?));
rows.push_materialized(
change_record.row_pk,
change_record.schema_key,
change_record.file_id,
snapshot,
metadata,
false,
value.created_at,
value.updated_at,
global,
Some(value.change_id),
Some(value.commit_id),
false,
branch_id,
);
if decoded_snapshot.is_some() {
rows.set_decoded_snapshot(row_index, decoded_snapshot);
}
if raw_snapshot.is_some() {
rows.set_raw_snapshot(row_index, raw_snapshot);
}
rows.set_durable_predecessor(row_index, durable_predecessor);
if let Some(coordinate) = columnar_base_coordinate {
rows.set_columnar_base_coordinate(row_index, coordinate);
}
}
MaterializedHotStateExactBatch::new(rows.finish(), slots)
}
type PackedCurrentBaseShadow<'a> = &'a [Option<CommitId>];
async fn load_packed_current_base_exact_entries(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
keys: &[TrackedStateKeyRef<'_>],
shadow: PackedCurrentBaseShadow<'_>,
transaction_cache: Option<&HotStateTransactionCache>,
) -> Result<
Vec<
Option<(
crate::tracked_state::TrackedStateIndexValue,
crate::changelog::ChangeRecord,
Option<crate::tracked_state::TrackedStateBaseCoordinate>,
Option<CommitId>,
)>,
>,
LixError,
> {
if keys.is_empty() {
return Ok(Vec::new());
}
debug_assert!(shadow.is_empty() || shadow.len() == keys.len());
let transaction_cache = match transaction_cache {
Some(cache) if cache.should_reuse_packed_points(generation)? => Some(cache),
Some(_) | None => None,
};
let mut base_refs = match transaction_cache
.map(|cache| cache.packed_current_base_refs(branch_id, generation))
.transpose()?
.flatten()
{
Some(refs) => refs,
None => {
let refs = packed_current_base_refs(store, branch_id, generation).await?;
if let Some(cache) = transaction_cache {
cache.remember_packed_current_base_refs(branch_id, generation, &refs)?;
}
refs
}
};
base_refs.retain(|base_ref| {
base_ref
.file_id
.as_deref()
.is_none_or(|file_id| keys.iter().any(|key| key.file_id == Some(file_id)))
});
if base_refs.is_empty() {
return Ok((0..keys.len()).map(|_| None).collect());
}
let skipped = packed_current_base_unresolved_indices(keys, shadow, &base_refs);
let unresolved_keys;
let selected = match skipped.as_deref() {
Some(indices) => {
if indices.is_empty() {
return Ok((0..keys.len()).map(|_| None).collect());
}
unresolved_keys = indices.iter().map(|&index| keys[index]).collect::<Vec<_>>();
unresolved_keys.as_slice()
}
None => keys,
};
let loaded = Box::pin(load_packed_current_base_exact_entries_from_refs(
store,
&base_refs,
selected,
transaction_cache,
))
.await?;
let Some(indices) = skipped else {
return Ok(loaded);
};
let mut output = (0..keys.len()).map(|_| None).collect::<Vec<_>>();
for (index, entry) in indices.into_iter().zip(loaded) {
output[index] = entry;
}
Ok(output)
}
fn packed_current_base_unresolved_indices(
keys: &[TrackedStateKeyRef<'_>],
shadow: PackedCurrentBaseShadow<'_>,
base_refs: &[PackedCurrentBaseRef],
) -> Option<Vec<usize>> {
if shadow.len() != keys.len() {
return None;
}
let newest_base = base_refs.iter().map(|base_ref| base_ref.commit_id).max()?;
let unresolved = (0..keys.len())
.filter(|&index| shadow[index].is_none_or(|resolved| resolved < newest_base))
.collect::<Vec<_>>();
(unresolved.len() != keys.len()).then_some(unresolved)
}
async fn load_packed_current_base_exact_entries_from_refs(
store: &(impl StorageAdapterRead + ?Sized),
base_refs: &[PackedCurrentBaseRef],
keys: &[TrackedStateKeyRef<'_>],
transaction_cache: Option<&HotStateTransactionCache>,
) -> Result<
Vec<
Option<(
crate::tracked_state::TrackedStateIndexValue,
crate::changelog::ChangeRecord,
Option<crate::tracked_state::TrackedStateBaseCoordinate>,
Option<CommitId>,
)>,
>,
LixError,
> {
if let [base_ref] = base_refs {
return Ok(
crate::tracked_state::load_owned_commit_delta_entries_one_ordered_ref(
store,
base_ref.commit_id,
keys,
transaction_cache.map(|cache| &cache.commit_delta_points),
)
.await?
.into_iter()
.map(|entry| {
entry.map(|entry| {
(
entry.value,
entry.change_record,
entry.base_coordinate,
base_ref.checkpoint_commit_id,
)
})
})
.collect(),
);
}
let owned_keys = keys
.iter()
.map(|key| TrackedStateKey {
schema_key: key.schema_key.to_owned(),
file_id: key.file_id.map(str::to_owned),
row_pk: key.row_pk.clone(),
})
.collect::<Vec<_>>();
let mut requests = Vec::with_capacity(base_refs.len().saturating_mul(keys.len()));
for base_ref in base_refs.iter() {
requests.extend(
owned_keys
.iter()
.cloned()
.map(|key| (base_ref.commit_id, key)),
);
}
let loaded = crate::tracked_state::load_owned_commit_delta_entries(store, &requests).await?;
let mut winners = (0..keys.len()).map(|_| None).collect::<Vec<
Option<(
crate::tracked_state::TrackedStateIndexValue,
crate::changelog::ChangeRecord,
Option<crate::tracked_state::TrackedStateBaseCoordinate>,
Option<CommitId>,
)>,
>>();
for (base_ref, entries) in base_refs.iter().zip(loaded.chunks(keys.len())) {
for (slot, entry) in winners.iter_mut().zip(entries) {
let Some(entry) = entry else {
continue;
};
if slot
.as_ref()
.is_none_or(|(previous, _, _, _)| previous.commit_id < entry.value.commit_id)
{
*slot = Some((
entry.value.clone(),
entry.change_record.clone(),
entry.base_coordinate,
base_ref.checkpoint_commit_id,
));
}
}
}
Ok(winners)
}
fn compare_materialized_live_identities(
left: &MaterializedHotStateRow,
right: &MaterializedHotStateRow,
) -> Ordering {
left.schema_key
.cmp(&right.schema_key)
.then_with(|| left.row_pk.cmp(&right.row_pk))
.then_with(|| left.file_id.cmp(&right.file_id))
}
fn merge_ordered_live_rows(
left: Vec<MaterializedHotStateRow>,
right: Vec<MaterializedHotStateRow>,
) -> Vec<MaterializedHotStateRow> {
let mut left = VecDeque::from(left);
let mut right = VecDeque::from(right);
let mut merged = Vec::with_capacity(left.len().saturating_add(right.len()));
while let (Some(left_row), Some(right_row)) = (left.front(), right.front()) {
match compare_materialized_live_identities(left_row, right_row) {
Ordering::Less => {
merged.push(left.pop_front().expect("peeked left row exists"));
}
Ordering::Greater => {
merged.push(right.pop_front().expect("peeked right row exists"));
}
Ordering::Equal => {
let left_row = left.pop_front().expect("peeked left row exists");
let right_row = right.pop_front().expect("peeked right row exists");
if left_row.commit_id < right_row.commit_id {
merged.push(right_row);
} else {
merged.push(left_row);
}
}
}
}
merged.extend(left);
merged.extend(right);
merged
}
fn compare_materialized_live_identity_refs(
left: MaterializedHotStateRowRef<'_>,
right: MaterializedHotStateRowRef<'_>,
) -> Ordering {
left.schema_key()
.cmp(right.schema_key())
.then_with(|| left.row_pk().cmp(right.row_pk()))
.then_with(|| left.file_id().cmp(&right.file_id()))
}
fn merge_ordered_live_batches(
left: MaterializedHotStateBatch,
right: MaterializedHotStateBatch,
) -> MaterializedHotStateBatch {
if left.is_empty() {
return right;
}
if right.is_empty() {
return left;
}
let mut merged =
MaterializedHotStateBatchBuilder::with_capacity(left.len().saturating_add(right.len()));
let mut left_index = 0usize;
let mut right_index = 0usize;
while left_index < left.len() && right_index < right.len() {
let left_row = left.row(left_index);
let right_row = right.row(right_index);
match compare_materialized_live_identity_refs(left_row, right_row) {
Ordering::Less => {
merged.push_ref(left_row, None);
left_index += 1;
}
Ordering::Greater => {
merged.push_ref(right_row, None);
right_index += 1;
}
Ordering::Equal => {
if left_row.commit_id() < right_row.commit_id() {
merged.push_ref(right_row, None);
} else {
merged.push_ref(left_row, None);
}
left_index += 1;
right_index += 1;
}
}
}
while left_index < left.len() {
merged.push_ref(left.row(left_index), None);
left_index += 1;
}
while right_index < right.len() {
merged.push_ref(right.row(right_index), None);
right_index += 1;
}
merged.finish()
}
fn exclude_ordered_live_batch_identities(
rows: MaterializedHotStateBatch,
authority: &MaterializedHotStateBatch,
) -> MaterializedHotStateBatch {
if rows.is_empty() || authority.is_empty() {
return rows;
}
debug_assert!((1..rows.len()).all(|index| {
compare_materialized_live_identity_refs(rows.row(index - 1), rows.row(index)).is_lt()
}));
debug_assert!((1..authority.len()).all(|index| {
compare_materialized_live_identity_refs(authority.row(index - 1), authority.row(index))
.is_lt()
}));
let mut authority_index = 0usize;
rows.filter(
|row| loop {
let Some(authority_row) = authority.get(authority_index) else {
return true;
};
match compare_materialized_live_identity_refs(authority_row, row) {
Ordering::Less => authority_index += 1,
Ordering::Equal => return false,
Ordering::Greater => return true,
}
},
None,
)
}
pub(crate) struct RootBaseBatchCache {
tracked_state: crate::tracked_state::TrackedStateContext,
exact: root_exact_cache::Cache,
entries: std::sync::Mutex<RootBaseBatchCacheEntries>,
}
#[derive(Default)]
struct RootBaseBatchCacheEntries {
resident: Vec<RootBaseBatchCacheEntry>,
rows: usize,
}
struct RootBaseBatchCacheEntry {
base_commit_id: CommitId,
request: TrackedStateScanRequest,
batch: Arc<crate::tracked_state::MaterializedTrackedStateBatch>,
}
const ROOT_BASE_BATCH_CACHE_MAX_ENTRIES: usize = 16;
const ROOT_BASE_BATCH_CACHE_MAX_ROWS: usize = 250_000;
impl Default for RootBaseBatchCache {
fn default() -> Self {
Self::with_tracked_state(crate::tracked_state::TrackedStateContext::new())
}
}
impl RootBaseBatchCache {
pub(crate) fn with_tracked_state(
tracked_state: crate::tracked_state::TrackedStateContext,
) -> Self {
Self {
tracked_state,
exact: root_exact_cache::Cache::default(),
entries: Default::default(),
}
}
fn entries(&self) -> std::sync::MutexGuard<'_, RootBaseBatchCacheEntries> {
self.entries
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn get(
&self,
base_commit_id: CommitId,
request: &TrackedStateScanRequest,
) -> Option<Arc<crate::tracked_state::MaterializedTrackedStateBatch>> {
let mut entries = self.entries();
let index = entries.resident.iter().position(|entry| {
entry.base_commit_id == base_commit_id && &entry.request == request
})?;
let entry = entries.resident.remove(index);
let batch = Arc::clone(&entry.batch);
entries.resident.insert(0, entry);
Some(batch)
}
fn insert(
&self,
base_commit_id: CommitId,
request: TrackedStateScanRequest,
batch: Arc<crate::tracked_state::MaterializedTrackedStateBatch>,
) {
let rows = batch.len();
if rows > ROOT_BASE_BATCH_CACHE_MAX_ROWS {
return;
}
let mut entries = self.entries();
if let Some(index) = entries
.resident
.iter()
.position(|entry| entry.base_commit_id == base_commit_id && entry.request == request)
{
let previous = entries.resident.remove(index);
entries.rows = entries.rows.saturating_sub(previous.batch.len());
}
entries.resident.insert(
0,
RootBaseBatchCacheEntry {
base_commit_id,
request,
batch,
},
);
entries.rows = entries.rows.saturating_add(rows);
while entries.resident.len() > ROOT_BASE_BATCH_CACHE_MAX_ENTRIES
|| entries.rows > ROOT_BASE_BATCH_CACHE_MAX_ROWS
{
let Some(evicted) = entries.resident.pop() else {
break;
};
entries.rows = entries.rows.saturating_sub(evicted.batch.len());
}
}
}
pub(crate) struct HotStateStoreReader<S> {
pub(super) store: S,
pub(super) transaction_cache: Option<Arc<HotStateTransactionCache>>,
pub(super) root_base_cache: Option<Arc<RootBaseBatchCache>>,
}
impl<S> HotStateStoreReader<S> {
pub(crate) fn with_root_base_cache(mut self, cache: Arc<RootBaseBatchCache>) -> Self {
self.root_base_cache = Some(cache);
self
}
}
impl<S> HotStateStoreReader<S>
where
S: StorageAdapterRead,
{
pub(crate) async fn prepare_packed_identity_membership(
&self,
branch_id: &str,
generation: CommitId,
schema_key: &str,
) -> Result<Option<PackedIdentityMembership>, LixError> {
let Some(cache) = self.transaction_cache.as_ref() else {
return Ok(None);
};
let base_refs =
packed_exclusive_schema_base_refs(&self.store, branch_id, generation, schema_key)
.await?;
let [base_ref] = base_refs.as_slice() else {
return Ok(None);
};
let collection = self
.collection_control(
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?;
let Some(ordered_identity_digest) = collection.ordered_identity_digest else {
return Ok(None);
};
if collection.active_generation != generation
|| collection.live_count == DEFERRED_ROOT_LIVE_COUNT
{
return Ok(None);
}
let filter = TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
file_ids: vec![NullableKeyFilter::Null],
..TrackedStateFilter::default()
};
let Some(hot) =
hot_scan_entries(&self.store, branch_id, generation, &filter, Some(1), None).await?
else {
return Ok(None);
};
let has_hot_rows = match hot {
HotScanEntries::Decoded(rows) => !rows.is_empty(),
HotScanEntries::Finite(batches) => batches
.iter()
.flat_map(|batch| batch.values.iter())
.any(Option::is_some),
};
if has_hot_rows {
return Ok(None);
}
let cursor = cache
.commit_delta_points
.live_membership_cursor(base_ref.commit_id);
Ok(Some(PackedIdentityMembership {
cache: Arc::clone(cache),
cursor,
schema_key: schema_key.to_owned(),
live_count: collection.live_count,
ordered_identity_digest,
encoded_key: Vec::new(),
}))
}
async fn collection_control(
&self,
branch_id: &str,
generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<HotCollectionControl, LixError> {
let key = HotCollectionCacheKey {
branch_id: branch_id.to_owned(),
generation,
schema_key: scope.schema_key.to_owned(),
file_id: scope.file_id.map(str::to_owned),
};
if let Some(cache) = self.transaction_cache.as_deref()
&& let Some(control) = cache.collection_control(&key)?
{
return Ok(control);
}
let control =
load_hot_collection_control(&self.store, branch_id, generation, scope).await?;
if let Some(cache) = self.transaction_cache.as_deref() {
cache.remember_collection_control(key, control)?;
}
Ok(control)
}
pub(crate) async fn collection_generation(
&self,
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<crate::collection_generation::CollectionGeneration, LixError> {
let control = self
.collection_control(branch_id, branch_generation, scope)
.await?;
Ok(crate::collection_generation::CollectionGeneration {
active_generation: control.active_generation,
live_count: control.live_count,
ordered_identity_digest: control.ordered_identity_digest,
})
}
pub(crate) async fn exact_collection_live_count(
&self,
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
) -> Result<u64, LixError> {
let rows = Box::pin(self.scan_live_batch_for_generation(
branch_id,
branch_generation,
None,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![scope.schema_key.to_owned()],
file_ids: scope.file_id.map_or_else(Vec::new, |file_id| {
vec![NullableKeyFilter::Value(file_id.to_owned())]
}),
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["change_id".to_owned()],
},
limit: None,
},
))
.await?;
u64::try_from(rows.len())
.map_err(|_| head_value_error("hot collection live count exceeds u64"))
}
pub(crate) async fn validate_deterministic_setting_absence(
&self,
branch_generation: CommitId,
required_identity: TrackedStateKeyRef<'_>,
allow_bootstrap_absence: bool,
) -> Result<(), LixError> {
let bit = deterministic_identity_bit(required_identity.row_pk)
.filter(|_| {
required_identity.schema_key == EXACT_CLOSURE_SCHEMA_KEY
&& required_identity.file_id.is_none()
})
.ok_or_else(|| head_value_error("unsupported deterministic setting identity"))?;
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let control = load_stored_hot_collection_control(
&self.store,
crate::GLOBAL_BRANCH_ID,
branch_generation,
scope,
)
.await?;
let Some(control) = control else {
let key = StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
branch_generation,
scope,
)));
let witnesses = PointReadPlan::new(DETERMINISTIC_IDENTITY_WITNESS_SPACE, &[key])
.materialize(&self.store, StorageGetOptions::default())
.await?
.value;
if let Some(Some(StorageProjectedValue::FullValue(bytes))) = witnesses.first() {
let witness: DeterministicIdentityWitness =
storage_codec::decode("deterministic identity witness", bytes)?;
if !witness.collection_control.is_empty() || witness.presence != 0 {
return Err(head_value_error(
"deterministic setting collection control is missing",
));
}
let root_key = StorageKey(Bytes::from(hot_scope_prefix(
crate::GLOBAL_BRANCH_ID,
branch_generation,
)));
let roots = PointReadPlan::new(ROOT_CURRENT_BASE_SPACE, &[root_key])
.materialize(&self.store, StorageGetOptions::default())
.await?
.value;
if let Some(Some(StorageProjectedValue::FullValue(root))) = roots.first()
&& root.len() == 16
{
return Ok(());
}
return Err(head_value_error(
"empty local deterministic witness lacks its native root",
));
}
return if allow_bootstrap_absence {
Ok(())
} else {
Err(head_value_error(
"deterministic setting collection control is missing",
))
};
};
if control.active_generation != branch_generation
|| control.live_count == DEFERRED_ROOT_LIVE_COUNT
|| control.ordered_identity_digest.is_none()
{
return Err(head_value_error(
"deterministic setting collection closure is invalid",
));
}
let key = StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
branch_generation,
scope,
)));
let witness = PointReadPlan::new(DETERMINISTIC_IDENTITY_WITNESS_SPACE, &[key])
.materialize(&self.store, StorageGetOptions::default())
.await?
.value;
let Some(Some(StorageProjectedValue::FullValue(bytes))) = witness.first() else {
return Err(head_value_error(
"deterministic setting identity witness is missing",
));
};
let witness: DeterministicIdentityWitness =
storage_codec::decode("deterministic identity witness", bytes)?;
if witness.collection_control != storage_codec::encode("hot collection control", &control)?
|| witness.presence & !3 != 0
{
return Err(head_value_error(
"deterministic setting identity witness is invalid or stale",
));
}
let presence = witness.presence;
if presence & bit != 0 {
return Err(head_value_error(
"required point miss omitted a collection authority identity",
));
}
Ok(())
}
pub(crate) async fn validate_exact_collection_closure(
&self,
branch_id: &str,
branch_generation: CommitId,
scope: crate::collection_generation::CollectionScopeRef<'_>,
required_identity: TrackedStateKeyRef<'_>,
expected_domain: HotStateReadDomain,
allow_bootstrap_absence: bool,
) -> Result<(), LixError> {
let expected_untracked = match expected_domain {
HotStateReadDomain::Tracked => false,
HotStateReadDomain::Untracked => true,
HotStateReadDomain::Combined => {
return Err(head_value_error(
"exact collection closure requires one explicit state domain",
));
}
};
let control =
load_stored_hot_collection_control(&self.store, branch_id, branch_generation, scope)
.await?;
if let Some(control) = control {
if control.active_generation != branch_generation {
return Err(head_value_error(format!(
"selected collection '{}' control names stale generation {} instead of {branch_generation}",
scope.schema_key, control.active_generation
)));
}
if control.live_count == DEFERRED_ROOT_LIVE_COUNT {
return Err(head_value_error(format!(
"selected collection '{}' has no exact member count",
scope.schema_key
)));
}
if control.ordered_identity_digest.is_none() {
return Err(head_value_error(format!(
"selected collection '{}' has no exact identity digest",
scope.schema_key
)));
}
}
let scope_prefix = hot_scope_prefix(branch_id, branch_generation);
let mut selected_prefix = scope_prefix.clone();
write_key_string(&mut selected_prefix, scope.schema_key, KEY_PART_FINAL);
if let Some(file_id) = scope.file_id {
write_file_id(&mut selected_prefix, Some(file_id));
}
let range = StoragePrefix {
bytes: Bytes::from(selected_prefix),
}
.to_range()?;
let mut digest = CompleteHotCollectionDigest::new(branch_id, branch_generation, scope);
let mut actual = 0_u64;
let mut cursor = self
.store
.begin_scan(ROW_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let raw_key = entry.key.0;
let raw_value = full_value_bytes(entry.value)?;
let identity = validate_exact_collection_member(
branch_id,
branch_generation,
&scope_prefix,
scope,
required_identity,
expected_untracked,
raw_key.as_ref(),
raw_value.as_ref(),
)?;
if let Some(identity) = identity {
digest.push(&identity, raw_key.as_ref())?;
actual = actual
.checked_add(1)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?;
}
}
if !page_has_more {
break;
}
}
let actual_digest = digest.finish();
let Some(control) = control else {
if allow_bootstrap_absence && actual == 0 {
return Ok(());
}
return Err(head_value_error(format!(
"selected collection '{}' is missing its exact control",
scope.schema_key
)));
};
if actual != control.live_count {
return Err(head_value_error(format!(
"selected collection '{}' declares {} live members but materializes {actual}",
scope.schema_key, control.live_count
)));
}
if control.ordered_identity_digest != Some(actual_digest) {
return Err(head_value_error(format!(
"selected collection '{}' identity digest does not match its canonical members",
scope.schema_key
)));
}
Ok(())
}
pub(crate) async fn scan_live_batch_for_retention(
&self,
branch_id: &str,
control: BranchHeadControl,
request: &TrackedStateScanRequest,
requested_untracked: Option<bool>,
) -> Result<MaterializedHotStateBatch, LixError> {
self.scan_live_batch_for_retention_with_fallback(
branch_id,
control,
request,
requested_untracked,
None,
)
.await
}
pub(crate) async fn scan_live_batch_for_retention_with_fallback(
&self,
branch_id: &str,
control: BranchHeadControl,
request: &TrackedStateScanRequest,
requested_untracked: Option<bool>,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
let rows = self
.scan_live_batch_for_generation_with_fallback(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
request,
fallback_base_commit_id,
)
.await?;
Ok(match requested_untracked {
None => rows,
Some(untracked) => rows.filter(|row| row.untracked() == untracked, None),
})
}
pub(crate) async fn scan_live_batches_for_controls(
&self,
controls: &[(String, BranchHeadControl)],
request: &TrackedStateScanRequest,
requested_untracked: Option<bool>,
) -> Result<Vec<(String, MaterializedHotStateBatch)>, LixError> {
self.scan_live_batches_for_controls_with_fallback(
controls,
request,
requested_untracked,
false,
)
.await
}
pub(crate) async fn scan_live_batches_for_controls_with_fallback(
&self,
controls: &[(String, BranchHeadControl)],
request: &TrackedStateScanRequest,
requested_untracked: Option<bool>,
fallback_to_head: bool,
) -> Result<Vec<(String, MaterializedHotStateBatch)>, LixError> {
let mut rows = Vec::with_capacity(controls.len());
for (branch_id, control) in controls {
let branch_rows = self
.scan_live_batch_for_retention_with_fallback(
branch_id,
*control,
request,
requested_untracked,
fallback_to_head.then_some(control.head_commit_id),
)
.await?;
rows.push((branch_id.clone(), branch_rows));
}
Ok(rows)
}
pub(crate) async fn tracked_serving_commit_dependencies(
&self,
projections: &[(String, BranchHeadTrackedReachability)],
) -> Result<BTreeSet<CommitId>, LixError> {
let request = TrackedStateScanRequest {
filter: TrackedStateFilter::default(),
read_columns: TrackedStateReadColumns {
columns: vec!["commit_id".to_owned()],
},
limit: None,
};
let mut dependencies = BTreeSet::new();
for (branch_id, projection) in projections {
let batch = self
.scan_live_batch_for_generation(
branch_id,
projection.serving_generation,
projection.serving_checkpoint_commit_id,
&request,
)
.await?;
for row in batch.iter() {
if row.untracked() {
continue;
}
let commit_id = row.commit_id().ok_or_else(|| {
head_value_error(
"authenticated tracked current-state row has no semantic commit owner",
)
})?;
dependencies.insert(commit_id);
}
}
Ok(dependencies)
}
pub(crate) async fn scan_hot_index_identity_candidates(
&self,
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
values: &[HotIndexValue],
) -> Result<Option<Vec<(RowPk, Option<String>)>>, LixError> {
if values.is_empty() {
return Ok(None);
}
let witness = StorageKey(Bytes::from(encode_hot_index_witness_key(
branch_id, generation, schema_key, ordinal,
)));
let present = PointReadPlan::new(INDEX_SPACE, &[witness])
.materialize(&self.store, StorageGetOptions::default())
.await?;
let Some(StorageProjectedValue::FullValue(witness_value)) =
present.value.into_iter().next().flatten()
else {
return Ok(None);
};
let Some(entries_published) = decode_hot_index_witness(&witness_value) else {
return Ok(None);
};
if values.len() > hot_index_seek_budget(entries_published) {
return Ok(None);
}
let budget = hot_index_candidate_budget(entries_published);
let mut candidates = Vec::new();
for value in values {
let range = StoragePrefix {
bytes: Bytes::from(hot_index_value_prefix(
branch_id, generation, schema_key, ordinal, value,
)),
}
.to_range()?;
let mut cursor = self
.store
.begin_scan(INDEX_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let want = (budget + 1 - candidates.len()).min(HOT_INDEX_CANDIDATE_PAGE);
let (page, page_has_more) = cursor.next_page(want).await?.into_parts();
for entry in &page {
let StorageProjectedValue::FullValue(value) = &entry.value else {
continue;
};
let candidate = decode_hot_index_candidate(value)?;
candidates.push((candidate.row_pk, candidate.file_id));
}
if candidates.len() > budget {
return Ok(None);
}
if !page_has_more {
break;
}
}
}
Ok(Some(candidates))
}
pub(crate) async fn scan_hot_index_candidates(
&self,
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
values: &[HotIndexValue],
) -> Result<Option<Vec<RowPk>>, LixError> {
Ok(self
.scan_hot_index_identity_candidates(branch_id, generation, schema_key, ordinal, values)
.await?
.map(|candidates| candidates.into_iter().map(|(row_pk, _)| row_pk).collect()))
}
pub(crate) async fn scan_hot_index_range_candidates(
&self,
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
lower: Option<(&HotIndexValue, bool)>,
upper: Option<(&HotIndexValue, bool)>,
) -> Result<Option<Vec<RowPk>>, LixError> {
if lower.is_none() && upper.is_none() {
return Ok(None);
}
let witness = StorageKey(Bytes::from(encode_hot_index_witness_key(
branch_id, generation, schema_key, ordinal,
)));
let present = PointReadPlan::new(INDEX_SPACE, &[witness])
.materialize(&self.store, StorageGetOptions::default())
.await?;
let Some(StorageProjectedValue::FullValue(witness_value)) =
present.value.into_iter().next().flatten()
else {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_index_probe_refused_unwitnessed();
return Ok(None);
};
let Some(entries_published) = decode_hot_index_witness(&witness_value) else {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_index_probe_refused_unwitnessed();
return Ok(None);
};
let budget = hot_index_candidate_budget(entries_published);
let column_prefix = hot_index_column_prefix(branch_id, generation, schema_key, ordinal);
let value_prefix = |value: &HotIndexValue| {
hot_index_value_prefix(branch_id, generation, schema_key, ordinal, value)
};
let lower_key = match lower {
Some((value, true)) => value_prefix(value),
Some((value, false)) => match hot_index_key_successor(&value_prefix(value)) {
Some(successor) => successor,
None => return Ok(Some(Vec::new())),
},
None => column_prefix.clone(),
};
let upper_key = match upper {
Some((value, true)) => hot_index_key_successor(&value_prefix(value)),
Some((value, false)) => Some(value_prefix(value)),
None => hot_index_key_successor(&column_prefix),
};
if let Some(upper_key) = upper_key.as_ref()
&& lower_key >= *upper_key
{
return Ok(Some(Vec::new()));
}
let lower_bound = std::ops::Bound::Included(StorageKey(Bytes::from(lower_key)));
let upper_bound = match upper_key {
Some(upper_key) => std::ops::Bound::Excluded(StorageKey(Bytes::from(upper_key))),
None => std::ops::Bound::Unbounded,
};
let mut candidates = Vec::new();
let mut cursor = self
.store
.begin_scan(
INDEX_SPACE,
crate::storage_adapter::StorageKeyRange {
lower: lower_bound,
upper: upper_bound,
},
StorageBeginScanOptions::default(),
)
.await?;
loop {
let want = (budget + 1 - candidates.len()).min(HOT_INDEX_CANDIDATE_PAGE);
let (page, page_has_more) = cursor.next_page(want).await?.into_parts();
for entry in &page {
let StorageProjectedValue::FullValue(value) = &entry.value else {
continue;
};
candidates.push(decode_hot_index_candidate(value)?.row_pk);
}
if candidates.len() > budget {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_index_probe_refused_over_budget();
return Ok(None);
}
if !page_has_more {
break;
}
}
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_index_range_probe_engaged(candidates.len());
Ok(Some(candidates))
}
pub(crate) async fn has_schema_rows(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
) -> Result<bool, LixError> {
let mut prefix = hot_scope_prefix(branch_id, control.tracked_generation);
write_key_string(&mut prefix, schema_key, KEY_PART_FINAL);
let range = StoragePrefix {
bytes: Bytes::from(prefix),
}
.to_range()?;
let mut cursor = self
.store
.begin_scan(
ROW_SPACE,
range,
StorageBeginScanOptions {
projection: StorageCoreProjection::KeyOnly,
..StorageBeginScanOptions::default()
},
)
.await?;
let (page, _page_has_more) = cursor.next_page(1).await?.into_parts();
if !page.is_empty() {
return Ok(true);
}
if packed_current_base_has_schema(
&self.store,
branch_id,
control.tracked_generation,
schema_key,
)
.await?
{
return Ok(true);
}
let root =
if load_root_current_base_commit(&self.store, branch_id, control.tracked_generation)
.await?
.is_some()
{
Box::pin(scan_root_current_base_rows(
&self.store,
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["change_id".to_owned()],
},
limit: None,
},
self.root_base_cache.as_deref(),
None,
))
.await?
} else {
MaterializedHotStateBatch::default()
};
if !root.is_empty() {
return Ok(true);
}
Ok(false)
}
pub(crate) async fn scan_row_snapshots(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
row_pks: &[RowPk],
limit: Option<usize>,
) -> Result<Vec<Option<Bytes>>, LixError> {
self.scan_row_snapshots_for_generation(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
schema_key,
row_pks,
limit,
)
.await
}
pub(crate) async fn scan_exclusive_row_snapshots(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
) -> Result<Option<crate::tracked_state::ExclusiveRowSnapshotBatch>, LixError> {
let Some((commit_id, live_count)) = self
.row_columnar_base(branch_id, control, schema_key)
.await?
else {
return Ok(None);
};
let overlay_filter = TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
include_tombstones: true,
..TrackedStateFilter::default()
};
let overlay = hot_scan_entries(
&self.store,
branch_id,
control.tracked_generation,
&overlay_filter,
Some(1),
None,
)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget");
let overlay_is_empty = match overlay {
HotScanEntries::Decoded(rows) => rows.is_empty(),
HotScanEntries::Finite(batches) => batches
.iter()
.all(|batch| batch.values.iter().all(Option::is_none)),
};
if !overlay_is_empty {
return Ok(None);
}
let Some(rows) =
crate::tracked_state::load_exclusive_row_snapshots(&self.store, commit_id, schema_key)
.await?
else {
return Ok(None);
};
let capacity = usize::try_from(live_count)
.map_err(|_| head_value_error("exclusive row live count exceeds usize"))?;
if rows.len() != capacity {
return Err(head_value_error(format!(
"exclusive row base expected {live_count} live rows, decoded {}",
rows.len()
)));
}
Ok(Some(rows))
}
pub(crate) async fn scan_row_primary_keys(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
row_pks: &[RowPk],
limit: Option<usize>,
) -> Result<Vec<RowPk>, LixError> {
if matches!(limit, Some(0)) {
return Ok(Vec::new());
}
let rows = self
.scan_live_batch_for_generation(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
row_pks: row_pks.to_vec(),
include_tombstones: false,
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["snapshot_content".to_owned()],
},
limit,
},
)
.await?;
Ok(rows.into_identity_ordered_primary_keys())
}
pub(crate) async fn scan_native_row_snapshots(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
row_pks: &[RowPk],
limit: Option<usize>,
) -> Result<Vec<(RowPk, Bytes)>, LixError> {
if matches!(limit, Some(0)) {
return Ok(Vec::new());
}
let rows = self
.scan_live_batch_for_generation(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
row_pks: row_pks.to_vec(),
include_tombstones: false,
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["raw_snapshot".to_owned()],
},
limit,
},
)
.await?;
let mut snapshots = rows
.iter()
.map(|row| {
let snapshot = row.raw_snapshot().cloned().ok_or_else(|| {
head_value_error("live v69 row has no native snapshot payload")
})?;
Ok((row.row_pk().clone(), snapshot))
})
.collect::<Result<Vec<_>, LixError>>()?;
snapshots.sort_unstable_by(|left, right| left.0.cmp(&right.0));
Ok(snapshots)
}
pub(crate) async fn row_columnar_layout(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
) -> Result<
Option<(
crate::columnar_row_group::RowGroupSetId,
crate::columnar_row_group::RowGroupManifest,
Vec<RowColumnarOverlayRow>,
u64,
)>,
LixError,
> {
let Some((base_commit_id, live_count)) = self
.row_columnar_base(branch_id, control, schema_key)
.await?
else {
return Ok(None);
};
let id = crate::hot_state::row_group_set_id(base_commit_id, schema_key);
let Some(manifest) =
crate::columnar_row_group::load_row_group_manifest(&self.store, id).await?
else {
return Ok(None);
};
if manifest.namespace != schema_key {
return Err(head_value_error(
"row columnar sidecar disagrees with its collection publication",
));
}
let filter = TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
include_tombstones: true,
..TrackedStateFilter::default()
};
let Some(entries) = hot_scan_entries(
&self.store,
branch_id,
control.tracked_generation,
&filter,
None,
Some(ROW_COLUMNAR_OVERLAY_INPUT_ADMISSION_BYTES),
)
.await?
else {
return Ok(None);
};
let rows = materialize_hot_scan_entries(
&self.store,
entries,
ChangeRecordProjection::from_columns(&["raw_snapshot".to_owned()]),
branch_id,
control.working_diff_checkpoint_commit_id,
)
.await?;
if materialized_columnar_overlay_admission_bytes(&rows)?
> ROW_COLUMNAR_OVERLAY_OUTPUT_ADMISSION_BYTES
{
return Ok(None);
}
let mut overlay = Vec::with_capacity(rows.len());
let mut overlay_bytes = 0_usize;
for row in rows.iter() {
if row.file_id().is_some() || row.untracked() || row.global() {
return Ok(None);
}
let Some(row_commit_id) = row.commit_id() else {
return Ok(None);
};
if row_commit_id < base_commit_id {
continue;
}
overlay_bytes = overlay_bytes
.checked_add(size_of::<RowColumnarOverlayRow>())
.and_then(|bytes| bytes.checked_add(row.row_pk().estimated_heap_bytes()))
.and_then(|bytes| {
bytes.checked_add(row.snapshot_content().map_or(0, |snapshot| snapshot.len()))
})
.and_then(|bytes| {
bytes.checked_add(row.decoded_snapshot().map_or(0, |typed| {
usize::try_from(typed.estimated_size()).unwrap_or(usize::MAX)
}))
})
.and_then(|bytes| bytes.checked_add(row.raw_snapshot().map_or(0, Bytes::len)))
.ok_or_else(|| head_value_error("row columnar overlay byte size overflow"))?;
if overlay_bytes > ROW_COLUMNAR_OVERLAY_OUTPUT_ADMISSION_BYTES {
return Ok(None);
}
overlay.push(RowColumnarOverlayRow {
row_pk: row.row_pk().clone(),
snapshot_content: row
.snapshot_content()
.map(|snapshot| Bytes::copy_from_slice(snapshot.as_bytes())),
decoded_snapshot: row.decoded_snapshot().cloned(),
raw_snapshot: row.raw_snapshot().cloned(),
deleted: row.deleted(),
columnar_base_coordinate: row.columnar_base_coordinate(),
});
}
Ok(Some((id, manifest, overlay, live_count)))
}
async fn row_columnar_base(
&self,
branch_id: &str,
control: BranchHeadControl,
schema_key: &str,
) -> Result<Option<(CommitId, u64)>, LixError> {
let collection = load_hot_collection_control(
&self.store,
branch_id,
control.tracked_generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?;
if collection.active_generation != control.tracked_generation {
return Ok(None);
}
let base_refs = packed_exclusive_schema_base_refs(
&self.store,
branch_id,
control.tracked_generation,
schema_key,
)
.await?;
let [base_ref] = base_refs.as_slice() else {
return Ok(None);
};
let active_base_refs =
packed_current_base_refs(&self.store, branch_id, control.tracked_generation).await?;
if !active_base_refs
.iter()
.any(|active| active.commit_id == base_ref.commit_id)
{
return Err(head_value_error(
"exclusive schema index references an inactive packed current base",
));
}
for active in active_base_refs
.iter()
.filter(|active| active.commit_id != base_ref.commit_id)
{
if crate::tracked_state::commit_delta_contains_schema(
&self.store,
active.commit_id,
schema_key,
)
.await?
{
return Ok(None);
}
}
Ok(Some((base_ref.commit_id, collection.live_count)))
}
pub(crate) async fn scan_tracked_tombstones_for_control(
&self,
branch_id: &str,
control: BranchHeadControl,
) -> Result<Vec<MaterializedHotStateRow>, LixError> {
let filter = TrackedStateFilter {
include_tombstones: true,
..TrackedStateFilter::default()
};
let HotScanEntries::Decoded(entries) = hot_scan_entries(
&self.store,
branch_id,
control.tracked_generation,
&filter,
None,
None,
)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget") else {
unreachable!("an unconstrained HOT scan cannot select the finite point-read route");
};
let mut tombstones = Vec::new();
for (identity, bytes) in entries {
let value = decode_head_value(&bytes)?;
if value.deleted && !value.untracked {
tombstones.push((identity, bytes));
}
}
Ok(materialize_live_entries(
&self.store,
tombstones,
ChangeRecordProjection::identity_only(),
branch_id,
control.working_diff_checkpoint_commit_id,
)
.await?
.into_rows())
}
#[cfg(test)]
pub(crate) async fn scan_live_rows_if_current(
&self,
branch_id: &str,
expected_head: &str,
request: &TrackedStateScanRequest,
) -> Result<Option<Vec<MaterializedHotStateRow>>, LixError> {
let expected_head = CommitId::parse_lix(expected_head, "hot-state expected commit")?;
let control = BranchHeadControlContext::new()
.reader(&self.store)
.load(branch_id)
.await?;
let Some(control) = control.filter(|control| control.head_commit_id == expected_head)
else {
return Ok(None);
};
Ok(Some(
self.scan_live_batch_for_generation(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
request,
)
.await?
.into_rows(),
))
}
async fn scan_live_batch_for_generation(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
request: &TrackedStateScanRequest,
) -> Result<MaterializedHotStateBatch, LixError> {
self.scan_live_batch_for_generation_with_fallback(
branch_id,
generation,
active_checkpoint_commit_id,
request,
None,
)
.await
}
async fn scan_live_batch_for_generation_with_fallback(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
request: &TrackedStateScanRequest,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
self.scan_live_batch_for_generation_with_visibility(
branch_id,
generation,
active_checkpoint_commit_id,
request,
true,
fallback_base_commit_id,
)
.await
}
async fn scan_live_batch_for_generation_with_visibility(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
request: &TrackedStateScanRequest,
apply_collection_visibility: bool,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
let collection_control = if apply_collection_visibility {
match request.filter.schema_keys.as_slice() {
[schema_key]
if schema_key
!= crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY =>
{
Some(
load_hot_collection_visibility_control(
&self.store,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?,
)
}
_ => None,
}
} else {
None
};
let replaced_generation =
collection_control.filter(|control| control.active_generation != generation);
if replaced_generation.is_some_and(|control| control.live_count == 0) {
return Ok(MaterializedHotStateBatch::default());
}
let mut entries = hot_scan_entries(
&self.store,
branch_id,
generation,
&request.filter,
None,
None,
)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget");
if let Some(control) = replaced_generation {
filter_hot_scan_entries_by_collection_generation(&mut entries, control)?;
}
let projection = ChangeRecordProjection::from_columns(&request.read_columns.columns);
let rows = materialize_hot_scan_entries(
&self.store,
entries,
projection,
branch_id,
active_checkpoint_commit_id,
)
.await?;
let rows = rows.filter(
|row| {
replaced_generation.is_none_or(|control| {
survives_collection_generation_fence(
row.untracked(),
row.commit_id(),
control.active_generation,
false,
)
})
},
None,
);
let has_overlay_rows = !rows.is_empty();
let packed_limit = if !has_overlay_rows && replaced_generation.is_none() {
request.limit.map(|limit| limit.saturating_sub(rows.len()))
} else {
None
};
let packed_rows = if let Some(keys) = packed_exact_keys_for_filter(&request.filter) {
let key_refs = keys
.iter()
.map(|key| TrackedStateKeyRef {
schema_key: &key.schema_key,
file_id: key.file_id.as_deref(),
row_pk: &key.row_pk,
})
.collect::<Vec<_>>();
let shadow = packed_current_base_shadow_from_rows(&key_refs, &rows);
load_packed_current_base_exact(
&self.store,
branch_id,
generation,
active_checkpoint_commit_id,
&key_refs,
&shadow,
projection,
self.transaction_cache.as_deref(),
)
.await?
.into_present_batch()
.filter(|_| true, packed_limit)
} else {
scan_packed_current_base_rows(&self.store, branch_id, generation, request, packed_limit)
.await?
};
let root_rows = Box::pin(scan_root_current_base_rows_for_merge(
&self.store,
branch_id,
generation,
active_checkpoint_commit_id,
request,
rows.len().saturating_add(packed_rows.len()),
self.root_base_cache.as_deref(),
fallback_base_commit_id,
))
.await?;
let combined = merge_ordered_live_batches(rows, packed_rows);
let rows = merge_ordered_live_batches(combined, root_rows);
if request.filter.include_tombstones
&& request.limit.is_none()
&& replaced_generation.is_none()
{
return Ok(rows);
}
Ok(rows.filter(
|row| request.filter.include_tombstones || !row.deleted(),
request.limit,
))
}
#[cfg(test)]
pub(crate) async fn load_projected_live_rows_if_current(
&self,
branch_id: &str,
expected_head: &str,
keys: &[TrackedStateKey],
projection: &ChangeRecordProjection,
) -> Result<Option<Vec<Option<MaterializedHotStateRow>>>, LixError> {
let expected_head = CommitId::parse_lix(expected_head, "hot-state expected commit")?;
let control = BranchHeadControlContext::new()
.reader(&self.store)
.load(branch_id)
.await?;
let Some(control) = control.filter(|control| control.head_commit_id == expected_head)
else {
return Ok(None);
};
Ok(Some(
self.load_projected_live_batch(branch_id, control, keys, projection)
.await?
.into_rows(),
))
}
pub(crate) async fn load_projected_live_rows(
&self,
branch_id: &str,
control: BranchHeadControl,
keys: &[TrackedStateKey],
projection: &ChangeRecordProjection,
) -> Result<Vec<Option<MaterializedHotStateRow>>, LixError> {
self.load_projected_live_batch(branch_id, control, keys, projection)
.await
.map(MaterializedHotStateExactBatch::into_rows)
}
pub(crate) async fn load_projected_live_batch(
&self,
branch_id: &str,
control: BranchHeadControl,
keys: &[TrackedStateKey],
projection: &ChangeRecordProjection,
) -> Result<MaterializedHotStateExactBatch, LixError> {
let keys = keys
.iter()
.map(|key| TrackedStateKeyRef {
schema_key: key.schema_key.as_str(),
file_id: key.file_id.as_deref(),
row_pk: &key.row_pk,
})
.collect::<Vec<_>>();
self.load_projected_live_batch_refs(branch_id, control, &keys, projection)
.await
}
pub(crate) async fn load_projected_live_batch_refs(
&self,
branch_id: &str,
control: BranchHeadControl,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
) -> Result<MaterializedHotStateExactBatch, LixError> {
self.load_projected_live_batch_refs_for_domain(
branch_id,
control,
keys,
projection,
HotStateReadDomain::Combined,
)
.await
}
pub(crate) async fn load_projected_live_batch_refs_for_domain(
&self,
branch_id: &str,
control: BranchHeadControl,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
domain: HotStateReadDomain,
) -> Result<MaterializedHotStateExactBatch, LixError> {
self.load_projected_live_batch_refs_for_domain_with_fallback(
branch_id, control, keys, projection, domain, None,
)
.await
}
pub(crate) async fn load_projected_live_batch_refs_for_domain_with_fallback(
&self,
branch_id: &str,
control: BranchHeadControl,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
domain: HotStateReadDomain,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateExactBatch, LixError> {
let rows = Box::pin(
self.load_projected_live_batch_for_generation_refs_with_fallback(
branch_id,
control.tracked_generation,
control.working_diff_checkpoint_commit_id,
keys,
projection,
fallback_base_commit_id,
),
)
.await?;
match domain {
HotStateReadDomain::Combined => Ok(rows),
HotStateReadDomain::Tracked => rows.filter(|row| !row.untracked()),
HotStateReadDomain::Untracked => rows.filter(|row| row.untracked()),
}
}
async fn load_projected_live_batch_for_generation_refs(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
) -> Result<MaterializedHotStateExactBatch, LixError> {
self.load_projected_live_batch_for_generation_refs_with_fallback(
branch_id,
generation,
active_checkpoint_commit_id,
keys,
projection,
None,
)
.await
}
async fn load_projected_live_batch_for_generation_refs_with_fallback(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateExactBatch, LixError> {
self.load_projected_live_batch_for_generation_refs_with_visibility(
branch_id,
generation,
active_checkpoint_commit_id,
keys,
projection,
true,
fallback_base_commit_id,
)
.await
}
async fn load_projected_live_batch_for_generation_refs_with_visibility(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
keys: &[TrackedStateKeyRef<'_>],
projection: &ChangeRecordProjection,
apply_collection_visibility: bool,
fallback_base_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateExactBatch, LixError> {
if keys.is_empty() {
return Ok(MaterializedHotStateExactBatch::default());
}
let replaced_generation = apply_collection_visibility
.then(|| {
keys.first()
.filter(|first| keys.iter().all(|key| key.schema_key == first.schema_key))
.filter(|first| {
first.schema_key
!= crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
})
.map(|first| async {
load_hot_collection_visibility_control(
&self.store,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: first.schema_key,
file_id: None,
},
)
.await
})
})
.flatten();
let replaced_generation = match replaced_generation {
Some(control) => {
let control = control.await?;
(control.active_generation != generation).then_some(control)
}
None => None,
};
if replaced_generation.is_some_and(|control| control.live_count == 0) {
return MaterializedHotStateExactBatch::new(
MaterializedHotStateBatch::default(),
vec![None; keys.len()],
);
}
let mut values =
hot_load_identity_ref_bytes(&self.store, branch_id, generation, keys).await?;
if let Some(control) = replaced_generation {
for value in &mut values {
let visible = value
.as_deref()
.map(decode_head_value)
.transpose()?
.is_some_and(|value| {
survives_collection_generation_fence(
value.untracked,
value.commit_id,
control.active_generation,
false,
)
});
if !visible {
*value = None;
}
}
}
let mut slots = Vec::with_capacity(values.len());
let mut entries = Vec::with_capacity(values.iter().flatten().count());
for (identity, value) in keys.iter().copied().zip(values) {
slots.push(value.map(|value| {
let ordinal =
u32::try_from(entries.len()).expect("live-state exact batch exceeds u32 rows");
entries.push((identity, value));
ordinal
}));
}
let rows = materialize_live_entries(
&self.store,
entries,
*projection,
branch_id,
active_checkpoint_commit_id,
)
.await?;
let packed_shadow = slots
.iter()
.map(|slot| {
slot.and_then(|slot| rows.get(slot as usize))
.and_then(|row| row.commit_id())
})
.collect::<Vec<_>>();
let packed = load_packed_current_base_exact(
&self.store,
branch_id,
generation,
active_checkpoint_commit_id,
keys,
&packed_shadow,
*projection,
self.transaction_cache.as_deref(),
)
.await?;
let root_base_commit_id = load_root_current_base_commit(&self.store, branch_id, generation)
.await?
.or(fallback_base_commit_id);
let root = if root_base_commit_id.is_some() {
Box::pin(load_root_current_base_exact(
&self.store,
branch_id,
generation,
active_checkpoint_commit_id,
keys,
*projection,
self.root_base_cache.as_deref(),
root_base_commit_id,
))
.await?
} else {
MaterializedHotStateExactBatch::new(
MaterializedHotStateBatch::default(),
vec![None; keys.len()],
)?
};
let mut resolved = Vec::with_capacity(keys.len());
for (index, slot) in slots.into_iter().enumerate() {
let mut row = slot.and_then(|slot| rows.get(slot as usize));
for candidate in [packed.row(index), root.row(index)].into_iter().flatten() {
if row.is_none_or(
|current| match (current.commit_id(), candidate.commit_id()) {
(Some(current), Some(candidate)) => candidate > current,
(None, Some(_)) => false,
(Some(_), None) => false,
(None, None) => false,
},
) {
row = Some(candidate);
}
}
resolved.push(row.filter(|row| {
replaced_generation.is_none_or(|control| {
survives_collection_generation_fence(
row.untracked(),
row.commit_id(),
control.active_generation,
true,
)
})
}));
}
let mut builder = MaterializedHotStateBatchBuilder::with_capacity(keys.len());
let mut combined_slots = Vec::with_capacity(keys.len());
for row in resolved {
let row = row.filter(|row| {
replaced_generation.is_none_or(|control| {
survives_collection_generation_fence(
row.untracked(),
row.commit_id(),
control.active_generation,
true,
)
})
});
combined_slots.push(
row.map(|row| {
u32::try_from(builder.push_ref(row, None)).map_err(|_| {
LixError::new(
LixError::CODE_INTERNAL_ERROR,
"exact live-state result exceeds u32 rows",
)
})
})
.transpose()?,
);
}
MaterializedHotStateExactBatch::new(builder.finish(), combined_slots)
}
pub(crate) async fn working_diff_epoch(
&self,
branch_id: &str,
) -> Result<Option<TrackedWorkingDiffEpoch>, LixError> {
load_tracked_working_diff_epoch(&self.store, branch_id).await
}
pub(crate) async fn root_current_base_commit(
&self,
branch_id: &str,
generation: CommitId,
) -> Result<Option<CommitId>, LixError> {
load_root_current_base_commit(&self.store, branch_id, generation).await
}
pub(crate) async fn working_diff_for_control(
&self,
branch_id: &str,
control: BranchHeadControl,
request: &TrackedStateDiffRequest,
) -> Result<Option<TrackedWorkingDiff>, LixError> {
if self
.root_current_base_commit(branch_id, control.tracked_generation)
.await?
.is_some()
{
return Ok(None);
}
let Some(epoch) = self.working_diff_epoch(branch_id).await? else {
return Ok(None);
};
let generation = epoch.generation;
if generation != control.tracked_generation
|| control.working_diff_checkpoint_commit_id != Some(epoch.checkpoint_commit_id)
{
return Ok(None);
}
let Some(entries) = hot_working_diff_entries(
&self.store,
branch_id,
epoch.checkpoint_commit_id,
generation,
epoch.coverage,
&request.filter,
)
.await?
else {
return Ok(None);
};
let diff = if request.retain_payloads {
let mut changes: BTreeMap<
ChangeId,
crate::tracked_state::AuthoritativeLiveChangeRequest,
> = BTreeMap::new();
for row in entries.iter().flat_map(|entry| {
[
entry.visible_before(),
entry.after.as_ref().filter(|row| !row.deleted),
]
.into_iter()
.flatten()
}) {
let key = TrackedStateKey {
schema_key: row.identity.schema_key().to_owned(),
file_id: row.identity.file_id().map(str::to_owned),
row_pk: row.identity.row_pk().clone(),
};
if let Some(existing) = changes.get(&row.change_id) {
if existing.key != key || existing.updated_at != row.updated_at {
return Err(LixError::new(
LixError::CODE_INTERNAL_ERROR,
format!(
"working diff contains conflicting identities or lifetimes for change '{}'",
row.change_id
),
));
}
} else {
changes.insert(
row.change_id,
crate::tracked_state::AuthoritativeLiveChangeRequest {
change_id: row.change_id,
source_commit_id: row.commit_id,
key,
updated_at: row.updated_at,
},
);
}
}
let requests = changes.into_values().collect::<Vec<_>>();
let records = crate::tracked_state::load_authoritative_live_change_records(
&self.store,
&requests,
)
.await?;
let payloads = crate::tracked_state::TrackedStatePayloadBatch::from_payloads(
records
.into_iter()
.map(|record| (record.change_id, record.snapshot, record.metadata)),
)?;
TrackedStateDiff::from_entries_with_payloads(entries, payloads)
} else {
TrackedStateDiff::from_entries(entries)
};
Ok(Some(TrackedWorkingDiff {
checkpoint_commit_id: epoch.checkpoint_commit_id,
diff,
}))
}
async fn scan_row_snapshots_for_generation(
&self,
branch_id: &str,
generation: CommitId,
active_checkpoint_commit_id: Option<CommitId>,
schema_key: &str,
row_pks: &[RowPk],
limit: Option<usize>,
) -> Result<Vec<Option<Bytes>>, LixError> {
if matches!(limit, Some(0)) {
return Ok(Vec::new());
}
let rows = self
.scan_live_batch_for_generation(
branch_id,
generation,
active_checkpoint_commit_id,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
row_pks: row_pks.to_vec(),
include_tombstones: false,
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["snapshot_content".to_owned()],
},
limit,
},
)
.await?;
Ok(rows.into_identity_ordered_snapshots())
}
}
type HotRowMap = BTreeMap<HeadRowIdentity, Bytes>;
#[derive(Clone, Default)]
pub(crate) struct HotTrackedSnapshot {
rows: HotRowMap,
}
pub(crate) enum CompleteWorkingDiffMode {
Disabled,
ResetClean,
Rebase {
checkpoint_commit_id: CommitId,
checkpoint: HotTrackedSnapshot,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum WorkingDiffBaselineAction {
Continue,
Reset,
Rebase {
previous_checkpoint_commit_id: CommitId,
},
}
impl HotTrackedSnapshot {
pub(crate) fn inherited_catalog_differs_from(
&self,
previous: &MaterializedHotStateBatch,
local: &Self,
branch_generation: CommitId,
) -> Result<bool, LixError> {
let mut rows = self.rows.clone();
rows.extend(
local
.rows
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
);
let controls = complete_collection_generation_controls(branch_generation, &rows)?;
let next = rows
.iter()
.filter(|(key, _)| key.schema_key == "lix_registered_schema" && key.file_id.is_none())
.filter_map(|(key, bytes)| match decode_head_value(bytes) {
Ok(value) if value.deleted => None,
Ok(value)
if !row_belongs_to_active_collection_generation(
&controls,
branch_generation,
&key.schema_key,
key.file_id.as_deref(),
value.untracked,
value.commit_id,
) =>
{
None
}
Ok(value) => Some(
value
.snapshot
.map(|snapshot| (&key.row_pk, snapshot))
.ok_or_else(|| head_value_error("live catalog row has no payload")),
),
Err(error) => Some(Err(error)),
})
.collect::<Result<BTreeMap<_, _>, LixError>>()?;
let previous = previous
.iter()
.map(|row| {
row.raw_snapshot()
.map(|snapshot| (row.row_pk(), snapshot.as_ref()))
.ok_or_else(|| head_value_error("live serving catalog row has no payload"))
})
.collect::<Result<BTreeMap<_, _>, LixError>>()?;
Ok(next != previous)
}
pub(crate) fn from_materialized_rows(
tracked_rows: Vec<MaterializedTrackedStateRow>,
) -> Result<Self, LixError> {
let mut rows = BTreeMap::new();
for row in tracked_rows {
let snapshot = if row.deleted {
None
} else {
let typed = row.decoded_snapshot.as_ref().ok_or_else(|| {
head_value_error("live lifecycle row is missing its native typed payload")
})?;
Some(
typed
.durable_payload()
.map_err(|error| {
head_value_error(format!(
"cannot retain lifecycle typed payload: {error:?}"
))
})?
.to_vec(),
)
};
let identity = HeadRowIdentity {
schema_key: row.schema_key,
row_pk: row.row_pk,
file_id: row.file_id,
};
let metadata = row
.metadata
.as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|error| {
head_value_error(&format!("bootstrap metadata is invalid JSON: {error}"))
})?
.map(lix_schema::Jsonb::from_value);
let value = HeadValueRef {
change_id: Some(row.change_id),
commit_id: Some(row.commit_id),
untracked: false,
deleted: row.deleted,
created_at: LixTimestamp::expect_parse(
"hot tracked snapshot created_at",
&row.created_at,
),
updated_at: LixTimestamp::expect_parse(
"hot tracked snapshot updated_at",
&row.updated_at,
),
snapshot: snapshot.as_deref(),
metadata: metadata.as_ref(),
columnar_base_coordinate: None,
working_diff_baseline: WorkingDiffBaseline::Disabled,
};
if rows
.insert(identity, Bytes::from(encode_head_value(&value)?))
.is_some()
{
return Err(LixError::new(
LixError::CODE_INTERNAL_ERROR,
"tracked hot snapshot contains duplicate row identity",
));
}
}
Ok(Self { rows })
}
}
pub(crate) struct HotStateWriter<'a, S: ?Sized> {
pub(super) store: &'a S,
pub(super) writes: &'a mut StorageWriteSet,
pub(super) transaction_global_schema_keys: Option<&'a BTreeSet<String>>,
}
impl<'a, S: ?Sized> HotStateWriter<'a, S> {
pub(crate) fn with_transaction_global_schema_keys(
mut self,
schema_keys: &'a BTreeSet<String>,
) -> Self {
self.transaction_global_schema_keys = Some(schema_keys);
self
}
}
impl<S> HotStateWriter<'_, S>
where
S: StorageAdapterRead + ?Sized,
{
pub(crate) async fn checkpoint_tombstone_compaction_mask(
&self,
branch_id: &str,
generation: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
) -> Result<Vec<bool>, LixError> {
let refs = deltas.iter().collect::<Vec<_>>();
hot_compaction_mask(
self.store,
branch_id,
generation,
&refs,
None,
HotTombstoneMaskKind::Checkpoint,
self.transaction_global_schema_keys,
)
.await
}
pub(crate) fn stage_empty_root_deterministic_witness(
&mut self,
branch_id: &str,
generation: CommitId,
) -> Result<(), LixError> {
if branch_id != crate::GLOBAL_BRANCH_ID {
return Ok(());
}
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let witness = DeterministicIdentityWitness {
collection_control: Vec::new(),
presence: 0,
};
self.writes.put(
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
StorageKey(Bytes::from(hot_collection_control_key(
branch_id, generation, scope,
))),
StorageValue {
bytes: Bytes::from(storage_codec::encode(
"deterministic identity witness",
&witness,
)?),
},
);
Ok(())
}
pub(crate) async fn stage_untracked_for_root_generation(
&mut self,
branch_id: &str,
source_generation: CommitId,
target_generation: CommitId,
target_head: CommitId,
) -> Result<(), LixError> {
if source_generation == target_generation {
return Err(head_value_error(
"untracked transfer requires a fresh generation",
));
}
let rows = load_hot_untracked_generation(self.store, branch_id, source_generation).await?;
if !rows.keys().any(|identity| {
identity.schema_key == EXACT_CLOSURE_SCHEMA_KEY && identity.file_id.is_none()
}) {
self.stage_empty_root_deterministic_witness(branch_id, target_generation)?;
}
if rows.is_empty() {
return Ok(());
}
let keys = rows
.keys()
.map(|identity| TrackedStateKeyRef {
schema_key: &identity.schema_key,
file_id: identity.file_id.as_deref(),
row_pk: &identity.row_pk,
})
.collect::<Vec<_>>();
let mut reader = crate::tracked_state::TrackedStateContext::new().reader(self.store);
let tracked = reader
.load_projected_batch_at_commit_refs(
&target_head.to_string(),
&keys,
&ChangeRecordProjection::identity_only(),
)
.await?;
for (index, identity) in rows.keys().enumerate() {
if tracked.row(index).is_some() {
return Err(LixError::new(
LixError::CODE_UNIQUE,
format!(
"candidate tracked identity conflicts with local untracked row in schema '{}'",
identity.schema_key,
),
));
}
}
stage_complete_hot_rows(self.writes, branch_id, target_generation, rows);
Ok(())
}
pub(crate) fn stage_root_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
head_commit_id: CommitId,
) {
self.writes.put(
ROOT_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation))),
StorageValue {
bytes: Bytes::copy_from_slice(head_commit_id.as_uuid().as_bytes()),
},
);
}
pub(crate) async fn stage_inherited_catalog_refresh(
&mut self,
branch_id: &str,
generation: CommitId,
local_catalog: HotTrackedSnapshot,
mut inherited_catalog: HotTrackedSnapshot,
) -> Result<(), LixError> {
const CATALOG: &str = "lix_registered_schema";
if let Some(root) = load_root_current_base_commit(self.store, branch_id, generation).await?
{
let node = crate::commit_graph::CommitGraphContext::new()
.reader(self.store)
.load_node(&root)
.await?
.ok_or_else(|| head_value_error("current-base commit is missing"))?;
if node.base_commit_id.is_none() {
self.writes.delete(
ROOT_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation))),
);
}
}
inherited_catalog
.rows
.retain(|key, _| !local_catalog.rows.contains_key(key));
normalize_complete_hot_snapshot_baselines(
&mut inherited_catalog.rows,
WorkingDiffBaseline::Clean,
)?;
let filter = TrackedStateFilter {
schema_keys: vec![CATALOG.to_owned()],
include_tombstones: true,
..TrackedStateFilter::default()
};
let HotScanEntries::Decoded(previous) =
hot_scan_entries(self.store, branch_id, generation, &filter, None, None)
.await?
.expect("unbounded catalog scan cannot exhaust a byte budget")
else {
unreachable!("catalog scan has no finite primary-key predicate");
};
let mut untracked = BTreeMap::new();
for (identity, bytes) in previous {
let value = decode_head_value(&bytes)?;
let key = identity.into_row_identity();
if value.untracked {
untracked.insert(key, bytes);
} else if !local_catalog.rows.contains_key(&key)
&& !inherited_catalog.rows.contains_key(&key)
{
self.writes.delete(
ROW_SPACE,
StorageKey(Bytes::from(encode_hot_row_key_parts(
branch_id,
generation,
&key.schema_key,
&key.row_pk,
key.file_id.as_deref(),
))),
);
}
}
let mut complete_catalog = local_catalog.rows;
complete_catalog.extend(
inherited_catalog
.rows
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
);
merge_final_untracked_rows(&mut complete_catalog, untracked)?;
stage_complete_collection_controls(self.writes, branch_id, generation, &complete_catalog)?;
stage_complete_hot_rows(self.writes, branch_id, generation, inherited_catalog.rows);
Ok(())
}
pub(crate) async fn stage_ordered_insert_current_base<'a, I>(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
rows: I,
row_columnar_write_sets: &crate::hot_state::RowColumnarWriteSets,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError>
where
I: ExactSizeIterator<Item = (&'a str, &'a RowPk)> + Clone,
{
if rows.len() == 0 {
return Err(head_value_error(
"ordered packed current base requires at least one inserted row",
));
}
let mut previous = None::<(&str, &RowPk)>;
let mut schema_rows = BTreeMap::<&str, Vec<&RowPk>>::new();
for (schema_key, row_pk) in rows {
if previous.is_some_and(|(previous_schema, previous_row_pk)| {
previous_schema
.cmp(schema_key)
.then_with(|| previous_row_pk.cmp(row_pk))
!= Ordering::Less
}) {
return Err(head_value_error(
"ordered packed current-base identities are not strictly increasing",
));
}
previous = Some((schema_key, row_pk));
schema_rows.entry(schema_key).or_default().push(row_pk);
}
let schema_increments = schema_rows
.into_iter()
.map(|(schema_key, row_pks)| {
let live_count = u64::try_from(row_pks.len())
.map_err(|_| head_value_error("packed current-base row count exceeds u64"))?;
Ok((
schema_key,
PackedCollectionIncrement {
live_count,
ordered_identity_digest:
crate::collection_generation::ordered_single_string_identity_digest(
row_pks,
),
},
))
})
.collect::<Result<BTreeMap<_, _>, LixError>>()?;
for schema_key in schema_increments.keys() {
if let Some(encoded) =
row_columnar_write_sets.get(&(new_head, (*schema_key).to_string()))
{
crate::columnar_row_group::stage_row_group_set(
self.writes,
crate::hot_state::row_group_set_id(new_head, schema_key),
encoded,
)?;
}
}
self.stage_packed_insert_current_base_manifest(
branch_id,
generation,
new_head,
schema_increments,
None,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
}
pub(crate) async fn stage_certified_columnar_insert_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
parts: &crate::tracked_state::ColumnarMutationPartSet,
lifecycle: &crate::tracked_state::CommitDeltaLifecycleSummary,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
if parts.owner_commit_id != *new_head.as_uuid().as_bytes()
|| parts.row_count == 0
|| lifecycle.scope.schema_key != parts.schema_key
|| lifecycle.scope.file_id.is_some()
|| lifecycle.uniform_created_at != parts.uniform_created_at
{
return Err(head_value_error(
"certified columnar current base disagrees with mutation authority",
));
}
let schema_increments = BTreeMap::from([(
parts.schema_key.as_str(),
PackedCollectionIncrement {
live_count: u64::from(parts.row_count),
ordered_identity_digest: Some(lifecycle.ordered_identity_digest),
},
)]);
self.stage_packed_insert_current_base_manifest(
branch_id,
generation,
new_head,
schema_increments,
None,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
}
pub(crate) async fn stage_complete_collection_replacement_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
schema_key: &str,
row_count: usize,
row_columnar_write_sets: &crate::hot_state::RowColumnarWriteSets,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(CommitId, bool), LixError> {
let row_count = u64::try_from(row_count)
.map_err(|_| head_value_error("packed replacement row count exceeds u64"))?;
if row_count == 0 {
return Err(head_value_error(
"packed collection replacement requires at least one row",
));
}
let control = load_hot_collection_control(
self.store,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?;
self.stage_complete_collection_replacement_current_base_with_control(
branch_id,
generation,
new_head,
schema_key,
row_count,
control,
row_columnar_write_sets,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
}
async fn stage_complete_collection_replacement_current_base_with_control(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
schema_key: &str,
row_count: u64,
control: HotCollectionControl,
row_columnar_write_sets: &crate::hot_state::RowColumnarWriteSets,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(CommitId, bool), LixError> {
let live_count = if control.live_count == DEFERRED_ROOT_LIVE_COUNT {
let reader = HotStateStoreReader {
store: &*self.store,
transaction_cache: None,
root_base_cache: None,
};
reader
.exact_collection_live_count(
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?
} else {
control.live_count
};
if live_count != row_count {
return Err(head_value_error(format!(
"packed collection replacement expected {row_count} live rows in '{schema_key}', found {}",
live_count
)));
}
let replaced =
packed_exclusive_schema_base_refs(self.store, branch_id, generation, schema_key)
.await?;
if replaced
.iter()
.any(|base_ref| base_ref.commit_id == new_head)
{
return Err(head_value_error(
"packed collection replacement must publish a new commit",
));
}
let mut manifest_key = hot_scope_prefix(branch_id, generation);
manifest_key.reserve(16);
manifest_key.extend_from_slice(new_head.as_uuid().as_bytes());
let expected_checkpoint = working_diff_capture_checkpoint_commit_id
.map_or([0; 16], |checkpoint| *checkpoint.as_uuid().as_bytes());
if replaced.is_empty() {
if working_diff_capture_checkpoint_commit_id.is_some() {
coverage
.add_encoded_group_key(&manifest_key)
.ok_or_else(|| {
head_value_error("packed current-base diff count exceeds u64")
})?;
}
} else {
let base_keys = replaced
.iter()
.map(|base_ref| {
let mut key = hot_scope_prefix(branch_id, generation);
key.reserve(16);
key.extend_from_slice(base_ref.commit_id.as_uuid().as_bytes());
StorageKey(Bytes::from(key))
})
.collect::<Vec<_>>();
let base_values = PointReadPlan::new(PACKED_CURRENT_BASE_SPACE, &base_keys)
.materialize(self.store, StorageGetOptions::default())
.await?
.value;
for ((base_ref, base_key), value) in replaced.iter().zip(&base_keys).zip(base_values) {
let value = value.ok_or_else(|| {
head_value_error(
"exclusive-schema index references an inactive packed current base",
)
})?;
let base_checkpoint = full_value_bytes(value)?;
let base_is_dirty_in_active_epoch = base_checkpoint.as_ref() == expected_checkpoint;
if working_diff_capture_checkpoint_commit_id.is_none()
&& !base_is_dirty_in_active_epoch
{
return Err(head_value_error(
"packed collection replacement has a different working-diff owner",
));
}
if working_diff_capture_checkpoint_commit_id.is_some()
&& base_is_dirty_in_active_epoch
{
coverage
.remove_encoded_group_key(&base_key.0)
.ok_or_else(|| {
head_value_error("packed current-base diff coverage underflow")
})?;
}
self.writes
.delete(PACKED_CURRENT_BASE_SPACE, base_key.clone());
self.writes.delete(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
StorageKey(base_ref.index_key.clone()),
);
}
if working_diff_capture_checkpoint_commit_id.is_some() {
coverage
.add_encoded_group_key(&manifest_key)
.ok_or_else(|| {
head_value_error("packed current-base diff count exceeds u64")
})?;
}
}
if let Some(encoded) = row_columnar_write_sets.get(&(new_head, schema_key.to_string())) {
crate::columnar_row_group::stage_row_group_set(
self.writes,
crate::hot_state::row_group_set_id(new_head, schema_key),
encoded,
)?;
}
self.writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(manifest_key)),
StorageValue {
bytes: Bytes::copy_from_slice(&expected_checkpoint),
},
);
stage_packed_exclusive_schema_base_ref(
self.writes,
branch_id,
generation,
schema_key,
new_head,
);
self.writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
Ok((generation, !replaced.is_empty()))
}
pub(crate) async fn try_stage_exact_collection_replacement_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
parent_commit_id: CommitId,
parent_authority_certified: bool,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
row_columnar_write_sets: &crate::hot_state::RowColumnarWriteSets,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<Option<CommitId>, LixError> {
let Some(first) = deltas.first() else {
return Ok(None);
};
let schema_key = first.schema_key;
if deltas.iter().any(|delta| {
delta.schema_key != schema_key
|| delta.file_id.is_some()
|| delta.untracked
|| delta.deleted
|| delta.commit_id != Some(new_head)
|| delta.change_id.is_none()
}) {
return Ok(None);
}
let control = load_hot_collection_control(
self.store,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?;
if control.active_generation != generation
|| control.live_count != u64::try_from(deltas.len()).unwrap_or(u64::MAX)
{
return Ok(None);
}
let mut row_pks = deltas.iter().map(|delta| delta.row_pk).collect::<Vec<_>>();
row_pks.sort_unstable();
if row_pks.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(head_value_error(
"packed collection replacement contains duplicate identities",
));
}
let Some(identity_digest) =
crate::collection_generation::ordered_single_string_identity_digest(
row_pks.iter().copied(),
)
else {
return Ok(None);
};
if control.ordered_identity_digest != Some(identity_digest) {
return Ok(None);
}
if !parent_authority_certified
&& !self
.authoritative_collection_matches(
parent_commit_id,
schema_key,
&row_pks,
identity_digest,
)
.await?
{
return Ok(None);
}
self.stage_complete_collection_replacement_current_base_with_control(
branch_id,
generation,
new_head,
schema_key,
u64::try_from(row_pks.len()).unwrap_or(u64::MAX),
control,
row_columnar_write_sets,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
.map(|(generation, _)| Some(generation))
}
pub(crate) async fn try_stage_exact_collection_delete_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
parent_commit_id: CommitId,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
) -> Result<Option<CommitId>, LixError> {
if working_diff_capture_checkpoint_commit_id.is_some() {
return Ok(None);
}
let Some(first) = deltas.first() else {
return Ok(None);
};
let schema_key = first.schema_key;
if deltas.iter().any(|delta| {
delta.schema_key != schema_key
|| delta.file_id.is_some()
|| delta.untracked
|| !delta.deleted
|| delta.commit_id != Some(new_head)
|| delta.change_id.is_none()
}) {
return Ok(None);
}
let control = load_hot_collection_control(
self.store,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.await?;
if control.active_generation != generation
|| control.live_count != u64::try_from(deltas.len()).unwrap_or(u64::MAX)
{
return Ok(None);
}
let mut row_pks = deltas.iter().map(|delta| delta.row_pk).collect::<Vec<_>>();
row_pks.sort_unstable();
if row_pks.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(head_value_error(
"packed collection deletion contains duplicate identities",
));
}
let Some(identity_digest) =
crate::collection_generation::ordered_single_string_identity_digest(
row_pks.iter().copied(),
)
else {
return Ok(None);
};
if control.ordered_identity_digest != Some(identity_digest) {
return Ok(None);
}
if !self
.authoritative_collection_matches(
parent_commit_id,
schema_key,
&row_pks,
identity_digest,
)
.await?
{
return Ok(None);
}
let replaced =
packed_exclusive_schema_base_refs(self.store, branch_id, generation, schema_key)
.await?;
if replaced.is_empty() {
return Ok(None);
}
let base_keys = replaced
.iter()
.map(|base_ref| {
let mut key = hot_scope_prefix(branch_id, generation);
key.reserve(16);
key.extend_from_slice(base_ref.commit_id.as_uuid().as_bytes());
StorageKey(Bytes::from(key))
})
.collect::<Vec<_>>();
let base_values = PointReadPlan::new(PACKED_CURRENT_BASE_SPACE, &base_keys)
.materialize(self.store, StorageGetOptions::default())
.await?
.value;
for value in base_values {
let value = value.ok_or_else(|| {
head_value_error(
"exclusive-schema index references an inactive packed current base",
)
})?;
if full_value_bytes(value)?.as_ref() != [0; 16] {
return Ok(None);
}
}
for (base_ref, base_key) in replaced.iter().zip(base_keys) {
self.writes.delete(PACKED_CURRENT_BASE_SPACE, base_key);
self.writes.delete(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
StorageKey(base_ref.index_key.clone()),
);
}
stage_hot_collection_control(
self.writes,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
HotCollectionControl {
active_generation: new_head,
live_count: 0,
ordered_identity_digest: None,
},
)?;
Ok(Some(generation))
}
async fn authoritative_collection_matches(
&self,
parent_commit_id: CommitId,
schema_key: &str,
expected_row_pks: &[&RowPk],
expected_identity_digest: [u8; 32],
) -> Result<bool, LixError> {
let mut reader = crate::tracked_state::TrackedStateContext::new().reader(&*self.store);
let rows = reader
.scan_batch_at_commit(
&parent_commit_id.to_string(),
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
file_ids: vec![NullableKeyFilter::Null],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["schema_key".to_owned()],
},
limit: None,
},
)
.await?;
if rows.len() != expected_row_pks.len() {
return Ok(false);
}
let mut authoritative_row_pks = rows.iter().map(|row| row.row_pk()).collect::<Vec<_>>();
authoritative_row_pks.sort_unstable();
if authoritative_row_pks.as_slice() != expected_row_pks {
return Ok(false);
}
Ok(
crate::collection_generation::ordered_single_string_identity_digest(
authoritative_row_pks,
) == Some(expected_identity_digest),
)
}
pub(crate) async fn try_stage_packed_insert_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
absence_guards: &[TrackedStateKeyRef<'_>],
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<Option<CommitId>, LixError> {
if deltas.is_empty() {
return Err(head_value_error(
"packed current base requires at least one inserted row",
));
}
let mut sorted = deltas.iter().collect::<Vec<_>>();
for delta in &sorted {
delta.validate()?;
if delta.untracked
|| delta.deleted
|| delta.file_id.is_some()
|| delta.commit_id != Some(new_head)
|| delta.change_id.is_none()
{
return Err(head_value_error(
"packed current base accepts only live tracked unfiled creates",
));
}
}
sorted.sort_unstable_by(|left, right| compare_hot_deltas(left, right));
if sorted
.windows(2)
.any(|pair| compare_hot_deltas(pair[0], pair[1]).is_eq())
{
return Err(current_state_duplicate_delta_error(sorted[1]));
}
let mut schema_rows = BTreeMap::<&str, Vec<&RowPk>>::new();
for delta in &sorted {
schema_rows
.entry(delta.schema_key)
.or_default()
.push(delta.row_pk);
}
let preloaded_controls = if absence_guards.is_empty() {
let scopes = schema_rows
.keys()
.map(
|schema_key| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.collect::<Vec<_>>();
let controls =
load_hot_collection_controls(self.store, branch_id, generation, &scopes).await?;
if controls.iter().any(|control| control.live_count != 0) {
return Ok(None);
}
Some(controls)
} else {
let mut guarded = absence_guards
.iter()
.map(|guard| (guard.schema_key, guard.row_pk, guard.file_id))
.collect::<Vec<_>>();
guarded.sort_unstable();
if sorted
.iter()
.map(|delta| (delta.schema_key, delta.row_pk, delta.file_id))
.ne(guarded)
{
return Err(head_value_error(
"packed current base rows do not exactly match their validated absence proofs",
));
}
None
};
let schema_increments = schema_rows
.into_iter()
.map(|(schema_key, row_pks)| {
let live_count = u64::try_from(row_pks.len())
.map_err(|_| head_value_error("packed current-base row count exceeds u64"))?;
Ok((
schema_key,
PackedCollectionIncrement {
live_count,
ordered_identity_digest:
crate::collection_generation::ordered_single_string_identity_digest(
row_pks,
),
},
))
})
.collect::<Result<BTreeMap<_, _>, LixError>>()?;
self.stage_packed_insert_current_base_manifest(
branch_id,
generation,
new_head,
schema_increments,
preloaded_controls,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
.map(Some)
}
pub(crate) async fn try_stage_certified_fresh_file_current_base(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
certified_file_id: &str,
deltas: &[CurrentStateDeltaRef<'_>],
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<Option<CommitId>, LixError> {
if deltas.is_empty() {
return Err(head_value_error(
"certified fresh-file packed base requires at least one row",
));
}
let mut sorted = deltas.iter().collect::<Vec<_>>();
for delta in &sorted {
delta.validate()?;
if delta.untracked
|| delta.deleted
|| delta.file_id != Some(certified_file_id)
|| delta.commit_id != Some(new_head)
|| delta.change_id.is_none()
{
return Err(head_value_error(
"certified fresh-file packed base accepts only live tracked creates for its exact file",
));
}
}
sorted.sort_unstable_by(|left, right| compare_hot_deltas(left, right));
if sorted
.windows(2)
.any(|pair| compare_hot_deltas(pair[0], pair[1]).is_eq())
{
return Err(current_state_duplicate_delta_error(sorted[1]));
}
let mut scope_members =
BTreeMap::<(String, Option<String>), Vec<&CurrentStateDeltaRef<'_>>>::new();
for delta in &sorted {
scope_members
.entry((delta.schema_key.to_owned(), None))
.or_default()
.push(delta);
scope_members
.entry((
delta.schema_key.to_owned(),
Some(certified_file_id.to_owned()),
))
.or_default()
.push(delta);
}
let scopes = scope_members
.keys()
.map(
|(schema_key, file_id)| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
)
.collect::<Vec<_>>();
let controls =
load_hot_collection_controls(self.store, branch_id, generation, &scopes).await?;
if scope_members
.keys()
.zip(&controls)
.any(|((_, file_id), control)| file_id.is_some() && control.live_count != 0)
{
return Ok(None);
}
for (((schema_key, file_id), members), mut control) in
scope_members.into_iter().zip(controls)
{
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: &schema_key,
file_id: file_id.as_deref(),
};
let increment = u64::try_from(members.len())
.map_err(|_| head_value_error("hot collection live count exceeds u64"))?;
let was_empty = control.live_count == 0;
let increment_digest = if was_empty {
let mut digest = CompleteHotCollectionDigest::new(branch_id, generation, scope);
let mut canonical_key = hot_scope_prefix(branch_id, generation);
let scope_prefix_len = canonical_key.len();
for delta in members {
canonical_key.truncate(scope_prefix_len);
write_key_string(&mut canonical_key, delta.schema_key, KEY_PART_FINAL);
write_file_id(&mut canonical_key, delta.file_id);
write_row_pk(&mut canonical_key, delta.row_pk);
digest.push_parts(delta.row_pk, delta.file_id, &canonical_key)?;
}
Some(digest.finish())
} else {
None
};
if control.live_count == DEFERRED_ROOT_LIVE_COUNT {
control.ordered_identity_digest = None;
} else {
control.live_count = control
.live_count
.checked_add(increment)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?;
control.ordered_identity_digest = if was_empty { increment_digest } else { None };
}
stage_hot_collection_control(self.writes, branch_id, generation, scope, control)?;
}
let mut manifest_key = hot_scope_prefix(branch_id, generation);
manifest_key.extend_from_slice(new_head.as_uuid().as_bytes());
if working_diff_capture_checkpoint_commit_id.is_some() {
coverage
.add_encoded_group_key(&manifest_key)
.ok_or_else(|| head_value_error("packed current-base diff count exceeds u64"))?;
}
self.writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(manifest_key)),
StorageValue {
bytes: packed_current_base_value(
working_diff_capture_checkpoint_commit_id,
Some(certified_file_id),
)?,
},
);
self.writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
Ok(Some(generation))
}
async fn stage_packed_insert_current_base_manifest(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
schema_increments: BTreeMap<&str, PackedCollectionIncrement>,
preloaded_controls: Option<Vec<HotCollectionControl>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
let exclusive_schema_key = (schema_increments.len() == 1).then(|| {
*schema_increments
.keys()
.next()
.expect("one schema increment")
});
let mut manifest_key = hot_scope_prefix(branch_id, generation);
manifest_key.reserve(16);
manifest_key.extend_from_slice(new_head.as_uuid().as_bytes());
if working_diff_capture_checkpoint_commit_id.is_some() {
coverage
.add_encoded_group_key(&manifest_key)
.ok_or_else(|| head_value_error("packed current-base diff count exceeds u64"))?;
}
let scopes = schema_increments
.keys()
.map(
|schema_key| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
)
.collect::<Vec<_>>();
let controls = match preloaded_controls {
Some(controls) if controls.len() == scopes.len() => controls,
Some(_) => {
return Err(head_value_error(
"packed current-base control certificate has the wrong scope count",
));
}
None => {
load_hot_collection_controls(self.store, branch_id, generation, &scopes).await?
}
};
for ((schema_key, increment), mut control) in schema_increments.into_iter().zip(controls) {
let was_empty = control.live_count == 0;
if control.live_count == DEFERRED_ROOT_LIVE_COUNT {
control.ordered_identity_digest = None;
} else {
control.live_count = control
.live_count
.checked_add(increment.live_count)
.ok_or_else(|| head_value_error("hot collection live count exceeds u64"))?;
control.ordered_identity_digest = if was_empty {
increment.ordered_identity_digest
} else {
None
};
}
stage_hot_collection_control(
self.writes,
branch_id,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: None,
},
control,
)?;
}
self.writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(manifest_key)),
StorageValue {
bytes: working_diff_capture_checkpoint_commit_id.map_or_else(
|| Bytes::from_static(&[0; 16]),
|checkpoint| Bytes::copy_from_slice(checkpoint.as_uuid().as_bytes()),
),
},
);
if let Some(schema_key) = exclusive_schema_key {
stage_packed_exclusive_schema_base_ref(
self.writes,
branch_id,
generation,
schema_key,
new_head,
);
}
self.writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(branch_id, generation))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
Ok(generation)
}
#[cfg(any(test, feature = "storage-benches"))]
pub(crate) async fn stage_commit(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[TrackedHeadDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
) -> Result<CommitId, LixError> {
let typed_rows = deltas
.iter()
.map(test_snapshot)
.collect::<Result<Vec<_>, _>>()?;
let deltas = deltas
.iter()
.zip(&typed_rows)
.map(|(delta, snapshot)| test_current_delta(delta, snapshot.as_deref()))
.collect::<Vec<_>>();
let mut coverage = WorkingDiffIndexCoverage::default();
let generation = self
.stage_current_state_with_working_diff(
branch_id,
parent_generation,
new_head,
&deltas,
absence_guards,
parent_rows,
None,
&mut coverage,
)
.await?;
#[cfg(test)]
stage_test_current_control(self.writes, branch_id, new_head, generation, None)?;
Ok(generation)
}
#[cfg(test)]
pub(crate) async fn stage_commit_with_working_diff(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[TrackedHeadDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
let typed_rows = deltas
.iter()
.map(test_snapshot)
.collect::<Result<Vec<_>, _>>()?;
let deltas = deltas
.iter()
.zip(&typed_rows)
.map(|(delta, snapshot)| test_current_delta(delta, snapshot.as_deref()))
.collect::<Vec<_>>();
self.stage_current_state_with_working_diff(
branch_id,
parent_generation,
new_head,
&deltas,
absence_guards,
parent_rows,
working_diff_capture_checkpoint_commit_id,
coverage,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn stage_current_state_with_working_diff(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
self.stage_current_state_with_working_diff_inner(
branch_id,
parent_generation,
new_head,
deltas,
&[],
absence_guards,
parent_rows,
working_diff_capture_checkpoint_commit_id,
coverage,
false,
None,
None,
WorkingDiffBaselineAction::Continue,
&BTreeMap::new(),
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn stage_current_state_with_certified_predecessors(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
durable_predecessors: &[CertifiedCurrentStatePredecessorRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
self.stage_current_state_with_working_diff_inner(
branch_id,
parent_generation,
new_head,
deltas,
durable_predecessors,
absence_guards,
parent_rows,
working_diff_capture_checkpoint_commit_id,
coverage,
false,
None,
None,
WorkingDiffBaselineAction::Continue,
&BTreeMap::new(),
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn stage_checkpoint_current_state(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
checkpoint_commit_id: CommitId,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
let generation = self
.stage_current_state_with_working_diff_inner(
branch_id,
Some(generation),
new_head,
deltas,
&[],
absence_guards,
None,
Some(checkpoint_commit_id),
coverage,
false,
None,
None,
WorkingDiffBaselineAction::Reset,
&BTreeMap::new(),
)
.await?;
stage_retire_packed_current_bases(self.store, self.writes, branch_id, generation).await?;
Ok(generation)
}
pub(crate) async fn stage_partial_checkpoint_current_state(
&mut self,
branch_id: &str,
generation: CommitId,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
previous_checkpoint_commit_id: CommitId,
checkpoint_commit_id: CommitId,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<CommitId, LixError> {
self.stage_current_state_with_working_diff_inner(
branch_id,
Some(generation),
new_head,
deltas,
&[],
&BTreeSet::new(),
None,
Some(checkpoint_commit_id),
coverage,
false,
None,
None,
WorkingDiffBaselineAction::Rebase {
previous_checkpoint_commit_id,
},
&BTreeMap::new(),
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn stage_validated_insert_current_state_with_working_diff(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
absence_guards: &[TrackedStateKeyRef<'_>],
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
validated_absent_file_id: Option<&str>,
) -> Result<CommitId, LixError> {
if parent_generation.is_none() {
let owned_guards = absence_guards
.iter()
.map(|guard| TrackedStateKey {
schema_key: guard.schema_key.to_string(),
file_id: guard.file_id.map(str::to_string),
row_pk: guard.row_pk.clone(),
})
.collect::<BTreeSet<_>>();
return self
.stage_current_state_with_working_diff_inner(
branch_id,
parent_generation,
new_head,
deltas,
&[],
&owned_guards,
parent_rows,
working_diff_capture_checkpoint_commit_id,
coverage,
true,
validated_absent_file_id,
None,
WorkingDiffBaselineAction::Continue,
&BTreeMap::new(),
)
.await;
}
let no_owned_guards = BTreeSet::new();
self.stage_current_state_with_working_diff_inner(
branch_id,
parent_generation,
new_head,
deltas,
&[],
&no_owned_guards,
parent_rows,
working_diff_capture_checkpoint_commit_id,
coverage,
true,
validated_absent_file_id,
Some(absence_guards),
WorkingDiffBaselineAction::Continue,
&BTreeMap::new(),
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn stage_current_state_with_working_diff_inner(
&mut self,
branch_id: &str,
parent_generation: Option<CommitId>,
new_head: CommitId,
deltas: &[CurrentStateDeltaRef<'_>],
durable_predecessors: &[CertifiedCurrentStatePredecessorRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
parent_rows: Option<Vec<MaterializedTrackedStateRow>>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
absence_guards_validated: bool,
validated_absent_file_id: Option<&str>,
borrowed_absence_guards: Option<&[TrackedStateKeyRef<'_>]>,
working_diff_baseline_action: WorkingDiffBaselineAction,
certified_live_increments: &BTreeMap<(String, Option<String>), u64>,
) -> Result<CommitId, LixError> {
if matches!(
working_diff_baseline_action,
WorkingDiffBaselineAction::Rebase { .. }
) && working_diff_capture_checkpoint_commit_id.is_none()
{
return Err(head_value_error(
"partial-checkpoint rebase requires an active checkpoint",
));
}
let reset_working_diff_baselines =
working_diff_baseline_action == WorkingDiffBaselineAction::Reset;
let rebase_working_diff_baselines = matches!(
working_diff_baseline_action,
WorkingDiffBaselineAction::Rebase { .. }
);
let predecessor_checkpoint_commit_id = match working_diff_baseline_action {
WorkingDiffBaselineAction::Rebase {
previous_checkpoint_commit_id,
} => Some(previous_checkpoint_commit_id),
WorkingDiffBaselineAction::Continue | WorkingDiffBaselineAction::Reset => {
working_diff_capture_checkpoint_commit_id
}
};
let generation = parent_generation.unwrap_or(new_head);
let sorted = {
let _span = tracing::debug_span!(
target: "lix_perf",
"lix.perf.materialization.hot.sort"
)
.entered();
let mut sorted = deltas.iter().collect::<Vec<_>>();
for delta in &sorted {
delta.validate()?;
}
let mut already_strictly_sorted = true;
for pair in sorted.windows(2) {
match compare_hot_deltas(pair[0], pair[1]) {
Ordering::Less => {}
Ordering::Equal => {
return Err(current_state_duplicate_delta_error(pair[1]));
}
Ordering::Greater => {
already_strictly_sorted = false;
break;
}
}
}
if !already_strictly_sorted {
sorted.sort_unstable_by(|left, right| compare_hot_deltas(left, right));
for pair in sorted.windows(2) {
if compare_hot_deltas(pair[0], pair[1]).is_eq() {
return Err(current_state_duplicate_delta_error(pair[1]));
}
}
}
sorted
};
let durable_previous_values = {
let mut predecessor_index = 0usize;
let mut aligned = Vec::with_capacity(sorted.len());
for delta in &sorted {
let predecessor = durable_predecessors.get(predecessor_index);
match predecessor
.map(|predecessor| compare_certified_predecessor_to_delta(predecessor, delta))
{
Some(Ordering::Less) => {
return Err(head_value_error(
"certified predecessor does not belong to a staged delta",
));
}
Some(Ordering::Equal) => {
let value = durable_predecessors[predecessor_index].value.view()?;
if value.untracked || value.deleted {
return Err(head_value_error(
"certified predecessor must be a live tracked row",
));
}
aligned.push(Some(durable_predecessors[predecessor_index].value.clone()));
predecessor_index += 1;
}
Some(Ordering::Greater) | None => aligned.push(None),
}
}
if predecessor_index != durable_predecessors.len() {
return Err(head_value_error(
"certified predecessor does not belong to a staged delta",
));
}
aligned
};
if parent_generation.is_none() {
if !durable_predecessors.is_empty() {
return Err(head_value_error(
"bootstrap publication cannot carry durable predecessors",
));
}
stage_hot_bootstrap(
self.writes,
branch_id,
generation,
parent_rows.unwrap_or_default(),
&sorted,
absence_guards,
working_diff_capture_checkpoint_commit_id,
coverage,
)?;
return Ok(generation);
}
let identities = {
let _span = tracing::debug_span!(
target: "lix_perf",
"lix.perf.materialization.hot.identities"
)
.entered();
encode_hot_mutation_identities(branch_id, generation, &sorted)
};
let loaded_previous_values = hot_load_primary_mutation_identity_refs(
self.store,
&identities,
&sorted,
&durable_previous_values,
absence_guards_validated,
validated_absent_file_id,
)
.instrument(tracing::debug_span!(
target: "lix_perf",
"lix.perf.materialization.hot.previous"
))
.await?;
let mut loaded_previous_values = loaded_previous_values.into_iter();
let mut previous_values = sorted
.iter()
.zip(durable_previous_values.iter())
.map(|(delta, durable_predecessor)| {
if hot_delta_is_guarded_by_absent_file(
delta,
absence_guards_validated,
validated_absent_file_id,
) {
None
} else if let Some(durable_predecessor) = durable_predecessor {
Some(durable_predecessor.clone())
} else {
loaded_previous_values
.next()
.expect("every unguarded hot delta has one loaded previous value")
}
})
.collect::<Vec<_>>();
let mut previous_from_packed = vec![false; previous_values.len()];
debug_assert_eq!(loaded_previous_values.len(), 0);
let packed_previous_indices = durable_previous_values
.iter()
.enumerate()
.filter_map(|(index, predecessor)| predecessor.is_none().then_some(index))
.collect::<Vec<_>>();
let packed_previous_keys = packed_previous_indices
.iter()
.map(|&index| {
let delta = sorted[index];
TrackedStateKeyRef {
schema_key: delta.schema_key,
row_pk: delta.row_pk,
file_id: delta.file_id,
}
})
.collect::<Vec<_>>();
let packed_previous_shadow = packed_previous_indices
.iter()
.map(|&index| {
Ok(previous_values[index]
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.and_then(|previous| previous.commit_id))
})
.collect::<Result<Vec<_>, LixError>>()?;
let packed_previous = Box::pin(load_packed_current_base_exact_entries(
self.store,
branch_id,
generation,
&packed_previous_keys,
&packed_previous_shadow,
None,
))
.await?;
for (index, packed_previous) in packed_previous_indices.iter().copied().zip(packed_previous)
{
let previous = &mut previous_values[index];
let Some((packed_value, _, base_coordinate, base_checkpoint_commit_id)) =
&packed_previous
else {
continue;
};
let packed_is_newer = match (
previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?,
Some(packed_value.commit_id),
) {
(None, Some(_)) => true,
(Some(previous), Some(packed)) => {
previous.commit_id.is_some_and(|previous| packed > previous)
}
_ => false,
};
if !packed_is_newer {
continue;
}
previous_from_packed[index] = true;
*previous = Some(CertifiedCurrentStatePredecessor::Packed(PackedHeadValue {
change_id: packed_value.change_id,
commit_id: packed_value.commit_id,
deleted: packed_value.deleted,
created_at: packed_value.created_at,
updated_at: packed_value.updated_at,
working_diff_baseline: packed_current_base_working_diff_baseline(
predecessor_checkpoint_commit_id,
*base_checkpoint_commit_id,
),
columnar_base_coordinate: base_coordinate.map(|coordinate| {
ColumnarBaseCoordinate {
base_commit_id: coordinate.base_commit_id,
group_index: coordinate.group_index,
row_index: coordinate.row_index,
}
}),
}));
}
let root_previous = if load_root_current_base_commit(self.store, branch_id, generation)
.await?
.is_some()
{
Box::pin(load_root_current_base_exact(
self.store,
branch_id,
generation,
predecessor_checkpoint_commit_id,
&packed_previous_keys,
ChangeRecordProjection::identity_only(),
None,
None,
))
.await?
} else {
MaterializedHotStateExactBatch::new(
MaterializedHotStateBatch::default(),
vec![None; packed_previous_keys.len()],
)?
};
for (index, candidate) in packed_previous_indices
.iter()
.copied()
.zip((0..packed_previous_keys.len()).map(|index| root_previous.row(index)))
{
let Some(candidate) = candidate else {
continue;
};
let candidate_is_newer = previous_values[index]
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.is_none_or(|previous| {
candidate.commit_id().is_some_and(|candidate| {
previous
.commit_id
.is_none_or(|previous| candidate > previous)
})
});
if candidate_is_newer {
previous_from_packed[index] = true;
previous_values[index] = candidate.durable_predecessor().cloned();
}
}
let mut collection_controls =
load_incremental_collection_controls(self.store, branch_id, generation, &sorted)
.await?;
let missing_certified_scopes = certified_live_increments
.keys()
.filter(|scope| !collection_controls.contains_key(*scope))
.map(
|(schema_key, file_id)| crate::collection_generation::CollectionScopeRef {
schema_key,
file_id: file_id.as_deref(),
},
)
.collect::<Vec<_>>();
let missing_certified_controls = load_hot_collection_controls(
self.store,
branch_id,
generation,
&missing_certified_scopes,
)
.await?;
collection_controls.extend(
missing_certified_scopes
.into_iter()
.zip(missing_certified_controls)
.map(|(scope, control)| {
(
(
scope.schema_key.to_owned(),
scope.file_id.map(str::to_owned),
),
control,
)
}),
);
apply_incremental_collection_generation_deltas(&mut collection_controls, &sorted)?;
let mut retired_predecessor = vec![false; previous_values.len()];
for (index, (delta, previous)) in sorted.iter().zip(&mut previous_values).enumerate() {
if delta.schema_key == crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY {
continue;
}
let belongs_to_retired_generation = previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.is_some_and(|value| {
!row_belongs_to_active_collection_generation(
&collection_controls,
generation,
delta.schema_key,
delta.file_id,
value.untracked,
value.commit_id,
)
});
if belongs_to_retired_generation {
*previous = None;
retired_predecessor[index] = true;
}
}
let mut canonical_created_ats: Vec<Option<LixTimestamp>> = Vec::new();
{
let mut unresolved = Vec::new();
let mut unresolved_slots = Vec::new();
for (index, (delta, previous)) in sorted.iter().zip(&previous_values).enumerate() {
if delta.deleted
|| previous.is_some()
|| (!delta.untracked && retired_predecessor[index])
{
continue;
}
unresolved.push(TrackedStateKeyRef {
schema_key: delta.schema_key,
file_id: delta.file_id,
row_pk: delta.row_pk,
});
unresolved_slots.push((index, delta.untracked));
}
#[cfg(any(test, feature = "storage-benches"))]
if !unresolved.is_empty() {
BROAD_CANONICAL_CREATED_AT_LOOKUPS
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
BROAD_CANONICAL_CREATED_AT_KEYS.fetch_add(
unresolved.len() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
if !unresolved.is_empty()
&& let Some(control) = BranchHeadControlContext::new()
.reader(self.store)
.load(branch_id)
.await?
&& crate::tracked_state::load_commit_state_authority_ids(
self.store,
std::slice::from_ref(&control.head_commit_id),
)
.await?
.into_iter()
.next()
.flatten()
.is_some()
{
let mut reader =
crate::tracked_state::TrackedStateContext::new().reader(self.store);
let canonical = reader
.load_projected_batch_at_commit_refs(
&control.head_commit_id.to_string(),
&unresolved,
&ChangeRecordProjection::identity_only(),
)
.await?;
canonical_created_ats = vec![None; sorted.len()];
for (slot, (index, untracked)) in unresolved_slots.iter().copied().enumerate() {
let Some(row) = canonical.row(slot) else {
continue;
};
if untracked {
let key = &unresolved[slot];
return Err(LixError::new(
LixError::CODE_UNIQUE,
format!(
"cannot insert untracked row in schema '{}': a tracked row with the same primary key exists in canonical history; retention is immutable for an identity",
key.schema_key,
),
));
}
canonical_created_ats[index] = Some(row.created_at());
#[cfg(any(test, feature = "storage-benches"))]
BROAD_CANONICAL_CREATED_AT_HITS
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
}
let mut created_ats = Vec::with_capacity(sorted.len());
for (index, (delta, previous)) in sorted.iter().zip(&previous_values).enumerate() {
let Some(previous) = previous else {
created_ats.push(
canonical_created_ats
.get(index)
.copied()
.flatten()
.unwrap_or(delta.created_at),
);
continue;
};
let existing = previous.view()?;
if let Some(borrowed_absence_guards) = borrowed_absence_guards {
reject_borrowed_guarded_live_member(borrowed_absence_guards, delta, existing)?;
} else {
reject_guarded_live_member(absence_guards, delta, existing)?;
}
reject_retention_change(delta, existing)?;
created_ats.push(if reset_working_diff_baselines && !delta.untracked {
delta.created_at
} else {
effective_hot_created_at(existing, working_diff_capture_checkpoint_commit_id)
});
}
let (sorted, previous_values, created_ats) = if reset_working_diff_baselines {
let mut retained_deltas = Vec::with_capacity(sorted.len());
let mut retained_previous = Vec::with_capacity(previous_values.len());
let mut retained_created_ats = Vec::with_capacity(created_ats.len());
for (((delta, previous), created_at), previous_from_packed) in sorted
.into_iter()
.zip(previous_values)
.zip(created_ats)
.zip(previous_from_packed)
{
let identical_immutable_change = !previous_from_packed
&& !delta.untracked
&& !delta.deleted
&& delta.schema_key
!= crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
&& previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.is_some_and(|value| {
value.change_id == delta.change_id
&& value.deleted == delta.deleted
&& value.created_at == delta.created_at
&& value.updated_at == delta.updated_at
&& value.working_diff_baseline == WorkingDiffBaseline::Clean
});
if identical_immutable_change {
continue;
}
retained_deltas.push(delta);
retained_previous.push(previous);
retained_created_ats.push(created_at);
}
(retained_deltas, retained_previous, retained_created_ats)
} else {
(sorted, previous_values, created_ats)
};
let compacted = if reset_working_diff_baselines {
Box::pin(hot_compaction_mask(
self.store,
branch_id,
generation,
&sorted,
None,
HotTombstoneMaskKind::Checkpoint,
self.transaction_global_schema_keys,
))
.await?
} else {
Vec::new()
};
let interval_local = match (
reset_working_diff_baselines,
working_diff_capture_checkpoint_commit_id,
) {
(false, Some(active_checkpoint_commit_id)) => {
let preconditions = hot_interval_local_preconditions(
&sorted,
&previous_values,
active_checkpoint_commit_id,
)?;
if preconditions.iter().any(|admitted| *admitted) {
Box::pin(hot_compaction_mask(
self.store,
branch_id,
generation,
&sorted,
Some(&preconditions),
HotTombstoneMaskKind::IntervalLocal,
self.transaction_global_schema_keys,
))
.await?
} else {
Vec::new()
}
}
_ => Vec::new(),
};
let identities = encode_hot_mutation_identities(branch_id, generation, &sorted);
let unmatched_guards = if absence_guards_validated || absence_guards.is_empty() {
BTreeSet::new()
} else {
let validated_delta_keys = sorted
.iter()
.map(|delta| TrackedStateKey {
schema_key: delta.schema_key.to_string(),
row_pk: delta.row_pk.clone(),
file_id: delta.file_id.map(str::to_string),
})
.collect::<BTreeSet<_>>();
absence_guards
.iter()
.filter(|key| !validated_delta_keys.contains(*key))
.cloned()
.collect::<BTreeSet<_>>()
};
reject_hot_absence_guards(self.store, branch_id, generation, &unmatched_guards).await?;
let mut root_rebase_baselines = vec![None; sorted.len()];
if rebase_working_diff_baselines
&& load_root_current_base_commit(self.store, branch_id, generation)
.await?
.is_some()
{
let keys = sorted
.iter()
.filter(|delta| !delta.untracked)
.map(|delta| TrackedStateKeyRef {
schema_key: delta.schema_key,
file_id: delta.file_id,
row_pk: delta.row_pk,
})
.collect::<Vec<_>>();
let mut reader = crate::tracked_state::TrackedStateContext::new().reader(self.store);
let before = reader
.load_projected_batch_at_commit_refs(
&predecessor_checkpoint_commit_id
.expect("rebase has a previous checkpoint")
.to_string(),
&keys,
&ChangeRecordProjection::identity_only(),
)
.await?;
let checkpoint_commit_id =
working_diff_capture_checkpoint_commit_id.expect("rebase has a new checkpoint");
let mut slot = 0;
for (index, delta) in sorted.iter().enumerate() {
if delta.untracked {
continue;
}
let row = before.row(slot);
slot += 1;
root_rebase_baselines[index] = Some(match row {
Some(row) => {
if Some(row.change_id()) == delta.change_id {
return Err(head_value_error(
"partial-checkpoint remainder is unchanged at its canonical checkpoint",
));
}
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version: WorkingDiffVersion {
change_id: row.change_id(),
commit_id: row.commit_id(),
deleted: row.deleted(),
created_at: row.created_at(),
updated_at: row.updated_at(),
snapshot: WorkingDiffSlotFingerprint::unresolved(),
metadata: WorkingDiffSlotFingerprint::unresolved(),
},
}
}
None if !delta.deleted => WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
},
None => {
return Err(head_value_error(
"partial-checkpoint remainder deletes a canonically absent row",
));
}
});
}
}
let mut next_coverage = *coverage;
let diff_scope = working_diff_capture_checkpoint_commit_id.map(|checkpoint_commit_id| {
encode_working_diff_scope_prefix(branch_id, checkpoint_commit_id, generation)
});
let diff_key_capacity = diff_scope.as_deref().map_or(0, |scope| {
sorted
.iter()
.try_fold(0_usize, |total, delta| {
total.checked_add(encoded_hot_identity_key_len(
scope.len(),
delta.schema_key,
delta.row_pk,
delta.file_id,
)?)
})
.unwrap_or(0)
});
let mut diff_key_bytes = Vec::with_capacity(diff_key_capacity);
let mut diff_puts = Vec::with_capacity(diff_scope.as_ref().map_or(0, |_| sorted.len()));
let next_value_capacity = sorted
.iter()
.zip(&previous_values)
.try_fold(0_usize, |total, (delta, previous)| {
let inherited_coordinate = previous.as_ref().is_some_and(|previous| {
previous
.view()
.expect("HOT predecessor was validated before capacity planning")
.columnar_base_coordinate
.is_some()
});
checked_add_hot_next_value_capacity(
total,
delta,
working_diff_capture_checkpoint_commit_id.is_some(),
inherited_coordinate,
)
})
.unwrap_or(0);
let mut next_value_ranges = Vec::with_capacity(sorted.len());
let mut next_value_bytes = Vec::with_capacity(next_value_capacity);
{
let _span = tracing::debug_span!(
target: "lix_perf",
"lix.perf.materialization.hot.values"
)
.entered();
for (index, (delta, (created_at, previous))) in sorted
.iter()
.zip(created_ats.iter().zip(&previous_values))
.enumerate()
{
let (working_diff_baseline, newly_dirty) = if reset_working_diff_baselines
&& !delta.untracked
{
(WorkingDiffBaseline::Clean, false)
} else if let Some(baseline) = root_rebase_baselines[index] {
(baseline, true)
} else if rebase_working_diff_baselines && !delta.untracked {
let previous = previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.ok_or_else(|| {
head_value_error("partial-checkpoint remainder has no HOT before-image")
})?;
rebase_hot_working_diff_baseline(
working_diff_capture_checkpoint_commit_id
.expect("partial-checkpoint rebase requires a checkpoint"),
previous,
)?
} else if working_diff_capture_checkpoint_commit_id.is_some() && !delta.untracked {
let previous = previous
.as_ref()
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?;
next_hot_working_diff_baseline(
working_diff_capture_checkpoint_commit_id,
delta,
previous,
)?
} else {
(WorkingDiffBaseline::Disabled, false)
};
if newly_dirty {
let key = append_hot_diff_key_parts(
&mut diff_key_bytes,
diff_scope
.as_deref()
.expect("a newly dirty hot row requires an active checkpoint"),
delta.schema_key,
delta.row_pk,
delta.file_id,
);
next_coverage
.add_encoded_group_key(&diff_key_bytes[key.clone()])
.ok_or_else(|| {
head_value_error("hot working-diff index count exceeds u64")
})?;
diff_puts.push(EncodedPut {
key: buffer_range(&key),
value: BufferRange::default(),
});
}
let interval_local_row = interval_local.get(index).copied().unwrap_or(false);
if interval_local_row
&& (newly_dirty
|| !matches!(
working_diff_baseline,
WorkingDiffBaseline::BeforeAbsent { .. }
))
{
return Err(head_value_error(
"interval-local hot tombstone is not provably net-absent at elision",
));
}
let compacted_row = compacted.get(index).copied().unwrap_or(false);
if compacted_row
&& (working_diff_baseline != WorkingDiffBaseline::Clean || newly_dirty)
{
return Err(head_value_error(
"compacted hot tombstone is not provably clean at removal",
));
}
next_value_ranges.push(if delta.physically_deletes() || compacted_row {
None
} else {
let mut value = delta.value_ref(*created_at, working_diff_baseline);
value.columnar_base_coordinate = next_columnar_base_coordinate(
reset_working_diff_baselines,
delta,
previous.as_ref(),
)?;
Some(append_head_value(&mut next_value_bytes, &value)?)
});
}
}
let next_value_bytes = Bytes::from(next_value_bytes);
stage_incremental_collection_controls(
self.writes,
branch_id,
generation,
&sorted,
&previous_values,
collection_controls,
certified_live_increments,
)?;
if sorted
.iter()
.any(|delta| scope_requires_exact_closure(branch_id, delta.schema_key, delta.file_id))
{
let staged = sorted
.iter()
.zip(&next_value_ranges)
.filter(|(delta, _)| {
scope_requires_exact_closure(branch_id, delta.schema_key, delta.file_id)
})
.map(|(delta, range)| {
(
HeadRowIdentity {
schema_key: delta.schema_key.to_owned(),
row_pk: delta.row_pk.clone(),
file_id: delta.file_id.map(str::to_owned),
},
range
.as_ref()
.map(|range| next_value_bytes.slice(range.clone())),
)
})
.collect::<BTreeMap<_, _>>();
Box::pin(restage_exact_closure_collection_control(
self.store,
self.writes,
branch_id,
generation,
&staged,
))
.await?;
}
async {
stage_hot_diff_batch(
self.writes,
diff_scope.as_deref().unwrap_or_default(),
diff_key_bytes,
diff_puts,
)?;
stage_hot_mutation_batch(self.writes, identities, next_value_bytes, next_value_ranges);
stage_incremental_file_delete_cascades(
self.store,
self.writes,
branch_id,
generation,
&sorted,
working_diff_capture_checkpoint_commit_id,
reset_working_diff_baselines,
&mut next_coverage,
)
.await
}
.instrument(tracing::debug_span!(
target: "lix_perf",
"lix.perf.materialization.hot.stage"
))
.await?;
*coverage = next_coverage;
Ok(generation)
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn stage_complete_current_state_with_working_diff(
&mut self,
branch_id: &str,
generation: CommitId,
parent_tracked: HotTrackedSnapshot,
preserved_untracked_generation: Option<CommitId>,
tracked_deltas: &[CurrentStateDeltaRef<'_>],
untracked_deltas: &[CurrentStateDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
working_diff_mode: CompleteWorkingDiffMode,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(HotTrackedSnapshot, BTreeSet<String>), LixError> {
let mut rows = parent_tracked.rows;
let mut untracked_rows = match preserved_untracked_generation {
Some(previous_generation) => {
load_hot_untracked_generation(self.store, branch_id, previous_generation).await?
}
None => BTreeMap::new(),
};
let sorted_untracked = sorted_lifecycle_hot_deltas(untracked_deltas, true)?;
let sorted_tracked = sorted_lifecycle_hot_deltas(tracked_deltas, false)?;
reject_lifecycle_retention_collisions(&sorted_untracked, &sorted_tracked)?;
for delta in &sorted_untracked {
apply_complete_hot_snapshot_delta(&mut untracked_rows, delta, absence_guards)?;
}
merge_final_untracked_rows(&mut rows, untracked_rows)?;
for delta in &sorted_tracked {
apply_complete_hot_snapshot_delta(&mut rows, delta, absence_guards)?;
}
match working_diff_mode {
CompleteWorkingDiffMode::Disabled => {
normalize_complete_hot_snapshot_baselines(&mut rows, WorkingDiffBaseline::Disabled)?
}
CompleteWorkingDiffMode::ResetClean => {
normalize_complete_hot_snapshot_baselines(&mut rows, WorkingDiffBaseline::Clean)?
}
CompleteWorkingDiffMode::Rebase {
checkpoint_commit_id,
checkpoint,
} => rebase_complete_hot_snapshot_working_diff(
self.writes,
branch_id,
generation,
checkpoint_commit_id,
&mut rows,
checkpoint.rows,
coverage,
)?,
}
let mut final_tracked = BTreeMap::new();
let mut schema_keys = BTreeSet::new();
for (identity, bytes) in &rows {
schema_keys.insert(identity.schema_key.clone());
if !decode_head_value(bytes.as_ref())?.untracked {
final_tracked.insert(identity.clone(), bytes.clone());
}
}
stage_complete_collection_controls(self.writes, branch_id, generation, &rows)?;
stage_complete_hot_rows(self.writes, branch_id, generation, rows);
Ok((
HotTrackedSnapshot {
rows: final_tracked,
},
schema_keys,
))
}
}
#[allow(clippy::too_many_arguments)]
fn rebase_complete_hot_snapshot_working_diff(
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
checkpoint_commit_id: CommitId,
rows: &mut HotRowMap,
checkpoint_rows: HotRowMap,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(), LixError> {
*coverage = WorkingDiffIndexCoverage::default();
let scope = encode_working_diff_scope_prefix(branch_id, checkpoint_commit_id, generation);
let mut diff_key_bytes = Vec::new();
let mut diff_puts = Vec::new();
for (identity, bytes) in rows.iter_mut() {
let after = decode_head_value(bytes.as_ref())?;
if after.untracked {
continue;
}
let before = checkpoint_rows
.get(identity)
.map(|bytes| decode_head_value(bytes.as_ref()))
.transpose()?;
let before_version = before.and_then(HeadValueView::working_diff_version);
let after_version = after
.working_diff_version()
.ok_or_else(|| head_value_error("complete tracked row has no working-diff version"))?;
let (baseline, dirty) = match before_version {
Some(before) if before.change_id == after_version.change_id => {
(WorkingDiffBaseline::Clean, false)
}
Some(before) => (
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version: before,
},
true,
),
None => (
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
},
!after.deleted,
),
};
*bytes = Bytes::from(reencode_head_value_with_baseline(after, baseline)?);
if dirty {
let key = append_hot_diff_key_parts(
&mut diff_key_bytes,
&scope,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
coverage
.add_encoded_group_key(&diff_key_bytes[key.clone()])
.ok_or_else(|| head_value_error("hot working-diff index count exceeds u64"))?;
diff_puts.push(EncodedPut {
key: buffer_range(&key),
value: BufferRange::default(),
});
}
}
stage_hot_diff_batch(writes, &scope, diff_key_bytes, diff_puts)
}
#[allow(clippy::too_many_arguments)]
async fn stage_incremental_file_delete_cascades(
store: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
deltas: &[&CurrentStateDeltaRef<'_>],
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
reset_working_diff_baselines: bool,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(), LixError> {
let mut cascades = BTreeMap::<String, &CurrentStateDeltaRef<'_>>::new();
for cascade in deltas {
let Some(file_id) = file_delete_cascade_id(cascade)? else {
continue;
};
cascades.insert(file_id, cascade);
}
if cascades.is_empty() {
return Ok(());
}
#[cfg(test)]
INCREMENTAL_CASCADE_EXPLICIT_INDEX_BUILDS.with(|builds| {
builds.set(builds.get().saturating_add(1));
});
let explicit = deltas
.iter()
.map(|delta| HeadRowIdentity {
schema_key: delta.schema_key.to_string(),
row_pk: delta.row_pk.clone(),
file_id: delta.file_id.map(str::to_string),
})
.collect::<BTreeSet<_>>();
let (identities, values) = Box::pin(hot_load_file_scope_predecessors(
store,
branch_id,
generation,
working_diff_capture_checkpoint_commit_id,
&cascades,
))
.await?;
let scope = hot_scope_prefix(branch_id, generation);
let key_capacity = identities
.iter()
.try_fold(0_usize, |total, identity| {
let key_len = encoded_hot_identity_key_len(
scope.len(),
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)?;
total.checked_add(key_len)
})
.unwrap_or(0);
let mut mutations = HotCascadeMutationBuffers::with_capacity(
identities.len(),
key_capacity,
working_diff_capture_checkpoint_commit_id.is_some(),
);
let diff_scope = working_diff_capture_checkpoint_commit_id.map(|checkpoint_commit_id| {
encode_working_diff_scope_prefix(branch_id, checkpoint_commit_id, generation)
});
let diff_key_capacity = diff_scope.as_deref().map_or(0, |scope| {
identities
.iter()
.try_fold(0_usize, |total, identity| {
total.checked_add(encoded_hot_identity_key_len(
scope.len(),
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)?)
})
.unwrap_or(0)
});
let mut diff_key_bytes = Vec::with_capacity(diff_key_capacity);
let mut diff_puts = Vec::with_capacity(diff_scope.as_ref().map_or(0, |_| identities.len()));
for (identity, previous) in identities.into_iter().zip(values) {
let row_identity = identity.clone().into_row_identity();
if explicit.contains(&row_identity) {
continue;
}
let cascade = cascades
.get(
identity
.file_id
.as_deref()
.expect("file-backed identity requires file id"),
)
.expect("file scan only returns requested cascade ids");
let existing = previous.view()?;
if cascade.untracked != existing.untracked || existing.deleted {
continue;
}
let row_start = mutations.key_bytes.len();
mutations.key_bytes.extend_from_slice(&scope);
write_key_string(
&mut mutations.key_bytes,
&identity.schema_key,
KEY_PART_FINAL,
);
write_file_id(&mut mutations.key_bytes, identity.file_id.as_deref());
write_row_pk(&mut mutations.key_bytes, &identity.row_pk);
let row_key = BufferRange::new(row_start, mutations.key_bytes.len() - row_start);
if existing.untracked {
mutations.row_deletes.push(row_key);
continue;
}
let (baseline, newly_dirty) = if reset_working_diff_baselines {
(WorkingDiffBaseline::Clean, false)
} else {
next_cascade_working_diff_baseline(working_diff_capture_checkpoint_commit_id, existing)?
};
if newly_dirty {
let key = append_hot_diff_key_parts(
&mut diff_key_bytes,
diff_scope
.as_deref()
.expect("new cascade dirty row requires active checkpoint"),
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
coverage
.add_encoded_group_key(&diff_key_bytes[key.clone()])
.ok_or_else(|| head_value_error("hot working-diff index count exceeds u64"))?;
diff_puts.push(EncodedPut {
key: buffer_range(&key),
value: BufferRange::default(),
});
}
let value = append_head_value(
&mut mutations.value_bytes,
&HeadValueRef {
change_id: cascade.change_id,
commit_id: cascade.commit_id,
untracked: false,
deleted: true,
created_at: existing.created_at,
updated_at: cascade.updated_at,
snapshot: None,
metadata: None,
columnar_base_coordinate: existing.columnar_base_coordinate,
working_diff_baseline: baseline,
},
)?;
let value = buffer_range(&value);
mutations.row_puts.push(EncodedPut {
key: row_key,
value,
});
}
stage_hot_diff_batch(
writes,
diff_scope.as_deref().unwrap_or_default(),
diff_key_bytes,
diff_puts,
)?;
if !mutations.row_puts.is_empty() || !mutations.row_deletes.is_empty() {
stage_hot_encoded_mutation_ranges(
writes,
Bytes::from(mutations.key_bytes),
Bytes::from(mutations.value_bytes),
mutations.row_puts,
mutations.row_deletes,
Vec::new(),
);
}
Ok(())
}
struct HotCascadeMutationBuffers {
key_bytes: Vec<u8>,
value_bytes: Vec<u8>,
row_puts: Vec<EncodedPut>,
row_deletes: Vec<BufferRange>,
}
impl HotCascadeMutationBuffers {
fn with_capacity(row_capacity: usize, key_capacity: usize, active_checkpoint: bool) -> Self {
let checkpoint_bytes = if active_checkpoint {
WORKING_DIFF_CHECKPOINT_BYTES + WORKING_DIFF_VERSION_BYTES
} else {
0
};
let value_bytes_per_row = HEAD_VALUE_TYPED_HEADER_BYTES
.checked_add(checkpoint_bytes)
.and_then(|bytes| bytes.checked_add(COLUMNAR_BASE_COORDINATE_BYTES));
let value_capacity = value_bytes_per_row
.and_then(|value_bytes| row_capacity.checked_mul(value_bytes))
.unwrap_or(0);
Self {
key_bytes: Vec::with_capacity(key_capacity),
value_bytes: Vec::with_capacity(value_capacity),
row_puts: Vec::with_capacity(row_capacity),
row_deletes: Vec::with_capacity(row_capacity),
}
}
}
#[cfg(test)]
std::thread_local! {
static INCREMENTAL_CASCADE_EXPLICIT_INDEX_BUILDS: std::cell::Cell<usize> =
const { std::cell::Cell::new(0) };
}
#[cfg(test)]
fn incremental_cascade_explicit_index_builds() -> usize {
INCREMENTAL_CASCADE_EXPLICIT_INDEX_BUILDS.with(std::cell::Cell::get)
}
fn next_cascade_working_diff_baseline(
active_checkpoint_commit_id: Option<CommitId>,
previous: HeadValueView<'_>,
) -> Result<(WorkingDiffBaseline, bool), LixError> {
let Some(active_checkpoint_commit_id) = active_checkpoint_commit_id else {
return Ok((WorkingDiffBaseline::Disabled, false));
};
match previous.working_diff_baseline {
WorkingDiffBaseline::Clean => {
let before = previous
.working_diff_version()
.ok_or_else(|| head_value_error("tracked cascade member has no version"))?;
Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: active_checkpoint_commit_id,
version: before,
},
true,
))
}
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
} if checkpoint_commit_id == active_checkpoint_commit_id => Ok((
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
},
false,
)),
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version,
} if checkpoint_commit_id == active_checkpoint_commit_id => Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version,
},
false,
)),
WorkingDiffBaseline::BeforeAbsent { .. } | WorkingDiffBaseline::BeforePresent { .. } => {
let mut before = previous
.working_diff_version()
.ok_or_else(|| head_value_error("tracked cascade member has no version"))?;
before.created_at =
effective_hot_created_at(previous, Some(active_checkpoint_commit_id));
before.commit_id = active_checkpoint_commit_id;
Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: active_checkpoint_commit_id,
version: before,
},
true,
))
}
WorkingDiffBaseline::Disabled => Err(head_value_error(
"active checkpoint generation contains a cascade member without a baseline",
)),
}
}
fn next_hot_working_diff_baseline(
active_checkpoint_commit_id: Option<CommitId>,
delta: &CurrentStateDeltaRef<'_>,
previous: Option<HeadValueView<'_>>,
) -> Result<(WorkingDiffBaseline, bool), LixError> {
let Some(active_checkpoint_commit_id) = active_checkpoint_commit_id else {
return Ok((WorkingDiffBaseline::Disabled, false));
};
if delta.untracked {
return Ok((WorkingDiffBaseline::Disabled, false));
}
let Some(previous) = previous else {
return Ok((
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id: active_checkpoint_commit_id,
},
true,
));
};
if previous.untracked {
return Err(head_value_error(
"tracked mutation has an untracked primary before image",
));
}
match previous.working_diff_baseline {
WorkingDiffBaseline::Clean => {
let before = previous
.working_diff_version()
.ok_or_else(|| head_value_error("tracked mutation has no working-diff version"))?;
Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: active_checkpoint_commit_id,
version: before,
},
true,
))
}
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
} if checkpoint_commit_id == active_checkpoint_commit_id => Ok((
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
},
false,
)),
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version,
} if checkpoint_commit_id == active_checkpoint_commit_id => Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version,
},
false,
)),
WorkingDiffBaseline::BeforeAbsent { .. } | WorkingDiffBaseline::BeforePresent { .. } => {
let mut before = previous
.working_diff_version()
.ok_or_else(|| head_value_error("tracked mutation has no working-diff version"))?;
before.created_at =
effective_hot_created_at(previous, Some(active_checkpoint_commit_id));
before.commit_id = active_checkpoint_commit_id;
Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: active_checkpoint_commit_id,
version: before,
},
true,
))
}
WorkingDiffBaseline::Disabled => Err(head_value_error(
"active checkpoint generation contains a tracked row without a baseline",
)),
}
}
fn rebase_hot_working_diff_baseline(
checkpoint_commit_id: CommitId,
previous: HeadValueView<'_>,
) -> Result<(WorkingDiffBaseline, bool), LixError> {
match previous.working_diff_baseline {
WorkingDiffBaseline::BeforeAbsent { .. } => Ok((
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
},
true,
)),
WorkingDiffBaseline::BeforePresent { version, .. } => Ok((
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id,
version,
},
true,
)),
WorkingDiffBaseline::Clean | WorkingDiffBaseline::Disabled => Err(head_value_error(
"partial-checkpoint remainder is not dirty in the previous epoch",
)),
}
}
fn next_columnar_base_coordinate(
reset_working_diff_baselines: bool,
delta: &CurrentStateDeltaRef<'_>,
previous: Option<&CertifiedCurrentStatePredecessor>,
) -> Result<Option<ColumnarBaseCoordinate>, LixError> {
if reset_working_diff_baselines {
return Ok(None);
}
Ok(delta.columnar_base_coordinate.or(previous
.map(CertifiedCurrentStatePredecessor::view)
.transpose()?
.and_then(|value| value.columnar_base_coordinate)))
}
async fn load_hot_untracked_generation(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
) -> Result<HotRowMap, LixError> {
let filter = TrackedStateFilter {
include_tombstones: true,
..TrackedStateFilter::default()
};
let HotScanEntries::Decoded(entries) =
hot_scan_entries(store, branch_id, generation, &filter, None, None)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget")
else {
unreachable!("an unconstrained HOT scan cannot select the finite point-read route");
};
let mut rows = BTreeMap::new();
for (identity, bytes) in entries {
let value = decode_head_value(bytes.as_ref())?;
if !value.untracked {
continue;
}
if value.deleted {
return Err(head_value_error(
"untracked hot row must be physically removed rather than tombstoned",
));
}
match rows.entry(identity.into_row_identity()) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(bytes);
}
std::collections::btree_map::Entry::Occupied(entry) => {
let identity = entry.key();
return Err(LixError::new(
LixError::CODE_UNIQUE,
format!(
"hot generation contains duplicate untracked identity in schema '{}' row_pk {:?}",
identity.schema_key, identity.row_pk
),
));
}
}
}
Ok(rows)
}
fn merge_final_untracked_rows(
rows: &mut HotRowMap,
untracked_rows: HotRowMap,
) -> Result<(), LixError> {
for (identity, bytes) in untracked_rows {
match rows.entry(identity) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(bytes);
}
std::collections::btree_map::Entry::Occupied(entry) => {
let identity = entry.key();
return Err(LixError::new(
LixError::CODE_UNIQUE,
format!(
"cannot materialize tracked and untracked hot rows with the same identity in schema '{}' row_pk {:?}",
identity.schema_key, identity.row_pk
),
));
}
}
}
Ok(())
}
fn sorted_lifecycle_hot_deltas<'a>(
deltas: &'a [CurrentStateDeltaRef<'a>],
expect_untracked: bool,
) -> Result<Vec<&'a CurrentStateDeltaRef<'a>>, LixError> {
let mut sorted = Vec::with_capacity(deltas.len());
for delta in deltas {
delta.validate()?;
if delta.untracked != expect_untracked {
return Err(head_value_error(if expect_untracked {
"untracked lifecycle delta was marked tracked"
} else {
"tracked lifecycle delta was marked untracked"
}));
}
sorted.push(delta);
}
sorted.sort_unstable_by(|left, right| compare_hot_deltas(left, right));
for pair in sorted.windows(2) {
if compare_hot_deltas(pair[0], pair[1]).is_eq() {
return Err(current_state_duplicate_delta_error(pair[1]));
}
}
Ok(sorted)
}
fn reject_lifecycle_retention_collisions(
untracked: &[&CurrentStateDeltaRef<'_>],
tracked: &[&CurrentStateDeltaRef<'_>],
) -> Result<(), LixError> {
let mut untracked_index = 0;
let mut tracked_index = 0;
while untracked_index < untracked.len() && tracked_index < tracked.len() {
match compare_hot_deltas(untracked[untracked_index], tracked[tracked_index]) {
Ordering::Less => untracked_index += 1,
Ordering::Greater => tracked_index += 1,
Ordering::Equal => {
if !untracked[untracked_index].physically_deletes() {
return Err(current_state_duplicate_delta_error(tracked[tracked_index]));
}
untracked_index += 1;
tracked_index += 1;
}
}
}
Ok(())
}
fn apply_complete_hot_snapshot_delta(
rows: &mut HotRowMap,
delta: &CurrentStateDeltaRef<'_>,
absence_guards: &BTreeSet<TrackedStateKey>,
) -> Result<(), LixError> {
apply_complete_file_delete_cascade(rows, delta)?;
let identity = HeadRowIdentity {
schema_key: delta.schema_key.to_string(),
row_pk: delta.row_pk.clone(),
file_id: delta.file_id.map(str::to_string),
};
let previous = rows.get(&identity).map(|bytes| bytes.as_ref());
if let Some(previous) = previous {
let existing = decode_head_value(previous)?;
reject_guarded_live_member(absence_guards, delta, existing)?;
reject_retention_change(delta, existing)?;
}
if delta.physically_deletes() {
rows.remove(&identity);
} else {
let created_at = previous
.map(decode_head_value)
.transpose()?
.map_or(delta.created_at, |value| value.created_at);
rows.insert(
identity,
Bytes::from(encode_head_value(&{
let mut value = delta.value_ref(created_at, WorkingDiffBaseline::Disabled);
value.columnar_base_coordinate = None;
value
})?),
);
}
Ok(())
}
fn apply_complete_file_delete_cascade(
rows: &mut HotRowMap,
delta: &CurrentStateDeltaRef<'_>,
) -> Result<(), LixError> {
let Some(file_id) = file_delete_cascade_id(delta)? else {
return Ok(());
};
let identities = rows
.keys()
.filter(|identity| identity.file_id.as_deref() == Some(file_id.as_str()))
.cloned()
.collect::<Vec<_>>();
for identity in identities {
let Some(previous) = rows.get(&identity) else {
continue;
};
let existing = decode_head_value(previous.as_ref())?;
if delta.untracked != existing.untracked || existing.deleted {
continue;
}
if existing.untracked {
rows.remove(&identity);
continue;
}
rows.insert(
identity,
Bytes::from(encode_head_value(&HeadValueRef {
change_id: delta.change_id,
commit_id: delta.commit_id,
untracked: false,
deleted: true,
created_at: existing.created_at,
updated_at: delta.updated_at,
snapshot: None,
metadata: None,
columnar_base_coordinate: existing.columnar_base_coordinate,
working_diff_baseline: WorkingDiffBaseline::Disabled,
})?),
);
}
Ok(())
}
fn file_delete_cascade_id(delta: &CurrentStateDeltaRef<'_>) -> Result<Option<String>, LixError> {
if delta.schema_key != FILE_DESCRIPTOR_SCHEMA_KEY || !delta.deleted {
return Ok(None);
}
delta
.row_pk
.as_single_string_owned()
.map(Some)
.map_err(|error| {
head_value_error(&format!(
"file descriptor tombstone has invalid identity: {error}"
))
})
}
fn normalize_complete_hot_snapshot_baselines(
rows: &mut HotRowMap,
tracked_baseline: WorkingDiffBaseline,
) -> Result<(), LixError> {
for bytes in rows.values_mut() {
let value = decode_head_value(bytes.as_ref())?;
if value.untracked {
continue;
}
*bytes = Bytes::from(reencode_head_value_with_baseline(value, tracked_baseline)?);
}
Ok(())
}
fn compare_hot_deltas(
left: &CurrentStateDeltaRef<'_>,
right: &CurrentStateDeltaRef<'_>,
) -> Ordering {
left.schema_key
.cmp(right.schema_key)
.then_with(|| left.row_pk.cmp(right.row_pk))
.then_with(|| left.file_id.cmp(&right.file_id))
}
fn compare_certified_predecessor_to_delta(
predecessor: &CertifiedCurrentStatePredecessorRef<'_>,
delta: &CurrentStateDeltaRef<'_>,
) -> Ordering {
predecessor
.schema_key
.cmp(delta.schema_key)
.then_with(|| predecessor.row_pk.cmp(delta.row_pk))
.then_with(|| predecessor.file_id.cmp(&delta.file_id))
}
fn hot_identity(
branch_id: &str,
generation: CommitId,
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> HeadIdentity {
HeadIdentity {
branch_id: branch_id.to_string(),
generation,
schema_key: schema_key.to_string(),
row_pk: row_pk.clone(),
file_id: file_id.map(str::to_string),
}
}
struct EncodedHotMutationIdentities {
key_bytes: Bytes,
key_ranges: Vec<EncodedHotMutationIdentityRanges>,
}
#[derive(Clone, Copy)]
struct EncodedHotMutationIdentityRanges {
row_key: BufferRange,
file_schema_key: Option<BufferRange>,
}
fn encode_hot_mutation_identities(
branch_id: &str,
generation: CommitId,
deltas: &[&CurrentStateDeltaRef<'_>],
) -> EncodedHotMutationIdentities {
let scope = hot_scope_prefix(branch_id, generation);
let encoded_capacity = encoded_hot_mutation_identity_capacity(scope.len(), deltas).unwrap_or(0);
let mut encoded = Vec::with_capacity(encoded_capacity);
let mut key_ranges = Vec::with_capacity(deltas.len());
for delta in deltas {
key_ranges.push(append_hot_mutation_identity(&mut encoded, &scope, delta));
}
EncodedHotMutationIdentities {
key_bytes: Bytes::from(encoded),
key_ranges,
}
}
fn encoded_hot_mutation_identity_capacity(
scope_len: usize,
deltas: &[&CurrentStateDeltaRef<'_>],
) -> Option<usize> {
deltas.iter().try_fold(0_usize, |total, delta| {
let key_len =
encoded_hot_identity_key_len(scope_len, delta.schema_key, delta.row_pk, delta.file_id)?;
let marker_len = if delta.file_id.is_some() {
scope_len.checked_add(encoded_key_bytes_len(delta.schema_key.as_bytes())?)?
} else {
0
};
total.checked_add(key_len)?.checked_add(marker_len)
})
}
fn append_hot_mutation_identity(
encoded: &mut Vec<u8>,
scope: &[u8],
delta: &CurrentStateDeltaRef<'_>,
) -> EncodedHotMutationIdentityRanges {
let row_start = encoded.len();
encoded.extend_from_slice(scope);
write_key_string(encoded, delta.schema_key, KEY_PART_FINAL);
write_file_id(encoded, delta.file_id);
write_row_pk(encoded, delta.row_pk);
let row_key = BufferRange::new(row_start, encoded.len() - row_start);
let file_schema_key = delta.file_id.map(|_| {
let marker_start = encoded.len();
encoded.extend_from_slice(scope);
write_key_string(encoded, delta.schema_key, KEY_PART_FINAL);
BufferRange::new(marker_start, encoded.len() - marker_start)
});
EncodedHotMutationIdentityRanges {
row_key,
file_schema_key,
}
}
async fn hot_load_primary_mutation_identity_refs(
store: &(impl StorageAdapterRead + ?Sized),
identities: &EncodedHotMutationIdentities,
deltas: &[&CurrentStateDeltaRef<'_>],
durable_predecessors: &[Option<CertifiedCurrentStatePredecessor>],
absence_guards_validated: bool,
validated_absent_file_id: Option<&str>,
) -> Result<Vec<Option<CertifiedCurrentStatePredecessor>>, LixError> {
assert_eq!(
identities.key_ranges.len(),
deltas.len(),
"every hot mutation identity must have one source delta"
);
assert_eq!(
durable_predecessors.len(),
deltas.len(),
"every hot mutation identity must have one predecessor slot"
);
let read_count = deltas
.iter()
.zip(durable_predecessors)
.filter(|(delta, durable_predecessor)| {
durable_predecessor.is_none()
&& !hot_delta_is_guarded_by_absent_file(
delta,
absence_guards_validated,
validated_absent_file_id,
)
})
.count();
if read_count == 0 {
return Ok(Vec::new());
}
if read_count == deltas.len()
&& let Some(values) = hot_scan_dense_mutation_identity_range(store, identities).await?
{
return Ok(values
.into_iter()
.map(|value| value.map(CertifiedCurrentStatePredecessor::Encoded))
.collect());
}
let mut keys = Vec::with_capacity(read_count);
for ((identity, delta), durable_predecessor) in identities
.key_ranges
.iter()
.zip(deltas)
.zip(durable_predecessors)
{
if durable_predecessor.is_some()
|| hot_delta_is_guarded_by_absent_file(
delta,
absence_guards_validated,
validated_absent_file_id,
)
{
continue;
}
let start = identity.row_key.offset();
keys.push(StorageKey(
identities
.key_bytes
.slice(start..start + identity.row_key.len()),
));
}
PointReadPlan::new(ROW_SPACE, &keys)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.map(|value| {
value
.map(full_value_bytes)
.transpose()
.map(|value| value.map(CertifiedCurrentStatePredecessor::Encoded))
})
.collect()
}
async fn hot_scan_dense_mutation_identity_range(
store: &(impl StorageAdapterRead + ?Sized),
identities: &EncodedHotMutationIdentities,
) -> Result<Option<Vec<Option<Bytes>>>, LixError> {
hot_scan_dense_encoded_key_range(store, identities.key_ranges.len(), |index| {
let range = identities.key_ranges[index].row_key;
let start = range.offset();
&identities.key_bytes[start..start.saturating_add(range.len())]
})
.await
}
fn hot_delta_is_guarded_by_absent_file(
delta: &CurrentStateDeltaRef<'_>,
absence_guards_validated: bool,
validated_absent_file_id: Option<&str>,
) -> bool {
absence_guards_validated
&& validated_absent_file_id.is_some_and(|file_id| delta.file_id == Some(file_id))
}
fn checked_add_hot_next_value_capacity(
total: usize,
delta: &CurrentStateDeltaRef<'_>,
active_checkpoint: bool,
inherited_coordinate: bool,
) -> Option<usize> {
if delta.physically_deletes() {
return Some(total);
}
let (snapshot_len, metadata_len) = if delta.deleted {
(0, 0)
} else {
(
delta.snapshot.map_or(0, <[u8]>::len),
delta
.metadata
.map(lix_schema::Jsonb::binary_len)
.transpose()
.ok()?
.unwrap_or(0),
)
};
u32::try_from(snapshot_len).ok()?;
u32::try_from(metadata_len).ok()?;
let baseline_len = if active_checkpoint && !delta.untracked {
WORKING_DIFF_CHECKPOINT_BYTES + WORKING_DIFF_VERSION_BYTES
} else {
0
};
let encoded_len = HEAD_VALUE_TYPED_HEADER_BYTES
.checked_add(snapshot_len)?
.checked_add(metadata_len)?
.checked_add(baseline_len)?
.checked_add(
(delta.columnar_base_coordinate.is_some() || inherited_coordinate)
.then_some(COLUMNAR_BASE_COORDINATE_BYTES)
.unwrap_or(0),
)?;
total.checked_add(encoded_len)
}
async fn hot_generation_has_any_base(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
) -> Result<bool, LixError> {
if !packed_current_base_refs(store, branch_id, generation)
.await?
.is_empty()
{
return Ok(true);
}
if load_root_current_base_commit(store, branch_id, generation)
.await?
.is_some()
{
return Ok(true);
}
if hot_space_prefix_has_entry(
store,
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
Bytes::from(hot_scope_prefix(branch_id, generation)),
)
.await?
{
return Ok(true);
}
Ok(false)
}
async fn hot_space_prefix_has_entry(
store: &(impl StorageAdapterRead + ?Sized),
space: StorageSpace,
prefix: Bytes,
) -> Result<bool, LixError> {
let range = StoragePrefix { bytes: prefix }.to_range()?;
let mut cursor = store
.begin_scan(
space,
range,
StorageBeginScanOptions {
projection: StorageCoreProjection::KeyOnly,
..StorageBeginScanOptions::default()
},
)
.await?;
let (page, _has_more) = cursor.next_page(1).await?.into_parts();
Ok(!page.is_empty())
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum HotTombstoneMaskKind {
Checkpoint,
IntervalLocal,
}
fn hot_interval_local_preconditions(
deltas: &[&CurrentStateDeltaRef<'_>],
previous_values: &[Option<CertifiedCurrentStatePredecessor>],
active_checkpoint_commit_id: CommitId,
) -> Result<Vec<bool>, LixError> {
let mut preconditions = vec![false; deltas.len()];
for (index, (delta, previous)) in deltas.iter().zip(previous_values).enumerate() {
if !delta.deleted || delta.untracked {
continue;
}
let Some(previous) = previous else {
continue;
};
let previous = previous.view()?;
if previous.untracked {
continue;
}
preconditions[index] = matches!(
previous.working_diff_baseline,
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id,
} if checkpoint_commit_id == active_checkpoint_commit_id
);
}
Ok(preconditions)
}
async fn hot_compaction_mask(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
deltas: &[&CurrentStateDeltaRef<'_>],
preconditions: Option<&[bool]>,
kind: HotTombstoneMaskKind,
transaction_global_schema_keys: Option<&BTreeSet<String>>,
) -> Result<Vec<bool>, LixError> {
let mask = vec![false; deltas.len()];
#[cfg(any(test, feature = "storage-benches"))]
{
let (routes, offered) = match kind {
HotTombstoneMaskKind::Checkpoint => {
(&COMPACTED_TOMBSTONE_ROUTES, &COMPACTED_TOMBSTONE_OFFERED)
}
HotTombstoneMaskKind::IntervalLocal => (
&INTERVAL_LOCAL_TOMBSTONE_ROUTES,
&INTERVAL_LOCAL_TOMBSTONE_OFFERED,
),
};
routes.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
offered.fetch_add(deltas.len() as u64, std::sync::atomic::Ordering::Relaxed);
}
let is_candidate = |index: usize, delta: &CurrentStateDeltaRef<'_>| {
delta.deleted
&& !delta.untracked
&& delta.schema_key != crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
&& !scope_requires_exact_closure(branch_id, delta.schema_key, delta.file_id)
&& preconditions.is_none_or(|pre| pre.get(index).copied().unwrap_or(false))
};
let candidates = deltas
.iter()
.enumerate()
.filter(|(index, delta)| is_candidate(*index, delta))
.count();
if candidates == 0 {
return Ok(mask);
}
#[cfg(any(test, feature = "storage-benches"))]
match kind {
HotTombstoneMaskKind::Checkpoint => &COMPACTED_TOMBSTONE_CANDIDATES,
HotTombstoneMaskKind::IntervalLocal => &INTERVAL_LOCAL_TOMBSTONE_CANDIDATES,
}
.fetch_add(candidates as u64, std::sync::atomic::Ordering::Relaxed);
if hot_generation_has_any_base(store, branch_id, generation).await? {
return Ok(mask);
}
let global_is_below = branch_id != crate::GLOBAL_BRANCH_ID;
if global_is_below && transaction_global_schema_keys.is_none() {
return Ok(mask);
}
let global_control = if global_is_below {
BranchHeadControlContext::new()
.reader(store)
.load(crate::GLOBAL_BRANCH_ID)
.await?
} else {
None
};
let reader = HotStateStoreReader {
store,
transaction_cache: None,
root_base_cache: None,
};
let mut mask = mask;
let mut compacted = 0_u64;
let mut verdicts: BTreeMap<&str, bool> = BTreeMap::new();
for (index, delta) in deltas.iter().enumerate() {
if !is_candidate(index, delta) {
continue;
}
let blocked = if !global_is_below {
false
} else if let Some(&verdict) = verdicts.get(delta.schema_key) {
verdict
} else {
let verdict = transaction_global_schema_keys
.is_some_and(|schema_keys| schema_keys.contains(delta.schema_key))
|| match global_control {
Some(control) => {
reader
.has_schema_rows(crate::GLOBAL_BRANCH_ID, control, delta.schema_key)
.await?
}
None => false,
};
verdicts.insert(delta.schema_key, verdict);
verdict
};
if !blocked {
mask[index] = true;
compacted += 1;
}
}
#[cfg(any(test, feature = "storage-benches"))]
match kind {
HotTombstoneMaskKind::Checkpoint => &COMPACTED_TOMBSTONE_COMPACTED,
HotTombstoneMaskKind::IntervalLocal => &INTERVAL_LOCAL_TOMBSTONE_ELIDED,
}
.fetch_add(compacted, std::sync::atomic::Ordering::Relaxed);
let _ = compacted;
let _ = kind;
Ok(mask)
}
fn stage_hot_mutation_batch(
writes: &mut StorageWriteSet,
identities: EncodedHotMutationIdentities,
value_bytes: Bytes,
value_ranges: Vec<Option<Range<usize>>>,
) {
assert_eq!(
identities.key_ranges.len(),
value_ranges.len(),
"every hot mutation identity must have one staged value"
);
let put_count = value_ranges.iter().flatten().count();
let delete_count = value_ranges.len() - put_count;
let file_count = identities
.key_ranges
.iter()
.filter(|identity| identity.file_schema_key.is_some())
.count();
let mut row_puts = Vec::with_capacity(put_count);
let mut row_deletes = Vec::with_capacity(delete_count);
let mut file_schema_puts = Vec::with_capacity(file_count);
for (identity, value) in identities.key_ranges.iter().zip(&value_ranges) {
if let Some(value) = value {
let value = buffer_range(value);
row_puts.push(EncodedPut {
key: identity.row_key,
value,
});
} else {
row_deletes.push(identity.row_key);
}
if let Some(key) = identity.file_schema_key {
file_schema_puts.push(key);
}
}
file_schema_puts.sort_unstable_by(|left, right| {
let left = &identities.key_bytes[left.offset()..left.offset().saturating_add(left.len())];
let right =
&identities.key_bytes[right.offset()..right.offset().saturating_add(right.len())];
left.cmp(right)
});
file_schema_puts.dedup_by(|left, right| {
identities.key_bytes[left.offset()..left.offset().saturating_add(left.len())]
== identities.key_bytes[right.offset()..right.offset().saturating_add(right.len())]
});
stage_hot_encoded_mutation_ranges(
writes,
identities.key_bytes,
value_bytes,
row_puts,
row_deletes,
file_schema_puts,
);
}
fn stage_hot_encoded_mutation_ranges(
writes: &mut StorageWriteSet,
key_bytes: Bytes,
value_bytes: Bytes,
row_puts: Vec<EncodedPut>,
row_deletes: Vec<BufferRange>,
mut file_schema_puts: Vec<BufferRange>,
) {
let row_batch = EncodedMutationBatch::try_new(
key_bytes.clone(),
value_bytes.clone(),
row_puts,
row_deletes,
)
.expect("hot row ranges originate in the supplied encoded buffers");
writes.stage_encoded_batch(ROW_SPACE, row_batch);
file_schema_puts.retain(|key| {
!writes.contains_put(
FILE_SPACE,
&key_bytes[key.offset()..key.offset().saturating_add(key.len())],
)
});
if !file_schema_puts.is_empty() {
let file_puts = file_schema_puts
.into_iter()
.map(|key| EncodedPut {
key,
value: BufferRange::new(0, 0),
})
.collect();
let file_batch =
EncodedMutationBatch::try_new(key_bytes, Bytes::new(), file_puts, Vec::new())
.expect("hot file schema ranges originate in the supplied encoded buffers");
writes.stage_encoded_batch(FILE_SPACE, file_batch);
}
}
fn stage_hot_diff_batch(
writes: &mut StorageWriteSet,
scope: &[u8],
identity_key_bytes: Vec<u8>,
identity_puts: Vec<EncodedPut>,
) -> Result<(), LixError> {
if identity_puts.is_empty() {
return Ok(());
}
if scope.is_empty() {
return Err(head_value_error(
"hot diff identities require a checkpoint scope",
));
}
if identity_puts.len() < HOT_DIFF_PACK_MIN_IDENTITIES {
let batch = EncodedMutationBatch::try_new(
Bytes::from(identity_key_bytes),
Bytes::new(),
identity_puts,
Vec::new(),
)
.expect("direct hot diff ranges originate in the supplied encoded buffer");
writes.stage_encoded_batch(DIFF_SPACE, batch);
return Ok(());
}
let value_capacity = identity_key_bytes
.len()
.saturating_sub(scope.len().saturating_mul(identity_puts.len()))
.saturating_add(identity_puts.len().saturating_mul(4))
.saturating_add(
identity_puts
.len()
.div_ceil(HOT_DIFF_SEGMENT_MAX_IDENTITIES as usize)
.saturating_mul(HOT_DIFF_SEGMENT_HEADER_BYTES),
);
let mut value_bytes = Vec::with_capacity(value_capacity);
let mut value_ranges = Vec::<Range<usize>>::new();
let mut segment_start = value_bytes.len();
value_bytes.push(HOT_DIFF_SEGMENT_VERSION);
value_bytes.extend_from_slice(&0_u32.to_le_bytes());
let mut segment_count = 0_u32;
for put in identity_puts {
let key_start = put.key.offset();
let key_end = key_start
.checked_add(put.key.len())
.ok_or_else(|| head_value_error("hot diff key range overflow"))?;
let full_key = identity_key_bytes
.get(key_start..key_end)
.ok_or_else(|| head_value_error("hot diff key range escapes its arena"))?;
let suffix = full_key
.strip_prefix(scope)
.ok_or_else(|| head_value_error("hot diff identity key is outside its scope"))?;
let suffix_len = u32::try_from(suffix.len())
.map_err(|_| head_value_error("hot diff identity suffix exceeds u32"))?;
let encoded_len = 4_usize.saturating_add(suffix.len());
if HOT_DIFF_SEGMENT_HEADER_BYTES.saturating_add(encoded_len) > HOT_DIFF_SEGMENT_MAX_BYTES {
return Err(head_value_error(
"hot diff identity exceeds the segment size limit",
));
}
let current_len = value_bytes.len() - segment_start;
if segment_count > 0
&& (segment_count == HOT_DIFF_SEGMENT_MAX_IDENTITIES
|| current_len.saturating_add(encoded_len) > HOT_DIFF_SEGMENT_MAX_BYTES)
{
value_bytes[segment_start + 1..segment_start + HOT_DIFF_SEGMENT_HEADER_BYTES]
.copy_from_slice(&segment_count.to_le_bytes());
value_ranges.push(segment_start..value_bytes.len());
segment_start = value_bytes.len();
value_bytes.push(HOT_DIFF_SEGMENT_VERSION);
value_bytes.extend_from_slice(&0_u32.to_le_bytes());
segment_count = 0;
}
value_bytes.extend_from_slice(&suffix_len.to_le_bytes());
value_bytes.extend_from_slice(suffix);
segment_count += 1;
}
value_bytes[segment_start + 1..segment_start + HOT_DIFF_SEGMENT_HEADER_BYTES]
.copy_from_slice(&segment_count.to_le_bytes());
value_ranges.push(segment_start..value_bytes.len());
let mut key_bytes = Vec::with_capacity(value_ranges.len().saturating_mul(scope.len() + 32));
let mut puts = Vec::with_capacity(value_ranges.len());
for value in value_ranges {
let key_start = key_bytes.len();
key_bytes.extend_from_slice(scope);
key_bytes.extend_from_slice(blake3::hash(&value_bytes[value.clone()]).as_bytes());
puts.push(EncodedPut {
key: BufferRange::new(key_start, key_bytes.len() - key_start),
value: buffer_range(&value),
});
}
let batch = EncodedMutationBatch::try_new(
Bytes::from(key_bytes),
Bytes::from(value_bytes),
puts,
Vec::new(),
)
.expect("hot diff segment ranges originate in the supplied encoded buffers");
writes.stage_encoded_batch(DIFF_SPACE, batch);
Ok(())
}
fn buffer_range(range: &Range<usize>) -> BufferRange {
BufferRange::new(range.start, range.end - range.start)
}
fn stage_complete_hot_rows(
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
rows: HotRowMap,
) {
if rows.is_empty() {
return;
}
let scope = hot_scope_prefix(branch_id, generation);
let file_schema_keys = rows
.keys()
.filter(|identity| identity.file_id.is_some())
.map(|identity| identity.schema_key.clone())
.collect::<BTreeSet<_>>();
let value_capacity = rows.values().map(Bytes::len).sum();
let marker_key_capacity = file_schema_keys
.iter()
.map(|schema_key| {
scope
.len()
.saturating_add(encoded_key_bytes_len(schema_key.as_bytes()).unwrap_or(0))
})
.sum::<usize>();
let key_capacity = rows
.len()
.saturating_mul(scope.len() + 32)
.saturating_add(
rows.keys()
.map(|identity| {
identity
.schema_key
.len()
.saturating_add(identity.row_pk.estimated_heap_bytes())
.saturating_add(
identity
.file_id
.as_ref()
.map_or(0, |file_id| file_id.len().saturating_mul(2)),
)
})
.sum(),
)
.saturating_add(marker_key_capacity);
let mut key_bytes = Vec::with_capacity(key_capacity);
let mut value_bytes = Vec::with_capacity(value_capacity);
let mut row_puts = Vec::with_capacity(rows.len());
let mut file_puts = Vec::with_capacity(file_schema_keys.len());
for (identity, value) in rows {
let value_start = value_bytes.len();
value_bytes.extend_from_slice(value.as_ref());
let value = BufferRange::new(value_start, value_bytes.len() - value_start);
let row_start = key_bytes.len();
key_bytes.extend_from_slice(&scope);
write_key_string(&mut key_bytes, &identity.schema_key, KEY_PART_FINAL);
write_file_id(&mut key_bytes, identity.file_id.as_deref());
write_row_pk(&mut key_bytes, &identity.row_pk);
row_puts.push(EncodedPut {
key: BufferRange::new(row_start, key_bytes.len() - row_start),
value,
});
}
for schema_key in file_schema_keys {
let file_start = key_bytes.len();
key_bytes.extend_from_slice(&scope);
write_key_string(&mut key_bytes, &schema_key, KEY_PART_FINAL);
file_puts.push(EncodedPut {
key: BufferRange::new(file_start, key_bytes.len() - file_start),
value: BufferRange::new(0, 0),
});
}
let key_bytes = Bytes::from(key_bytes);
let value_bytes = Bytes::from(value_bytes);
let row_batch =
EncodedMutationBatch::try_new(key_bytes.clone(), value_bytes.clone(), row_puts, Vec::new())
.expect("complete hot row ranges originate in the supplied encoded buffers");
writes.stage_encoded_batch(ROW_SPACE, row_batch);
file_puts.retain(|put| {
!writes.contains_put(
FILE_SPACE,
&key_bytes[put.key.offset()..put.key.offset().saturating_add(put.key.len())],
)
});
if !file_puts.is_empty() {
let file_batch =
EncodedMutationBatch::try_new(key_bytes, Bytes::new(), file_puts, Vec::new())
.expect("complete hot file ranges originate in the supplied encoded buffers");
writes.stage_encoded_batch(FILE_SPACE, file_batch);
}
}
#[cfg(test)]
pub(super) fn stage_test_hot_value(
writes: &mut StorageWriteSet,
identity: &HeadIdentity,
value: &HeadValue,
) -> Result<(), LixError> {
let rows = BTreeMap::from([(
HeadRowIdentity {
schema_key: identity.schema_key.clone(),
row_pk: identity.row_pk.clone(),
file_id: identity.file_id.clone(),
},
Bytes::from(encode_head_value(&value.as_ref())?),
)]);
stage_complete_hot_rows(writes, &identity.branch_id, identity.generation, rows);
Ok(())
}
fn stage_hot_bootstrap(
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
parent_rows: Vec<MaterializedTrackedStateRow>,
deltas: &[&CurrentStateDeltaRef<'_>],
absence_guards: &BTreeSet<TrackedStateKey>,
working_diff_capture_checkpoint_commit_id: Option<CommitId>,
coverage: &mut WorkingDiffIndexCoverage,
) -> Result<(), LixError> {
let mut rows = HotRowMap::new();
let tracked_baseline = if working_diff_capture_checkpoint_commit_id.is_some() {
WorkingDiffBaseline::Clean
} else {
WorkingDiffBaseline::Disabled
};
for row in parent_rows {
let snapshot = if row.deleted {
None
} else {
let typed = row.decoded_snapshot.as_ref().ok_or_else(|| {
head_value_error("live bootstrap row is missing its native typed payload")
})?;
Some(
typed
.durable_payload()
.map_err(|error| {
head_value_error(format!(
"cannot retain bootstrap typed payload: {error:?}"
))
})?
.to_vec(),
)
};
let key = TrackedStateKey {
schema_key: row.schema_key.clone(),
row_pk: row.row_pk.clone(),
file_id: row.file_id.clone(),
};
if absence_guards.contains(&key) && !row.deleted {
return Err(tracked_head_duplicate_insert_error(&key));
}
let identity = HeadRowIdentity {
schema_key: row.schema_key,
row_pk: row.row_pk,
file_id: row.file_id,
};
let metadata = row
.metadata
.as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|error| {
head_value_error(&format!("bootstrap metadata is invalid JSON: {error}"))
})?
.map(lix_schema::Jsonb::from_value);
let value = HeadValueRef {
change_id: Some(row.change_id),
commit_id: Some(row.commit_id),
untracked: false,
deleted: row.deleted,
created_at: LixTimestamp::expect_parse("hot bootstrap created_at", &row.created_at),
updated_at: LixTimestamp::expect_parse("hot bootstrap updated_at", &row.updated_at),
snapshot: snapshot.as_deref(),
metadata: metadata.as_ref(),
columnar_base_coordinate: None,
working_diff_baseline: tracked_baseline,
};
if rows
.insert(identity, Bytes::from(encode_head_value(&value)?))
.is_some()
{
return Err(LixError::new(
LixError::CODE_INTERNAL_ERROR,
"hot bootstrap contains duplicate tracked row identity",
));
}
}
for delta in deltas {
apply_complete_file_delete_cascade(&mut rows, delta)?;
let identity = HeadRowIdentity {
schema_key: delta.schema_key.to_string(),
row_pk: delta.row_pk.clone(),
file_id: delta.file_id.map(str::to_string),
};
let previous = rows.get(&identity).map(|bytes| bytes.as_ref());
if let Some(previous) = previous {
let existing = decode_head_value(previous)?;
reject_guarded_live_member(absence_guards, delta, existing)?;
reject_retention_change(delta, existing)?;
}
if delta.physically_deletes() {
rows.remove(&identity);
} else {
let created_at = previous
.map(decode_head_value)
.transpose()?
.map_or(delta.created_at, |value| value.created_at);
rows.insert(
identity,
Bytes::from(encode_head_value(&{
let mut value = delta.value_ref(
created_at,
if delta.untracked {
WorkingDiffBaseline::Disabled
} else {
tracked_baseline
},
);
value.columnar_base_coordinate = None;
value
})?),
);
}
}
stage_complete_collection_controls(writes, branch_id, generation, &rows)?;
stage_complete_hot_rows(writes, branch_id, generation, rows);
*coverage = WorkingDiffIndexCoverage::default();
Ok(())
}
async fn reject_hot_absence_guards(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
guards: &BTreeSet<TrackedStateKey>,
) -> Result<(), LixError> {
if guards.is_empty() {
return Ok(());
}
let identities = guards
.iter()
.map(|key| {
hot_identity(
branch_id,
generation,
&key.schema_key,
&key.row_pk,
key.file_id.as_deref(),
)
})
.collect::<Vec<_>>();
for (identity, value) in identities
.iter()
.zip(hot_load_primary_identity_bytes(store, &identities).await?)
{
let Some(value) = value else {
continue;
};
let value = decode_head_value(&value)?;
if !value.deleted {
return Err(tracked_head_duplicate_insert_error(&TrackedStateKey {
schema_key: identity.schema_key.clone(),
row_pk: identity.row_pk.clone(),
file_id: identity.file_id.clone(),
}));
}
}
Ok(())
}
#[derive(Clone, Copy)]
struct EncodedHotPointKeyRanges {
primary: BufferRange,
}
struct EncodedHotPointKeys {
bytes: Bytes,
ranges: Vec<EncodedHotPointKeyRanges>,
}
impl EncodedHotPointKeys {
fn primary_key(&self, index: usize) -> StorageKey {
self.key_for_range(self.ranges[index].primary)
}
fn primary_key_bytes(&self, index: usize) -> &[u8] {
let range = self.ranges[index].primary;
&self.bytes[range.offset()..range.offset() + range.len()]
}
fn key_for_range(&self, range: BufferRange) -> StorageKey {
let start = range.offset();
StorageKey(self.bytes.slice(start..start + range.len()))
}
}
fn encode_hot_point_keys(
branch_id: &str,
generation: CommitId,
keys: &[TrackedStateKeyRef<'_>],
) -> EncodedHotPointKeys {
encode_hot_point_keys_with(branch_id, generation, keys.len(), |index| keys[index])
}
fn encode_hot_point_keys_with<'a>(
branch_id: &str,
generation: CommitId,
key_count: usize,
mut key_at: impl FnMut(usize) -> TrackedStateKeyRef<'a>,
) -> EncodedHotPointKeys {
let scope = hot_scope_prefix(branch_id, generation);
let planned_capacity = (0..key_count).try_fold(0_usize, |total, index| {
let key = key_at(index);
let primary_len =
encoded_hot_identity_key_len(scope.len(), key.schema_key, key.row_pk, key.file_id)?;
total.checked_add(primary_len)
});
let capacity = planned_capacity.unwrap_or(0);
let mut bytes = Vec::with_capacity(capacity);
let mut ranges = Vec::with_capacity(key_count);
for index in 0..key_count {
let key = key_at(index);
let primary_start = bytes.len();
bytes.extend_from_slice(&scope);
write_key_string(&mut bytes, key.schema_key, KEY_PART_FINAL);
write_file_id(&mut bytes, key.file_id);
write_row_pk(&mut bytes, key.row_pk);
let primary = BufferRange::new(primary_start, bytes.len() - primary_start);
ranges.push(EncodedHotPointKeyRanges { primary });
}
debug_assert!(planned_capacity.is_none() || bytes.len() == capacity);
EncodedHotPointKeys {
bytes: Bytes::from(bytes),
ranges,
}
}
#[derive(Clone, Copy)]
struct FiniteHotIdentityRef<'a> {
row_pk: &'a RowPk,
file_id: Option<&'a str>,
}
struct FiniteHotIdentityBatchRef<'a> {
branch_id: &'a str,
generation: CommitId,
schema_key: &'a str,
identities: Vec<FiniteHotIdentityRef<'a>>,
encoded: EncodedHotPointKeys,
}
impl<'a> FiniteHotIdentityBatchRef<'a> {
fn new(
branch_id: &'a str,
generation: CommitId,
schema_key: &'a str,
mut row_pks: Vec<&'a RowPk>,
mut file_ids: Vec<Option<&'a str>>,
) -> Option<Self> {
row_pks.sort_unstable();
row_pks.dedup();
file_ids.sort_unstable();
file_ids.dedup();
let identity_count = row_pks.len().checked_mul(file_ids.len())?;
let mut identities = Vec::with_capacity(identity_count);
for row_pk in row_pks {
for &file_id in &file_ids {
identities.push(FiniteHotIdentityRef { row_pk, file_id });
}
}
let encoded =
encode_hot_point_keys_with(branch_id, generation, identities.len(), |index| {
TrackedStateKeyRef {
schema_key,
row_pk: identities[index].row_pk,
file_id: identities[index].file_id,
}
});
Some(Self {
branch_id,
generation,
schema_key,
identities,
encoded,
})
}
fn len(&self) -> usize {
self.identities.len()
}
fn key_ref(&self, index: usize) -> TrackedStateKeyRef<'a> {
let identity = self.identities[index];
TrackedStateKeyRef {
schema_key: self.schema_key,
row_pk: identity.row_pk,
file_id: identity.file_id,
}
}
}
struct FiniteHotEntryBatchRef<'a> {
identities: FiniteHotIdentityBatchRef<'a>,
values: Vec<Option<Bytes>>,
}
#[derive(Debug)]
struct HotScanIdentity {
key: Bytes,
schema_key: HotScanString,
row_pk: RowPk,
file_id: Option<HotScanString>,
}
#[derive(Debug)]
enum HotScanString {
Borrowed(Range<u32>),
Owned(String),
}
impl HotScanString {
fn as_str<'a>(&'a self, key: &'a Bytes) -> &'a str {
match self {
Self::Borrowed(range) => {
let range = range.start as usize..range.end as usize;
unsafe { std::str::from_utf8_unchecked(&key[range]) }
}
Self::Owned(value) => value,
}
}
fn into_shared_str(self, key: &Bytes) -> SharedStr {
match self {
Self::Borrowed(range) => {
let range = range.start as usize..range.end as usize;
let value = {
unsafe { std::str::from_utf8_unchecked(&key[range]) }
};
#[cfg(feature = "storage-benches")]
{
crate::storage_bench::record_hot_scan_key_handle_clone();
}
SharedStr::from_utf8_slice(key.clone(), value)
.expect("decoded key string remains inside its retained key")
}
Self::Owned(value) => SharedStr::from(value),
}
}
fn into_string(self, key: &Bytes) -> String {
match self {
Self::Borrowed(range) => {
let range = range.start as usize..range.end as usize;
unsafe { std::str::from_utf8_unchecked(&key[range]) }.to_owned()
}
Self::Owned(value) => value,
}
}
#[cfg(test)]
fn owns_fallback_buffer(&self) -> bool {
matches!(self, Self::Owned(_))
}
}
impl HotScanIdentity {
fn schema_key(&self) -> &str {
self.schema_key.as_str(&self.key)
}
fn file_id(&self) -> Option<&str> {
self.file_id
.as_ref()
.map(|file_id| file_id.as_str(&self.key))
}
fn matches_filter(&self, filter: &TrackedStateFilter) -> bool {
(filter.schema_keys.is_empty()
|| filter
.schema_keys
.iter()
.any(|schema_key| schema_key == self.schema_key()))
&& filter.matches_row_pk(&self.row_pk)
&& (filter.file_ids.is_empty()
|| filter.file_ids.iter().any(|filter| match filter {
NullableKeyFilter::Any => true,
NullableKeyFilter::Null => self.file_id().is_none(),
NullableKeyFilter::Value(value) => self.file_id() == Some(value.as_str()),
}))
}
fn into_row_identity(self) -> HeadRowIdentity {
let Self {
key,
schema_key,
row_pk,
file_id,
} = self;
HeadRowIdentity {
schema_key: schema_key.into_string(&key),
row_pk,
file_id: file_id.map(|file_id| file_id.into_string(&key)),
}
}
#[cfg(test)]
fn owned_metadata_buffer_count(&self) -> usize {
usize::from(self.schema_key.owns_fallback_buffer())
+ usize::from(
self.file_id
.as_ref()
.is_some_and(HotScanString::owns_fallback_buffer),
)
}
}
impl PartialEq for HotScanIdentity {
fn eq(&self, other: &Self) -> bool {
self.schema_key() == other.schema_key()
&& self.row_pk == other.row_pk
&& self.file_id() == other.file_id()
}
}
impl Eq for HotScanIdentity {}
impl PartialOrd for HotScanIdentity {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HotScanIdentity {
fn cmp(&self, other: &Self) -> Ordering {
self.schema_key()
.cmp(other.schema_key())
.then_with(|| self.row_pk.cmp(&other.row_pk))
.then_with(|| self.file_id().cmp(&other.file_id()))
}
}
impl LiveMaterializationIdentity for HotScanIdentity {
fn schema_key(&self) -> &str {
HotScanIdentity::schema_key(self)
}
fn row_pk(&self) -> &RowPk {
&self.row_pk
}
fn push_materialized(
self,
rows: &mut MaterializedHotStateBatchBuilder,
snapshot_content: Option<SharedStr>,
metadata: Option<SharedStr>,
deleted: bool,
created_at: LixTimestamp,
updated_at: LixTimestamp,
global: bool,
change_id: Option<ChangeId>,
commit_id: Option<CommitId>,
untracked: bool,
branch_id: &str,
) {
let Self {
key,
schema_key,
row_pk,
file_id,
} = self;
let schema_key = schema_key.as_str(&key);
let file_id = file_id.as_ref().map(|file_id| file_id.as_str(&key));
rows.push_materialized_interned(
row_pk,
schema_key,
file_id,
snapshot_content,
metadata,
deleted,
created_at,
updated_at,
global,
change_id,
commit_id,
untracked,
branch_id,
);
}
}
enum HotScanEntries<'a> {
Finite(Vec<FiniteHotEntryBatchRef<'a>>),
Decoded(Vec<(HotScanIdentity, Bytes)>),
}
fn filter_hot_scan_entries_by_collection_generation(
entries: &mut HotScanEntries<'_>,
control: HotCollectionControl,
) -> Result<(), LixError> {
let visible = |bytes: &Bytes| -> Result<bool, LixError> {
let value = decode_head_value(bytes)?;
Ok(survives_collection_generation_fence(
value.untracked,
value.commit_id,
control.active_generation,
false,
))
};
match entries {
HotScanEntries::Decoded(rows) => {
let mut retained = Vec::with_capacity(rows.len());
for (identity, bytes) in rows.drain(..) {
if visible(&bytes)? {
retained.push((identity, bytes));
}
}
*rows = retained;
}
HotScanEntries::Finite(batches) => {
for batch in batches {
for value in &mut batch.values {
if value
.as_ref()
.map(&visible)
.transpose()?
.is_some_and(|visible| !visible)
{
*value = None;
}
}
}
}
}
Ok(())
}
fn hot_exact_identity_batches<'a>(
branch_id: &'a str,
generation: CommitId,
filter: &'a TrackedStateFilter,
) -> Option<Vec<FiniteHotIdentityBatchRef<'a>>> {
if filter.schema_keys.is_empty() || filter.row_pks.is_empty() {
return None;
}
let mut schema_keys = filter
.schema_keys
.iter()
.map(String::as_str)
.collect::<Vec<_>>();
schema_keys.sort_unstable();
schema_keys.dedup();
let row_pks = filter
.row_pks
.iter()
.filter(|row_pk| filter.matches_row_pk(row_pk))
.collect::<Vec<_>>();
let file_ids = if filter.file_ids.is_empty() {
vec![None]
} else {
filter
.file_ids
.iter()
.map(|file_id| match file_id {
NullableKeyFilter::Null => Some(None),
NullableKeyFilter::Value(value) => Some(Some(value.as_str())),
NullableKeyFilter::Any => None,
})
.collect::<Option<Vec<_>>>()?
};
schema_keys
.into_iter()
.map(|schema_key| {
FiniteHotIdentityBatchRef::new(
branch_id,
generation,
schema_key,
row_pks.clone(),
file_ids.clone(),
)
})
.collect()
}
async fn hot_load_finite_identity_bytes(
store: &(impl StorageAdapterRead + ?Sized),
batch: &FiniteHotIdentityBatchRef<'_>,
) -> Result<Vec<Option<Bytes>>, LixError> {
if batch.identities.is_empty() {
return Ok(Vec::new());
}
let keys = (0..batch.len())
.map(|index| batch.encoded.primary_key(index))
.collect::<Vec<_>>();
PointReadPlan::new(ROW_SPACE, &keys)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.map(|value| value.map(full_value_bytes).transpose())
.collect()
}
async fn hot_scan_finite_identity_batches<'a>(
store: &(impl StorageAdapterRead + ?Sized),
batches: Vec<FiniteHotIdentityBatchRef<'a>>,
limit: Option<usize>,
) -> Result<Vec<FiniteHotEntryBatchRef<'a>>, LixError> {
let expected_generation = batches.first().map(|batch| batch.generation);
let mut remaining = limit.unwrap_or(usize::MAX);
let mut entries = Vec::with_capacity(batches.len());
for identities in batches {
debug_assert_eq!(Some(identities.generation), expected_generation);
if remaining == 0 {
break;
}
let mut values = if limit.is_none()
&& let Some(values) = hot_scan_dense_identity_range(store, &identities).await?
{
values
} else {
hot_load_finite_identity_bytes(store, &identities).await?
};
if limit.is_some() {
for value in &mut values {
if value.is_none() {
continue;
}
if remaining == 0 {
*value = None;
} else {
remaining -= 1;
}
}
}
entries.push(FiniteHotEntryBatchRef { identities, values });
}
Ok(entries)
}
async fn materialize_hot_scan_entries(
store: &(impl StorageAdapterRead + ?Sized),
entries: HotScanEntries<'_>,
projection: ChangeRecordProjection,
branch_id: &str,
active_checkpoint_commit_id: Option<CommitId>,
) -> Result<MaterializedHotStateBatch, LixError> {
match entries {
HotScanEntries::Decoded(entries) => {
materialize_live_entries(
store,
entries,
projection,
branch_id,
active_checkpoint_commit_id,
)
.await
}
HotScanEntries::Finite(batches) => {
let row_count = batches
.iter()
.map(|batch| batch.values.iter().flatten().count())
.sum();
let mut entries = Vec::with_capacity(row_count);
for batch in batches {
debug_assert_eq!(batch.identities.branch_id, branch_id);
for (index, value) in batch.values.into_iter().enumerate() {
let Some(value) = value else {
continue;
};
entries.push((batch.identities.key_ref(index), value));
}
}
materialize_live_entries(
store,
entries,
projection,
branch_id,
active_checkpoint_commit_id,
)
.await
}
}
}
async fn hot_load_identity_ref_bytes(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
identities: &[TrackedStateKeyRef<'_>],
) -> Result<Vec<Option<Bytes>>, LixError> {
if identities.is_empty() {
return Ok(Vec::new());
}
let encoded = encode_hot_point_keys(branch_id, generation, identities);
let keys = (0..identities.len())
.map(|index| encoded.primary_key(index))
.collect::<Vec<_>>();
PointReadPlan::new(ROW_SPACE, &keys)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.map(|value| value.map(full_value_bytes).transpose())
.collect()
}
async fn hot_load_primary_identity_bytes(
store: &(impl StorageAdapterRead + ?Sized),
identities: &[HeadIdentity],
) -> Result<Vec<Option<Bytes>>, LixError> {
if identities.is_empty() {
return Ok(Vec::new());
}
let scope = &identities[0];
debug_assert!(identities.iter().all(|identity| {
identity.branch_id == scope.branch_id && identity.generation == scope.generation
}));
let identities = identities
.iter()
.map(|identity| TrackedStateKeyRef {
schema_key: identity.schema_key.as_str(),
file_id: identity.file_id.as_deref(),
row_pk: &identity.row_pk,
})
.collect::<Vec<_>>();
let encoded = encode_hot_point_keys(scope.branch_id.as_str(), scope.generation, &identities);
let keys = (0..identities.len())
.map(|index| encoded.primary_key(index))
.collect::<Vec<_>>();
PointReadPlan::new(ROW_SPACE, &keys)
.materialize(store, StorageGetOptions::default())
.await?
.value
.into_iter()
.map(|value| value.map(full_value_bytes).transpose())
.collect()
}
async fn hot_load_file_scope_predecessors(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
checkpoint: Option<CommitId>,
cascades: &BTreeMap<String, &CurrentStateDeltaRef<'_>>,
) -> Result<(Vec<HeadIdentity>, Vec<CertifiedCurrentStatePredecessor>), LixError> {
let mut schemas = hot_file_backed_schema_keys(store, branch_id, generation)
.await?
.into_iter()
.collect::<BTreeSet<_>>();
let file_ids = cascades
.keys()
.cloned()
.map(NullableKeyFilter::Value)
.collect::<Vec<_>>();
let mut bases = packed_current_base_refs(store, branch_id, generation)
.await?
.into_iter()
.filter(|base| packed_base_matches_file_filter(base, &file_ids))
.map(|base| base.commit_id)
.collect::<BTreeSet<_>>();
if let Some(root) = load_root_current_base_commit(store, branch_id, generation).await? {
bases.insert(root);
}
let mut native = crate::tracked_state::TrackedStateContext::new().reader(store);
for base in bases {
schemas.extend(native.schema_keys_at_commit(base).await?);
}
let reader = TrackedHeadContext::new().reader(store);
let mut keys = BTreeSet::new();
for schema_key in schemas {
let rows = Box::pin(reader.scan_live_batch_for_generation(
branch_id,
generation,
checkpoint,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key],
file_ids: file_ids.clone(),
include_tombstones: false,
..Default::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["change_id".into()],
},
limit: None,
},
))
.await?;
keys.extend(rows.iter().map(|row| TrackedStateKey {
schema_key: row.schema_key().into(),
file_id: row.file_id().map(str::to_owned),
row_pk: row.row_pk().clone(),
}));
}
let keys = keys.into_iter().collect::<Vec<_>>();
let key_refs = keys
.iter()
.map(|key| TrackedStateKeyRef {
schema_key: &key.schema_key,
file_id: key.file_id.as_deref(),
row_pk: &key.row_pk,
})
.collect::<Vec<_>>();
let exact = Box::pin(reader.load_projected_live_batch_for_generation_refs(
branch_id,
generation,
checkpoint,
&key_refs,
&ChangeRecordProjection::identity_only(),
))
.await?;
let mut identities = Vec::with_capacity(keys.len());
let mut predecessors = Vec::with_capacity(keys.len());
for (index, key) in keys.into_iter().enumerate() {
let row = exact
.row(index)
.ok_or_else(|| head_value_error("effective file scope changed inside pinned read"))?;
let predecessor = row.durable_predecessor().cloned().ok_or_else(|| {
head_value_error("effective file cascade row has no durable predecessor")
})?;
identities.push(HeadIdentity {
branch_id: branch_id.into(),
generation,
schema_key: key.schema_key,
file_id: key.file_id,
row_pk: key.row_pk,
});
predecessors.push(predecessor);
}
Ok((identities, predecessors))
}
fn working_diff_baseline_before(
baseline: WorkingDiffBaseline,
checkpoint_commit_id: CommitId,
) -> Option<Option<WorkingDiffVersion>> {
match baseline {
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id: owner,
} if owner == checkpoint_commit_id => Some(None),
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: owner,
version,
} if owner == checkpoint_commit_id => Some(Some(version)),
WorkingDiffBaseline::BeforeAbsent { .. } | WorkingDiffBaseline::BeforePresent { .. } => {
None
}
WorkingDiffBaseline::Disabled | WorkingDiffBaseline::Clean => None,
}
}
fn packed_working_diff_slot(slot: &Option<lix_schema::Jsonb>) -> WorkingDiffSlotFingerprint {
match slot {
None => WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
Some(metadata) => WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_INLINE,
hash: *blake3::hash(
metadata
.binary()
.expect("validated native JSONB metadata must encode")
.as_ref(),
)
.as_bytes(),
},
}
}
fn packed_working_diff_snapshot(payload: Option<&[u8]>) -> WorkingDiffSlotFingerprint {
payload.map_or(
WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
|payload| WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_INLINE,
hash: *blake3::hash(payload).as_bytes(),
},
)
}
fn packed_compact_working_diff_version(
value: &crate::tracked_state::TrackedStateIndexValue,
) -> WorkingDiffVersion {
WorkingDiffVersion {
change_id: value.change_id,
commit_id: value.commit_id,
deleted: value.deleted,
created_at: value.created_at,
updated_at: value.updated_at,
snapshot: WorkingDiffSlotFingerprint::unresolved(),
metadata: WorkingDiffSlotFingerprint::unresolved(),
}
}
#[cfg(test)]
pub(crate) static WORKING_DIFF_PATH_HITS: WorkingDiffPathHits = WorkingDiffPathHits {
index_scan: std::sync::atomic::AtomicUsize::new(0),
primary_scan: std::sync::atomic::AtomicUsize::new(0),
};
#[cfg(test)]
pub(crate) struct WorkingDiffPathHits {
pub(crate) index_scan: std::sync::atomic::AtomicUsize,
pub(crate) primary_scan: std::sync::atomic::AtomicUsize,
}
async fn hot_working_diff_bypass_filter<'a>(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
filter: &'a TrackedStateFilter,
) -> Result<Option<Cow<'a, TrackedStateFilter>>, LixError> {
if !filter.row_pks.is_empty() {
return Ok((!filter.schema_keys.is_empty()).then_some(Cow::Borrowed(filter)));
}
if filter.file_ids.is_empty()
|| filter
.file_ids
.iter()
.any(|file_id| !matches!(file_id, NullableKeyFilter::Value(_)))
{
return Ok(None);
}
if !filter.schema_keys.is_empty() {
return Ok(Some(Cow::Borrowed(filter)));
}
let schema_keys = hot_file_backed_schema_keys(store, branch_id, generation).await?;
if schema_keys.is_empty() {
return Ok(None);
}
let mut bypass = filter.clone();
bypass.schema_keys = schema_keys;
Ok(Some(Cow::Owned(bypass)))
}
async fn hot_file_backed_schema_keys(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
) -> Result<Vec<String>, LixError> {
let scope = hot_scope_prefix(branch_id, generation);
let range = StoragePrefix {
bytes: Bytes::from(scope.clone()),
}
.to_range()?;
let mut schema_keys = Vec::new();
let mut cursor = store
.begin_scan(
FILE_SPACE,
range,
StorageBeginScanOptions {
projection: StorageCoreProjection::KeyOnly,
..StorageBeginScanOptions::default()
},
)
.await?;
while let Some(entries) = cursor.next_chunk().await? {
for entry in entries {
schema_keys.push(decode_hot_file_schema_key_in_scope(entry.key.0, &scope)?);
}
}
schema_keys.sort();
schema_keys.dedup();
Ok(schema_keys)
}
fn decode_hot_file_schema_key_in_scope(key: Bytes, scope: &[u8]) -> Result<String, LixError> {
if !key.starts_with(scope) {
return Err(key_codec_error(
"hot file marker does not begin with its scanned scope",
));
}
let mut offset = scope.len();
let (schema_key, terminator) = read_hot_scan_key_string(&key, &mut offset, "schema key")?;
if terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot file marker schema key has an invalid terminator",
));
}
if offset != key.len() {
return Err(key_codec_error("hot file marker key has trailing bytes"));
}
Ok(schema_key.as_str(&key).to_string())
}
async fn hot_working_diff_entries(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
checkpoint_commit_id: CommitId,
generation: CommitId,
expected_coverage: WorkingDiffIndexCoverage,
filter: &TrackedStateFilter,
) -> Result<Option<Vec<TrackedStateDiffEntry>>, LixError> {
let packed_refs = packed_current_base_refs(store, branch_id, generation).await?;
let packed_refs = packed_refs
.into_iter()
.filter(|base| base.checkpoint_commit_id == Some(checkpoint_commit_id))
.collect::<Vec<_>>();
let has_matching_packed_ref = packed_refs
.iter()
.any(|base| packed_base_matches_file_filter(base, &filter.file_ids));
if !has_matching_packed_ref {
if let Some(bypass) =
hot_working_diff_bypass_filter(store, branch_id, generation, filter).await?
{
return hot_working_diff_entries_from_primary(
store,
branch_id,
checkpoint_commit_id,
generation,
bypass.as_ref(),
)
.await;
}
}
#[cfg(test)]
WORKING_DIFF_PATH_HITS
.index_scan
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let scope = encode_working_diff_scope_prefix(branch_id, checkpoint_commit_id, generation);
let range = StoragePrefix {
bytes: Bytes::from(scope.clone()),
}
.to_range()?;
let mut actual_coverage = WorkingDiffIndexCoverage::default();
let mut selected = BTreeMap::<HeadIdentity, Option<WorkingDiffVersion>>::new();
let mut cursor = store
.begin_scan(DIFF_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let Ok(bytes) = full_value_bytes(entry.value) else {
return Ok(None);
};
if bytes.is_empty() {
if actual_coverage
.add_encoded_group_key(entry.key.0.as_ref())
.is_none()
{
return Ok(None);
}
let Ok(identity) = decode_hot_diff_key_in_scope(entry.key.0.as_ref(), &scope)
else {
return Ok(None);
};
if matches_filter(&identity, filter) {
selected.insert(
HeadIdentity {
branch_id: branch_id.to_string(),
generation,
schema_key: identity.schema_key,
row_pk: identity.row_pk,
file_id: identity.file_id,
},
None,
);
}
continue;
}
let Ok(segment_scope) = decode_hot_diff_segment_key(entry.key.0.as_ref()) else {
return Ok(None);
};
if segment_scope.digest != *blake3::hash(&bytes).as_bytes() {
return Ok(None);
}
let decoded =
visit_hot_diff_segment(&bytes, &scope, &mut actual_coverage, |identity| {
if matches_filter(&identity, filter) {
selected.insert(
HeadIdentity {
branch_id: branch_id.to_string(),
generation,
schema_key: identity.schema_key,
row_pk: identity.row_pk,
file_id: identity.file_id,
},
None,
);
}
});
if decoded.is_err() {
return Ok(None);
}
}
if !page_has_more {
break;
}
}
for base_ref in &packed_refs {
if actual_coverage
.add_encoded_group_key(&base_ref.coverage_key)
.is_none()
{
return Ok(None);
}
if !packed_base_matches_file_filter(base_ref, &filter.file_ids) {
continue;
}
let Ok(members) =
crate::tracked_state::scan_commit_delta_members(store, base_ref.commit_id).await
else {
return Ok(None);
};
for (key, value) in members {
if !packed_identity_matches_filter(
&key.schema_key,
&key.row_pk,
key.file_id.as_deref(),
filter,
) {
continue;
}
let identity = HeadIdentity {
branch_id: branch_id.to_string(),
generation,
schema_key: key.schema_key,
row_pk: key.row_pk,
file_id: key.file_id,
};
let version = packed_compact_working_diff_version(&value);
match selected.entry(identity) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(Some(version));
}
std::collections::btree_map::Entry::Occupied(mut entry)
if entry
.get()
.is_none_or(|previous| previous.commit_id < version.commit_id) =>
{
entry.insert(Some(version));
}
std::collections::btree_map::Entry::Occupied(_) => {}
}
}
}
if actual_coverage != expected_coverage {
if !has_matching_packed_ref {
return hot_working_diff_entries_from_primary(
store,
branch_id,
checkpoint_commit_id,
generation,
filter,
)
.await;
}
return Ok(None);
}
let (selected, base_versions): (Vec<_>, Vec<_>) = selected.into_iter().unzip();
let after_values = hot_load_primary_identity_bytes(store, &selected).await?;
let mut candidates = Vec::with_capacity(selected.len());
for ((identity, after), base_after) in selected.into_iter().zip(after_values).zip(base_versions)
{
let hot_after = if let Some(after) = after {
let Ok(after) = decode_head_value(&after) else {
return Ok(None);
};
if after.untracked {
return Ok(None);
}
let baseline = after.working_diff_baseline;
let Some(after) = after.working_diff_version() else {
return Ok(None);
};
let before = if baseline == WorkingDiffBaseline::Clean
&& base_after.is_some_and(|packed| after.commit_id < packed.commit_id)
{
Some(after)
} else {
let Some(before) = working_diff_baseline_before(baseline, checkpoint_commit_id)
else {
return Ok(None);
};
before
};
Some((before, after))
} else {
None
};
let Some((before, after)) = choose_hot_or_packed_working_diff(hot_after, base_after) else {
return Ok(None);
};
let identity = identity.into_row_identity();
candidates.push((
TrackedStateKey {
schema_key: identity.schema_key,
row_pk: identity.row_pk,
file_id: identity.file_id,
},
before,
after,
));
}
Ok(Some(
classify_hot_working_diff_entries(store, candidates).await?,
))
}
fn choose_hot_or_packed_working_diff(
hot: Option<(Option<WorkingDiffVersion>, WorkingDiffVersion)>,
packed: Option<WorkingDiffVersion>,
) -> Option<(Option<WorkingDiffVersion>, WorkingDiffVersion)> {
match (hot, packed) {
(Some(hot), Some(packed)) if hot.1.commit_id < packed.commit_id => Some((hot.0, packed)),
(Some(hot), _) => Some(hot),
(None, Some(packed)) => Some((None, packed)),
(None, None) => None,
}
}
async fn hot_working_diff_entries_from_primary(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
checkpoint_commit_id: CommitId,
generation: CommitId,
filter: &TrackedStateFilter,
) -> Result<Option<Vec<TrackedStateDiffEntry>>, LixError> {
#[cfg(test)]
WORKING_DIFF_PATH_HITS
.primary_scan
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let rows = hot_scan_entries(store, branch_id, generation, filter, None, None)
.await?
.expect("unbounded HOT scan cannot exhaust a byte budget");
match rows {
HotScanEntries::Decoded(rows) => {
let mut candidates = Vec::with_capacity(rows.len());
for (identity, bytes) in rows {
let Some(versions) = finite_working_diff_versions(&bytes, checkpoint_commit_id)
else {
return Ok(None);
};
let Some((before, after)) = versions else {
continue;
};
candidates.push((identity, before, after));
}
Ok(Some(
classify_hot_working_diff_scan_entries(store, candidates).await?,
))
}
HotScanEntries::Finite(batches) => {
let row_count = batches
.iter()
.map(|batch| batch.values.iter().flatten().count())
.sum();
let mut candidates = Vec::with_capacity(row_count);
for batch in batches {
for (index, bytes) in batch.values.into_iter().enumerate() {
let Some(bytes) = bytes else {
continue;
};
let Some(versions) = finite_working_diff_versions(&bytes, checkpoint_commit_id)
else {
return Ok(None);
};
let Some((before, after)) = versions else {
continue;
};
candidates.push((batch.identities.key_ref(index), before, after));
}
}
Ok(Some(
classify_hot_working_diff_entry_refs(store, candidates).await?,
))
}
}
}
fn finite_working_diff_versions(
bytes: &Bytes,
checkpoint_commit_id: CommitId,
) -> Option<Option<(Option<WorkingDiffVersion>, WorkingDiffVersion)>> {
let after = decode_head_value(bytes).ok()?;
if after.untracked || after.working_diff_baseline == WorkingDiffBaseline::Clean {
return Some(None);
}
if working_diff_checkpoint_owner(after.working_diff_baseline)
.is_some_and(|owner| owner != checkpoint_commit_id)
{
return Some(None);
}
let before = working_diff_baseline_before(after.working_diff_baseline, checkpoint_commit_id)?;
let after = after.working_diff_version()?;
Some(Some((before, after)))
}
async fn classify_hot_working_diff_entries(
store: &(impl StorageAdapterRead + ?Sized),
mut candidates: Vec<(
TrackedStateKey,
Option<WorkingDiffVersion>,
WorkingDiffVersion,
)>,
) -> Result<Vec<TrackedStateDiffEntry>, LixError> {
resolve_working_diff_comparison_payloads(
store,
&mut candidates,
|(key, _, _)| key.clone(),
|(_, before, _)| before,
|(_, _, after)| after,
)
.await?;
let row_count = candidates.len();
let mut keys = Vec::with_capacity(row_count);
let mut versions = Vec::with_capacity(row_count);
for (key, before, after) in candidates {
keys.push(key);
versions.push((before, after));
}
let identities = TrackedStateDiffIdentity::from_key_batch(keys)?;
let mut entries = Vec::with_capacity(row_count);
for (identity, (before, after)) in identities.into_iter().zip(versions) {
if let Some(entry) = classify_hot_working_diff_entry(identity, before, after)? {
entries.push(entry);
}
}
Ok(entries)
}
async fn classify_hot_working_diff_entry_refs(
store: &(impl StorageAdapterRead + ?Sized),
mut candidates: Vec<(
TrackedStateKeyRef<'_>,
Option<WorkingDiffVersion>,
WorkingDiffVersion,
)>,
) -> Result<Vec<TrackedStateDiffEntry>, LixError> {
resolve_working_diff_comparison_payloads(
store,
&mut candidates,
|(key, _, _)| TrackedStateKey {
schema_key: key.schema_key.to_owned(),
file_id: key.file_id.map(str::to_owned),
row_pk: key.row_pk.clone(),
},
|(_, before, _)| before,
|(_, _, after)| after,
)
.await?;
let row_count = candidates.len();
let identities =
TrackedStateDiffIdentity::from_key_refs(row_count, |index| candidates[index].0)?;
let mut entries = Vec::with_capacity(row_count);
for (identity, (_, before, after)) in identities.into_iter().zip(candidates) {
if let Some(entry) = classify_hot_working_diff_entry(identity, before, after)? {
entries.push(entry);
}
}
Ok(entries)
}
async fn classify_hot_working_diff_scan_entries(
store: &(impl StorageAdapterRead + ?Sized),
mut candidates: Vec<(
HotScanIdentity,
Option<WorkingDiffVersion>,
WorkingDiffVersion,
)>,
) -> Result<Vec<TrackedStateDiffEntry>, LixError> {
resolve_working_diff_comparison_payloads(
store,
&mut candidates,
|(identity, _, _)| TrackedStateKey {
schema_key: identity.schema_key().to_owned(),
file_id: identity.file_id().map(str::to_owned),
row_pk: identity.row_pk.clone(),
},
|(_, before, _)| before,
|(_, _, after)| after,
)
.await?;
let row_count = candidates.len();
let identities = TrackedStateDiffIdentity::from_key_refs(row_count, |index| {
let identity = &candidates[index].0;
TrackedStateKeyRef {
schema_key: identity.schema_key(),
file_id: identity.file_id(),
row_pk: &identity.row_pk,
}
})?;
let mut entries = Vec::with_capacity(row_count);
for (identity, (_, before, after)) in identities.into_iter().zip(candidates) {
if let Some(entry) = classify_hot_working_diff_entry(identity, before, after)? {
entries.push(entry);
}
}
Ok(entries)
}
#[derive(Debug, Clone, Copy)]
enum WorkingDiffPayloadSide {
Before,
After,
}
struct PendingWorkingDiffPayload {
candidate_index: usize,
side: WorkingDiffPayloadSide,
key: TrackedStateKey,
}
async fn resolve_working_diff_comparison_payloads<T>(
store: &(impl StorageAdapterRead + ?Sized),
candidates: &mut [T],
key_of: impl Fn(&T) -> TrackedStateKey,
before_of: impl Fn(&mut T) -> &mut Option<WorkingDiffVersion>,
after_of: impl Fn(&mut T) -> &mut WorkingDiffVersion,
) -> Result<(), LixError> {
let mut pending = Vec::new();
for index in 0..candidates.len() {
let before = *before_of(&mut candidates[index]);
let after = *after_of(&mut candidates[index]);
let Some(before) = before else {
continue;
};
if before.deleted || after.deleted || before.change_id == after.change_id {
continue;
}
let key = key_of(&candidates[index]);
if before.payload_is_unresolved() {
pending.push(PendingWorkingDiffPayload {
candidate_index: index,
side: WorkingDiffPayloadSide::Before,
key: key.clone(),
});
}
if after.payload_is_unresolved() {
pending.push(PendingWorkingDiffPayload {
candidate_index: index,
side: WorkingDiffPayloadSide::After,
key,
});
}
}
if pending.is_empty() {
return Ok(());
}
let requests = pending
.iter()
.map(|pending| {
let version = match pending.side {
WorkingDiffPayloadSide::Before => {
before_of(&mut candidates[pending.candidate_index])
.as_ref()
.expect("pending before image is present")
}
WorkingDiffPayloadSide::After => after_of(&mut candidates[pending.candidate_index]),
};
crate::tracked_state::AuthoritativeLiveChangeRequest {
change_id: version.change_id,
source_commit_id: version.commit_id,
key: pending.key.clone(),
updated_at: version.updated_at,
}
})
.collect::<Vec<_>>();
let records =
crate::tracked_state::load_authoritative_live_change_records(store, &requests).await?;
for (pending, record) in pending.into_iter().zip(records) {
let version = match pending.side {
WorkingDiffPayloadSide::Before => before_of(&mut candidates[pending.candidate_index])
.as_mut()
.expect("pending before image is present"),
WorkingDiffPayloadSide::After => after_of(&mut candidates[pending.candidate_index]),
};
version.resolve_payload_slots(
packed_working_diff_snapshot(record.snapshot.as_deref()),
packed_working_diff_slot(&record.metadata),
);
}
Ok(())
}
fn classify_hot_working_diff_entry(
diff_identity: TrackedStateDiffIdentity,
before: Option<WorkingDiffVersion>,
after: WorkingDiffVersion,
) -> Result<Option<TrackedStateDiffEntry>, LixError> {
let before_row = before.map(|version| version.into_diff_row(diff_identity.clone()));
let after_row = after.into_diff_row(diff_identity.clone());
match (
before_row.as_ref().filter(|row| !row.deleted),
(!after_row.deleted).then_some(&after_row),
) {
(None, None) => Ok(None),
(None, Some(_)) => Ok(Some(TrackedStateDiffEntry {
identity: diff_identity,
kind: TrackedStateDiffKind::Added,
before: before_row,
after: Some(after_row),
})),
(Some(_), None) => Ok(Some(TrackedStateDiffEntry {
identity: diff_identity,
kind: TrackedStateDiffKind::Removed,
before: before_row,
after: Some(after_row),
})),
(Some(_), Some(_)) => {
let before = before.expect("a present before row implies a before version");
match before.payload_equality(after) {
WorkingDiffPayloadEquality::Equal => Ok(None),
WorkingDiffPayloadEquality::Different => Ok(Some(TrackedStateDiffEntry {
identity: diff_identity,
kind: TrackedStateDiffKind::Modified,
before: before_row,
after: Some(after_row),
})),
WorkingDiffPayloadEquality::Unresolved => Err(head_value_error(
"working-diff classification reached an unresolved payload",
)),
}
}
}
}
async fn hot_scan_entries<'a>(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &'a str,
generation: CommitId,
filter: &'a TrackedStateFilter,
limit: Option<usize>,
retained_byte_budget: Option<usize>,
) -> Result<Option<HotScanEntries<'a>>, LixError> {
#[cfg(feature = "storage-benches")]
let is_blob_ref_probe = filter.schema_keys.len() == 1
&& filter.schema_keys[0] == "lix_binary_blob_ref"
&& !filter.row_pks.is_empty();
#[cfg(feature = "storage-benches")]
let is_file_live_batch = filter.schema_keys.len() == 2
&& filter
.schema_keys
.iter()
.any(|key| key == "lix_file_descriptor")
&& filter
.schema_keys
.iter()
.any(|key| key == "lix_binary_blob_ref");
#[cfg(feature = "storage-benches")]
if is_file_live_batch {
crate::storage_bench::record_file_live_scan_call();
}
#[cfg(feature = "storage-benches")]
if is_blob_ref_probe {
crate::storage_bench::record_hot_blob_ref_scan_call();
}
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_scan_call();
if let Some(identities) = hot_exact_identity_batches(branch_id, generation, filter) {
let may_use_null_point_batch = !filter.file_ids.is_empty()
|| !hot_schema_has_file_members(store, branch_id, generation, &filter.schema_keys)
.await?;
if may_use_null_point_batch {
#[cfg(feature = "storage-benches")]
if is_blob_ref_probe {
crate::storage_bench::record_hot_blob_ref_scan_point_batch();
}
#[cfg(feature = "storage-benches")]
{
crate::storage_bench::record_hot_scan_point_batch();
if is_file_live_batch {
crate::storage_bench::record_file_live_scan_point_batch();
}
}
let entries = HotScanEntries::Finite(
hot_scan_finite_identity_batches(store, identities, limit).await?,
);
return Ok(hot_scan_entries_fit_budget(entries, retained_byte_budget));
}
}
if let Some(prefixes) = hot_file_scan_prefixes(branch_id, generation, filter) {
#[cfg(feature = "storage-benches")]
if is_blob_ref_probe {
crate::storage_bench::record_hot_blob_ref_scan_file_prefix();
}
#[cfg(feature = "storage-benches")]
{
crate::storage_bench::record_hot_scan_file_prefix();
if is_file_live_batch {
crate::storage_bench::record_file_live_scan_file_prefix();
}
}
let entries = HotScanEntries::Decoded(
scan_hot_file_entries(store, branch_id, generation, prefixes, filter, limit).await?,
);
return Ok(hot_scan_entries_fit_budget(entries, retained_byte_budget));
}
#[cfg(feature = "storage-benches")]
if is_blob_ref_probe {
crate::storage_bench::record_hot_blob_ref_scan_fallback();
}
#[cfg(feature = "storage-benches")]
{
crate::storage_bench::record_hot_scan_fallback(!filter.row_pks.is_empty());
if is_file_live_batch {
crate::storage_bench::record_file_live_scan_fallback();
}
}
let scope = hot_scope_prefix(branch_id, generation);
let mut prefixes = hot_row_scan_prefixes(&scope, filter);
prefixes.sort();
prefixes.dedup();
let mut rows = Vec::new();
let mut saw_file_backed_row = false;
let mut retained_bytes = 0_usize;
let physical_limit = limit.filter(|_| hot_filter_has_one_fixed_file_bucket(filter));
for prefix in prefixes {
let range = StoragePrefix {
bytes: Bytes::from(prefix),
}
.to_range()?;
let mut cursor = store
.begin_scan(ROW_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let remaining = physical_limit.map(|limit| limit.saturating_sub(rows.len()));
if matches!(remaining, Some(0)) {
let rows = if saw_file_backed_row {
canonicalize_hot_scan_rows(rows, limit)?
} else {
rows
};
return Ok(Some(HotScanEntries::Decoded(rows)));
}
let (page, page_has_more) = cursor
.next_page(remaining.unwrap_or(crate::storage_adapter::MAX_SCAN_PAGE_ROWS))
.await?
.into_parts();
for entry in page {
let encoded_key_bytes = entry.key.0.len();
let identity = decode_hot_scan_row_key_in_scope(entry.key.0, &scope)?;
let entry_matches_filter = identity.matches_filter(filter);
#[cfg(any(test, feature = "storage-benches"))]
{
HOT_SCAN_DECODED_ENTRIES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
#[cfg(feature = "storage-benches")]
{
if is_blob_ref_probe {
crate::storage_bench::record_hot_blob_ref_scan_entry(entry_matches_filter);
}
crate::storage_bench::record_hot_scan_fallback_entry(entry_matches_filter);
}
#[cfg(feature = "storage-benches")]
if is_file_live_batch {
crate::storage_bench::record_file_live_scan_entry(entry_matches_filter);
}
if entry_matches_filter {
saw_file_backed_row |= identity.file_id().is_some();
let value = full_value_bytes(entry.value)?;
#[cfg(any(test, feature = "storage-benches"))]
{
HOT_SCAN_MATCHED_ENTRIES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if value.len() > 1 && value[1] & 0b0000_0001 != 0 {
HOT_SCAN_TOMBSTONE_ENTRIES
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
retained_bytes = retained_bytes
.checked_add(encoded_key_bytes)
.and_then(|bytes| bytes.checked_add(value.len()))
.and_then(|bytes| bytes.checked_add(size_of::<(HotScanIdentity, Bytes)>()))
.ok_or_else(|| head_value_error("HOT scan resident byte size overflow"))?;
if retained_byte_budget.is_some_and(|budget| retained_bytes > budget) {
return Ok(None);
}
rows.push((identity, value));
if physical_limit.is_some_and(|limit| rows.len() >= limit) {
let rows = if saw_file_backed_row {
canonicalize_hot_scan_rows(rows, limit)?
} else {
rows
};
return Ok(Some(HotScanEntries::Decoded(rows)));
}
}
}
if !page_has_more {
break;
}
}
}
if saw_file_backed_row {
rows = canonicalize_hot_scan_rows(rows, limit)?;
} else if let Some(limit) = limit {
rows.truncate(limit);
}
Ok(Some(HotScanEntries::Decoded(rows)))
}
fn hot_filter_has_one_fixed_file_bucket(filter: &TrackedStateFilter) -> bool {
let Some(first) = filter.file_ids.first() else {
return false;
};
!matches!(first, NullableKeyFilter::Any)
&& filter.file_ids.iter().all(|file_id| file_id == first)
}
fn canonicalize_hot_scan_rows(
mut rows: Vec<(HotScanIdentity, Bytes)>,
limit: Option<usize>,
) -> Result<Vec<(HotScanIdentity, Bytes)>, LixError> {
let already_strictly_ordered = rows
.windows(2)
.all(|pair| pair[0].0.cmp(&pair[1].0).is_lt());
if !already_strictly_ordered {
rows.sort_unstable_by(|left, right| left.0.cmp(&right.0));
for pair in rows.windows(2) {
if pair[0].0 != pair[1].0 {
continue;
}
if pair[0].0.key != pair[1].0.key || pair[0].1 != pair[1].1 {
return Err(head_value_error(format!(
"duplicate HOT authority for schema '{}' row_pk {:?} file_id {:?} has different physical bytes",
pair[0].0.schema_key(),
pair[0].0.row_pk,
pair[0].0.file_id(),
)));
}
}
rows.dedup_by(|left, right| left.0 == right.0);
}
if let Some(limit) = limit {
rows.truncate(limit);
}
Ok(rows)
}
fn hot_scan_entries_fit_budget<'a>(
entries: HotScanEntries<'a>,
retained_byte_budget: Option<usize>,
) -> Option<HotScanEntries<'a>> {
let Some(budget) = retained_byte_budget else {
return Some(entries);
};
let retained_bytes = match &entries {
HotScanEntries::Decoded(rows) => rows
.capacity()
.saturating_mul(size_of::<(HotScanIdentity, Bytes)>())
.saturating_add(rows.iter().fold(0_usize, |bytes, (identity, value)| {
bytes
.saturating_add(identity.key.len())
.saturating_add(value.len())
.saturating_add(match &identity.schema_key {
HotScanString::Borrowed(_) => 0,
HotScanString::Owned(value) => value.capacity(),
})
.saturating_add(match &identity.file_id {
None | Some(HotScanString::Borrowed(_)) => 0,
Some(HotScanString::Owned(value)) => value.capacity(),
})
})),
HotScanEntries::Finite(batches) => batches
.capacity()
.saturating_mul(size_of::<FiniteHotEntryBatchRef<'_>>())
.saturating_add(batches.iter().fold(0_usize, |bytes, batch| {
bytes
.saturating_add(
batch
.identities
.identities
.capacity()
.saturating_mul(size_of::<FiniteHotIdentityRef<'_>>()),
)
.saturating_add(batch.identities.encoded.bytes.len())
.saturating_add(
batch
.identities
.encoded
.ranges
.capacity()
.saturating_mul(size_of::<EncodedHotPointKeyRanges>()),
)
.saturating_add(
batch
.values
.capacity()
.saturating_mul(size_of::<Option<Bytes>>()),
)
.saturating_add(
batch
.values
.iter()
.flatten()
.fold(0_usize, |bytes, value| bytes.saturating_add(value.len())),
)
})),
};
(retained_bytes <= budget).then_some(entries)
}
async fn hot_scan_dense_identity_range(
store: &(impl StorageAdapterRead + ?Sized),
identities: &FiniteHotIdentityBatchRef<'_>,
) -> Result<Option<Vec<Option<Bytes>>>, LixError> {
hot_scan_dense_encoded_key_range(store, identities.len(), |index| {
identities.encoded.primary_key_bytes(index)
})
.await
}
async fn hot_scan_dense_encoded_key_range<'a>(
store: &(impl StorageAdapterRead + ?Sized),
key_count: usize,
key_at: impl Fn(usize) -> &'a [u8],
) -> Result<Option<Vec<Option<Bytes>>>, LixError> {
if key_count < HOT_DENSE_SCAN_MIN_IDENTITIES {
return Ok(None);
}
if key_count == 0 {
return Ok(Some(Vec::new()));
}
if (1..key_count).any(|index| key_at(index - 1) > key_at(index)) {
return Ok(None);
}
let first_key = StorageKey(Bytes::copy_from_slice(key_at(0)));
let last_key = StorageKey(Bytes::copy_from_slice(key_at(key_count - 1)));
let range = crate::storage_adapter::StorageKeyRange {
lower: std::ops::Bound::Included(first_key),
upper: std::ops::Bound::Included(last_key),
};
let scan_budget = key_count.saturating_mul(HOT_DENSE_SCAN_MAX_OVERREAD);
let mut scanned = 0;
let mut requested_index = 0;
let mut values = vec![None; key_count];
let mut cursor = store
.begin_scan(ROW_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let remaining_budget = scan_budget.saturating_sub(scanned);
if remaining_budget == 0 {
return Ok(None);
}
let (page, page_has_more) = cursor
.next_page(remaining_budget.min(crate::storage_adapter::MAX_SCAN_PAGE_ROWS))
.await?
.into_parts();
scanned += page.len();
for entry in page {
while requested_index < key_count && key_at(requested_index) < entry.key.0.as_ref() {
requested_index += 1;
}
if requested_index < key_count && key_at(requested_index) == entry.key.0.as_ref() {
values[requested_index] = Some(full_value_bytes(entry.value)?);
requested_index += 1;
}
}
if requested_index == key_count || !page_has_more {
return Ok(Some(values));
}
}
}
fn hot_row_scan_prefixes(scope: &[u8], filter: &TrackedStateFilter) -> Vec<Vec<u8>> {
if filter.schema_keys.is_empty() {
return vec![scope.to_vec()];
}
filter
.schema_keys
.iter()
.map(|schema_key| {
let mut prefix = scope.to_vec();
write_key_string(&mut prefix, schema_key, KEY_PART_FINAL);
prefix
})
.collect()
}
fn hot_file_scan_prefixes(
branch_id: &str,
generation: CommitId,
filter: &TrackedStateFilter,
) -> Option<Vec<Vec<u8>>> {
if filter.schema_keys.is_empty()
|| filter.file_ids.is_empty()
|| !filter.row_pks.is_empty()
|| filter
.file_ids
.iter()
.any(|file_id| matches!(file_id, NullableKeyFilter::Any))
{
return None;
}
let mut prefixes = Vec::with_capacity(filter.schema_keys.len() * filter.file_ids.len());
for schema_key in &filter.schema_keys {
for file_id in &filter.file_ids {
let mut prefix = hot_scope_prefix(branch_id, generation);
write_key_string(&mut prefix, schema_key, KEY_PART_FINAL);
match file_id {
NullableKeyFilter::Null => write_file_id(&mut prefix, None),
NullableKeyFilter::Value(file_id) => write_file_id(&mut prefix, Some(file_id)),
NullableKeyFilter::Any => {
unreachable!("file-id projection predicate was checked above")
}
}
prefixes.push(prefix);
}
}
prefixes.sort();
prefixes.dedup();
Some(prefixes)
}
async fn scan_hot_file_entries(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
prefixes: Vec<Vec<u8>>,
filter: &TrackedStateFilter,
limit: Option<usize>,
) -> Result<Vec<(HotScanIdentity, Bytes)>, LixError> {
let scope = hot_scope_prefix(branch_id, generation);
let mut rows = Vec::new();
for prefix in prefixes {
let Some(range) = hot_file_row_pk_range(prefix, filter)? else {
continue;
};
let mut cursor = store
.begin_scan(ROW_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let identity = decode_hot_scan_row_key_in_scope(entry.key.0, &scope)?;
#[cfg(feature = "storage-benches")]
{
if filter.schema_keys.len() == 2
&& filter
.schema_keys
.iter()
.any(|key| key == "lix_file_descriptor")
&& filter
.schema_keys
.iter()
.any(|key| key == "lix_binary_blob_ref")
{
crate::storage_bench::record_file_live_scan_entry(
identity.matches_filter(filter),
);
}
}
#[cfg(any(test, feature = "storage-benches"))]
{
HOT_SCAN_DECODED_ENTRIES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
if identity.matches_filter(filter) {
let value = full_value_bytes(entry.value)?;
#[cfg(any(test, feature = "storage-benches"))]
{
HOT_SCAN_MATCHED_ENTRIES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if value.len() > 1 && value[1] & 0b0000_0001 != 0 {
HOT_SCAN_TOMBSTONE_ENTRIES
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
rows.push((identity, value));
}
}
if !page_has_more {
break;
}
}
}
canonicalize_hot_scan_rows(rows, limit)
}
fn hot_file_row_pk_range(
prefix: Vec<u8>,
filter: &TrackedStateFilter,
) -> Result<Option<crate::storage_adapter::StorageKeyRange>, LixError> {
let mut range = StoragePrefix {
bytes: Bytes::copy_from_slice(&prefix),
}
.to_range()?;
if let Some(lower) = filter.row_pk_lower.as_ref() {
let mut key = prefix.clone();
write_row_pk(&mut key, &lower.row_pk);
if !lower.inclusive {
let Some(successor) = hot_index_key_successor(&key) else {
return Ok(None);
};
key = successor;
}
range.lower = std::ops::Bound::Included(StorageKey(Bytes::from(key)));
}
if let Some(upper) = filter.row_pk_upper.as_ref() {
let mut key = prefix;
write_row_pk(&mut key, &upper.row_pk);
if upper.inclusive {
let Some(successor) = hot_index_key_successor(&key) else {
return Ok(None);
};
key = successor;
}
range.upper = std::ops::Bound::Excluded(StorageKey(Bytes::from(key)));
}
if let (std::ops::Bound::Included(lower), std::ops::Bound::Excluded(upper)) =
(&range.lower, &range.upper)
&& lower >= upper
{
return Ok(None);
}
Ok(Some(range))
}
async fn hot_schema_has_file_members(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
schema_keys: &[String],
) -> Result<bool, LixError> {
if schema_keys.is_empty() {
return Ok(true);
}
for schema_key in schema_keys {
if hot_schema_has_file_member(store, branch_id, generation, schema_key).await? {
return Ok(true);
}
}
Ok(false)
}
async fn hot_schema_has_file_member(
store: &(impl StorageAdapterRead + ?Sized),
branch_id: &str,
generation: CommitId,
schema_key: &str,
) -> Result<bool, LixError> {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_scan_file_member_guard_read();
let scope = hot_scope_prefix(branch_id, generation);
let key = StorageKey(Bytes::from(encode_hot_file_schema_key(&scope, schema_key)));
let values = PointReadPlan::new(FILE_SPACE, &[key])
.materialize(
store,
StorageGetOptions {
projection: StorageCoreProjection::KeyOnly,
},
)
.await?;
Ok(values.value.into_iter().next().flatten().is_some())
}
fn hot_scope_prefix(branch_id: &str, generation: CommitId) -> Vec<u8> {
encode_scope_prefix(branch_id, generation)
}
#[cfg(test)]
pub(super) fn encode_hot_row_key(identity: &HeadIdentity) -> Vec<u8> {
encode_hot_row_key_parts(
&identity.branch_id,
identity.generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)
}
fn encode_hot_row_key_parts(
branch_id: &str,
generation: CommitId,
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> Vec<u8> {
let mut key = hot_scope_prefix(branch_id, generation);
write_key_string(&mut key, schema_key, KEY_PART_FINAL);
write_file_id(&mut key, file_id);
write_row_pk(&mut key, row_pk);
key
}
#[cfg(test)]
pub(crate) fn encode_hot_row_key_for_test(
branch_id: &str,
generation: CommitId,
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> Vec<u8> {
encode_hot_row_key_parts(branch_id, generation, schema_key, row_pk, file_id)
}
fn validate_exact_collection_member(
branch_id: &str,
branch_generation: CommitId,
scope_prefix: &[u8],
scope: crate::collection_generation::CollectionScopeRef<'_>,
required_identity: TrackedStateKeyRef<'_>,
expected_untracked: bool,
raw_key: &[u8],
raw_value: &[u8],
) -> Result<Option<HeadRowIdentity>, LixError> {
let identity = decode_hot_row_key_in_scope(raw_key, scope_prefix)?;
if identity.schema_key != scope.schema_key
|| scope
.file_id
.is_some_and(|file_id| identity.file_id.as_deref() != Some(file_id))
{
return Err(head_value_error(
"selected collection scan escaped its exact scope",
));
}
let canonical = encode_hot_row_key_parts(
branch_id,
branch_generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
);
validate_canonical_exact_collection_key(raw_key, &canonical)?;
let value = decode_head_value(raw_value)?;
let is_required_identity = identity.schema_key == required_identity.schema_key
&& identity.row_pk == *required_identity.row_pk
&& identity.file_id.as_deref() == required_identity.file_id;
if value.deleted {
if !is_required_identity {
return Ok(None);
}
return Err(head_value_error(
"required collection identity is a tombstone instead of a live member",
));
}
if is_required_identity && value.untracked != expected_untracked {
return Err(head_value_error(
"required collection identity belongs to the wrong state domain",
));
}
if is_required_identity {
return Err(head_value_error(
"required point miss omitted a live collection authority member",
));
}
Ok(Some(identity))
}
fn validate_canonical_exact_collection_key(
raw_key: &[u8],
canonical_key: &[u8],
) -> Result<(), LixError> {
if canonical_key != raw_key {
return Err(head_value_error(
"selected collection contains a non-canonical identity key",
));
}
Ok(())
}
#[cfg(test)]
fn encode_hot_diff_key(checkpoint_commit_id: CommitId, identity: &HeadIdentity) -> Vec<u8> {
encode_hot_diff_key_parts(
&identity.branch_id,
checkpoint_commit_id,
identity.generation,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)
}
#[cfg(test)]
fn encode_hot_diff_key_parts(
branch_id: &str,
checkpoint_commit_id: CommitId,
generation: CommitId,
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> Vec<u8> {
let scope = encode_working_diff_scope_prefix(branch_id, checkpoint_commit_id, generation);
let mut key = Vec::with_capacity(
encoded_hot_identity_key_len(scope.len(), schema_key, row_pk, file_id).unwrap_or(0),
);
append_hot_diff_key_parts(&mut key, &scope, schema_key, row_pk, file_id);
key
}
fn append_hot_diff_key_parts(
key_bytes: &mut Vec<u8>,
scope: &[u8],
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> Range<usize> {
let start = key_bytes.len();
key_bytes.extend_from_slice(scope);
write_key_string(key_bytes, schema_key, KEY_PART_FINAL);
write_row_pk(key_bytes, row_pk);
write_file_id(key_bytes, file_id);
start..key_bytes.len()
}
fn encoded_hot_identity_key_len(
scope_len: usize,
schema_key: &str,
row_pk: &RowPk,
file_id: Option<&str>,
) -> Option<usize> {
let file_id_len = match file_id {
Some(file_id) => encoded_key_bytes_len(file_id.as_bytes())?,
None => 0,
};
scope_len
.checked_add(encoded_key_bytes_len(schema_key.as_bytes())?)?
.checked_add(encoded_row_pk_len(row_pk)?)?
.checked_add(1)?
.checked_add(file_id_len)
}
fn encoded_row_pk_len(row_pk: &RowPk) -> Option<usize> {
row_pk
.components
.iter()
.try_fold(1_usize, |total, component| {
let payload_len = match component {
crate::row_pk::RowPkComponent::Uuid(_) => Some(16 + 1),
crate::row_pk::RowPkComponent::Integer(_) => Some(8 + 1),
crate::row_pk::RowPkComponent::String(value) => {
encoded_key_bytes_len(value.as_bytes())
}
crate::row_pk::RowPkComponent::Bytes(value) => {
encoded_key_bytes_len(value.as_ref())
}
}?;
total.checked_add(1)?.checked_add(payload_len)
})
}
fn encoded_key_bytes_len(value: &[u8]) -> Option<usize> {
value
.len()
.checked_add(memchr::memchr_iter(KEY_PART_FINAL, value).count())?
.checked_add(2)
}
fn decode_hot_scan_row_key_in_scope(key: Bytes, scope: &[u8]) -> Result<HotScanIdentity, LixError> {
if !key.starts_with(scope) {
return Err(key_codec_error(
"hot row does not begin with its scanned scope",
));
}
let mut offset = scope.len();
let (schema_key, schema_terminator) =
read_hot_scan_key_string(&key, &mut offset, "schema key")?;
if schema_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot row schema key has an invalid terminator",
));
}
let file_id = read_hot_scan_file_id(&key, &mut offset)?;
let row_pk = read_hot_scan_row_pk(&key, &mut offset)?;
if offset != key.len() {
return Err(key_codec_error("hot row key has trailing bytes"));
}
#[cfg(feature = "storage-benches")]
{
crate::storage_bench::record_hot_scan_row_decoded();
}
Ok(HotScanIdentity {
key,
schema_key,
row_pk,
file_id,
})
}
#[cfg(test)]
pub(crate) fn hot_decode_row_pk_probe(bytes: &[u8]) -> Option<(RowPk, usize)> {
let mut offset = 0usize;
read_hot_scan_row_pk(&Bytes::copy_from_slice(bytes), &mut offset)
.ok()
.map(|row_pk| (row_pk, offset))
}
fn read_hot_scan_row_pk(bytes: &Bytes, offset: &mut usize) -> Result<RowPk, LixError> {
let version = bytes
.get(*offset)
.copied()
.ok_or_else(|| key_codec_error("is truncated before row primary key version"))?;
*offset += 1;
if version != ROW_PK_CODEC_V1 {
return Err(key_codec_error(&format!(
"has unsupported row primary key codec version {version}"
)));
}
let mut components = SmallVec::new();
loop {
let (part, terminator) = read_hot_scan_row_pk_part(bytes, offset)?;
components.push(part);
match terminator {
KEY_PART_FINAL => break,
KEY_PART_MORE => {}
_ => {
return Err(key_codec_error("row primary key has an invalid terminator"));
}
}
}
RowPk::from_components(components)
.map_err(|error| key_codec_error(&format!("contains an invalid row primary key: {error}")))
}
fn read_hot_scan_row_pk_part(
bytes: &Bytes,
offset: &mut usize,
) -> Result<(crate::row_pk::RowPkComponent, u8), LixError> {
let tag = bytes
.get(*offset)
.copied()
.ok_or_else(|| key_codec_error("is truncated before row primary key part tag"))?;
*offset += 1;
match tag {
ROW_PK_STRING => {
let (value, terminator) = read_hot_scan_key_string(bytes, offset, "row primary key")?;
Ok((
crate::row_pk::RowPkComponent::String(value.into_shared_str(bytes)),
terminator,
))
}
ROW_PK_BYTES => {
let (value, terminator) =
read_hot_scan_shared_bytes(bytes, offset, "row primary key bytes")?;
Ok((crate::row_pk::RowPkComponent::Bytes(value), terminator))
}
ROW_PK_UUID => {
let uuid_end = offset
.checked_add(ROW_PK_UUID_BYTES)
.ok_or_else(|| key_codec_error("UUIDv7 row primary key offset overflow"))?;
let uuid_bytes: [u8; 16] = bytes
.get(*offset..uuid_end)
.ok_or_else(|| key_codec_error("is truncated in UUIDv7 row primary key"))?
.try_into()
.expect("UUIDv7 slice has fixed length");
let terminator = bytes
.get(uuid_end)
.copied()
.ok_or_else(|| key_codec_error("is truncated after UUIDv7 row primary key"))?;
if !is_key_part_terminator(terminator) {
return Err(key_codec_error(
"UUIDv7 row primary key has an invalid terminator",
));
}
*offset = uuid_end + 1;
Ok((crate::row_pk::RowPkComponent::Uuid(uuid_bytes), terminator))
}
ROW_PK_INTEGER => {
let integer_end = offset
.checked_add(ROW_PK_INTEGER_BYTES)
.ok_or_else(|| key_codec_error("integer row primary key offset overflow"))?;
let ordered = u64::from_be_bytes(
bytes
.get(*offset..integer_end)
.ok_or_else(|| key_codec_error("is truncated in integer row primary key"))?
.try_into()
.expect("integer slice has fixed length"),
);
let terminator = bytes
.get(integer_end)
.copied()
.ok_or_else(|| key_codec_error("is truncated after integer row primary key"))?;
if !is_key_part_terminator(terminator) {
return Err(key_codec_error(
"integer row primary key has an invalid terminator",
));
}
*offset = integer_end + 1;
Ok((
crate::row_pk::RowPkComponent::Integer(i64_from_ordered_integer(ordered)),
terminator,
))
}
_ => Err(key_codec_error("has an unknown row primary key part tag")),
}
}
fn read_hot_scan_file_id(
bytes: &Bytes,
offset: &mut usize,
) -> Result<Option<HotScanString>, LixError> {
let tag = *bytes
.get(*offset)
.ok_or_else(|| key_codec_error("is truncated before file id"))?;
*offset += 1;
match tag {
FILE_ID_NONE => Ok(None),
FILE_ID_SOME => {
let (file_id, terminator) = read_hot_scan_key_string(bytes, offset, "file id")?;
if terminator != KEY_PART_FINAL {
return Err(key_codec_error("file id has an invalid terminator"));
}
Ok(Some(file_id))
}
_ => Err(key_codec_error("has an invalid file id tag")),
}
}
fn read_hot_scan_key_string(
bytes: &Bytes,
offset: &mut usize,
field: &str,
) -> Result<(HotScanString, u8), LixError> {
let part = scan_key_part(bytes.as_ref(), *offset)
.map_err(|error| head_key_part_error(error, field))?;
*offset = part.end;
match part.value {
ScannedKeyValue::Verbatim(range) => {
std::str::from_utf8(&bytes[range.clone()])
.map_err(|error| key_codec_error(&format!("{field} is not UTF-8: {error}")))?;
let start = u32::try_from(range.start)
.map_err(|_| key_codec_error(&format!("{field} offset exceeds u32")))?;
let end = u32::try_from(range.end)
.map_err(|_| key_codec_error(&format!("{field} offset exceeds u32")))?;
Ok((HotScanString::Borrowed(start..end), part.terminator))
}
ScannedKeyValue::Unescaped(value) => {
let value = String::from_utf8(value).map_err(|error| {
key_codec_error(&format!("{field} is not UTF-8: {}", error.utf8_error()))
})?;
Ok((HotScanString::Owned(value), part.terminator))
}
}
}
fn read_hot_scan_shared_bytes(
bytes: &Bytes,
offset: &mut usize,
field: &str,
) -> Result<(Bytes, u8), LixError> {
let part = scan_key_part(bytes.as_ref(), *offset)
.map_err(|error| head_key_part_error(error, field))?;
*offset = part.end;
let value = match part.value {
ScannedKeyValue::Verbatim(range) => {
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_scan_key_handle_clone();
bytes.slice(range)
}
ScannedKeyValue::Unescaped(value) => Bytes::from(value),
};
Ok((value, part.terminator))
}
fn decode_hot_row_key_in_scope(bytes: &[u8], scope: &[u8]) -> Result<HeadRowIdentity, LixError> {
if !bytes.starts_with(scope) {
return Err(key_codec_error(
"hot row does not begin with its scanned scope",
));
}
let mut offset = scope.len();
let (schema_key, schema_terminator) = read_key_string(bytes, &mut offset, "schema key")?;
if schema_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot row schema key has an invalid terminator",
));
}
let file_id = read_file_id(bytes, &mut offset)?;
let row_pk = read_row_pk(bytes, &mut offset)?;
if offset != bytes.len() {
return Err(key_codec_error("hot row key has trailing bytes"));
}
Ok(HeadRowIdentity {
schema_key,
row_pk,
file_id,
})
}
const HOT_INDEX_ENTRY_TAG: u8 = 0x05;
const HOT_INDEX_WITNESS_TAG: u8 = 0x06;
const HOT_INDEX_MEMBERSHIP_TAG: u8 = 0x07;
const HOT_INDEX_CANDIDATE_PAGE: usize = 256;
#[cfg(test)]
pub(crate) fn hot_index_key_is_witness(key: &[u8]) -> bool {
hot_index_key_has_tag(key, HOT_INDEX_WITNESS_TAG)
}
#[cfg(test)]
pub(crate) fn hot_index_key_is_entry(key: &[u8]) -> bool {
hot_index_key_has_tag(key, HOT_INDEX_ENTRY_TAG)
}
#[cfg(test)]
fn hot_index_key_has_tag(key: &[u8], tag: u8) -> bool {
let mut offset = 0;
let Ok((_, branch_terminator)) = read_key_string(key, &mut offset, "branch id") else {
return false;
};
if branch_terminator != KEY_PART_FINAL {
return false;
}
let Some(generation_end) = offset.checked_add(16) else {
return false;
};
if key.get(offset..generation_end).is_none() {
return false;
}
offset = generation_end;
let Ok((_, schema_terminator)) = read_key_string(key, &mut offset, "schema key") else {
return false;
};
schema_terminator == KEY_PART_FINAL && key.get(offset).copied() == Some(tag)
}
#[derive(Debug, Clone)]
pub(crate) struct HotIndexEntry {
pub(crate) untracked: bool,
pub(crate) schema_key: String,
pub(crate) ordinal: u16,
pub(crate) value: Option<HotIndexValue>,
pub(crate) file_id: Option<String>,
pub(crate) row_pk: RowPk,
}
#[derive(Debug, Clone, musli::Encode, musli::Decode)]
#[musli(packed)]
struct HotIndexCandidateValue {
row_pk: RowPk,
#[musli(with = storage_codec::option)]
file_id: Option<String>,
}
pub(crate) async fn stage_hot_index_entries(
read: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
entries: &[HotIndexEntry],
witnessed_collections: &BTreeSet<(String, u16)>,
) -> Result<(), LixError> {
stage_hot_index_entries_inner(
read,
writes,
branch_id,
generation,
entries,
witnessed_collections,
false,
)
.await
}
pub(crate) async fn stage_hot_index_entries_rebuild(
read: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
entries: &[HotIndexEntry],
witnessed_collections: &BTreeSet<(String, u16)>,
) -> Result<(), LixError> {
stage_hot_index_entries_inner(
read,
writes,
branch_id,
generation,
entries,
witnessed_collections,
true,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn stage_hot_index_entries_inner(
read: &(impl StorageAdapterRead + ?Sized),
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
entries: &[HotIndexEntry],
witnessed_collections: &BTreeSet<(String, u16)>,
rebuild: bool,
) -> Result<(), LixError> {
let mut latest = BTreeMap::new();
for entry in entries {
let mut key = hot_scope_prefix(branch_id, generation);
write_key_string(&mut key, &entry.schema_key, KEY_PART_FINAL);
key.push(HOT_INDEX_MEMBERSHIP_TAG);
key.extend_from_slice(&entry.ordinal.to_be_bytes());
write_file_id(&mut key, entry.file_id.as_deref());
write_row_pk(&mut key, &entry.row_pk);
key.push(u8::from(entry.untracked));
latest.insert(StorageKey(Bytes::from(key)), entry);
}
let membership_keys = latest.keys().cloned().collect::<Vec<_>>();
let previous = if rebuild {
vec![None; membership_keys.len()]
} else {
PointReadPlan::new(INDEX_SPACE, &membership_keys)
.materialize(read, StorageGetOptions::default())
.await?
.value
};
let mut staged = BTreeSet::new();
let mut published_by_collection: BTreeMap<(String, u16), u64> = BTreeMap::new();
for ((membership, entry), previous) in latest.into_iter().zip(previous) {
let next_key = entry.value.as_ref().map(|value| {
let mut key = encode_hot_index_entry_key(
branch_id,
generation,
&entry.schema_key,
entry.ordinal,
value,
entry.file_id.as_deref(),
&entry.row_pk,
);
key.push(u8::from(entry.untracked));
key
});
if let Some(StorageProjectedValue::FullValue(old)) = previous {
if next_key.as_deref() != Some(old.as_ref()) {
writes.delete(INDEX_SPACE, StorageKey(old));
}
}
let Some(key) = next_key else {
writes.delete(INDEX_SPACE, membership);
continue;
};
writes.put(
INDEX_SPACE,
membership,
StorageValue {
bytes: Bytes::from(key.clone()),
},
);
if !staged.insert(key.clone()) {
continue;
}
let identity = storage_codec::encode(
"hot index candidate identity",
&HotIndexCandidateValue {
row_pk: entry.row_pk.clone(),
file_id: entry.file_id.clone(),
},
)?;
writes.put(
INDEX_SPACE,
StorageKey(Bytes::from(key)),
StorageValue {
bytes: Bytes::from(identity),
},
);
*published_by_collection
.entry((entry.schema_key.clone(), entry.ordinal))
.or_default() += 1;
}
for collection in witnessed_collections {
published_by_collection
.entry(collection.clone())
.or_default();
}
if published_by_collection.is_empty() {
return Ok(());
}
let witness_keys = published_by_collection
.keys()
.map(|(schema_key, ordinal)| {
StorageKey(Bytes::from(encode_hot_index_witness_key(
branch_id, generation, schema_key, *ordinal,
)))
})
.collect::<Vec<_>>();
let previous = if rebuild {
vec![None; witness_keys.len()]
} else {
PointReadPlan::new(INDEX_SPACE, &witness_keys)
.materialize(read, StorageGetOptions::default())
.await?
.value
};
for ((collection, published), (key, previous)) in published_by_collection
.into_iter()
.zip(witness_keys.into_iter().zip(previous.into_iter()))
{
let existing = match previous {
Some(StorageProjectedValue::FullValue(bytes)) => Some(decode_hot_index_witness(&bytes)),
Some(StorageProjectedValue::KeyOnly) | None => None,
};
if existing.is_none() && !witnessed_collections.contains(&collection) {
continue;
}
let total = existing.flatten().unwrap_or(0).saturating_add(published);
if !staged.insert(key.0.to_vec()) {
continue;
}
writes.put(
INDEX_SPACE,
key,
StorageValue {
bytes: Bytes::from(encode_hot_index_witness(total).to_vec()),
},
);
}
Ok(())
}
fn encode_hot_index_witness(entries_published: u64) -> [u8; 8] {
entries_published.to_be_bytes()
}
fn decode_hot_index_witness(value: &[u8]) -> Option<u64> {
value.try_into().ok().map(u64::from_be_bytes)
}
fn decode_hot_index_candidate(value: &[u8]) -> Result<HotIndexCandidateValue, LixError> {
storage_codec::decode("hot index candidate identity", value)
}
fn hot_index_seek_budget(entries_published: u64) -> usize {
(entries_published / 2).clamp(128, 16_384) as usize
}
fn hot_index_candidate_budget(entries_published: u64) -> usize {
const MIN_CANDIDATE_BUDGET: u64 = 64;
usize::try_from((entries_published / 2).max(MIN_CANDIDATE_BUDGET)).unwrap_or(usize::MAX)
}
pub(crate) use crate::row_state::HotIndexValue;
impl HotIndexValue {
fn write(&self, out: &mut Vec<u8>) {
match self {
Self::String(value) => {
out.push(ROW_PK_STRING);
write_key_string(out, value, KEY_PART_FINAL);
}
Self::Integer(value) => {
out.push(ROW_PK_INTEGER);
out.extend_from_slice(&ordered_integer_from_i64(*value).to_be_bytes());
out.push(KEY_PART_FINAL);
}
}
}
}
pub(crate) fn encode_hot_index_entry_key(
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
value: &HotIndexValue,
file_id: Option<&str>,
row_pk: &RowPk,
) -> Vec<u8> {
let mut key = hot_index_value_prefix(branch_id, generation, schema_key, ordinal, value);
write_file_id(&mut key, file_id);
write_row_pk(&mut key, row_pk);
key
}
pub(crate) fn hot_index_value_prefix(
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
value: &HotIndexValue,
) -> Vec<u8> {
let mut key = hot_scope_prefix(branch_id, generation);
write_key_string(&mut key, schema_key, KEY_PART_FINAL);
key.push(HOT_INDEX_ENTRY_TAG);
key.extend_from_slice(&ordinal.to_be_bytes());
value.write(&mut key);
key
}
pub(crate) fn hot_index_column_prefix(
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
) -> Vec<u8> {
let mut key = hot_scope_prefix(branch_id, generation);
write_key_string(&mut key, schema_key, KEY_PART_FINAL);
key.push(HOT_INDEX_ENTRY_TAG);
key.extend_from_slice(&ordinal.to_be_bytes());
key
}
fn hot_index_key_successor(prefix: &[u8]) -> Option<Vec<u8>> {
let mut successor = prefix.to_vec();
while let Some(last) = successor.last_mut() {
if *last == u8::MAX {
successor.pop();
} else {
*last += 1;
return Some(successor);
}
}
None
}
pub(crate) fn encode_hot_index_witness_key(
branch_id: &str,
generation: CommitId,
schema_key: &str,
ordinal: u16,
) -> Vec<u8> {
let mut key = hot_scope_prefix(branch_id, generation);
write_key_string(&mut key, schema_key, KEY_PART_FINAL);
key.push(HOT_INDEX_WITNESS_TAG);
key.extend_from_slice(&ordinal.to_be_bytes());
key
}
fn encode_hot_file_schema_key(scope: &[u8], schema_key: &str) -> Vec<u8> {
let mut key = Vec::with_capacity(
scope
.len()
.saturating_add(encoded_key_bytes_len(schema_key.as_bytes()).unwrap_or(0)),
);
key.extend_from_slice(scope);
write_key_string(&mut key, schema_key, KEY_PART_FINAL);
key
}
struct HotDiffSegmentScope {
branch_id: String,
checkpoint_commit_id: CommitId,
generation: CommitId,
digest: [u8; 32],
}
fn decode_hot_diff_key_in_scope(bytes: &[u8], scope: &[u8]) -> Result<HeadRowIdentity, LixError> {
if !bytes.starts_with(scope) {
return Err(key_codec_error(
"hot diff row does not begin with its scanned scope",
));
}
let mut offset = scope.len();
let (schema_key, schema_terminator) = read_key_string(bytes, &mut offset, "schema key")?;
if schema_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot diff row schema key has an invalid terminator",
));
}
let row_pk = read_row_pk(bytes, &mut offset)?;
let file_id = read_file_id(bytes, &mut offset)?;
if offset != bytes.len() {
return Err(key_codec_error("hot diff row key has trailing bytes"));
}
Ok(HeadRowIdentity {
schema_key,
row_pk,
file_id,
})
}
fn decode_hot_diff_segment_key(bytes: &[u8]) -> Result<HotDiffSegmentScope, LixError> {
let mut offset = 0;
let (branch_id, branch_terminator) = read_key_string(bytes, &mut offset, "branch id")?;
if branch_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot diff branch id has an invalid terminator",
));
}
let checkpoint_commit_id = read_generation(bytes, &mut offset)?;
let generation = read_generation(bytes, &mut offset)?;
let digest_bytes = bytes
.get(offset..)
.ok_or_else(|| key_codec_error("hot diff segment key is truncated before its digest"))?;
let digest = <[u8; 32]>::try_from(digest_bytes)
.map_err(|_| key_codec_error("hot diff segment key has an invalid digest length"))?;
Ok(HotDiffSegmentScope {
branch_id,
checkpoint_commit_id,
generation,
digest,
})
}
fn visit_hot_diff_segment(
bytes: &[u8],
scope: &[u8],
coverage: &mut WorkingDiffIndexCoverage,
mut visit: impl FnMut(HeadRowIdentity),
) -> Result<(), LixError> {
if bytes.len() < HOT_DIFF_SEGMENT_HEADER_BYTES {
return Err(key_codec_error("hot diff segment is truncated"));
}
if bytes[0] != HOT_DIFF_SEGMENT_VERSION {
return Err(key_codec_error("hot diff segment has an unknown version"));
}
let count = u32::from_le_bytes(
bytes[1..HOT_DIFF_SEGMENT_HEADER_BYTES]
.try_into()
.expect("hot diff segment header has a fixed count width"),
);
if count == 0 || count > HOT_DIFF_SEGMENT_MAX_IDENTITIES {
return Err(key_codec_error(
"hot diff segment has an invalid identity count",
));
}
let mut offset = HOT_DIFF_SEGMENT_HEADER_BYTES;
let mut full_key = Vec::with_capacity(scope.len() + 128);
full_key.extend_from_slice(scope);
for _ in 0..count {
let length_end = offset
.checked_add(4)
.ok_or_else(|| key_codec_error("hot diff segment length offset overflow"))?;
let encoded_length = bytes.get(offset..length_end).ok_or_else(|| {
key_codec_error("hot diff segment is truncated before identity length")
})?;
let suffix_len = u32::from_le_bytes(
encoded_length
.try_into()
.expect("hot diff identity length has a fixed width"),
) as usize;
if suffix_len == 0 {
return Err(key_codec_error("hot diff segment has an empty identity"));
}
let suffix_end = length_end
.checked_add(suffix_len)
.ok_or_else(|| key_codec_error("hot diff segment identity offset overflow"))?;
let suffix = bytes
.get(length_end..suffix_end)
.ok_or_else(|| key_codec_error("hot diff segment is truncated in an identity"))?;
full_key.truncate(scope.len());
full_key.extend_from_slice(suffix);
coverage
.add_encoded_group_key(&full_key)
.ok_or_else(|| head_value_error("hot working-diff index count exceeds u64"))?;
visit(decode_hot_diff_key_in_scope(&full_key, scope)?);
offset = suffix_end;
}
if offset != bytes.len() {
return Err(key_codec_error("hot diff segment has trailing bytes"));
}
Ok(())
}
fn decode_hot_diff_key(bytes: &[u8]) -> Result<(CommitId, HeadIdentity), LixError> {
let mut offset = 0;
let (branch_id, branch_terminator) = read_key_string(bytes, &mut offset, "branch id")?;
if branch_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot diff branch id has an invalid terminator",
));
}
let checkpoint_commit_id = read_generation(bytes, &mut offset)?;
let generation = read_generation(bytes, &mut offset)?;
let (schema_key, schema_terminator) = read_key_string(bytes, &mut offset, "schema key")?;
if schema_terminator != KEY_PART_FINAL {
return Err(key_codec_error(
"hot diff schema key has an invalid terminator",
));
}
let row_pk = read_row_pk(bytes, &mut offset)?;
let file_id = read_file_id(bytes, &mut offset)?;
if offset != bytes.len() {
return Err(key_codec_error("hot diff key has trailing bytes"));
}
Ok((
checkpoint_commit_id,
HeadIdentity {
branch_id,
generation,
schema_key,
row_pk,
file_id,
},
))
}
const GENERATION_SCOPED_SPACES: &[StorageSpace] = &[
INDEX_SPACE,
ROW_SPACE,
FILE_SPACE,
COLLECTION_CONTROL_SPACE,
DETERMINISTIC_IDENTITY_WITNESS_SPACE,
PACKED_CURRENT_BASE_SPACE,
PACKED_CURRENT_BASE_CONTROL_SPACE,
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
ROOT_CURRENT_BASE_SPACE,
];
pub(crate) fn root_generation_absence_preconditions(
branch_id: &str,
generation: CommitId,
checkpoint: CommitId,
) -> Result<Vec<crate::storage_adapter::StoragePrecondition>, LixError> {
let range = StoragePrefix {
bytes: Bytes::from(encode_scope_prefix(branch_id, generation)),
}
.to_range()?;
let mut guards = GENERATION_SCOPED_SPACES
.iter()
.map(
|space| crate::storage_adapter::StoragePrecondition::RangeEmpty {
space: *space,
range: range.clone(),
},
)
.collect::<Vec<_>>();
guards.push(crate::storage_adapter::StoragePrecondition::RangeEmpty {
space: DIFF_SPACE,
range: StoragePrefix {
bytes: Bytes::from(encode_working_diff_scope_prefix(
branch_id, checkpoint, generation,
)),
}
.to_range()?,
});
Ok(guards)
}
pub(crate) fn hot_generation_scope_prefix(branch_id: &str, generation: CommitId) -> Vec<u8> {
encode_scope_prefix(branch_id, generation)
}
pub(crate) async fn stage_retire_hot_generation<S>(
store: &S,
writes: &mut StorageWriteSet,
branch_id: &str,
generation: CommitId,
) -> Result<u64, LixError>
where
S: StorageAdapterRead + ?Sized,
{
let prefix = StoragePrefix {
bytes: Bytes::from(encode_scope_prefix(branch_id, generation)),
};
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_retire_call();
let mut deleted = 0_u64;
for space in GENERATION_SCOPED_SPACES {
#[cfg(feature = "storage-benches")]
let space_start = std::time::Instant::now();
let declared = writes.declared_keys(*space);
let mut cursor = store
.begin_scan(
*space,
prefix.to_range()?,
StorageBeginScanOptions {
projection: StorageCoreProjection::KeyOnly,
..StorageBeginScanOptions::default()
},
)
.await?;
#[cfg(feature = "storage-benches")]
let open_nanos = u64::try_from(space_start.elapsed().as_nanos()).unwrap_or(u64::MAX);
#[cfg(feature = "storage-benches")]
let mut space_rows = 0_u64;
#[cfg(feature = "storage-benches")]
let mut space_pages = 0_u64;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
#[cfg(feature = "storage-benches")]
{
space_pages = space_pages.saturating_add(1);
}
for entry in page {
#[cfg(feature = "storage-benches")]
{
space_rows = space_rows.saturating_add(1);
}
if declared.contains(entry.key.0.as_ref()) {
continue;
}
writes.delete(*space, entry.key);
deleted = deleted.saturating_add(1);
}
if !page_has_more {
break;
}
}
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_retire_space(
space.id.0,
space_rows,
space_pages,
open_nanos,
u64::try_from(space_start.elapsed().as_nanos()).unwrap_or(u64::MAX),
);
}
#[cfg(feature = "storage-benches")]
crate::storage_bench::record_hot_retire_deleted(deleted);
Ok(deleted)
}
pub(crate) async fn stage_collect_stale_hot_diff_records<S>(
store: &S,
writes: &mut StorageWriteSet,
active: &BTreeMap<String, ActiveWorkingDiffScope>,
) -> Result<(), LixError>
where
S: StorageAdapterRead + ?Sized,
{
let range = StoragePrefix {
bytes: Bytes::new(),
}
.to_range()?;
let mut cursor = store
.begin_scan(DIFF_SPACE, range, StorageBeginScanOptions::default())
.await?;
loop {
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await?
.into_parts();
for entry in page {
let keep = match full_value_bytes(entry.value) {
Ok(bytes) if bytes.is_empty() => decode_hot_diff_key(entry.key.0.as_ref())
.is_ok_and(|(checkpoint_commit_id, identity)| {
active.get(&identity.branch_id).is_some_and(|scope| {
scope.checkpoint_commit_id == checkpoint_commit_id
&& scope.generation == identity.generation
})
}),
Ok(bytes) => {
decode_hot_diff_segment_key(entry.key.0.as_ref()).is_ok_and(|segment_scope| {
if !active.get(&segment_scope.branch_id).is_some_and(|scope| {
scope.checkpoint_commit_id == segment_scope.checkpoint_commit_id
&& scope.generation == segment_scope.generation
}) {
return false;
}
let scope = encode_working_diff_scope_prefix(
&segment_scope.branch_id,
segment_scope.checkpoint_commit_id,
segment_scope.generation,
);
let mut coverage = WorkingDiffIndexCoverage::default();
segment_scope.digest == *blake3::hash(&bytes).as_bytes()
&& visit_hot_diff_segment(&bytes, &scope, &mut coverage, |_| {}).is_ok()
})
}
_ => false,
};
if !keep {
writes.delete(DIFF_SPACE, entry.key);
}
}
if !page_has_more {
break;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use bytes::Bytes;
use super::*;
use crate::branch::{BranchHeadControl, stage_branch_head_control};
use crate::storage_adapter::{
Memory, StorageAdapter, StorageBeginScanOptions, StorageGetManyRequest,
StorageGetManyResult, StorageKeyRange, StorageReadOptions, StorageScanCursor,
StorageWriteOptions,
};
#[test]
fn adding_a_field_to_the_packed_collection_control_breaks_older_readers() {
#[derive(musli::Encode)]
#[musli(packed)]
struct WidenedHotCollectionControl {
active_generation: CommitId,
live_count: u64,
ordered_identity_digest: Option<[u8; 32]>,
compacted: bool,
}
let generation = CommitId::for_test_label("compaction-arity");
for digest in [None, Some([0xA5u8; 32])] {
let current = HotCollectionControl {
active_generation: generation,
live_count: 7,
ordered_identity_digest: digest,
};
let current_bytes = storage_codec::encode("hot collection control", ¤t)
.expect("current control should encode");
let round_tripped: HotCollectionControl =
storage_codec::decode("hot collection control", ¤t_bytes)
.expect("current control should round-trip");
assert_eq!(round_tripped, current);
let widened = WidenedHotCollectionControl {
active_generation: generation,
live_count: 7,
ordered_identity_digest: digest,
compacted: true,
};
let widened_bytes = storage_codec::encode("widened hot collection control", &widened)
.expect("widened control should encode");
assert!(
widened_bytes.len() > current_bytes.len(),
"the fourth positional field must actually widen the encoding"
);
let error = storage_codec::decode::<HotCollectionControl>(
"hot collection control",
&widened_bytes,
)
.expect_err(
"a three-field reader must reject a four-field control rather than \
silently decoding it",
);
let message = error.to_string();
assert!(
message.contains("failed to decode hot collection control"),
"expected a decode rejection, got: {message}"
);
}
}
#[test]
fn root_base_batch_cache_key_is_exact() {
let cache = RootBaseBatchCache::default();
let first = CommitId::for_test_label("root-base-cache-first");
let second = CommitId::for_test_label("root-base-cache-second");
let request =
|schema_key: &str, columns: &[&str], limit: Option<usize>| TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![schema_key.to_owned()],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: columns.iter().map(|column| (*column).to_owned()).collect(),
},
limit,
};
let batch = Arc::new(crate::tracked_state::MaterializedTrackedStateBatch::default());
let stored = request("s", &["change_id"], None);
cache.insert(first, stored.clone(), Arc::clone(&batch));
assert!(cache.get(first, &stored).is_some(), "exact key must hit");
assert!(
cache.get(second, &stored).is_none(),
"a different base commit must miss"
);
assert!(
cache
.get(first, &request("other", &["change_id"], None))
.is_none(),
"a different schema filter must miss"
);
assert!(
cache
.get(first, &request("s", &["snapshot_content"], None))
.is_none(),
"a different projection must miss"
);
assert!(
cache
.get(first, &request("s", &["change_id"], Some(1)))
.is_none(),
"a different limit must miss"
);
}
#[test]
fn root_base_batch_cache_evicts_least_recently_used() {
let cache = RootBaseBatchCache::default();
let commit_id = CommitId::for_test_label("root-base-cache-evict");
let request = |index: usize| TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![format!("s{index}")],
..TrackedStateFilter::default()
},
..TrackedStateScanRequest::default()
};
let batch = Arc::new(crate::tracked_state::MaterializedTrackedStateBatch::default());
for index in 0..=ROOT_BASE_BATCH_CACHE_MAX_ENTRIES {
cache.insert(commit_id, request(index), Arc::clone(&batch));
}
assert_eq!(
cache.entries().resident.len(),
ROOT_BASE_BATCH_CACHE_MAX_ENTRIES
);
assert!(
cache.get(commit_id, &request(0)).is_none(),
"the oldest entry must have been evicted"
);
assert!(
cache
.get(commit_id, &request(ROOT_BASE_BATCH_CACHE_MAX_ENTRIES))
.is_some(),
"the newest entry must be resident"
);
}
#[test]
fn root_scope_memo_reproduces_the_per_scope_rule() {
let branch_generation = CommitId::for_test_label("memo-branch-generation");
let root_generation = CommitId::for_test_label("memo-root-generation");
let unfiled = ("s".to_owned(), None);
let filed = ("s".to_owned(), Some("f".to_owned()));
let reference =
|created_at: LixTimestamp,
file_id: Option<&str>,
active: &BTreeMap<(String, Option<String>), RootCollectionGeneration>,
stored: &BTreeMap<(String, Option<String>), HotCollectionControl>| {
[
Some(("s".to_owned(), None)),
file_id.map(|file_id| ("s".to_owned(), Some(file_id.to_owned()))),
]
.into_iter()
.flatten()
.all(|scope| {
let root = active
.get(&scope)
.map_or(branch_generation, |generation| generation.commit_id);
if stored
.get(&scope)
.is_some_and(|control| control.active_generation != root)
{
return false;
}
active
.get(&scope)
.is_none_or(|generation| created_at >= generation.created_at)
})
};
let stamp = |text: &str| LixTimestamp::expect_parse("memo test timestamp", text);
let low = stamp("2026-01-01T00:00:00Z");
let high = stamp("2026-03-01T00:00:00Z");
let control = |generation: CommitId| HotCollectionControl {
active_generation: generation,
live_count: 0,
ordered_identity_digest: None,
};
let generation = |created_at: LixTimestamp| RootCollectionGeneration {
commit_id: root_generation,
created_at,
};
let unfiled_floor = BTreeMap::from([(unfiled.clone(), generation(high))]);
let both_floors = BTreeMap::from([
(unfiled.clone(), generation(low)),
(filed.clone(), generation(high)),
]);
let cases: Vec<(
BTreeMap<(String, Option<String>), RootCollectionGeneration>,
BTreeMap<(String, Option<String>), HotCollectionControl>,
)> = vec![
(BTreeMap::new(), BTreeMap::new()),
(unfiled_floor, BTreeMap::new()),
(both_floors, BTreeMap::new()),
(
BTreeMap::new(),
BTreeMap::from([(filed.clone(), control(root_generation))]),
),
(
BTreeMap::new(),
BTreeMap::from([(unfiled.clone(), control(branch_generation))]),
),
];
for (index, (active, stored)) in cases.iter().enumerate() {
for file_id in [None, Some("f")] {
let mut memo = RootScopeMemo::default();
for created_at in [
stamp("2025-01-01T00:00:00Z"),
stamp("2026-01-01T00:00:00Z"),
stamp("2026-03-01T00:00:00Z"),
stamp("2027-01-01T00:00:00Z"),
] {
let verdict = memo.verdict("s", file_id, branch_generation, active, stored);
let actual = !verdict.disqualified
&& verdict.floor.is_none_or(|floor| created_at >= floor);
assert_eq!(
actual,
reference(created_at, file_id, active, stored),
"case {index}, file_id {file_id:?}, created_at {created_at:?}"
);
}
}
}
let active = BTreeMap::from([(
filed.clone(),
RootCollectionGeneration {
commit_id: root_generation,
created_at: high,
},
)]);
let stored = BTreeMap::new();
let mut memo = RootScopeMemo::default();
let filed_verdict = memo.verdict("s", Some("f"), branch_generation, &active, &stored);
let unfiled_verdict = memo.verdict("s", None, branch_generation, &active, &stored);
assert_eq!(filed_verdict.floor, Some(high));
assert_eq!(
unfiled_verdict.floor, None,
"the unfiled scope has no collection generation and must not inherit the filed floor"
);
}
#[test]
fn transaction_hot_state_cache_is_bounded_per_metadata_lane() {
let cache = HotStateTransactionCache::default();
for index in 0..=TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES {
let generation = CommitId::for_test_label(&format!("cache-generation-{index}"));
cache
.remember_collection_control(
HotCollectionCacheKey {
branch_id: "cache-branch".to_owned(),
generation,
schema_key: format!("schema-{index}"),
file_id: None,
},
HotCollectionControl {
active_generation: generation,
live_count: index as u64,
ordered_identity_digest: None,
},
)
.expect("remember collection control");
cache
.remember_packed_current_base_refs(
&format!("packed-branch-{index}"),
generation,
&[PackedCurrentBaseRef {
commit_id: generation,
checkpoint_commit_id: None,
file_id: None,
coverage_key: Bytes::new(),
}],
)
.expect("remember packed current-base refs");
assert!(
!cache
.should_reuse_packed_points(generation)
.expect("observe first packed point scope")
);
for observation in 2..=TRANSACTION_PACKED_POINT_CACHE_MIN_OBSERVATIONS {
assert_eq!(
cache
.should_reuse_packed_points(generation)
.expect("observe packed point scope"),
index < TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES
&& observation == TRANSACTION_PACKED_POINT_CACHE_MIN_OBSERVATIONS,
);
}
}
assert_eq!(
cache.collection_controls.lock().unwrap().len(),
TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES
);
assert_eq!(
cache.packed_current_base_refs.lock().unwrap().len(),
TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES
);
assert_eq!(
cache
.packed_point_generation_observations
.lock()
.unwrap()
.len(),
TRANSACTION_HOT_STATE_CACHE_MAX_ENTRIES
);
}
#[test]
fn packed_current_base_file_scope_roundtrips_and_filters_exactly() {
let checkpoint = CommitId::for_test_label("packed-file-checkpoint");
let encoded = packed_current_base_value(Some(checkpoint), Some("file-a"))
.expect("encode packed file scope");
let (decoded_checkpoint, decoded_file) =
decode_packed_current_base_value(&encoded).expect("decode packed file scope");
assert_eq!(decoded_checkpoint, Some(checkpoint));
assert_eq!(decoded_file.as_deref(), Some("file-a"));
let base_ref = PackedCurrentBaseRef {
commit_id: CommitId::for_test_label("packed-file-owner"),
checkpoint_commit_id: decoded_checkpoint,
file_id: decoded_file,
coverage_key: Bytes::new(),
};
assert!(packed_base_matches_file_filter(
&base_ref,
&[NullableKeyFilter::Value("file-a".to_owned())]
));
assert!(!packed_base_matches_file_filter(
&base_ref,
&[NullableKeyFilter::Value("file-b".to_owned())]
));
assert!(!packed_base_matches_file_filter(
&base_ref,
&[NullableKeyFilter::Null]
));
assert!(packed_base_matches_file_filter(
&base_ref,
&[NullableKeyFilter::Any]
));
let unscoped = packed_current_base_value(None, None).expect("encode unscoped base");
assert_eq!(unscoped.len(), 16);
assert_eq!(
decode_packed_current_base_value(&unscoped).expect("decode unscoped base"),
(None, None)
);
}
#[tokio::test]
async fn transaction_reader_reuses_collection_control_point_read() {
const BRANCH_ID: &str = "collection-control-cache-branch";
const SCHEMA_KEY: &str = "collection_control_cache_schema";
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("collection-control-cache-generation");
let mut writes = StorageWriteSet::new();
stage_hot_collection_control(
&mut writes,
BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
},
HotCollectionControl {
active_generation: generation,
live_count: 7,
ordered_identity_digest: Some([3; 32]),
},
)
.expect("stage collection control fixture");
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("publish collection control fixture");
let get_many_calls = Arc::new(AtomicUsize::new(0));
let reader = HotStateStoreReader {
store: CountingRead {
inner: storage
.begin_read(StorageReadOptions::default())
.await
.expect("open collection control fixture read"),
get_many_calls: Arc::clone(&get_many_calls),
scan_calls: None,
},
transaction_cache: Some(Arc::new(HotStateTransactionCache::default())),
root_base_cache: None,
};
for _ in 0..2 {
let control = reader
.collection_generation(
BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
},
)
.await
.expect("load cached collection control");
assert_eq!(control.live_count, 7);
assert_eq!(control.ordered_identity_digest, Some([3; 32]));
}
assert_eq!(
get_many_calls.load(Ordering::Relaxed),
1,
"the immutable transaction snapshot should point-read a control once"
);
}
struct CountingRead<R> {
inner: R,
get_many_calls: Arc<AtomicUsize>,
scan_calls: Option<Arc<AtomicUsize>>,
}
impl<R: StorageAdapterRead> StorageAdapterRead for CountingRead<R> {
fn snapshot_cache_key(&self) -> Option<u128> {
self.inner.snapshot_cache_key()
}
async fn get_many(
&self,
requests: &[StorageGetManyRequest<'_>],
) -> Result<StorageGetManyResult, crate::storage_adapter::StorageError> {
self.get_many_calls.fetch_add(1, Ordering::Relaxed);
self.inner.get_many(requests).await
}
async fn begin_scan(
&self,
space: StorageSpace,
range: StorageKeyRange,
opts: StorageBeginScanOptions,
) -> Result<StorageScanCursor<'_>, crate::storage_adapter::StorageError> {
if let Some(scan_calls) = &self.scan_calls {
scan_calls.fetch_add(1, Ordering::Relaxed);
}
self.inner.begin_scan(space, range, opts).await
}
}
struct JsonCountingRead<R> {
inner: R,
json_get_many_calls: Arc<AtomicUsize>,
}
struct PackedSegmentCountingRead<R> {
inner: R,
segments: Arc<AtomicUsize>,
}
impl<R: StorageAdapterRead> StorageAdapterRead for PackedSegmentCountingRead<R> {
async fn get_many(
&self,
requests: &[StorageGetManyRequest<'_>],
) -> Result<StorageGetManyResult, crate::storage_adapter::StorageError> {
for request in requests {
if request.space == crate::tracked_state::TRACKED_STATE_COMMIT_DELTA_SEGMENT_SPACE {
self.segments
.fetch_add(request.keys.len(), Ordering::Relaxed);
}
}
self.inner.get_many(requests).await
}
async fn begin_scan(
&self,
space: StorageSpace,
range: StorageKeyRange,
opts: StorageBeginScanOptions,
) -> Result<StorageScanCursor<'_>, crate::storage_adapter::StorageError> {
self.inner.begin_scan(space, range, opts).await
}
}
#[tokio::test]
async fn packed_logical_pk_lookup_is_bounded_and_keeps_file_scopes() {
const BRANCH: &str = "01991b1d-6d8b-7000-8000-000000000071";
const FILE_A: &str = "01991b1d-6d8b-7000-8000-000000000072";
const FILE_B: &str = "01991b1d-6d8b-7000-8000-000000000073";
const FILE_C: &str = "01991b1d-6d8b-7000-8000-000000000074";
for row_count in [512, 5_000] {
let storage = StorageAdapter::new(Memory::new());
let label = format!("packed-pk-catalog-{row_count}");
let generation = CommitId::for_test_label(&label);
let selected = format!("row-{:05}", row_count / 2);
let make_row =
|key: String, file_id: Option<&str>, deleted: bool| MaterializedTrackedStateRow {
row_pk: RowPk::single(key.clone()),
schema_key: "lix_key_value".to_owned(),
file_id: file_id.map(str::to_owned),
snapshot_content: (!deleted).then(|| {
serde_json::json!({"key": key, "value": "payload"})
.to_string()
.into()
}),
decoded_snapshot: None,
metadata: None,
deleted,
created_at: timestamp().to_string(),
updated_at: timestamp().to_string(),
change_id: ChangeId::for_test_label(&format!("{label}-{key}-{file_id:?}")),
commit_id: generation,
};
let mut rows = (0..row_count)
.map(|index| make_row(format!("row-{index:05}"), None, false))
.collect::<Vec<_>>();
rows.push(make_row(selected.clone(), Some(FILE_A), false));
rows.push(make_row(selected.clone(), Some(FILE_B), false));
rows.push(make_row(selected.clone(), Some(FILE_C), true));
rows.push(make_row("aab-deleted".to_owned(), None, true));
let parent_label = format!("{label}-parent");
let mut inherited = make_row("aaa-inherited-only".to_owned(), None, false);
inherited.commit_id = CommitId::for_test_label(&parent_label);
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
BRANCH,
&parent_label,
&[inherited],
)
.await;
let mut read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
crate::test_support::stage_tracked_root_from_materialized(
&mut read,
&mut writes,
&crate::tracked_state::TrackedStateContext::new(),
&label,
Some(&parent_label),
&rows,
)
.await
.unwrap();
let mut base_key = hot_scope_prefix(BRANCH, generation);
base_key.extend_from_slice(generation.as_uuid().as_bytes());
writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(BRANCH, generation))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(base_key)),
StorageValue {
bytes: Bytes::from_static(&[0; 16]),
},
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let segments = Arc::new(AtomicUsize::new(0));
let counted = PackedSegmentCountingRead {
inner: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
segments: Arc::clone(&segments),
};
let request = TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec!["lix_key_value".to_owned()],
row_pks: vec![
RowPk::single("aaa-inherited-only"),
RowPk::single("aab-deleted"),
RowPk::single(selected.clone()),
RowPk::single("zzz-missing"),
],
..Default::default()
},
..Default::default()
};
let found = scan_packed_current_base_rows(&counted, BRANCH, generation, &request, None)
.await
.unwrap();
assert_eq!(found.len(), 3);
assert_eq!(
found
.iter()
.map(|row| row.file_id().map(str::to_owned))
.collect::<Vec<_>>(),
vec![None, Some(FILE_A.to_owned()), Some(FILE_B.to_owned())]
);
assert!(
segments.load(Ordering::Relaxed) <= 8,
"finite PK lookup read {} packed segments for {row_count} unrelated rows",
segments.load(Ordering::Relaxed)
);
let mut null_only = request.clone();
null_only.filter.file_ids = vec![NullableKeyFilter::Null];
assert_eq!(
scan_packed_current_base_rows(&counted, BRANCH, generation, &null_only, None)
.await
.unwrap()
.len(),
1
);
let limited =
scan_packed_current_base_rows(&counted, BRANCH, generation, &request, Some(1))
.await
.unwrap();
assert_eq!(
limited.len(),
1,
"an absent inherited candidate must not consume LIMIT"
);
assert_eq!(
limited.iter().next().unwrap().row_pk(),
&RowPk::single(selected)
);
}
}
#[tokio::test]
async fn packed_logical_pk_lookup_unions_local_and_selected_source_scopes() {
const BRANCH: &str = "01991b1d-6d8b-7000-8000-000000000091";
const FILE_A: &str = "01991b1d-6d8b-7000-8000-000000000092";
const FILE_B: &str = "01991b1d-6d8b-7000-8000-000000000093";
const SCHEMA: &str = "packed_alias_scope_probe";
let storage = StorageAdapter::new(Memory::new());
let source = CommitId::for_test_label("packed-alias-scope-source");
let owner = CommitId::for_test_label("packed-alias-scope-owner");
let row_pk = RowPk::single("shared");
let source_payload =
native_snapshot_payload(&row_pk, serde_json::json!({"value": "source"}));
let local_payload = native_snapshot_payload(&row_pk, serde_json::json!({"value": "local"}));
let source_delta = crate::tracked_state::TrackedStateDeltaRef {
schema_key: SCHEMA,
file_id: Some(FILE_B),
row_pk: &row_pk,
change_id: ChangeId::for_test_label("packed-alias-scope-source-row"),
commit_id: source,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
};
let local_delta = crate::tracked_state::TrackedStateDeltaRef {
file_id: Some(FILE_A),
change_id: ChangeId::for_test_label("packed-alias-scope-local-row"),
commit_id: owner,
..source_delta
};
let packed_delta = |delta, snapshot| crate::tracked_state::TrackedStateCommitDeltaRef {
delta,
metadata: None,
snapshot: Some(snapshot),
origin_key: None,
base_coordinate: None,
authored: true,
};
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
let source_stage = crate::tracked_state::stage_addressable_commit_deltas(
&mut writes,
&[packed_delta(source_delta, source_payload.as_slice())],
&[false],
)
.unwrap();
let local_stage =
crate::tracked_state::stage_addressable_commit_deltas_with_selected_source(
&mut writes,
&[packed_delta(local_delta, local_payload.as_slice())],
&[false],
source,
)
.unwrap();
for (commit_id, staged, deltas, file_id) in [
(source, source_stage, vec![source_delta], FILE_B),
(owner, local_stage, vec![source_delta, local_delta], FILE_A),
] {
let mutations = staged.mutation_inventory().clone();
let scope = mutations.single_partition.as_ref().unwrap();
assert_eq!(scope.schema_key, SCHEMA);
assert_eq!(scope.file_id.as_deref(), Some(file_id));
let mut overlay = crate::tracked_state::TrackedStateChunkOverlay::new();
let catalog = crate::tracked_state::stage_row_pk_index_from_deltas(
&read,
&mut writes,
&mut overlay,
deltas,
commit_id,
)
.await
.unwrap();
crate::tracked_state::stage_commit_state_manifest(
&mut writes,
&crate::tracked_state::CommitStateManifest {
incorporation: crate::tracked_state::CommitStateIncorporation::None,
commit_id,
change_account_id: crate::ANONYMOUS_ACCOUNT_ID.to_owned(),
replay_debt: crate::tracked_state::CommitStateReplayDebt {
depth: 1,
rows: u64::from(mutations.member_count),
bytes: 0,
},
mutations,
touched_scope_filter: Default::default(),
global_scope: false,
current_state_scoped_ranges: None,
row_pk_index_root_id: catalog,
snapshot_root: None,
},
)
.unwrap();
}
let scope_key = hot_scope_prefix(BRANCH, owner);
writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(scope_key.clone())),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
let mut base_key = scope_key;
base_key.extend_from_slice(owner.as_uuid().as_bytes());
writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(base_key)),
StorageValue {
bytes: Bytes::from_static(&[0; 16]),
},
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let request = TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![SCHEMA.to_owned()],
row_pks: vec![row_pk.clone(), RowPk::single("missing")],
..Default::default()
},
..Default::default()
};
let rows = scan_packed_current_base_rows(&read, BRANCH, owner, &request, None)
.await
.unwrap();
assert_eq!(
rows.len(),
2,
"the selected source contributes a second file scope"
);
let keys = [FILE_A, FILE_B].map(|file_id| TrackedStateKeyRef {
schema_key: SCHEMA,
file_id: Some(file_id),
row_pk: &row_pk,
});
let refs = packed_current_base_refs(&read, BRANCH, owner)
.await
.unwrap();
let exact = load_packed_current_base_exact_entries_from_refs(&read, &refs, &keys, None)
.await
.unwrap();
for ((row, exact), expected_value) in rows.iter().zip(exact).zip(["local", "source"]) {
let (value, change, _, _) = exact.expect("exact identity must exist");
assert_eq!(row.file_id(), change.file_id.as_deref());
assert_eq!(
row.commit_id(),
Some(owner),
"source values resolve through the alias owner"
);
assert_eq!(row.change_id(), Some(value.change_id));
let snapshot: serde_json::Value =
serde_json::from_str(row.snapshot_content().unwrap()).unwrap();
assert_eq!(snapshot["value"], expected_value);
}
}
impl<R: StorageAdapterRead> StorageAdapterRead for JsonCountingRead<R> {
fn snapshot_cache_key(&self) -> Option<u128> {
self.inner.snapshot_cache_key()
}
async fn get_many(
&self,
requests: &[StorageGetManyRequest<'_>],
) -> Result<StorageGetManyResult, crate::storage_adapter::StorageError> {
if requests
.iter()
.any(|request| request.space == crate::storage_spaces::RETIRED_JSON_SPACE)
{
self.json_get_many_calls.fetch_add(1, Ordering::Relaxed);
}
self.inner.get_many(requests).await
}
async fn begin_scan(
&self,
space: StorageSpace,
range: StorageKeyRange,
opts: StorageBeginScanOptions,
) -> Result<StorageScanCursor<'_>, crate::storage_adapter::StorageError> {
self.inner.begin_scan(space, range, opts).await
}
}
fn timestamp() -> LixTimestamp {
LixTimestamp::expect_parse("hot working-diff test timestamp", "2026-01-01T00:00:00Z")
}
fn native_snapshot_payload(row_pk: &RowPk, value: serde_json::Value) -> Vec<u8> {
let row =
WasmTypedRow::from_test_json_unchecked(row_pk, &value).expect("test row should build");
row.durable_payload()
.expect("test typed snapshot should encode")
.to_vec()
}
fn encoded_test_hot_value(generation: CommitId, untracked: bool, deleted: bool) -> Bytes {
let row_pk = RowPk::single("closure-row");
let snapshot = (!deleted)
.then(|| native_snapshot_payload(&row_pk, serde_json::json!({"value": "closure"})));
Bytes::from(
encode_head_value(&HeadValueRef {
change_id: Some(ChangeId::for_test_label("closure-change")),
commit_id: (!untracked).then_some(generation),
untracked,
deleted,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: snapshot.as_deref(),
metadata: None,
columnar_base_coordinate: None,
working_diff_baseline: WorkingDiffBaseline::Disabled,
})
.expect("closure fixture HOT value should encode"),
)
}
#[tokio::test]
async fn inherited_catalog_visibility_compares_deletions_and_local_tombstones() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("catalog-visibility");
let identity = |key: &str| HeadRowIdentity {
schema_key: "lix_registered_schema".to_owned(),
row_pk: RowPk::single(key),
file_id: None,
};
let live = encoded_test_hot_value(generation, false, false);
let tombstone = encoded_test_hot_value(generation, false, true);
let mut writes = StorageWriteSet::new();
stage_complete_hot_rows(
&mut writes,
"branch",
generation,
HotRowMap::from([(identity("closure-row"), live.clone())]),
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let previous = TrackedHeadContext::new()
.reader(&read)
.scan_live_batch_for_generation(
"branch",
generation,
None,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec!["lix_registered_schema".to_owned()],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["raw_snapshot".to_owned()],
},
limit: None,
},
)
.await
.unwrap();
assert_eq!(previous.len(), 1);
let inherited = HotTrackedSnapshot {
rows: HotRowMap::from([(identity("closure-row"), live)]),
};
let local = HotTrackedSnapshot::default();
assert!(
!inherited
.inherited_catalog_differs_from(&previous, &local, generation)
.unwrap()
);
assert!(
HotTrackedSnapshot::default()
.inherited_catalog_differs_from(&previous, &local, generation)
.unwrap(),
"removing an inherited definition changes visibility"
);
let local = HotTrackedSnapshot {
rows: HotRowMap::from([(identity("closure-row"), tombstone)]),
};
let absent = MaterializedHotStateBatch::default();
assert!(
!inherited
.inherited_catalog_differs_from(&absent, &local, generation)
.unwrap(),
"a local tombstone masks a new inherited definition"
);
assert!(
!HotTrackedSnapshot::default()
.inherited_catalog_differs_from(&absent, &local, generation)
.unwrap(),
"removing the masked inherited definition also leaves visibility unchanged"
);
}
#[tokio::test]
async fn inherited_catalog_visibility_reuses_publication_generation_fences() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("catalog-fence-serving");
let mut commits = [
CommitId::for_test_label("catalog-fence-a"),
CommitId::for_test_label("catalog-fence-b"),
CommitId::for_test_label("catalog-fence-c"),
];
commits.sort();
let [old, fence, new] = commits;
assert_ne!(generation, fence);
let identity = HeadRowIdentity {
schema_key: "lix_registered_schema".to_owned(),
row_pk: RowPk::single("closure-row"),
file_id: None,
};
let marker = HeadRowIdentity {
schema_key: crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
row_pk: RowPk::single(crate::collection_generation::collection_scope_key(
crate::collection_generation::CollectionScopeRef {
schema_key: "lix_registered_schema",
file_id: None,
},
)),
file_id: None,
};
let local = HotTrackedSnapshot {
rows: HotRowMap::from([(marker, encoded_test_hot_value(fence, false, false))]),
};
let inherited = |commit| HotTrackedSnapshot {
rows: HotRowMap::from([(
identity.clone(),
encoded_test_hot_value(commit, false, false),
)]),
};
let old_catalog = inherited(old);
let mut rows = local.rows.clone();
rows.extend(old_catalog.rows.clone());
let mut writes = StorageWriteSet::new();
stage_complete_collection_controls(&mut writes, "branch", generation, &rows).unwrap();
stage_complete_hot_rows(&mut writes, "branch", generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let request = TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec!["lix_registered_schema".to_owned()],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns {
columns: vec!["raw_snapshot".to_owned()],
},
limit: None,
};
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let previous = TrackedHeadContext::new()
.reader(&read)
.scan_live_batch_for_generation("branch", generation, None, &request)
.await
.unwrap();
assert_eq!(
previous.len(),
0,
"the publisher's fence hides the old schema"
);
assert!(
!old_catalog
.inherited_catalog_differs_from(&previous, &local, generation)
.unwrap(),
"unchanged hidden schema must not invalidate on a data-only global refresh"
);
assert!(
!inherited(fence)
.inherited_catalog_differs_from(&previous, &local, generation)
.unwrap(),
"a row at the exclusive fence is still hidden"
);
let new_catalog = inherited(new);
assert_eq!(
decode_head_value(&old_catalog.rows[&identity])
.unwrap()
.snapshot,
decode_head_value(&new_catalog.rows[&identity])
.unwrap()
.snapshot
);
assert!(
new_catalog
.inherited_catalog_differs_from(&previous, &local, generation)
.unwrap(),
"identical payload newly crossing the fence changes visibility"
);
let mut writes = StorageWriteSet::new();
TrackedHeadContext::new()
.writer(&read, &mut writes)
.stage_inherited_catalog_refresh("branch", generation, local, new_catalog)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let published = TrackedHeadContext::new()
.reader(&read)
.scan_live_batch_for_generation("branch", generation, None, &request)
.await
.unwrap();
assert_eq!(
published.len(),
1,
"the comparison agrees with the actual publication"
);
}
#[tokio::test]
async fn inherited_catalog_refresh_preserves_owned_bytes_and_removes_retired_cache() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("catalog-refresh");
let identity = |key: &str| HeadRowIdentity {
schema_key: "lix_registered_schema".to_owned(),
row_pk: RowPk::single(key),
file_id: None,
};
let tracked = encoded_test_hot_value(generation, false, false);
let tombstone = encoded_test_hot_value(generation, false, true);
let untracked = encoded_test_hot_value(generation, true, false);
let local = HotRowMap::from([
(identity("owned"), tracked.clone()),
(identity("hidden"), tombstone.clone()),
]);
let mut previous = local.clone();
previous.extend([
(identity("retired"), tracked.clone()),
(identity("inherited"), tracked.clone()),
(identity("private"), untracked.clone()),
]);
let mut writes = StorageWriteSet::new();
stage_complete_hot_rows(&mut writes, "branch", generation, previous);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
HotStateWriter {
store: &read,
writes: &mut writes,
transaction_global_schema_keys: None,
}
.stage_inherited_catalog_refresh(
"branch",
generation,
HotTrackedSnapshot { rows: local },
HotTrackedSnapshot {
rows: HotRowMap::from([
(identity("owned"), tracked.clone()),
(identity("hidden"), tracked.clone()),
(identity("inherited"), tracked.clone()),
(identity("new"), tracked),
]),
},
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("one mutation per key, including replacements");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let filter = TrackedStateFilter {
schema_keys: vec!["lix_registered_schema".to_owned()],
include_tombstones: true,
..TrackedStateFilter::default()
};
let HotScanEntries::Decoded(rows) =
hot_scan_entries(&read, "branch", generation, &filter, None, None)
.await
.unwrap()
.unwrap()
else {
panic!("decoded catalog");
};
let rows = rows
.into_iter()
.map(|(key, value)| (key.into_row_identity(), value))
.collect::<HotRowMap>();
assert_eq!(rows.len(), 5);
assert!(!rows.contains_key(&identity("retired")));
assert_eq!(
rows[&identity("hidden")],
tombstone,
"local tombstone is not overwritten by inheritance"
);
assert_eq!(
rows[&identity("private")],
untracked,
"history-free bytes are untouched"
);
assert!(
decode_head_value(&rows[&identity("new")])
.unwrap()
.commit_id
.is_some()
);
}
#[test]
fn exact_collection_member_rejects_noncanonical_domain_tombstone_and_order() {
const BRANCH_ID: &str = "closure-member-branch";
const SCHEMA_KEY: &str = "closure_member_schema";
let generation = CommitId::for_test_label("closure-member-generation");
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
};
let scope_prefix = hot_scope_prefix(BRANCH_ID, generation);
let identity = HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("member-a"),
file_id: None,
};
let missing_row_pk = RowPk::single("missing-member");
let missing_identity = TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: None,
};
let required_identity = TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &identity.row_pk,
file_id: None,
};
let key =
encode_hot_row_key_parts(BRANCH_ID, generation, SCHEMA_KEY, &identity.row_pk, None);
let untracked = encoded_test_hot_value(generation, true, false);
validate_exact_collection_member(
BRANCH_ID,
generation,
&scope_prefix,
scope,
missing_identity,
true,
&key,
&untracked,
)
.expect("live untracked member should validate");
let tracked = encoded_test_hot_value(generation, false, false);
let wrong_domain = validate_exact_collection_member(
BRANCH_ID,
generation,
&scope_prefix,
scope,
required_identity,
true,
&key,
&tracked,
)
.expect_err("tracked member must not satisfy an untracked closure");
assert!(wrong_domain.message.contains("wrong state domain"));
let tombstone = encoded_test_hot_value(generation, false, true);
let tombstone_error = validate_exact_collection_member(
BRANCH_ID,
generation,
&scope_prefix,
scope,
required_identity,
false,
&key,
&tombstone,
)
.expect_err("tombstone must not satisfy a live closure");
assert!(tombstone_error.message.contains("tombstone"));
let mut malformed = key.clone();
malformed.pop();
assert!(
validate_exact_collection_member(
BRANCH_ID,
generation,
&scope_prefix,
scope,
missing_identity,
true,
&malformed,
&untracked,
)
.is_err()
);
let mut noncanonical = key.clone();
noncanonical.push(0);
let noncanonical_error = validate_canonical_exact_collection_key(&noncanonical, &key)
.expect_err("raw and canonical encodings must match byte-for-byte");
assert!(noncanonical_error.message.contains("non-canonical"));
let mut digest = CompleteHotCollectionDigest::new(BRANCH_ID, generation, scope);
digest
.push(&identity, &key)
.expect("first canonical identity should hash");
let duplicate = digest
.push(&identity, &key)
.expect_err("duplicate canonical identity must fail");
assert!(duplicate.message.contains("duplicate canonical identity"));
let high_identity = HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("member-z"),
file_id: None,
};
let high_key = encode_hot_row_key_parts(
BRANCH_ID,
generation,
SCHEMA_KEY,
&high_identity.row_pk,
None,
);
let mut out_of_order = CompleteHotCollectionDigest::new(BRANCH_ID, generation, scope);
out_of_order
.push(&high_identity, &high_key)
.expect("first high identity should hash");
let ordering = out_of_order
.push(&identity, &key)
.expect_err("descending identity must fail");
assert!(ordering.message.contains("not in canonical order"));
}
#[tokio::test]
async fn complete_collection_digest_closes_typed_file_members_and_authenticated_empty() {
const BRANCH_ID: &str = "closure-file-branch";
const SCHEMA_KEY: &str = "closure_file_schema";
let generation = CommitId::for_test_label("closure-file-generation");
let mut rows = HotRowMap::new();
let typed_pk = RowPk::from_components(smallvec::smallvec![
crate::row_pk::RowPkComponent::Integer(-7),
crate::row_pk::RowPkComponent::Bytes(Bytes::from_static(b"typed")),
])
.expect("typed composite primary key");
for (row_pk, file_id) in [
(RowPk::single("unfiled"), None),
(typed_pk, Some("a.lix".to_owned())),
(RowPk::single("file-string"), Some("b.lix".to_owned())),
] {
rows.insert(
HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk,
file_id,
},
encoded_test_hot_value(generation, false, false),
);
}
let storage = StorageAdapter::new(Memory::new());
let mut writes = StorageWriteSet::new();
stage_complete_collection_controls(&mut writes, BRANCH_ID, generation, &rows)
.expect("complete controls should stage");
stage_complete_hot_rows(&mut writes, BRANCH_ID, generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("typed file fixture should publish");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("typed file fixture should read");
let reader = HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
};
let missing_row_pk = RowPk::single("missing-member");
let missing_identity = TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: None,
};
reader
.validate_exact_collection_closure(
BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
},
missing_identity,
HotStateReadDomain::Tracked,
false,
)
.await
.expect("schema scope should close in canonical file/member order");
reader
.validate_exact_collection_closure(
BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: Some("a.lix"),
},
TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: Some("a.lix"),
},
HotStateReadDomain::Tracked,
false,
)
.await
.expect("typed file scope should close with complete PK encoding");
const EMPTY_SCHEMA_KEY: &str = "authenticated_empty_schema";
let empty_scope = crate::collection_generation::CollectionScopeRef {
schema_key: EMPTY_SCHEMA_KEY,
file_id: None,
};
let marker_identity = HeadRowIdentity {
schema_key: crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
row_pk: RowPk::single(crate::collection_generation::collection_scope_key(
empty_scope,
)),
file_id: None,
};
let marker_rows = HotRowMap::from([(
marker_identity,
encoded_test_hot_value(generation, false, false),
)]);
let mut writes = StorageWriteSet::new();
stage_complete_collection_controls(&mut writes, BRANCH_ID, generation, &marker_rows)
.expect("authenticated empty control should stage");
stage_complete_hot_rows(&mut writes, BRANCH_ID, generation, marker_rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("authenticated empty fixture should publish");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("authenticated empty fixture should read");
HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
}
.validate_exact_collection_closure(
BRANCH_ID,
generation,
empty_scope,
TrackedStateKeyRef {
schema_key: EMPTY_SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: None,
},
HotStateReadDomain::Tracked,
false,
)
.await
.expect("explicit empty control should authenticate an empty scope");
}
#[tokio::test]
async fn empty_root_local_witness_does_not_mask_removed_mutated_control() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("empty-local-witness");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = storage.new_write_set();
let context = TrackedHeadContext::new();
let mut writer = context.writer(&read, &mut writes);
writer.stage_root_current_base(crate::GLOBAL_BRANCH_ID, generation, generation);
writer
.stage_empty_root_deterministic_witness(crate::GLOBAL_BRANCH_ID, generation)
.unwrap();
drop(read);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let pk = RowPk::single("lix_deterministic_mode");
let identity = TrackedStateKeyRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
row_pk: &pk,
file_id: None,
};
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
context
.reader(&read)
.validate_deterministic_setting_absence(generation, identity, false)
.await
.unwrap();
drop(read);
let mut rows = HotRowMap::new();
rows.insert(
HeadRowIdentity {
schema_key: EXACT_CLOSURE_SCHEMA_KEY.into(),
row_pk: RowPk::single("other-local-setting"),
file_id: None,
},
encoded_test_hot_value(generation, true, false),
);
let mut writes = storage.new_write_set();
stage_complete_collection_controls(&mut writes, crate::GLOBAL_BRANCH_ID, generation, &rows)
.unwrap();
stage_complete_hot_rows(&mut writes, crate::GLOBAL_BRANCH_ID, generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let mut writes = storage.new_write_set();
writes.delete(
COLLECTION_CONTROL_SPACE,
StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
},
))),
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let error = context
.reader(&read)
.validate_deterministic_setting_absence(generation, identity, false)
.await
.unwrap_err();
assert!(error.message.contains("collection control is missing"));
}
#[tokio::test]
async fn witness_migration_preserves_replaced_global_collection_with_existing_settings() {
let generation = CommitId::for_test_label("witness-replaced-serving");
let mut ordered = [
CommitId::for_test_label("witness-old"),
CommitId::for_test_label("witness-fence"),
];
ordered.sort();
let [old, fence] = ordered;
assert_ne!(generation, fence);
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: EXACT_CLOSURE_SCHEMA_KEY,
file_id: None,
};
let mut rows = HotRowMap::new();
for name in [
"lix_deterministic_mode",
"lix_deterministic_sequence_number",
] {
rows.insert(
HeadRowIdentity {
schema_key: EXACT_CLOSURE_SCHEMA_KEY.to_owned(),
row_pk: RowPk::single(name),
file_id: None,
},
encoded_test_hot_value(generation, true, false),
);
}
rows.insert(
HeadRowIdentity {
schema_key: EXACT_CLOSURE_SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("retired"),
file_id: None,
},
encoded_test_hot_value(old, false, false),
);
rows.insert(
HeadRowIdentity {
schema_key: crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY
.to_owned(),
row_pk: RowPk::single(crate::collection_generation::collection_scope_key(scope)),
file_id: None,
},
encoded_test_hot_value(fence, false, false),
);
let storage = StorageAdapter::new(Memory::new());
let mut writes = storage.new_write_set();
stage_complete_collection_controls(&mut writes, crate::GLOBAL_BRANCH_ID, generation, &rows)
.unwrap();
stage_complete_hot_rows(&mut writes, crate::GLOBAL_BRANCH_ID, generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let key = StorageKey(Bytes::from(hot_collection_control_key(
crate::GLOBAL_BRANCH_ID,
generation,
scope,
)));
let mut remove = storage.new_write_set();
remove.delete(DETERMINISTIC_IDENTITY_WITNESS_SPACE, key.clone());
storage
.commit_write_set(remove, StorageWriteOptions::default())
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let control =
load_stored_hot_collection_control(&read, crate::GLOBAL_BRANCH_ID, generation, scope)
.await
.unwrap()
.unwrap();
assert_eq!(control.active_generation, fence);
assert_eq!(
control.live_count, 2,
"retired physical member must not enter the closure"
);
let mut migration = storage.new_write_set();
let preconditions = stage_deterministic_identity_witness_migration(
&read,
&mut migration,
generation,
1,
100,
1024 * 1024,
)
.await
.unwrap();
drop(read);
storage
.commit_write_set(
migration,
StorageWriteOptions {
preconditions,
..Default::default()
},
)
.await
.unwrap();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let witness = PointReadPlan::new(DETERMINISTIC_IDENTITY_WITNESS_SPACE, &[key])
.materialize(&read, StorageGetOptions::default())
.await
.unwrap()
.value;
let Some(Some(StorageProjectedValue::FullValue(bytes))) = witness.first() else {
panic!("migration must produce witness");
};
let witness: DeterministicIdentityWitness =
storage_codec::decode("witness", bytes).unwrap();
assert_eq!(witness.presence, 3);
assert_eq!(
witness.collection_control,
storage_codec::encode("control", &control).unwrap()
);
}
#[tokio::test]
async fn collection_generation_fence_retires_stale_tracked_rows_but_not_untracked_rows() {
const BRANCH_ID: &str = "fence-branch";
const SCHEMA_KEY: &str = "fence_schema";
let mut ordered = [
CommitId::for_test_label("fence-commit-a"),
CommitId::for_test_label("fence-commit-b"),
];
ordered.sort();
let [old_generation, fence_commit] = ordered;
let generation = CommitId::for_test_label("fence-serving-generation");
assert_ne!(
generation, fence_commit,
"an active fence requires the control to name a different generation"
);
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
};
let stale_tracked = HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("stale-tracked"),
file_id: None,
};
let untracked = HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("live-untracked"),
file_id: None,
};
let marker = HeadRowIdentity {
schema_key: crate::collection_generation::COLLECTION_GENERATION_SCHEMA_KEY.to_owned(),
row_pk: RowPk::single(crate::collection_generation::collection_scope_key(scope)),
file_id: None,
};
let rows = HotRowMap::from([
(
stale_tracked,
encoded_test_hot_value(old_generation, false, false),
),
(
untracked.clone(),
encoded_test_hot_value(generation, true, false),
),
(marker, encoded_test_hot_value(fence_commit, false, false)),
]);
let storage = StorageAdapter::new(Memory::new());
let mut writes = StorageWriteSet::new();
stage_complete_collection_controls(&mut writes, BRANCH_ID, generation, &rows)
.expect("fence controls should stage");
stage_complete_hot_rows(&mut writes, BRANCH_ID, generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("fence fixture should publish");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("fence fixture should read");
let reader = HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
};
let batch = reader
.scan_live_batch_for_generation(
BRANCH_ID,
generation,
None,
&TrackedStateScanRequest {
filter: TrackedStateFilter {
schema_keys: vec![SCHEMA_KEY.to_owned()],
..TrackedStateFilter::default()
},
read_columns: TrackedStateReadColumns::default(),
limit: None,
},
)
.await
.expect("fenced scope should scan");
let surviving = batch
.iter()
.map(|row| (row.row_pk().clone(), row.untracked()))
.collect::<Vec<_>>();
assert_eq!(
surviving,
vec![(RowPk::single("live-untracked"), true)],
"the fence must retire the stale tracked row and keep the untracked row"
);
}
#[tokio::test]
async fn exact_collection_closure_distinguishes_bootstrap_from_published_missing_digest() {
const BRANCH_ID: &str = "closure-bootstrap-branch";
const SCHEMA_KEY: &str = "closure_bootstrap_schema";
let generation = CommitId::for_test_label("closure-bootstrap-generation");
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
};
let missing_row_pk = RowPk::single("missing-member");
let required_identity = TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: None,
};
let storage = StorageAdapter::new(Memory::new());
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("bootstrap read should open");
let reader = HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
};
reader
.validate_exact_collection_closure(
BRANCH_ID,
generation,
scope,
required_identity,
HotStateReadDomain::Untracked,
true,
)
.await
.expect("an explicitly allowed empty bootstrap may omit its control");
let error = reader
.validate_exact_collection_closure(
BRANCH_ID,
generation,
scope,
required_identity,
HotStateReadDomain::Untracked,
false,
)
.await
.expect_err("a published empty scope must carry its exact control");
assert!(error.message.contains("missing its exact control"));
drop(read);
let mut writes = StorageWriteSet::new();
stage_hot_collection_control(
&mut writes,
BRANCH_ID,
generation,
scope,
HotCollectionControl {
active_generation: generation,
live_count: 0,
ordered_identity_digest: None,
},
)
.expect("digestless published control should encode as a corruption fixture");
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("digestless corruption fixture should publish");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("published corruption read should open");
let error = HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
}
.validate_exact_collection_closure(
BRANCH_ID,
generation,
scope,
required_identity,
HotStateReadDomain::Untracked,
true,
)
.await
.expect_err("bootstrap allowance must not accept a published digestless control");
assert!(error.message.contains("no exact identity digest"));
}
#[tokio::test]
async fn exact_collection_closure_rejects_missing_malformed_stale_and_forged_controls() {
const BRANCH_ID: &str = "closure-control-branch";
const SCHEMA_KEY: &str = "closure_control_schema";
let generation = CommitId::for_test_label("closure-control-generation");
let scope = crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
};
let rows = HotRowMap::from([(
HeadRowIdentity {
schema_key: SCHEMA_KEY.to_owned(),
row_pk: RowPk::single("member"),
file_id: None,
},
encoded_test_hot_value(generation, false, false),
)]);
let memory = Memory::new();
let storage = StorageAdapter::new(memory.clone());
let mut writes = StorageWriteSet::new();
stage_complete_collection_controls(&mut writes, BRANCH_ID, generation, &rows)
.expect("base control should stage");
stage_complete_hot_rows(&mut writes, BRANCH_ID, generation, rows);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("base closure fixture should publish");
let base = memory.fork().expect("base fixture fork");
drop(storage);
drop(memory);
let missing_row_pk = RowPk::single("missing-member");
for (label, expected) in [
("missing", "missing its exact control"),
("malformed", "hot collection control"),
("stale", "stale generation"),
("no-digest", "no exact identity digest"),
("forged", "identity digest"),
] {
let storage = StorageAdapter::new(base.fork().expect("fork base closure fixture"));
let control_key = StorageKey(Bytes::from(hot_collection_control_key(
BRANCH_ID, generation, scope,
)));
let mut writes = StorageWriteSet::new();
match label {
"missing" => writes.delete(COLLECTION_CONTROL_SPACE, control_key),
"malformed" => writes.put(
COLLECTION_CONTROL_SPACE,
control_key,
StorageValue {
bytes: Bytes::from_static(b"\0"),
},
),
"stale" => stage_hot_collection_control(
&mut writes,
BRANCH_ID,
generation,
scope,
HotCollectionControl {
active_generation: CommitId::for_test_label("stale-generation"),
live_count: 1,
ordered_identity_digest: Some([0; 32]),
},
)
.expect("stale control should encode"),
"no-digest" => stage_hot_collection_control(
&mut writes,
BRANCH_ID,
generation,
scope,
HotCollectionControl {
active_generation: generation,
live_count: 1,
ordered_identity_digest: None,
},
)
.expect("digest-free control should encode"),
"forged" => stage_hot_collection_control(
&mut writes,
BRANCH_ID,
generation,
scope,
HotCollectionControl {
active_generation: generation,
live_count: 1,
ordered_identity_digest: Some([0; 32]),
},
)
.expect("forged control should encode"),
_ => unreachable!("closed corruption fixture set"),
}
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("control corruption should publish below the reader");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("corrupt control fixture should read");
let error = HotStateStoreReader {
store: &read,
transaction_cache: None,
root_base_cache: None,
}
.validate_exact_collection_closure(
BRANCH_ID,
generation,
scope,
TrackedStateKeyRef {
schema_key: SCHEMA_KEY,
row_pk: &missing_row_pk,
file_id: None,
},
HotStateReadDomain::Tracked,
false,
)
.await
.expect_err("corrupt exact control must fail closed");
assert!(
error.message.contains(expected),
"unexpected {label} control error: {error:?}"
);
}
}
fn live_row(row_pk: &str, commit_label: &str) -> MaterializedHotStateRow {
MaterializedHotStateRow {
row_pk: RowPk::single(row_pk),
schema_key: "schema".to_owned(),
file_id: None,
snapshot_content: None,
metadata: None,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
global: false,
change_id: Some(ChangeId::for_test_label(&format!("change-{commit_label}"))),
commit_id: Some(CommitId::for_test_label(commit_label)),
untracked: false,
branch_id: Arc::from("branch"),
}
}
#[test]
fn sparse_hot_overlay_merges_with_packed_rows_in_identity_order() {
let hot = vec![live_row("c", "hot-c")];
let packed = vec![live_row("a", "packed-a"), live_row("b", "packed-b")];
let merged = merge_ordered_live_rows(hot, packed);
assert_eq!(
merged
.iter()
.map(|row| row.row_pk.as_single_string_owned().expect("single key"))
.collect::<Vec<_>>(),
["a", "b", "c"]
);
}
#[test]
fn ordered_authority_exclusion_removes_only_identity_collisions() {
let rows = MaterializedHotStateBatch::from_rows(vec![
live_row("a", "candidate-a"),
live_row("b", "candidate-b"),
live_row("c", "candidate-c"),
live_row("d", "candidate-d"),
]);
let authority = MaterializedHotStateBatch::from_rows(vec![
live_row("a", "authority-a"),
live_row("c", "authority-c"),
]);
let filtered = exclude_ordered_live_batch_identities(rows, &authority);
assert_eq!(
filtered
.iter()
.map(|row| { row.row_pk().as_single_string_owned().expect("single key") })
.collect::<Vec<_>>(),
["b", "d"]
);
}
#[tokio::test]
async fn packed_mutation_lookup_retains_large_refs_and_finds_system_schemas() {
const COMMIT_LABEL: &str = "packed-system-schema-base";
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label(COMMIT_LABEL);
let row_pk = RowPk::single("packed-system-row");
let snapshot = serde_json::json!({
"key": "packed-system-row",
"value": "x".repeat(1024 + 1),
})
.to_string();
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
crate::GLOBAL_BRANCH_ID,
COMMIT_LABEL,
&[MaterializedTrackedStateRow {
row_pk: row_pk.clone(),
schema_key: "lix_key_value".to_owned(),
file_id: None,
snapshot_content: Some(snapshot.into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: timestamp().to_string(),
updated_at: timestamp().to_string(),
change_id: ChangeId::for_test_label("packed-system-change"),
commit_id: generation,
}],
)
.await;
let mut manifest_key = hot_scope_prefix(crate::GLOBAL_BRANCH_ID, generation);
manifest_key.extend_from_slice(generation.as_uuid().as_bytes());
let mut writes = StorageWriteSet::new();
writes.delete(
ROW_SPACE,
StorageKey(Bytes::from(encode_hot_row_key(&HeadIdentity {
branch_id: crate::GLOBAL_BRANCH_ID.to_owned(),
generation,
schema_key: "lix_key_value".to_owned(),
row_pk: row_pk.clone(),
file_id: None,
}))),
);
writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(manifest_key)),
StorageValue {
bytes: Bytes::from_static(&[0; 16]),
},
);
writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(
crate::GLOBAL_BRANCH_ID,
generation,
))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("publish packed system-schema fixture");
let json_get_many_calls = Arc::new(AtomicUsize::new(0));
let get_many_calls = Arc::new(AtomicUsize::new(0));
let scan_calls = Arc::new(AtomicUsize::new(0));
let read = JsonCountingRead {
inner: CountingRead {
inner: storage
.begin_read(StorageReadOptions::default())
.await
.expect("open packed fixture read"),
get_many_calls: Arc::clone(&get_many_calls),
scan_calls: Some(Arc::clone(&scan_calls)),
},
json_get_many_calls: Arc::clone(&json_get_many_calls),
};
let transaction_cache = Arc::new(HotStateTransactionCache::default());
let request_keys = [TrackedStateKeyRef {
schema_key: "lix_key_value",
row_pk: &row_pk,
file_id: None,
}];
let entries = load_packed_current_base_exact_entries(
&read,
crate::GLOBAL_BRANCH_ID,
generation,
&request_keys,
&[],
Some(transaction_cache.as_ref()),
)
.await
.expect("load packed mutation predecessor");
for _ in 2..=TRANSACTION_PACKED_POINT_CACHE_MIN_OBSERVATIONS {
let observed = load_packed_current_base_exact_entries(
&read,
crate::GLOBAL_BRANCH_ID,
generation,
&request_keys,
&[],
Some(transaction_cache.as_ref()),
)
.await
.expect("observe repeated packed mutation predecessor");
assert!(observed[0].is_some());
}
let admitted = load_packed_current_base_exact_entries(
&read,
crate::GLOBAL_BRANCH_ID,
generation,
&request_keys,
&[],
Some(transaction_cache.as_ref()),
)
.await
.expect("admit repeated packed mutation predecessor");
assert!(admitted[0].is_some());
let admitted_read_counts = (
get_many_calls.load(Ordering::Relaxed),
scan_calls.load(Ordering::Relaxed),
);
let reused = load_packed_current_base_exact_entries(
&read,
crate::GLOBAL_BRANCH_ID,
generation,
&request_keys,
&[],
Some(transaction_cache.as_ref()),
)
.await
.expect("reuse admitted packed mutation predecessor");
assert!(reused[0].is_some());
assert_eq!(
(
get_many_calls.load(Ordering::Relaxed),
scan_calls.load(Ordering::Relaxed),
),
admitted_read_counts,
"a transaction snapshot must reuse an admitted packed segment by immutable address"
);
let (_, change, _, _) = entries[0].as_ref().expect("packed predecessor exists");
assert!(
change.snapshot.is_some(),
"mutation lookup must retain the native typed payload"
);
assert_eq!(
json_get_many_calls.load(Ordering::Relaxed),
0,
"mutation predecessor lookup must not read large JSON payloads"
);
let reader = HotStateStoreReader {
store: read,
transaction_cache: Some(transaction_cache),
root_base_cache: None,
};
let control = BranchHeadControl {
head_commit_id: generation,
tracked_generation: generation,
current_state_revision: 0,
schema_presence_bloom: [u64::MAX; 4],
working_diff_checkpoint_commit_id: None,
created_at: timestamp(),
updated_at: timestamp(),
ref_change_id: ChangeId::for_test_label("packed-system-ref"),
};
assert!(
reader
.has_schema_rows(crate::GLOBAL_BRANCH_ID, control, "lix_key_value",)
.await
.expect("probe packed system schema"),
"engine-owned schemas must probe packed bases before skipping plugin segments"
);
let exact = reader
.load_projected_live_batch_refs(
crate::GLOBAL_BRANCH_ID,
control,
&[TrackedStateKeyRef {
schema_key: "lix_key_value",
row_pk: &row_pk,
file_id: None,
}],
&ChangeRecordProjection::full(),
)
.await
.expect("load packed system row through exact live-state API");
assert!(
exact.row(0).is_some(),
"engine-owned exact lookups must resolve packed bases before skipping plugin segments"
);
}
#[tokio::test]
async fn complete_replacement_retires_only_exclusive_schema_bases() {
const BRANCH_ID: &str = "exclusive-schema-replacement";
const SCHEMA_KEY: &str = "target_schema";
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("exclusive-generation");
let shared_head = CommitId::for_test_label("shared-base");
let exclusive_head = CommitId::for_test_label("exclusive-base");
let replacement_head = CommitId::for_test_label("replacement-base");
let mut fixture_writes = StorageWriteSet::new();
stage_hot_collection_control(
&mut fixture_writes,
BRANCH_ID,
generation,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
},
HotCollectionControl {
active_generation: generation,
live_count: 1_024,
ordered_identity_digest: None,
},
)
.expect("stage replacement collection control");
for head in [shared_head, exclusive_head] {
let mut key = hot_scope_prefix(BRANCH_ID, generation);
key.extend_from_slice(head.as_uuid().as_bytes());
fixture_writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(key)),
StorageValue {
bytes: Bytes::from_static(&[0; 16]),
},
);
}
fixture_writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(hot_scope_prefix(BRANCH_ID, generation))),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
stage_packed_exclusive_schema_base_ref(
&mut fixture_writes,
BRANCH_ID,
generation,
SCHEMA_KEY,
exclusive_head,
);
storage
.commit_write_set(fixture_writes, StorageWriteOptions::default())
.await
.expect("commit packed replacement fixture");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("open packed replacement read");
let mut writes = StorageWriteSet::new();
let mut coverage = WorkingDiffIndexCoverage::default();
let (_, retired) = HotStateWriter {
store: &read,
writes: &mut writes,
transaction_global_schema_keys: None,
}
.stage_complete_collection_replacement_current_base(
BRANCH_ID,
generation,
replacement_head,
SCHEMA_KEY,
1_024,
&crate::hot_state::RowColumnarWriteSets::new(),
None,
&mut coverage,
)
.await
.expect("stage complete packed replacement");
assert!(retired);
drop(read);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit complete packed replacement");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("verify complete packed replacement");
let base_keys = [shared_head, exclusive_head, replacement_head]
.into_iter()
.map(|head| {
let mut key = hot_scope_prefix(BRANCH_ID, generation);
key.extend_from_slice(head.as_uuid().as_bytes());
StorageKey(Bytes::from(key))
})
.collect::<Vec<_>>();
let bases = PointReadPlan::new(PACKED_CURRENT_BASE_SPACE, &base_keys)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read replacement bases")
.value;
assert!(
bases[0].is_some(),
"a shared-schema base must remain visible"
);
assert!(bases[1].is_none(), "the exclusive predecessor must retire");
assert!(bases[2].is_some(), "the replacement base must publish");
}
#[tokio::test]
async fn corrupt_collection_control_cannot_certify_omitted_authoritative_members() {
const BRANCH_ID: &str = "01920000-0000-7000-8000-0000000000ca";
const SCHEMA_KEY: &str = "corrupt_control_schema";
let storage = StorageAdapter::new(Memory::new());
let parent_commit_id = CommitId::for_test_label("corrupt-control-parent");
let row_pks = [
RowPk::single("row-a"),
RowPk::single("row-b"),
RowPk::single("row-c"),
];
let created_at = timestamp();
let rows = row_pks
.iter()
.enumerate()
.map(|(index, row_pk)| MaterializedTrackedStateRow {
row_pk: row_pk.clone(),
schema_key: SCHEMA_KEY.to_owned(),
file_id: None,
snapshot_content: Some(format!(r#"{{"index":{index}}}"#).into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
change_id: ChangeId::for_test_label(&format!("corrupt-control-{index}")),
commit_id: parent_commit_id,
})
.collect::<Vec<_>>();
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
BRANCH_ID,
"corrupt-control-parent",
&rows,
)
.await;
let forged_members = [&row_pks[0], &row_pks[1]];
let forged_digest =
crate::collection_generation::ordered_single_string_identity_digest(forged_members)
.expect("single-string fixture identities should hash");
let mut corrupt_writes = StorageWriteSet::new();
stage_hot_collection_control(
&mut corrupt_writes,
BRANCH_ID,
parent_commit_id,
crate::collection_generation::CollectionScopeRef {
schema_key: SCHEMA_KEY,
file_id: None,
},
HotCollectionControl {
active_generation: parent_commit_id,
live_count: 2,
ordered_identity_digest: Some(forged_digest),
},
)
.expect("forged derived control should encode");
storage
.commit_write_set(corrupt_writes, StorageWriteOptions::default())
.await
.expect("forged derived control should commit");
let new_head = CommitId::for_test_label("corrupt-control-child");
let change_ids = [
ChangeId::for_test_label("corrupt-control-child-a"),
ChangeId::for_test_label("corrupt-control-child-b"),
];
let replacement_snapshots = forged_members
.iter()
.map(|row_pk| {
let row = WasmTypedRow::from_test_json_unchecked(
row_pk,
&serde_json::json!({"replacement": true}),
)
.expect("replacement snapshot should type");
encode_snapshot(&row).expect("replacement snapshot should encode")
})
.collect::<Vec<_>>();
let replacement_deltas = forged_members
.iter()
.zip(change_ids)
.zip(&replacement_snapshots)
.map(|((row_pk, change_id), snapshot)| CurrentStateDeltaRef {
schema_key: SCHEMA_KEY,
file_id: None,
row_pk,
change_id: Some(change_id),
commit_id: Some(new_head),
untracked: false,
deleted: false,
created_at,
updated_at: created_at,
snapshot: Some(snapshot),
metadata: None,
columnar_base_coordinate: None,
})
.collect::<Vec<_>>();
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("corrupt-control read should open");
let mut replacement_writes = StorageWriteSet::new();
let replacement = HotStateWriter {
store: &read,
writes: &mut replacement_writes,
transaction_global_schema_keys: None,
}
.try_stage_exact_collection_replacement_current_base(
BRANCH_ID,
parent_commit_id,
parent_commit_id,
false,
new_head,
&replacement_deltas,
&crate::hot_state::RowColumnarWriteSets::new(),
None,
&mut WorkingDiffIndexCoverage::default(),
)
.await
.expect("authority mismatch should fail closed");
assert_eq!(replacement, None);
assert_eq!(replacement_writes.stats().staged_puts, 0);
assert_eq!(replacement_writes.stats().staged_deletes, 0);
let deletion_deltas = replacement_deltas
.iter()
.map(|delta| CurrentStateDeltaRef {
deleted: true,
snapshot: None,
..*delta
})
.collect::<Vec<_>>();
let mut deletion_writes = StorageWriteSet::new();
let deletion = HotStateWriter {
store: &read,
writes: &mut deletion_writes,
transaction_global_schema_keys: None,
}
.try_stage_exact_collection_delete_current_base(
BRANCH_ID,
parent_commit_id,
parent_commit_id,
new_head,
&deletion_deltas,
None,
)
.await
.expect("delete authority mismatch should fail closed");
assert_eq!(deletion, None);
assert_eq!(deletion_writes.stats().staged_puts, 0);
assert_eq!(deletion_writes.stats().staged_deletes, 0);
}
#[tokio::test]
async fn checkpoint_retires_materialized_packed_bases_in_active_generation() {
const BRANCH_ID: &str = "01920000-0000-7000-8000-0000000000c9";
const COMMIT_LABEL: &str = "checkpoint-packed-base";
const SCHEMA_KEY: &str = "checkpoint_schema";
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label(COMMIT_LABEL);
let row_pk = RowPk::single("packed-row");
let created_at = timestamp();
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
BRANCH_ID,
COMMIT_LABEL,
&[MaterializedTrackedStateRow {
row_pk: row_pk.clone(),
schema_key: SCHEMA_KEY.to_owned(),
file_id: None,
snapshot_content: Some(r#"{"key":"packed-row"}"#.into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
change_id: ChangeId::for_test_label("checkpoint-packed-base-change"),
commit_id: generation,
}],
)
.await;
let mut manifest_key = hot_scope_prefix(BRANCH_ID, generation);
manifest_key.extend_from_slice(generation.as_uuid().as_bytes());
let control_key = hot_scope_prefix(BRANCH_ID, generation);
let index_key =
packed_exclusive_schema_base_key(BRANCH_ID, generation, SCHEMA_KEY, generation);
let mut fixture_writes = StorageWriteSet::new();
fixture_writes.delete(
ROW_SPACE,
StorageKey(Bytes::from(encode_hot_row_key(&HeadIdentity {
branch_id: BRANCH_ID.to_owned(),
generation,
schema_key: SCHEMA_KEY.to_owned(),
row_pk: row_pk.clone(),
file_id: None,
}))),
);
fixture_writes.put(
PACKED_CURRENT_BASE_SPACE,
StorageKey(Bytes::from(manifest_key.clone())),
StorageValue {
bytes: Bytes::from_static(&[0; 16]),
},
);
fixture_writes.put(
PACKED_CURRENT_BASE_CONTROL_SPACE,
StorageKey(Bytes::from(control_key.clone())),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
fixture_writes.put(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
StorageKey(Bytes::from(index_key.clone())),
StorageValue {
bytes: Bytes::from_static(&[1]),
},
);
storage
.commit_write_set(fixture_writes, StorageWriteOptions::default())
.await
.expect("publish packed checkpoint fixture");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("open packed checkpoint read");
let checkpoint_head = CommitId::for_test_label("checkpoint-packed-head");
let checkpoint_change = ChangeId::for_test_label("checkpoint-packed-base-change");
let typed = WasmTypedRow::from_test_json_unchecked(
&row_pk,
&serde_json::json!({"key": "packed-row"}),
)
.expect("packed checkpoint fixture should type");
let snapshot = typed.durable_payload().expect("typed payload");
let delta = CurrentStateDeltaRef {
schema_key: SCHEMA_KEY,
file_id: None,
row_pk: &row_pk,
change_id: Some(checkpoint_change),
commit_id: Some(generation),
untracked: false,
deleted: false,
created_at,
updated_at: created_at,
snapshot: Some(snapshot.as_ref()),
metadata: None,
columnar_base_coordinate: None,
};
let mut checkpoint_writes = StorageWriteSet::new();
let mut coverage = WorkingDiffIndexCoverage::default();
HotStateWriter {
store: &read,
writes: &mut checkpoint_writes,
transaction_global_schema_keys: None,
}
.stage_checkpoint_current_state(
BRANCH_ID,
generation,
checkpoint_head,
&[delta],
&BTreeSet::new(),
generation,
&mut coverage,
)
.await
.expect("stage checkpoint over packed base");
drop(read);
storage
.commit_write_set(checkpoint_writes, StorageWriteOptions::default())
.await
.expect("commit checkpoint over packed base");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("verify packed checkpoint retirement");
let packed = PointReadPlan::new(
PACKED_CURRENT_BASE_SPACE,
&[StorageKey(Bytes::from(manifest_key))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read retired packed manifest")
.value;
let control = PointReadPlan::new(
PACKED_CURRENT_BASE_CONTROL_SPACE,
&[StorageKey(Bytes::from(control_key))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read retired packed control")
.value;
assert!(packed[0].is_none());
assert!(control[0].is_none());
let index = PointReadPlan::new(
PACKED_CURRENT_EXCLUSIVE_SCHEMA_BASE_SPACE,
&[StorageKey(Bytes::from(index_key))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read retired packed exclusive-schema index")
.value;
assert!(index[0].is_none());
let hot = PointReadPlan::new(
ROW_SPACE,
&[StorageKey(Bytes::from(encode_hot_row_key(&HeadIdentity {
branch_id: BRANCH_ID.to_owned(),
generation,
schema_key: SCHEMA_KEY.to_owned(),
row_pk,
file_id: None,
})))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read materialized checkpoint row")
.value;
assert!(
hot[0].is_some(),
"checkpoint must materialize a packed-only row before retiring its base"
);
}
fn diff_identity(branch_id: &str, generation: CommitId, row: &str) -> HeadIdentity {
HeadIdentity {
branch_id: branch_id.to_string(),
generation,
schema_key: "schema".to_string(),
row_pk: RowPk::single(row),
file_id: None,
}
}
fn working_diff_version(label: &str) -> WorkingDiffVersion {
WorkingDiffVersion {
change_id: ChangeId::for_test_label(&format!("{label}-change")),
commit_id: CommitId::for_test_label(&format!("{label}-commit")),
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
metadata: WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
}
}
#[test]
fn packed_recreate_wins_over_older_hot_working_diff_tombstone() {
let baseline = working_diff_version("checkpoint-baseline");
let mut hot = working_diff_version("hot-tombstone");
hot.commit_id = CommitId::new(uuid::Uuid::from_u128(1));
hot.deleted = true;
let mut packed = working_diff_version("packed-recreate");
packed.commit_id = CommitId::new(uuid::Uuid::from_u128(2));
let (before, after) =
choose_hot_or_packed_working_diff(Some((Some(baseline), hot)), Some(packed))
.expect("one current version must win");
assert_eq!(before, Some(baseline));
assert_eq!(after, packed);
assert!(!after.deleted);
}
#[test]
fn packed_exact_keys_are_ordered_and_deduplicated() {
let filter = TrackedStateFilter {
schema_keys: vec![
"schema-b".to_owned(),
"schema-a".to_owned(),
"schema-a".to_owned(),
],
row_pks: vec![
RowPk::single("second"),
RowPk::single("first"),
RowPk::single("first"),
],
file_ids: vec![NullableKeyFilter::Null],
..TrackedStateFilter::default()
};
let keys = packed_exact_keys_for_filter(&filter).expect("filter is finite");
assert_eq!(
keys,
vec![
TrackedStateKey {
schema_key: "schema-a".to_owned(),
file_id: None,
row_pk: RowPk::single("first"),
},
TrackedStateKey {
schema_key: "schema-a".to_owned(),
file_id: None,
row_pk: RowPk::single("second"),
},
TrackedStateKey {
schema_key: "schema-b".to_owned(),
file_id: None,
row_pk: RowPk::single("first"),
},
TrackedStateKey {
schema_key: "schema-b".to_owned(),
file_id: None,
row_pk: RowPk::single("second"),
},
]
);
let bounded = TrackedStateFilter {
schema_keys: vec!["schema".to_owned()],
row_pks: vec![
RowPk::single("first"),
RowPk::single("second"),
RowPk::single("second"),
RowPk::single("third"),
],
row_pk_lower: Some(crate::tracked_state::RowPkRangeBound {
row_pk: RowPk::single("second"),
inclusive: true,
}),
row_pk_upper: Some(crate::tracked_state::RowPkRangeBound {
row_pk: RowPk::single("third"),
inclusive: false,
}),
file_ids: vec![NullableKeyFilter::Null],
..TrackedStateFilter::default()
};
assert_eq!(
packed_exact_keys_for_filter(&bounded).expect("bounded filter is finite"),
vec![TrackedStateKey {
schema_key: "schema".to_owned(),
file_id: None,
row_pk: RowPk::single("second"),
}]
);
let filed = TrackedStateFilter {
schema_keys: vec!["schema".to_owned()],
row_pks: vec![RowPk::single("owner")],
file_ids: vec![
NullableKeyFilter::Value("file-b".to_owned()),
NullableKeyFilter::Null,
NullableKeyFilter::Value("file-a".to_owned()),
],
..TrackedStateFilter::default()
};
assert_eq!(
packed_exact_keys_for_filter(&filed).expect("explicit file filters are finite"),
vec![
TrackedStateKey {
schema_key: "schema".to_owned(),
file_id: None,
row_pk: RowPk::single("owner"),
},
TrackedStateKey {
schema_key: "schema".to_owned(),
file_id: Some("file-a".to_owned()),
row_pk: RowPk::single("owner"),
},
TrackedStateKey {
schema_key: "schema".to_owned(),
file_id: Some("file-b".to_owned()),
row_pk: RowPk::single("owner"),
},
]
);
assert!(
packed_exact_keys_for_filter(&TrackedStateFilter {
schema_keys: vec!["schema".to_owned()],
row_pks: vec![RowPk::single("owner")],
file_ids: vec![NullableKeyFilter::Any],
..TrackedStateFilter::default()
})
.is_none()
);
assert!(
packed_exact_keys_for_filter(&TrackedStateFilter {
schema_keys: vec!["schema".to_owned()],
row_pks: vec![RowPk::single("owner")],
..TrackedStateFilter::default()
})
.is_none()
);
}
fn single_hot_diff_segment(
checkpoint_commit_id: CommitId,
identity: &HeadIdentity,
) -> (Vec<u8>, Vec<u8>) {
let scope = encode_working_diff_scope_prefix(
&identity.branch_id,
checkpoint_commit_id,
identity.generation,
);
let full_key = encode_hot_diff_key(checkpoint_commit_id, identity);
let suffix = full_key
.strip_prefix(scope.as_slice())
.expect("encoded hot diff identity starts with its scope");
let mut value = Vec::with_capacity(HOT_DIFF_SEGMENT_HEADER_BYTES + 4 + suffix.len());
value.push(HOT_DIFF_SEGMENT_VERSION);
value.extend_from_slice(&1_u32.to_le_bytes());
value.extend_from_slice(
&u32::try_from(suffix.len())
.expect("test identity suffix fits u32")
.to_le_bytes(),
);
value.extend_from_slice(suffix);
let mut key = scope;
key.extend_from_slice(blake3::hash(&value).as_bytes());
(key, value)
}
#[tokio::test]
async fn hot_working_diff_entries_share_one_identity_batch() {
let storage = StorageAdapter::new(Memory::new());
let store = storage
.begin_read(StorageReadOptions::default())
.await
.expect("classification read should open");
let candidates = ["first", "second"]
.into_iter()
.map(|row| {
(
TrackedStateKey {
schema_key: "schema".to_owned(),
row_pk: RowPk::single(row),
file_id: Some("file".to_owned()),
},
None,
working_diff_version(row),
)
})
.collect();
let entries = classify_hot_working_diff_entries(&store, candidates)
.await
.expect("valid working diff batch");
assert_eq!(entries.len(), 2);
assert!(entries[0].identity.shares_batch_with(&entries[1].identity));
for entry in &entries {
assert!(
entry
.identity
.shares_key_with(&entry.after.as_ref().expect("after row").identity)
);
}
}
#[tokio::test]
async fn finite_hot_working_diff_borrows_keys_into_one_identity_batch() {
let storage = StorageAdapter::new(Memory::new());
let store = storage
.begin_read(StorageReadOptions::default())
.await
.expect("classification read should open");
let schema_key = String::from("schema");
let file_id = String::from("file");
let row_pks = [RowPk::single("first"), RowPk::single("second")];
let candidates = row_pks
.iter()
.enumerate()
.map(|(index, row_pk)| {
(
TrackedStateKeyRef {
schema_key: &schema_key,
row_pk,
file_id: Some(&file_id),
},
None,
working_diff_version(if index == 0 { "first" } else { "second" }),
)
})
.collect();
let entries = classify_hot_working_diff_entry_refs(&store, candidates)
.await
.expect("valid borrowed diff batch");
assert_eq!(entries.len(), 2);
assert!(entries[0].identity.shares_batch_with(&entries[1].identity));
assert_eq!(entries[0].identity.schema_key(), schema_key);
assert_eq!(entries[1].identity.file_id(), Some(file_id.as_str()));
for entry in &entries {
assert!(
entry
.identity
.shares_key_with(&entry.after.as_ref().expect("after row").identity)
);
}
}
#[test]
fn ten_thousand_finite_hot_identities_share_one_primary_key_arena() {
let generation = CommitId::for_test_label("point-key-generation");
let row_pks = (0..5_000)
.map(|index| RowPk::single(format!("row-{index:05}")))
.collect::<Vec<_>>();
let branch_id = String::from("branch");
let schema_key = String::from("schema");
let batch = FiniteHotIdentityBatchRef::new(
&branch_id,
generation,
&schema_key,
row_pks.iter().collect(),
vec![None, Some("file")],
)
.expect("test identity count is representable");
assert_eq!(batch.len(), 10_000);
assert_eq!(batch.encoded.ranges.len(), batch.len());
assert_eq!(batch.encoded.ranges.capacity(), batch.len());
assert_eq!(
batch
.encoded
.ranges
.last()
.map(|ranges| ranges.primary.offset() + ranges.primary.len()),
Some(batch.encoded.bytes.len())
);
for index in [0, 1, 9_999] {
let identity = batch.key_ref(index);
assert_eq!(batch.branch_id.as_ptr(), branch_id.as_ptr());
assert_eq!(identity.schema_key.as_ptr(), schema_key.as_ptr());
let primary = batch.encoded.ranges[index].primary;
let key = batch.encoded.primary_key(index);
assert_eq!(
key.0.as_ptr(),
batch.encoded.bytes[primary.offset()..].as_ptr(),
"primary point key {index} must remain a slice of the batch arena"
);
}
}
#[test]
fn ten_thousand_hot_scan_identities_borrow_repeated_metadata() {
const ROW_COUNT: usize = 10_000;
let generation = CommitId::for_test_label("borrowed-scan-generation");
let scope = hot_scope_prefix("branch", generation);
let schema_key = "shared_schema";
let file_id = "shared_file";
let row_pks = (0..ROW_COUNT)
.map(|index| RowPk::single(format!("row-{index:05}")))
.collect::<Vec<_>>();
let capacity = row_pks
.iter()
.try_fold(0_usize, |total, row_pk| {
total.checked_add(
encoded_hot_identity_key_len(scope.len(), schema_key, row_pk, Some(file_id))
.expect("test key size is representable"),
)
})
.expect("test key arena size is representable");
let mut key_bytes = Vec::with_capacity(capacity);
let ranges = row_pks
.iter()
.map(|row_pk| {
let start = key_bytes.len();
key_bytes.extend_from_slice(&scope);
write_key_string(&mut key_bytes, schema_key, KEY_PART_FINAL);
write_file_id(&mut key_bytes, Some(file_id));
write_row_pk(&mut key_bytes, row_pk);
start..key_bytes.len()
})
.collect::<Vec<_>>();
assert_eq!(key_bytes.len(), capacity);
let key_bytes = Bytes::from(key_bytes);
let identities = ranges
.into_iter()
.map(|range| {
decode_hot_scan_row_key_in_scope(key_bytes.slice(range), &scope)
.expect("decode borrowed hot scan key")
})
.collect::<Vec<_>>();
assert_eq!(identities.len(), ROW_COUNT);
assert_eq!(
identities
.iter()
.map(HotScanIdentity::owned_metadata_buffer_count)
.sum::<usize>(),
0,
"normal schema and file ids must remain ranges over storage keys"
);
assert!(identities.iter().all(|identity| {
identity.schema_key() == schema_key && identity.file_id() == Some(file_id)
}));
let mut rows = MaterializedHotStateBatchBuilder::with_capacity(ROW_COUNT);
for identity in identities {
identity.push_materialized(
&mut rows,
None,
None,
false,
timestamp(),
timestamp(),
false,
None,
None,
true,
"branch",
);
}
let rows = rows.finish();
assert_eq!(rows.len(), ROW_COUNT);
assert_eq!(rows.dictionary_entry_count(), 3);
assert_eq!(rows.dictionary_arena_buffer_count(), 1);
assert_eq!(
rows.dictionary_arena_allocation_count(),
1,
"materialization should allocate one small identity arena, not per-row buffers"
);
assert_eq!(rows.dictionary_arena_large_allocation_count(), 0);
assert_eq!(
rows.row(0).schema_key().as_ptr(),
rows.row(ROW_COUNT - 1).schema_key().as_ptr()
);
assert_eq!(
rows.row(0).file_id().expect("file").as_ptr(),
rows.row(ROW_COUNT - 1).file_id().expect("file").as_ptr()
);
}
fn adversarial_hot_scan_entry(
generation: CommitId,
row_pk: &str,
file_id: &str,
value: &'static [u8],
) -> (HotScanIdentity, Bytes) {
let scope = hot_scope_prefix("branch", generation);
let key = Bytes::from(encode_hot_row_key_parts(
"branch",
generation,
"schema",
&RowPk::single(row_pk),
Some(file_id),
));
let identity = decode_hot_scan_row_key_in_scope(key, &scope)
.expect("decode adversarial HOT scan identity");
(identity, Bytes::from_static(value))
}
#[test]
fn hot_scan_canonicalizes_before_limit_and_collapses_only_identical_duplicates() {
let generation = CommitId::for_test_label("adversarial-hot-canonical-order");
let physical_rows = || {
vec![
adversarial_hot_scan_entry(generation, "row-z", "file-a", b"z"),
adversarial_hot_scan_entry(generation, "row-z", "file-a", b"z"),
adversarial_hot_scan_entry(generation, "row-a", "file-b", b"a"),
]
};
let canonical = canonicalize_hot_scan_rows(physical_rows(), None)
.expect("identical repeated HOT observations should canonicalize");
assert_eq!(canonical.len(), 2);
assert_eq!(
canonical
.iter()
.map(|(identity, _)| (identity.row_pk.clone(), identity.file_id()))
.collect::<Vec<_>>(),
[
(RowPk::single("row-a"), Some("file-b")),
(RowPk::single("row-z"), Some("file-a")),
]
);
let limited = canonicalize_hot_scan_rows(physical_rows(), Some(1))
.expect("LIMIT should apply after HOT canonicalization");
assert_eq!(limited.len(), 1);
assert_eq!(limited[0].0.row_pk, RowPk::single("row-a"));
assert_eq!(limited[0].0.file_id(), Some("file-b"));
}
#[test]
fn hot_scan_rejects_conflicting_duplicate_authority() {
let generation = CommitId::for_test_label("conflicting-hot-authority");
let error = canonicalize_hot_scan_rows(
vec![
adversarial_hot_scan_entry(generation, "row", "file", b"older"),
adversarial_hot_scan_entry(generation, "row", "file", b"newer"),
],
None,
)
.expect_err("one HOT identity cannot have two authoritative byte values");
assert!(
error
.message
.contains("duplicate HOT authority for schema 'schema'"),
"unexpected duplicate-authority error: {error:?}"
);
}
#[test]
fn hot_scan_admission_is_bounded_by_retained_bytes_not_row_count() {
const TINY_ROW_COUNT: usize = 5_000;
const BUDGET: usize = 4 * 1024 * 1024;
let generation = CommitId::for_test_label("hot-scan-byte-budget");
let scope = hot_scope_prefix("branch", generation);
let tiny_rows = (0..TINY_ROW_COUNT)
.map(|index| {
let row_pk = RowPk::single(format!("row-{index:05}"));
let key = Bytes::from(encode_hot_row_key_parts(
"branch", generation, "schema", &row_pk, None,
));
let identity = decode_hot_scan_row_key_in_scope(key, &scope)
.expect("decode tiny HOT scan identity");
(identity, Bytes::from_static(b"{}"))
})
.collect::<Vec<_>>();
assert!(
hot_scan_entries_fit_budget(HotScanEntries::Decoded(tiny_rows), Some(BUDGET),)
.is_some(),
"thousands of narrows must not trip a cardinality policy"
);
let row_pk = RowPk::single("large");
let key = Bytes::from(encode_hot_row_key_parts(
"branch", generation, "schema", &row_pk, None,
));
let identity =
decode_hot_scan_row_key_in_scope(key, &scope).expect("decode large HOT scan identity");
let wide_rows = vec![(identity, Bytes::from(vec![0_u8; BUDGET]))];
assert!(
hot_scan_entries_fit_budget(HotScanEntries::Decoded(wide_rows), Some(BUDGET),)
.is_none(),
"retained payload bytes must govern fallback even for one row"
);
}
#[test]
fn hot_diff_keys_append_into_one_exact_arena() {
let checkpoint = CommitId::for_test_label("shared-hot-diff-checkpoint");
let generation = CommitId::for_test_label("shared-hot-diff-generation");
let scope = encode_working_diff_scope_prefix("branch", checkpoint, generation);
let identities = [
HeadRowIdentity {
schema_key: "schema".to_string(),
row_pk: RowPk::single("first"),
file_id: None,
},
HeadRowIdentity {
schema_key: "schema\0escaped".to_string(),
row_pk: RowPk::single("second-escaped"),
file_id: Some("file\0id".to_string()),
},
];
let capacity = identities
.iter()
.try_fold(0_usize, |total, identity| {
total.checked_add(encoded_hot_identity_key_len(
scope.len(),
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)?)
})
.expect("test identities have a representable encoded size");
let mut key_bytes = Vec::with_capacity(capacity);
let allocation = key_bytes.as_ptr();
let ranges = identities
.iter()
.map(|identity| {
append_hot_diff_key_parts(
&mut key_bytes,
&scope,
&identity.schema_key,
&identity.row_pk,
identity.file_id.as_deref(),
)
})
.collect::<Vec<_>>();
assert_eq!(key_bytes.len(), capacity);
assert_eq!(key_bytes.capacity(), capacity);
assert_eq!(key_bytes.as_ptr(), allocation);
assert_eq!(ranges[0].end, ranges[1].start);
for (range, expected) in ranges.into_iter().zip(identities) {
let (decoded_checkpoint, decoded_identity) =
decode_hot_diff_key(&key_bytes[range]).expect("decode appended hot-diff key");
assert_eq!(decoded_checkpoint, checkpoint);
assert_eq!(decoded_identity.branch_id, "branch");
assert_eq!(decoded_identity.generation, generation);
assert_eq!(decoded_identity.schema_key, expected.schema_key);
assert_eq!(decoded_identity.row_pk, expected.row_pk);
assert_eq!(decoded_identity.file_id, expected.file_id);
}
}
#[test]
fn columnar_base_coordinate_survives_repeated_hot_updates_and_tombstones() {
let row_pk = RowPk::single("coordinated-row");
let coordinate = ColumnarBaseCoordinate {
base_commit_id: CommitId::for_test_label("coordinate-base"),
group_index: 7,
row_index: 31,
};
let previous_typed =
WasmTypedRow::from_test_json_unchecked(&row_pk, &serde_json::json!({}))
.expect("coordinated predecessor should type");
let updated_typed =
WasmTypedRow::from_test_json_unchecked(&row_pk, &serde_json::json!({"updated": true}))
.expect("coordinated update should type");
let previous_snapshot = previous_typed.durable_payload().expect("typed payload");
let updated_snapshot = updated_typed.durable_payload().expect("typed payload");
let previous = HeadValueRef {
change_id: Some(ChangeId::for_test_label("coordinate-before-change")),
commit_id: Some(CommitId::for_test_label("coordinate-before-commit")),
untracked: false,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: Some(previous_snapshot.as_ref()),
metadata: None,
columnar_base_coordinate: Some(coordinate),
working_diff_baseline: WorkingDiffBaseline::Disabled,
};
let mut predecessor = CertifiedCurrentStatePredecessor::Encoded(Bytes::from(
encode_head_value(&previous).expect("encode coordinated predecessor"),
));
for deleted in [false, true] {
let delta = CurrentStateDeltaRef {
schema_key: "schema",
file_id: None,
row_pk: &row_pk,
change_id: Some(ChangeId::for_test_label(if deleted {
"coordinate-delete-change"
} else {
"coordinate-update-change"
})),
commit_id: Some(CommitId::for_test_label(if deleted {
"coordinate-delete-commit"
} else {
"coordinate-update-commit"
})),
untracked: false,
deleted,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: (!deleted).then_some(updated_snapshot.as_ref()),
metadata: None,
columnar_base_coordinate: None,
};
let inherited = next_columnar_base_coordinate(false, &delta, Some(&predecessor))
.expect("inherit coordinate");
assert_eq!(inherited, Some(coordinate));
assert_eq!(
next_columnar_base_coordinate(true, &delta, Some(&predecessor))
.expect("clear coordinate for new base"),
None
);
let mut next = delta.value_ref(timestamp(), WorkingDiffBaseline::Disabled);
next.columnar_base_coordinate = inherited;
predecessor = CertifiedCurrentStatePredecessor::Encoded(Bytes::from(
encode_head_value(&next).expect("encode repeated coordinated mutation"),
));
}
assert!(predecessor.view().expect("decode tombstone").deleted);
assert_eq!(
predecessor
.view()
.expect("decode tombstone coordinate")
.columnar_base_coordinate,
Some(coordinate)
);
}
#[tokio::test]
async fn hot_diff_segments_preserve_identity_coverage_with_bounded_puts() {
const IDENTITY_COUNT: usize = 10_000;
let checkpoint = CommitId::for_test_label("segmented-hot-diff-checkpoint");
let generation = CommitId::for_test_label("segmented-hot-diff-generation");
let scope = encode_working_diff_scope_prefix("branch", checkpoint, generation);
let mut identity_key_bytes = Vec::new();
let mut identity_puts = Vec::with_capacity(IDENTITY_COUNT);
let mut expected_coverage = WorkingDiffIndexCoverage::default();
for index in 0..IDENTITY_COUNT {
let row_pk = RowPk::single(format!("row-{index:05}"));
let key = append_hot_diff_key_parts(
&mut identity_key_bytes,
&scope,
"schema",
&row_pk,
Some("file.md"),
);
expected_coverage
.add_encoded_group_key(&identity_key_bytes[key.clone()])
.expect("test coverage count fits u64");
identity_puts.push(EncodedPut {
key: buffer_range(&key),
value: BufferRange::default(),
});
}
let mut writes = StorageWriteSet::new();
stage_hot_diff_batch(&mut writes, &scope, identity_key_bytes, identity_puts)
.expect("stage segmented hot diff");
assert!(
writes.stats().staged_puts <= 3,
"ten thousand short identities should require at most three bounded segments"
);
let storage = StorageAdapter::new(Memory::new());
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit segmented hot diff");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("open segmented hot diff read");
let range = StoragePrefix {
bytes: Bytes::from(scope.clone()),
}
.to_range()
.expect("valid prefix range");
let mut cursor = read
.begin_scan(DIFF_SPACE, range, StorageBeginScanOptions::default())
.await
.expect("begin segmented hot diff scan");
let (page, page_has_more) = cursor
.next_page(crate::storage_adapter::MAX_SCAN_PAGE_ROWS)
.await
.expect("scan segmented hot diff")
.into_parts();
assert!(!page_has_more);
let mut actual_coverage = WorkingDiffIndexCoverage::default();
let mut decoded = 0_usize;
for entry in page {
let bytes = full_value_bytes(entry.value).expect("full segment value");
let segment_scope =
decode_hot_diff_segment_key(entry.key.0.as_ref()).expect("segment key");
assert_eq!(segment_scope.digest, *blake3::hash(&bytes).as_bytes());
visit_hot_diff_segment(&bytes, &scope, &mut actual_coverage, |_| decoded += 1)
.expect("decode hot diff segment");
}
assert_eq!(decoded, IDENTITY_COUNT);
assert_eq!(actual_coverage, expected_coverage);
}
#[test]
fn hot_mutation_keys_append_into_one_exact_arena() {
let generation = CommitId::for_test_label("shared-hot-mutation-generation");
let scope = hot_scope_prefix("branch", generation);
let first_pk = RowPk::single("first-row");
let second_pk = RowPk::single("second");
let first_snapshot =
native_snapshot_payload(&first_pk, serde_json::json!({"value": "first"}));
let second_snapshot =
native_snapshot_payload(&second_pk, serde_json::json!({"value": "second"}));
let first = CurrentStateDeltaRef {
schema_key: "schema\0escaped",
file_id: Some("file\0id"),
row_pk: &first_pk,
change_id: Some(ChangeId::for_test_label("hot-mutation-first")),
commit_id: None,
untracked: true,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: Some(&first_snapshot),
metadata: None,
columnar_base_coordinate: None,
};
let second = CurrentStateDeltaRef {
schema_key: "schema_without_file",
file_id: None,
row_pk: &second_pk,
change_id: Some(ChangeId::for_test_label("hot-mutation-second")),
commit_id: None,
untracked: true,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: Some(&second_snapshot),
metadata: None,
columnar_base_coordinate: None,
};
let deltas = [&first, &second];
let capacity = encoded_hot_mutation_identity_capacity(scope.len(), &deltas)
.expect("test identities have a representable encoded size");
let mut key_bytes = Vec::with_capacity(capacity);
let allocation = key_bytes.as_ptr();
let ranges = deltas
.iter()
.map(|delta| append_hot_mutation_identity(&mut key_bytes, &scope, delta))
.collect::<Vec<_>>();
assert_eq!(key_bytes.len(), capacity);
assert_eq!(key_bytes.capacity(), capacity);
assert_eq!(key_bytes.as_ptr(), allocation);
assert_eq!(ranges[0].row_key.offset(), 0);
assert_eq!(
ranges[0]
.file_schema_key
.expect("first identity has a file schema marker")
.offset()
+ ranges[0]
.file_schema_key
.expect("first identity has a file schema marker")
.len(),
ranges[1].row_key.offset()
);
for (range, delta) in ranges.iter().zip(deltas) {
let row_start = range.row_key.offset();
let row = decode_hot_row_key_in_scope(
&key_bytes[row_start..row_start + range.row_key.len()],
&scope,
)
.expect("decode shared row key");
assert_eq!(row.schema_key, delta.schema_key);
assert_eq!(row.row_pk, *delta.row_pk);
assert_eq!(row.file_id.as_deref(), delta.file_id);
let scan_row = decode_hot_scan_row_key_in_scope(
Bytes::copy_from_slice(&key_bytes[row_start..row_start + range.row_key.len()]),
&scope,
)
.expect("decode shared row key for direct scan");
assert_eq!(scan_row.schema_key(), delta.schema_key);
assert_eq!(scan_row.row_pk, *delta.row_pk);
assert_eq!(scan_row.file_id(), delta.file_id);
assert_eq!(
scan_row.owned_metadata_buffer_count(),
usize::from(delta.schema_key.contains('\0'))
+ usize::from(delta.file_id.is_some_and(|file_id| file_id.contains('\0'))),
"only escaped metadata should take an owned fallback"
);
if let Some(marker) = range.file_schema_key {
let marker_start = marker.offset();
assert_eq!(
&key_bytes[marker_start..marker_start + marker.len()],
encode_hot_file_schema_key(&scope, delta.schema_key)
);
}
}
let encoded = encode_hot_mutation_identities("branch", generation, &deltas);
assert_eq!(encoded.key_bytes.as_ref(), key_bytes);
assert_eq!(encoded.key_ranges.len(), ranges.len());
for (encoded, expected) in encoded.key_ranges.iter().zip(ranges) {
assert_eq!(encoded.row_key, expected.row_key);
assert_eq!(encoded.file_schema_key, expected.file_schema_key);
}
}
#[test]
fn hot_next_values_append_into_one_planned_arena() {
let tracked_pk = RowPk::single("tracked");
let tombstone_pk = RowPk::single("tombstone");
let untracked_pk = RowPk::single("untracked");
let removed_pk = RowPk::single("removed");
let tracked_typed = WasmTypedRow::from_test_json_unchecked(
&tracked_pk,
&serde_json::json!({"tracked": true}),
)
.expect("tracked fixture should type");
let untracked_typed = WasmTypedRow::from_test_json_unchecked(
&untracked_pk,
&serde_json::json!({"large": "snapshot"}),
)
.expect("untracked fixture should type");
let tracked_snapshot = tracked_typed.durable_payload().expect("typed payload");
let untracked_snapshot = untracked_typed.durable_payload().expect("typed payload");
let tracked_metadata = lix_schema::Jsonb::from_value(serde_json::json!({"source": "test"}));
let tracked = CurrentStateDeltaRef {
schema_key: "tracked_schema",
file_id: Some("tracked.json"),
row_pk: &tracked_pk,
change_id: Some(ChangeId::for_test_label("planned-value-change")),
commit_id: Some(CommitId::for_test_label("planned-value-commit")),
untracked: false,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: Some(tracked_snapshot.as_ref()),
metadata: Some(&tracked_metadata),
columnar_base_coordinate: None,
};
let tombstone = CurrentStateDeltaRef {
schema_key: "tracked_schema",
file_id: None,
row_pk: &tombstone_pk,
change_id: Some(ChangeId::for_test_label("planned-tombstone-change")),
commit_id: Some(CommitId::for_test_label("planned-tombstone-commit")),
untracked: false,
deleted: true,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: None,
metadata: None,
columnar_base_coordinate: None,
};
let untracked = CurrentStateDeltaRef {
schema_key: "untracked_schema",
file_id: Some("untracked.json"),
row_pk: &untracked_pk,
change_id: Some(ChangeId::for_test_label("hot-untracked-member")),
commit_id: None,
untracked: true,
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: Some(untracked_snapshot.as_ref()),
metadata: None,
columnar_base_coordinate: None,
};
let removed = CurrentStateDeltaRef {
schema_key: "untracked_schema",
file_id: Some("removed.json"),
row_pk: &removed_pk,
change_id: Some(ChangeId::for_test_label("hot-untracked-removed")),
commit_id: None,
untracked: true,
deleted: true,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: None,
metadata: None,
columnar_base_coordinate: None,
};
let deltas = [&tracked, &tombstone, &untracked, &removed];
let ordinary_capacity = deltas
.iter()
.try_fold(0_usize, |total, delta| {
checked_add_hot_next_value_capacity(total, delta, false, false)
})
.expect("ordinary test values have a representable encoded size");
let mut ordinary = Vec::with_capacity(ordinary_capacity);
let ordinary_allocation = ordinary.as_ptr();
let mut ordinary_expected = Vec::new();
let mut ordinary_ranges = Vec::new();
for delta in deltas {
if delta.physically_deletes() {
continue;
}
let value = delta.value_ref(delta.created_at, WorkingDiffBaseline::Disabled);
ordinary_expected.extend_from_slice(
&encode_head_value(&value).expect("encode ordinary expected value"),
);
ordinary_ranges.push(
append_head_value(&mut ordinary, &value).expect("append ordinary planned value"),
);
}
assert_eq!(ordinary, ordinary_expected);
assert_eq!(ordinary.len(), ordinary_capacity);
assert_eq!(ordinary.capacity(), ordinary_capacity);
assert_eq!(ordinary.as_ptr(), ordinary_allocation);
for range in ordinary_ranges {
assert_eq!(
decode_head_value(&ordinary[range])
.expect("decode ordinary planned value")
.working_diff_baseline,
WorkingDiffBaseline::Disabled
);
}
let before = WorkingDiffVersion {
change_id: ChangeId::for_test_label("planned-before-change"),
commit_id: CommitId::for_test_label("planned-before-commit"),
deleted: false,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
metadata: WorkingDiffSlotFingerprint {
kind: WORKING_DIFF_SLOT_NONE,
hash: [0; CONTENT_HASH_BYTES],
},
};
let checkpoint_capacity = [&tracked, &tombstone, &untracked, &removed]
.iter()
.try_fold(0_usize, |total, delta| {
checked_add_hot_next_value_capacity(total, delta, true, false)
})
.expect("checkpoint test values have a representable encoded size");
let checkpoint_baselines = [
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: CommitId::for_test_label("checkpoint"),
version: before,
},
WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: CommitId::for_test_label("checkpoint"),
version: before,
},
WorkingDiffBaseline::Disabled,
WorkingDiffBaseline::Disabled,
];
let mut checkpoint = Vec::with_capacity(checkpoint_capacity);
let checkpoint_allocation = checkpoint.as_ptr();
let mut checkpoint_expected = Vec::new();
for (delta, baseline) in [&tracked, &tombstone, &untracked, &removed]
.into_iter()
.zip(checkpoint_baselines)
{
if delta.physically_deletes() {
continue;
}
let value = delta.value_ref(delta.created_at, baseline);
checkpoint_expected.extend_from_slice(
&encode_head_value(&value).expect("encode checkpoint expected value"),
);
append_head_value(&mut checkpoint, &value).expect("append checkpoint planned value");
}
assert_eq!(checkpoint, checkpoint_expected);
assert_eq!(checkpoint.len(), checkpoint_capacity);
assert_eq!(checkpoint.capacity(), checkpoint_capacity);
assert_eq!(checkpoint.as_ptr(), checkpoint_allocation);
let before_absent = tracked.value_ref(
tracked.created_at,
WorkingDiffBaseline::BeforeAbsent {
checkpoint_commit_id: CommitId::for_test_label("checkpoint"),
},
);
let before_absent_bytes =
encode_head_value(&before_absent).expect("encode before-absent checkpoint value");
let tracked_checkpoint_capacity =
checked_add_hot_next_value_capacity(0, &tracked, true, false)
.expect("tracked checkpoint value has a representable size");
assert_eq!(
tracked_checkpoint_capacity,
before_absent_bytes.len() + WORKING_DIFF_VERSION_BYTES,
"new checkpoint rows use the same safe fixed-size upper bound"
);
assert!(
checked_add_hot_next_value_capacity(usize::MAX, &tracked, false, false).is_none(),
"overflow must select the caller's zero-capacity fallback"
);
assert_eq!(
checked_add_hot_next_value_capacity(usize::MAX, &tracked, false, false).unwrap_or(0),
0
);
}
#[test]
fn hot_tracked_snapshot_clones_share_encoded_row_values() {
let decoded_snapshot = Arc::new(WasmTypedRow {
schema_fingerprint: [9; 32],
row_pk: vec![lix_schema::Value::Text("row".to_owned())].into(),
row: lix_schema::Row::from([(
"id".to_owned(),
lix_schema::Value::Text("row".to_owned()),
)]),
native_payload: std::sync::OnceLock::new(),
boundary_create_validation: std::sync::OnceLock::new(),
});
let snapshot =
HotTrackedSnapshot::from_materialized_rows(vec![MaterializedTrackedStateRow {
row_pk: RowPk::single("row"),
schema_key: "schema".to_string(),
file_id: None,
snapshot_content: None,
decoded_snapshot: Some(Arc::clone(&decoded_snapshot)),
metadata: None,
deleted: false,
created_at: "2026-01-01T00:00:00Z".to_string(),
updated_at: "2026-01-01T00:00:00Z".to_string(),
change_id: ChangeId::for_test_label("hot-shared-value-change"),
commit_id: CommitId::for_test_label("hot-shared-value-commit"),
}])
.expect("encode tracked snapshot");
let cloned = snapshot.clone();
let source = snapshot.rows.values().next().expect("source encoded row");
let retained = cloned.rows.values().next().expect("cloned encoded row");
assert_eq!(source.as_ptr(), retained.as_ptr());
assert_eq!(source.len(), retained.len());
let decoded = decode_head_value(source).expect("decode typed tracked snapshot");
assert_eq!(
decoded.snapshot.expect("tracked snapshot retains payload")[0],
crate::plugin::wire::typed::STORAGE_ROW_PAYLOAD_VERSION,
"HOT lifecycle persistence must omit the envelope-owned primary key"
);
assert_eq!(
WasmTypedRow::decode_durable_payload(
Arc::from(
decoded
.snapshot
.expect("tracked snapshot retained typed payload"),
),
"schema",
&RowPk::single("row"),
)
.expect("decode native typed payload against its HOT envelope"),
*decoded_snapshot
);
}
#[test]
fn hot_batch_staging_retains_encoded_key_and_value_arenas() {
let key_bytes = Bytes::from_static(b"row-keymarker");
let value_bytes = Bytes::from_static(b"value");
let identities = EncodedHotMutationIdentities {
key_bytes: key_bytes.clone(),
key_ranges: vec![EncodedHotMutationIdentityRanges {
row_key: BufferRange::new(0, 7),
file_schema_key: Some(BufferRange::new(7, 6)),
}],
};
let mut writes = StorageWriteSet::new();
stage_hot_mutation_batch(&mut writes, identities, value_bytes, vec![Some(0..5)]);
let stats = writes.arena_stats();
assert_eq!(stats.spaces, 2);
assert_eq!(stats.put_descriptors, 2);
assert_eq!(stats.key_inline_bytes, 0);
assert_eq!(stats.value_inline_bytes, 0);
assert_eq!(stats.key_shared_buffers, 2);
assert_eq!(stats.value_shared_buffers, 2);
}
#[test]
fn ten_thousand_file_cascade_mutations_reserve_shared_buffers_once() {
const ROW_COUNT: usize = 10_000;
let tombstone = HeadValueRef {
change_id: Some(ChangeId::for_test_label("cascade-reserve-change")),
commit_id: Some(CommitId::for_test_label("cascade-reserve-commit")),
untracked: false,
deleted: true,
created_at: timestamp(),
updated_at: timestamp(),
snapshot: None,
metadata: None,
columnar_base_coordinate: None,
working_diff_baseline: WorkingDiffBaseline::BeforePresent {
checkpoint_commit_id: CommitId::for_test_label("cascade-reserve-checkpoint"),
version: working_diff_version("cascade-reserve-before"),
},
};
let encoded_tombstone =
encode_head_value(&tombstone).expect("encode maximum cascade tombstone");
assert_eq!(
encoded_tombstone.len(),
HEAD_VALUE_TYPED_HEADER_BYTES
+ WORKING_DIFF_CHECKPOINT_BYTES
+ WORKING_DIFF_VERSION_BYTES,
"the cascade value reservation must cover the largest checkpoint tombstone"
);
let mut buffers = HotCascadeMutationBuffers::with_capacity(ROW_COUNT, 0, true);
let value_allocation = buffers.value_bytes.as_ptr();
let row_put_allocation = buffers.row_puts.as_ptr();
let row_delete_allocation = buffers.row_deletes.as_ptr();
let descriptor = EncodedPut {
key: BufferRange::default(),
value: BufferRange::default(),
};
for _ in 0..ROW_COUNT {
append_head_value(&mut buffers.value_bytes, &tombstone)
.expect("append planned cascade tombstone");
buffers.row_puts.push(descriptor);
buffers.row_deletes.push(BufferRange::default());
}
assert_eq!(
buffers.value_bytes.len(),
ROW_COUNT
* (HEAD_VALUE_TYPED_HEADER_BYTES
+ WORKING_DIFF_CHECKPOINT_BYTES
+ WORKING_DIFF_VERSION_BYTES)
);
assert_eq!(buffers.value_bytes.as_ptr(), value_allocation);
assert_eq!(buffers.row_puts.as_ptr(), row_put_allocation);
assert_eq!(buffers.row_deletes.as_ptr(), row_delete_allocation);
assert!(buffers.row_puts.capacity() >= ROW_COUNT);
assert!(buffers.row_deletes.capacity() >= ROW_COUNT);
}
#[test]
fn the_candidate_budget_floors_small_planes_and_halves_large_ones() {
assert_eq!(hot_index_candidate_budget(0), 64);
assert_eq!(hot_index_candidate_budget(1), 64);
assert_eq!(hot_index_candidate_budget(128), 64);
assert_eq!(hot_index_candidate_budget(129), 64);
assert_eq!(hot_index_candidate_budget(200), 100);
assert_eq!(hot_index_candidate_budget(20_000), 10_000);
assert_eq!(
hot_index_candidate_budget(u64::MAX),
(u64::MAX / 2) as usize
);
}
#[test]
fn the_budget_refuses_exactly_the_buckets_that_lost_to_the_scan() {
for (entries_published, bucket, expected_served) in [
(1_u64, 1_usize, true),
(11, 10, true),
(101, 100, false),
(1_001, 1_000, false),
(5_001, 5_000, false),
(100, 100, false),
(1_000, 1_000, false),
(10_000, 10_000, false),
(199, 100, false),
(1_999, 1_000, false),
(19_999, 10_000, false),
] {
let served = bucket <= hot_index_candidate_budget(entries_published);
assert_eq!(
served, expected_served,
"plane of {entries_published} entries, bucket of {bucket}",
);
}
}
#[test]
fn equality_seek_budget_scales_with_plane_and_caps_stale_estimates() {
assert_eq!(hot_index_seek_budget(0), 128);
assert_eq!(hot_index_seek_budget(100), 128);
assert_eq!(hot_index_seek_budget(2_000), 1_000);
assert_eq!(hot_index_seek_budget(u64::MAX), 16_384);
}
#[cfg(feature = "storage-benches")]
#[tokio::test]
async fn oversized_absent_equality_probe_opens_no_index_ranges() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("seek-budget");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
stage_hot_index_entries(
&read,
&mut writes,
"branch",
generation,
&[],
&BTreeSet::from([("schema".into(), 0)]),
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let reader = HotStateStoreReader {
store: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
transaction_cache: None,
root_base_cache: None,
};
for count in [256, 4096] {
let values = (0..count).map(HotIndexValue::Integer).collect::<Vec<_>>();
let (result, census) = crate::storage_bench::measure_checkpoint_foreground(
reader
.scan_hot_index_identity_candidates("branch", generation, "schema", 0, &values),
)
.await;
assert!(result.unwrap().is_none());
assert_eq!(census.scan_starts, 0, "budget must precede range opens");
assert_eq!(census.point_keys, 1, "only the collection witness is read");
}
}
#[test]
fn a_witness_round_trips_its_published_count() {
assert_eq!(
decode_hot_index_witness(&encode_hot_index_witness(0)),
Some(0)
);
assert_eq!(
decode_hot_index_witness(&encode_hot_index_witness(7_919)),
Some(7_919)
);
assert_eq!(decode_hot_index_witness(&[]), None);
assert_eq!(decode_hot_index_witness(&[0, 1, 2]), None);
}
#[tokio::test]
async fn legacy_incomplete_index_is_not_trusted_or_blessed_by_new_writes() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("legacy-index-generation");
let mut legacy_key = hot_scope_prefix("branch", generation);
write_key_string(&mut legacy_key, "schema", KEY_PART_FINAL);
legacy_key.push(0x00);
legacy_key.extend_from_slice(&0_u16.to_be_bytes());
let mut writes = StorageWriteSet::new();
writes.put(
INDEX_SPACE,
StorageKey(Bytes::from(legacy_key)),
StorageValue {
bytes: Bytes::copy_from_slice(&0_u64.to_be_bytes()),
},
);
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let value = HotIndexValue::String("missing".into());
for append in [false, true] {
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
if append {
let mut writes = StorageWriteSet::new();
stage_hot_index_entries(
&read,
&mut writes,
"branch",
generation,
&[HotIndexEntry {
untracked: false,
schema_key: "schema".into(),
ordinal: 0,
value: Some(HotIndexValue::String("fresh".into())),
file_id: None,
row_pk: RowPk::single("fresh"),
}],
&BTreeSet::new(),
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
}
let reader = HotStateStoreReader {
store: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
transaction_cache: None,
root_base_cache: None,
};
assert_eq!(
reader
.scan_hot_index_candidates(
"branch",
generation,
"schema",
0,
std::slice::from_ref(&value)
)
.await
.unwrap(),
None,
"legacy equality index must fall back, append={append}"
);
assert_eq!(
reader
.scan_hot_index_range_candidates(
"branch",
generation,
"schema",
0,
Some((&value, true)),
None
)
.await
.unwrap(),
None,
"legacy range index must fall back, append={append}"
);
}
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
stage_hot_index_entries(
&read,
&mut writes,
"branch",
generation,
&[HotIndexEntry {
untracked: false,
schema_key: "new_schema".into(),
ordinal: 0,
value: Some(value.clone()),
file_id: None,
row_pk: RowPk::single("present"),
}],
&BTreeSet::from([("new_schema".into(), 0)]),
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let reader = HotStateStoreReader {
store: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
transaction_cache: None,
root_base_cache: None,
};
assert_eq!(
reader
.scan_hot_index_candidates("branch", generation, "new_schema", 0, &[value])
.await
.unwrap(),
Some(vec![RowPk::single("present")])
);
}
#[tokio::test]
async fn indexed_candidates_retain_same_pk_across_file_scopes() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("identity-index-generation");
let value = HotIndexValue::String("shared".into());
let row_pk = RowPk::single("same");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
stage_hot_index_entries(
&read,
&mut writes,
"branch",
generation,
&[
HotIndexEntry {
untracked: false,
schema_key: "schema".into(),
ordinal: 0,
value: Some(value.clone()),
file_id: Some("file-a".into()),
row_pk: row_pk.clone(),
},
HotIndexEntry {
untracked: false,
schema_key: "schema".into(),
ordinal: 0,
value: Some(value.clone()),
file_id: Some("file-b".into()),
row_pk: row_pk.clone(),
},
],
&BTreeSet::from([("schema".into(), 0)]),
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
let reader = HotStateStoreReader {
store: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
transaction_cache: None,
root_base_cache: None,
};
assert_eq!(
reader
.scan_hot_index_identity_candidates(
"branch",
generation,
"schema",
0,
std::slice::from_ref(&value),
)
.await
.unwrap(),
Some(vec![
(row_pk.clone(), Some("file-a".into())),
(row_pk, Some("file-b".into())),
])
);
}
#[tokio::test]
async fn reverse_membership_retires_values_without_erasing_other_lanes_or_files() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("reverse-membership");
let entry = |untracked, file: &str, value: Option<&str>| HotIndexEntry {
schema_key: "schema".into(),
ordinal: 0,
untracked,
file_id: Some(file.into()),
row_pk: RowPk::single("same"),
value: value.map(|value| HotIndexValue::String(value.into())),
};
for (entries, witnesses) in [
(
vec![
entry(false, "a", Some("old")),
entry(true, "a", Some("old")),
entry(false, "b", Some("old")),
],
BTreeSet::from([("schema".into(), 0)]),
),
(
vec![entry(false, "a", Some("new")), entry(false, "b", None)],
BTreeSet::new(),
),
] {
let read = storage
.begin_read(StorageReadOptions::default())
.await
.unwrap();
let mut writes = StorageWriteSet::new();
stage_hot_index_entries(
&read,
&mut writes,
"branch",
generation,
&entries,
&witnesses,
)
.await
.unwrap();
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.unwrap();
}
let reader = HotStateStoreReader {
store: storage
.begin_read(StorageReadOptions::default())
.await
.unwrap(),
transaction_cache: None,
root_base_cache: None,
};
for value in ["old", "new"] {
assert_eq!(
reader
.scan_hot_index_identity_candidates(
"branch",
generation,
"schema",
0,
&[HotIndexValue::String(value.into())]
)
.await
.unwrap(),
Some(vec![(RowPk::single("same"), Some("a".into()))])
);
}
}
#[tokio::test]
async fn ordinary_incremental_import_skips_file_cascade_identity_index() {
const DELTAS: usize = 4096;
let storage = StorageAdapter::new(Memory::new());
let read = crate::storage_adapter::SharedStorageAdapterRead::new(
storage
.begin_read(StorageReadOptions::default())
.await
.expect("open ordinary incremental import read"),
);
let row_pk = RowPk::single("ordinary");
let timestamp = timestamp();
let snapshot = native_snapshot_payload(&row_pk, serde_json::json!({"value": "ordinary"}));
let delta = CurrentStateDeltaRef {
schema_key: "ordinary_schema",
file_id: Some("ordinary.json"),
row_pk: &row_pk,
change_id: Some(ChangeId::for_test_label("hot-ordinary-incremental")),
commit_id: None,
untracked: true,
deleted: false,
created_at: timestamp,
updated_at: timestamp,
snapshot: Some(&snapshot),
metadata: None,
columnar_base_coordinate: None,
};
let deltas = vec![δ DELTAS];
let generation = CommitId::for_test_label("ordinary-import-generation");
let mut writes = StorageWriteSet::new();
let mut coverage = WorkingDiffIndexCoverage::default();
let explicit_index_builds = incremental_cascade_explicit_index_builds();
stage_incremental_file_delete_cascades(
&read,
&mut writes,
"ordinary-import",
generation,
&deltas,
None,
false,
&mut coverage,
)
.await
.expect("ordinary imports do not need file-delete cascade staging");
assert_eq!(
incremental_cascade_explicit_index_builds(),
explicit_index_builds,
"ordinary imports must return before allocating the batch-sized explicit identity index"
);
assert!(writes.is_empty());
}
#[tokio::test]
async fn dense_mutation_identity_range_scan_matches_point_reads() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("dense-mutation-generation");
let row_pks = (0..HOT_DENSE_SCAN_MIN_IDENTITIES)
.map(|index| RowPk::single(format!("{index:04}")))
.collect::<Vec<_>>();
let timestamp = timestamp();
let snapshots = row_pks
.iter()
.map(|row_pk| native_snapshot_payload(row_pk, serde_json::json!({"value": "dense"})))
.collect::<Vec<_>>();
let deltas = row_pks
.iter()
.zip(&snapshots)
.map(|(row_pk, snapshot)| CurrentStateDeltaRef {
schema_key: "schema",
file_id: None,
row_pk,
change_id: Some(ChangeId::for_test_label("hot-planned-arena")),
commit_id: None,
untracked: true,
deleted: false,
created_at: timestamp,
updated_at: timestamp,
snapshot: Some(snapshot),
metadata: None,
columnar_base_coordinate: None,
})
.collect::<Vec<_>>();
let delta_refs = deltas.iter().collect::<Vec<_>>();
let encoded = encode_hot_mutation_identities("branch", generation, &delta_refs);
let keys = encoded
.key_ranges
.iter()
.map(|ranges| {
let start = ranges.row_key.offset();
StorageKey(
encoded
.key_bytes
.slice(start..start.saturating_add(ranges.row_key.len())),
)
})
.collect::<Vec<_>>();
let mut writes = StorageWriteSet::new();
for key in &keys {
writes.put(
ROW_SPACE,
key.clone(),
StorageValue {
bytes: Bytes::from_static(b"row"),
},
);
}
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit dense mutation fixture");
let read = crate::storage_adapter::SharedStorageAdapterRead::new(
storage
.begin_read(StorageReadOptions::default())
.await
.expect("open dense mutation read"),
);
let dense = hot_scan_dense_mutation_identity_range(&read, &encoded)
.await
.expect("scan dense mutation range")
.expect("dense mutation range should stay on the scan path");
let point = PointReadPlan::new(ROW_SPACE, &keys)
.materialize(&read, StorageGetOptions::default())
.await
.expect("point-read dense mutation identities")
.value
.into_iter()
.map(|value| value.map(full_value_bytes).transpose())
.collect::<Result<Vec<_>, _>>()
.expect("decode dense mutation point reads");
assert_eq!(dense, point);
assert_eq!(
dense.iter().flatten().count(),
HOT_DENSE_SCAN_MIN_IDENTITIES
);
}
#[tokio::test]
async fn dense_identity_range_scan_returns_requested_rows() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("dense-range-generation");
let all_identities = (0..HOT_DENSE_SCAN_MIN_IDENTITIES * 2)
.map(|index| diff_identity("branch", generation, &format!("{index:04}")))
.collect::<Vec<_>>();
let requested = all_identities
.iter()
.step_by(2)
.cloned()
.collect::<Vec<_>>();
let requested_batch = FiniteHotIdentityBatchRef::new(
"branch",
generation,
"schema",
requested.iter().map(|identity| &identity.row_pk).collect(),
vec![None],
)
.expect("dense identity count is representable");
let mut writes = StorageWriteSet::new();
for identity in &all_identities {
writes.put(
ROW_SPACE,
StorageKey(Bytes::from(encode_hot_row_key(identity))),
StorageValue {
bytes: Bytes::from_static(b"row"),
},
);
}
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit dense-range fixture");
let read = crate::storage_adapter::SharedStorageAdapterRead::new(
storage
.begin_read(StorageReadOptions::default())
.await
.expect("open dense-range read"),
);
let dense = hot_scan_dense_identity_range(&read, &requested_batch)
.await
.expect("scan dense identity range")
.expect("dense range should stay on the scan path");
let point = hot_load_finite_identity_bytes(&read, &requested_batch)
.await
.expect("point-read the same dense identities");
assert_eq!(dense, point);
assert_eq!(dense.iter().flatten().count(), requested.len());
}
#[tokio::test]
async fn sparse_identity_range_scan_returns_to_point_reads() {
let storage = StorageAdapter::new(Memory::new());
let generation = CommitId::for_test_label("sparse-range-generation");
let all_identities = (0..HOT_DENSE_SCAN_MIN_IDENTITIES * 4)
.map(|index| diff_identity("branch", generation, &format!("{index:04}")))
.collect::<Vec<_>>();
let requested = all_identities
.iter()
.step_by(4)
.cloned()
.collect::<Vec<_>>();
let requested_batch = FiniteHotIdentityBatchRef::new(
"branch",
generation,
"schema",
requested.iter().map(|identity| &identity.row_pk).collect(),
vec![None],
)
.expect("sparse identity count is representable");
let mut writes = StorageWriteSet::new();
for identity in &all_identities {
writes.put(
ROW_SPACE,
StorageKey(Bytes::from(encode_hot_row_key(identity))),
StorageValue {
bytes: Bytes::from_static(b"row"),
},
);
}
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit sparse-range fixture");
let read = crate::storage_adapter::SharedStorageAdapterRead::new(
storage
.begin_read(StorageReadOptions::default())
.await
.expect("open sparse-range read"),
);
let rows = hot_scan_dense_identity_range(&read, &requested_batch)
.await
.expect("probe sparse identity range");
assert!(
rows.is_none(),
"sparse ranges must return to the exact point-read path"
);
let point = hot_load_finite_identity_bytes(&read, &requested_batch)
.await
.expect("point-read sparse identities after dense fallback");
assert_eq!(point.iter().flatten().count(), requested.len());
}
#[tokio::test]
async fn working_diff_gc_keeps_only_control_bound_hot_records() {
let storage = StorageAdapter::new(Memory::new());
let active_generation = CommitId::for_test_label("active-generation");
let active_checkpoint = CommitId::for_test_label("active-checkpoint");
let stale_generation = CommitId::for_test_label("stale-generation");
let stale_checkpoint = CommitId::for_test_label("stale-checkpoint");
let orphan_generation = CommitId::for_test_label("orphan-generation");
let orphan_checkpoint = CommitId::for_test_label("orphan-checkpoint");
let active_identity = diff_identity("active", active_generation, "active-row");
let stale_identity = diff_identity("stale", stale_generation, "stale-row");
let orphan_identity = diff_identity("deleted", orphan_generation, "orphan-row");
let (active_key, active_value) =
single_hot_diff_segment(active_checkpoint, &active_identity);
let (stale_key, stale_value) = single_hot_diff_segment(stale_checkpoint, &stale_identity);
let (orphan_key, orphan_value) =
single_hot_diff_segment(orphan_checkpoint, &orphan_identity);
let mut writes = StorageWriteSet::new();
stage_tracked_working_diff_epoch(
&mut writes,
"active",
TrackedWorkingDiffEpoch {
checkpoint_commit_id: active_checkpoint,
generation: active_generation,
coverage: WorkingDiffIndexCoverage::default(),
},
)
.expect("stage active epoch");
stage_branch_head_control(
&mut writes,
"active",
BranchHeadControl {
head_commit_id: active_generation,
tracked_generation: active_generation,
current_state_revision: 0,
schema_presence_bloom: [u64::MAX; 4],
working_diff_checkpoint_commit_id: Some(active_checkpoint),
created_at: timestamp(),
updated_at: timestamp(),
ref_change_id: ChangeId::for_test_label("active-ref"),
},
)
.expect("stage active control");
stage_tracked_working_diff_epoch(
&mut writes,
"stale",
TrackedWorkingDiffEpoch {
checkpoint_commit_id: stale_checkpoint,
generation: stale_generation,
coverage: WorkingDiffIndexCoverage::default(),
},
)
.expect("stage stale epoch");
stage_branch_head_control(
&mut writes,
"stale",
BranchHeadControl {
head_commit_id: stale_generation,
tracked_generation: stale_generation,
current_state_revision: 0,
schema_presence_bloom: [u64::MAX; 4],
working_diff_checkpoint_commit_id: None,
created_at: timestamp(),
updated_at: timestamp(),
ref_change_id: ChangeId::for_test_label("stale-ref"),
},
)
.expect("stage stale control");
stage_tracked_working_diff_epoch(
&mut writes,
"deleted",
TrackedWorkingDiffEpoch {
checkpoint_commit_id: orphan_checkpoint,
generation: orphan_generation,
coverage: WorkingDiffIndexCoverage::default(),
},
)
.expect("stage orphan epoch");
for (key, value) in [
(&active_key, active_value),
(&stale_key, stale_value),
(&orphan_key, orphan_value),
] {
writes.put(
DIFF_SPACE,
StorageKey(Bytes::copy_from_slice(key)),
StorageValue {
bytes: Bytes::from(value),
},
);
}
storage
.commit_write_set(writes, StorageWriteOptions::default())
.await
.expect("commit hot working-diff GC fixture");
let read = crate::storage_adapter::SharedStorageAdapterRead::new(
storage
.begin_read(StorageReadOptions::default())
.await
.expect("open hot working-diff GC read"),
);
let mut gc_writes = StorageWriteSet::new();
stage_collect_stale_working_diff_indexes(&read, &mut gc_writes)
.await
.expect("stage hot working-diff GC");
drop(read);
storage
.commit_write_set(gc_writes, StorageWriteOptions::default())
.await
.expect("commit hot working-diff GC");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("open hot working-diff GC verification read");
let active_epoch = PointReadPlan::new(
TRACKED_WORKING_DIFF_MARKER_SPACE,
&[StorageKey(Bytes::from(
working_diff_marker_key("active").expect("active working-diff marker key"),
))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read active epoch")
.value
.into_iter()
.next()
.flatten();
assert!(active_epoch.is_some(), "active epoch must survive GC");
for (branch_id, key) in [("stale", stale_key), ("deleted", orphan_key)] {
let epoch = PointReadPlan::new(
TRACKED_WORKING_DIFF_MARKER_SPACE,
&[StorageKey(Bytes::from(
working_diff_marker_key(branch_id).expect("working-diff marker key"),
))],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read stale epoch")
.value
.into_iter()
.next()
.flatten();
assert!(epoch.is_none(), "inactive epoch must be reclaimed");
let record = PointReadPlan::new(DIFF_SPACE, &[StorageKey(Bytes::from(key))])
.materialize(&read, StorageGetOptions::default())
.await
.expect("read stale hot record")
.value
.into_iter()
.next()
.flatten();
assert!(record.is_none(), "inactive hot record must be reclaimed");
}
let active_record = PointReadPlan::new(DIFF_SPACE, &[StorageKey(Bytes::from(active_key))])
.materialize(&read, StorageGetOptions::default())
.await
.expect("read active hot record")
.value
.into_iter()
.next()
.flatten();
assert!(active_record.is_some(), "active hot record must survive GC");
}
#[tokio::test]
async fn checkpoint_compaction_keeps_only_globally_shadowed_tombstones() {
const BRANCH_ID: &str = "01920000-0000-7000-8000-0000000000e5";
const BRANCH_LABEL: &str = "e45e-compaction-branch";
const GLOBAL_LABEL: &str = "e45e-compaction-global";
const SHADOWED_SCHEMA: &str = "e45e_shadowed_schema";
const PRIVATE_SCHEMA: &str = "e45e_private_schema";
let storage = StorageAdapter::new(Memory::new());
let created_at = timestamp();
let shadowed_pk = RowPk::single("shadowed-row");
let private_pk = RowPk::single("private-row");
let global_commit = CommitId::for_test_label(GLOBAL_LABEL);
let generation = CommitId::for_test_label(BRANCH_LABEL);
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
crate::GLOBAL_BRANCH_ID,
GLOBAL_LABEL,
&[MaterializedTrackedStateRow {
row_pk: shadowed_pk.clone(),
schema_key: SHADOWED_SCHEMA.to_owned(),
file_id: None,
snapshot_content: Some(r#"{"key":"global-row"}"#.into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
change_id: ChangeId::for_test_label("e45e-global-change"),
commit_id: global_commit,
}],
)
.await;
crate::test_support::seed_branch_head_with_rows(
storage.clone(),
BRANCH_ID,
BRANCH_LABEL,
&[
MaterializedTrackedStateRow {
row_pk: private_pk.clone(),
schema_key: PRIVATE_SCHEMA.to_owned(),
file_id: None,
snapshot_content: Some(r#"{"key":"private-row"}"#.into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
change_id: ChangeId::for_test_label("e45e-branch-private"),
commit_id: generation,
},
MaterializedTrackedStateRow {
row_pk: shadowed_pk.clone(),
schema_key: SHADOWED_SCHEMA.to_owned(),
file_id: None,
snapshot_content: Some(r#"{"key":"branch-row"}"#.into()),
decoded_snapshot: None,
metadata: None,
deleted: false,
created_at: created_at.to_string(),
updated_at: created_at.to_string(),
change_id: ChangeId::for_test_label("e45e-branch-shadowed"),
commit_id: generation,
},
],
)
.await;
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("open compaction gate read");
let checkpoint_head = CommitId::for_test_label("e45e-compaction-checkpoint");
let deltas = [
CurrentStateDeltaRef {
schema_key: PRIVATE_SCHEMA,
file_id: None,
row_pk: &private_pk,
change_id: Some(ChangeId::for_test_label("e45e-delete-private")),
commit_id: Some(checkpoint_head),
untracked: false,
deleted: true,
created_at,
updated_at: created_at,
snapshot: None,
metadata: None,
columnar_base_coordinate: None,
},
CurrentStateDeltaRef {
schema_key: SHADOWED_SCHEMA,
file_id: None,
row_pk: &shadowed_pk,
change_id: Some(ChangeId::for_test_label("e45e-delete-shadowed")),
commit_id: Some(checkpoint_head),
untracked: false,
deleted: true,
created_at,
updated_at: created_at,
snapshot: None,
metadata: None,
columnar_base_coordinate: None,
},
];
let staged_on_global = BTreeSet::new();
let mut checkpoint_writes = StorageWriteSet::new();
let mut coverage = WorkingDiffIndexCoverage::default();
HotStateWriter {
store: &read,
writes: &mut checkpoint_writes,
transaction_global_schema_keys: Some(&staged_on_global),
}
.stage_checkpoint_current_state(
BRANCH_ID,
generation,
checkpoint_head,
&deltas,
&BTreeSet::new(),
checkpoint_head,
&mut coverage,
)
.await
.expect("checkpoint should stage");
drop(read);
storage
.commit_write_set(checkpoint_writes, StorageWriteOptions::default())
.await
.expect("commit compaction checkpoint");
let read = storage
.begin_read(StorageReadOptions::default())
.await
.expect("verify compaction gate");
let key = |schema_key: &str, row_pk: &RowPk| {
StorageKey(Bytes::from(encode_hot_row_key(&HeadIdentity {
branch_id: BRANCH_ID.to_owned(),
generation,
schema_key: schema_key.to_owned(),
row_pk: row_pk.clone(),
file_id: None,
})))
};
let rows = PointReadPlan::new(
ROW_SPACE,
&[
key(SHADOWED_SCHEMA, &shadowed_pk),
key(PRIVATE_SCHEMA, &private_pk),
],
)
.materialize(&read, StorageGetOptions::default())
.await
.expect("read compacted generation")
.value;
assert!(
rows[0].is_some(),
"a tombstone shadowing a global row must survive the checkpoint"
);
assert!(
rows[1].is_none(),
"a tombstone shadowing nothing must be reclaimed by the checkpoint"
);
}
}
#[cfg(test)]
mod effective_file_cascade_tests {
use super::*;
#[tokio::test]
async fn deleting_file_removes_root_backed_rows_and_preserves_other_file() {
const FILE: &str = "01920000-0000-7000-8000-0000000000a1";
const OTHER: &str = "01920000-0000-7000-8000-0000000000a2";
let backing = crate::storage_adapter::Memory::new();
let lix = crate::open_lix()
.with_storage(backing.clone())
.await
.unwrap();
let schema = serde_json::json!({
"$schema":"https://lix.dev/schema-v1.json", "key":"cascade_probe",
"columns":[{"name":"id","type":"text","nullable":false},{"name":"value","type":"text","nullable":false}],
"primary_key":["id"]
});
lix.execute(
"INSERT INTO lix_registered_schema(value) VALUES(CAST($1 AS JSONB))",
&[crate::Value::Text(schema.to_string())],
)
.await
.unwrap();
lix.execute("INSERT INTO lix_file(id,path,content) VALUES($1,'/deleted.bin',CAST('a' AS BYTEA)),($2,'/retained.bin',CAST('b' AS BYTEA))", &[crate::Value::Text(FILE.into()),crate::Value::Text(OTHER.into())]).await.unwrap();
let values = (0..600)
.map(|index| format!("('row-{index}','value','{FILE}')"))
.chain(std::iter::once(format!("('retained','other','{OTHER}')")))
.collect::<Vec<_>>()
.join(",");
lix.execute(
&format!("INSERT INTO cascade_probe(id,value,lixcol_file_id) VALUES {values}"),
&[],
)
.await
.unwrap();
lix.create_checkpoint().await.unwrap();
let branch_id = lix.active_branch_id().await.unwrap();
let storage = lix.storage_adapter();
let read = storage.begin_read(Default::default()).await.unwrap();
let observation = BranchHeadControlContext::new()
.reader(&read)
.load_observed(std::slice::from_ref(&branch_id))
.await
.unwrap()
.pop()
.unwrap();
let mut control = observation.control.unwrap();
let checkpoint = control.working_diff_checkpoint_commit_id.unwrap();
drop(read);
lix.close().await.unwrap();
let generation = CommitId::for_test_label("file-cascade-root-generation");
let read = storage.begin_read(Default::default()).await.unwrap();
let mut writes = storage.new_write_set();
TrackedHeadContext::new()
.writer(&read, &mut writes)
.stage_root_current_base(&branch_id, generation, checkpoint);
let epoch_guard =
stage_root_working_diff_epoch(&read, &mut writes, &branch_id, generation, checkpoint)
.await
.unwrap();
let control_guard =
crate::branch::branch_head_control_precondition(&branch_id, observation.raw_token)
.unwrap();
control.tracked_generation = generation;
stage_branch_head_control(&mut writes, &branch_id, control).unwrap();
drop(read);
storage
.commit_write_set(
writes,
crate::storage_adapter::StorageWriteOptions {
preconditions: vec![epoch_guard, control_guard],
..Default::default()
},
)
.await
.unwrap();
let read = storage.begin_read(Default::default()).await.unwrap();
assert_eq!(
load_root_current_base_commit(&read, &branch_id, generation)
.await
.unwrap(),
Some(checkpoint)
);
let mut physical = read
.begin_scan(
ROW_SPACE,
StoragePrefix {
bytes: Bytes::from(hot_scope_prefix(&branch_id, generation)),
}
.to_range()
.unwrap(),
StorageBeginScanOptions {
projection: StorageCoreProjection::KeyOnly,
..Default::default()
},
)
.await
.unwrap();
let (physical_rows, _) = physical.next_page(1).await.unwrap().into_parts();
assert!(
physical_rows.is_empty(),
"regression requires root-backed state with no physical HOT rows"
);
drop(physical);
drop(read);
let lix = crate::open_lix().with_storage(backing).await.unwrap();
lix.execute(
"DELETE FROM lix_file WHERE id=$1",
&[crate::Value::Text(FILE.into())],
)
.await
.unwrap();
let deleted = lix
.execute(
"SELECT id FROM cascade_probe WHERE lixcol_file_id=$1",
&[crate::Value::Text(FILE.into())],
)
.await
.unwrap();
assert!(
deleted.rows().is_empty(),
"descriptor deletion must cascade into root-backed semantic state"
);
let retained = lix.execute("SELECT id FROM cascade_probe WHERE lixcol_file_id=$1 AND id='retained' AND value='other'", &[crate::Value::Text(OTHER.into())]).await.unwrap();
assert_eq!(retained.rows().len(), 1);
}
}