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

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


The following commit(s) were added to refs/heads/main by this push:
     new 273e779  fix(transforms): Add can transform method to transform 
interface (#463)
273e779 is described below

commit 273e77920d73730a90ef50be1fb2491725d0f534
Author: Leon Lin <[email protected]>
AuthorDate: Thu Jun 19 13:16:21 2025 -0700

    fix(transforms): Add can transform method to transform interface (#463)
    
    ### Description
    * While implementing `UpdateSpec`, I noticed that some helper methods
    for transforms are missing. Both Iceberg Python and Java expose a
    `canTransform` method in the `Transform` interface to validate if a
    transform can be applied to a given type. Adding this check would help
    validate inputs before applying spec updates.
    *
    
https://github.com/apache/iceberg-python/blob/main/pyiceberg/transforms.py#L157
    *
    
https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/transforms/Transform.java#L69
    
    ### Testing
    Added `TestCanTransform`
---
 transforms.go      |  69 ++++++++++++++++++++++++++++---
 transforms_test.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 180 insertions(+), 6 deletions(-)

diff --git a/transforms.go b/transforms.go
index 4cd02f5..f59b3f8 100644
--- a/transforms.go
+++ b/transforms.go
@@ -85,6 +85,7 @@ func ParseTransform(s string) (Transform, error) {
 type Transform interface {
        fmt.Stringer
        encoding.TextMarshaler
+       CanTransform(t Type) bool
        ResultType(t Type) Type
        PreservesOrder() bool
        Equals(Transform) bool
@@ -104,6 +105,11 @@ func (t IdentityTransform) MarshalText() ([]byte, error) {
 
 func (IdentityTransform) String() string { return "identity" }
 
+func (IdentityTransform) CanTransform(t Type) bool {
+       _, ok := t.(PrimitiveType)
+
+       return ok
+}
 func (IdentityTransform) ResultType(t Type) Type { return t }
 func (IdentityTransform) PreservesOrder() bool   { return true }
 
@@ -162,6 +168,7 @@ func (t VoidTransform) MarshalText() ([]byte, error) {
 
 func (VoidTransform) String() string { return "void" }
 
+func (VoidTransform) CanTransform(Type) bool { return true }
 func (VoidTransform) ResultType(t Type) Type { return t }
 func (VoidTransform) PreservesOrder() bool   { return false }
 
@@ -195,6 +202,24 @@ func (t BucketTransform) MarshalText() ([]byte, error) {
 
 func (t BucketTransform) String() string { return fmt.Sprintf("bucket[%d]", 
t.NumBuckets) }
 
+func (BucketTransform) CanTransform(t Type) bool {
+       switch t.(type) {
+       case Int32Type,
+               DateType,
+               Int64Type,
+               TimeType,
+               TimestampType,
+               TimestampTzType,
+               DecimalType,
+               StringType,
+               FixedType,
+               BinaryType,
+               UUIDType:
+               return true
+       default:
+               return false
+       }
+}
 func (BucketTransform) ResultType(Type) Type { return PrimitiveTypes.Int32 }
 func (BucketTransform) PreservesOrder() bool { return false }
 
@@ -356,6 +381,18 @@ func (t TruncateTransform) MarshalText() ([]byte, error) {
 
 func (t TruncateTransform) String() string { return 
fmt.Sprintf("truncate[%d]", t.Width) }
 
+func (TruncateTransform) CanTransform(t Type) bool {
+       switch t.(type) {
+       case Int32Type,
+               Int64Type,
+               StringType,
+               BinaryType,
+               DecimalType:
+               return true
+       default:
+               return false
+       }
+}
 func (TruncateTransform) ResultType(t Type) Type { return t }
 func (TruncateTransform) PreservesOrder() bool   { return true }
 func (t TruncateTransform) Equals(other Transform) bool {
@@ -518,6 +555,15 @@ type timeTransform interface {
        Transformer(Type) (func(any) Optional[int32], error)
 }
 
+func canTransformTime(t timeTransform, sourceType Type) bool {
+       switch sourceType.(type) {
+       case DateType, TimestampType, TimestampTzType:
+               return true
+       default:
+               return false
+       }
+}
+
 func projectTimeTransform(t timeTransform, name string, pred BoundPredicate) 
(UnboundPredicate, error) {
        if _, ok := pred.Term().(*BoundTransform); ok {
                return projectTransformPredicate(t, name, pred)
@@ -553,8 +599,9 @@ func (t YearTransform) MarshalText() ([]byte, error) {
 
 func (YearTransform) String() string { return "year" }
 
-func (YearTransform) ResultType(Type) Type { return PrimitiveTypes.Int32 }
-func (YearTransform) PreservesOrder() bool { return true }
+func (t YearTransform) CanTransform(sourceType Type) bool { return 
canTransformTime(t, sourceType) }
+func (YearTransform) ResultType(Type) Type                { return 
PrimitiveTypes.Int32 }
+func (YearTransform) PreservesOrder() bool                { return true }
 
 func (YearTransform) Equals(other Transform) bool {
        _, ok := other.(YearTransform)
@@ -631,8 +678,9 @@ func (t MonthTransform) MarshalText() ([]byte, error) {
 
 func (MonthTransform) String() string { return "month" }
 
-func (MonthTransform) ResultType(Type) Type { return PrimitiveTypes.Int32 }
-func (MonthTransform) PreservesOrder() bool { return true }
+func (t MonthTransform) CanTransform(sourceType Type) bool { return 
canTransformTime(t, sourceType) }
+func (MonthTransform) ResultType(Type) Type                { return 
PrimitiveTypes.Int32 }
+func (MonthTransform) PreservesOrder() bool                { return true }
 
 func (MonthTransform) Equals(other Transform) bool {
        _, ok := other.(MonthTransform)
@@ -720,8 +768,9 @@ func (t DayTransform) MarshalText() ([]byte, error) {
 
 func (DayTransform) String() string { return "day" }
 
-func (DayTransform) ResultType(Type) Type { return PrimitiveTypes.Int32 }
-func (DayTransform) PreservesOrder() bool { return true }
+func (t DayTransform) CanTransform(sourceType Type) bool { return 
canTransformTime(t, sourceType) }
+func (DayTransform) ResultType(Type) Type                { return 
PrimitiveTypes.Int32 }
+func (DayTransform) PreservesOrder() bool                { return true }
 
 func (DayTransform) Equals(other Transform) bool {
        _, ok := other.(DayTransform)
@@ -798,6 +847,14 @@ func (t HourTransform) MarshalText() ([]byte, error) {
 
 func (HourTransform) String() string { return "hour" }
 
+func (t HourTransform) CanTransform(sourceType Type) bool {
+       switch sourceType.(type) {
+       case TimestampType, TimestampTzType:
+               return true
+       default:
+               return false
+       }
+}
 func (HourTransform) ResultType(Type) Type { return PrimitiveTypes.Int32 }
 func (HourTransform) PreservesOrder() bool { return true }
 
diff --git a/transforms_test.go b/transforms_test.go
index 4abebeb..7f92c29 100644
--- a/transforms_test.go
+++ b/transforms_test.go
@@ -237,3 +237,120 @@ func TestManifestPartitionVals(t *testing.T) {
                })
        }
 }
+
+func TestCanTransform(t *testing.T) {
+       tests := []struct {
+               transform  iceberg.Transform
+               allowed    []iceberg.Type
+               notAllowed []iceberg.Type
+       }{
+               {
+                       transform: iceberg.IdentityTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Date,
+                               iceberg.PrimitiveTypes.Time, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.FixedTypeOf(2),
+                       },
+                       notAllowed: []iceberg.Type{
+                               &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.VoidTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Date,
+                               iceberg.PrimitiveTypes.Time, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.FixedTypeOf(2), &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+                       notAllowed: []iceberg.Type{},
+               },
+               {
+                       transform: iceberg.BucketTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Int32, 
iceberg.PrimitiveTypes.Int64, iceberg.PrimitiveTypes.Date,
+                               iceberg.PrimitiveTypes.Time, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.PrimitiveTypes.String, iceberg.FixedTypeOf(2), 
iceberg.PrimitiveTypes.Binary,
+                               iceberg.PrimitiveTypes.UUID,
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Float32, iceberg.PrimitiveTypes.Float64,
+                               &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.TruncateTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Int32, 
iceberg.PrimitiveTypes.Int64, iceberg.PrimitiveTypes.String,
+                               iceberg.PrimitiveTypes.Binary, 
iceberg.DecimalTypeOf(2, 1),
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Float32, iceberg.PrimitiveTypes.Float64,
+                               iceberg.PrimitiveTypes.Date, 
iceberg.PrimitiveTypes.Time, iceberg.PrimitiveTypes.Timestamp,
+                               iceberg.PrimitiveTypes.TimestampTz, 
iceberg.PrimitiveTypes.UUID, iceberg.FixedTypeOf(2),
+                               &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.YearTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Date, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Time,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.FixedTypeOf(2), &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.MonthTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Date, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Time,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.FixedTypeOf(2), &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.DayTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Date, 
iceberg.PrimitiveTypes.Timestamp, iceberg.PrimitiveTypes.TimestampTz,
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Time,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.DecimalTypeOf(2, 1), 
iceberg.FixedTypeOf(2), &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+               {
+                       transform: iceberg.HourTransform{},
+                       allowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Timestamp, 
iceberg.PrimitiveTypes.TimestampTz,
+                       },
+                       notAllowed: []iceberg.Type{
+                               iceberg.PrimitiveTypes.Bool, 
iceberg.PrimitiveTypes.Int32, iceberg.PrimitiveTypes.Int64,
+                               iceberg.PrimitiveTypes.Float32, 
iceberg.PrimitiveTypes.Float64, iceberg.PrimitiveTypes.Time,
+                               iceberg.PrimitiveTypes.String, 
iceberg.PrimitiveTypes.Binary, iceberg.PrimitiveTypes.UUID,
+                               iceberg.PrimitiveTypes.Date, 
iceberg.DecimalTypeOf(2, 1), iceberg.FixedTypeOf(2),
+                               &iceberg.StructType{}, &iceberg.ListType{}, 
&iceberg.MapType{},
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               for _, typ := range tt.allowed {
+                       assert.True(t, tt.transform.CanTransform(typ), "%s: 
expected CanTransform(%T) to be true", tt.transform.String(), typ)
+               }
+               for _, typ := range tt.notAllowed {
+                       assert.False(t, tt.transform.CanTransform(typ), "%s: 
expected CanTransform(%T) to be false", tt.transform.String(), typ)
+               }
+       }
+}

Reply via email to