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)
+ }
+ }
+}