trine-kv 0.6.0

Embedded LSM MVCC key-value database.
Documentation
use super::{
    BlobFileHeader, BlobRecord, CodecId, DurabilityMode, Error, InternalKey, NativeFileBackend,
    NativeFileObject, Path, Result, Sequence, StorageObjectWriteBackend, StorageReadBackend,
    ValueRef, blob_object_len, blob_object_len_async, blob_storage_backend,
    open_blob_read_object_with_backend, open_blob_read_object_with_backend_async,
    read_indexed_blob_record, read_indexed_blob_record_async, read_indexed_value_with_backend,
    read_indexed_value_with_backend_async, usize_to_u64, validate_indexed_blob_header,
    validate_indexed_blob_header_async, write_blob_file_with_backend_async,
    write_blob_file_with_backend_with_durability,
};

#[allow(dead_code)]
pub(crate) fn write_large_values(
    db_path: &Path,
    file_id: u64,
    threshold: usize,
    compression: CodecId,
    records: &[(InternalKey, Option<ValueRef>)],
) -> Result<Vec<(InternalKey, Option<ValueRef>)>> {
    let backend = blob_storage_backend();
    write_large_values_with_backend(&backend, db_path, file_id, threshold, compression, records)
}

pub(crate) fn write_large_values_with_backend(
    backend: &NativeFileBackend,
    db_path: &Path,
    file_id: u64,
    threshold: usize,
    compression: CodecId,
    records: &[(InternalKey, Option<ValueRef>)],
) -> Result<Vec<(InternalKey, Option<ValueRef>)>> {
    write_large_values_with_backend_with_durability(
        backend,
        db_path,
        file_id,
        threshold,
        compression,
        records,
        DurabilityMode::SyncAll,
    )
}

pub(crate) fn write_large_values_with_backend_with_durability(
    backend: &NativeFileBackend,
    db_path: &Path,
    file_id: u64,
    threshold: usize,
    compression: CodecId,
    records: &[(InternalKey, Option<ValueRef>)],
    durability: DurabilityMode,
) -> Result<Vec<(InternalKey, Option<ValueRef>)>> {
    let needs_blob_file = records.iter().any(
        |(_, value)| matches!(value, Some(ValueRef::Inline(bytes)) if bytes.len() >= threshold),
    );
    if !needs_blob_file {
        return Ok(records.to_vec());
    }

    let mut blob_records = Vec::new();
    for (internal_key, value) in records {
        if let Some(ValueRef::Inline(bytes)) = value
            && bytes.len() >= threshold
        {
            blob_records.push(BlobRecord {
                internal_key: internal_key.clone(),
                value: bytes.clone(),
                compression,
            });
        }
    }

    let creation_sequence = records
        .iter()
        .map(|(internal_key, _)| internal_key.sequence())
        .max()
        .unwrap_or(Sequence::ZERO);
    let threshold_bytes = usize_to_u64(threshold, "blob threshold")?;
    let header = BlobFileHeader::new(file_id, creation_sequence, threshold_bytes, compression);
    let indexes = write_blob_file_with_backend_with_durability(
        backend,
        db_path,
        file_id,
        header,
        &blob_records,
        durability,
    )?;
    let mut index_iter = indexes.into_iter();

    let mut rewritten = Vec::with_capacity(records.len());

    for (internal_key, value) in records {
        let value = match value {
            Some(ValueRef::Inline(bytes)) if bytes.len() >= threshold => {
                let index = index_iter.next().ok_or_else(|| Error::Corruption {
                    message: "missing blob index for separated value".to_owned(),
                })?;
                Some(ValueRef::BlobIndex(index))
            }
            value => value.clone(),
        };
        rewritten.push((internal_key.clone(), value));
    }
    if index_iter.next().is_some() {
        return Err(Error::Corruption {
            message: "unused blob index after rewriting large values".to_owned(),
        });
    }

    Ok(rewritten)
}
pub(crate) async fn write_large_values_with_backend_async<B>(
    backend: &B,
    db_path: &Path,
    file_id: u64,
    threshold: usize,
    compression: CodecId,
    records: &[(InternalKey, Option<ValueRef>)],
    durability: DurabilityMode,
) -> Result<Vec<(InternalKey, Option<ValueRef>)>>
where
    B: StorageObjectWriteBackend,
{
    let needs_blob_file = records.iter().any(
        |(_, value)| matches!(value, Some(ValueRef::Inline(bytes)) if bytes.len() >= threshold),
    );
    if !needs_blob_file {
        return Ok(records.to_vec());
    }

    let mut blob_records = Vec::new();
    for (internal_key, value) in records {
        if let Some(ValueRef::Inline(bytes)) = value
            && bytes.len() >= threshold
        {
            blob_records.push(BlobRecord {
                internal_key: internal_key.clone(),
                value: bytes.clone(),
                compression,
            });
        }
    }

    let creation_sequence = records
        .iter()
        .map(|(internal_key, _)| internal_key.sequence())
        .max()
        .unwrap_or(Sequence::ZERO);
    let threshold_bytes = usize_to_u64(threshold, "blob threshold")?;
    let header = BlobFileHeader::new(file_id, creation_sequence, threshold_bytes, compression);
    let indexes = write_blob_file_with_backend_async(
        backend,
        db_path,
        file_id,
        header,
        &blob_records,
        durability,
    )
    .await?;
    let mut index_iter = indexes.into_iter();

    let mut rewritten = Vec::with_capacity(records.len());

    for (internal_key, value) in records {
        let value = match value {
            Some(ValueRef::Inline(bytes)) if bytes.len() >= threshold => {
                let index = index_iter.next().ok_or_else(|| Error::Corruption {
                    message: "missing blob index for separated value".to_owned(),
                })?;
                Some(ValueRef::BlobIndex(index))
            }
            value => value.clone(),
        };
        rewritten.push((internal_key.clone(), value));
    }
    if index_iter.next().is_some() {
        return Err(Error::Corruption {
            message: "unused blob index after rewriting large values".to_owned(),
        });
    }

    Ok(rewritten)
}

