blob: 6df42e2cc6c722871eec0a0227e1672ade5a03a4 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(fxbug.dev/96139): need validation after deserialization.
use {
crate::lsm_tree::types::{OrdLowerBound, OrdUpperBound},
serde::{Deserialize, Serialize},
std::cmp::{max, min},
std::ops::Range,
};
/// The common case for extents which cover the data payload of some object.
pub const DEFAULT_DATA_ATTRIBUTE_ID: u64 = 0;
/// ExtentKey is a child of ObjectKey for Object attributes that have attached extents
/// (at time of writing this was only the used for file contents).
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ExtentKey {
pub range: Range<u64>,
}
impl ExtentKey {
/// Creates an ExtentKey.
pub fn new(range: std::ops::Range<u64>) -> Self {
Self { range }
}
/// Returns the range of bytes common between this extent and |other|.
pub fn overlap(&self, other: &ExtentKey) -> Option<Range<u64>> {
if self.range.end <= other.range.start || self.range.start >= other.range.end {
None
} else {
Some(max(self.range.start, other.range.start)..min(self.range.end, other.range.end))
}
}
/// Returns the search key for this extent; that is, a key which is <= this key under Ord and
/// OrdLowerBound.
/// This would be used when searching for an extent with |find| (when we want to find any
/// overlapping extent, which could include extents that start earlier).
/// For example, if the tree has extents 50..150 and 150..200 and we wish to read 100..200,
/// we'd search for 0..101 which would set the iterator to 50..150.
pub fn search_key(&self) -> Self {
assert_ne!(self.range.start, self.range.end);
ExtentKey::search_key_from_offset(self.range.start)
}
/// Similar to previous, but from an offset. Returns a search key that will find the first
/// extent that touches offset..
pub fn search_key_from_offset(offset: u64) -> Self {
Self { range: 0..offset + 1 }
}
/// Returns the merge key for this extent; that is, a key which is <= this extent and any other
/// possibly overlapping extent, under Ord. This would be used to set the hint for |merge_into|.
///
/// For example, if the tree has extents 0..50, 50..150 and 150..200 and we wish to insert
/// 100..150, we'd use a merge hint of 0..100 which would set the iterator to 50..150 (the first
/// element > 100..150 under Ord).
pub fn key_for_merge_into(&self) -> Self {
Self { range: 0..self.range.start }
}
}
// The normal comparison uses the end of the range before the start of the range. This makes
// searching for records easier because it's easy to find K.. (where K is the key you are searching
// for), which is what we want since our search routines find items with keys >= a search key.
// OrdLowerBound orders by the start of an extent.
impl OrdUpperBound for ExtentKey {
fn cmp_upper_bound(&self, other: &ExtentKey) -> std::cmp::Ordering {
// The comparison uses the end of the range so that we can more easily do queries. Whilst
// it might be tempting to break ties by comparing the range start, next_key currently
// relies on keys with the same end being equal, and since we do not support overlapping
// keys within the same layer, ties can always be broken using layer index. Insertions into
// the mutable layer should always be done using merge_into, which will ensure keys don't
// end up overlapping.
self.range.end.cmp(&other.range.end)
}
}
impl OrdLowerBound for ExtentKey {
// Orders by the start of the range rather than the end, and doesn't include the end in the
// comparison. This is used when merging, where we want to merge keys in lower-bound order.
fn cmp_lower_bound(&self, other: &ExtentKey) -> std::cmp::Ordering {
self.range.start.cmp(&other.range.start)
}
}
impl Ord for ExtentKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// We expect cmp_upper_bound and cmp_lower_bound to be used mostly, but ObjectKey needs an
// Ord method in order to compare other enum variants, and Transaction requires an ObjectKey
// to implement Ord.
self.range.start.cmp(&other.range.start).then(self.range.end.cmp(&other.range.end))
}
}
impl PartialOrd for ExtentKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Checksums {
None,
/// A vector of checksums, one per block.
Fletcher(Vec<u64>),
}
impl Checksums {
pub fn split_off(&mut self, at: usize) -> Checksums {
match self {
Checksums::None => Checksums::None,
Checksums::Fletcher(sums) => Checksums::Fletcher(sums.split_off(at)),
}
}
}
/// ExtentValue is the payload for an extent in the object store, which describes where the extent
/// is physically located.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ExtentValue {
/// Indicates a deleted extent; that is, the logical range described by the extent key is
/// considered to be deleted.
None,
/// The location of the extent and other related information. `key_id` identifies which
/// of the object's keys should be used. `checksums` hold post-encryption checksums.
Some { device_offset: u64, checksums: Checksums, key_id: u64 },
}
impl ExtentValue {
pub fn new(device_offset: u64) -> ExtentValue {
ExtentValue::Some { device_offset, checksums: Checksums::None, key_id: 0 }
}
/// Creates an ExtentValue with a checksum
pub fn with_checksum(device_offset: u64, checksums: Checksums) -> ExtentValue {
ExtentValue::Some { device_offset, checksums, key_id: 0 }
}
/// Creates an ObjectValue for a deletion of an object extent.
pub fn deleted_extent() -> ExtentValue {
ExtentValue::None
}
pub fn is_deleted(&self) -> bool {
if let ExtentValue::None = self {
true
} else {
false
}
}
/// Returns a new ExtentValue offset by `amount`. Both `amount` and `extent_len` must be
/// multiples of the underlying block size.
pub fn offset_by(&self, amount: u64, extent_len: u64) -> Self {
match self {
ExtentValue::None => Self::deleted_extent(),
ExtentValue::Some { device_offset, checksums, key_id } => {
if let Checksums::Fletcher(checksums) = checksums {
if checksums.len() > 0 {
let index = (amount / (extent_len / checksums.len() as u64)) as usize;
return ExtentValue::Some {
device_offset: device_offset + amount,
checksums: Checksums::Fletcher(checksums[index..].to_vec()),
key_id: *key_id,
};
}
}
ExtentValue::Some {
device_offset: device_offset + amount,
checksums: Checksums::None,
key_id: *key_id,
}
}
}
}
/// Returns a new ExtentValue after shrinking the extent from |original_len| to |new_len|.
pub fn shrunk(&self, original_len: u64, new_len: u64) -> Self {
match self {
ExtentValue::None => Self::deleted_extent(),
ExtentValue::Some { device_offset, checksums, key_id } => {
if let Checksums::Fletcher(checksums) = checksums {
if checksums.len() > 0 {
let checksum_len =
(new_len / (original_len / checksums.len() as u64)) as usize;
return ExtentValue::Some {
device_offset: *device_offset,
checksums: Checksums::Fletcher(checksums[..checksum_len].to_vec()),
key_id: *key_id,
};
}
}
ExtentValue::Some {
device_offset: *device_offset,
checksums: Checksums::None,
key_id: *key_id,
}
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::ExtentKey,
crate::lsm_tree::types::{OrdLowerBound, OrdUpperBound},
std::cmp::Ordering,
};
#[test]
fn test_extent_cmp() {
let extent = ExtentKey::new(100..150);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(0..100)), Ordering::Greater);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(0..110)), Ordering::Greater);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(0..150)), Ordering::Equal);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(99..150)), Ordering::Equal);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(100..150)), Ordering::Equal);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(0..151)), Ordering::Less);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(100..151)), Ordering::Less);
assert_eq!(extent.cmp_upper_bound(&ExtentKey::new(150..1000)), Ordering::Less);
}
#[test]
fn test_extent_cmp_lower_bound() {
let extent = ExtentKey::new(100..150);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(0..100)), Ordering::Greater);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(0..110)), Ordering::Greater);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(0..150)), Ordering::Greater);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(0..1000)), Ordering::Greater);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(99..1000)), Ordering::Greater);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(100..150)), Ordering::Equal);
// cmp_lower_bound does not check the upper bound of the range
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(100..1000)), Ordering::Equal);
assert_eq!(extent.cmp_lower_bound(&ExtentKey::new(101..102)), Ordering::Less);
}
#[test]
fn test_extent_search_and_insertion_key() {
let extent = ExtentKey::new(100..150);
assert_eq!(extent.search_key(), ExtentKey::new(0..101));
assert_eq!(extent.cmp_lower_bound(&extent.search_key()), Ordering::Greater);
assert_eq!(extent.cmp_upper_bound(&extent.search_key()), Ordering::Greater);
assert_eq!(extent.key_for_merge_into(), ExtentKey::new(0..100));
assert_eq!(extent.cmp_lower_bound(&extent.key_for_merge_into()), Ordering::Greater);
}
}