diff --git a/betree/include/betree.h b/betree/include/betree.h index d7131aec..b952058b 100644 --- a/betree/include/betree.h +++ b/betree/include/betree.h @@ -1,7 +1,7 @@ #ifndef betree_h #define betree_h -/* Generated with cbindgen:0.24.3 */ +/* Generated with cbindgen:0.29.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -68,6 +68,11 @@ typedef struct err_t err_t; */ typedef struct name_iter_t name_iter_t; +/** + * Opaque struct for an iterator over objects. + */ +typedef struct obj_iter_t obj_iter_t; + /** * The object store wrapper type */ @@ -144,6 +149,20 @@ typedef struct byte_slice_t { const struct byte_slice_rc_t *arc; } byte_slice_t; +/** + * Holds status information for an object. + */ +typedef struct betree_object_info_t { + /** + * Size of the object in bytes. + */ + uint64_t size; + /** + * Last modification time in microseconds since the UNIX epoch. + */ + uint64_t mtime_us; +} betree_object_info_t; + #define STORAGE_PREF_FASTEST (storage_pref_t){ ._0 = StoragePreference_FASTEST } #define STORAGE_PREF_NONE (storage_pref_t){ ._0 = StoragePreference_NONE } @@ -375,6 +394,16 @@ void betree_free_err(struct err_t *err); */ void betree_free_name_iter(struct name_iter_t *name_iter); +/** + * Free the object iterator. + */ +void betree_free_obj_iter(struct obj_iter_t *iter); + +/** + * Frees the memory allocated for betree_object_info_t. + */ +void betree_free_object_info(struct betree_object_info_t *info); + /** * Free a range iterator. */ @@ -437,6 +466,31 @@ struct obj_t *betree_object_create(struct obj_store_t *os, */ int betree_object_delete(struct obj_t *obj, struct err_t **err); +/** + * Retrieve status information about an object. + * + * On success, returns a pointer to a `betree_object_info_t` struct, which + * must be freed with `betree_free_object_info`. + * On error, returns null. If `err` is not null, it will be populated. + */ +struct betree_object_info_t *betree_object_info(struct obj_t *obj, struct err_t **err); + +/** + * Create an iterator over all objects in the object store. + * On success, returns a pointer to the iterator which must be freed with `betree_free_obj_iter`. + */ +struct obj_iter_t *betree_object_iter_all(struct obj_store_t *os, struct err_t **err); + +/** + * Advance the iterator and get the next object's key. + * On success (an item was found), returns 0 and populates `key`. `key` must be freed with `betree_free_byte_slice`. + * When the iterator is exhausted, returns -1. + * On error, returns -1 and sets `err`. + */ +int betree_object_iter_next(struct obj_iter_t *iter, + struct byte_slice_t *key, + struct err_t **err); + /** * Open an existing object. */ @@ -467,6 +521,15 @@ int betree_object_read_at(struct obj_t *obj, unsigned long *n_read, struct err_t **err); +/** + * Create an iterator over objects with a given prefix in the object store. + * On success, returns a pointer to the iterator which must be freed with `betree_free_obj_iter`. + */ +struct obj_iter_t *betree_object_store_list_prefix(struct obj_store_t *os, + const char *prefix, + unsigned int prefix_len, + struct err_t **err); + /** * Try to write `buf_len` bytes from `buf` into `obj`, starting at `offset` bytes into the objects * data. @@ -575,4 +638,4 @@ struct range_iter_t *betree_snapshot_range(const struct ss_t *ss, */ int betree_sync_db(struct db_t *db, struct err_t **err); -#endif /* betree_h */ +#endif /* betree_h */ diff --git a/betree/src/c_interface.rs b/betree/src/c_interface.rs index b4717b08..2c6393dd 100644 --- a/betree/src/c_interface.rs +++ b/betree/src/c_interface.rs @@ -4,6 +4,7 @@ use std::{ env::SplitPaths, ffi::{CStr, OsStr}, io::{stderr, BufReader, Write}, + ops::RangeFull, os::{ raw::{c_char, c_int, c_uint, c_ulong}, unix::prelude::OsStrExt, @@ -25,6 +26,9 @@ use crate::{ DatabaseConfiguration, StoragePreference, }; +use crate::object::ObjectInfo; +use std::time::UNIX_EPOCH; + /// The type for a database configuration pub struct cfg_t(DatabaseConfiguration); /// The general error type @@ -164,6 +168,16 @@ impl HandleResult for Box>> { + type Result = *mut obj_iter_t; + fn success(self) -> *mut obj_iter_t { + b(obj_iter_t(self)) + } + fn fail() -> *mut obj_iter_t { + null_mut() + } +} + impl HandleResult for ObjectStore { type Result = *mut obj_store_t; fn success(self) -> *mut obj_store_t { @@ -505,6 +519,7 @@ pub unsafe extern "C" fn betree_iter_datasets( err: *mut *mut err_t, ) -> *mut name_iter_t { let db = &mut (*db).0; + // Result>> db.iter_datasets() .map(|it| Box::new(it) as Box>>) .handle_result(err) @@ -961,23 +976,142 @@ pub unsafe extern "C" fn betree_object_write_at( .handle_result(err) } -/* -/// Return the objects size in bytes. +/// Holds status information for an object. +#[repr(C)] +pub struct betree_object_info_t { + /// Size of the object in bytes. + pub size: u64, + /// Last modification time in microseconds since the UNIX epoch. + pub mtime_us: u64, +} + +/// Frees the memory allocated for betree_object_info_t. #[no_mangle] -pub unsafe extern "C" fn betree_object_status(obj: *const obj_t, err: *mut *mut err_t) -> c_ulong { - let obj = &(*obj).0; - let info = obj.info(); - obj. - obj.size() +pub unsafe extern "C" fn betree_free_object_info(info: *mut betree_object_info_t) { + let _ = Box::from_raw(info); } -/// Returns the last modification timestamp in microseconds since the Unix epoch. +/// Retrieve status information about an object. +/// +/// On success, returns a pointer to a `betree_object_info_t` struct, which +/// must be freed with `betree_free_object_info`. +/// On error, returns null. If `err` is not null, it will be populated. #[no_mangle] -pub unsafe extern "C" fn betree_object_mtime_us(obj: *const obj_t) -> c_ulong { +pub unsafe extern "C" fn betree_object_info<'os>( + obj: *mut obj_t<'os>, + err: *mut *mut err_t, +) -> *mut betree_object_info_t { let obj = &(*obj).0; - obj.modification_time() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_micros() as u64) - .unwrap_or(0) + match obj.info() { + Ok(Some(info)) => { + let mtime_us = info + .mtime + .duration_since(UNIX_EPOCH) + .map(|d| d.as_micros() as u64) + .unwrap_or(0); + + let result_info = betree_object_info_t { + size: info.size, + mtime_us, + }; + Box::into_raw(Box::new(result_info)) + } + Ok(None) => { + // Object does not exist or has no metadata + handle_err(Error::DoesNotExist, err); + null_mut() + } + Err(e) => { + handle_err(e, err); + null_mut() + } + } +} + +/// Opaque struct for an iterator over objects. +pub struct obj_iter_t(Box>>); + +/// Create an iterator over all objects in the object store. +/// On success, returns a pointer to the iterator which must be freed with `betree_free_obj_iter`. +#[no_mangle] +pub unsafe extern "C" fn betree_object_iter_all( + os: *mut obj_store_t, + err: *mut *mut err_t, +) -> *mut obj_iter_t { + let os = &mut (*os).0; + os.list_objects::<_, &[u8]>(..) + .map(|iter| { + let new_iter: Box>> = Box::new( + iter.map(|(handle, info)| Ok((CowBytes::from(handle.object.key()), info))), + ); + new_iter + }) + .handle_result(err) +} + +/// Create an iterator over objects with a given prefix in the object store. +/// On success, returns a pointer to the iterator which must be freed with `betree_free_obj_iter`. +#[no_mangle] +pub unsafe extern "C" fn betree_object_store_list_prefix( + os: *mut obj_store_t, + prefix: *const c_char, + prefix_len: c_uint, + err: *mut *mut err_t, +) -> *mut obj_iter_t { + let os = &mut (*os).0; + let prefix_slice = from_raw_parts(prefix as *const u8, prefix_len as usize); + + let mut end_key = prefix_slice.to_vec(); + // A simple way to create an end bound for a prefix search is to find the + // "next" key in byte-order. For this we find the last byte which is not + // 0xFF, increment it, and truncate the rest. + if let Some(pos) = end_key.iter().rposition(|&b| b != 0xFF) { + end_key[pos] += 1; + end_key.truncate(pos + 1); + } else { + // The prefix is all 0xFF, there's no byte-larger key with this prefix + // so we return an empty iterator. + return b(obj_iter_t(Box::new(std::iter::empty()))); + } + + os.list_objects(prefix_slice..&*end_key) + .map(|iter| { + let new_iter: Box>> = Box::new( + iter.map(|(handle, info)| Ok((CowBytes::from(handle.object.key()), info))), + ); + new_iter + }) + .handle_result(err) +} + +/// Advance the iterator and get the next object's key. +/// On success (an item was found), returns 0 and populates `key`. `key` must be freed with `betree_free_byte_slice`. +/// When the iterator is exhausted, returns -1. +/// On error, returns -1 and sets `err`. +#[no_mangle] +pub unsafe extern "C" fn betree_object_iter_next( + iter: *mut obj_iter_t, + key: *mut byte_slice_t, + err: *mut *mut err_t, +) -> c_int { + let iter = &mut (*iter).0; + match iter.next() { + None => -1, // End of iterator + Some(Ok((k, _info))) => { + write(key, k.into()); + 0 + } + Some(Err(e)) => { + handle_err(e, err); + -1 + } + } +} + +/// Free the object iterator. +#[no_mangle] +pub unsafe extern "C" fn betree_free_obj_iter(iter: *mut obj_iter_t) { + if !iter.is_null() { + let _ = Box::from_raw(iter); + } } -*/