#[allow(dead_code)]
pub(crate) fn inline_blob_values(
    db_path: &Path,
    records: &[(InternalKey, Option<ValueRef>)],
) -> Result<Vec<(InternalKey, Option<ValueRef>)>> {
    let backend = blob_storage_backend();
    inline_blob_values_with_backend(&backend, db_path, records)
}

pub(crate) fn inline_blob_values_with_backend(
    backend: &NativeFileBackend,
    db_path: &Path,
    records: &[(InternalKey, Option<ValueRef>)],
) -> Result<Vec<(InternalKey, Option<ValueRef>)>> {
    let mut rewritten = Vec::with_capacity(records.len());
    let mut blob_file = None;
    for (internal_key, value) in records {
        let value = match value {
            Some(ValueRef::Inline(bytes)) => Some(ValueRef::Inline(bytes.clone())),
            Some(value @ ValueRef::BlobIndex(_)) => {
                Some(ValueRef::Inline(read_value_for_internal_key_cached(
                    backend,
                    db_path,
                    value,
                    Some(internal_key),
                    &mut blob_file,
                )?))
            }
            None => None,
        };
        rewritten.push((internal_key.clone(), value));
    }
    Ok(rewritten)
}

pub(super) struct CachedBlobFile {
    object: NativeFileObject,
    len: u64,
}

pub(super) fn read_value_for_internal_key_cached(
    backend: &NativeFileBackend,
    db_path: &Path,
    value: &ValueRef,
    expected_internal_key: Option<&InternalKey>,
    blob_file: &mut Option<(u64, CachedBlobFile)>,
) -> Result<Vec<u8>> {
    match value {
        ValueRef::Inline(bytes) => Ok(bytes.clone()),
        ValueRef::BlobIndex(index) => {
            let blob_file = cached_blob_file(backend, db_path, index.file_id, blob_file)?;
            let record = read_indexed_blob_record(&blob_file.object, blob_file.len, index)?;
            if record.index != *index {
                return Err(Error::Corruption {
                    message: "blob index metadata mismatch".to_owned(),
                });
            }
            if expected_internal_key.is_some_and(|expected| record.record.internal_key != *expected)
            {
                return Err(Error::Corruption {
                    message: "blob record internal key mismatch".to_owned(),
                });
            }
            Ok(record.record.value)
        }
    }
}

