laskoviymishka commented on code in PR #3070:
URL: https://github.com/apache/iceberg-rust/pull/3070#discussion_r4087492616


##########
crates/iceberg/src/spec/schema/mod.rs:
##########
@@ -348,7 +352,21 @@ impl Schema {
     pub fn field_by_name_case_insensitive(&self, field_name: &str) -> 
Option<&NestedFieldRef> {
         self.lowercase_name_to_id
             .get(&field_name.to_lowercase())
-            .and_then(|id| self.field_by_id(*id))
+            .and_then(|id| id.and_then(|id| self.field_by_id(id)))

Review Comment:
   I'd promote `_checked` to `pub` and point this doc comment at it, or at 
minimum document that `None` now means "missing or ambiguous" — because this is 
stable public surface (it's in `public-api.txt`) and the contract just moved 
silently.
   
   Before, a caller always got *some* field back for a name with case-variants; 
now an ambiguous name returns `None`, identical to "field doesn't exist," and 
the only variant that can tell the two apart is `pub(crate)`. So external 
callers both lose the old behavior and can't reach the new one. The doc still 
just says "in a case-insensitive way," which no longer tells the whole story.



##########
crates/iceberg/src/spec/schema/mod.rs:
##########
@@ -973,6 +991,38 @@ table {
         }
     }
 
+    #[test]
+    fn test_schema_field_by_name_case_insensitive_checked() {

Review Comment:
   While we're adding `_checked` coverage: nothing pins the new behavior of the 
*public* `field_by_name_case_insensitive` on a colliding schema. A one-liner 
asserting it returns `None` for `Id` would make the silent contract change 
visible if someone touches this later.



##########
crates/iceberg/src/spec/schema/mod.rs:
##########
@@ -348,7 +352,21 @@ impl Schema {
     pub fn field_by_name_case_insensitive(&self, field_name: &str) -> 
Option<&NestedFieldRef> {
         self.lowercase_name_to_id
             .get(&field_name.to_lowercase())
-            .and_then(|id| self.field_by_id(*id))
+            .and_then(|id| id.and_then(|id| self.field_by_id(id)))
+    }
+
+    pub(crate) fn field_by_name_case_insensitive_checked(
+        &self,
+        field_name: &str,
+    ) -> Result<Option<&NestedFieldRef>> {
+        match self.lowercase_name_to_id.get(&field_name.to_lowercase()) {
+            Some(Some(id)) => Ok(self.field_by_id(*id)),
+            Some(None) => Err(Error::new(
+                ErrorKind::DataInvalid,
+                format!("Field name {field_name} is ambiguous when case 
sensitivity is disabled"),

Review Comment:
   The message can only name the lookup key, not what it collided with, because 
the sentinel drops the IDs back at build time (`*existing = None`). Java's 
reads "id and ID collide," which is a lot more actionable against a wide schema.
   
   If we want that, the map value could hold the colliders (a small `Vec<i32>` 
or a two-ID tuple) instead of `Option<i32>` — the extra alloc only happens on 
the rare collision. If we'd rather keep the sentinel, I'd at least reword: 
"when case sensitivity is disabled" reads a little oddly, something like 
`"multiple fields match {field_name} case-insensitively"` is clearer. wdyt?



##########
crates/iceberg/src/expr/term.rs:
##########
@@ -311,10 +311,10 @@ impl Bind for Reference {
 
     fn bind(&self, schema: SchemaRef, case_sensitive: bool) -> 
crate::Result<Self::Bound> {
         let field = if case_sensitive {
-            schema.field_by_name(&self.name)
+            Ok(schema.field_by_name(&self.name))

Review Comment:
   The `Ok(...)` wrapper on the case-sensitive arm only exists so the trailing 
`}?` typechecks across both arms. Moving the `?` into the else arm reads 
cleaner and makes it obvious only that branch returns early:
   
   ```rust
   let field = if case_sensitive {
       schema.field_by_name(&self.name)
   } else {
       schema.field_by_name_case_insensitive_checked(&self.name)?
   };
   ```



##########
crates/iceberg/src/spec/schema/mod.rs:
##########
@@ -348,7 +352,21 @@ impl Schema {
     pub fn field_by_name_case_insensitive(&self, field_name: &str) -> 
Option<&NestedFieldRef> {
         self.lowercase_name_to_id
             .get(&field_name.to_lowercase())
-            .and_then(|id| self.field_by_id(*id))
+            .and_then(|id| id.and_then(|id| self.field_by_id(id)))
+    }
+
+    pub(crate) fn field_by_name_case_insensitive_checked(

Review Comment:
   `_checked` has no doc comment, and this is the spot to note that the 
per-name check is an intentional Rust-only refinement. Java's index is 
all-or-nothing — any collision in the schema poisons *every* case-insensitive 
lookup, so even unambiguous `data` throws — and PyIceberg silently keeps the 
last collider. Same table + column can therefore succeed here, error in Spark, 
and pick-one in PyIceberg. Ours is arguably the nicest of the three, so I 
wouldn't change it; I'd just document the divergence and spell out what 
"ambiguous" means while adding the doc.



##########
crates/iceberg/src/scan/mod.rs:
##########
@@ -52,13 +52,17 @@ use crate::{Error, ErrorKind, Result};
 pub type ArrowRecordBatchStream = BoxStream<'static, Result<RecordBatch>>;
 
 /// Resolves a column name to its field ID, honouring the scan's case 
sensitivity.
-fn resolve_field_id(schema: &Schema, column_name: &str, case_sensitive: bool) 
-> Option<i32> {
+fn resolve_field_id(
+    schema: &Schema,
+    column_name: &str,
+    case_sensitive: bool,
+) -> Result<Option<i32>> {
     if case_sensitive {
-        schema.field_id_by_name(column_name)
+        Ok(schema.field_id_by_name(column_name))
     } else {
-        schema
-            .field_by_name_case_insensitive(column_name)
-            .map(|field| field.id)
+        Ok(schema
+            .field_by_name_case_insensitive_checked(column_name)?

Review Comment:
   Same shape as `bind` — the `Ok(` wrapping the whole 
`...checked(...)?.map(...)` is a bit hard to parse. A let-binding first reads 
clearer:
   
   ```rust
   let field = schema.field_by_name_case_insensitive_checked(column_name)?;
   Ok(field.map(|f| f.id))
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to