diff --git a/executor.go b/executor.go index c3adf0278..25f5c074e 100644 --- a/executor.go +++ b/executor.go @@ -4486,25 +4486,37 @@ func (s SignedRow) ToTable() (*pb.TableResponse, error) { // ToRows implements the ToRowser interface. func (s SignedRow) ToRows(callback func(*pb.RowResponse) error) error { - // TODO: address the overflow issue with values outside the int64 range + ci := []*pb.ColumnInfo{{Name: s.Field(), Datatype: "int64"}} negs := s.Neg.Columns() for i := len(negs) - 1; i >= 0; i-- { + val, err := toNegInt64(negs[i]) + if err != nil { + return errors.Wrap(err, "converting uint64 to int64 (negative)") + } + if err := callback(&pb.RowResponse{ Headers: ci, Columns: []*pb.ColumnResponse{ - &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: -1 * int64(negs[i])}}, - }}); err != nil { + &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: val}}, + }, + }); err != nil { return errors.Wrap(err, "calling callback") } ci = nil } for _, id := range s.Pos.Columns() { + val, err := toInt64(id) + if err != nil { + return errors.Wrap(err, "converting uint64 to int64 (positive)") + } + if err := callback(&pb.RowResponse{ Headers: ci, Columns: []*pb.ColumnResponse{ - &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: int64(id)}}, - }}); err != nil { + &pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: val}}, + }, + }); err != nil { return errors.Wrap(err, "calling callback") } ci = nil @@ -4512,6 +4524,31 @@ func (s SignedRow) ToRows(callback func(*pb.RowResponse) error) error { return nil } +func toNegInt64(n uint64) (int64, error) { + const absMinInt64 = uint64(1 << 63) + + if n > absMinInt64 { + return 0, errors.Errorf("value %d overflows int64", n) + } + + if n == absMinInt64 { + return int64(-1 << 63), nil + } + + // n < 1 << 63 + return -int64(n), nil +} + +func toInt64(n uint64) (int64, error) { + const maxInt64 = uint64(1<<63) - 1 + + if n > maxInt64 { + return 0, errors.Errorf("value %d overflows int64", n) + } + + return int64(n), nil +} + func (sr *SignedRow) union(other SignedRow) SignedRow { ret := SignedRow{&Row{}, &Row{}, ""} diff --git a/executor_internal_test.go b/executor_internal_test.go index 68b0f1f4e..909f48f7a 100644 --- a/executor_internal_test.go +++ b/executor_internal_test.go @@ -495,3 +495,71 @@ func TestValCountComparisons(t *testing.T) { }) } } + +func TestToNegInt64(t *testing.T) { + tests := []struct { + u64 uint64 + i64 int64 + overflow bool + }{ + { + u64: uint64(1 << 63), + i64: int64(-1 << 63), + }, + { + u64: uint64(1<<63) - 1, + i64: int64(-1<<63) + 1, + }, + { + u64: uint64(1<<63) + 1, + overflow: true, + }, + } + + for _, tc := range tests { + val, err := toNegInt64(tc.u64) + if err != nil && !tc.overflow { + t.Fatalf("error: %+v, expected: %+v", err, tc) + } + + if val != tc.i64 { + t.Fatalf("Expected: %+v, Got: %+v", tc.i64, val) + } + } +} + +func TestToInt64(t *testing.T) { + tests := []struct { + u64 uint64 + i64 int64 + overflow bool + }{ + { + u64: uint64(1<<63) - 1, + i64: 1<<63 - 1, + }, + { + u64: uint64(0), + i64: 0, + }, + { + u64: uint64(1 << 63), + overflow: true, + }, + { + u64: 1<<64 - 1, + overflow: true, + }, + } + + for _, tc := range tests { + val, err := toInt64(tc.u64) + if err != nil && !tc.overflow { + t.Fatalf("error: %+v, expected: %+v", err, tc) + } + + if val != tc.i64 { + t.Fatalf("Expected: %+v, Got: %+v", tc.i64, val) + } + } +}