You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@iceberg.apache.org by "ZENOTME (via GitHub)" <gi...@apache.org> on 2025/02/06 12:26:57 UTC
[PR] feat: support strict projection [iceberg-rust]
ZENOTME opened a new pull request, #946:
URL: https://github.com/apache/iceberg-rust/pull/946
This PR is part of #735. Most of code is test migrate from: https://github.com/apache/iceberg-python/blob/main/tests/test_transforms.py#L997
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "Fokko (via GitHub)" <gi...@apache.org>.
Fokko commented on PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#issuecomment-2806623563
Thanks for working on this @ZENOTME This is great!
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "liurenjie1024 (via GitHub)" <gi...@apache.org>.
liurenjie1024 merged PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "ZENOTME (via GitHub)" <gi...@apache.org>.
ZENOTME commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1947484109
##########
crates/iceberg/src/spec/transform.rs:
##########
@@ -296,6 +297,165 @@ impl Transform {
}
}
+ /// Strictly projects a given predicate according to the transformation
+ /// specified by the `Transform` instance.
+ ///
+ /// This method ensures that the projected predicate is strictly aligned
+ /// with the transformation logic, providing a more precise filtering
+ /// mechanism for transformed data.
+ ///
+ /// # Example
+ /// Suppose, we have row filter `a = 10`, and a partition spec
+ /// `bucket(a, 37) as bs`, if one row matches `a = 10`, then its partition
+ /// value should match `bucket(10, 37) as bs`, and we project `a = 10` to
+ /// `bs = bucket(10, 37)`
+ pub fn strict_project(
+ &self,
+ name: &str,
+ predicate: &BoundPredicate,
+ ) -> Result<Option<Predicate>> {
+ let func = create_transform_function(self)?;
+
+ match self {
+ Transform::Identity => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => Ok(Some(Predicate::Binary(BinaryExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literal().to_owned(),
+ )))),
+ BoundPredicate::Set(expr) => Ok(Some(Predicate::Set(SetExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literals().to_owned(),
+ )))),
+ _ => Ok(None),
+ },
+ Transform::Bucket(_) => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => {
+ self.project_binary_expr(name, PredicateOperator::NotEq, expr, &func)
+ }
+ BoundPredicate::Set(expr) => {
+ self.project_set_expr(expr, PredicateOperator::NotIn, name, &func)
+ }
+ _ => Ok(None),
+ },
+ Transform::Truncate(width) => {
+ match predicate {
+ BoundPredicate::Unary(expr) => {
+ if matches!(
+ expr.term().field().field_type.as_primitive_type(),
+ Some(&PrimitiveType::Int)
+ | Some(&PrimitiveType::Long)
+ | Some(&PrimitiveType::Decimal { .. })
+ ) {
+ Self::project_unary(expr.op(), name)
+ } else {
+ // #TODO: Why other type is not supported?
Review Comment:
It's a question actually. I refer the implementation from: https://github.com/apache/iceberg-python/blob/0aecc0365b08617deee97d8a88c16d5e3aa0724e/pyiceberg/transforms.py#L822. But I'm not sure why it can't apply to other type like string, bytes? cc @Fokko
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "ZENOTME (via GitHub)" <gi...@apache.org>.
ZENOTME commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1947490272
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
Seems this is a compile-time invariant.
https://github.com/apache/iceberg-python/blob/0aecc0365b08617deee97d8a88c16d5e3aa0724e/pyiceberg/expressions/visitors.py#L810
We should call `rewrite_not` in the project function to ensure this invariant at compile time. It also needs to be done at InclusiveProjection.
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "Fokko (via GitHub)" <gi...@apache.org>.
Fokko commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1983923593
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
Yes, it should not happen since all the expressions should be rewritten by the time it reaches this visitor. We just have to make sure that we don't silently ignore this and end up with data issues.
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "liurenjie1024 (via GitHub)" <gi...@apache.org>.
liurenjie1024 commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r2042168911
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ unreachable!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
```suggestion
unreachable!("StrictProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
```
nit: I prefer to throw `Unexpected` error here.
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "sdd (via GitHub)" <gi...@apache.org>.
sdd commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1983907055
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
I always thought it would be nice to be able to capture this invariant in the type system somehow but it is probably more work than it is worth and adds more complexity than we'd gain in value :-)
Personally I'd have thought the combination of a doc comment on the struct and the `unreachable!` in the not-handler should be sufficient for anyone who is doing any reasonable level of testing to be able to deal with this during their development process. We can always revisit it if it ever does cause problems in the future rather than spending time on it now.
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "Xuanwo (via GitHub)" <gi...@apache.org>.
Xuanwo commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1947461987
##########
crates/iceberg/src/spec/transform.rs:
##########
@@ -296,6 +297,165 @@ impl Transform {
}
}
+ /// Strictly projects a given predicate according to the transformation
+ /// specified by the `Transform` instance.
+ ///
+ /// This method ensures that the projected predicate is strictly aligned
+ /// with the transformation logic, providing a more precise filtering
+ /// mechanism for transformed data.
+ ///
+ /// # Example
+ /// Suppose, we have row filter `a = 10`, and a partition spec
+ /// `bucket(a, 37) as bs`, if one row matches `a = 10`, then its partition
+ /// value should match `bucket(10, 37) as bs`, and we project `a = 10` to
+ /// `bs = bucket(10, 37)`
+ pub fn strict_project(
+ &self,
+ name: &str,
+ predicate: &BoundPredicate,
+ ) -> Result<Option<Predicate>> {
+ let func = create_transform_function(self)?;
+
+ match self {
+ Transform::Identity => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => Ok(Some(Predicate::Binary(BinaryExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literal().to_owned(),
+ )))),
+ BoundPredicate::Set(expr) => Ok(Some(Predicate::Set(SetExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literals().to_owned(),
+ )))),
+ _ => Ok(None),
+ },
+ Transform::Bucket(_) => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => {
+ self.project_binary_expr(name, PredicateOperator::NotEq, expr, &func)
+ }
+ BoundPredicate::Set(expr) => {
+ self.project_set_expr(expr, PredicateOperator::NotIn, name, &func)
+ }
+ _ => Ok(None),
+ },
+ Transform::Truncate(width) => {
+ match predicate {
+ BoundPredicate::Unary(expr) => {
+ if matches!(
+ expr.term().field().field_type.as_primitive_type(),
+ Some(&PrimitiveType::Int)
+ | Some(&PrimitiveType::Long)
+ | Some(&PrimitiveType::Decimal { .. })
+ ) {
+ Self::project_unary(expr.op(), name)
+ } else {
+ // #TODO: Why other type is not supported?
Review Comment:
Would you like to add a new issue for this?
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
How about returning errors instead of `panic`?
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "ZENOTME (via GitHub)" <gi...@apache.org>.
ZENOTME commented on PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#issuecomment-2639722083
cc @Fokko @liurenjie1024 @Xuanwo @sdd
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "ZENOTME (via GitHub)" <gi...@apache.org>.
ZENOTME commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1948036791
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
I think there is a discussion about this in: https://github.com/apache/iceberg-rust/pull/335/files#r1571889478 and it explain why we don't call `rewrite_not` inside project function. But this design may cause some problems because in the future the InclusiveProjector may not only used in table scan and the new development may not realized this point. I think at least we should add comment for project to declare that the predicate must not contains `not`. And we can provide two way:
```
fn project_predicate_without_not() // project predicate without not
fn project() // rewrite the predicate before project
```
cc @sdd @Fokko @marvinlanhenke
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "Xuanwo (via GitHub)" <gi...@apache.org>.
Xuanwo commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1947494581
##########
crates/iceberg/src/expr/visitors/strict_projection.rs:
##########
@@ -0,0 +1,3231 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::collections::HashMap;
+
+use fnv::FnvHashSet;
+
+use crate::expr::visitors::bound_predicate_visitor::{visit, BoundPredicateVisitor};
+use crate::expr::{BoundPredicate, BoundReference, Predicate};
+use crate::spec::{Datum, PartitionField, PartitionSpecRef};
+use crate::Error;
+
+// # TODO
+// Remove this after delete support
+#[allow(dead_code)]
+pub(crate) struct StrictProjection {
+ partition_spec: PartitionSpecRef,
+ cached_parts: HashMap<i32, Vec<PartitionField>>,
+}
+
+#[allow(dead_code)]
+impl StrictProjection {
+ pub(crate) fn new(partition_spec: PartitionSpecRef) -> Self {
+ Self {
+ partition_spec,
+ cached_parts: HashMap::new(),
+ }
+ }
+
+ fn get_parts_for_field_id(&mut self, field_id: i32) -> &Vec<PartitionField> {
+ if let std::collections::hash_map::Entry::Vacant(e) = self.cached_parts.entry(field_id) {
+ let mut parts: Vec<PartitionField> = vec![];
+ for partition_spec_field in self.partition_spec.fields() {
+ if partition_spec_field.source_id == field_id {
+ parts.push(partition_spec_field.clone())
+ }
+ }
+
+ e.insert(parts);
+ }
+
+ &self.cached_parts[&field_id]
+ }
+
+ pub(crate) fn strict_project(
+ &mut self,
+ predicate: &BoundPredicate,
+ ) -> crate::Result<Predicate> {
+ visit(self, predicate)
+ }
+
+ fn get_parts(
+ &mut self,
+ reference: &BoundReference,
+ predicate: &BoundPredicate,
+ ) -> Result<Predicate, Error> {
+ let field_id = reference.field().id;
+
+ // This could be made a bit neater if `try_reduce` ever becomes stable
+ self.get_parts_for_field_id(field_id).iter().try_fold(
+ Predicate::AlwaysFalse,
+ |res, part| {
+ // consider (ts > 2019-01-01T01:00:00) with day(ts) and hour(ts)
+ // projections: d >= 2019-01-02 and h >= 2019-01-01-02 (note the inclusive bounds).
+ // any timestamp where either projection predicate is true must match the original
+ // predicate. For example, ts = 2019-01-01T03:00:00 matches the hour projection but not
+ // the day, but does match the original predicate.
+ Ok(
+ if let Some(pred_for_part) =
+ part.transform.strict_project(&part.name, predicate)?
+ {
+ if res == Predicate::AlwaysFalse {
+ pred_for_part
+ } else {
+ res.or(pred_for_part)
+ }
+ } else {
+ res
+ },
+ )
+ },
+ )
+ }
+}
+
+impl BoundPredicateVisitor for StrictProjection {
+ type T = Predicate;
+
+ fn always_true(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysTrue)
+ }
+
+ fn always_false(&mut self) -> crate::Result<Self::T> {
+ Ok(Predicate::AlwaysFalse)
+ }
+
+ fn and(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.and(rhs))
+ }
+
+ fn or(&mut self, lhs: Self::T, rhs: Self::T) -> crate::Result<Self::T> {
+ Ok(lhs.or(rhs))
+ }
+
+ fn not(&mut self, _inner: Self::T) -> crate::Result<Self::T> {
+ panic!("InclusiveProjection should not be performed against Predicates that contain a Not operator. Ensure that \"Rewrite Not\" gets applied to the originating Predicate before binding it.")
Review Comment:
So it's better to use `unreachable!()` here?
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org
Re: [PR] feat: support strict projection [iceberg-rust]
Posted by "Fokko (via GitHub)" <gi...@apache.org>.
Fokko commented on code in PR #946:
URL: https://github.com/apache/iceberg-rust/pull/946#discussion_r1984748281
##########
crates/iceberg/src/spec/transform.rs:
##########
@@ -296,6 +297,165 @@ impl Transform {
}
}
+ /// Strictly projects a given predicate according to the transformation
+ /// specified by the `Transform` instance.
+ ///
+ /// This method ensures that the projected predicate is strictly aligned
+ /// with the transformation logic, providing a more precise filtering
+ /// mechanism for transformed data.
+ ///
+ /// # Example
+ /// Suppose, we have row filter `a = 10`, and a partition spec
+ /// `bucket(a, 37) as bs`, if one row matches `a = 10`, then its partition
+ /// value should match `bucket(10, 37) as bs`, and we project `a = 10` to
+ /// `bs = bucket(10, 37)`
+ pub fn strict_project(
+ &self,
+ name: &str,
+ predicate: &BoundPredicate,
+ ) -> Result<Option<Predicate>> {
+ let func = create_transform_function(self)?;
+
+ match self {
+ Transform::Identity => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => Ok(Some(Predicate::Binary(BinaryExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literal().to_owned(),
+ )))),
+ BoundPredicate::Set(expr) => Ok(Some(Predicate::Set(SetExpression::new(
+ expr.op(),
+ Reference::new(name),
+ expr.literals().to_owned(),
+ )))),
+ _ => Ok(None),
+ },
+ Transform::Bucket(_) => match predicate {
+ BoundPredicate::Unary(expr) => Self::project_unary(expr.op(), name),
+ BoundPredicate::Binary(expr) => {
+ self.project_binary_expr(name, PredicateOperator::NotEq, expr, &func)
+ }
+ BoundPredicate::Set(expr) => {
+ self.project_set_expr(expr, PredicateOperator::NotIn, name, &func)
+ }
+ _ => Ok(None),
+ },
+ Transform::Truncate(width) => {
+ match predicate {
+ BoundPredicate::Unary(expr) => {
+ if matches!(
+ expr.term().field().field_type.as_primitive_type(),
+ Some(&PrimitiveType::Int)
+ | Some(&PrimitiveType::Long)
+ | Some(&PrimitiveType::Decimal { .. })
+ ) {
+ Self::project_unary(expr.op(), name)
+ } else {
+ // #TODO: Why other type is not supported?
Review Comment:
Not sure, looking at Java, that should be fine:
- String: https://github.com/apache/iceberg/blob/19330fa19f833f481063704c1e0122e068289259/api/src/main/java/org/apache/iceberg/transforms/Truncate.java#L364-L365
- Binary: https://github.com/apache/iceberg/blob/19330fa19f833f481063704c1e0122e068289259/api/src/main/java/org/apache/iceberg/transforms/Truncate.java#L445-L446
--
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: issues-unsubscribe@iceberg.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@iceberg.apache.org
For additional commands, e-mail: issues-help@iceberg.apache.org