Skip to content

Commit 0f0c338

Browse files
authored
Feat/issue 89 support bigint (#90)
1 parent 00be309 commit 0f0c338

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+579
-191
lines changed

.github/workflows/run-tests.yml

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,17 @@ jobs:
1515
fail-fast: true
1616
matrix:
1717
go:
18-
- '1.16'
19-
- '1.17'
20-
- '1.18'
21-
- '1.19'
22-
- '1.20'
23-
- '1.21'
18+
- '1.22.x'
19+
- '1.23.x'
2420
proton:
2521
- latest
2622

27-
services:
28-
proton:
29-
image: ghcr.io/timeplus-io/proton:${{ matrix.proton }}
30-
ports:
31-
- 3218:3218 # HTTP Streaming
32-
- 8123:8123 # HTTP Snapshot
33-
- 8463:8463 # TCP Streaming
34-
- 5432:5432 # Postgres Snapshot
35-
- 7587:7587 # TCP Snapshot
36-
env:
37-
MAX_CONCURRENT_QUERIES: 100 # Default: 100
38-
MAX_CONCURRENT_SELECT_QUERIES: 100 # Default: 100
39-
MAX_CONCURRENT_INSERT_QUERIES: 100 # Default: 100
40-
MAX_CONCURRENT_STREAMING_QUERIES: 100 # Default: 100
41-
MAX_SERVER_MEMORY_USAGE_TO_RAM_RATIO: 0.9 # Default: 0.9
42-
MAX_SERVER_MEMORY_CACHE_TO_RAM_RATIO: 0.5 # Default: 0.5
43-
4423
steps:
45-
- uses: actions/checkout@v2
24+
- uses: actions/checkout@v5
4625

4726
- name: Install Go ${{ matrix.go }}
48-
uses: actions/setup-go@v2.1.5
27+
uses: actions/setup-go@v5
4928
with:
50-
stable: false
5129
go-version: ${{ matrix.go }}
5230

5331
- name: Run tests
@@ -56,6 +34,40 @@ jobs:
5634
sudo rm -rf /usr/local/lib/android
5735
sudo rm -rf /opt/ghc
5836
df -h
37+
docker run -d --name proton \
38+
-p 3218:3218 \
39+
-p 8123:8123 \
40+
-p 8463:8463 \
41+
-p 5432:5432 \
42+
-p 7587:7587 \
43+
-e MAX_CONCURRENT_QUERIES=100 \
44+
-e MAX_CONCURRENT_SELECT_QUERIES=100 \
45+
-e MAX_CONCURRENT_INSERT_QUERIES=100 \
46+
-e MAX_CONCURRENT_STREAMING_QUERIES=100 \
47+
-e MAX_SERVER_MEMORY_USAGE_TO_RAM_RATIO=0.9 \
48+
-e MAX_SERVER_MEMORY_CACHE_TO_RAM_RATIO=0.5 \
49+
ghcr.io/timeplus-io/proton:${{ matrix.proton }} \
50+
/bin/bash -c "sed -i'' 's/preallocate: true/preallocate: false/g' /etc/proton-server/config.yaml && exec /entrypoint.sh"
51+
ready=0
52+
for i in $(seq 1 60); do
53+
if curl -fsS "http://127.0.0.1:8123/?query=SELECT%201" | grep -qx "1"; then
54+
echo "Proton is ready"
55+
ready=1
56+
break
57+
fi
58+
sleep 1
59+
done
60+
if [ "$ready" -ne 1 ]; then
61+
echo "Proton did not become ready within 60 seconds"
62+
docker logs --tail=200 proton || true
63+
exit 1
64+
fi
5965
go test -v .
6066
go test -v ./tests
6167
go test -v ./lib/...
68+
69+
- name: Dump Proton logs (on failure)
70+
if: failure()
71+
run: |
72+
docker ps -a
73+
docker logs --tail=200 proton || true

lib/column/bigint.go

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type BigInt struct {
3030
data []byte
3131
chType Type
3232
name string
33+
signed bool
3334
}
3435

3536
func (col *BigInt) Name() string {
@@ -78,14 +79,18 @@ func (col *BigInt) Append(v interface{}) (nulls []uint8, err error) {
7879
case []big.Int:
7980
nulls = make([]uint8, len(v))
8081
for _, v := range v {
81-
col.append(&v)
82+
if err := col.append(&v); err != nil {
83+
return nil, err
84+
}
8285
}
8386
case []*big.Int:
8487
nulls = make([]uint8, len(v))
8588
for i, v := range v {
8689
switch {
8790
case v != nil:
88-
col.append(v)
91+
if err := col.append(v); err != nil {
92+
return nil, err
93+
}
8994
default:
9095
col.data, nulls[i] = append(col.data, make([]byte, col.size)...), 1
9196
}
@@ -103,11 +108,11 @@ func (col *BigInt) Append(v interface{}) (nulls []uint8, err error) {
103108
func (col *BigInt) AppendRow(v interface{}) error {
104109
switch v := v.(type) {
105110
case big.Int:
106-
col.append(&v)
111+
return col.append(&v)
107112
case *big.Int:
108113
switch {
109114
case v != nil:
110-
col.append(v)
115+
return col.append(v)
111116
default:
112117
col.data = append(col.data, make([]byte, col.size)...)
113118
}
@@ -133,40 +138,85 @@ func (col *BigInt) Encode(encoder *binary.Encoder) error {
133138
}
134139

135140
func (col *BigInt) row(i int) *big.Int {
136-
return rawToBigInt(col.data[i*col.size : (i+1)*col.size])
141+
src := col.data[i*col.size : (i+1)*col.size]
142+
if col.signed {
143+
return rawToBigInt(src)
144+
}
145+
return rawToBigUInt(src)
137146
}
138147

139-
func (col *BigInt) append(v *big.Int) {
148+
func (col *BigInt) append(v *big.Int) error {
140149
dest := make([]byte, col.size)
141-
bigIntToRaw(dest, new(big.Int).Set(v))
150+
var err error
151+
if col.signed {
152+
err = bigIntToRawSigned(dest, new(big.Int).Set(v))
153+
} else {
154+
err = bigIntToRawUnsigned(dest, new(big.Int).Set(v))
155+
}
156+
if err != nil {
157+
return &Error{
158+
ColumnType: string(col.chType),
159+
Err: err,
160+
}
161+
}
142162
col.data = append(col.data, dest...)
163+
return nil
143164
}
144165

145-
func bigIntToRaw(dest []byte, v *big.Int) {
146-
var sign int
166+
func bigIntToRawUnsigned(dest []byte, v *big.Int) error {
147167
if v.Sign() < 0 {
168+
return fmt.Errorf("negative value for %s", "unsigned integer")
169+
}
170+
if v.BitLen() > len(dest)*8 {
171+
return fmt.Errorf("value out of range for %d-bit unsigned integer", len(dest)*8)
172+
}
173+
v.FillBytes(dest)
174+
endianSwap(dest, false)
175+
return nil
176+
}
177+
178+
func bigIntToRawSigned(dest []byte, v *big.Int) error {
179+
bits := len(dest) * 8
180+
switch v.Sign() {
181+
case -1:
182+
abs := new(big.Int).Abs(v)
183+
if abs.BitLen() > bits || (abs.BitLen() == bits && abs.TrailingZeroBits() != uint(bits-1)) {
184+
return fmt.Errorf("value out of range for %d-bit signed integer", bits)
185+
}
148186
v.Not(v).FillBytes(dest)
149-
sign = -1
150-
} else {
187+
endianSwap(dest, true)
188+
return nil
189+
default:
190+
if v.BitLen() > bits-1 {
191+
return fmt.Errorf("value out of range for %d-bit signed integer", bits)
192+
}
151193
v.FillBytes(dest)
194+
endianSwap(dest, false)
195+
return nil
152196
}
153-
endianSwap(dest, sign < 0)
154197
}
155198

156-
func rawToBigInt(v []byte) *big.Int {
199+
func rawToBigUInt(src []byte) *big.Int {
200+
tmp := append([]byte(nil), src...)
201+
endianSwap(tmp, false)
202+
return new(big.Int).SetBytes(tmp)
203+
}
204+
205+
func rawToBigInt(src []byte) *big.Int {
206+
tmp := append([]byte(nil), src...)
157207
// LittleEndian to BigEndian
158-
endianSwap(v, false)
208+
endianSwap(tmp, false)
159209
var lt = new(big.Int)
160-
if len(v) > 0 && v[0]&0x80 != 0 {
210+
if len(tmp) > 0 && tmp[0]&0x80 != 0 {
161211
// [0] ^ will +1
162-
for i := 0; i < len(v); i++ {
163-
v[i] = ^v[i]
212+
for i := 0; i < len(tmp); i++ {
213+
tmp[i] = ^tmp[i]
164214
}
165-
lt.SetBytes(v)
215+
lt.SetBytes(tmp)
166216
// neg ^ will -1
167217
lt.Not(lt)
168218
} else {
169-
lt.SetBytes(v)
219+
lt.SetBytes(tmp)
170220
}
171221
return lt
172222
}

lib/column/bigint_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package column
2+
3+
import (
4+
"bytes"
5+
"math/big"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestUInt128ColumnFactory(t *testing.T) {
11+
c, err := Type("uint128").Column("col", time.UTC)
12+
if err != nil {
13+
t.Fatalf("Column(): %v", err)
14+
}
15+
bi, ok := c.(*BigInt)
16+
if !ok {
17+
t.Fatalf("expected *BigInt, got %T", c)
18+
}
19+
if bi.size != 16 {
20+
t.Fatalf("expected size=16, got %d", bi.size)
21+
}
22+
if bi.signed {
23+
t.Fatalf("expected unsigned BigInt for uint128")
24+
}
25+
}
26+
27+
func TestUInt128RoundTripHighBit(t *testing.T) {
28+
v := new(big.Int).Lsh(big.NewInt(1), 127)
29+
v.Add(v, big.NewInt(12345))
30+
31+
raw := make([]byte, 16)
32+
if err := bigIntToRawUnsigned(raw, v); err != nil {
33+
t.Fatalf("bigIntToRawUnsigned(): %v", err)
34+
}
35+
rawCopy := append([]byte(nil), raw...)
36+
37+
got := rawToBigUInt(raw)
38+
if got.Cmp(v) != 0 {
39+
t.Fatalf("round-trip mismatch: got=%s want=%s", got.String(), v.String())
40+
}
41+
if !bytes.Equal(raw, rawCopy) {
42+
t.Fatalf("rawToBigUInt mutated input")
43+
}
44+
}
45+
46+
func TestUInt128AppendRejectsNegativeAndOverflow(t *testing.T) {
47+
col := &BigInt{
48+
size: 16,
49+
chType: "uint128",
50+
signed: false,
51+
}
52+
53+
if err := col.AppendRow(big.NewInt(-1)); err == nil {
54+
t.Fatalf("expected error for negative value")
55+
}
56+
57+
tooBig := new(big.Int).Lsh(big.NewInt(1), 128)
58+
if err := col.AppendRow(tooBig); err == nil {
59+
t.Fatalf("expected error for out-of-range value")
60+
}
61+
}
62+
63+
func TestRawToBigIntDoesNotMutateInput(t *testing.T) {
64+
raw := make([]byte, 16)
65+
if err := bigIntToRawSigned(raw, big.NewInt(-128)); err != nil {
66+
t.Fatalf("bigIntToRawSigned(): %v", err)
67+
}
68+
rawCopy := append([]byte(nil), raw...)
69+
70+
_ = rawToBigInt(raw)
71+
if !bytes.Equal(raw, rawCopy) {
72+
t.Fatalf("rawToBigInt mutated input")
73+
}
74+
}

lib/column/codegen/column.tpl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,28 @@ func (t Type) Column(name string, tz *time.Location) (Interface, error) {
4545
size: 16,
4646
chType: t,
4747
name: name,
48+
signed: true,
49+
}, nil
50+
case "uint128":
51+
return &BigInt{
52+
size: 16,
53+
chType: t,
54+
name: name,
55+
signed: false,
4856
}, nil
4957
case "int256":
5058
return &BigInt{
5159
size: 32,
5260
chType: t,
5361
name: name,
62+
signed: true,
5463
}, nil
5564
case "uint256":
5665
return &BigInt{
5766
size: 32,
5867
chType: t,
5968
name: name,
69+
signed: false,
6070
}, nil
6171
case "ipv4":
6272
return &IPv4{name: name}, nil
@@ -281,4 +291,4 @@ func (col *{{ .ChType }}) AppendRow(v interface{}) error {
281291
return nil
282292
}
283293

284-
{{- end }}
294+
{{- end }}

lib/column/column_gen.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/column/decimal.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,12 @@ func (col *Decimal) Encode(encoder *binary.Encoder) error {
236236
default:
237237
bi = v.BigInt()
238238
}
239-
bigIntToRaw(scratch[i*size:(i+1)*size], bi)
239+
if err := bigIntToRawSigned(scratch[i*size:(i+1)*size], bi); err != nil {
240+
return &Error{
241+
ColumnType: string(col.chType),
242+
Err: err,
243+
}
244+
}
240245
}
241246
return encoder.Raw(scratch)
242247
}

tests/issues/164_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestIssue164(t *testing.T) {
4141
if !assert.NoError(t, err) {
4242
return
4343
}
44-
if batch, err := scope.Prepare("INSERT INTO issue_164 (* except _tp_time)"); assert.NoError(t, err) {
44+
if batch, err := scope.Prepare("INSERT INTO issue_164 (* except (_tp_time, _tp_sn))"); assert.NoError(t, err) {
4545
stmtParams := make([]interface{}, 0)
4646
stmtParams = append(stmtParams, sql.NamedArg{Name: "id", Value: int32(10)})
4747
stmtParams = append(stmtParams, sql.NamedArg{Name: "anything", Value: nil})

0 commit comments

Comments
 (0)