From bb06b09092b9923ad8a7cee304a80092d2dd2e0c Mon Sep 17 00:00:00 2001 From: Utkarsh Chourasia <78140690+JammUtkarsh@users.noreply.github.com> Date: Fri, 6 Jan 2023 20:01:35 +0530 Subject: [PATCH] feat(client): replace long256 string with big.Int (#8) --- sender.go | 37 ++++++++++++++++++++++------ sender_integration_test.go | 14 +++++++---- sender_test.go | 49 +++++++++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/sender.go b/sender.go index c4f3261..345d155 100644 --- a/sender.go +++ b/sender.go @@ -325,15 +325,28 @@ func (s *LineSender) Int64Column(name string, val int64) *LineSender { // Long256Column adds a 256-bit unsigned integer (long256) column // value to the ILP message. -// -// val should contain a hex encoded 256-bit unsigned integer value -// with "0x" prefix and "i" suffix. Any attempt to set a string which is -// not a hexadecimal will not be parsed and rejected by the database. -// +// +// Only non-negative numbers that fit into 256-bit unsigned integer are +// supported and any other input value would lead to an error. +// // Column name cannot contain any of the following characters: // '\n', '\r', '?', '.', ',', ”', '"', '\\', '/', ':', ')', '(', '+', // '-', '*' '%%', '~', or a non-printable char. -func (s *LineSender) Long256Column(name, val string) *LineSender { +func (s *LineSender) Long256Column(name string, val *big.Int) *LineSender { + if val.Sign() < 0 { + if s.lastErr != nil { + return s + } + s.lastErr = fmt.Errorf("long256 cannot be negative: %s", val.String()) + return s + } + if val.BitLen() > 256 { + if s.lastErr != nil { + return s + } + s.lastErr = fmt.Errorf("long256 cannot be larger than 256-bit: %v", val.BitLen()) + return s + } if !s.prepareForField(name) { return s } @@ -342,7 +355,10 @@ func (s *LineSender) Long256Column(name, val string) *LineSender { return s } s.buf.WriteByte('=') - s.lastErr = s.writeStrValue(val, false) + s.buf.WriteByte('0') + s.buf.WriteByte('x') + s.buf.WriteBigInt(val) + s.buf.WriteByte('i') if s.lastErr != nil { return s } @@ -828,3 +844,10 @@ func (b *buffer) WriteFloat(f float64) { s := strconv.AppendFloat(a[0:0], f, 'G', -1, 64) b.Write(s) } + +func (b *buffer) WriteBigInt(i *big.Int) { + // We need up to 64 bytes to fit an unsigned 256-bit number. + var a [64]byte + s := i.Append(a[0:0], 16) + b.Write(s) +} diff --git a/sender_integration_test.go b/sender_integration_test.go index 753a339..e6cdb90 100644 --- a/sender_integration_test.go +++ b/sender_integration_test.go @@ -29,6 +29,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "net/http" "net/url" "path/filepath" @@ -231,12 +232,13 @@ func TestE2EValidWrites(t *testing.T) { "all column types", testTable, func(s *qdb.LineSender) error { + val, _ := big.NewInt(0).SetString("123a4", 16) err := s. Table(testTable). Symbol("sym_col", "test_ilp1"). Float64Column("double_col", 12.2). Int64Column("long_col", 12). - Long256Column("long256_col", "0x123a4i"). + Long256Column("long256_col", val). StringColumn("str_col", "foobar"). BoolColumn("bool_col", true). TimestampColumn("timestamp_col", 42). @@ -245,12 +247,13 @@ func TestE2EValidWrites(t *testing.T) { return err } + val, _ = big.NewInt(0).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) return s. Table(testTable). Symbol("sym_col", "test_ilp2"). Float64Column("double_col", 11.2). Int64Column("long_col", 11). - Long256Column("long256_col", "0x123a3i"). + Long256Column("long256_col", val). StringColumn("str_col", "barbaz"). BoolColumn("bool_col", false). TimestampColumn("timestamp_col", 43). @@ -269,7 +272,7 @@ func TestE2EValidWrites(t *testing.T) { }, Dataset: [][]interface{}{ {"test_ilp1", float64(12.2), float64(12), "0x0123a4", "foobar", true, "1970-01-01T00:00:00.000042Z", "1970-01-01T00:00:00.000001Z"}, - {"test_ilp2", float64(11.2), float64(11), "0x0123a3", "barbaz", false, "1970-01-01T00:00:00.000043Z", "1970-01-01T00:00:00.000002Z"}, + {"test_ilp2", float64(11.2), float64(11), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "barbaz", false, "1970-01-01T00:00:00.000043Z", "1970-01-01T00:00:00.000002Z"}, }, Count: 2, }, @@ -340,9 +343,10 @@ func TestE2EValidWrites(t *testing.T) { "single column long256", testTable, func(s *qdb.LineSender) error { + val, _ := big.NewInt(0).SetString("7fffffffffffffff", 16) return s. Table(testTable). - Long256Column("foobar", "0x123a4i"). + Long256Column("foobar", val). At(ctx, 42000) }, tableData{ @@ -351,7 +355,7 @@ func TestE2EValidWrites(t *testing.T) { {"timestamp", "TIMESTAMP"}, }, Dataset: [][]interface{}{ - {"0x0123a4", "1970-01-01T00:00:00.000042Z"}, + {"0x7fffffffffffffff", "1970-01-01T00:00:00.000042Z"}, }, Count: 1, }, diff --git a/sender_test.go b/sender_test.go index 60234b2..43ae175 100644 --- a/sender_test.go +++ b/sender_test.go @@ -32,6 +32,7 @@ import ( "io/ioutil" "log" "math" + "math/big" "net" "reflect" "strconv" @@ -192,11 +193,11 @@ func TestLong256Column(t *testing.T) { val string expected string }{ - {"random bits ", "0x0123a4i", "0x0123a4"}, - {"8bit max", "0xffffffffi", "0xffffffff"}, - {"16bit max", "0xffffffffffffffffi", "0xffffffffffffffff"}, - {"32bit max", "0xffffffffffffffffffffffffffffffffi", "0xffffffffffffffffffffffffffffffff"}, - {"64bit long", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"zero", "0", "0x0"}, + {"one", "1", "0x1"}, + {"32-bit max", strconv.FormatInt(math.MaxInt32, 16), "0x7fffffff"}, + {"64-bit random", strconv.FormatInt(7423093023234231, 16), "0x1a5f4386c8d8b7"}, + {"256-bit max", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, } for _, tc := range testCases { @@ -207,7 +208,8 @@ func TestLong256Column(t *testing.T) { sender, err := qdb.NewLineSender(ctx, qdb.WithAddress(srv.addr)) assert.NoError(t, err) - err = sender.Table(testTable).Long256Column("a_col", tc.val).AtNow(ctx) + newVal, _ := big.NewInt(0).SetString(tc.val, 16) + err = sender.Table(testTable).Long256Column("a_col", newVal).AtNow(ctx) assert.NoError(t, err) err = sender.Flush(ctx) @@ -424,6 +426,41 @@ func TestErrorOnNegativeTimestamp(t *testing.T) { assert.Empty(t, sender.Messages()) } +func TestErrorOnNegativeLong256(t *testing.T) { + ctx := context.Background() + + srv, err := newTestServer(readAndDiscard) + assert.NoError(t, err) + defer srv.close() + + sender, err := qdb.NewLineSender(ctx, qdb.WithAddress(srv.addr)) + assert.NoError(t, err) + defer sender.Close() + + err = sender.Table(testTable).Long256Column("long256_col", big.NewInt(-42)).AtNow(ctx) + + assert.ErrorContains(t, err, "long256 cannot be negative: -42") + assert.Empty(t, sender.Messages()) +} + +func TestErrorOnLargerLong256(t *testing.T) { + ctx := context.Background() + + srv, err := newTestServer(readAndDiscard) + assert.NoError(t, err) + defer srv.close() + + sender, err := qdb.NewLineSender(ctx, qdb.WithAddress(srv.addr)) + assert.NoError(t, err) + defer sender.Close() + + bigVal, _ := big.NewInt(0).SetString("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) + err = sender.Table(testTable).Long256Column("long256_col", bigVal).AtNow(ctx) + + assert.ErrorContains(t, err, "long256 cannot be larger than 256-bit: 260") + assert.Empty(t, sender.Messages()) +} + func TestErrorOnSymbolCallAfterColumn(t *testing.T) { ctx := context.Background()