Skip to content

Commit

Permalink
[storage] Add move_at to Storage #177 (#178)
Browse files Browse the repository at this point in the history
* Update file_storage.rs

* Update storage.rs

* Update storage_impl.rs

* Update pr.yaml
  • Loading branch information
michaelvlach authored Sep 9, 2022
1 parent fad455e commit 5581541
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- uses: actions/checkout@v3
- uses: taiki-e/install-action@cargo-llvm-cov
- run: rustup component add llvm-tools-preview
- run: cargo llvm-cov --fail-uncovered-regions 92 --fail-uncovered-functions 0 --fail-uncovered-lines 0
- run: cargo llvm-cov --fail-uncovered-regions 99 --fail-uncovered-functions 0 --fail-uncovered-lines 0

test:
runs-on: ubuntu-latest
Expand Down
27 changes: 26 additions & 1 deletion src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,36 @@ pub(crate) trait Storage<T: StorageImpl = Self>: StorageImpl<T> {
self.transaction();
let mut record = self.record(index)?;
let bytes = V::serialize(value);
self.ensure_record_size(&mut record, index, offset, bytes.len())?;
self.ensure_record_size(&mut record, index, offset, bytes.len() as u64)?;
self.write(Self::value_position(record.position, offset), bytes)?;
self.commit()
}

fn move_at(
&mut self,
index: i64,
offset_from: u64,
offset_to: u64,
size: u64,
) -> Result<(), DbError> {
if offset_from == offset_to || size == 0 {
return Ok(());
}

let mut record = self.record(index)?;
Self::validate_move_size(offset_from, size, record.size)?;
self.transaction();
self.ensure_record_size(&mut record, index, offset_to, size)?;
self.move_bytes(
Self::value_position_u64(record.position, offset_from),
Self::value_position_u64(record.position, offset_to),
size,
)?;
self.commit()?;

Ok(())
}

fn remove(&mut self, index: i64) -> Result<(), DbError> {
self.transaction();
let position = self.record(index)?.position;
Expand Down
97 changes: 97 additions & 0 deletions src/storage/file_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,103 @@ mod tests {
);
}

#[test]
fn move_at() {
let test_file = TestFile::from("./file_storage-move_at.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

let index = storage.insert(&vec![1_i64, 2_i64, 3_i64]).unwrap();
let offset_from = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>() * 2) as u64;
let offset_to = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>()) as u64;
let size = std::mem::size_of::<u64>() as u64;

storage
.move_at(index, offset_from, offset_to, size)
.unwrap();

assert_eq!(
storage.value::<Vec<i64>>(index).unwrap(),
vec![1_i64, 3_i64, 0_i64]
)
}

#[test]
fn move_at_beyond_end() {
let test_file = TestFile::from("./file_storage-move_at_beyond_end.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

let index = storage.insert(&vec![1_i64, 2_i64, 3_i64]).unwrap();
let offset_from = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>()) as u64;
let offset_to = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>() * 4) as u64;
let size = std::mem::size_of::<u64>() as u64;

storage
.move_at(index, offset_from, offset_to, size)
.unwrap();

storage.insert_at(index, 0, &5_u64).unwrap();

assert_eq!(
storage.value::<Vec<i64>>(index).unwrap(),
vec![1_i64, 0_i64, 3_i64, 0_i64, 2_i64]
)
}

