diff options
Diffstat (limited to 'log_db')
| -rw-r--r-- | log_db/benches/utils.rs | 2 | ||||
| -rw-r--r-- | log_db/src/common.rs | 57 | ||||
| -rw-r--r-- | log_db/src/lib.rs | 52 | ||||
| -rw-r--r-- | log_db/src/record.rs | 2 |
4 files changed, 50 insertions, 63 deletions
diff --git a/log_db/benches/utils.rs b/log_db/benches/utils.rs index 0f9c620..b9b641e 100644 --- a/log_db/benches/utils.rs +++ b/log_db/benches/utils.rs @@ -90,7 +90,7 @@ pub fn prefill_db( insts: &mut Vec<Inst>, n_records: usize, compact: bool, -) -> Result<(), DBError> { +) -> DBResult<()> { for i in 0..(n_records - insts.len()) { let inst = random_inst(0, n_records as i64); insts.push(inst.clone()); diff --git a/log_db/src/common.rs b/log_db/src/common.rs index a6fd1d2..5539689 100644 --- a/log_db/src/common.rs +++ b/log_db/src/common.rs @@ -42,10 +42,12 @@ pub fn metadata_filename(num: u16) -> String { format!("metadata.{}", num) } +pub type DBResult<A> = Result<A, DBError>; + #[derive(Debug, Error)] pub enum DBError { #[error("lock request failed: {0}")] - LockRequestError(#[from] LockRequestError), + LockRequestError(String), #[error("validation failed: {0}")] ValidationError(String), #[error("consistency check failed: {0}")] @@ -432,7 +434,7 @@ pub fn get_secondary_memtable_index_by_field<Field: Eq>( sks.iter().position(|schema_field| schema_field == field) } -pub fn is_file_same_as_path(file: &File, path: &PathBuf) -> io::Result<bool> { +pub fn is_file_same_as_path(file: &File, path: &PathBuf) -> DBResult<bool> { // Get the metadata for the open file handle let file_metadata = file.metadata()?; @@ -468,7 +470,7 @@ pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { } /// Set the active segment to the segment with the given ordinal number. -pub fn set_active_segment(data_dir_path: &Path, segment_num: u16) -> Result<(), io::Error> { +pub fn set_active_segment(data_dir_path: &Path, segment_num: u16) -> DBResult<()> { let tmp_uuid = Uuid::new_v4(); let tmp_filename = format!("active_{}", tmp_uuid.to_string()); let tmp_path = data_dir_path.join(tmp_filename); @@ -489,7 +491,7 @@ pub fn set_active_segment(data_dir_path: &Path, segment_num: u16) -> Result<(), pub fn create_segment_metadata_file( data_dir_path: &Path, data_file_uuid: &Uuid, -) -> Result<(u16, PathBuf), io::Error> { +) -> DBResult<(u16, PathBuf)> { let current_greatest_num = greatest_segment_number(data_dir_path)?; let new_num = current_greatest_num + 1; @@ -514,7 +516,7 @@ pub fn create_segment_metadata_file( } /// Parse the segment number from a metadata file path -pub fn parse_segment_number(metadata_path: &Path) -> Result<u16, io::Error> { +pub fn parse_segment_number(metadata_path: &Path) -> DBResult<u16> { let filename = metadata_path .file_name() .expect("No filename in symlink") @@ -529,17 +531,14 @@ pub fn parse_segment_number(metadata_path: &Path) -> Result<u16, io::Error> { .parse::<u16>(); segment_number.map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "Failed to parse segment number from filename", - ) + DBError::ValidationError("Failed to parse segment number from filename".to_owned()) }) } /// Get the number of the segment with the greatest ordinal. /// This is the newest segment, i.e. the one that is pointed to by the `active` symlink. /// If there are no segments yet, returns 0. -pub fn greatest_segment_number(data_dir_path: &Path) -> Result<u16, io::Error> { +pub fn greatest_segment_number(data_dir_path: &Path) -> DBResult<u16> { let active_symlink = data_dir_path.join(ACTIVE_SYMLINK_FILENAME); if !fs::exists(&active_symlink)? { @@ -553,7 +552,7 @@ pub fn greatest_segment_number(data_dir_path: &Path) -> Result<u16, io::Error> { /// Create a new segment data file and return its UUID. /// A data file contains the segment data, tightly packed without separators. /// An accompanying metadata file is required to interpret the data. -pub fn create_segment_data_file(data_dir_path: &Path) -> Result<(Uuid, PathBuf), io::Error> { +pub fn create_segment_data_file(data_dir_path: &Path) -> DBResult<(Uuid, PathBuf)> { let uuid = Uuid::new_v4(); let new_segment_path = data_dir_path.join(uuid.to_string()); fs::OpenOptions::new() @@ -567,7 +566,7 @@ pub fn create_segment_data_file(data_dir_path: &Path) -> Result<(Uuid, PathBuf), /// Reads the metadata header from the metadata file. /// Leaves the file seek head at the beginning of the records, after the header. -pub fn read_metadata_header(metadata_file: &mut fs::File) -> Result<MetadataHeader, io::Error> { +pub fn read_metadata_header(metadata_file: &mut fs::File) -> DBResult<MetadataHeader> { metadata_file.seek(SeekFrom::Start(0))?; let mut buf = [0u8; METADATA_FILE_HEADER_SIZE]; metadata_file.read_exact(&mut buf)?; @@ -576,7 +575,7 @@ pub fn read_metadata_header(metadata_file: &mut fs::File) -> Result<MetadataHead Ok(header) } -pub fn validate_metadata_header(header: &MetadataHeader) -> Result<(), DBError> { +pub fn validate_metadata_header(header: &MetadataHeader) -> DBResult<()> { if header.version != 1 { return Err(DBError::ValidationError( "Unsupported metadata file version".to_owned(), @@ -592,9 +591,7 @@ pub enum IsMetadatafileValidResult { TruncateToSize(u64), } -pub fn is_metadata_file_valid( - metadata_file: &mut fs::File, -) -> Result<IsMetadatafileValidResult, io::Error> { +pub fn is_metadata_file_valid(metadata_file: &mut fs::File) -> DBResult<IsMetadatafileValidResult> { let size = metadata_file.seek(SeekFrom::End(0))? as usize; if size < METADATA_FILE_HEADER_SIZE { @@ -626,7 +623,7 @@ pub fn is_metadata_file_valid( pub fn ensure_active_metadata_is_valid( data_dir: &Path, metadata_file: &mut fs::File, -) -> Result<bool, io::Error> { +) -> DBResult<bool> { let current_len = metadata_file.seek(SeekFrom::End(0))? as usize; match is_metadata_file_valid(metadata_file)? { @@ -683,18 +680,8 @@ pub fn ensure_active_metadata_is_valid( const LOCK_WAIT_MAX_MS: u64 = 1000; -#[derive(Error, Debug)] -pub enum LockRequestError { - #[error("lock request file was removed unexpectedly")] - LockRequestFileRemoved, - #[error("timed out while waiting for a lock, max wait time: {0} ms")] - TimedOut(u64), - #[error("unexpected IO error: {0}")] - IOError(#[from] io::Error), -} - -pub fn is_exclusive_lock_requested(data_dir: &Path) -> Result<bool, LockRequestError> { let lock_request_path = data_dir.join(EXCL_LOCK_REQUEST_FILENAME); +pub fn is_exclusive_lock_requested(data_dir: &Path) -> DBResult<bool> { let lock_request_file = fs::OpenOptions::new() .create(true) .write(true) // When requesting a lock, we need to have either read or write permissions @@ -707,14 +694,16 @@ pub fn is_exclusive_lock_requested(data_dir: &Path) -> Result<bool, LockRequestE if e.kind() == lock_contended_error().kind() { return Ok(true); } - return Err(LockRequestError::IOError(e)); + return Err(DBError::IOError(e)); } Ok(_) => { // Check that the exclusive lock request file is still the same as the one we opened if !is_file_same_as_path(&lock_request_file, &lock_request_path)? { // The lock request file has been removed - return Err(LockRequestError::LockRequestFileRemoved); + return Err(DBError::ConsistencyError( + "Lock request file was removed while checking for exclusive lock".to_owned(), + )); } lock_request_file.unlock()?; @@ -723,7 +712,7 @@ pub fn is_exclusive_lock_requested(data_dir: &Path) -> Result<bool, LockRequestE } } -pub fn request_shared_lock(data_dir: &Path, file: &mut fs::File) -> Result<(), LockRequestError> { +pub fn request_shared_lock(data_dir: &Path, file: &mut fs::File) -> DBResult<()> { let mut timeout = 5; loop { if is_exclusive_lock_requested(data_dir)? { @@ -735,7 +724,9 @@ pub fn request_shared_lock(data_dir: &Path, file: &mut fs::File) -> Result<(), L timeout *= 2; if timeout > LOCK_WAIT_MAX_MS { - return Err(LockRequestError::TimedOut(LOCK_WAIT_MAX_MS)); + return Err(DBError::LockRequestError( + "Acquisition of shared lock timed out after {LOCK_WAIT_MAX_MS}".to_owned(), + )); } } else { file.lock_shared()?; @@ -744,7 +735,7 @@ pub fn request_shared_lock(data_dir: &Path, file: &mut fs::File) -> Result<(), L } } -pub fn request_exclusive_lock(data_dir: &Path, file: &mut fs::File) -> Result<(), io::Error> { +pub fn request_exclusive_lock(data_dir: &Path, file: &mut fs::File) -> DBResult<()> { // Create a lock on the exclusive lock request file to signal to readers that they should wait let lock_request_path = data_dir.join(EXCL_LOCK_REQUEST_FILENAME); let lock_request_file = fs::OpenOptions::new() diff --git a/log_db/src/lib.rs b/log_db/src/lib.rs index 50e5a65..97ea28f 100644 --- a/log_db/src/lib.rs +++ b/log_db/src/lib.rs @@ -75,7 +75,7 @@ impl<R: Recordable> ConfigBuilder<R> { self } - pub fn initialize(&self) -> Result<DB<R>, DBError> { + pub fn initialize(&self) -> DBResult<DB<R>> { let config = Config { fields: R::schema(), primary_key: R::primary_key(), @@ -125,7 +125,7 @@ impl<R: Recordable> DB<R> { ConfigBuilder::new() } - fn initialize(config: Config<R>) -> Result<DB<R>, DBError> { + fn initialize(config: Config<R>) -> DBResult<DB<R>> { info!("Initializing DB..."); // If data_dir does not exist or is empty, create it and any necessary files // After creation, the directory should always be in a complete state @@ -234,7 +234,7 @@ impl<R: Recordable> DB<R> { /// Refresh the in-memory indexes from the log files. /// This needs to only be called if the read consistency is set to `ReadConsistency::Eventual`. - pub fn refresh_indexes(&mut self) -> Result<(), DBError> { + pub fn refresh_indexes(&mut self) -> DBResult<()> { let active_symlink_path = self.data_dir.join(ACTIVE_SYMLINK_FILENAME); let active_target = fs::read_link(active_symlink_path)?; let active_metadata_path = self.data_dir.join(active_target); @@ -256,8 +256,7 @@ impl<R: Recordable> DB<R> { ))); } - request_shared_lock(&self.data_dir, &mut metadata_file) - .map_err(|lre| DBError::LockRequestError(lre))?; + request_shared_lock(&self.data_dir, &mut metadata_file)?; let metadata_header = read_metadata_header(&mut metadata_file)?; validate_metadata_header(&metadata_header)?; @@ -333,7 +332,7 @@ impl<R: Recordable> DB<R> { /// Insert a record into the database. If the primary key value already exists, /// the existing record will be replaced by the supplied one. - pub fn upsert(&mut self, recordable: R) -> Result<(), DBError> { + pub fn upsert(&mut self, recordable: R) -> DBResult<()> { let record = Record::from(&recordable.into_record()); debug!("Upserting record: {:?}", record); @@ -345,7 +344,7 @@ impl<R: Recordable> DB<R> { /// Insert a batch of records into the database. If the primary key value for a record already exists, /// the existing record will be replaced by the supplied one. Records are inserted in the order they are given. - pub fn batch_upsert(&mut self, recordables: Vec<R>) -> Result<(), DBError> { + pub fn batch_upsert(&mut self, recordables: Vec<R>) -> DBResult<()> { let records = recordables .into_iter() .map(|r| Record::from(&r.into_record())) @@ -360,10 +359,7 @@ impl<R: Recordable> DB<R> { self.batch_upsert_records(records.into_iter()) } - fn batch_upsert_records( - &mut self, - records: impl Iterator<Item = Record>, - ) -> Result<(), DBError> { + fn batch_upsert_records(&mut self, records: impl Iterator<Item = Record>) -> DBResult<()> { debug!("Opening file in append mode and acquiring exclusive lock..."); // Acquire an exclusive lock for writing @@ -444,7 +440,7 @@ impl<R: Recordable> DB<R> { /// Get a record by its primary index value. /// E.g. `db.get(Value::Int(10))`. - pub fn get(&mut self, value: &Value) -> Result<Option<R>, DBError> { + pub fn get(&mut self, value: &Value) -> DBResult<Option<R>> { let value_batch = std::iter::once(value); let records = self.batch_find_by_records(&self.config.primary_key.clone(), value_batch)?; assert!(records.len() <= 1); @@ -457,7 +453,7 @@ impl<R: Recordable> DB<R> { /// Get a collection of records based on a field value. /// Indexes will be used if they are applicable. - pub fn find_by(&mut self, field: &R::Field, value: &Value) -> Result<Vec<R>, DBError> { + pub fn find_by(&mut self, field: &R::Field, value: &Value) -> DBResult<Vec<R>> { let value_batch = std::iter::once(value); Ok(self .batch_find_by_records(field, value_batch)? @@ -474,7 +470,7 @@ impl<R: Recordable> DB<R> { &mut self, field: &R::Field, values: &[Value], - ) -> Result<Vec<(usize, R)>, DBError> { + ) -> DBResult<Vec<(usize, R)>> { Ok(self .batch_find_by_records(field, values.iter())? .into_iter() @@ -486,7 +482,7 @@ impl<R: Recordable> DB<R> { &mut self, field: &R::Field, values: impl Iterator<Item = &'a Value>, - ) -> Result<Vec<(usize, Record)>, DBError> { + ) -> DBResult<Vec<(usize, Record)>> { let field_type = self.get_field_type(field).ok_or(DBError::ValidationError( "Field not found in schema".to_owned(), ))?; @@ -504,7 +500,7 @@ impl<R: Recordable> DB<R> { ))) } }) - .collect::<Result<Vec<IndexableValue>, DBError>>()?; + .collect::<DBResult<Vec<IndexableValue>>>()?; // Otherwise, continue with querying secondary indexes. debug!( @@ -546,7 +542,7 @@ impl<R: Recordable> DB<R> { Ok(log_keys) } }) - .collect::<Result<Vec<Vec<&LogKey>>, DBError>>()?; + .collect::<DBResult<Vec<Vec<&LogKey>>>>()?; debug!("Found log keys in memtable: {:?}", log_key_batches); @@ -570,7 +566,7 @@ impl<R: Recordable> DB<R> { fn read_tagged_log_keys<'a>( &self, log_keys: impl Iterator<Item = (usize, &'a LogKey)>, - ) -> Result<Vec<(usize, Record)>, DBError> { + ) -> DBResult<Vec<(usize, Record)>> { let mut records = vec![]; let mut log_keys_map = BTreeMap::new(); @@ -636,7 +632,7 @@ impl<R: Recordable> DB<R> { &mut self, field: &R::Field, range: B, - ) -> Result<Vec<R>, DBError> { + ) -> DBResult<Vec<R>> { Ok(self .range_by_records(field, range)? .into_iter() @@ -648,12 +644,12 @@ impl<R: Recordable> DB<R> { &mut self, field: &R::Field, range: B, - ) -> Result<Vec<Record>, DBError> { + ) -> DBResult<Vec<Record>> { fn range_bound_to_indexable( bound: Bound<&Value>, field_type: &ValueType, - ) -> Result<Bound<IndexableValue>, DBError> { - fn convert(value: &Value, field_type: &ValueType) -> Result<IndexableValue, DBError> { + ) -> DBResult<Bound<IndexableValue>> { + fn convert(value: &Value, field_type: &ValueType) -> DBResult<IndexableValue> { if !type_check(&value, field_type) { return Err(DBError::ValidationError(format!( "Queried value does not match type: {:?}", @@ -706,7 +702,7 @@ impl<R: Recordable> DB<R> { /// Ensures that the `self.metadata_file` and `self.data_file` handles are still pointing to the correct files. /// If the segment has been rotated, the handle will be closed and reopened. /// Returns `false` if the file has been rotated and the handle has been reopened, `true` otherwise. - fn ensure_metadata_file_is_active(&mut self) -> Result<bool, DBError> { + fn ensure_metadata_file_is_active(&mut self) -> DBResult<bool> { let active_target = fs::read_link(&self.data_dir.join(ACTIVE_SYMLINK_FILENAME))?; let active_metadata_path = &self.data_dir.join(active_target); @@ -738,7 +734,7 @@ impl<R: Recordable> DB<R> { /// /// Deletion is done by marking the record as a tombstone. The record will still be present in the log file, /// but will be ignored by reads. Upon compaction, tombstoned records will be removed. - pub fn delete_by(&mut self, field: &R::Field, value: &Value) -> Result<Vec<R>, DBError> { + pub fn delete_by(&mut self, field: &R::Field, value: &Value) -> DBResult<Vec<R>> { let recs = self.delete_by_field(field, value)?; Ok(recs @@ -748,7 +744,7 @@ impl<R: Recordable> DB<R> { } /// Delete record by primary key. - pub fn delete(&mut self, pk: &Value) -> Result<Option<R>, DBError> { + pub fn delete(&mut self, pk: &Value) -> DBResult<Option<R>> { let recs = self.delete_by_field(&self.config.primary_key.clone(), pk)?; assert!(recs.len() <= 1); @@ -758,7 +754,7 @@ impl<R: Recordable> DB<R> { .map(|rec| R::from_record(rec.values))) } - fn delete_by_field(&mut self, field: &R::Field, value: &Value) -> Result<Vec<Record>, DBError> { + fn delete_by_field(&mut self, field: &R::Field, value: &Value) -> DBResult<Vec<Record>> { let value_batch = std::iter::once(value); let recs: Vec<Record> = self .batch_find_by_records(field, value_batch)? @@ -822,7 +818,7 @@ impl<R: Recordable> DB<R> { /// Note that this function is synchronous and may block for a relatively long time. /// You may call this function in a separate thread or process to avoid blocking the main thread. /// However, the database will be exclusively locked, so all writes and reads will be blocked during the tasks. - pub fn do_maintenance_tasks(&mut self) -> Result<(), DBError> { + pub fn do_maintenance_tasks(&mut self) -> DBResult<()> { request_exclusive_lock(&self.data_dir, &mut self.active_metadata_file)?; ensure_active_metadata_is_valid(&self.data_dir, &mut self.active_metadata_file)?; @@ -837,7 +833,7 @@ impl<R: Recordable> DB<R> { Ok(()) } - fn rotate_and_compact(&mut self) -> Result<(), io::Error> { + fn rotate_and_compact(&mut self) -> DBResult<()> { debug!("Active log size exceeds threshold, starting rotation and compaction..."); self.active_data_file.lock_shared()?; diff --git a/log_db/src/record.rs b/log_db/src/record.rs index 4a30902..cbb1faa 100644 --- a/log_db/src/record.rs +++ b/log_db/src/record.rs @@ -49,7 +49,7 @@ impl Record { &self.values[index] } - pub fn validate<Field: Eq>(&self, schema: &Vec<(Field, ValueType)>) -> Result<(), DBError> { + pub fn validate<Field: Eq>(&self, schema: &Vec<(Field, ValueType)>) -> DBResult<()> { // Validate the record length if self.values.len() != schema.len() { return Err(DBError::ValidationError(format!( |
