From 29be4666bb8a9da4e0d6798d68bd38da7e014b8b Mon Sep 17 00:00:00 2001 From: Matt Topol Date: Fri, 24 May 2024 14:46:55 -0400 Subject: [PATCH] implementing feedback --- literals.go | 23 +++++++++++++---------- literals_test.go | 22 ++++++++++++++++++---- types.go | 6 ++++++ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/literals.go b/literals.go index 882f6e9..9b42e59 100644 --- a/literals.go +++ b/literals.go @@ -467,8 +467,7 @@ func (t TimestampLiteral) To(typ Type) (Literal, error) { case TimestampTzType: return t, nil case DateType: - tm := time.UnixMicro(int64(t)).UTC() - return DateLiteral(tm.Truncate(24*time.Hour).Unix() / int64((time.Hour * 24).Seconds())), nil + return DateLiteral(Timestamp(t).ToDate()), nil } return nil, fmt.Errorf("%w: TimestampLiteral to %s", ErrBadCast, typ) } @@ -538,19 +537,23 @@ func (s StringLiteral) To(typ Type) (Literal, error) { return TimeLiteral(val), nil case TimestampType: - val, err := arrow.TimestampFromString(string(s), arrow.Microsecond) + // requires RFC3339 with no time zone + tm, err := time.Parse("2006-01-02T15:04:05", string(s)) if err != nil { - return nil, fmt.Errorf("%w: casting '%s' to %s - %s", - ErrBadCast, s, typ, err.Error()) + return nil, fmt.Errorf("%w: invalid Timestamp format for casting from string '%s': %s", + ErrBadCast, s, err.Error()) } - return TimestampLiteral(val), nil + + return TimestampLiteral(Timestamp(tm.UTC().UnixMicro())), nil case TimestampTzType: - val, err := arrow.TimestampFromString(string(s), arrow.Microsecond) + // requires RFC3339 format WITH time zone + tm, err := time.Parse(time.RFC3339, string(s)) if err != nil { - return nil, fmt.Errorf("%w: casting '%s' to %s - %s", - ErrBadCast, s, typ, err.Error()) + return nil, fmt.Errorf("%w: invalid TimestampTz format for casting from string '%s': %s", + ErrBadCast, s, err.Error()) } - return TimestampLiteral(val), nil + + return TimestampLiteral(Timestamp(tm.UTC().UnixMicro())), nil case UUIDType: val, err := uuid.Parse(string(s)) if err != nil { diff --git a/literals_test.go b/literals_test.go index cf0b3d6..f7a483b 100644 --- a/literals_test.go +++ b/literals_test.go @@ -358,12 +358,8 @@ func TestStringLiteralConversion(t *testing.T) { iceberg.NewLiteral(iceberg.Date(arrow.Date32FromTime(tm)))}, {iceberg.StringLiteral("14:21:01.919"), iceberg.NewLiteral(iceberg.Time(51661919000))}, - {iceberg.StringLiteral("2017-08-18T14:21:01.919234+00:00"), - iceberg.NewLiteral(iceberg.Timestamp(1503066061919234))}, {iceberg.StringLiteral("2017-08-18T14:21:01.919234"), iceberg.NewLiteral(iceberg.Timestamp(1503066061919234))}, - {iceberg.StringLiteral("2017-08-18T14:21:01.919234-07:00"), - iceberg.NewLiteral(iceberg.Timestamp(1503091261919234))}, {iceberg.StringLiteral(expected.String()), iceberg.NewLiteral(expected)}, {iceberg.StringLiteral("34.560"), iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(34560), Scale: 3})}, @@ -383,6 +379,24 @@ func TestStringLiteralConversion(t *testing.T) { assert.Truef(t, tt.to.Equals(got), "expected: %s, got: %s", tt.to, got) }) } + + lit := iceberg.StringLiteral("2017-08-18T14:21:01.919234-07:00") + casted, err := lit.To(iceberg.PrimitiveTypes.TimestampTz) + require.NoError(t, err) + expectedTimestamp := iceberg.NewLiteral(iceberg.Timestamp(1503091261919234)) + assert.Truef(t, casted.Equals(expectedTimestamp), "expected: %s, got: %s", + expectedTimestamp, casted) + + _, err = lit.To(iceberg.PrimitiveTypes.Timestamp) + require.Error(t, err) + assert.ErrorIs(t, err, iceberg.ErrBadCast) + assert.ErrorContains(t, err, `parsing time "2017-08-18T14:21:01.919234-07:00": extra text: "-07:00"`) + assert.ErrorContains(t, err, "invalid Timestamp format for casting from string") + + _, err = iceberg.StringLiteral("2017-08-18T14:21:01.919234").To(iceberg.PrimitiveTypes.TimestampTz) + require.Error(t, err) + assert.ErrorIs(t, err, iceberg.ErrBadCast) + assert.ErrorContains(t, err, `cannot parse "" as "Z07:00"`) } func TestLiteralIdentityConversions(t *testing.T) { diff --git a/types.go b/types.go index 50c742a..5aabdb4 100644 --- a/types.go +++ b/types.go @@ -23,6 +23,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/apache/arrow/go/v16/arrow/decimal128" "golang.org/x/exp/slices" @@ -535,6 +536,11 @@ func (TimeType) String() string { return "time" } type Timestamp int64 +func (t Timestamp) ToDate() Date { + tm := time.UnixMicro(int64(t)).UTC() + return Date(tm.Truncate(24*time.Hour).Unix() / int64((time.Hour * 24).Seconds())) +} + // TimestampType represents a number of microseconds since the unix epoch // without regard for timezone. type TimestampType struct{}