#[test]
fn move_at_missing_index() {
let test_file = TestFile::from("./file_storage-move_at_missing_index.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

assert_eq!(
storage.move_at(1, 0, 1, 10),
Err(DbError::Storage("index '1' not found".to_string()))
);
}

#[test]
fn move_at_same_offset() {
let test_file = TestFile::from("./file_storage-move_at_same_offset.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

let index = storage.insert(&vec![1_i64, 2_i64, 3_i64]).unwrap();

assert_eq!(storage.move_at(index, 0, 0, 10), Ok(()));
assert_eq!(
storage.value::<Vec<i64>>(index).unwrap(),
vec![1_i64, 2_i64, 3_i64]
);
}

#[test]
fn move_at_size_out_of_bounds() {
let test_file = TestFile::from("./file_storage-move_at_size_out_of_bounds.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

let index = storage.insert(&vec![1_i64, 2_i64, 3_i64]).unwrap();
let offset_from = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>() * 3) as u64;
let offset_to = (std::mem::size_of::<u64>() + std::mem::size_of::<i64>() * 2) as u64;
let size = (std::mem::size_of::<u64>() * 10) as u64;

assert_eq!(
storage.move_at(index, offset_from, offset_to, size),
Err(DbError::Storage("move size out of bounds".to_string()))
);
}

#[test]
fn move_at_zero_size() {
let test_file = TestFile::from("./file_storage-move_at_zero_size.agdb");
let mut storage = FileStorage::try_from(test_file.file_name().as_str()).unwrap();

let index = storage.insert(&vec![1_i64, 2_i64, 3_i64]).unwrap();

assert_eq!(storage.move_at(index, 0, 1, 0), Ok(()));
assert_eq!(
storage.value::<Vec<i64>>(index).unwrap(),
vec![1_i64, 2_i64, 3_i64]
);
}

#[test]
fn remove() {
let test_file = TestFile::from("./file_storage-remove.agdb");
Expand Down
41 changes: 38 additions & 3 deletions src/storage/storage_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ pub(crate) trait StorageImpl<T = Self> {
record: &mut StorageRecord,
index: i64,
offset: u64,
value_size: usize,
value_size: u64,
) -> Result<(), DbError> {
let new_size = offset + value_size as u64;
let new_size = offset + value_size;

if new_size > record.size {
self.resize_record(index, new_size, offset, record)?;
Expand All @@ -90,6 +90,13 @@ pub(crate) trait StorageImpl<T = Self> {
Ok(())
}

fn erase_bytes(&mut self, position: u64, size: u64) -> Result<(), DbError> {
self.write(
std::io::SeekFrom::Start(position),
vec![0_u8; size as usize],
)
}

fn indexes_by_position(&self) -> Vec<i64>;
fn insert_wal_record(&mut self, record: WriteAheadLogRecord) -> Result<(), DbError>;

Expand All @@ -99,6 +106,7 @@ pub(crate) trait StorageImpl<T = Self> {

fn is_at_end(&mut self, record: &StorageRecord) -> Result<bool, DbError> {
let file_size = self.seek(std::io::SeekFrom::End(0))?;

Ok(
(record.position + std::mem::size_of::<StorageRecord>() as u64 + record.size)
== file_size,
Expand All @@ -120,6 +128,21 @@ pub(crate) trait StorageImpl<T = Self> {
new_size,
)?;
self.invalidate_record(index, old_position)?;

Ok(())
}

fn move_bytes(&mut self, from: u64, to: u64, size: u64) -> Result<(), DbError> {
let bytes = self.read(std::io::SeekFrom::Start(from), size)?;
self.write(std::io::SeekFrom::Start(to), bytes)?;

if from < to {
self.erase_bytes(from, std::cmp::min(size, to - from))?;
} else {
let position = std::cmp::max(to + size, from);
self.erase_bytes(position, from + size - position)?;
}

Ok(())
}

Expand Down Expand Up @@ -230,6 +253,14 @@ pub(crate) trait StorageImpl<T = Self> {
Ok(())
}

fn validate_move_size(offset: u64, size: u64, record_size: u64) -> Result<(), DbError> {
if record_size < (offset + size) {
return Err(DbError::Storage("move size out of bounds".to_string()));
}

Ok(())
}

fn validate_offset<V>(size: u64, offset: u64) -> Result<(), DbError> {
if size < offset {
return Err(DbError::Storage(
Expand All @@ -251,7 +282,11 @@ pub(crate) trait StorageImpl<T = Self> {
}

fn value_position(position: u64, offset: u64) -> std::io::SeekFrom {
std::io::SeekFrom::Start(position + std::mem::size_of::<StorageRecord>() as u64 + offset)
std::io::SeekFrom::Start(Self::value_position_u64(position, offset))
}

fn value_position_u64(position: u64, offset: u64) -> u64 {
position + std::mem::size_of::<StorageRecord>() as u64 + offset
}

fn value_read_size<V>(size: u64, offset: u64) -> Result<u64, DbError> {
Expand Down

0 comments on commit 5581541

Please sign in to comment.