From 2e928607d532892af3c1c4f755ef9ab809b17411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Egelund-M=C3=BCller?= Date: Wed, 9 Oct 2019 18:56:22 +0200 Subject: [PATCH] Fixes a bug which caused positive integers between 2^63 to 2^64-1 stored in a bytes.decimal to erroneously decode as negative numbers. To understand the bug, note that big.Int.BitLen returns the same value for positive and negative numbers. But if the most significant bit of a positive number is on the word boundary, it requires an extra bit to be properly represented in two's complement. The library did not account for that, thus causing positive numbers to turn negative when casting them to a (signed) int64 -- which doesn't cause an overflow error in Go, see https://play.golang.org/p/hQOopoudJcm. The bug would have never happened if the library didn't consider decimals with a numerator larger than 64 bits out of bounds, which seems somewhat counterintuitive to the purpose of decimals. So, this fix resolves the bug by not trying to coerce the numerator into an int64. On that note, unless I'm missing something, it seems to me that a) the library doesn't respect the precision specified in the schema when encoding and decoding values, and b) the library in several places relies on math.Pow10(scale)) fitting in an int64, which is not necessarily the case. --- logical_type.go | 7 ++----- logical_type_test.go | 7 +++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/logical_type.go b/logical_type.go index 3a811fc..e997ef4 100644 --- a/logical_type.go +++ b/logical_type.go @@ -250,11 +250,8 @@ func nativeFromDecimalBytes(fn toNativeFn, precision, scale int) toNativeFn { } i := big.NewInt(0) fromSignedBytes(i, bs) - if i.BitLen() > 64 { - // Avro spec specifies we return underlying type if the logicalType is invalid - return d, b, err - } - r := big.NewRat(i.Int64(), int64(math.Pow10(scale))) + r := new(big.Rat) + r.SetFrac(i, big.NewInt(int64(math.Pow10(scale)))) return r, b, nil } } diff --git a/logical_type_test.go b/logical_type_test.go index 6591b51..24a2465 100644 --- a/logical_type_test.go +++ b/logical_type_test.go @@ -97,6 +97,13 @@ func TestDecimalBytesLogicalTypeEncode(t *testing.T) { testBinaryCodecPass(t, schema, big.NewRat(617, 50), []byte("\x04\x04\xd2")) testBinaryCodecPass(t, schema, big.NewRat(-617, 50), []byte("\x04\xfb\x2e")) testBinaryCodecPass(t, schema, big.NewRat(0, 1), []byte("\x02\x00")) + schema0scale := `{"type": "bytes", "logicalType": "decimal", "precision": 100, "scale": 0}` + r1, _ := new(big.Rat).SetString("9223372036854775808") // 2^63 + testBinaryCodecPass(t, schema0scale, r1, []byte("\x12\x00\x80\x00\x00\x00\x00\x00\x00\x00")) + r2, _ := new(big.Rat).SetString("18446744073709551615") // 2^64 - 1 + testBinaryCodecPass(t, schema0scale, r2, []byte("\x12\x00\xff\xff\xff\xff\xff\xff\xff\xff")) + r3, _ := new(big.Rat).SetString("170141183460469231731687303715884105728") + testBinaryCodecPass(t, schema0scale, r3, []byte("\x22\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")) } func TestDecimalFixedLogicalTypeEncode(t *testing.T) {