This is an automated email from the ASF dual-hosted git repository.

liurenjie1024 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new f1ea6e1  feat: `StructAccessor.get` returns `Result<Option<Datum>>` 
instead of `Result<Datum>` (#390)
f1ea6e1 is described below

commit f1ea6e137eaabf22ff7d091bfafa0a05739a6516
Author: Scott Donnelly <[email protected]>
AuthorDate: Fri Jun 7 08:25:27 2024 +0100

    feat: `StructAccessor.get` returns `Result<Option<Datum>>` instead of 
`Result<Datum>` (#390)
    
    This is so that the accessor's result can represent null field values.
    
    Fixes: #379
---
 crates/iceberg/src/expr/accessor.rs | 48 +++++++++++++++++++++++++++++++------
 crates/iceberg/src/spec/schema.rs   | 10 ++++----
 crates/iceberg/src/spec/values.rs   |  5 ++++
 3 files changed, 51 insertions(+), 12 deletions(-)

diff --git a/crates/iceberg/src/expr/accessor.rs 
b/crates/iceberg/src/expr/accessor.rs
index f002539..2e2258f 100644
--- a/crates/iceberg/src/expr/accessor.rs
+++ b/crates/iceberg/src/expr/accessor.rs
@@ -15,11 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::spec::{Datum, Literal, PrimitiveType, Struct};
-use crate::{Error, ErrorKind};
 use serde_derive::{Deserialize, Serialize};
 use std::sync::Arc;
 
+use crate::spec::{Datum, Literal, PrimitiveType, Struct};
+use crate::{Error, ErrorKind, Result};
+
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
 pub struct StructAccessor {
     position: usize,
@@ -54,11 +55,13 @@ impl StructAccessor {
         &self.r#type
     }
 
-    pub(crate) fn get<'a>(&'a self, container: &'a Struct) -> 
crate::Result<Datum> {
+    pub(crate) fn get<'a>(&'a self, container: &'a Struct) -> 
Result<Option<Datum>> {
         match &self.inner {
             None => {
-                if let Literal::Primitive(literal) = &container[self.position] 
{
-                    Ok(Datum::new(self.r#type().clone(), literal.clone()))
+                if container.is_null_at_index(self.position) {
+                    Ok(None)
+                } else if let Literal::Primitive(literal) = 
&container[self.position] {
+                    Ok(Some(Datum::new(self.r#type().clone(), 
literal.clone())))
                 } else {
                     Err(Error::new(
                         ErrorKind::Unexpected,
@@ -95,7 +98,19 @@ mod tests {
         let test_struct =
             Struct::from_iter(vec![Some(Literal::bool(false)), 
Some(Literal::bool(true))]);
 
-        assert_eq!(accessor.get(&test_struct).unwrap(), Datum::bool(true));
+        assert_eq!(accessor.get(&test_struct).unwrap(), 
Some(Datum::bool(true)));
+    }
+
+    #[test]
+    fn test_single_level_accessor_null() {
+        let accessor = StructAccessor::new(1, PrimitiveType::Boolean);
+
+        assert_eq!(accessor.r#type(), &PrimitiveType::Boolean);
+        assert_eq!(accessor.position(), 1);
+
+        let test_struct = Struct::from_iter(vec![Some(Literal::bool(false)), 
None]);
+
+        assert_eq!(accessor.get(&test_struct).unwrap(), None);
     }
 
     #[test]
@@ -115,6 +130,25 @@ mod tests {
             Some(Literal::Struct(nested_test_struct)),
         ]);
 
-        assert_eq!(accessor.get(&test_struct).unwrap(), Datum::bool(true));
+        assert_eq!(accessor.get(&test_struct).unwrap(), 
Some(Datum::bool(true)));
+    }
+
+    #[test]
+    fn test_nested_accessor_null() {
+        let nested_accessor = StructAccessor::new(0, PrimitiveType::Boolean);
+        let accessor = StructAccessor::wrap(2, Box::new(nested_accessor));
+
+        assert_eq!(accessor.r#type(), &PrimitiveType::Boolean);
+        //assert_eq!(accessor.position(), 1);
+
+        let nested_test_struct = Struct::from_iter(vec![None, 
Some(Literal::bool(true))]);
+
+        let test_struct = Struct::from_iter(vec![
+            Some(Literal::bool(false)),
+            Some(Literal::bool(false)),
+            Some(Literal::Struct(nested_test_struct)),
+        ]);
+
+        assert_eq!(accessor.get(&test_struct).unwrap(), None);
     }
 }
diff --git a/crates/iceberg/src/spec/schema.rs 
b/crates/iceberg/src/spec/schema.rs
index 4302e3b..acdcae6 100644
--- a/crates/iceberg/src/spec/schema.rs
+++ b/crates/iceberg/src/spec/schema.rs
@@ -1683,7 +1683,7 @@ table {
                 .unwrap()
                 .get(&test_struct)
                 .unwrap(),
-            Datum::string("foo value")
+            Some(Datum::string("foo value"))
         );
         assert_eq!(
             schema
@@ -1691,7 +1691,7 @@ table {
                 .unwrap()
                 .get(&test_struct)
                 .unwrap(),
-            Datum::int(1002)
+            Some(Datum::int(1002))
         );
         assert_eq!(
             schema
@@ -1699,7 +1699,7 @@ table {
                 .unwrap()
                 .get(&test_struct)
                 .unwrap(),
-            Datum::bool(true)
+            Some(Datum::bool(true))
         );
         assert_eq!(
             schema
@@ -1707,7 +1707,7 @@ table {
                 .unwrap()
                 .get(&test_struct)
                 .unwrap(),
-            Datum::string("Testy McTest")
+            Some(Datum::string("Testy McTest"))
         );
         assert_eq!(
             schema
@@ -1715,7 +1715,7 @@ table {
                 .unwrap()
                 .get(&test_struct)
                 .unwrap(),
-            Datum::int(33)
+            Some(Datum::int(33))
         );
     }
 
diff --git a/crates/iceberg/src/spec/values.rs 
b/crates/iceberg/src/spec/values.rs
index 57eecdc..567a847 100644
--- a/crates/iceberg/src/spec/values.rs
+++ b/crates/iceberg/src/spec/values.rs
@@ -1440,6 +1440,11 @@ impl Struct {
             },
         )
     }
+
+    /// returns true if the field at position `index` is null
+    pub fn is_null_at_index(&self, index: usize) -> bool {
+        self.null_bitmap[index]
+    }
 }
 
 impl Index<usize> for Struct {

Reply via email to