From 059f9cca90e6aea6a32d371723c5cfd652ed2eb2 Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Fri, 28 Jul 2023 13:19:20 +0300 Subject: [PATCH] tupleconv: add tt `interval` type. Added support for the tarantool `interval` type: - implemented a parser for this format. Currently, the supported format is the order of interval fields from most significant to least significant, as follows: `year,month,week,day,hour,min,sec,nsec,adjust`. - added the `interval` type to the table of tarantool types, and now the parsing of interval is used for parsing `any` and `scalar` types. --- mapper.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++--- mapper_test.go | 21 ++++++++++++++++++++ tt.go | 3 +++ tt_test.go | 44 +++++++++++++++++++++++++++++++++------- tt_types.go | 1 + 5 files changed, 113 insertions(+), 10 deletions(-) diff --git a/mapper.go b/mapper.go index 7bd019e..af86648 100644 --- a/mapper.go +++ b/mapper.go @@ -2,13 +2,15 @@ package tupleconv import ( "encoding/json" + "errors" "fmt" - "github.com/google/uuid" - "github.com/tarantool/go-tarantool/v2/datetime" - "github.com/tarantool/go-tarantool/v2/decimal" "strconv" "strings" "time" + + "github.com/google/uuid" + "github.com/tarantool/go-tarantool/v2/datetime" + "github.com/tarantool/go-tarantool/v2/decimal" ) // Mapper is a mapper from S to T. @@ -30,6 +32,7 @@ var ( _ Mapper[string, any] = (*StringToSliceMapper)(nil) _ Mapper[string, any] = (*StringToNullMapper)(nil) _ Mapper[string, any] = (*IdentityMapper[string])(nil) + _ Mapper[string, any] = (*StringToIntervalMapper)(nil) ) type IdentityMapper[S any] struct{} @@ -257,3 +260,48 @@ func (mapper StringToNullMapper) Map(src string) (any, error) { } return nil, nil } + +// StringToIntervalMapper is a mapper from string to datetime.Interval. +type StringToIntervalMapper struct{} + +// MakeStringToIntervalMapper creates StringToIntervalMapper. +func MakeStringToIntervalMapper() StringToIntervalMapper { + return StringToIntervalMapper{} +} + +// intervalFieldsNumber is the number of fields in datetime.Interval. +const intervalFieldsNumber = 9 + +var errUnexpectedIntervalFmt = errors.New("unexpected interval format") + +// Map is the implementation of Mapper[string] for StringToIntervalMapper. +func (StringToIntervalMapper) Map(src string) (any, error) { + parts := strings.Split(src, ",") + if len(parts) != intervalFieldsNumber { + return nil, errUnexpectedIntervalFmt + } + partsAsInt64 := [intervalFieldsNumber]int64{} + for i, part := range parts { + var err error + if partsAsInt64[i], err = strconv.ParseInt(part, 10, 64); err != nil { + return nil, errUnexpectedIntervalFmt + } + } + adjust := datetime.Adjust(partsAsInt64[8]) + if adjust != datetime.NoneAdjust && + adjust != datetime.ExcessAdjust && adjust != datetime.LastAdjust { + return nil, errUnexpectedIntervalFmt + } + interval := datetime.Interval{ + Year: partsAsInt64[0], + Month: partsAsInt64[1], + Week: partsAsInt64[2], + Day: partsAsInt64[3], + Hour: partsAsInt64[4], + Min: partsAsInt64[5], + Sec: partsAsInt64[6], + Nsec: partsAsInt64[7], + Adjust: adjust, + } + return interval, nil +} diff --git a/mapper_test.go b/mapper_test.go index 87427f6..a8ad9c3 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -223,6 +223,27 @@ func TestParsers(t *testing.T) { {value: "505", isErr: true}, {value: "nil", isErr: true}, }, + tupleconv.MakeStringToIntervalMapper(): { + // Basic. + { + value: "1,2,3,4,5,6,7,8,1", + expected: datetime.Interval{ + Year: 1, Month: 2, Week: 3, Day: 4, Hour: 5, Min: 6, Sec: 7, Nsec: 8, Adjust: 1, + }, + }, + { + value: "-11,-1110,0,0,0,0,0,1,2", + expected: datetime.Interval{ + Year: -11, Month: -1110, Nsec: 1, Adjust: 2, + }, + }, + + // Error. + {value: "1,2,3,4,5,6,7,8,3", isErr: true}, // Invalid adjust. + {value: "1,2,3", isErr: true}, + {value: "1,2,3,4,5,6,7a,8,0", isErr: true}, + {value: "", isErr: true}, + }, } for parser, cases := range tests { diff --git a/tt.go b/tt.go index 87cff46..52b65f3 100644 --- a/tt.go +++ b/tt.go @@ -60,6 +60,7 @@ func makeStringToTTMappers(opts StringToTTMapperOpts) map[TypeName]Mapper[string MakeStringToBoolMapper(), MakeStringToDateTimeMapper(), MakeStringToUUIDMapper(), + MakeStringToIntervalMapper(), MakeIdentityMapper[string](), // Always success. }), @@ -72,10 +73,12 @@ func makeStringToTTMappers(opts StringToTTMapperOpts) map[TypeName]Mapper[string MakeStringToBoolMapper(), MakeStringToDateTimeMapper(), MakeStringToUUIDMapper(), + MakeStringToIntervalMapper(), MakeIdentityMapper[string](), // Always success. }), TypeNullable: MakeStringToNullMapper(opts.NullValue), + TypeInterval: MakeStringToIntervalMapper(), } return result diff --git a/tt_test.go b/tt_test.go index 995e604..a800c36 100644 --- a/tt_test.go +++ b/tt_test.go @@ -1,6 +1,11 @@ package tupleconv_test import ( + "math/big" + "reflect" + "testing" + "time" + "github.com/google/uuid" dec "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" @@ -9,10 +14,6 @@ import ( "github.com/tarantool/go-tarantool/v2/datetime" "github.com/tarantool/go-tarantool/v2/decimal" "github.com/tarantool/go-tupleconv" - "math/big" - "reflect" - "testing" - "time" ) type ttTestCase struct { @@ -351,6 +352,18 @@ func TestTTBasic(t *testing.T) { {value: "09b56913-11f0-4fa4-b5d0-901b5efa532a", expected: someUUID}, {value: "2020-08-22T11:27:43.123456789-02:00", expected: dateTime1}, {value: "1`e2", expected: "1`e2"}, + { + value: "1,2,3,4,5,6,7,8,1", + expected: datetime.Interval{ + Year: 1, Month: 2, Week: 3, Day: 4, Hour: 5, Min: 6, Sec: 7, Nsec: 8, Adjust: 1, + }, + }, + { + value: "-11,-1110,0,0,0,0,0,1,2", + expected: datetime.Interval{ + Year: -11, Month: -1110, Nsec: 1, Adjust: 2, + }, + }, }, tupleconv.TypeScalar: { @@ -377,14 +390,31 @@ func TestTTBasic(t *testing.T) { -9223372036854775808)), 0)}, {value: "1.447e+44", expected: dec.NewFromBigInt(big.NewInt(1447), 41)}, + }, + + tupleconv.TypeInterval: { + // Basic. + { + value: "1,2,3,4,5,6,7,8,1", + expected: datetime.Interval{ + Year: 1, Month: 2, Week: 3, Day: 4, Hour: 5, Min: 6, Sec: 7, Nsec: 8, Adjust: 1, + }, + }, + { + value: "-11,-1110,0,0,0,0,0,1,2", + expected: datetime.Interval{ + Year: -11, Month: -1110, Nsec: 1, Adjust: 2, + }, + }, // Nullable. {value: "", isNullable: true, expected: nil}, // Error. - {value: "abacaba", isErr: true}, - {value: "-1/0", isErr: true}, - {value: "1**5", isErr: true}, + {value: "1,2,3,4,5,6,7,8,3", isErr: true}, // Invalid adjust. + {value: "1,2,3", isErr: true}, + {value: "1,2,3,4,5,6,7a,8,0", isErr: true}, + {value: "", isErr: true}, }, } diff --git a/tt_types.go b/tt_types.go index 0ad9ce8..26b8611 100644 --- a/tt_types.go +++ b/tt_types.go @@ -37,4 +37,5 @@ const ( TypeScalar TypeName = "scalar" TypeAny TypeName = "any" TypeNullable TypeName = "nullable" + TypeInterval TypeName = "interval" )