diff --git a/doc.go b/doc.go index d7711a1b7..9354278a1 100644 --- a/doc.go +++ b/doc.go @@ -207,10 +207,27 @@ The names in parantheses correspond to SQL types: - float32 (real) - float64 (double) - types.Decimal (decimal) + - types.LocalDate (date) + - types.LocalTime (time) + - types.LocalDateTime (timestamp) + - types.OffsetDateTime (timestamp with time zone) - time.Time (date) Detected by checking: hour == minute == second == nanoseconds = 0 - time.Time (time) Detected by checking: year == 0, month == day == 1 - - time.Time (timestamp) Detected by checking: hour == minute == second == nanoseconds = 0, timezone == time.Local - - time.Time (timestamp with time zone) Detected by checking: hour == minute == second == nanoseconds = 0, timezone != time.Local + - time.Time (timestamp) Detected by checking: not time, timezone == time.Local + - time.Time (timestamp with time zone) Detected by checking: not time, timezone != time.Local + - serialization.JSON (json) + +Using Date/Time + +time.Time values are automatically serialized to the correct type. + +In order to force using a specific date/time type, create a time.Time value and cast it to the target type: + + t := time.Now() + dateValue := types.LocalDate(t) + timeValue := types.LocalTime(t) + dateTimeValue := types.LocalDateTime(t) + dateTimeWithTimezoneValue := types.OffsetDateTime(t) Management Center Integration diff --git a/internal/proto/codec/builtin.go b/internal/proto/codec/builtin.go index c42effa70..7d61fea1b 100644 --- a/internal/proto/codec/builtin.go +++ b/internal/proto/codec/builtin.go @@ -460,31 +460,31 @@ func (fixSizedTypesCodec) DecodeDouble(buffer []byte, offset int32) float64 { return math.Float64frombits(binary.LittleEndian.Uint64(buffer[offset:])) } -func (fixSizedTypesCodec) DecodeLocalDate(buffer []byte, offset int32) time.Time { +func (fixSizedTypesCodec) DecodeLocalDate(buffer []byte, offset int32) types.LocalDate { y, m, d := decodeLocalDate(buffer, offset) - return time.Date(y, m, d, 0, 0, 0, 0, time.Local) + return types.LocalDate(time.Date(y, m, d, 0, 0, 0, 0, time.Local)) } -func (fixSizedTypesCodec) DecodeLocalTime(buffer []byte, offset int32) time.Time { +func (fixSizedTypesCodec) DecodeLocalTime(buffer []byte, offset int32) types.LocalTime { h, m, s, nanos := decodeLocalTime(buffer, offset) - return time.Date(0, 1, 1, h, m, s, nanos, time.Local) + return types.LocalTime(time.Date(0, 1, 1, h, m, s, nanos, time.Local)) } -func (fixSizedTypesCodec) DecodeLocalDateTime(buffer []byte, offset int32) time.Time { +func (fixSizedTypesCodec) DecodeLocalDateTime(buffer []byte, offset int32) types.LocalDateTime { y, m, d := decodeLocalDate(buffer, offset) offset += proto.LocalDateSizeInBytes h, mn, s, nanos := decodeLocalTime(buffer, offset) - return time.Date(y, m, d, h, mn, s, nanos, time.Local) + return types.LocalDateTime(time.Date(y, m, d, h, mn, s, nanos, time.Local)) } -func (fixSizedTypesCodec) DecodeDateTimeWithTimeZone(buffer []byte, offset int32) time.Time { +func (fixSizedTypesCodec) DecodeDateTimeWithTimeZone(buffer []byte, offset int32) types.OffsetDateTime { y, m, d := decodeLocalDate(buffer, offset) offset += proto.LocalDateSizeInBytes h, mn, s, nanos := decodeLocalTime(buffer, offset) offset += proto.LocalTimeSizeInBytes offsetSecs := int(FixSizedTypesCodec.DecodeInt(buffer, offset)) tz := time.FixedZone("", offsetSecs) - return time.Date(y, m, d, h, mn, s, nanos, tz) + return types.OffsetDateTime(time.Date(y, m, d, h, mn, s, nanos, tz)) } func (fixSizedTypesCodec) EncodeUUID(buffer []byte, offset int32, uuid types.UUID) { diff --git a/internal/serialization/class_definition_writer.go b/internal/serialization/class_definition_writer.go index 6f31d5044..d4995d830 100644 --- a/internal/serialization/class_definition_writer.go +++ b/internal/serialization/class_definition_writer.go @@ -17,8 +17,6 @@ package serialization import ( - "time" - ihzerrors "github.com/hazelcast/hazelcast-go-client/internal/hzerrors" "github.com/hazelcast/hazelcast-go-client/serialization" "github.com/hazelcast/hazelcast-go-client/types" @@ -127,35 +125,35 @@ func (cdw *ClassDefinitionWriter) WriteStringArray(fieldName string, value []str must(cdw.classDefinition.AddStringArrayField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteDate(fieldName string, value *time.Time) { +func (cdw *ClassDefinitionWriter) WriteDate(fieldName string, value *types.LocalDate) { must(cdw.classDefinition.AddDateField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTime(fieldName string, value *time.Time) { +func (cdw *ClassDefinitionWriter) WriteTime(fieldName string, value *types.LocalTime) { must(cdw.classDefinition.AddTimeField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTimestamp(fieldName string, value *time.Time) { +func (cdw *ClassDefinitionWriter) WriteTimestamp(fieldName string, value *types.LocalDateTime) { must(cdw.classDefinition.AddTimestampField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTimestampWithTimezone(fieldName string, value *time.Time) { +func (cdw *ClassDefinitionWriter) WriteTimestampWithTimezone(fieldName string, value *types.OffsetDateTime) { must(cdw.classDefinition.AddTimestampWithTimezoneField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteDateArray(fieldName string, value []time.Time) { +func (cdw *ClassDefinitionWriter) WriteDateArray(fieldName string, value []types.LocalDate) { must(cdw.classDefinition.AddDateArrayField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTimeArray(fieldName string, value []time.Time) { +func (cdw *ClassDefinitionWriter) WriteTimeArray(fieldName string, value []types.LocalTime) { must(cdw.classDefinition.AddTimeArrayField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTimestampArray(fieldName string, value []time.Time) { +func (cdw *ClassDefinitionWriter) WriteTimestampArray(fieldName string, value []types.LocalDateTime) { must(cdw.classDefinition.AddTimestampArrayField(fieldName)) } -func (cdw *ClassDefinitionWriter) WriteTimestampWithTimezoneArray(fieldName string, value []time.Time) { +func (cdw *ClassDefinitionWriter) WriteTimestampWithTimezoneArray(fieldName string, value []types.OffsetDateTime) { must(cdw.classDefinition.AddTimestampWithTimezoneArrayField(fieldName)) } diff --git a/internal/serialization/default_portable_reader.go b/internal/serialization/default_portable_reader.go index 5529a47ee..089980839 100644 --- a/internal/serialization/default_portable_reader.go +++ b/internal/serialization/default_portable_reader.go @@ -19,6 +19,7 @@ package serialization import ( "fmt" "time" + "unsafe" ihzerrors "github.com/hazelcast/hazelcast-go-client/internal/hzerrors" "github.com/hazelcast/hazelcast-go-client/serialization" @@ -94,22 +95,22 @@ func TypeByID(fieldType serialization.FieldDefinitionType) string { return "types.Decimal" case serialization.TypeDecimalArray: return "[]types.Decimal" - case serialization.TypeTime: - return "time.Time (time)" - case serialization.TypeTimeArray: - return "[]time.Time (time)" case serialization.TypeDate: - return "time.Time (date)" + return "types.LocalDate" case serialization.TypeDateArray: - return "[]time.Time (date)" + return "[]types.LocalDate" + case serialization.TypeTime: + return "types.LocalTime" + case serialization.TypeTimeArray: + return "[]types.LocalTime" case serialization.TypeTimestamp: - return "time.Time (timestamp)" + return "types.LocalDateTime" case serialization.TypeTimestampArray: - return "[]time.Time (timestamp)" + return "[]types.LocalDateTime" case serialization.TypeTimestampWithTimezone: - return "time.Time (timestamp with timezone)" + return "types.OffsetDateTime" case serialization.TypeTimestampWithTimezoneArray: - return "[]time.Time (timestamp with timezone)" + return "[]types.OffsetDateTime" } return "UNKNOWN" } @@ -346,66 +347,70 @@ func (pr *DefaultPortableReader) GetRawDataInput() serialization.DataInput { return pr.input } -func (pr *DefaultPortableReader) ReadDate(fieldName string) (t *time.Time) { +func (pr *DefaultPortableReader) ReadDate(fieldName string) (t *types.LocalDate) { pr.readNullable(fieldName, serialization.TypeDate, func() { v := ReadPortableDate(pr.input) - t = &v + t = (*types.LocalDate)(&v) }) return } -func (pr *DefaultPortableReader) ReadTime(fieldName string) (t *time.Time) { +func (pr *DefaultPortableReader) ReadTime(fieldName string) (t *types.LocalTime) { pr.readNullable(fieldName, serialization.TypeTime, func() { v := ReadPortableTime(pr.input) - t = &v + t = (*types.LocalTime)(&v) }) return } -func (pr *DefaultPortableReader) ReadTimestamp(fieldName string) (t *time.Time) { +func (pr *DefaultPortableReader) ReadTimestamp(fieldName string) (t *types.LocalDateTime) { pr.readNullable(fieldName, serialization.TypeTimestamp, func() { v := ReadPortableTimestamp(pr.input) - t = &v + t = (*types.LocalDateTime)(&v) }) return } -func (pr *DefaultPortableReader) ReadTimestampWithTimezone(fieldName string) (t *time.Time) { +func (pr *DefaultPortableReader) ReadTimestampWithTimezone(fieldName string) (t *types.OffsetDateTime) { pr.readNullable(fieldName, serialization.TypeTimestampWithTimezone, func() { v := ReadPortableTimestampWithTimezone(pr.input) - t = &v + t = (*types.OffsetDateTime)(&v) }) return } -func (pr *DefaultPortableReader) ReadDateArray(fieldName string) (t []time.Time) { +func (pr *DefaultPortableReader) ReadDateArray(fieldName string) (t []types.LocalDate) { pos := pr.positionByField(fieldName, serialization.TypeDateArray) pr.runAtPosition(pos, func() { - t = readArrayOfTime(pr.input, ReadPortableDate) + v := readArrayOfTime(pr.input, ReadPortableDate) + t = *(*[]types.LocalDate)(unsafe.Pointer(&v)) }) return } -func (pr *DefaultPortableReader) ReadTimeArray(fieldName string) (t []time.Time) { +func (pr *DefaultPortableReader) ReadTimeArray(fieldName string) (t []types.LocalTime) { pos := pr.positionByField(fieldName, serialization.TypeTimeArray) pr.runAtPosition(pos, func() { - t = readArrayOfTime(pr.input, ReadPortableTime) + v := readArrayOfTime(pr.input, ReadPortableTime) + t = *(*[]types.LocalTime)(unsafe.Pointer(&v)) }) return } -func (pr *DefaultPortableReader) ReadTimestampArray(fieldName string) (t []time.Time) { +func (pr *DefaultPortableReader) ReadTimestampArray(fieldName string) (t []types.LocalDateTime) { pos := pr.positionByField(fieldName, serialization.TypeTimestampArray) pr.runAtPosition(pos, func() { - t = readArrayOfTime(pr.input, ReadPortableTimestamp) + v := readArrayOfTime(pr.input, ReadPortableTimestamp) + t = *(*[]types.LocalDateTime)(unsafe.Pointer(&v)) }) return } -func (pr *DefaultPortableReader) ReadTimestampWithTimezoneArray(fieldName string) (t []time.Time) { +func (pr *DefaultPortableReader) ReadTimestampWithTimezoneArray(fieldName string) (t []types.OffsetDateTime) { pos := pr.positionByField(fieldName, serialization.TypeTimestampWithTimezoneArray) pr.runAtPosition(pos, func() { - t = readArrayOfTime(pr.input, ReadPortableTimestampWithTimezone) + v := readArrayOfTime(pr.input, ReadPortableTimestampWithTimezone) + t = *(*[]types.OffsetDateTime)(unsafe.Pointer(&v)) }) return } diff --git a/internal/serialization/default_portable_writer.go b/internal/serialization/default_portable_writer.go index 7b96d4c4e..328ed0bbe 100644 --- a/internal/serialization/default_portable_writer.go +++ b/internal/serialization/default_portable_writer.go @@ -19,6 +19,7 @@ package serialization import ( "fmt" "time" + "unsafe" ihzerrors "github.com/hazelcast/hazelcast-go-client/internal/hzerrors" "github.com/hazelcast/hazelcast-go-client/serialization" @@ -184,48 +185,56 @@ func (pw *DefaultPortableWriter) WritePortableArray(fieldName string, portableAr } } -func (pw *DefaultPortableWriter) WriteDate(fieldName string, t *time.Time) { +func (pw *DefaultPortableWriter) WriteDate(fieldName string, t *types.LocalDate) { pw.writeNullableField(fieldName, serialization.TypeDate, t == nil, func() { - WritePortableDate(pw.output.ObjectDataOutput, *t) + tt := (*time.Time)(t) + WritePortableDate(pw.output.ObjectDataOutput, *tt) }) } -func (pw *DefaultPortableWriter) WriteTime(fieldName string, t *time.Time) { +func (pw *DefaultPortableWriter) WriteTime(fieldName string, t *types.LocalTime) { pw.writeNullableField(fieldName, serialization.TypeTime, t == nil, func() { - WritePortableTime(pw.output.ObjectDataOutput, *t) + tt := (*time.Time)(t) + WritePortableTime(pw.output.ObjectDataOutput, *tt) }) } -func (pw *DefaultPortableWriter) WriteTimestamp(fieldName string, t *time.Time) { +func (pw *DefaultPortableWriter) WriteTimestamp(fieldName string, t *types.LocalDateTime) { pw.writeNullableField(fieldName, serialization.TypeTimestamp, t == nil, func() { - WritePortableTimestamp(pw.output.ObjectDataOutput, *t) + tt := (*time.Time)(t) + WritePortableTimestamp(pw.output.ObjectDataOutput, *tt) }) } -func (pw *DefaultPortableWriter) WriteTimestampWithTimezone(fieldName string, t *time.Time) { +func (pw *DefaultPortableWriter) WriteTimestampWithTimezone(fieldName string, t *types.OffsetDateTime) { pw.writeNullableField(fieldName, serialization.TypeTimestampWithTimezone, t == nil, func() { - WritePortableTimestampWithTimezone(pw.output.ObjectDataOutput, *t) + tt := (*time.Time)(t) + WritePortableTimestampWithTimezone(pw.output.ObjectDataOutput, *tt) }) } -func (pw *DefaultPortableWriter) WriteDateArray(fieldName string, ts []time.Time) { +func (pw *DefaultPortableWriter) WriteDateArray(fieldName string, a []types.LocalDate) { pw.setPosition(fieldName, int32(serialization.TypeDateArray)) - writeArrayOfTime(pw.output.ObjectDataOutput, ts, WritePortableDate) + ts := (*([]time.Time))(unsafe.Pointer(&a)) + writeArrayOfTime(pw.output.ObjectDataOutput, *ts, WritePortableDate) } -func (pw *DefaultPortableWriter) WriteTimeArray(fieldName string, ts []time.Time) { +func (pw *DefaultPortableWriter) WriteTimeArray(fieldName string, a []types.LocalTime) { pw.setPosition(fieldName, int32(serialization.TypeTimeArray)) - writeArrayOfTime(pw.output.ObjectDataOutput, ts, WritePortableTime) + ts := (*([]time.Time))(unsafe.Pointer(&a)) + writeArrayOfTime(pw.output.ObjectDataOutput, *ts, WritePortableTime) } -func (pw *DefaultPortableWriter) WriteTimestampArray(fieldName string, ts []time.Time) { +func (pw *DefaultPortableWriter) WriteTimestampArray(fieldName string, a []types.LocalDateTime) { pw.setPosition(fieldName, int32(serialization.TypeTimestampArray)) - writeArrayOfTime(pw.output.ObjectDataOutput, ts, WritePortableTimestamp) + ts := (*([]time.Time))(unsafe.Pointer(&a)) + writeArrayOfTime(pw.output.ObjectDataOutput, *ts, WritePortableTimestamp) } -func (pw *DefaultPortableWriter) WriteTimestampWithTimezoneArray(fieldName string, ts []time.Time) { +func (pw *DefaultPortableWriter) WriteTimestampWithTimezoneArray(fieldName string, a []types.OffsetDateTime) { pw.setPosition(fieldName, int32(serialization.TypeTimestampWithTimezoneArray)) - writeArrayOfTime(pw.output.ObjectDataOutput, ts, WritePortableTimestampWithTimezone) + ts := (*([]time.Time))(unsafe.Pointer(&a)) + writeArrayOfTime(pw.output.ObjectDataOutput, *ts, WritePortableTimestampWithTimezone) } func (pw *DefaultPortableWriter) WriteDecimal(fieldName string, d *types.Decimal) { diff --git a/internal/serialization/default_serializer.go b/internal/serialization/default_serializer.go index 87ddac3d1..d0b60fe4b 100644 --- a/internal/serialization/default_serializer.go +++ b/internal/serialization/default_serializer.go @@ -517,10 +517,14 @@ func (JavaLocalDateSerializer) ID() int32 { } func (JavaLocalDateSerializer) Read(input serialization.DataInput) interface{} { - return ReadDate(input) + return types.LocalDate(ReadDate(input)) } func (JavaLocalDateSerializer) Write(output serialization.DataOutput, i interface{}) { + if t, ok := i.(types.LocalDate); ok { + WriteDate(output, time.Time(t)) + return + } WriteDate(output, i.(time.Time)) } @@ -531,10 +535,14 @@ func (JavaLocalTimeSerializer) ID() int32 { } func (JavaLocalTimeSerializer) Read(input serialization.DataInput) interface{} { - return ReadTime(input) + return types.LocalTime(ReadTime(input)) } func (JavaLocalTimeSerializer) Write(output serialization.DataOutput, i interface{}) { + if t, ok := i.(types.LocalTime); ok { + WriteTime(output, time.Time(t)) + return + } WriteTime(output, i.(time.Time)) } @@ -545,12 +553,15 @@ func (JavaLocalDateTimeSerializer) ID() int32 { } func (JavaLocalDateTimeSerializer) Read(input serialization.DataInput) interface{} { - return ReadTimestamp(input) + return types.LocalDateTime(ReadTimestamp(input)) } func (JavaLocalDateTimeSerializer) Write(output serialization.DataOutput, i interface{}) { - t := i.(time.Time) - WriteTimestamp(output, t) + if t, ok := i.(types.LocalDateTime); ok { + WriteTimestamp(output, time.Time(t)) + return + } + WriteTimestamp(output, i.(time.Time)) } type JavaOffsetDateTimeSerializer struct{} @@ -560,12 +571,15 @@ func (JavaOffsetDateTimeSerializer) ID() int32 { } func (JavaOffsetDateTimeSerializer) Read(input serialization.DataInput) interface{} { - return ReadTimestampWithTimezone(input) + return types.OffsetDateTime(ReadTimestampWithTimezone(input)) } func (JavaOffsetDateTimeSerializer) Write(output serialization.DataOutput, i interface{}) { - t := i.(time.Time) - WriteTimestampWithTimezone(output, t) + if t, ok := i.(types.OffsetDateTime); ok { + WriteTimestampWithTimezone(output, time.Time(t)) + return + } + WriteTimestampWithTimezone(output, i.(time.Time)) } type GobSerializer struct{} diff --git a/internal/serialization/portable_serializer_test.go b/internal/serialization/portable_serializer_test.go index 5178f54a5..5ee427efe 100644 --- a/internal/serialization/portable_serializer_test.go +++ b/internal/serialization/portable_serializer_test.go @@ -207,12 +207,12 @@ func TestPortableSerializer_NilPortable(t *testing.T) { } type fake struct { - date time.Time - time time.Time - timestamp time.Time - timestampWithTimeZone time.Time + date types.LocalDate + time types.LocalTime + timestamp types.LocalDateTime + timestampWithTimeZone types.OffsetDateTime portable serialization.Portable - nilDate *time.Time + nilDate *types.LocalDate dec types.Decimal utf string utfArr []string @@ -225,10 +225,10 @@ type fake struct { i32Arr []int32 boolArr []bool portableArr []serialization.Portable - dateArr []time.Time - timeArr []time.Time - timestampArr []time.Time - timestampWithTimeZoneArr []time.Time + dateArr []types.LocalDate + timeArr []types.LocalTime + timestampArr []types.LocalDateTime + timestampWithTimeZoneArr []types.OffsetDateTime decArr []types.Decimal i64 int64 f64 float64 @@ -353,15 +353,15 @@ func TestPortableSerializer2(t *testing.T) { expectedRet := &fake{ byt: byt, boo: boo, ui16: ui16, i16: i16, i32: i32, i64: i64, f32: f32, f64: f64, utf: utf, portable: portable, bytArr: bytArr, boolArr: boolArr, ui16Arr: ui16Arr, i16Arr: i16Arr, i32Arr: i32Arr, i64Arr: i64Arr, f32Arr: f32Arr, f64Arr: f64Arr, utfArr: utfArr, portableArr: portableArr, - date: time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local), - time: time.Date(0, 1, 1, 12, 23, 45, 500, time.Local), - timestamp: time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local), - timestampWithTimeZone: time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000)), + date: types.LocalDate(time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local)), + time: types.LocalTime(time.Date(0, 1, 1, 12, 23, 45, 500, time.Local)), + timestamp: types.LocalDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local)), + timestampWithTimeZone: types.OffsetDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000))), dec: types.NewDecimal(big.NewInt(123_456_789), 100), - dateArr: []time.Time{time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local), time.Date(2022, 10, 16, 0, 0, 0, 0, time.Local)}, - timeArr: []time.Time{time.Date(0, 1, 1, 12, 23, 45, 500, time.Local), time.Date(0, 1, 1, 18, 3, 15, 1500, time.Local)}, - timestampArr: []time.Time{time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local), time.Date(2022, 11, 5, 14, 13, 41, 200, time.Local)}, - timestampWithTimeZoneArr: []time.Time{time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000)), time.Date(2021, 2, 7, 18, 21, 5, 5500, time.FixedZone("", -2000))}, + dateArr: []types.LocalDate{types.LocalDate(time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local)), types.LocalDate(time.Date(2022, 10, 16, 0, 0, 0, 0, time.Local))}, + timeArr: []types.LocalTime{types.LocalTime(time.Date(0, 1, 1, 12, 23, 45, 500, time.Local)), types.LocalTime(time.Date(0, 1, 1, 18, 3, 15, 1500, time.Local))}, + timestampArr: []types.LocalDateTime{types.LocalDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local)), types.LocalDateTime(time.Date(2022, 11, 5, 14, 13, 41, 200, time.Local))}, + timestampWithTimeZoneArr: []types.OffsetDateTime{types.OffsetDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000))), types.OffsetDateTime(time.Date(2021, 2, 7, 18, 21, 5, 5500, time.FixedZone("", -2000)))}, decArr: []types.Decimal{types.NewDecimal(big.NewInt(123_456_789), 100), types.NewDecimal(big.NewInt(-123_456_789_123), 100_000)}, } data, err := service.ToData(expectedRet) @@ -428,15 +428,15 @@ func TestPortableSerializer4(t *testing.T) { expectedRet := &fake{ byt: byt, boo: boo, ui16: ui16, i16: i16, i32: i32, i64: i64, f32: f32, f64: f64, utf: utf, bytArr: bytArr, boolArr: boolArr, ui16Arr: ui16Arr, i16Arr: i16Arr, i32Arr: i32Arr, i64Arr: i64Arr, f32Arr: f32Arr, f64Arr: f64Arr, utfArr: utfArr, portableArr: portableArr, - date: time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local), - time: time.Date(0, 1, 1, 12, 23, 45, 500, time.Local), - timestamp: time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local), - timestampWithTimeZone: time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000)), + date: types.LocalDate(time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local)), + time: types.LocalTime(time.Date(0, 1, 1, 12, 23, 45, 500, time.Local)), + timestamp: types.LocalDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local)), + timestampWithTimeZone: types.OffsetDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000))), dec: types.NewDecimal(big.NewInt(123_456_789), 100), - dateArr: []time.Time{time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local), time.Date(2022, 10, 16, 0, 0, 0, 0, time.Local)}, - timeArr: []time.Time{time.Date(0, 1, 1, 12, 23, 45, 500, time.Local), time.Date(0, 1, 1, 18, 3, 15, 1500, time.Local)}, - timestampArr: []time.Time{time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local), time.Date(2022, 11, 5, 14, 13, 41, 200, time.Local)}, - timestampWithTimeZoneArr: []time.Time{time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000)), time.Date(2021, 2, 7, 18, 21, 5, 5500, time.FixedZone("", -2000))}, + dateArr: []types.LocalDate{types.LocalDate(time.Date(2021, 12, 6, 0, 0, 0, 0, time.Local)), types.LocalDate(time.Date(2022, 10, 16, 0, 0, 0, 0, time.Local))}, + timeArr: []types.LocalTime{types.LocalTime(time.Date(0, 1, 1, 12, 23, 45, 500, time.Local)), types.LocalTime(time.Date(0, 1, 1, 18, 3, 15, 1500, time.Local))}, + timestampArr: []types.LocalDateTime{types.LocalDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.Local)), types.LocalDateTime(time.Date(2022, 11, 5, 14, 13, 41, 200, time.Local))}, + timestampWithTimeZoneArr: []types.OffsetDateTime{types.OffsetDateTime(time.Date(2021, 12, 6, 12, 23, 45, 500, time.FixedZone("", -12000))), types.OffsetDateTime(time.Date(2021, 2, 7, 18, 21, 5, 5500, time.FixedZone("", -2000)))}, decArr: []types.Decimal{types.NewDecimal(big.NewInt(123_456_789), 100), types.NewDecimal(big.NewInt(-123_456_789_123), 100_000)}, } data, err := service.ToData(expectedRet) diff --git a/internal/serialization/serialization.go b/internal/serialization/serialization.go index eece7bd79..0f8981bec 100644 --- a/internal/serialization/serialization.go +++ b/internal/serialization/serialization.go @@ -350,6 +350,14 @@ func (s *Service) lookupBuiltinSerializer(obj interface{}) pubserialization.Seri return javaArrayListSerializer case types.UUID: return uuidSerializer + case types.LocalDate: + return javaLocalDateSerializer + case types.LocalTime: + return javaLocalTimeSerializer + case types.LocalDateTime: + return javaLocalDateTimeSerializer + case types.OffsetDateTime: + return javaOffsetDateTimeSerializer case time.Time: return dateTimeSerializer(o) case *big.Int: diff --git a/internal/serialization/serialization_improvements_test.go b/internal/serialization/serialization_improvements_test.go index 8efe0c7d2..6c9a6aa46 100644 --- a/internal/serialization/serialization_improvements_test.go +++ b/internal/serialization/serialization_improvements_test.go @@ -89,7 +89,7 @@ func TestSerializationImprovements_JavaDate(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, target, value) + assert.Equal(t, types.LocalDateTime(target), value) } func TestSerializationImprovements(t *testing.T) { @@ -102,23 +102,43 @@ func TestSerializationImprovements(t *testing.T) { }{ { input: time.Date(2021, 2, 10, 0, 0, 0, 0, time.Local), - name: "JavaLocalDate", - target: time.Date(2021, 2, 10, 0, 0, 0, 0, time.Local), + name: "JavaLocalDate from types.LocalDate", + target: types.LocalDate(time.Date(2021, 2, 10, 0, 0, 0, 0, time.Local)), }, { input: time.Date(0, 1, 1, 1, 2, 3, 50, time.Local), - name: "JavaLocalTime", - target: time.Date(0, 1, 1, 1, 2, 3, 50, time.Local), + name: "JavaLocalTime from types.LocalTime", + target: types.LocalTime(time.Date(0, 1, 1, 1, 2, 3, 50, time.Local)), }, { input: time.Date(2021, 2, 10, 1, 2, 3, 4, time.Local), - name: "JavaLocalDateTime", - target: time.Date(2021, 2, 10, 1, 2, 3, 4, time.Local), + name: "JavaLocalDateTime from types.LocalDateTime", + target: types.LocalDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.Local)), }, { input: time.Date(2021, 2, 10, 1, 2, 3, 4, time.FixedZone("", -3*60*60)), + name: "JavaOffsetDateTime from types.OffsetDateTime", + target: types.OffsetDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.FixedZone("", -3*60*60))), + }, + { + input: types.LocalDate(time.Date(2021, 2, 10, 0, 0, 0, 0, time.Local)), + name: "JavaLocalDate", + target: types.LocalDate(time.Date(2021, 2, 10, 0, 0, 0, 0, time.Local)), + }, + { + input: types.LocalTime(time.Date(0, 1, 1, 1, 2, 3, 50, time.Local)), + name: "JavaLocalTime", + target: types.LocalTime(time.Date(0, 1, 1, 1, 2, 3, 50, time.Local)), + }, + { + input: types.LocalDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.Local)), + name: "JavaLocalDateTime", + target: types.LocalDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.Local)), + }, + { + input: types.OffsetDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.FixedZone("", -3*60*60))), name: "JavaOffsetDateTime", - target: time.Date(2021, 2, 10, 1, 2, 3, 4, time.FixedZone("", -3*60*60)), + target: types.OffsetDateTime(time.Date(2021, 2, 10, 1, 2, 3, 4, time.FixedZone("", -3*60*60))), }, { input: []interface{}{"foo", int64(22)}, diff --git a/serialization/api.go b/serialization/api.go index ddc5a8c2b..50d02aed7 100644 --- a/serialization/api.go +++ b/serialization/api.go @@ -17,8 +17,6 @@ package serialization import ( - "time" - "github.com/hazelcast/hazelcast-go-client/types" ) @@ -287,22 +285,22 @@ type PortableWriter interface { // All unnamed fields must be written after portable fields. // Attempts to write named fields after GetRawDataOutput is called will panic. GetRawDataOutput() DataOutput - // WriteDate writes the date part of a time.Time value. - WriteDate(fieldName string, t *time.Time) - // WriteTime writes the time part of a time.Time value. - WriteTime(fieldName string, t *time.Time) - // WriteTimestamp writes the date and time of a time.Time value, without the timezone offset. - WriteTimestamp(fieldName string, t *time.Time) - // WriteTimestampWithTimezone writes the date and time of a time.Time value, with the timezone offset. - WriteTimestampWithTimezone(fieldName string, t *time.Time) - // WriteDateArray writes date parts of time.Time values. - WriteDateArray(fieldName string, t []time.Time) - // WriteTimeArray writes time parts of time.Time values. - WriteTimeArray(fieldName string, t []time.Time) - // WriteTimestampArray writes date and time of time.Time values, without the timezone offset. - WriteTimestampArray(fieldName string, t []time.Time) - // WriteTimestampWithTimezoneArray writes date and time of time.Time values, with the timezone offset. - WriteTimestampWithTimezoneArray(fieldName string, t []time.Time) + // WriteDate writes a date. + WriteDate(fieldName string, t *types.LocalDate) + // WriteTime writes a time. + WriteTime(fieldName string, t *types.LocalTime) + // WriteTimestamp writes the date and time without the timezone offset. + WriteTimestamp(fieldName string, t *types.LocalDateTime) + // WriteTimestampWithTimezone writes the date and time with the timezone offset. + WriteTimestampWithTimezone(fieldName string, t *types.OffsetDateTime) + // WriteDateArray writes a date array. + WriteDateArray(fieldName string, t []types.LocalDate) + // WriteTimeArray writes a time array. + WriteTimeArray(fieldName string, t []types.LocalTime) + // WriteTimestampArray writes a timestamp array. + WriteTimestampArray(fieldName string, t []types.LocalDateTime) + // WriteTimestampWithTimezoneArray writes a timestamp with timezone array. + WriteTimestampWithTimezoneArray(fieldName string, t []types.OffsetDateTime) // WriteDecimal writes the given decimal value. // The decimal value may be nil. WriteDecimal(fieldName string, d *types.Decimal) @@ -384,24 +382,24 @@ type PortableReader interface { GetRawDataInput() DataInput // ReadDate reads the date. // It may return nil. - ReadDate(fieldName string) *time.Time + ReadDate(fieldName string) *types.LocalDate // ReadTime reads the time. // It may return nil. - ReadTime(fieldName string) *time.Time + ReadTime(fieldName string) *types.LocalTime // ReadTimestamp reads the time stamp. // It may return nil. - ReadTimestamp(fieldName string) *time.Time + ReadTimestamp(fieldName string) *types.LocalDateTime // ReadTimestampWithTimezone reads the time stamp with time zone. // It may return nil. - ReadTimestampWithTimezone(fieldName string) *time.Time - // ReadDateArray reads the date arrau. - ReadDateArray(fieldName string) []time.Time + ReadTimestampWithTimezone(fieldName string) *types.OffsetDateTime + // ReadDateArray reads the date array. + ReadDateArray(fieldName string) []types.LocalDate // ReadTimeArray reads the time array. - ReadTimeArray(fieldName string) []time.Time + ReadTimeArray(fieldName string) []types.LocalTime // ReadTimestampArray reads the time stamp array. - ReadTimestampArray(fieldName string) []time.Time + ReadTimestampArray(fieldName string) []types.LocalDateTime // ReadTimestampWithTimezoneArray reads the time stamp with time zone array. - ReadTimestampWithTimezoneArray(fieldName string) []time.Time + ReadTimestampWithTimezoneArray(fieldName string) []types.OffsetDateTime // ReadDecimal reads a decimal. // It may return nil. ReadDecimal(fieldName string) (d *types.Decimal) diff --git a/sql/driver/doc.go b/sql/driver/doc.go index 5868c099c..9e32a8aff 100644 --- a/sql/driver/doc.go +++ b/sql/driver/doc.go @@ -175,12 +175,28 @@ The names in parentheses correspond to SQL types: - float32 (real) - float64 (double) - types.Decimal (decimal) + - types.LocalDate (date) + - types.LocalTime (time) + - types.LocalDateTime (timestamp) + - types.OffsetDateTime (timestamp with time zone) - time.Time (date) Detected by checking: hour == minute == second == nanoseconds = 0 - time.Time (time) Detected by checking: year == 0, month == day == 1 - time.Time (timestamp) Detected by checking: not time, timezone == time.Local - time.Time (timestamp with time zone) Detected by checking: not time, timezone != time.Local - serialization.JSON (json) +Using Date/Time Types + +time.Time values are automatically serialized to the correct type. + +In order to force using a specific date/time type, create a time.Time value and cast it to the target type: + + t := time.Now() + dateValue := types.LocalDate(t) + timeValue := types.LocalTime(t) + dateTimeValue := types.LocalDateTime(t) + dateTimeWithTimezoneValue := types.OffsetDateTime(t) + Using Raw Values You can directly use one of the supported data types. diff --git a/sql/driver/driver_it_test.go b/sql/driver/driver_it_test.go index c989f9c82..fd3f21cc9 100644 --- a/sql/driver/driver_it_test.go +++ b/sql/driver/driver_it_test.go @@ -41,9 +41,10 @@ import ( ) const ( - factoryID = 100 - recordClassID = 1 - recordWithDateTimeClassID = 2 + factoryID = 100 + recordClassID = 1 + recordWithDateTimeClassID = 2 + recordWithDateTimeClassID2 = 3 ) type Record struct { @@ -119,6 +120,48 @@ func (r RecordWithDateTime) ClassID() int32 { } func (r RecordWithDateTime) WritePortable(wr serialization.PortableWriter) { + wr.WriteDate("datevalue", (*types.LocalDate)(r.DateValue)) + wr.WriteTime("timevalue", (*types.LocalTime)(r.TimeValue)) + wr.WriteTimestamp("timestampvalue", (*types.LocalDateTime)(r.TimestampValue)) + wr.WriteTimestampWithTimezone("timestampwithtimezonevalue", (*types.OffsetDateTime)(r.TimestampWithTimezoneValue)) + +} + +func (r *RecordWithDateTime) ReadPortable(rd serialization.PortableReader) { + r.DateValue = (*time.Time)(rd.ReadDate("datevalue")) + r.TimeValue = (*time.Time)(rd.ReadTime("timevalue")) + r.TimestampValue = (*time.Time)(rd.ReadTimestamp("timestampvalue")) + r.TimestampWithTimezoneValue = (*time.Time)(rd.ReadTimestampWithTimezone("timestampwithtimezonevalue")) +} + +type RecordWithDateTime2 struct { + DateValue *types.LocalDate + TimeValue *types.LocalTime + TimestampValue *types.LocalDateTime + TimestampWithTimezoneValue *types.OffsetDateTime +} + +func NewRecordWithDateTime2(t *time.Time) *RecordWithDateTime { + dv := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local) + tv := time.Date(0, 1, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local) + tsv := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local) + return &RecordWithDateTime{ + DateValue: &dv, + TimeValue: &tv, + TimestampValue: &tsv, + TimestampWithTimezoneValue: t, + } +} + +func (r RecordWithDateTime2) FactoryID() int32 { + return factoryID +} + +func (r RecordWithDateTime2) ClassID() int32 { + return recordWithDateTimeClassID2 +} + +func (r RecordWithDateTime2) WritePortable(wr serialization.PortableWriter) { wr.WriteDate("datevalue", r.DateValue) wr.WriteTime("timevalue", r.TimeValue) wr.WriteTimestamp("timestampvalue", r.TimestampValue) @@ -126,7 +169,7 @@ func (r RecordWithDateTime) WritePortable(wr serialization.PortableWriter) { } -func (r *RecordWithDateTime) ReadPortable(rd serialization.PortableReader) { +func (r *RecordWithDateTime2) ReadPortable(rd serialization.PortableReader) { r.DateValue = rd.ReadDate("datevalue") r.TimeValue = rd.ReadTime("timevalue") r.TimestampValue = rd.ReadTimestamp("timestampvalue") @@ -141,6 +184,8 @@ func (f recordFactory) Create(classID int32) serialization.Portable { return &Record{} case recordWithDateTimeClassID: return &RecordWithDateTime{} + case recordWithDateTimeClassID2: + return &RecordWithDateTime2{} } panic(fmt.Sprintf("unknown class ID: %d", classID)) } @@ -203,22 +248,26 @@ func TestSQLQuery(t *testing.T) { { keyFmt: "int", valueFmt: "date", keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 21, 0, 0, 0, 0, time.Local) }, + valueFn: func(i int) interface{} { return types.LocalDate(time.Date(2021, 12, 21, 0, 0, 0, 0, time.Local)) }, }, { keyFmt: "int", valueFmt: "time", keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(0, 1, 1, 14, 15, 16, 200, time.Local) }, + valueFn: func(i int) interface{} { return types.LocalTime(time.Date(0, 1, 1, 14, 15, 16, 200, time.Local)) }, }, { keyFmt: "int", valueFmt: "timestamp", - keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 23, 14, 15, 16, 200, time.Local) }, + keyFn: func(i int) interface{} { return int32(i) }, + valueFn: func(i int) interface{} { + return types.LocalDateTime(time.Date(2021, 12, 23, 14, 15, 16, 200, time.Local)) + }, }, { keyFmt: "int", valueFmt: "timestamp with time zone", - keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 23, 14, 15, 16, 200, time.FixedZone("", -6000)) }, + keyFn: func(i int) interface{} { return int32(i) }, + valueFn: func(i int) interface{} { + return types.OffsetDateTime(time.Date(2021, 12, 23, 14, 15, 16, 200, time.FixedZone("", -6000))) + }, }, } for _, tc := range testCases { @@ -428,6 +477,87 @@ func TestSQLWithPortableDateTime(t *testing.T) { }) } +func TestSQLWithPortableDateTime2(t *testing.T) { + it.SkipIf(t, "hz < 5.0") + cb := func(c *hz.Config) { + c.Serialization.SetPortableFactories(&recordFactory{}) + } + it.SQLTesterWithConfigBuilder(t, cb, func(t *testing.T, client *hz.Client, config *hz.Config, m *hz.Map, mapName string) { + db := driver.Open(*config) + defer db.Close() + q := fmt.Sprintf(` + CREATE MAPPING "%s" ( + __key BIGINT, + datevalue DATE, + timevalue TIME, + timestampvalue TIMESTAMP, + timestampwithtimezonevalue TIMESTAMP WITH TIME ZONE + ) + TYPE IMAP + OPTIONS ( + 'keyFormat' = 'bigint', + 'valueFormat' = 'portable', + 'valuePortableFactoryId' = '100', + 'valuePortableClassId' = '3' + ) + `, mapName) + t.Logf("Query: %s", q) + it.MustValue(db.Exec(q)) + dt := time.Date(2021, 12, 22, 23, 40, 12, 3400, time.FixedZone("A/B", -5*60*60)) + rec := NewRecordWithDateTime2(&dt) + _, err := db.Exec(fmt.Sprintf(`INSERT INTO "%s" (__key, datevalue, timevalue, timestampvalue, timestampwithtimezonevalue) VALUES(?, ?, ?, ?, ?)`, mapName), + 1, *rec.DateValue, *rec.TimeValue, *rec.TimestampValue, *rec.TimestampWithTimezoneValue) + if err != nil { + t.Fatal(err) + } + targetDate := types.LocalDate(time.Date(2021, 12, 22, 0, 0, 0, 0, time.Local)) + targetTime := types.LocalTime(time.Date(0, 1, 1, 23, 40, 12, 3400, time.Local)) + targetTimestamp := types.LocalDateTime(time.Date(2021, 12, 22, 23, 40, 12, 3400, time.Local)) + targetTimestampWithTimezone := types.OffsetDateTime(time.Date(2021, 12, 22, 23, 40, 12, 3400, time.FixedZone("", -5*60*60))) + var k int64 + // select the value itself + row := db.QueryRow(fmt.Sprintf(`SELECT __key, this from "%s"`, mapName)) + var v interface{} + var vs []interface{} + if err := row.Scan(&k, &v); err != nil { + t.Fatal(err) + } + vs = append(vs, v) + targetThis := []interface{}{&RecordWithDateTime2{ + DateValue: &targetDate, + TimeValue: &targetTime, + TimestampValue: &targetTimestamp, + TimestampWithTimezoneValue: &targetTimestampWithTimezone, + }} + assert.Equal(t, targetThis, vs) + // select individual fields + row = db.QueryRow(fmt.Sprintf(` + SELECT + __key, datevalue, timevalue, timestampvalue, timestampwithtimezonevalue + FROM "%s" LIMIT 1 + `, mapName)) + var vDate types.LocalDate + var vTime types.LocalTime + var vTimestamp types.LocalDateTime + var vTimestampWithTimezone types.OffsetDateTime + if err := row.Scan(&k, &vDate, &vTime, &vTimestamp, &vTimestampWithTimezone); err != nil { + t.Fatal(err) + } + if !(time.Time)(targetDate).Equal((time.Time)(vDate)) { + t.Fatalf("%v != %v", targetDate, vDate) + } + if !(time.Time)(targetTime).Equal((time.Time)(vTime)) { + t.Fatalf("%v != %v", targetTime, vTime) + } + if !(time.Time)(targetTimestamp).Equal((time.Time)(vTimestamp)) { + t.Fatalf("%v != %v", targetTimestamp, vTimestamp) + } + if !(time.Time)(targetTimestampWithTimezone).Equal((time.Time)(vTimestampWithTimezone)) { + t.Fatalf("%v != %v", targetTimestampWithTimezone, vTimestamp) + } + }) +} + func TestSQLQueryWithCursorBufferSize(t *testing.T) { it.SkipIf(t, "hz < 5.0") fn := func(i int) interface{} { return int32(i) } diff --git a/sql_it_test.go b/sql_it_test.go index d3331b2a8..455e3000f 100644 --- a/sql_it_test.go +++ b/sql_it_test.go @@ -35,9 +35,10 @@ import ( ) const ( - factoryID = 100 - recordClassID = 1 - recordWithDateTimeClassID = 2 + factoryID = 100 + recordClassID = 1 + recordWithDateTimeClassID = 2 + recordWithDateTimeClassID2 = 3 ) type Record struct { @@ -112,6 +113,48 @@ func (r RecordWithDateTime) ClassID() int32 { } func (r RecordWithDateTime) WritePortable(wr serialization.PortableWriter) { + wr.WriteDate("datevalue", (*types.LocalDate)(r.DateValue)) + wr.WriteTime("timevalue", (*types.LocalTime)(r.TimeValue)) + wr.WriteTimestamp("timestampvalue", (*types.LocalDateTime)(r.TimestampValue)) + wr.WriteTimestampWithTimezone("timestampwithtimezonevalue", (*types.OffsetDateTime)(r.TimestampWithTimezoneValue)) + +} + +func (r *RecordWithDateTime) ReadPortable(rd serialization.PortableReader) { + r.DateValue = (*time.Time)(rd.ReadDate("datevalue")) + r.TimeValue = (*time.Time)(rd.ReadTime("timevalue")) + r.TimestampValue = (*time.Time)(rd.ReadTimestamp("timestampvalue")) + r.TimestampWithTimezoneValue = (*time.Time)(rd.ReadTimestampWithTimezone("timestampwithtimezonevalue")) +} + +type RecordWithDateTime2 struct { + DateValue *types.LocalDate + TimeValue *types.LocalTime + TimestampValue *types.LocalDateTime + TimestampWithTimezoneValue *types.OffsetDateTime +} + +func NewRecordWithDateTime2(t *time.Time) *RecordWithDateTime { + dv := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local) + tv := time.Date(0, 1, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local) + tsv := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local) + return &RecordWithDateTime{ + DateValue: &dv, + TimeValue: &tv, + TimestampValue: &tsv, + TimestampWithTimezoneValue: t, + } +} + +func (r RecordWithDateTime2) FactoryID() int32 { + return factoryID +} + +func (r RecordWithDateTime2) ClassID() int32 { + return recordWithDateTimeClassID2 +} + +func (r RecordWithDateTime2) WritePortable(wr serialization.PortableWriter) { wr.WriteDate("datevalue", r.DateValue) wr.WriteTime("timevalue", r.TimeValue) wr.WriteTimestamp("timestampvalue", r.TimestampValue) @@ -119,7 +162,7 @@ func (r RecordWithDateTime) WritePortable(wr serialization.PortableWriter) { } -func (r *RecordWithDateTime) ReadPortable(rd serialization.PortableReader) { +func (r *RecordWithDateTime2) ReadPortable(rd serialization.PortableReader) { r.DateValue = rd.ReadDate("datevalue") r.TimeValue = rd.ReadTime("timevalue") r.TimestampValue = rd.ReadTimestamp("timestampvalue") @@ -134,6 +177,8 @@ func (f recordFactory) Create(classID int32) serialization.Portable { return &Record{} case recordWithDateTimeClassID: return &RecordWithDateTime{} + case recordWithDateTimeClassID2: + return &RecordWithDateTime2{} } panic(fmt.Sprintf("unknown class ID: %d", classID)) } @@ -196,22 +241,26 @@ func TestSQLQuery(t *testing.T) { { keyFmt: "int", valueFmt: "date", keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 21, 0, 0, 0, 0, time.Local) }, + valueFn: func(i int) interface{} { return types.LocalDate(time.Date(2021, 12, 21, 0, 0, 0, 0, time.Local)) }, }, { keyFmt: "int", valueFmt: "time", keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(0, 1, 1, 14, 15, 16, 200, time.Local) }, + valueFn: func(i int) interface{} { return types.LocalTime(time.Date(0, 1, 1, 14, 15, 16, 200, time.Local)) }, }, { keyFmt: "int", valueFmt: "timestamp", - keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 23, 14, 15, 16, 200, time.Local) }, + keyFn: func(i int) interface{} { return int32(i) }, + valueFn: func(i int) interface{} { + return types.LocalDateTime(time.Date(2021, 12, 23, 14, 15, 16, 200, time.Local)) + }, }, { keyFmt: "int", valueFmt: "timestamp with time zone", - keyFn: func(i int) interface{} { return int32(i) }, - valueFn: func(i int) interface{} { return time.Date(2021, 12, 23, 14, 15, 16, 200, time.FixedZone("", -6000)) }, + keyFn: func(i int) interface{} { return int32(i) }, + valueFn: func(i int) interface{} { + return types.OffsetDateTime(time.Date(2021, 12, 23, 14, 15, 16, 200, time.FixedZone("", -6000))) + }, }, } for _, tc := range testCases { @@ -390,6 +439,91 @@ func TestSQLWithPortableDateTime(t *testing.T) { }) } +func TestSQLWithPortableDateTime2(t *testing.T) { + it.SkipIf(t, "hz < 5.0") + cb := func(c *hz.Config) { + c.Serialization.SetPortableFactories(&recordFactory{}) + } + it.SQLTesterWithConfigBuilder(t, cb, func(t *testing.T, client *hz.Client, config *hz.Config, m *hz.Map, mapName string) { + q := fmt.Sprintf(` + CREATE MAPPING "%s" ( + __key BIGINT, + datevalue DATE, + timevalue TIME, + timestampvalue TIMESTAMP, + timestampwithtimezonevalue TIMESTAMP WITH TIME ZONE + ) + TYPE IMAP + OPTIONS ( + 'keyFormat' = 'bigint', + 'valueFormat' = 'portable', + 'valuePortableFactoryId' = '100', + 'valuePortableClassId' = '3' + ) + `, mapName) + t.Logf("Query: %s", q) + it.MustValue(client.ExecSQL(context.Background(), q)) + dt := time.Date(2021, 12, 22, 23, 40, 12, 3400, time.FixedZone("A/B", -5*60*60)) + rec := NewRecordWithDateTime2(&dt) + _, err := client.ExecSQL(context.Background(), fmt.Sprintf(`INSERT INTO "%s" (__key, datevalue, timevalue, timestampvalue, timestampwithtimezonevalue) VALUES(?, ?, ?, ?, ?)`, mapName), + 1, *rec.DateValue, *rec.TimeValue, *rec.TimestampValue, *rec.TimestampWithTimezoneValue) + if err != nil { + t.Fatal(err) + } + targetDate := types.LocalDate(time.Date(2021, 12, 22, 0, 0, 0, 0, time.Local)) + targetTime := types.LocalTime(time.Date(0, 1, 1, 23, 40, 12, 3400, time.Local)) + targetTimestamp := types.LocalDateTime(time.Date(2021, 12, 22, 23, 40, 12, 3400, time.Local)) + targetTimestampWithTimezone := types.OffsetDateTime(time.Date(2021, 12, 22, 23, 40, 12, 3400, time.FixedZone("", -5*60*60))) + var k int64 + // select the value itself + row, err := queryRow(client, fmt.Sprintf(`SELECT __key, this from "%s"`, mapName)) + if err != nil { + t.Fatal(err) + } + var v interface{} + var vs []interface{} + if err := row.Scan(&k, &v); err != nil { + t.Fatal(err) + } + vs = append(vs, v) + targetThis := []interface{}{&RecordWithDateTime2{ + DateValue: &targetDate, + TimeValue: &targetTime, + TimestampValue: &targetTimestamp, + TimestampWithTimezoneValue: &targetTimestampWithTimezone, + }} + assert.Equal(t, targetThis, vs) + // select individual fields + row, err = queryRow(client, fmt.Sprintf(` + SELECT + __key, datevalue, timevalue, timestampvalue, timestampwithtimezonevalue + FROM "%s" LIMIT 1 + `, mapName)) + if err != nil { + t.Fatal(err) + } + var vDate types.LocalDate + var vTime types.LocalTime + var vTimestamp types.LocalDateTime + var vTimestampWithTimezone types.OffsetDateTime + if err := row.Scan(&k, &vDate, &vTime, &vTimestamp, &vTimestampWithTimezone); err != nil { + t.Fatal(err) + } + if !(time.Time)(targetDate).Equal((time.Time)(vDate)) { + t.Fatalf("%v != %v", targetDate, vDate) + } + if !(time.Time)(targetTime).Equal((time.Time)(vTime)) { + t.Fatalf("%v != %v", targetTime, vTime) + } + if !(time.Time)(targetTimestamp).Equal((time.Time)(vTimestamp)) { + t.Fatalf("%v != %v", targetTimestamp, vTimestamp) + } + if !(time.Time)(targetTimestampWithTimezone).Equal((time.Time)(vTimestampWithTimezone)) { + t.Fatalf("%v != %v", targetTimestampWithTimezone, vTimestamp) + } + }) +} + func TestSQLQueryWithCursorBufferSize(t *testing.T) { it.SkipIf(t, "hz < 5.0") fn := func(i int) interface{} { return int32(i) } diff --git a/types/time.go b/types/time.go new file mode 100644 index 000000000..950de1874 --- /dev/null +++ b/types/time.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008-2021, Hazelcast, Inc. All Rights Reserved. + * + * Licensed 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. + */ + +package types + +import ( + "time" +) + +// LocalDate is the date part of time.Time. +type LocalDate time.Time + +// LocalTime is the time part of time.Time. +type LocalTime time.Time + +// LocalDateTime is the date and time with local timezone. +type LocalDateTime time.Time + +// OffsetDateTime is the date and time with a timezone. +type OffsetDateTime time.Time