diff --git a/tiledb/api/examples/quickstart_dense.rs b/tiledb/api/examples/quickstart_dense.rs index 94784f11..9a62ab86 100644 --- a/tiledb/api/examples/quickstart_dense.rs +++ b/tiledb/api/examples/quickstart_dense.rs @@ -47,11 +47,12 @@ fn create_array() -> TileDBResult<()> { .build() }; - let attribute_a = tiledb::array::Attribute::new( + let attribute_a = tiledb::array::AttributeBuilder::new( &tdb, QUICKSTART_ATTRIBUTE_NAME, tiledb::Datatype::Int32, - )?; + )? + .build(); let schema = tiledb::array::SchemaBuilder::new( &tdb, diff --git a/tiledb/api/src/array/attribute.rs b/tiledb/api/src/array/attribute.rs index b360c2b1..3e99132c 100644 --- a/tiledb/api/src/array/attribute.rs +++ b/tiledb/api/src/array/attribute.rs @@ -1,5 +1,6 @@ extern crate tiledb_sys as ffi; +use std::convert::From; use std::ops::Deref; pub use tiledb_sys::Datatype; @@ -10,89 +11,56 @@ use crate::error::Error; use crate::filter_list::FilterList; use crate::Result as TileDBResult; -pub(crate) struct RawAttribute { - ffi: *mut ffi::tiledb_attribute_t, -} - -impl RawAttribute { - pub fn new(ffi: *mut ffi::tiledb_attribute_t) -> Self { - RawAttribute { ffi } - } +pub(crate) enum RawAttribute { + Owned(*mut ffi::tiledb_attribute_t), } impl Deref for RawAttribute { type Target = *mut ffi::tiledb_attribute_t; fn deref(&self) -> &Self::Target { - &self.ffi + let RawAttribute::Owned(ref ffi) = *self; + ffi } } impl Drop for RawAttribute { fn drop(&mut self) { + let RawAttribute::Owned(ref mut ffi) = *self; unsafe { - ffi::tiledb_attribute_free(&mut self.ffi); + ffi::tiledb_attribute_free(ffi); } } } -pub struct Attribute { +pub struct Attribute<'ctx> { + context: &'ctx Context, raw: RawAttribute, } -impl Attribute { +impl<'ctx> Attribute<'ctx> { pub(crate) fn as_mut_ptr(&self) -> *mut ffi::tiledb_attribute_t { *self.raw } - pub fn new( - ctx: &Context, - name: &str, - datatype: Datatype, - ) -> TileDBResult { - let mut c_attr: *mut ffi::tiledb_attribute_t = out_ptr!(); - let c_name = cstring!(name); - let res = unsafe { - ffi::tiledb_attribute_alloc( - ctx.as_mut_ptr(), - c_name.as_c_str().as_ptr(), - datatype as u32, - &mut c_attr, - ) - }; - if res == ffi::TILEDB_OK { - Ok(Attribute { - raw: RawAttribute::new(c_attr), - }) - } else { - Err(ctx.expect_last_error()) - } - } - - pub fn name(&self, ctx: &Context) -> TileDBResult { + pub fn name(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); let mut c_name = std::ptr::null::(); let res = unsafe { - ffi::tiledb_attribute_get_name( - ctx.as_mut_ptr(), - *self.raw, - &mut c_name, - ) + ffi::tiledb_attribute_get_name(c_context, *self.raw, &mut c_name) }; if res == ffi::TILEDB_OK { let c_name = unsafe { std::ffi::CStr::from_ptr(c_name) }; Ok(String::from(c_name.to_string_lossy())) } else { - Err(ctx.expect_last_error()) + Err(self.context.expect_last_error()) } } - pub fn datatype(&self, ctx: &Context) -> TileDBResult { + pub fn datatype(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); let mut c_dtype: std::ffi::c_uint = 0; let res = unsafe { - ffi::tiledb_attribute_get_type( - ctx.as_mut_ptr(), - *self.raw, - &mut c_dtype, - ) + ffi::tiledb_attribute_get_type(c_context, *self.raw, &mut c_dtype) }; if res == ffi::TILEDB_OK { if let Some(dtype) = Datatype::from_u32(c_dtype) { @@ -101,265 +69,306 @@ impl Attribute { Err(Error::from("Invalid Datatype value returned by TileDB")) } } else { - Err(ctx.expect_last_error()) + Err(self.context.expect_last_error()) } } - pub fn set_nullable( - &self, - ctx: &Context, - nullable: bool, - ) -> TileDBResult<()> { - let nullable: u8 = if nullable { 1 } else { 0 }; + pub fn is_nullable(&self) -> bool { + let c_context = self.context.as_mut_ptr(); + let mut c_nullable: std::ffi::c_uchar = 0; + let c_ret = unsafe { + ffi::tiledb_attribute_get_nullable( + c_context, + *self.raw, + &mut c_nullable, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check failure + c_nullable == 1 + } + + pub fn filter_list(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); + let mut flist = FilterList::default(); let res = unsafe { - ffi::tiledb_attribute_set_nullable( - ctx.as_mut_ptr(), + ffi::tiledb_attribute_get_filter_list( + c_context, *self.raw, - nullable, + flist.as_mut_ptr_ptr(), ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(flist) } else { - Err(ctx.expect_last_error()) + Err(self.context.expect_last_error()) } } - pub fn get_nullable(&self, ctx: &Context) -> TileDBResult { - let mut c_nullable: std::ffi::c_uchar = 0; + pub fn is_var_sized(&self) -> TileDBResult { + self.cell_val_num().map(|num| num == u32::MAX) + } + + pub fn cell_val_num(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); + let mut c_num: std::ffi::c_uint = 0; let res = unsafe { - ffi::tiledb_attribute_get_nullable( - ctx.as_mut_ptr(), - *self.raw, - &mut c_nullable, + ffi::tiledb_attribute_get_cell_val_num( + c_context, *self.raw, &mut c_num, ) }; if res == ffi::TILEDB_OK { - Ok(c_nullable == 1) + Ok(c_num as u32) } else { - Err(ctx.expect_last_error()) + Err(self.context.expect_last_error()) } } - pub fn set_filter_list( - &self, - ctx: &Context, - filter_list: &FilterList, - ) -> TileDBResult<()> { + pub fn cell_size(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); + let mut c_size: std::ffi::c_ulonglong = 0; let res = unsafe { - ffi::tiledb_attribute_set_filter_list( - ctx.as_mut_ptr(), + ffi::tiledb_attribute_get_cell_size( + c_context, *self.raw, - filter_list.as_mut_ptr(), + &mut c_size, ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(c_size as u64) } else { - Err(ctx.expect_last_error()) + Err(self.context.expect_last_error()) } } - pub fn get_filter_list(&self, ctx: &Context) -> TileDBResult { - let mut flist = FilterList::default(); + pub fn fill_value(&self) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); + let c_attr = *self.raw; + let mut c_ptr: *const std::ffi::c_void = out_ptr!(); + let mut c_size: u64 = 0; + let res = unsafe { - ffi::tiledb_attribute_get_filter_list( - ctx.as_mut_ptr(), - *self.raw, - flist.as_mut_ptr_ptr(), + ffi::tiledb_attribute_get_fill_value( + c_context, + c_attr, + &mut c_ptr, + &mut c_size, ) }; - if res == ffi::TILEDB_OK { - Ok(flist) - } else { - Err(ctx.expect_last_error()) + + if res != ffi::TILEDB_OK { + return Err(self.context.expect_last_error()); } + + if c_size != std::mem::size_of::() as u64 { + return Err(Error::from("Invalid value size returned by TileDB")); + } + + let c_val: Conv::CAPIType = unsafe { *c_ptr.cast::() }; + + Ok(Conv::to_rust(&c_val)) } - pub fn set_var_sized(&self, ctx: &Context) -> TileDBResult<()> { - self.set_cell_val_num(ctx, u32::MAX) + pub fn fill_value_nullable( + &self, + ) -> TileDBResult<(Conv, bool)> { + let c_context = self.context.as_mut_ptr(); + let c_attr = *self.raw; + let mut c_ptr: *const std::ffi::c_void = out_ptr!(); + let mut c_size: u64 = 0; + let mut c_validity: u8 = 0; + + let res = unsafe { + ffi::tiledb_attribute_get_fill_value_nullable( + c_context, + c_attr, + &mut c_ptr, + &mut c_size, + &mut c_validity, + ) + }; + + if res != ffi::TILEDB_OK { + return Err(self.context.expect_last_error()); + } + + if c_size != std::mem::size_of::() as u64 { + return Err(Error::from("Invalid value size returned by TileDB")); + } + + let c_val: Conv::CAPIType = unsafe { *c_ptr.cast::() }; + + Ok((Conv::to_rust(&c_val), c_validity != 0)) } +} - pub fn is_var_sized(&self, ctx: &Context) -> TileDBResult { - self.get_cell_val_num(ctx).map(|num| num == u32::MAX) +impl<'ctx> From<(&'ctx Context, RawAttribute)> for Attribute<'ctx> { + fn from(value: (&'ctx Context, RawAttribute)) -> Self { + Attribute { + context: value.0, + raw: value.1, + } } +} - pub fn set_cell_val_num( - &self, - ctx: &Context, - num: u32, - ) -> TileDBResult<()> { - let c_num = num as std::ffi::c_uint; +pub struct Builder<'ctx> { + attr: Attribute<'ctx>, +} + +impl<'ctx> Builder<'ctx> { + pub fn new( + context: &'ctx Context, + name: &str, + datatype: Datatype, + ) -> TileDBResult { + let c_context = context.as_mut_ptr(); + let mut c_attr: *mut ffi::tiledb_attribute_t = out_ptr!(); + let c_name = cstring!(name); let res = unsafe { - ffi::tiledb_attribute_set_cell_val_num( - ctx.as_mut_ptr(), - *self.raw, - c_num, + ffi::tiledb_attribute_alloc( + c_context, + c_name.as_c_str().as_ptr(), + datatype as u32, + &mut c_attr, ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(Builder { + attr: Attribute { + context, + raw: RawAttribute::Owned(c_attr), + }, + }) } else { - Err(ctx.expect_last_error()) + Err(context.expect_last_error()) } } - pub fn get_cell_val_num(&self, ctx: &Context) -> TileDBResult { - let mut c_num: std::ffi::c_uint = 0; + pub fn cell_val_num(self, num: u32) -> TileDBResult { + let c_context = self.attr.context.as_mut_ptr(); + let c_num = num as std::ffi::c_uint; let res = unsafe { - ffi::tiledb_attribute_get_cell_val_num( - ctx.as_mut_ptr(), - *self.raw, - &mut c_num, + ffi::tiledb_attribute_set_cell_val_num( + c_context, + *self.attr.raw, + c_num, ) }; if res == ffi::TILEDB_OK { - Ok(c_num as u32) + Ok(self) } else { - Err(ctx.expect_last_error()) + Err(self.attr.context.expect_last_error()) } } - pub fn get_cell_size(&self, ctx: &Context) -> TileDBResult { - let mut c_size: std::ffi::c_ulonglong = 0; + pub fn var_sized(self) -> TileDBResult { + self.cell_val_num(u32::MAX) + } + + pub fn nullability(self, nullable: bool) -> TileDBResult { + let c_context = self.attr.context.as_mut_ptr(); + let c_nullable: u8 = if nullable { 1 } else { 0 }; let res = unsafe { - ffi::tiledb_attribute_get_cell_size( - ctx.as_mut_ptr(), - *self.raw, - &mut c_size, + ffi::tiledb_attribute_set_nullable( + c_context, + *self.attr.raw, + c_nullable, ) }; if res == ffi::TILEDB_OK { - Ok(c_size as u64) + Ok(self) } else { - Err(ctx.expect_last_error()) + Err(self.attr.context.expect_last_error()) } } // This currently does not support setting multi-value cells. - pub fn set_fill_value( - &self, - ctx: &Context, + pub fn fill_value( + self, value: Conv, - ) -> TileDBResult<()> { - let c_val: Conv::CAPIType = value.to_capi(); - - if !self.datatype(ctx)?.is_compatible_type::() { + ) -> TileDBResult { + if !self.attr.datatype()?.is_compatible_type::() { return Err(Error::from(format!( "Attribute type mismatch: expected {}, found {}", - self.datatype(ctx)?, + self.attr.datatype()?, std::any::type_name::() ))); } + let c_context = self.attr.context.as_mut_ptr(); + let c_attr = *self.attr.raw; + let c_val: Conv::CAPIType = value.to_capi(); + let res = unsafe { ffi::tiledb_attribute_set_fill_value( - ctx.as_mut_ptr(), - *self.raw, + c_context, + c_attr, &c_val as *const Conv::CAPIType as *const std::ffi::c_void, std::mem::size_of::() as u64, ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(self) } else { - Err(ctx.expect_last_error()) - } - } - - pub fn get_fill_value( - &self, - ctx: &Context, - ) -> TileDBResult { - let mut c_ptr: *const std::ffi::c_void = out_ptr!(); - let mut c_size: u64 = 0; - - let res = unsafe { - ffi::tiledb_attribute_get_fill_value( - ctx.as_mut_ptr(), - *self.raw, - &mut c_ptr, - &mut c_size, - ) - }; - - if res != ffi::TILEDB_OK { - return Err(ctx.expect_last_error()); - } - - if c_size != std::mem::size_of::() as u64 { - return Err(Error::from("Invalid value size returned by TileDB")); + Err(self.attr.context.expect_last_error()) } - - let c_val: Conv::CAPIType = unsafe { *c_ptr.cast::() }; - - Ok(Conv::to_rust(&c_val)) } // This currently does not support setting multi-value cells. - pub fn set_fill_value_nullable( - &self, - ctx: &Context, + pub fn fill_value_nullability( + self, value: Conv, - validity: u8, - ) -> TileDBResult<()> { - let c_val: Conv::CAPIType = value.to_capi(); - - if !self.datatype(ctx)?.is_compatible_type::() { + nullable: bool, + ) -> TileDBResult { + if !self.attr.datatype()?.is_compatible_type::() { return Err(Error::from(format!( "Attribute type mismatch: expected {}, found {}", - self.datatype(ctx)?, + self.attr.datatype()?, std::any::type_name::() ))); } + let c_context = self.attr.context.as_mut_ptr(); + let c_attr = *self.attr.raw; + let c_val: Conv::CAPIType = value.to_capi(); + let c_nullable: u8 = if nullable { 1 } else { 0 }; + let res = unsafe { ffi::tiledb_attribute_set_fill_value_nullable( - ctx.as_mut_ptr(), - *self.raw, + c_context, + c_attr, &c_val as *const Conv::CAPIType as *const std::ffi::c_void, std::mem::size_of::() as u64, - validity, + c_nullable, ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(self) } else { - Err(ctx.expect_last_error()) + Err(self.attr.context.expect_last_error()) } } - pub fn get_fill_value_nullable( - &self, - ctx: &Context, - ) -> TileDBResult<(Conv, u8)> { - let mut c_ptr: *const std::ffi::c_void = out_ptr!(); - let mut c_size: u64 = 0; - let mut c_validity: u8 = 0; - + pub fn filter_list(self, filter_list: &FilterList) -> TileDBResult { + let c_context = self.attr.context.as_mut_ptr(); let res = unsafe { - ffi::tiledb_attribute_get_fill_value_nullable( - ctx.as_mut_ptr(), - *self.raw, - &mut c_ptr, - &mut c_size, - &mut c_validity, + ffi::tiledb_attribute_set_filter_list( + c_context, + *self.attr.raw, + // TODO: does the C API copy this? Or alias the pointer? Safety is not obvious + filter_list.as_mut_ptr(), ) }; - - if res != ffi::TILEDB_OK { - return Err(ctx.expect_last_error()); - } - - if c_size != std::mem::size_of::() as u64 { - return Err(Error::from("Invalid value size returned by TileDB")); + if res == ffi::TILEDB_OK { + Ok(self) + } else { + Err(self.attr.context.expect_last_error()) } + } - let c_val: Conv::CAPIType = unsafe { *c_ptr.cast::() }; - - Ok((Conv::to_rust(&c_val), c_validity)) + pub fn build(self) -> Attribute<'ctx> { + self.attr } } @@ -372,158 +381,206 @@ mod test { #[test] fn attribute_alloc() { let ctx = Context::new().expect("Error creating context instance."); - Attribute::new(&ctx, "foo", Datatype::UInt64) + Builder::new(&ctx, "foo", Datatype::UInt64) .expect("Error creating attribute instance."); } #[test] fn attribute_get_name_and_type() { let ctx = Context::new().expect("Error creating context instance."); - let attr = Attribute::new(&ctx, "xkcd", Datatype::UInt32) - .expect("Error creating attribute instance."); + let attr = Builder::new(&ctx, "xkcd", Datatype::UInt32) + .expect("Error creating attribute instance.") + .build(); - let name = attr.name(&ctx).expect("Error getting attribute name."); + let name = attr.name().expect("Error getting attribute name."); assert_eq!(&name, "xkcd"); - let dtype = attr - .datatype(&ctx) - .expect("Error getting attribute datatype."); + let dtype = attr.datatype().expect("Error getting attribute datatype."); assert_eq!(dtype, Datatype::UInt32); } #[test] fn attribute_set_nullable() { let ctx = Context::new().expect("Error creating context instance."); - let attr = Attribute::new(&ctx, "foo", Datatype::UInt64) - .expect("Error creating attribute instance."); - let nullable = attr - .get_nullable(&ctx) - .expect("Error getting attribute nullability."); - assert!(!nullable); + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt64) + .expect("Error creating attribute instance.") + .build(); - attr.set_nullable(&ctx, true) - .expect("Error setting attribute nullability."); - - let nullable = attr - .get_nullable(&ctx) - .expect("Error getting attribute nullability."); - assert!(nullable); + let nullable = attr.is_nullable(); + assert!(!nullable); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt64) + .expect("Error creating attribute instance.") + .nullability(true) + .expect("Error setting attribute nullability.") + .build(); + + let nullable = attr.is_nullable(); + assert!(nullable); + } } #[test] fn attribute_get_set_filter_list() { let ctx = Context::new().expect("Error creating context instance."); - let attr = Attribute::new(&ctx, "foo", Datatype::UInt8) - .expect("Error creating attribute instance."); - let flist1 = attr - .get_filter_list(&ctx) - .expect("Error getting attribute filter list."); - let nfilters = flist1 - .get_num_filters(&ctx) - .expect("Error getting number of filters."); - assert_eq!(nfilters, 0); - - let f1 = Filter::new(&ctx, FilterType::NONE) - .expect("Error creating filter 1."); - let f2 = Filter::new(&ctx, FilterType::BIT_WIDTH_REDUCTION) - .expect("Error creating filter 2."); - let f3 = Filter::new(&ctx, FilterType::ZSTD) - .expect("Error creating filter 3."); - let mut flist2 = - FilterList::new(&ctx).expect("Error creating filter list."); - flist2 - .add_filter(&ctx, &f1) - .expect("Error adding filter 1 to list."); - flist2 - .add_filter(&ctx, &f2) - .expect("Error adding filter 2 to list."); - flist2 - .add_filter(&ctx, &f3) - .expect("Error adding filter 3 to list."); - - attr.set_filter_list(&ctx, &flist2) - .expect("Error setting filter list."); - - let flist3 = attr - .get_filter_list(&ctx) - .expect("Error getting filter list."); - let nfilters = flist3 - .get_num_filters(&ctx) - .expect("Error getting number of filters."); - assert_eq!(nfilters, 3); + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt8) + .expect("Error creating attribute instance.") + .build(); + + let flist1 = attr + .filter_list() + .expect("Error getting attribute filter list."); + let nfilters = flist1 + .get_num_filters(&ctx) + .expect("Error getting number of filters."); + assert_eq!(nfilters, 0); + } + + { + let f1 = Filter::new(&ctx, FilterType::NONE) + .expect("Error creating filter 1."); + let f2 = Filter::new(&ctx, FilterType::BIT_WIDTH_REDUCTION) + .expect("Error creating filter 2."); + let f3 = Filter::new(&ctx, FilterType::ZSTD) + .expect("Error creating filter 3."); + let mut flist2 = + FilterList::new(&ctx).expect("Error creating filter list."); + flist2 + .add_filter(&ctx, &f1) + .expect("Error adding filter 1 to list."); + flist2 + .add_filter(&ctx, &f2) + .expect("Error adding filter 2 to list."); + flist2 + .add_filter(&ctx, &f3) + .expect("Error adding filter 3 to list."); + + let attr = Builder::new(&ctx, "foo", Datatype::UInt8) + .expect("Error creating attribute instance.") + .filter_list(&flist2) + .expect("Error setting filter list.") + .build(); + + let flist3 = + attr.filter_list().expect("Error getting filter list."); + let nfilters = flist3 + .get_num_filters(&ctx) + .expect("Error getting number of filters."); + assert_eq!(nfilters, 3); + } } #[test] fn attribute_cell_val_size() { let ctx = Context::new().expect("Error creating context instance."); - let attr = Attribute::new(&ctx, "foo", Datatype::UInt16) - .expect("Error creating attribute instance."); - - let num = attr - .get_cell_val_num(&ctx) - .expect("Error getting cell val num."); - assert_eq!(num, 1); - let size = attr - .get_cell_size(&ctx) - .expect("Error getting attribute cell size."); - assert_eq!(size, 2); - - attr.set_cell_val_num(&ctx, 3) - .expect("Error setting cell val num."); - let num = attr - .get_cell_val_num(&ctx) - .expect("Error getting cell val num."); - assert_eq!(num, 3); - let size = attr - .get_cell_size(&ctx) - .expect("Error getting attribute cell size."); - assert_eq!(size, 6); - - attr.set_cell_val_num(&ctx, u32::MAX) - .expect("Error setting cell val size."); - let is_var = attr - .is_var_sized(&ctx) - .expect("Error getting attribute var sized-ness."); - assert!(is_var); - - attr.set_cell_val_num(&ctx, 42) - .expect("Error setting cell val num."); - let num = attr - .get_cell_val_num(&ctx) - .expect("Error getting cell val num."); - assert_eq!(num, 42); - let size = attr - .get_cell_size(&ctx) - .expect("Error getting cell val size."); - assert_eq!(size, 84); - - attr.set_var_sized(&ctx) - .expect("Error setting attribute to var sized."); - let num = attr - .get_cell_val_num(&ctx) - .expect("Error getting cell val num."); - assert_eq!(num, u32::MAX); - let size = attr - .get_cell_size(&ctx) - .expect("Error getting cell val size."); - assert_eq!(size, u64::MAX); + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt16) + .expect("Error creating attribute instance.") + .build(); + + let num = attr.cell_val_num().expect("Error getting cell val num."); + assert_eq!(num, 1); + let size = attr + .cell_size() + .expect("Error getting attribute cell size."); + assert_eq!(size, 2); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt16) + .expect("Error creating attribute instance.") + .cell_val_num(3) + .expect("Error setting cell val num.") + .build(); + let num = attr.cell_val_num().expect("Error getting cell val num."); + assert_eq!(num, 3); + let size = attr + .cell_size() + .expect("Error getting attribute cell size."); + assert_eq!(size, 6); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt16) + .expect("Error creating attribute instance.") + .cell_val_num(u32::MAX) + .expect("Error setting cell val size.") + .build(); + let is_var = attr + .is_var_sized() + .expect("Error getting attribute var sized-ness."); + assert!(is_var); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt16) + .expect("Error creating attribute instance.") + .cell_val_num(42) + .expect("Error setting cell val num.") + .build(); + let num = attr.cell_val_num().expect("Error getting cell val num."); + assert_eq!(num, 42); + let size = attr.cell_size().expect("Error getting cell val size."); + assert_eq!(size, 84); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt16) + .expect("Error creating attribute instance.") + .var_sized() + .expect("Error setting var sized.") + .build(); + let num = attr.cell_val_num().expect("Error getting cell val num."); + assert_eq!(num, u32::MAX); + let size = attr.cell_size().expect("Error getting cell val size."); + assert_eq!(size, u64::MAX); + } } #[test] fn attribute_test_set_fill_value_error() -> TileDBResult<()> { let ctx = Context::new()?; - let attr = Attribute::new(&ctx, "foo", Datatype::UInt32)?; - attr.set_nullable(&ctx, true)?; - assert!(attr.set_fill_value(&ctx, 5_i32).is_err()); - assert!(attr.set_fill_value(&ctx, 5_u32).is_err()); - assert!(attr.set_fill_value(&ctx, 1.0_f64).is_err()); + // nullable + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(true)? + .fill_value(5_i32); + assert!(attr.is_err()); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(true)? + .fill_value(5_u32); + assert!(attr.is_err()); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(true)? + .fill_value(1.0_f64); + assert!(attr.is_err()); + } - attr.set_nullable(&ctx, false)?; - assert!(attr.set_fill_value_nullable(&ctx, 5_i32, 1).is_err()); - assert!(attr.set_fill_value_nullable(&ctx, 1.0_f64, 0).is_err()); + // non-nullable + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(false)? + .fill_value_nullability(5_i32, true); + assert!(attr.is_err()); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(false)? + .fill_value_nullability(5_i32, true); + assert!(attr.is_err()); + } + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(false)? + .fill_value_nullability(1.0_f64, false); + assert!(attr.is_err()); + } Ok(()) } @@ -531,15 +588,24 @@ mod test { #[test] fn attribute_test_fill_value() -> TileDBResult<()> { let ctx = Context::new()?; - let attr = Attribute::new(&ctx, "foo", Datatype::UInt32)?; - let val: u32 = attr.get_fill_value(&ctx)?; - assert_eq!(val, u32::MAX); + // default + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)?.build(); + + let val: u32 = attr.fill_value()?; + assert_eq!(val, u32::MAX); + } - assert!(attr.set_fill_value(&ctx, 5_u32).is_ok()); + // override + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .fill_value(5_u32)? + .build(); - let val: u32 = attr.get_fill_value(&ctx)?; - assert_eq!(val, 5); + let val: u32 = attr.fill_value()?; + assert_eq!(val, 5); + } Ok(()) } @@ -547,18 +613,29 @@ mod test { #[test] fn attribute_test_fill_value_nullable() -> TileDBResult<()> { let ctx = Context::new()?; - let attr = Attribute::new(&ctx, "foo", Datatype::UInt32)?; - attr.set_nullable(&ctx, true)?; - let (val, validity): (u32, u8) = attr.get_fill_value_nullable(&ctx)?; - assert_eq!(val, u32::MAX); - assert_eq!(validity, 0); + // default fill value + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(true)? + .build(); - assert!(attr.set_fill_value_nullable(&ctx, 5_u32, 12).is_ok()); + let (val, validity): (u32, bool) = attr.fill_value_nullable()?; + assert_eq!(val, u32::MAX); + assert!(!validity); + } + + // overridden + { + let attr = Builder::new(&ctx, "foo", Datatype::UInt32)? + .nullability(true)? + .fill_value_nullability(5_u32, true)? + .build(); - let (val, validity): (u32, u8) = attr.get_fill_value_nullable(&ctx)?; - assert_eq!(val, 5); - assert_eq!(validity, 12); + let (val, validity): (u32, bool) = attr.fill_value_nullable()?; + assert_eq!(val, 5); + assert!(validity); + } Ok(()) } diff --git a/tiledb/api/src/array/mod.rs b/tiledb/api/src/array/mod.rs index 27baa1df..37fa488d 100644 --- a/tiledb/api/src/array/mod.rs +++ b/tiledb/api/src/array/mod.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::ops::Deref; use crate::context::Context; @@ -8,7 +9,7 @@ mod dimension; mod domain; mod schema; -pub use attribute::Attribute; +pub use attribute::{Attribute, Builder as AttributeBuilder}; pub use dimension::{Builder as DimensionBuilder, Dimension}; pub use domain::{Builder as DomainBuilder, Domain}; pub use schema::{ArrayType, Builder as SchemaBuilder, Schema}; @@ -31,6 +32,7 @@ impl Mode { } } +#[derive(Debug, PartialEq)] pub enum Layout { Unordered, RowMajor, @@ -49,6 +51,19 @@ impl Layout { } } +impl TryFrom for Layout { + type Error = crate::error::Error; + fn try_from(value: ffi::tiledb_layout_t) -> TileDBResult { + match value { + ffi::tiledb_layout_t_TILEDB_UNORDERED => Ok(Layout::Unordered), + ffi::tiledb_layout_t_TILEDB_ROW_MAJOR => Ok(Layout::RowMajor), + ffi::tiledb_layout_t_TILEDB_COL_MAJOR => Ok(Layout::ColumnMajor), + ffi::tiledb_layout_t_TILEDB_HILBERT => Ok(Layout::Hilbert), + _ => Err(Self::Error::from(format!("Invalid layout: {}", value))), + } + } +} + pub(crate) struct RawArray { ffi: *mut ffi::tiledb_array_t, } @@ -194,7 +209,9 @@ pub mod tests { let s: Schema = SchemaBuilder::new(context, ArrayType::Sparse, d) .unwrap() .add_attribute( - Attribute::new(context, "a", Datatype::UInt64).unwrap(), + AttributeBuilder::new(context, "a", Datatype::UInt64) + .unwrap() + .build(), ) .unwrap() .into(); diff --git a/tiledb/api/src/array/schema.rs b/tiledb/api/src/array/schema.rs index 31a222cd..7075f18a 100644 --- a/tiledb/api/src/array/schema.rs +++ b/tiledb/api/src/array/schema.rs @@ -1,10 +1,13 @@ +use std::convert::TryFrom; use std::ops::Deref; +use crate::array::attribute::RawAttribute; use crate::array::domain::RawDomain; -use crate::array::{Attribute, Domain}; +use crate::array::{Attribute, Domain, Layout}; use crate::context::Context; use crate::Result as TileDBResult; +#[derive(Debug, PartialEq)] pub enum ArrayType { Dense, Sparse, @@ -19,6 +22,19 @@ impl ArrayType { } } +impl TryFrom for ArrayType { + type Error = crate::error::Error; + fn try_from(value: ffi::tiledb_array_type_t) -> TileDBResult { + match value { + ffi::tiledb_array_type_t_TILEDB_DENSE => Ok(ArrayType::Dense), + ffi::tiledb_array_type_t_TILEDB_SPARSE => Ok(ArrayType::Sparse), + _ => { + Err(Self::Error::from(format!("Invalid array type: {}", value))) + } + } + } +} + /// Wrapper for the CAPI handle. /// Ensures that the CAPI structure is freed. pub(crate) enum RawSchema { @@ -111,6 +127,66 @@ impl<'ctx> Schema<'ctx> { } } + pub fn array_type(&self) -> ArrayType { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let mut c_atype: ffi::tiledb_array_type_t = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_array_type( + c_context, + c_schema, + &mut c_atype, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check error + ArrayType::try_from(c_atype).expect("Invalid response from C API") + } + + pub fn capacity(&self) -> u64 { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let mut c_capacity: u64 = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_capacity( + c_context, + c_schema, + &mut c_capacity, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check error + c_capacity + } + + pub fn cell_order(&self) -> Layout { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let mut c_cell_order: ffi::tiledb_layout_t = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_cell_order( + c_context, + c_schema, + &mut c_cell_order, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check error + Layout::try_from(c_cell_order).expect("Invalid response from C API") + } + + pub fn tile_order(&self) -> Layout { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let mut c_tile_order: ffi::tiledb_layout_t = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_tile_order( + c_context, + c_schema, + &mut c_tile_order, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check error + Layout::try_from(c_tile_order).expect("Invalid response from C API") + } + pub fn allows_duplicates(&self) -> bool { let mut c_ret: std::os::raw::c_int = out_ptr!(); if unsafe { @@ -126,6 +202,41 @@ impl<'ctx> Schema<'ctx> { unreachable!("Rust API design should prevent sanity check failure") } } + + pub fn nattributes(&self) -> usize { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let mut c_nattrs: u32 = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_attribute_num( + c_context, + c_schema, + &mut c_nattrs, + ) + }; + assert_eq!(ffi::TILEDB_OK, c_ret); // Rust API should prevent sanity check error + c_nattrs as usize + } + + pub fn attribute(&self, index: usize) -> TileDBResult { + let c_context = self.context.as_mut_ptr(); + let c_schema = *self.raw; + let c_index = index as u32; + let mut c_attr: *mut ffi::tiledb_attribute_t = out_ptr!(); + let c_ret = unsafe { + ffi::tiledb_array_schema_get_attribute_from_index( + c_context, + c_schema, + c_index, + &mut c_attr, + ) + }; + if c_ret == ffi::TILEDB_OK { + Ok(Attribute::from((self.context, RawAttribute::Owned(c_attr)))) + } else { + Err(self.context.expect_last_error()) + } + } } pub struct Builder<'ctx> { @@ -169,6 +280,51 @@ impl<'ctx> Builder<'ctx> { }) } + pub fn capacity(self, capacity: u64) -> TileDBResult { + let c_context = self.schema.context.as_mut_ptr(); + let c_schema = *self.schema.raw; + let c_ret = unsafe { + ffi::tiledb_array_schema_set_capacity(c_context, c_schema, capacity) + }; + if c_ret == ffi::TILEDB_OK { + Ok(self) + } else { + Err(self.schema.context.expect_last_error()) + } + } + + pub fn cell_order(self, order: Layout) -> TileDBResult { + let c_context = self.schema.context.as_mut_ptr(); + let c_schema = *self.schema.raw; + let c_order = order.capi_enum(); + let c_ret = unsafe { + ffi::tiledb_array_schema_set_cell_order( + c_context, c_schema, c_order, + ) + }; + if c_ret == ffi::TILEDB_OK { + Ok(self) + } else { + Err(self.schema.context.expect_last_error()) + } + } + + pub fn tile_order(self, order: Layout) -> TileDBResult { + let c_context = self.schema.context.as_mut_ptr(); + let c_schema = *self.schema.raw; + let c_order = order.capi_enum(); + let c_ret = unsafe { + ffi::tiledb_array_schema_set_tile_order( + c_context, c_schema, c_order, + ) + }; + if c_ret == ffi::TILEDB_OK { + Ok(self) + } else { + Err(self.schema.context.expect_last_error()) + } + } + pub fn allow_duplicates(self, allow: bool) -> TileDBResult { let c_allow = if allow { 1 } else { 0 }; if unsafe { @@ -218,7 +374,7 @@ mod tests { use crate::array::schema::*; use crate::array::tests::*; - use crate::array::{DimensionBuilder, DomainBuilder}; + use crate::array::{AttributeBuilder, DimensionBuilder, DomainBuilder}; use crate::context::Context; use crate::Datatype; @@ -253,6 +409,49 @@ mod tests { assert_eq!(0, s.version()); } + #[test] + fn test_array_type() -> TileDBResult<()> { + let c: Context = Context::new()?; + + { + let s: Schema = + Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .build(); + let t = s.array_type(); + assert_eq!(ArrayType::Dense, t); + } + + { + let s: Schema = + Builder::new(&c, ArrayType::Sparse, unused_domain(&c)) + .unwrap() + .build(); + let t = s.array_type(); + assert_eq!(ArrayType::Sparse, t); + } + + Ok(()) + } + + #[test] + fn test_capacity() -> TileDBResult<()> { + let c: Context = Context::new()?; + + { + let cap_in = 100; + let s: Schema = + Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .capacity(cap_in) + .unwrap() + .build(); + let cap_out = s.capacity(); + assert_eq!(cap_in, cap_out); + } + Ok(()) + } + #[test] fn test_allow_duplicates() { let c: Context = Context::new().unwrap(); @@ -334,4 +533,122 @@ mod tests { Ok(()) } + + #[test] + fn test_layout() -> TileDBResult<()> { + let c: Context = Context::new()?; + + { + let s: Schema = + Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .tile_order(Layout::RowMajor) + .unwrap() + .cell_order(Layout::RowMajor) + .unwrap() + .build(); + let tile = s.tile_order(); + let cell = s.cell_order(); + assert_eq!(Layout::RowMajor, tile); + assert_eq!(Layout::RowMajor, cell); + } + { + let s: Schema = + Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .tile_order(Layout::ColumnMajor) + .unwrap() + .cell_order(Layout::ColumnMajor) + .unwrap() + .build(); + let tile = s.tile_order(); + let cell = s.cell_order(); + assert_eq!(Layout::ColumnMajor, tile); + assert_eq!(Layout::ColumnMajor, cell); + } + { + let r = Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .tile_order(Layout::Hilbert); + assert!(r.is_err()); + } + { + let r = Builder::new(&c, ArrayType::Sparse, unused_domain(&c)) + .unwrap() + .tile_order(Layout::Hilbert); + assert!(r.is_err()); + } + { + let r = Builder::new(&c, ArrayType::Dense, unused_domain(&c)) + .unwrap() + .cell_order(Layout::Hilbert); + assert!(r.is_err()); + } + { + let s: Schema = + Builder::new(&c, ArrayType::Sparse, unused_domain(&c)) + .unwrap() + .cell_order(Layout::Hilbert) + .unwrap() + .build(); + let cell = s.cell_order(); + assert_eq!(Layout::Hilbert, cell); + } + + Ok(()) + } + + #[test] + fn test_attributes() -> TileDBResult<()> { + let c: Context = Context::new()?; + + { + let s: Schema = + Builder::new(&c, ArrayType::Dense, unused_domain(&c))?.build(); + assert_eq!(0, s.nattributes()); + } + { + let s: Schema = { + let a1 = + AttributeBuilder::new(&c, "a1", Datatype::Int32)?.build(); + Builder::new(&c, ArrayType::Dense, unused_domain(&c))? + .add_attribute(a1)? + .build() + }; + assert_eq!(1, s.nattributes()); + + let a1 = s.attribute(0)?; + assert_eq!(Datatype::Int32, a1.datatype()?); + assert_eq!("a1", a1.name()?); + + let a2 = s.attribute(1); + assert!(a2.is_err()); + } + { + let s: Schema = { + let a1 = + AttributeBuilder::new(&c, "a1", Datatype::Int32)?.build(); + let a2 = + AttributeBuilder::new(&c, "a2", Datatype::Float64)?.build(); + Builder::new(&c, ArrayType::Dense, unused_domain(&c))? + .add_attribute(a1)? + .add_attribute(a2)? + .build() + }; + assert_eq!(2, s.nattributes()); + + let a1 = s.attribute(0)?; + assert_eq!(Datatype::Int32, a1.datatype()?); + assert_eq!("a1", a1.name()?); + + let a2 = s.attribute(1)?; + assert_eq!(Datatype::Float64, a2.datatype()?); + assert_eq!("a2", a2.name()?); + + let a3 = s.attribute(2); + assert!(a3.is_err()); + } + + Ok(()) + } }