pub(super) fn cached_blob_file<'files>(
    backend: &NativeFileBackend,
    db_path: &Path,
    file_id: u64,
    blob_file: &'files mut Option<(u64, CachedBlobFile)>,
) -> Result<&'files CachedBlobFile> {
    if blob_file
        .as_ref()
        .is_none_or(|(cached_file_id, _)| *cached_file_id != file_id)
    {
        let object = open_blob_read_object_with_backend(backend, db_path, file_id)?;
        let len = blob_object_len(&object, "referenced blob file metadata cannot be read")?;
        validate_indexed_blob_header(&object, file_id)?;
        *blob_file = Some((file_id, CachedBlobFile { object, len }));
    }
    blob_file
        .as_ref()
        .map(|(_, file)| file)
        .ok_or_else(|| Error::Corruption {
            message: "blob handle cache lost an inserted file".to_owned(),
        })
}
pub(crate) async fn inline_blob_values_with_backend_async<B>(
    backend: &B,
    db_path: &Path,
    records: &[(InternalKey, Option<ValueRef>)],
) -> Result<Vec<(InternalKey, Option<ValueRef>)>>
where
    B: StorageReadBackend,
{
    let mut rewritten = Vec::with_capacity(records.len());
    let mut blob_file = None;
    for (internal_key, value) in records {
        let value = match value {
            Some(ValueRef::Inline(bytes)) => Some(ValueRef::Inline(bytes.clone())),
            Some(value @ ValueRef::BlobIndex(_)) => Some(ValueRef::Inline(
                read_value_for_internal_key_cached_async(
                    backend,
                    db_path,
                    value,
                    Some(internal_key),
                    &mut blob_file,
                )
                .await?,
            )),
            None => None,
        };
        rewritten.push((internal_key.clone(), value));
    }
    Ok(rewritten)
}

struct AsyncCachedBlobFile<O> {
    object: O,
    len: u64,
}

async fn read_value_for_internal_key_cached_async<B>(
    backend: &B,
    db_path: &Path,
    value: &ValueRef,
    expected_internal_key: Option<&InternalKey>,
    blob_file: &mut Option<(u64, AsyncCachedBlobFile<B::ReadObject>)>,
) -> Result<Vec<u8>>
where
    B: StorageReadBackend,
{
    let file_id = match value {
        ValueRef::Inline(bytes) => return Ok(bytes.clone()),
        ValueRef::BlobIndex(index) => index.file_id,
    };
    if blob_file
        .as_ref()
        .is_none_or(|(cached_file_id, _)| *cached_file_id != file_id)
    {
        let object = open_blob_read_object_with_backend_async(backend, db_path, file_id).await?;
        let len =
            blob_object_len_async(&object, "referenced blob file metadata cannot be read").await?;
        validate_indexed_blob_header_async(&object, file_id).await?;
        *blob_file = Some((file_id, AsyncCachedBlobFile { object, len }));
    }
    let blob_file = blob_file
        .as_ref()
        .map(|(_, file)| file)
        .ok_or_else(|| Error::Corruption {
            message: "async blob handle cache lost an inserted file".to_owned(),
        })?;

    match value {
        ValueRef::Inline(_) => unreachable!("inline value returned before opening a blob"),
        ValueRef::BlobIndex(index) => {
            let record =
                read_indexed_blob_record_async(&blob_file.object, blob_file.len, index).await?;
            if record.index != *index {
                return Err(Error::Corruption {
                    message: "blob index metadata mismatch".to_owned(),
                });
            }
            if expected_internal_key.is_some_and(|expected| record.record.internal_key != *expected)
            {
                return Err(Error::Corruption {
                    message: "blob record internal key mismatch".to_owned(),
                });
            }
            Ok(record.record.value)
        }
    }
}

pub(crate) fn read_value_for_internal_key(
    db_path: &Path,
    value: &ValueRef,
    expected_internal_key: Option<&InternalKey>,
) -> Result<Vec<u8>> {
    let backend = blob_storage_backend();
    read_value_for_internal_key_with_backend(&backend, db_path, value, expected_internal_key)
}

pub(crate) fn read_value_for_internal_key_with_backend(
    backend: &NativeFileBackend,
    db_path: &Path,
    value: &ValueRef,
    expected_internal_key: Option<&InternalKey>,
) -> Result<Vec<u8>> {
    match value {
        ValueRef::Inline(bytes) => Ok(bytes.clone()),
        ValueRef::BlobIndex(index) => {
            read_indexed_value_with_backend(backend, db_path, index, expected_internal_key)
        }
    }
}
pub(crate) async fn read_value_for_internal_key_with_backend_async<B>(
    backend: &B,
    db_path: &Path,
    value: &ValueRef,
    expected_internal_key: Option<&InternalKey>,
) -> Result<Vec<u8>>
where
    B: StorageReadBackend,
{
    match value {
        ValueRef::Inline(bytes) => Ok(bytes.clone()),
        ValueRef::BlobIndex(index) => {
            read_indexed_value_with_backend_async(backend, db_path, index, expected_internal_key)
                .await
        }
    }
}