diff --git a/tiledb/api/Cargo.toml b/tiledb/api/Cargo.toml index c739f459..2b2fa459 100644 --- a/tiledb/api/Cargo.toml +++ b/tiledb/api/Cargo.toml @@ -8,7 +8,7 @@ name = "tiledb" path = "src/lib.rs" [dependencies] -serde = "1.0.136" +serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.114" tiledb-sys = { workspace = true } diff --git a/tiledb/api/src/array/attribute.rs b/tiledb/api/src/array/attribute.rs index 9ece833c..6ba9dc42 100644 --- a/tiledb/api/src/array/attribute.rs +++ b/tiledb/api/src/array/attribute.rs @@ -5,14 +5,13 @@ use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::ops::Deref; use serde_json::json; -pub use tiledb_sys::Datatype; use crate::context::Context; use crate::convert::{BitsEq, CAPIConverter}; use crate::error::Error; use crate::filter_list::{FilterList, RawFilterList}; use crate::fn_typed; -use crate::Result as TileDBResult; +use crate::{Datatype, Result as TileDBResult}; pub(crate) enum RawAttribute { Owned(*mut ffi::tiledb_attribute_t), @@ -70,11 +69,7 @@ impl<'ctx> Attribute<'ctx> { 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) { - Ok(dtype) - } else { - Err(Error::from("Invalid Datatype value returned by TileDB")) - } + Datatype::try_from(c_dtype) } else { Err(self.context.expect_last_error()) } @@ -238,13 +233,10 @@ impl<'ctx> Debug for Attribute<'ctx> { } else { None }, - /* - TODO "filters": match self.filter_list() { Ok(fl) => format!("{:?}", fl), Err(e) => format!("", e) }, - */ "raw": format!("{:p}", *self.raw) }); write!(f, "{}", json) @@ -274,15 +266,13 @@ impl<'c1, 'c2> PartialEq> for Attribute<'c1> { return false; } - /* let filter_match = match (self.filter_list(), other.filter_list()) { - (Ok(mine), Ok(theirs)) => unimplemented!(), + (Ok(mine), Ok(theirs)) => mine == theirs, _ => false, }; if !filter_match { return false; } - */ let cell_val_match = match (self.cell_val_num(), other.cell_val_num()) { (Ok(mine), Ok(theirs)) => mine == theirs, @@ -545,12 +535,17 @@ mod test { { let flist2 = FilterListBuilder::new(&ctx) .expect("Error creating filter list builder.") - .add_filter(NoopFilterBuilder::new(&ctx)?.build())? - .add_filter(BitWidthReductionFilterBuilder::new(&ctx)?.build())? - .add_filter( - CompressionFilterBuilder::new(&ctx, CompressionType::Zstd)? - .build(), - )? + .add_filter(Filter::create(&ctx, FilterData::None)?)? + .add_filter(Filter::create( + &ctx, + FilterData::BitWidthReduction { max_window: None }, + )?)? + .add_filter(Filter::create( + &ctx, + FilterData::Compression(CompressionData::new( + CompressionType::Zstd, + )), + )?)? .build(); let attr = Builder::new(&ctx, "foo", Datatype::UInt8) diff --git a/tiledb/api/src/array/dimension.rs b/tiledb/api/src/array/dimension.rs index 5e9e8654..88fa0a2b 100644 --- a/tiledb/api/src/array/dimension.rs +++ b/tiledb/api/src/array/dimension.rs @@ -59,7 +59,7 @@ impl<'ctx> Dimension<'ctx> { assert_eq!(ffi::TILEDB_OK, c_ret); - Datatype::from_capi_enum(c_datatype) + Datatype::try_from(c_datatype).expect("Invalid dimension type") } pub fn domain(&self) -> TileDBResult<[Conv; 2]> { @@ -303,13 +303,12 @@ mod tests { let domain: [i32; 2] = [1, 4]; let extent: i32 = 4; let fl = FilterListBuilder::new(&context)? - .add_filter( - CompressionFilterBuilder::new( - &context, + .add_filter(Filter::create( + &context, + FilterData::Compression(CompressionData::new( CompressionType::Lz4, - )? - .build(), - )? + )), + )?)? .build(); let dimension: Dimension = Builder::new::( &context, @@ -327,7 +326,13 @@ mod tests { assert_eq!(1, fl.get_num_filters().unwrap()); let outlz4 = fl.get_filter(0).unwrap(); - assert_eq!(ffi::FilterType::Lz4, outlz4.get_type().unwrap()); + match outlz4.filter_data().expect("Error reading filter data") { + FilterData::Compression(CompressionData { + kind: CompressionType::Lz4, + .. + }) => (), + _ => unreachable!(), + } } Ok(()) diff --git a/tiledb/api/src/datatype.rs b/tiledb/api/src/datatype.rs index b0daef85..3fdbec92 100644 --- a/tiledb/api/src/datatype.rs +++ b/tiledb/api/src/datatype.rs @@ -1,4 +1,485 @@ -#[macro_export] +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; + +use serde::{Deserialize, Serialize}; + +use crate::Result as TileDBResult; + +#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] +#[repr(u64)] +pub enum Datatype { + #[doc = " 32-bit signed integer"] + Int32, + #[doc = " 64-bit signed integer"] + Int64, + #[doc = " 32-bit floating point value"] + Float32, + #[doc = " 64-bit floating point value"] + Float64, + #[doc = " Character"] + Char, + #[doc = " 8-bit signed integer"] + Int8, + #[doc = " 8-bit unsigned integer"] + UInt8, + #[doc = " 16-bit signed integer"] + Int16, + #[doc = " 16-bit unsigned integer"] + UInt16, + #[doc = " 32-bit unsigned integer"] + UInt32, + #[doc = " 64-bit unsigned integer"] + UInt64, + #[doc = " ASCII string"] + StringAscii, + #[doc = " UTF-8 string"] + StringUtf8, + #[doc = " UTF-16 string"] + StringUtf16, + #[doc = " UTF-32 string"] + StringUtf32, + #[doc = " UCS2 string"] + StringUcs2, + #[doc = " UCS4 string"] + StringUcs4, + #[doc = " This can be any datatype. Must store (type tag, value) pairs."] + Any, + #[doc = " DateTime with year resolution"] + DateTimeYear, + #[doc = " DateTime with month resolution"] + DateTimeMonth, + #[doc = " DateTime with week resolution"] + DateTimeWeek, + #[doc = " DateTime with day resolution"] + DateTimeDay, + #[doc = " DateTime with hour resolution"] + DateTimeHour, + #[doc = " DateTime with minute resolution"] + DateTimeMinute, + #[doc = " DateTime with second resolution"] + DateTimeSecond, + #[doc = " DateTime with millisecond resolution"] + DateTimeMillisecond, + #[doc = " DateTime with microsecond resolution"] + DateTimeMicrosecond, + #[doc = " DateTime with nanosecond resolution"] + DateTimeNanosecond, + #[doc = " DateTime with picosecond resolution"] + DateTimePicosecond, + #[doc = " DateTime with femtosecond resolution"] + DateTimeFemtosecond, + #[doc = " DateTime with attosecond resolution"] + DateTimeAttosecond, + #[doc = " Time with hour resolution"] + TimeHour, + #[doc = " Time with minute resolution"] + TimeMinute, + #[doc = " Time with second resolution"] + TimeSecond, + #[doc = " Time with millisecond resolution"] + TimeMillisecond, + #[doc = " Time with microsecond resolution"] + TimeMicrosecond, + #[doc = " Time with nanosecond resolution"] + TimeNanosecond, + #[doc = " Time with picosecond resolution"] + TimePicosecond, + #[doc = " Time with femtosecond resolution"] + TimeFemtosecond, + #[doc = " Time with attosecond resolution"] + TimeAttosecond, + #[doc = " Byte sequence"] + Blob, + #[doc = " Boolean"] + Boolean, + #[doc = " Geometry data in well-known binary (WKB) format, stored as std::byte"] + GeometryWkb, + #[doc = " Geometry data in well-known text (WKT) format, stored as std::byte"] + GeometryWkt, +} + +impl Datatype { + pub(crate) fn capi_enum(&self) -> ffi::tiledb_datatype_t { + match *self { + Datatype::Int8 => ffi::tiledb_datatype_t_TILEDB_INT8, + Datatype::Int16 => ffi::tiledb_datatype_t_TILEDB_INT16, + Datatype::Int32 => ffi::tiledb_datatype_t_TILEDB_INT32, + Datatype::Int64 => ffi::tiledb_datatype_t_TILEDB_INT64, + Datatype::Float32 => ffi::tiledb_datatype_t_TILEDB_FLOAT32, + Datatype::Float64 => ffi::tiledb_datatype_t_TILEDB_FLOAT64, + Datatype::Char => ffi::tiledb_datatype_t_TILEDB_CHAR, + Datatype::UInt8 => ffi::tiledb_datatype_t_TILEDB_UINT8, + Datatype::UInt16 => ffi::tiledb_datatype_t_TILEDB_UINT16, + Datatype::UInt32 => ffi::tiledb_datatype_t_TILEDB_UINT32, + Datatype::UInt64 => ffi::tiledb_datatype_t_TILEDB_UINT64, + Datatype::StringAscii => ffi::tiledb_datatype_t_TILEDB_STRING_ASCII, + Datatype::StringUtf8 => ffi::tiledb_datatype_t_TILEDB_STRING_UTF8, + Datatype::StringUtf16 => ffi::tiledb_datatype_t_TILEDB_STRING_UTF16, + Datatype::StringUtf32 => ffi::tiledb_datatype_t_TILEDB_STRING_UTF32, + Datatype::StringUcs2 => ffi::tiledb_datatype_t_TILEDB_STRING_UCS2, + Datatype::StringUcs4 => ffi::tiledb_datatype_t_TILEDB_STRING_UCS4, + Datatype::Any => ffi::tiledb_datatype_t_TILEDB_ANY, + Datatype::DateTimeYear => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_YEAR + } + Datatype::DateTimeMonth => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_MONTH + } + Datatype::DateTimeWeek => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_WEEK + } + Datatype::DateTimeDay => ffi::tiledb_datatype_t_TILEDB_DATETIME_DAY, + Datatype::DateTimeHour => ffi::tiledb_datatype_t_TILEDB_DATETIME_HR, + Datatype::DateTimeMinute => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_MIN + } + Datatype::DateTimeSecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_SEC + } + Datatype::DateTimeMillisecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_MS + } + Datatype::DateTimeMicrosecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_US + } + Datatype::DateTimeNanosecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_NS + } + Datatype::DateTimePicosecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_PS + } + Datatype::DateTimeFemtosecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_FS + } + Datatype::DateTimeAttosecond => { + ffi::tiledb_datatype_t_TILEDB_DATETIME_AS + } + Datatype::TimeHour => ffi::tiledb_datatype_t_TILEDB_TIME_HR, + Datatype::TimeMinute => ffi::tiledb_datatype_t_TILEDB_TIME_MIN, + Datatype::TimeSecond => ffi::tiledb_datatype_t_TILEDB_TIME_SEC, + Datatype::TimeMillisecond => ffi::tiledb_datatype_t_TILEDB_TIME_MS, + Datatype::TimeMicrosecond => ffi::tiledb_datatype_t_TILEDB_TIME_US, + Datatype::TimeNanosecond => ffi::tiledb_datatype_t_TILEDB_TIME_NS, + Datatype::TimePicosecond => ffi::tiledb_datatype_t_TILEDB_TIME_PS, + Datatype::TimeFemtosecond => ffi::tiledb_datatype_t_TILEDB_TIME_FS, + Datatype::TimeAttosecond => ffi::tiledb_datatype_t_TILEDB_TIME_AS, + Datatype::Blob => ffi::tiledb_datatype_t_TILEDB_BLOB, + Datatype::Boolean => ffi::tiledb_datatype_t_TILEDB_BOOL, + Datatype::GeometryWkb => ffi::tiledb_datatype_t_TILEDB_GEOM_WKB, + Datatype::GeometryWkt => ffi::tiledb_datatype_t_TILEDB_GEOM_WKT, + } + } + + pub fn size(&self) -> u64 { + let copy = *self; + unsafe { ffi::tiledb_datatype_size(copy as ffi::tiledb_datatype_t) } + } + + pub fn to_string(&self) -> Option { + let copy = *self; + let c_dtype = copy as ffi::tiledb_datatype_t; + let mut c_str = std::ptr::null::(); + let res = unsafe { ffi::tiledb_datatype_to_str(c_dtype, &mut c_str) }; + if res == ffi::TILEDB_OK { + let c_msg = unsafe { std::ffi::CStr::from_ptr(c_str) }; + Some(String::from(c_msg.to_string_lossy())) + } else { + None + } + } + + pub fn from_string(dtype: &str) -> Option { + let c_dtype = + std::ffi::CString::new(dtype).expect("Error creating CString"); + let mut c_ret: ffi::tiledb_datatype_t = out_ptr!(); + let res = unsafe { + ffi::tiledb_datatype_from_str( + c_dtype.as_c_str().as_ptr(), + &mut c_ret, + ) + }; + + if res == ffi::TILEDB_OK { + match Datatype::try_from(c_ret) { + Ok(dt) => Some(dt), + Err(_) => None, + } + } else { + None + } + } + + pub fn is_compatible_type(&self) -> bool { + use std::any::TypeId; + + let tid = TypeId::of::(); + if tid == TypeId::of::() { + matches!(*self, Datatype::Float32) + } else if tid == TypeId::of::() { + matches!(*self, Datatype::Float64) + } else if tid == TypeId::of::() { + matches!(*self, Datatype::Char | Datatype::Int8) + } else if tid == TypeId::of::() { + matches!( + *self, + Datatype::Any + | Datatype::Blob + | Datatype::Boolean + | Datatype::GeometryWkb + | Datatype::GeometryWkt + | Datatype::StringAscii + | Datatype::StringUtf8 + | Datatype::UInt8 + ) + } else if tid == TypeId::of::() { + matches!(*self, Datatype::Int16) + } else if tid == TypeId::of::() { + matches!( + *self, + Datatype::StringUtf16 | Datatype::StringUcs2 | Datatype::UInt16 + ) + } else if tid == TypeId::of::() { + matches!(*self, Datatype::Int32) + } else if tid == TypeId::of::() { + matches!( + *self, + Datatype::StringUtf32 | Datatype::StringUcs4 | Datatype::UInt32 + ) + } else if tid == TypeId::of::() { + matches!( + *self, + Datatype::Int64 + | Datatype::DateTimeYear + | Datatype::DateTimeMonth + | Datatype::DateTimeWeek + | Datatype::DateTimeDay + | Datatype::DateTimeHour + | Datatype::DateTimeMinute + | Datatype::DateTimeSecond + | Datatype::DateTimeMillisecond + | Datatype::DateTimeMicrosecond + | Datatype::DateTimeNanosecond + | Datatype::DateTimePicosecond + | Datatype::DateTimeFemtosecond + | Datatype::DateTimeAttosecond + | Datatype::TimeHour + | Datatype::TimeMinute + | Datatype::TimeSecond + | Datatype::TimeMillisecond + | Datatype::TimeMicrosecond + | Datatype::TimeNanosecond + | Datatype::TimePicosecond + | Datatype::TimeFemtosecond + | Datatype::TimeAttosecond + ) + } else if tid == TypeId::of::() { + matches!(*self, Datatype::UInt64) + } else { + false + } + } + + /// Returns whether this type is an integral type (i.e. integer) + // Keep in sync with sm/enums/datatype.h::datatype_is_integer + pub fn is_integral_type(&self) -> bool { + matches!( + *self, + Datatype::Boolean + | Datatype::Int8 + | Datatype::Int16 + | Datatype::Int32 + | Datatype::Int64 + | Datatype::UInt8 + | Datatype::UInt16 + | Datatype::UInt32 + | Datatype::UInt64 + ) + } + + /// Returns whether this type is a real number (i.e. floating point) + // Keep in sync with sm/enums/datatype.h::datatype_is_real + pub fn is_real_type(&self) -> bool { + matches!(*self, Datatype::Float32 | Datatype::Float64) + } + + /// Returns whether this type is a variable-length string type + // Keep in sync with sm/enums/datatype.h::datatype_is_string + pub fn is_string_type(&self) -> bool { + matches!( + *self, + Datatype::StringAscii + | Datatype::StringUtf8 + | Datatype::StringUtf16 + | Datatype::StringUtf32 + | Datatype::StringUcs2 + | Datatype::StringUcs4 + ) + } + + /// Returns whether this type is a DateTime type of any resolution + // Keep in sync with sm/enums/datatype.h::datatype_is_datetime + pub fn is_datetime_type(&self) -> bool { + matches!( + *self, + Datatype::DateTimeYear + | Datatype::DateTimeMonth + | Datatype::DateTimeWeek + | Datatype::DateTimeDay + | Datatype::DateTimeHour + | Datatype::DateTimeMinute + | Datatype::DateTimeSecond + | Datatype::DateTimeMillisecond + | Datatype::DateTimeMicrosecond + | Datatype::DateTimeNanosecond + | Datatype::DateTimePicosecond + | Datatype::DateTimeFemtosecond + | Datatype::DateTimeAttosecond + ) + } + + /// Returns whether this type is a Time type of any resolution + // Keep in sync with sm/enums/datatype.h::datatype_is_time + pub fn is_time_type(&self) -> bool { + matches!( + *self, + Datatype::TimeHour + | Datatype::TimeMinute + | Datatype::TimeSecond + | Datatype::TimeMillisecond + | Datatype::TimeMicrosecond + | Datatype::TimeNanosecond + | Datatype::TimePicosecond + | Datatype::TimeFemtosecond + | Datatype::TimeAttosecond + ) + } + + /// Returns whether this type is a byte + // Keep in sync with sm/enums/datatype.h:datatype_is_byte + pub fn is_byte_type(&self) -> bool { + matches!( + *self, + Datatype::Boolean | Datatype::GeometryWkb | Datatype::GeometryWkt + ) + } + + /// Returns whether this type can be used as a dimension type of a sparse array + pub fn is_allowed_dimension_type_sparse(&self) -> bool { + self.is_integral_type() + || self.is_datetime_type() + || self.is_time_type() + || matches!( + *self, + Datatype::Float32 | Datatype::Float64 | Datatype::StringAscii + ) + } + + /// Returns whether this type can be used as a dimension type of a dense array + pub fn is_allowed_dimension_type_dense(&self) -> bool { + self.is_integral_type() + || self.is_datetime_type() + || self.is_time_type() + } +} + +impl Debug for Datatype { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + ::fmt(self, f) + } +} + +impl Display for Datatype { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!( + f, + "{}", + match self.to_string() { + Some(s) => s, + None => String::from(""), + } + ) + } +} + +impl TryFrom for Datatype { + type Error = crate::error::Error; + + fn try_from(value: ffi::tiledb_datatype_t) -> TileDBResult { + Ok(match value { + ffi::tiledb_datatype_t_TILEDB_INT8 => Datatype::Int8, + ffi::tiledb_datatype_t_TILEDB_INT16 => Datatype::Int16, + ffi::tiledb_datatype_t_TILEDB_INT32 => Datatype::Int32, + ffi::tiledb_datatype_t_TILEDB_INT64 => Datatype::Int64, + ffi::tiledb_datatype_t_TILEDB_FLOAT32 => Datatype::Float32, + ffi::tiledb_datatype_t_TILEDB_FLOAT64 => Datatype::Float64, + ffi::tiledb_datatype_t_TILEDB_CHAR => Datatype::Char, + ffi::tiledb_datatype_t_TILEDB_UINT8 => Datatype::UInt8, + ffi::tiledb_datatype_t_TILEDB_UINT16 => Datatype::UInt16, + ffi::tiledb_datatype_t_TILEDB_UINT32 => Datatype::UInt32, + ffi::tiledb_datatype_t_TILEDB_UINT64 => Datatype::UInt64, + ffi::tiledb_datatype_t_TILEDB_STRING_ASCII => Datatype::StringAscii, + ffi::tiledb_datatype_t_TILEDB_STRING_UTF8 => Datatype::StringUtf8, + ffi::tiledb_datatype_t_TILEDB_STRING_UTF16 => Datatype::StringUtf16, + ffi::tiledb_datatype_t_TILEDB_STRING_UTF32 => Datatype::StringUtf32, + ffi::tiledb_datatype_t_TILEDB_STRING_UCS2 => Datatype::StringUcs2, + ffi::tiledb_datatype_t_TILEDB_STRING_UCS4 => Datatype::StringUcs4, + ffi::tiledb_datatype_t_TILEDB_ANY => Datatype::Any, + ffi::tiledb_datatype_t_TILEDB_DATETIME_YEAR => { + Datatype::DateTimeYear + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_MONTH => { + Datatype::DateTimeMonth + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_WEEK => { + Datatype::DateTimeWeek + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_DAY => Datatype::DateTimeDay, + ffi::tiledb_datatype_t_TILEDB_DATETIME_HR => Datatype::DateTimeHour, + ffi::tiledb_datatype_t_TILEDB_DATETIME_MIN => { + Datatype::DateTimeMinute + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_SEC => { + Datatype::DateTimeSecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_MS => { + Datatype::DateTimeMillisecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_US => { + Datatype::DateTimeMicrosecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_NS => { + Datatype::DateTimeNanosecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_PS => { + Datatype::DateTimePicosecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_FS => { + Datatype::DateTimeFemtosecond + } + ffi::tiledb_datatype_t_TILEDB_DATETIME_AS => { + Datatype::DateTimeAttosecond + } + ffi::tiledb_datatype_t_TILEDB_TIME_HR => Datatype::TimeHour, + ffi::tiledb_datatype_t_TILEDB_TIME_MIN => Datatype::TimeMinute, + ffi::tiledb_datatype_t_TILEDB_TIME_SEC => Datatype::TimeSecond, + ffi::tiledb_datatype_t_TILEDB_TIME_MS => Datatype::TimeMillisecond, + ffi::tiledb_datatype_t_TILEDB_TIME_US => Datatype::TimeMicrosecond, + ffi::tiledb_datatype_t_TILEDB_TIME_NS => Datatype::TimeNanosecond, + ffi::tiledb_datatype_t_TILEDB_TIME_PS => Datatype::TimePicosecond, + ffi::tiledb_datatype_t_TILEDB_TIME_FS => Datatype::TimeFemtosecond, + ffi::tiledb_datatype_t_TILEDB_TIME_AS => Datatype::TimeAttosecond, + ffi::tiledb_datatype_t_TILEDB_BLOB => Datatype::Blob, + ffi::tiledb_datatype_t_TILEDB_BOOL => Datatype::Boolean, + ffi::tiledb_datatype_t_TILEDB_GEOM_WKB => Datatype::GeometryWkb, + ffi::tiledb_datatype_t_TILEDB_GEOM_WKT => Datatype::GeometryWkt, + _ => { + return Err(crate::error::Error::from(format!( + "Invalid datatype: {}", + value + ))) + } + }) + } +} /// Apply a generic function `$func` to data which implements `$datatype` and then run /// the expression `$then` on the result. @@ -22,6 +503,7 @@ // // Also we probably only need the third variation since that can easily implement the other ones // +#[macro_export] macro_rules! fn_typed { ($datatype:expr, $typename:ident, $then:expr) => {{ type Datatype = $crate::Datatype; @@ -232,3 +714,72 @@ macro_rules! fn_typed { } }}; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn datatype_test() { + for i in 0..256 { + println!("I: {}", i); + if i <= 43 { + let dt = Datatype::try_from(i as u32) + .expect("Error converting value to Datatype"); + assert_ne!( + format!("{}", dt), + "".to_string() + ); + assert!(check_valid(&dt)); + } else { + assert!(Datatype::try_from(i as u32).is_err()); + } + } + } + + fn check_valid(dt: &Datatype) -> bool { + let mut count = 0; + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + if dt.is_compatible_type::() { + count += 1; + } + + count == 1 + } +} diff --git a/tiledb/api/src/filter.rs b/tiledb/api/src/filter.rs index 4f359a49..57a6111c 100644 --- a/tiledb/api/src/filter.rs +++ b/tiledb/api/src/filter.rs @@ -1,9 +1,13 @@ +use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::ops::Deref; +use serde::{Deserialize, Serialize}; + use crate::context::Context; use crate::error::Error; -use crate::Result as TileDBResult; +use crate::{Datatype, Result as TileDBResult}; +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum CompressionType { Bzip2, Delta, @@ -15,11 +19,278 @@ pub enum CompressionType { Zstd, } +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum ChecksumType { Md5, Sha256, } +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum WebPFilterInputFormat { + None, + Rgb, + Bgr, + Rgba, + Bgra, +} + +impl WebPFilterInputFormat { + pub(crate) fn capi_enum(&self) -> u32 { + let ffi_enum = match *self { + WebPFilterInputFormat::None => ffi::WebPFilterInputFormat::NONE, + WebPFilterInputFormat::Rgb => ffi::WebPFilterInputFormat::RGB, + WebPFilterInputFormat::Bgr => ffi::WebPFilterInputFormat::BGR, + WebPFilterInputFormat::Rgba => ffi::WebPFilterInputFormat::RGBA, + WebPFilterInputFormat::Bgra => ffi::WebPFilterInputFormat::BGRA, + }; + ffi_enum as u32 + } +} + +impl TryFrom for WebPFilterInputFormat { + type Error = crate::error::Error; + fn try_from(value: u32) -> TileDBResult { + match value { + 0 => Ok(WebPFilterInputFormat::None), + 1 => Ok(WebPFilterInputFormat::Rgb), + 2 => Ok(WebPFilterInputFormat::Bgr), + 3 => Ok(WebPFilterInputFormat::Rgba), + 4 => Ok(WebPFilterInputFormat::Bgra), + _ => Err(Self::Error::from(format!( + "Invalid webp filter type: {}", + value + ))), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct CompressionData { + pub kind: CompressionType, + pub level: Option, + pub reinterpret_datatype: Option, +} + +impl CompressionData { + pub fn new(kind: CompressionType) -> Self { + CompressionData { + kind, + level: None, + reinterpret_datatype: None, + } + } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +pub enum ScaleFloatByteWidth { + I8, + I16, + I32, + #[default] // keep in sync with tiledb/sm/filter/float_scaling_filter.h + I64, +} + +impl ScaleFloatByteWidth { + pub(crate) fn capi_enum(&self) -> usize { + match *self { + Self::I8 => std::mem::size_of::(), + Self::I16 => std::mem::size_of::(), + Self::I32 => std::mem::size_of::(), + Self::I64 => std::mem::size_of::(), + } + } + + pub fn output_datatype(&self) -> Datatype { + match *self { + Self::I8 => Datatype::Int8, + Self::I16 => Datatype::Int16, + Self::I32 => Datatype::Int32, + Self::I64 => Datatype::Int64, + } + } +} + +impl TryFrom for ScaleFloatByteWidth { + type Error = crate::error::Error; + fn try_from(value: std::ffi::c_ulonglong) -> TileDBResult { + match value { + 1 => Ok(Self::I8), + 2 => Ok(Self::I16), + 4 => Ok(Self::I32), + 8 => Ok(Self::I64), + v => Err(Self::Error::from(format!( + "Invalid scale float byte width: {}", + v + ))), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum FilterData { + None, + BitShuffle, + ByteShuffle, + BitWidthReduction { + max_window: Option, + }, + Checksum(ChecksumType), + Compression(CompressionData), + PositiveDelta { + max_window: Option, + }, + ScaleFloat { + byte_width: Option, + factor: Option, + offset: Option, + }, + WebP { + input_format: Option, + lossless: Option, + quality: Option, + }, + Xor, +} + +impl FilterData { + pub fn capi_enum(&self) -> ffi::FilterType { + match *self { + FilterData::None => ffi::FilterType::None, + FilterData::BitShuffle { .. } => ffi::FilterType::BitShuffle, + FilterData::ByteShuffle { .. } => ffi::FilterType::ByteShuffle, + FilterData::BitWidthReduction { .. } => { + ffi::FilterType::BitWidthReduction + } + FilterData::Checksum(ChecksumType::Md5) => { + ffi::FilterType::ChecksumMD5 + } + FilterData::Checksum(ChecksumType::Sha256) => { + ffi::FilterType::ChecksumSHA256 + } + FilterData::Compression(CompressionData { + kind: CompressionType::Bzip2, + .. + }) => ffi::FilterType::Bzip2, + FilterData::Compression(CompressionData { + kind: CompressionType::Delta, + .. + }) => ffi::FilterType::Delta, + FilterData::Compression(CompressionData { + kind: CompressionType::Dictionary, + .. + }) => ffi::FilterType::Dictionary, + FilterData::Compression(CompressionData { + kind: CompressionType::DoubleDelta, + .. + }) => ffi::FilterType::DoubleDelta, + FilterData::Compression(CompressionData { + kind: CompressionType::Gzip, + .. + }) => ffi::FilterType::Gzip, + FilterData::Compression(CompressionData { + kind: CompressionType::Lz4, + .. + }) => ffi::FilterType::Lz4, + FilterData::Compression(CompressionData { + kind: CompressionType::Rle, + .. + }) => ffi::FilterType::Rle, + FilterData::Compression(CompressionData { + kind: CompressionType::Zstd, + .. + }) => ffi::FilterType::Zstd, + FilterData::PositiveDelta { .. } => ffi::FilterType::PositiveDelta, + FilterData::ScaleFloat { .. } => ffi::FilterType::ScaleFloat, + FilterData::WebP { .. } => ffi::FilterType::WebP, + FilterData::Xor => ffi::FilterType::Xor, + } + } + + /// Returns the output datatype when this filter is applied to the input type. + /// If the filter cannot accept the requested input type, None is returned. + pub fn transform_datatype(&self, input: &Datatype) -> Option { + /* + * Note to developers, this code should be kept in sync with + * tiledb/sm/filters/filter/ functions + * - `accepts_input_datatype` + * - `output_datatype` + * + * Those functions are not part of the external C API. + */ + match *self { + FilterData::None => Some(*input), + FilterData::BitShuffle => Some(*input), + FilterData::ByteShuffle => Some(*input), + FilterData::Checksum(_) => Some(*input), + FilterData::BitWidthReduction { .. } + | FilterData::PositiveDelta { .. } => { + if input.is_integral_type() + || input.is_datetime_type() + || input.is_time_type() + || input.is_byte_type() + { + Some(*input) + } else { + None + } + } + FilterData::Compression(CompressionData { + kind, + reinterpret_datatype, + .. + }) => match kind { + CompressionType::Delta | CompressionType::DoubleDelta => { + // these filters do not accept floating point + let check_type = + if let Some(Datatype::Any) = reinterpret_datatype { + *input + } else if let Some(reinterpret_datatype) = + reinterpret_datatype + { + reinterpret_datatype + } else { + return None; + }; + if check_type.is_real_type() { + None + } else { + Some(check_type) + } + } + _ => Some(*input), + }, + FilterData::ScaleFloat { byte_width, .. } => { + let input_size = input.size() as usize; + if input_size == std::mem::size_of::() + || input_size == std::mem::size_of::() + { + Some( + byte_width + .unwrap_or(ScaleFloatByteWidth::default()) + .output_datatype(), + ) + } else { + None + } + } + FilterData::WebP { .. } => { + if *input == Datatype::UInt8 { + Some(Datatype::UInt8) + } else { + None + } + } + FilterData::Xor => match input.size() { + 1 => Some(Datatype::Int8), + 2 => Some(Datatype::Int16), + 4 => Some(Datatype::Int32), + 8 => Some(Datatype::Int64), + _ => None, + }, + } + } +} + pub(crate) enum RawFilter { Owned(*mut ffi::tiledb_filter_t), } @@ -54,26 +325,154 @@ impl<'ctx> Filter<'ctx> { Filter { context, raw } } - fn create( + pub fn create( context: &'ctx Context, - filter_type: ffi::FilterType, + filter_data: FilterData, ) -> TileDBResult { + let c_context = context.capi(); let mut c_filter: *mut ffi::tiledb_filter_t = out_ptr!(); - let ftype = filter_type as u32; + let ftype = filter_data.capi_enum() as u32; let res = unsafe { - ffi::tiledb_filter_alloc(context.capi(), ftype, &mut c_filter) + ffi::tiledb_filter_alloc(c_context, ftype, &mut c_filter) }; - if res == ffi::TILEDB_OK { - Ok(Filter { - context, - raw: RawFilter::Owned(c_filter), - }) - } else { - Err(context.expect_last_error()) + if res != ffi::TILEDB_OK { + return Err(context.expect_last_error()); } + + let raw = RawFilter::Owned(c_filter); + + match filter_data { + FilterData::None => (), + FilterData::BitShuffle { .. } => (), + FilterData::ByteShuffle { .. } => (), + FilterData::BitWidthReduction { max_window } => { + if let Some(max_window) = max_window { + let c_size = max_window as std::ffi::c_uint; + Self::set_option( + context, + *raw, + ffi::FilterOption::BIT_WIDTH_MAX_WINDOW, + c_size, + )?; + } + } + FilterData::Checksum(ChecksumType::Md5) => (), + FilterData::Checksum(ChecksumType::Sha256) => (), + FilterData::Compression(CompressionData { + level, + reinterpret_datatype, + .. + }) => { + if let Some(level) = level { + let c_level = level as std::ffi::c_int; + Self::set_option( + context, + *raw, + ffi::FilterOption::COMPRESSION_LEVEL, + c_level, + )?; + } + if let Some(reinterpret_datatype) = reinterpret_datatype { + let c_datatype = + reinterpret_datatype.capi_enum() as std::ffi::c_uchar; + Self::set_option( + context, + *raw, + ffi::FilterOption::COMPRESSION_REINTERPRET_DATATYPE, + c_datatype, + )?; + } + } + FilterData::PositiveDelta { max_window } => { + if let Some(max_window) = max_window { + let c_size = max_window as std::ffi::c_uint; + Self::set_option( + context, + *raw, + ffi::FilterOption::POSITIVE_DELTA_MAX_WINDOW, + c_size, + )?; + } + } + FilterData::ScaleFloat { + byte_width, + factor, + offset, + } => { + if let Some(byte_width) = byte_width { + let c_width = byte_width.capi_enum(); + Self::set_option( + context, + *raw, + ffi::FilterOption::SCALE_FLOAT_BYTEWIDTH, + c_width, + )?; + } + + if let Some(factor) = factor { + let c_factor = factor as std::ffi::c_double; + Self::set_option( + context, + *raw, + ffi::FilterOption::SCALE_FLOAT_FACTOR, + c_factor, + )?; + } + + if let Some(offset) = offset { + let c_offset = offset as std::ffi::c_double; + Self::set_option( + context, + c_filter, + ffi::FilterOption::SCALE_FLOAT_OFFSET, + c_offset, + )?; + } + } + FilterData::WebP { + input_format, + lossless, + quality, + } => { + if let Some(input_format) = input_format { + let c_format = + input_format.capi_enum() as std::ffi::c_uchar; + Self::set_option( + context, + *raw, + ffi::FilterOption::WEBP_INPUT_FORMAT, + c_format, + )?; + } + + if let Some(lossless) = lossless { + let c_lossless: std::ffi::c_uchar = + if lossless { 1 } else { 0 }; + Self::set_option( + context, + *raw, + ffi::FilterOption::WEBP_LOSSLESS, + c_lossless, + )?; + } + + if let Some(quality) = quality { + let c_quality = quality as std::ffi::c_float; + Self::set_option( + context, + *raw, + ffi::FilterOption::WEBP_QUALITY, + c_quality, + )?; + } + } + FilterData::Xor => (), + }; + + Ok(Filter { context, raw }) } - pub fn get_type(&self) -> TileDBResult { + pub fn filter_data(&self) -> TileDBResult { let mut c_ftype: u32 = 0; let res = unsafe { ffi::tiledb_filter_get_type( @@ -82,553 +481,381 @@ impl<'ctx> Filter<'ctx> { &mut c_ftype, ) }; - if res == ffi::TILEDB_OK { - let ftype = ffi::FilterType::from_u32(c_ftype); - match ftype { - Some(ft) => Ok(ft), - None => Err(Error::from("Unknown filter type.")), - } - } else { - Err(self.context.expect_last_error()) - } - } - - pub fn get_bit_width_max_window(&self) -> TileDBResult { - let mut c_width: std::ffi::c_uint = 0; - self.get_option( - ffi::FilterOption::BIT_WIDTH_MAX_WINDOW, - &mut c_width as *mut std::ffi::c_uint as *mut std::ffi::c_void, - ) - .map(|_| c_width as u32) - } - - pub fn get_compression_level(&self) -> TileDBResult { - let mut c_level: std::ffi::c_int = 0; - self.get_option( - ffi::FilterOption::COMPRESSION_LEVEL, - &mut c_level as *mut std::ffi::c_int as *mut std::ffi::c_void, - ) - .map(|_| c_level as i32) - } - - pub fn get_compression_reinterpret_datatype( - &self, - ) -> TileDBResult { - let mut c_fmt: std::ffi::c_uchar = 0; - let res = self.get_option( - ffi::FilterOption::COMPRESSION_REINTERPRET_DATATYPE, - &mut c_fmt as *mut std::ffi::c_uchar as *mut std::ffi::c_void, - ); - match res { - Ok(()) => match ffi::Datatype::from_u32(c_fmt as u32) { - Some(dtype) => Ok(dtype), - None => Err(Error::from("Invalid compression reinterpret datatype returned from core.")) - }, - Err(msg) => Err(msg), + if res != ffi::TILEDB_OK { + return Err(self.context.expect_last_error()); } - } - - pub fn get_float_bytewidth(&self) -> TileDBResult { - let mut c_width: std::ffi::c_ulonglong = 0; - self.get_option( - ffi::FilterOption::SCALE_FLOAT_BYTEWIDTH, - &mut c_width as *mut std::ffi::c_ulonglong as *mut std::ffi::c_void, - ) - .map(|_| c_width as u64) - } - - pub fn get_float_factor(&self) -> TileDBResult { - let mut c_factor: std::ffi::c_double = 0.0; - self.get_option( - ffi::FilterOption::SCALE_FLOAT_FACTOR, - &mut c_factor as *mut std::ffi::c_double as *mut std::ffi::c_void, - ) - .map(|_| c_factor as f64) - } - - pub fn get_float_offset(&self) -> TileDBResult { - let mut c_factor: std::ffi::c_double = 0.0; - self.get_option( - ffi::FilterOption::SCALE_FLOAT_OFFSET, - &mut c_factor as *mut std::ffi::c_double as *mut std::ffi::c_void, - ) - .map(|_| c_factor as f64) - } - pub fn get_positive_delta_max_window(&self) -> TileDBResult { - let mut c_width: std::ffi::c_uint = 0; - self.get_option( - ffi::FilterOption::POSITIVE_DELTA_MAX_WINDOW, - &mut c_width as *mut std::ffi::c_uint as *mut std::ffi::c_void, - ) - .map(|_| c_width as u32) - } + let get_compression_data = |kind| -> TileDBResult { + let level = Some( + self.get_option::(ffi::FilterOption::COMPRESSION_LEVEL)?, + ); + let reinterpret_datatype = Some({ + let dtype = self.get_option::( + ffi::FilterOption::COMPRESSION_REINTERPRET_DATATYPE, + )?; + Datatype::try_from(dtype as ffi::tiledb_datatype_t).map_err( + |_| { + Error::from(format!( + "Invalid compression reinterpret datatype: {}", + dtype + )) + }, + )? + }); + Ok(FilterData::Compression(CompressionData { + kind, + level, + reinterpret_datatype, + })) + }; - pub fn get_webp_input_format( - &self, - ) -> TileDBResult { - let mut c_fmt: std::ffi::c_uchar = 0; - let res = self.get_option( - ffi::FilterOption::WEBP_INPUT_FORMAT, - &mut c_fmt as *mut std::ffi::c_uchar as *mut std::ffi::c_void, - ); - match res { - Ok(()) => { - match ffi::WebPFilterInputFormat::from_u32(c_fmt as u32) { - Some(fmt) => Ok(fmt), - None => Err(Error::from( - "Invalid WebP input filter format returned from core.", - )), - } + match ffi::FilterType::from_u32(c_ftype) { + None => Err(crate::error::Error::from(format!( + "Invalid filter type: {}", + c_ftype + ))), + Some(ffi::FilterType::None) => Ok(FilterData::None), + Some(ffi::FilterType::Gzip) => { + get_compression_data(CompressionType::Gzip) + } + Some(ffi::FilterType::Zstd) => { + get_compression_data(CompressionType::Zstd) + } + Some(ffi::FilterType::Lz4) => { + get_compression_data(CompressionType::Lz4) + } + Some(ffi::FilterType::Rle) => { + get_compression_data(CompressionType::Rle) + } + Some(ffi::FilterType::Bzip2) => { + get_compression_data(CompressionType::Bzip2) } - Err(msg) => Err(msg), + Some(ffi::FilterType::Dictionary) => { + get_compression_data(CompressionType::Dictionary) + } + Some(ffi::FilterType::DoubleDelta) => { + get_compression_data(CompressionType::DoubleDelta) + } + Some(ffi::FilterType::Delta) => { + get_compression_data(CompressionType::Delta) + } + Some(ffi::FilterType::BitShuffle) => Ok(FilterData::BitShuffle), + Some(ffi::FilterType::ByteShuffle) => Ok(FilterData::ByteShuffle), + Some(ffi::FilterType::Xor) => Ok(FilterData::Xor), + Some(ffi::FilterType::BitWidthReduction) => { + Ok(FilterData::BitWidthReduction { + max_window: Some(self.get_option::( + ffi::FilterOption::BIT_WIDTH_MAX_WINDOW, + )?), + }) + } + Some(ffi::FilterType::PositiveDelta) => { + Ok(FilterData::PositiveDelta { + max_window: Some(self.get_option::( + ffi::FilterOption::POSITIVE_DELTA_MAX_WINDOW, + )?), + }) + } + Some(ffi::FilterType::ChecksumMD5) => { + Ok(FilterData::Checksum(ChecksumType::Md5)) + } + Some(ffi::FilterType::ChecksumSHA256) => { + Ok(FilterData::Checksum(ChecksumType::Sha256)) + } + Some(ffi::FilterType::ScaleFloat) => Ok(FilterData::ScaleFloat { + byte_width: Some(ScaleFloatByteWidth::try_from( + self.get_option::( + ffi::FilterOption::SCALE_FLOAT_BYTEWIDTH, + )?, + )?), + factor: Some(self.get_option::( + ffi::FilterOption::SCALE_FLOAT_FACTOR, + )?), + offset: Some(self.get_option::( + ffi::FilterOption::SCALE_FLOAT_OFFSET, + )?), + }), + Some(ffi::FilterType::WebP) => Ok(FilterData::WebP { + input_format: Some(WebPFilterInputFormat::try_from( + self.get_option::( + ffi::FilterOption::WEBP_INPUT_FORMAT, + )?, + )?), + lossless: Some( + self.get_option::( + ffi::FilterOption::WEBP_LOSSLESS, + )? != 0, + ), + quality: Some(self.get_option::( + ffi::FilterOption::WEBP_QUALITY, + )?), + }), } } - pub fn get_webp_lossless(&self) -> TileDBResult { - let mut c_lossless: std::ffi::c_uchar = 0; - self.get_option( - ffi::FilterOption::WEBP_LOSSLESS, - &mut c_lossless as *mut std::ffi::c_uchar as *mut std::ffi::c_void, - ) - .map(|_| c_lossless != 0) - } - - pub fn get_webp_quality(&self) -> TileDBResult { - let mut c_factor: std::ffi::c_float = 0.0; - self.get_option( - ffi::FilterOption::WEBP_QUALITY, - &mut c_factor as *mut std::ffi::c_float as *mut std::ffi::c_void, - ) - .map(|_| c_factor as f32) - } - - fn get_option( - &self, - fopt: ffi::FilterOption, - val: *mut std::ffi::c_void, - ) -> TileDBResult<()> { + fn get_option(&self, fopt: ffi::FilterOption) -> TileDBResult { + let mut val: T = out_ptr!(); let res = unsafe { ffi::tiledb_filter_get_option( self.context.capi(), self.capi(), fopt as u32, - val, + &mut val as *mut T as *mut std::ffi::c_void, ) }; if res == ffi::TILEDB_OK { - Ok(()) + Ok(val) } else { Err(self.context.expect_last_error()) } } - fn set_option( - &self, + fn set_option( + context: &Context, + raw: *mut ffi::tiledb_filter_t, fopt: ffi::FilterOption, - val: *const std::ffi::c_void, + val: T, ) -> TileDBResult<()> { + let c_val = &val as *const T as *const std::ffi::c_void; let res = unsafe { ffi::tiledb_filter_set_option( - self.context.capi(), - self.capi(), + context.capi(), + raw, fopt as u32, - val, + c_val, ) }; if res == ffi::TILEDB_OK { Ok(()) } else { - Err(self.context.expect_last_error()) + Err(context.expect_last_error()) } } } -pub struct NoopFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> NoopFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(Self { - filter: Filter::create(context, ffi::FilterType::None)?, - }) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct CompressionFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> CompressionFilterBuilder<'ctx> { - pub fn new( - context: &'ctx Context, - comp_type: CompressionType, - ) -> TileDBResult { - let ftype: ffi::FilterType = match comp_type { - CompressionType::Bzip2 => ffi::FilterType::Bzip2, - CompressionType::Delta => ffi::FilterType::Delta, - CompressionType::Dictionary => ffi::FilterType::Dictionary, - CompressionType::DoubleDelta => ffi::FilterType::DoubleDelta, - CompressionType::Gzip => ffi::FilterType::Gzip, - CompressionType::Lz4 => ffi::FilterType::Lz4, - CompressionType::Rle => ffi::FilterType::Rle, - CompressionType::Zstd => ffi::FilterType::Zstd, - }; - - Ok(CompressionFilterBuilder { - filter: Filter::create(context, ftype)?, - }) - } - - pub fn set_compression_level(self, level: i32) -> TileDBResult { - let c_level = level as std::ffi::c_int; - self.filter.set_option( - ffi::FilterOption::COMPRESSION_LEVEL, - &c_level as *const std::ffi::c_int as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn set_reinterpret_datatype( - self, - dtype: ffi::Datatype, - ) -> TileDBResult { - let c_dtype = dtype as std::ffi::c_uchar; - self.filter.set_option( - ffi::FilterOption::COMPRESSION_REINTERPRET_DATATYPE, - &c_dtype as *const std::ffi::c_uchar as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct BitWidthReductionFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> BitWidthReductionFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(BitWidthReductionFilterBuilder { - filter: Filter::create( - context, - ffi::FilterType::BitWidthReduction, - )?, - }) - } - - pub fn set_max_window(self, size: u32) -> TileDBResult { - let c_size = size as std::ffi::c_uint; - self.filter.set_option( - ffi::FilterOption::BIT_WIDTH_MAX_WINDOW, - &c_size as *const std::ffi::c_uint as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct BitShuffleFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> BitShuffleFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(BitShuffleFilterBuilder { - filter: Filter::create(context, ffi::FilterType::BitShuffle)?, - }) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct ByteShuffleFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> ByteShuffleFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(ByteShuffleFilterBuilder { - filter: Filter::create(context, ffi::FilterType::ByteShuffle)?, - }) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct ScaleFloatFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> ScaleFloatFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(ScaleFloatFilterBuilder { - filter: Filter::create(context, ffi::FilterType::ScaleFloat)?, - }) - } - - pub fn set_bytewidth(self, width: u64) -> TileDBResult { - let c_width = width as std::ffi::c_ulonglong; - self.filter.set_option( - ffi::FilterOption::SCALE_FLOAT_BYTEWIDTH, - &c_width as *const std::ffi::c_ulonglong as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn set_factor(self, factor: f64) -> TileDBResult { - let c_factor = factor as std::ffi::c_double; - self.filter.set_option( - ffi::FilterOption::SCALE_FLOAT_FACTOR, - &c_factor as *const std::ffi::c_double as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn set_offset(self, offset: f64) -> TileDBResult { - let c_offset = offset as std::ffi::c_double; - self.filter.set_option( - ffi::FilterOption::SCALE_FLOAT_OFFSET, - &c_offset as *const std::ffi::c_double as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} - -pub struct PositiveDeltaFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} - -impl<'ctx> PositiveDeltaFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(PositiveDeltaFilterBuilder { - filter: Filter::create(context, ffi::FilterType::PositiveDelta)?, - }) - } - - pub fn set_max_window(self, size: u32) -> TileDBResult { - let c_size = size as std::ffi::c_uint; - self.filter.set_option( - ffi::FilterOption::POSITIVE_DELTA_MAX_WINDOW, - &c_size as *const std::ffi::c_uint as *const std::ffi::c_void, - )?; - Ok(self) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter +impl<'ctx> Debug for Filter<'ctx> { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self.filter_data() { + Ok(data) => write!(f, "{:?}", data), + Err(e) => write!(f, " { - filter: Filter<'ctx>, -} - -impl<'ctx> ChecksumFilterBuilder<'ctx> { - pub fn new( - context: &'ctx Context, - checksum_type: ChecksumType, - ) -> TileDBResult { - let ftype = match checksum_type { - ChecksumType::Md5 => ffi::FilterType::ChecksumMD5, - ChecksumType::Sha256 => ffi::FilterType::ChecksumSHA256, - }; - Ok(ChecksumFilterBuilder { - filter: Filter::create(context, ftype)?, - }) - } - - pub fn build(self) -> Filter<'ctx> { - self.filter +impl<'c1, 'c2> PartialEq> for Filter<'c1> { + fn eq(&self, other: &Filter<'c2>) -> bool { + match (self.filter_data(), other.filter_data()) { + (Ok(mine), Ok(theirs)) => mine == theirs, + _ => false, + } } } -pub struct XorFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} +#[cfg(test)] +mod tests { + use super::*; -impl<'ctx> XorFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(XorFilterBuilder { - filter: Filter::create(context, ffi::FilterType::Xor)?, - }) - } + /// Ensure that we can construct a filter from all options using default settings + #[test] + fn filter_default_construct() { + let ctx = Context::new().expect("Error creating context"); + + // bit width reduction + { + let f = Filter::create( + &ctx, + FilterData::BitWidthReduction { max_window: None }, + ) + .expect("Error creating bit width filter"); + assert!(matches!( + f.filter_data(), + Ok(FilterData::BitWidthReduction { .. }) + )); + } - pub fn build(self) -> Filter<'ctx> { - self.filter - } -} + // compression + { + let f = Filter::create( + &ctx, + FilterData::Compression(CompressionData::new( + CompressionType::Lz4, + )), + ) + .expect("Error creating compression filter"); + + assert!(matches!( + f.filter_data(), + Ok(FilterData::Compression(CompressionData { + kind: CompressionType::Lz4, + .. + })) + )); + } -pub struct WebPFilterBuilder<'ctx> { - filter: Filter<'ctx>, -} + // positive delta + { + let f = Filter::create( + &ctx, + FilterData::PositiveDelta { max_window: None }, + ) + .expect("Error creating positive delta filter"); -impl<'ctx> WebPFilterBuilder<'ctx> { - pub fn new(context: &'ctx Context) -> TileDBResult { - Ok(WebPFilterBuilder { - filter: Filter::create(context, ffi::FilterType::WebP)?, - }) - } + assert!(matches!( + f.filter_data(), + Ok(FilterData::PositiveDelta { .. }) + )); + } - pub fn set_input_format( - self, - format: ffi::WebPFilterInputFormat, - ) -> TileDBResult { - let c_format = format as std::ffi::c_uchar; - self.filter.set_option( - ffi::FilterOption::WEBP_INPUT_FORMAT, - &c_format as *const std::ffi::c_uchar as *const std::ffi::c_void, - )?; - Ok(self) - } + // scale float + { + let f = Filter::create( + &ctx, + FilterData::ScaleFloat { + byte_width: None, + factor: None, + offset: None, + }, + ) + .expect("Error creating scale float filter"); - pub fn set_lossless(self, lossless: bool) -> TileDBResult { - let c_lossless: std::ffi::c_uchar = if lossless { 1 } else { 0 }; - self.filter.set_option( - ffi::FilterOption::WEBP_LOSSLESS, - &c_lossless as *const std::ffi::c_uchar as *const std::ffi::c_void, - )?; - Ok(self) - } + assert!(matches!( + f.filter_data(), + Ok(FilterData::ScaleFloat { .. }) + )); + } - pub fn set_quality(self, quality: f32) -> TileDBResult { - let c_quality = quality as std::ffi::c_float; - self.filter.set_option( - ffi::FilterOption::WEBP_QUALITY, - &c_quality as *const std::ffi::c_float as *const std::ffi::c_void, - )?; - Ok(self) - } + // webp + { + let f = Filter::create( + &ctx, + FilterData::WebP { + input_format: None, + lossless: None, + quality: None, + }, + ) + .expect("Error creating webp filter"); - pub fn build(self) -> Filter<'ctx> { - self.filter + assert!(matches!(f.filter_data(), Ok(FilterData::WebP { .. }))); + } } -} - -#[cfg(test)] -mod tests { - use super::*; #[test] fn filter_get_set_compression_options() { let ctx = Context::new().expect("Error creating context instance."); - let f = CompressionFilterBuilder::new(&ctx, CompressionType::Lz4) - .expect("Error creating builder instance.") - .set_compression_level(23) - .expect("Error setting compression level.") - .set_reinterpret_datatype(ffi::Datatype::UInt16) - .expect("Error setting compression reinterpret datatype.") - .build(); - - let level = f - .get_compression_level() - .expect("Error getting compression level."); - assert_eq!(level, 23); - - let dt = f - .get_compression_reinterpret_datatype() - .expect("Error getting compression reinterpret datatype"); - assert_eq!(dt, ffi::Datatype::UInt16); + let f = Filter::create( + &ctx, + FilterData::Compression(CompressionData { + kind: CompressionType::Lz4, + level: Some(23), + reinterpret_datatype: Some(Datatype::UInt16), + }), + ) + .expect("Error creating compression filter"); + + match f.filter_data().expect("Error reading filter data") { + FilterData::Compression(CompressionData { + kind, + level, + reinterpret_datatype, + }) => { + assert_eq!(CompressionType::Lz4, kind); + assert_eq!(Some(23), level); + assert_eq!(Some(Datatype::UInt16), reinterpret_datatype); + } + _ => unreachable!(), + } } #[test] fn filter_get_set_bit_width_reduction_options() { let ctx = Context::new().expect("Error creating context instance."); - let f = BitWidthReductionFilterBuilder::new(&ctx) - .expect("Error creating bit width reduction filter.") - .set_max_window(75) - .expect("Error setting bit width max window.") - .build(); - - let size = f - .get_bit_width_max_window() - .expect("Error getting bit width max window size."); - assert_eq!(size, 75); + let f = Filter::create( + &ctx, + FilterData::BitWidthReduction { + max_window: Some(75), + }, + ) + .expect("Error creating bit width reduction filter."); + + match f.filter_data().expect("Error reading filter data") { + FilterData::BitWidthReduction { max_window } => { + assert_eq!(Some(75), max_window); + } + _ => unreachable!(), + } } #[test] fn filter_get_set_positive_delta_options() { let ctx = Context::new().expect("Error creating context instance."); - let f = PositiveDeltaFilterBuilder::new(&ctx) - .expect("Error creating positive delta filter.") - .set_max_window(75) - .expect("Error setting positive delta max window.") - .build(); - - let size = f - .get_positive_delta_max_window() - .expect("Error getting positive delta max window size."); - assert_eq!(size, 75); + let f = Filter::create( + &ctx, + FilterData::PositiveDelta { + max_window: Some(75), + }, + ) + .expect("Error creating positive delta filter."); + + match f.filter_data().expect("Error reading filter data") { + FilterData::PositiveDelta { max_window } => { + assert_eq!(Some(75), max_window) + } + _ => unreachable!(), + } } #[test] fn filter_get_set_scale_float_options() { let ctx = Context::new().expect("Error creating context instance."); - let f = ScaleFloatFilterBuilder::new(&ctx) - .expect("Error creating scale float filter.") - .set_bytewidth(2) - .expect("Error setting float byte width.") - .set_factor(0.643) - .expect("Error setting float factor.") - .set_offset(0.24) - .expect("Error setting float offset.") - .build(); - - let width = f - .get_float_bytewidth() - .expect("Error getting float bytewidth."); - assert_eq!(width, 2); - - let factor = f.get_float_factor().expect("Error getting float factor."); - assert_eq!(factor, 0.643); - - let offset = f.get_float_offset().expect("Error getting float offset."); - assert_eq!(offset, 0.24); + let f = Filter::create( + &ctx, + FilterData::ScaleFloat { + byte_width: Some(ScaleFloatByteWidth::I16), + factor: Some(0.643), + offset: Some(0.24), + }, + ) + .expect("Error creating scale float filter"); + + match f.filter_data().expect("Error reading filter data") { + FilterData::ScaleFloat { + byte_width, + factor, + offset, + } => { + assert_eq!(Some(ScaleFloatByteWidth::I16), byte_width); + assert_eq!(Some(0.643), factor); + assert_eq!(Some(0.24), offset); + } + _ => unreachable!(), + } } #[test] fn filter_get_set_wep_options() { let ctx = Context::new().expect("Error creating context instance."); - let f = WebPFilterBuilder::new(&ctx) - .expect("Error creating webp filter.") - .set_input_format(ffi::WebPFilterInputFormat::BGRA) - .expect("Error setting WebP input format.") - .set_lossless(true) - .expect("Error setting WebP lossless.") - .set_quality(0.712) - .expect("Error sestting WebP quality.") - .build(); - - let quality = - f.get_webp_quality().expect("Error getting webp quality."); - assert_eq!(quality, 0.712); - - let fmt = f - .get_webp_input_format() - .expect("Error getting webp input format."); - assert_eq!(fmt, ffi::WebPFilterInputFormat::BGRA); - - let lossless = - f.get_webp_lossless().expect("Error getting webp lossless."); - assert!(lossless); + let f = Filter::create( + &ctx, + FilterData::WebP { + input_format: Some(WebPFilterInputFormat::Bgra), + lossless: Some(true), + quality: Some(0.712), + }, + ) + .expect("Error creating webp filter"); + + match f.filter_data().expect("Error reading filter data") { + FilterData::WebP { + input_format, + lossless, + quality, + } => { + assert_eq!(Some(0.712), quality); + assert_eq!(Some(WebPFilterInputFormat::Bgra), input_format); + assert_eq!(Some(true), lossless); + } + _ => unreachable!(), + } } } diff --git a/tiledb/api/src/filter_list.rs b/tiledb/api/src/filter_list.rs index b367756d..2f9142c0 100644 --- a/tiledb/api/src/filter_list.rs +++ b/tiledb/api/src/filter_list.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::ops::Deref; use crate::context::Context; @@ -67,6 +68,12 @@ impl<'ctx> FilterList<'ctx> { } } + pub fn to_vec(&self) -> TileDBResult>> { + (0..self.get_num_filters()?) + .map(|f| self.get_filter(f)) + .collect() + } + pub fn get_max_chunk_size(&self, ctx: &Context) -> TileDBResult { let mut size: u32 = 0; let res = unsafe { @@ -84,6 +91,52 @@ impl<'ctx> FilterList<'ctx> { } } +impl<'ctx> Debug for FilterList<'ctx> { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let nfilters = match self.get_num_filters() { + Ok(n) => n, + Err(e) => return write!(f, "", e), + }; + write!(f, "[")?; + for fi in 0..nfilters { + match self.get_filter(fi) { + Ok(fd) => match fd.filter_data() { + Ok(fd) => write!(f, "{:?},", fd)?, + Err(e) => { + write!(f, "", fi, e)? + } + }, + Err(e) => write!(f, "", fi, e)?, + }; + } + write!(f, "]") + } +} + +impl<'c1, 'c2> PartialEq> for FilterList<'c1> { + fn eq(&self, other: &FilterList<'c2>) -> bool { + let size_match = match (self.get_num_filters(), other.get_num_filters()) + { + (Ok(mine), Ok(theirs)) => mine == theirs, + _ => false, + }; + if !size_match { + return false; + } + + for f in 0..self.get_num_filters().unwrap() { + let filter_match = match (self.get_filter(f), other.get_filter(f)) { + (Ok(mine), Ok(theirs)) => mine == theirs, + _ => false, + }; + if !filter_match { + return false; + } + } + true + } +} + pub struct Builder<'ctx> { filter_list: FilterList<'ctx>, } @@ -165,10 +218,12 @@ mod test { let flist = Builder::new(&ctx) .expect("Error creating filter list instance.") - .add_filter( - CompressionFilterBuilder::new(&ctx, CompressionType::Zstd)? - .build(), - )? + .add_filter(Filter::create( + &ctx, + FilterData::Compression(CompressionData::new( + CompressionType::Zstd, + )), + )?)? .build(); let nfilters = flist @@ -183,18 +238,19 @@ mod test { fn filter_list_get_filter() -> TileDBResult<()> { let ctx = Context::new().expect("Error creating context instance."); let flist = Builder::new(&ctx)? - .add_filter(NoopFilterBuilder::new(&ctx)?.build())? - .add_filter( - CompressionFilterBuilder::new( - &ctx, + .add_filter(Filter::create(&ctx, FilterData::None)?)? + .add_filter(Filter::create( + &ctx, + FilterData::Compression(CompressionData::new( CompressionType::Dictionary, - )? - .build(), - )? - .add_filter( - CompressionFilterBuilder::new(&ctx, CompressionType::Zstd)? - .build(), - )? + )), + )?)? + .add_filter(Filter::create( + &ctx, + FilterData::Compression(CompressionData::new( + CompressionType::Zstd, + )), + )?)? .build(); let nfilters = flist @@ -205,8 +261,14 @@ mod test { let filter4 = flist .get_filter(1) .expect("Error getting filter at index 1"); - let ftype = filter4.get_type().expect("Error getting filter type."); - assert_eq!(ftype, ffi::FilterType::Dictionary); + let ftype = filter4.filter_data().expect("Error getting filter data"); + assert!(matches!( + ftype, + FilterData::Compression(CompressionData { + kind: CompressionType::Dictionary, + .. + }) + )); Ok(()) } diff --git a/tiledb/api/src/lib.rs b/tiledb/api/src/lib.rs index 7f1c5f74..d9afa191 100644 --- a/tiledb/api/src/lib.rs +++ b/tiledb/api/src/lib.rs @@ -47,6 +47,6 @@ pub fn version() -> (i32, i32, i32) { } pub use array::Array; -pub use ffi::Datatype; +pub use datatype::Datatype; pub use query::{Builder as QueryBuilder, Query, QueryType}; pub type Result = std::result::Result; diff --git a/tiledb/api/tests/enum_conversion_tests.rs b/tiledb/api/tests/enum_conversion_tests.rs index 7e3b0be8..e834bdfe 100644 --- a/tiledb/api/tests/enum_conversion_tests.rs +++ b/tiledb/api/tests/enum_conversion_tests.rs @@ -1,4 +1,4 @@ -use tiledb_sys::Datatype; +use tiledb::Datatype; use tiledb_sys::Filesystem; use tiledb_sys::FilterOption; use tiledb_sys::FilterType; @@ -6,8 +6,8 @@ use tiledb_sys::FilterType; #[test] fn datatype_roundtrips() { for i in 0..256 { - let maybe_dt = Datatype::from_u32(i); - if maybe_dt.is_some() { + let maybe_dt = Datatype::try_from(i); + if maybe_dt.is_ok() { let dt = maybe_dt.unwrap(); let dt_str = dt.to_string().expect("Error creating string."); let str_dt = Datatype::from_string(&dt_str) diff --git a/tiledb/arrow/src/attribute.rs b/tiledb/arrow/src/attribute.rs index cd47bd8f..270be482 100644 --- a/tiledb/arrow/src/attribute.rs +++ b/tiledb/arrow/src/attribute.rs @@ -45,6 +45,7 @@ mod tests { let c: TileDBContext = TileDBContext::new()?; proptest!(|(attr in tiledb_test::attribute::arbitrary(&c))| { + let attr = attr.expect("Error constructing arbitrary tiledb attribute"); if let Some(arrow_field) = arrow_field(&attr).expect("Error reading tiledb attribute") { assert_eq!(attr.name()?, *arrow_field.name()); assert!(crate::datatype::is_same_physical_type(&attr.datatype()?, arrow_field.data_type())); diff --git a/tiledb/sys/src/datatype.rs b/tiledb/sys/src/datatype.rs index 9cef35a4..c7821bb4 100644 --- a/tiledb/sys/src/datatype.rs +++ b/tiledb/sys/src/datatype.rs @@ -1,9 +1,51 @@ -use std::any::TypeId; -use std::fmt::{Display, Formatter, Result as FmtResult}; - -use crate::constants::TILEDB_OK; use crate::types::capi_return_t; +pub const tiledb_datatype_t_TILEDB_INT32: tiledb_datatype_t = 0; +pub const tiledb_datatype_t_TILEDB_INT64: tiledb_datatype_t = 1; +pub const tiledb_datatype_t_TILEDB_FLOAT32: tiledb_datatype_t = 2; +pub const tiledb_datatype_t_TILEDB_FLOAT64: tiledb_datatype_t = 3; +pub const tiledb_datatype_t_TILEDB_CHAR: tiledb_datatype_t = 4; +pub const tiledb_datatype_t_TILEDB_INT8: tiledb_datatype_t = 5; +pub const tiledb_datatype_t_TILEDB_UINT8: tiledb_datatype_t = 6; +pub const tiledb_datatype_t_TILEDB_INT16: tiledb_datatype_t = 7; +pub const tiledb_datatype_t_TILEDB_UINT16: tiledb_datatype_t = 8; +pub const tiledb_datatype_t_TILEDB_UINT32: tiledb_datatype_t = 9; +pub const tiledb_datatype_t_TILEDB_UINT64: tiledb_datatype_t = 10; +pub const tiledb_datatype_t_TILEDB_STRING_ASCII: tiledb_datatype_t = 11; +pub const tiledb_datatype_t_TILEDB_STRING_UTF8: tiledb_datatype_t = 12; +pub const tiledb_datatype_t_TILEDB_STRING_UTF16: tiledb_datatype_t = 13; +pub const tiledb_datatype_t_TILEDB_STRING_UTF32: tiledb_datatype_t = 14; +pub const tiledb_datatype_t_TILEDB_STRING_UCS2: tiledb_datatype_t = 15; +pub const tiledb_datatype_t_TILEDB_STRING_UCS4: tiledb_datatype_t = 16; +pub const tiledb_datatype_t_TILEDB_ANY: tiledb_datatype_t = 17; +pub const tiledb_datatype_t_TILEDB_DATETIME_YEAR: tiledb_datatype_t = 18; +pub const tiledb_datatype_t_TILEDB_DATETIME_MONTH: tiledb_datatype_t = 19; +pub const tiledb_datatype_t_TILEDB_DATETIME_WEEK: tiledb_datatype_t = 20; +pub const tiledb_datatype_t_TILEDB_DATETIME_DAY: tiledb_datatype_t = 21; +pub const tiledb_datatype_t_TILEDB_DATETIME_HR: tiledb_datatype_t = 22; +pub const tiledb_datatype_t_TILEDB_DATETIME_MIN: tiledb_datatype_t = 23; +pub const tiledb_datatype_t_TILEDB_DATETIME_SEC: tiledb_datatype_t = 24; +pub const tiledb_datatype_t_TILEDB_DATETIME_MS: tiledb_datatype_t = 25; +pub const tiledb_datatype_t_TILEDB_DATETIME_US: tiledb_datatype_t = 26; +pub const tiledb_datatype_t_TILEDB_DATETIME_NS: tiledb_datatype_t = 27; +pub const tiledb_datatype_t_TILEDB_DATETIME_PS: tiledb_datatype_t = 28; +pub const tiledb_datatype_t_TILEDB_DATETIME_FS: tiledb_datatype_t = 29; +pub const tiledb_datatype_t_TILEDB_DATETIME_AS: tiledb_datatype_t = 30; +pub const tiledb_datatype_t_TILEDB_TIME_HR: tiledb_datatype_t = 31; +pub const tiledb_datatype_t_TILEDB_TIME_MIN: tiledb_datatype_t = 32; +pub const tiledb_datatype_t_TILEDB_TIME_SEC: tiledb_datatype_t = 33; +pub const tiledb_datatype_t_TILEDB_TIME_MS: tiledb_datatype_t = 34; +pub const tiledb_datatype_t_TILEDB_TIME_US: tiledb_datatype_t = 35; +pub const tiledb_datatype_t_TILEDB_TIME_NS: tiledb_datatype_t = 36; +pub const tiledb_datatype_t_TILEDB_TIME_PS: tiledb_datatype_t = 37; +pub const tiledb_datatype_t_TILEDB_TIME_FS: tiledb_datatype_t = 38; +pub const tiledb_datatype_t_TILEDB_TIME_AS: tiledb_datatype_t = 39; +pub const tiledb_datatype_t_TILEDB_BLOB: tiledb_datatype_t = 40; +pub const tiledb_datatype_t_TILEDB_BOOL: tiledb_datatype_t = 41; +pub const tiledb_datatype_t_TILEDB_GEOM_WKB: tiledb_datatype_t = 42; +pub const tiledb_datatype_t_TILEDB_GEOM_WKT: tiledb_datatype_t = 43; +pub type tiledb_datatype_t = ::std::os::raw::c_uint; + extern "C" { pub fn tiledb_datatype_to_str( datatype: u32, @@ -17,433 +59,3 @@ extern "C" { pub fn tiledb_datatype_size(type_: u32) -> u64; } - -#[allow(non_snake_case)] -pub type tiledb_datatype_t = ::std::os::raw::c_uint; - -// When I find the time, I should come back and turn these into a macro -// so that we can auto-generate the Datatype::from_u32 generation. - -#[derive(Clone, Copy, Debug, PartialEq)] -#[repr(u64)] -pub enum Datatype { - #[doc = " 32-bit signed integer"] - Int32 = 0, - #[doc = " 64-bit signed integer"] - Int64 = 1, - #[doc = " 32-bit floating point value"] - Float32 = 2, - #[doc = " 64-bit floating point value"] - Float64 = 3, - #[doc = " Character"] - Char = 4, - #[doc = " 8-bit signed integer"] - Int8 = 5, - #[doc = " 8-bit unsigned integer"] - UInt8 = 6, - #[doc = " 16-bit signed integer"] - Int16 = 7, - #[doc = " 16-bit unsigned integer"] - UInt16 = 8, - #[doc = " 32-bit unsigned integer"] - UInt32 = 9, - #[doc = " 64-bit unsigned integer"] - UInt64 = 10, - #[doc = " ASCII string"] - StringAscii = 11, - #[doc = " UTF-8 string"] - StringUtf8 = 12, - #[doc = " UTF-16 string"] - StringUtf16 = 13, - #[doc = " UTF-32 string"] - StringUtf32 = 14, - #[doc = " UCS2 string"] - StringUcs2 = 15, - #[doc = " UCS4 string"] - StringUcs4 = 16, - #[doc = " This can be any datatype. Must store (type tag, value) pairs."] - Any = 17, - #[doc = " Datetime with year resolution"] - DateTimeYear = 18, - #[doc = " Datetime with month resolution"] - DateTimeMonth = 19, - #[doc = " Datetime with week resolution"] - DateTimeWeek = 20, - #[doc = " Datetime with day resolution"] - DateTimeDay = 21, - #[doc = " Datetime with hour resolution"] - DateTimeHour = 22, - #[doc = " Datetime with minute resolution"] - DateTimeMinute = 23, - #[doc = " Datetime with second resolution"] - DateTimeSecond = 24, - #[doc = " Datetime with millisecond resolution"] - DateTimeMillisecond = 25, - #[doc = " Datetime with microsecond resolution"] - DateTimeMicrosecond = 26, - #[doc = " Datetime with nanosecond resolution"] - DateTimeNanosecond = 27, - #[doc = " Datetime with picosecond resolution"] - DateTimePicosecond = 28, - #[doc = " Datetime with femtosecond resolution"] - DateTimeFemtosecond = 29, - #[doc = " Datetime with attosecond resolution"] - DateTimeAttosecond = 30, - #[doc = " Time with hour resolution"] - TimeHour = 31, - #[doc = " Time with minute resolution"] - TimeMinute = 32, - #[doc = " Time with second resolution"] - TimeSecond = 33, - #[doc = " Time with millisecond resolution"] - TimeMillisecond = 34, - #[doc = " Time with microsecond resolution"] - TimeMicrosecond = 35, - #[doc = " Time with nanosecond resolution"] - TimeNanosecond = 36, - #[doc = " Time with picosecond resolution"] - TimePicosecond = 37, - #[doc = " Time with femtosecond resolution"] - TimeFemtosecond = 38, - #[doc = " Time with attosecond resolution"] - TimeAttosecond = 39, - #[doc = " std::byte"] - Blob = 40, - #[doc = " Boolean"] - Boolean = 41, - #[doc = " Geometry data in well-known binary (WKB) format, stored as std::byte"] - GeometryWkb = 42, - #[doc = " Geometry data in well-known text (WKT) format, stored as std::byte"] - GeometryWkt = 43, -} - -impl Datatype { - pub fn size(&self) -> u64 { - let copy = *self; - unsafe { tiledb_datatype_size(copy as tiledb_datatype_t) } - } - - /// TODO: this should not be exposed outside of the tiledb-rs library - pub fn capi_enum(&self) -> tiledb_datatype_t { - *self as tiledb_datatype_t - } - - pub fn from_capi_enum(c_datatype: tiledb_datatype_t) -> Self { - Self::from_u32(c_datatype).unwrap() - } - - pub fn to_string(&self) -> Option { - let copy = *self; - let c_dtype = copy as tiledb_datatype_t; - let mut c_str = std::ptr::null::(); - let res = unsafe { tiledb_datatype_to_str(c_dtype, &mut c_str) }; - if res == TILEDB_OK { - let c_msg = unsafe { std::ffi::CStr::from_ptr(c_str) }; - Some(String::from(c_msg.to_string_lossy())) - } else { - None - } - } - - pub fn from_string(dtype: &str) -> Option { - let c_dtype = - std::ffi::CString::new(dtype).expect("Error creating CString"); - let mut c_ret: u32 = 0; - let res = unsafe { - tiledb_datatype_from_str(c_dtype.as_c_str().as_ptr(), &mut c_ret) - }; - - if res == TILEDB_OK { - Datatype::from_u32(c_ret) - } else { - None - } - } - - pub fn from_u32(dtype: u32) -> Option { - match dtype { - 0 => Some(Datatype::Int32), - 1 => Some(Datatype::Int64), - 2 => Some(Datatype::Float32), - 3 => Some(Datatype::Float64), - 4 => Some(Datatype::Char), - 5 => Some(Datatype::Int8), - 6 => Some(Datatype::UInt8), - 7 => Some(Datatype::Int16), - 8 => Some(Datatype::UInt16), - 9 => Some(Datatype::UInt32), - 10 => Some(Datatype::UInt64), - 11 => Some(Datatype::StringAscii), - 12 => Some(Datatype::StringUtf8), - 13 => Some(Datatype::StringUtf16), - 14 => Some(Datatype::StringUtf32), - 15 => Some(Datatype::StringUcs2), - 16 => Some(Datatype::StringUcs4), - 17 => Some(Datatype::Any), - 18 => Some(Datatype::DateTimeYear), - 19 => Some(Datatype::DateTimeMonth), - 20 => Some(Datatype::DateTimeWeek), - 21 => Some(Datatype::DateTimeDay), - 22 => Some(Datatype::DateTimeHour), - 23 => Some(Datatype::DateTimeMinute), - 24 => Some(Datatype::DateTimeSecond), - 25 => Some(Datatype::DateTimeMillisecond), - 26 => Some(Datatype::DateTimeMicrosecond), - 27 => Some(Datatype::DateTimeNanosecond), - 28 => Some(Datatype::DateTimePicosecond), - 29 => Some(Datatype::DateTimeFemtosecond), - 30 => Some(Datatype::DateTimeAttosecond), - 31 => Some(Datatype::TimeHour), - 32 => Some(Datatype::TimeMinute), - 33 => Some(Datatype::TimeSecond), - 34 => Some(Datatype::TimeMillisecond), - 35 => Some(Datatype::TimeMicrosecond), - 36 => Some(Datatype::TimeNanosecond), - 37 => Some(Datatype::TimePicosecond), - 38 => Some(Datatype::TimeFemtosecond), - 39 => Some(Datatype::TimeAttosecond), - 40 => Some(Datatype::Blob), - 41 => Some(Datatype::Boolean), - 42 => Some(Datatype::GeometryWkb), - 43 => Some(Datatype::GeometryWkt), - _ => None, - } - } - - pub fn is_compatible_type(&self) -> bool { - let tid = TypeId::of::(); - if tid == TypeId::of::() { - matches!(*self, Datatype::Float32) - } else if tid == TypeId::of::() { - matches!(*self, Datatype::Float64) - } else if tid == TypeId::of::() { - matches!(*self, Datatype::Char | Datatype::Int8) - } else if tid == TypeId::of::() { - matches!( - *self, - Datatype::Any - | Datatype::Blob - | Datatype::Boolean - | Datatype::GeometryWkb - | Datatype::GeometryWkt - | Datatype::StringAscii - | Datatype::StringUtf8 - | Datatype::UInt8 - ) - } else if tid == TypeId::of::() { - matches!(*self, Datatype::Int16) - } else if tid == TypeId::of::() { - matches!( - *self, - Datatype::StringUtf16 | Datatype::StringUcs2 | Datatype::UInt16 - ) - } else if tid == TypeId::of::() { - matches!(*self, Datatype::Int32) - } else if tid == TypeId::of::() { - matches!( - *self, - Datatype::StringUtf32 | Datatype::StringUcs4 | Datatype::UInt32 - ) - } else if tid == TypeId::of::() { - matches!( - *self, - Datatype::Int64 - | Datatype::DateTimeYear - | Datatype::DateTimeMonth - | Datatype::DateTimeWeek - | Datatype::DateTimeDay - | Datatype::DateTimeHour - | Datatype::DateTimeMinute - | Datatype::DateTimeSecond - | Datatype::DateTimeMillisecond - | Datatype::DateTimeMicrosecond - | Datatype::DateTimeNanosecond - | Datatype::DateTimePicosecond - | Datatype::DateTimeFemtosecond - | Datatype::DateTimeAttosecond - | Datatype::TimeHour - | Datatype::TimeMinute - | Datatype::TimeSecond - | Datatype::TimeMillisecond - | Datatype::TimeMicrosecond - | Datatype::TimeNanosecond - | Datatype::TimePicosecond - | Datatype::TimeFemtosecond - | Datatype::TimeAttosecond - ) - } else if tid == TypeId::of::() { - matches!(*self, Datatype::UInt64) - } else { - false - } - } - - /// Returns whether this type is an integral type (i.e. integer) - // Keep in sync with sm/enums/datatype.h::datatype_is_integer - pub fn is_integral_type(&self) -> bool { - matches!( - *self, - Datatype::Boolean - | Datatype::Int8 - | Datatype::Int16 - | Datatype::Int32 - | Datatype::Int64 - | Datatype::UInt8 - | Datatype::UInt16 - | Datatype::UInt32 - | Datatype::UInt64 - ) - } - - /// Returns whether this type is a variable-length string type - // Keep in sync with sm/enums/datatype.h::datatype_is_string - pub fn is_string_type(&self) -> bool { - matches!( - *self, - Datatype::StringAscii - | Datatype::StringUtf8 - | Datatype::StringUtf16 - | Datatype::StringUtf32 - | Datatype::StringUcs2 - | Datatype::StringUcs4 - ) - } - - /// Returns whether this type is a DateTime type of any resolution - // Keep in sync with sm/enums/datatype.h::datatype_is_datetime - pub fn is_datetime_type(&self) -> bool { - matches!( - *self, - Datatype::DateTimeYear - | Datatype::DateTimeMonth - | Datatype::DateTimeWeek - | Datatype::DateTimeDay - | Datatype::DateTimeHour - | Datatype::DateTimeMinute - | Datatype::DateTimeSecond - | Datatype::DateTimeMillisecond - | Datatype::DateTimeMicrosecond - | Datatype::DateTimeNanosecond - | Datatype::DateTimePicosecond - | Datatype::DateTimeFemtosecond - | Datatype::DateTimeAttosecond - ) - } - - /// Returns whether this type is a Time type of any resolution - // Keep in sync with sm/enums/datatype.h::datatype_is_time - pub fn is_time_type(&self) -> bool { - matches!( - *self, - Datatype::TimeHour - | Datatype::TimeMinute - | Datatype::TimeSecond - | Datatype::TimeMillisecond - | Datatype::TimeMicrosecond - | Datatype::TimeNanosecond - | Datatype::TimePicosecond - | Datatype::TimeFemtosecond - | Datatype::TimeAttosecond - ) - } - - /// Returns whether this type can be used as a dimension type of a sparse array - pub fn is_allowed_dimension_type_sparse(&self) -> bool { - self.is_integral_type() - || self.is_datetime_type() - || self.is_time_type() - || matches!( - *self, - Datatype::Float32 | Datatype::Float64 | Datatype::StringAscii - ) - } - - /// Returns whether this type can be used as a dimension type of a dense array - pub fn is_allowed_dimension_type_dense(&self) -> bool { - self.is_integral_type() - || self.is_datetime_type() - || self.is_time_type() - } -} - -impl Display for Datatype { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!( - f, - "{}", - match self.to_string() { - Some(s) => s, - None => String::from(""), - } - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn datatype_test() { - for i in 0..256 { - println!("I: {}", i); - if i <= 43 { - let dt = Datatype::from_u32(i as u32) - .expect("Error converting value to Datatype"); - assert_ne!( - format!("{}", dt), - "".to_string() - ); - assert!(check_valid(&dt)); - } else { - assert!(Datatype::from_u32(i as u32).is_none()); - } - } - } - - fn check_valid(dt: &Datatype) -> bool { - let mut count = 0; - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - if dt.is_compatible_type::() { - count += 1; - } - - count == 1 - } -} diff --git a/tiledb/test/src/attribute.rs b/tiledb/test/src/attribute.rs index dccfc258..bb21d960 100644 --- a/tiledb/test/src/attribute.rs +++ b/tiledb/test/src/attribute.rs @@ -1,6 +1,7 @@ use proptest::prelude::*; use tiledb::array::{Attribute, AttributeBuilder}; use tiledb::context::Context; +use tiledb::Result as TileDBResult; pub fn arbitrary_name() -> impl Strategy { proptest::string::string_regex("[a-zA-Z0-9_]*") @@ -11,12 +12,21 @@ pub fn arbitrary_name() -> impl Strategy { ) } -pub fn arbitrary(context: &Context) -> impl Strategy { - (arbitrary_name(), crate::datatype::arbitrary_implemented()).prop_map( +pub fn arbitrary( + context: &Context, +) -> impl Strategy> { + (arbitrary_name(), crate::datatype::arbitrary_implemented()).prop_flat_map( |(name, dt)| { - AttributeBuilder::new(context, name.as_ref(), dt) - .expect("Error building attribute") - .build() + ( + Just(name), + Just(dt), + crate::filter::arbitrary_list_for_datatype(context, dt), + ) + .prop_map(|(name, dt, filters)| { + Ok(AttributeBuilder::new(context, name.as_ref(), dt)? + .filter_list(&filters?)? + .build()) + }) }, ) } @@ -30,7 +40,9 @@ mod tests { fn attribute_arbitrary() { let ctx = Context::new().expect("Error creating context"); - proptest!(|(_ in arbitrary(&ctx))| {}); + proptest!(|(attr in arbitrary(&ctx))| { + attr.expect("Error constructing arbitrary attribute"); + }); } #[test] @@ -38,6 +50,7 @@ mod tests { let ctx = Context::new().expect("Error creating context"); proptest!(|(attr in arbitrary(&ctx))| { + let attr = attr.expect("Error constructing arbitrary attribute"); assert_eq!(attr, attr); }); } diff --git a/tiledb/test/src/filter.rs b/tiledb/test/src/filter.rs new file mode 100644 index 00000000..f656b2ff --- /dev/null +++ b/tiledb/test/src/filter.rs @@ -0,0 +1,263 @@ +use std::collections::VecDeque; + +use proptest::prelude::*; +use proptest::strategy::Just; +use tiledb::context::Context; +use tiledb::filter::*; +use tiledb::filter_list::FilterList; +use tiledb::{Datatype, Result as TileDBResult}; + +use crate::strategy::LifetimeBoundStrategy; + +pub fn arbitrary_bitwidthreduction() -> impl Strategy { + const MIN_WINDOW: u32 = 8; + const MAX_WINDOW: u32 = 1024; + prop_oneof![ + Just(FilterData::BitWidthReduction { max_window: None }), + (MIN_WINDOW..=MAX_WINDOW).prop_map(|max_window| { + FilterData::BitWidthReduction { + max_window: Some(max_window), + } + }) + ] +} + +pub fn arbitrary_compression_reinterpret_datatype( +) -> impl Strategy { + crate::datatype::arbitrary_implemented() +} + +pub fn arbitrary_compression() -> impl Strategy { + const MIN_COMPRESSION_LEVEL: i32 = 1; + const MAX_COMPRESSION_LEVEL: i32 = 9; + ( + prop_oneof![ + Just(CompressionType::Bzip2), + Just(CompressionType::Delta), + Just(CompressionType::Dictionary), + Just(CompressionType::DoubleDelta), + Just(CompressionType::Gzip), + Just(CompressionType::Lz4), + Just(CompressionType::Rle), + Just(CompressionType::Zstd), + ], + MIN_COMPRESSION_LEVEL..=MAX_COMPRESSION_LEVEL, + arbitrary_compression_reinterpret_datatype(), + ) + .prop_map(|(kind, level, reinterpret_datatype)| { + FilterData::Compression(CompressionData { + kind, + level: Some(level), + reinterpret_datatype: Some(reinterpret_datatype), + }) + }) +} + +pub fn arbitrary_positivedelta() -> impl Strategy { + const MIN_WINDOW: u32 = 8; + const MAX_WINDOW: u32 = 1024; + + (MIN_WINDOW..=MAX_WINDOW).prop_map(|max_window| FilterData::PositiveDelta { + max_window: Some(max_window), + }) +} + +pub fn arbitrary_scalefloat() -> impl Strategy { + ( + prop_oneof![ + Just(ScaleFloatByteWidth::I8), + Just(ScaleFloatByteWidth::I16), + Just(ScaleFloatByteWidth::I32), + Just(ScaleFloatByteWidth::I64), + ], + proptest::num::f64::NORMAL, + proptest::num::f64::NORMAL, + ) + .prop_map(|(byte_width, factor, offset)| FilterData::ScaleFloat { + byte_width: Some(byte_width), + factor: Some(factor), + offset: Some(offset), + }) +} + +pub fn arbitrary_webp() -> impl Strategy { + ( + prop_oneof![ + Just(WebPFilterInputFormat::None), + Just(WebPFilterInputFormat::Rgb), + Just(WebPFilterInputFormat::Bgr), + Just(WebPFilterInputFormat::Rgba), + Just(WebPFilterInputFormat::Bgra), + ], + prop_oneof![Just(false), Just(true)], + 0f32..=100f32, + ) + .prop_map(|(input_format, lossless, quality)| FilterData::WebP { + input_format: Some(input_format), + lossless: Some(lossless), + quality: Some(quality), + }) +} + +pub fn arbitrary_data() -> impl Strategy { + prop_oneof![ + Just(FilterData::BitShuffle), + Just(FilterData::ByteShuffle), + arbitrary_bitwidthreduction(), + Just(FilterData::Checksum(ChecksumType::Md5)), + Just(FilterData::Checksum(ChecksumType::Sha256)), + arbitrary_compression(), + arbitrary_positivedelta(), + arbitrary_scalefloat(), + arbitrary_webp(), + Just(FilterData::Xor) + ] +} + +pub fn arbitrary_data_for_datatype( + input_datatype: Datatype, +) -> impl Strategy { + arbitrary_data() + .prop_filter("Filter does not accept input type", move |filter| { + filter.transform_datatype(&input_datatype).is_some() + }) +} + +pub fn arbitrary_for_datatype( + context: &Context, + input_datatype: Datatype, +) -> impl Strategy> { + arbitrary_data_for_datatype(input_datatype) + .prop_map(|filter| Filter::create(context, filter)) +} + +pub fn arbitrary( + context: &Context, +) -> impl Strategy> { + arbitrary_data().prop_map(|filter| Filter::create(context, filter)) +} + +fn arbitrary_pipeline( + start: Datatype, + nfilters: usize, +) -> impl Strategy> { + if nfilters == 0 { + Just(VecDeque::new()).boxed() + } else { + arbitrary_data_for_datatype(start).prop_flat_map(move |filter| { + /* the transform type must be Some per filter in `arbitrary_data` */ + let next = filter.transform_datatype(&start).unwrap(); + arbitrary_pipeline(next, nfilters - 1).bind().prop_map(move |mut filter_vec| { + filter_vec.push_front(filter.clone()); + filter_vec + }) + }).boxed() + } +} + +pub fn arbitrary_list_for_datatype( + context: &Context, + datatype: Datatype, +) -> impl Strategy> { + const MIN_FILTERS: usize = 0; + const MAX_FILTERS: usize = 4; + + (MIN_FILTERS..=MAX_FILTERS).prop_flat_map(move |nfilters| { + arbitrary_pipeline(datatype, nfilters).prop_map(move |filter_vec| { + let mut b = tiledb::filter_list::Builder::new(context)?; + let mut current_dt = datatype; + for filter in filter_vec.iter() { + current_dt = if let Some(next_dt) = + filter.transform_datatype(¤t_dt) + { + next_dt + } else { + return Err(tiledb::error::Error::from(format!( + "Error in filter pipeline construction: {} -> {:?}", + datatype, filter_vec + ))); + } + } + for filter in filter_vec { + b = b.add_filter(Filter::create(context, filter)?)?; + } + Ok(b.build()) + }) + }) +} + +pub fn arbitrary_list( + context: &Context, +) -> impl Strategy> { + crate::datatype::arbitrary() + .prop_flat_map(|dt| arbitrary_list_for_datatype(context, dt)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + /// Test that the arbitrary filter construction always succeeds + fn filter_arbitrary() { + let ctx = Context::new().expect("Error creating context"); + + proptest!(|(filt in arbitrary(&ctx))| { + filt.expect("Error constructing arbitrary filter"); + }); + } + + /// Test that the arbitrary filter construction always succeeds with a supplied datatype + #[test] + fn filter_arbitrary_for_datatype() { + let ctx = Context::new().expect("Error creating context"); + + proptest!(|((dt, filt) in crate::datatype::arbitrary().prop_flat_map(|dt| (Just(dt), arbitrary_for_datatype(&ctx, dt))))| { + let filt = filt.expect("Error constructing arbitrary filter"); + + let filt_data = filt.filter_data().expect("Error reading filter data"); + assert!(filt_data.transform_datatype(&dt).is_some()); + }); + } + + #[test] + /// Test that the arbitrary filter list construction always succeeds + fn filter_list_arbitrary() { + let ctx = Context::new().expect("Error creating context"); + + proptest!(|(fl in arbitrary_list(&ctx))| { + fl.expect("Error constructing arbitrary filter list"); + }); + } + + #[test] + /// Test that the arbitrary filter list construction always succeeds with a supplied datatype + fn filter_list_arbitrary_for_datatype() { + let ctx = Context::new().expect("Error creating context"); + + proptest!(|((dt, fl) in crate::datatype::arbitrary_implemented().prop_flat_map(|dt| (Just(dt), arbitrary_list_for_datatype(&ctx, dt))))| { + let fl = fl.expect("Error constructing arbitrary filter"); + + let mut current_dt = dt; + + let fl = fl.to_vec().expect("Error collecting filters"); + for (fi, f) in fl.iter().enumerate() { + if let Some(next_dt) = f.filter_data().expect("Error reading filter data").transform_datatype(¤t_dt) { + current_dt = next_dt + } else { + panic!("Constructed invalid filter list: {:?}, invalid at position {}", fl, fi) + } + } + }); + } + + #[test] + fn filter_eq_reflexivity() { + let ctx = Context::new().expect("Error creating context"); + + proptest!(|(attr in arbitrary(&ctx))| { + let attr = attr.expect("Error constructing arbitrary filter"); + assert_eq!(attr, attr); + }); + } +} diff --git a/tiledb/test/src/lib.rs b/tiledb/test/src/lib.rs index 5d9d9ef5..945ed6c0 100644 --- a/tiledb/test/src/lib.rs +++ b/tiledb/test/src/lib.rs @@ -6,5 +6,6 @@ pub mod attribute; pub mod datatype; pub mod dimension; pub mod domain; +pub mod filter; pub mod schema; pub mod strategy; diff --git a/tiledb/test/src/schema.rs b/tiledb/test/src/schema.rs index 90b7ca43..f012e307 100644 --- a/tiledb/test/src/schema.rs +++ b/tiledb/test/src/schema.rs @@ -25,7 +25,7 @@ pub fn arbitrary( let mut b = SchemaBuilder::new(context, array_type, domain?)?; for attr in attrs { /* TODO: how to ensure no duplicate names, assuming that matters? */ - b = b.add_attribute(attr)? + b = b.add_attribute(attr?)? } Ok(b.build())