You're quite right. I need to remove the b > 1 conditional. Will need to
create thorough test cases to make sure it complies the sqlite formatting
and handles these cases. I can see now, even if there was big-endian
uvarint() implementation I would still need to write my own, given sqlite
9-byte optimisation.
On Saturday, October 4, 2025 at 1:29:03 PM UTC+2 Brian Candler wrote:
> Here is a test case for which your function still doesn't work:
> fmt.Println(Uvarint([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
> 0x00})) // should return 18446744073709551360 (=0xffffffffffffff00), 9
>
> If you have read 8 bytes with the top bit set, then you must
> *unconditionally* consume all 8 bits of the 9th byte, regardless of its
> value.
>
> On Saturday, 4 October 2025 at 10:42:59 UTC+1 R. Men wrote:
>
>> Hi Brian,
>>
>> Yes, it seems I'll have to go the custom function route, given their
>> non-standard encoding. Thanks for confirming, and really appreciate those
>> tests. I fixed my code to handle >2 byte ints and special case for the 9th
>> byte (for which SQLite encoding treats all bits as data). Leaving here in
>> case anyone else is interested. Have a good weekend!
>>
>> package main
>>
>> import "fmt"
>>
>> const MaxVarintLen64 = 9
>>
>> func Uvarint(buf []byte) (uint64, int) {
>> var x uint64
>> var s uint = 7
>> for i, b := range buf {
>> if i == MaxVarintLen64 {
>> // Catch byte reads past MaxVarintLen64.
>> // See issue https://golang.org/issues/41185
>> return 0, -(i + 1) // overflow
>> }
>> if i == MaxVarintLen64-1 && b > 1 {
>> x <<= s + 1
>> return x | uint64(b), i + 1
>> }
>>
>> if b < 0x80 {
>> x <<= s
>> return x | uint64(b), i + 1
>> }
>> x <<= s
>> x |= uint64(b & 0x7f)
>> }
>> return 0, 0
>> }
>>
>> func main() {
>> fmt.Println(Uvarint([]byte{0x81, 0x47}))
>> // should return 199, 2
>> fmt.Println(Uvarint([]byte{0xff, 0xff, 0x7f}))
>> // should return 2097151 (=0x1fffff), 3
>> fmt.Println(Uvarint([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
>> 0x7f})) // should return 72057594037927935 (=0xffffffffffffff), 8
>> fmt.Println(Uvarint([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
>> 0xff, 0xff})) // should return 18446744073709551615 (=0xffffffffffffffff), 9
>> }
>>
>> On Saturday, October 4, 2025 at 9:45:26 AM UTC+2 Brian Candler wrote:
>>
>>> So in short, you are saying that the byte sequence 0x81, 0x47 written by
>>> SQLite decodes by binary.Uvarint to 9089, but you wanted it to decode to
>>> 199.
>>>
>>> What this means is: the encoding that SQLite has chosen to use is *not*
>>> the varint as defined by protobuf (and implemented by the Go standard
>>> library). And therefore, you do indeed need to write your own custom
>>> decoding function.
>>>
>>> The SQLite file format is defined here:
>>> https://www.sqlite.org/fileformat.html
>>>
>>> *A variable-length integer or "varint" is a static Huffman encoding of
>>> 64-bit twos-complement integers that uses less space for small positive
>>> values. A varint is between 1 and 9 bytes in length. The varint consists of
>>> either zero or more bytes which have the high-order bit set followed by a
>>> single byte with the high-order bit clear, or nine bytes, whichever is
>>> shorter. The lower seven bits of each of the first eight bytes and all 8
>>> bits of the ninth byte are used to reconstruct the 64-bit twos-complement
>>> integer. Varints are big-endian: bits taken from the earlier byte of the
>>> varint are more significant than bits taken from the later bytes.*
>>>
>>> And for protobuf, see:
>>> https://protobuf.dev/programming-guides/encoding/#varints
>>>
>>> On Saturday, 4 October 2025 at 01:31:25 UTC+1 R. Men wrote:
>>>
>>>> Sure, I'll share my code and what I'm trying to do. Thank you all for
>>>> the help so far. My program reads the sql table's metadata to determine
>>>> the
>>>> type and length of each column in the table. These values are encoded as
>>>> varint of unsigned bigendian integers. I already validated the expected
>>>> values match the tables's actual data type/size.
>>>>
>>>> package main
>>>>
>>>> import (
>>>> "encoding/binary"
>>>> "fmt"
>>>> )
>>>>
>>>> func main() {
>>>> // SQLite format 3, sample DB file record header
>>>> //Expected: 7 23 27 27 1 199
>>>> // |-------| |-------| |-------| |-------|
>>>> |------| |----------------|
>>>> inputs := []byte{0x07, 0x17, 0x1b, 0x1b, 0x01, 0x81, 0x47}
>>>> offset := 0
>>>> for remaining := len(inputs); remaining > 0; {
>>>> d, n := binary.Uvarint(inputs[offset:])
>>>> if n <= 0 {
>>>> break
>>>> }
>>>>
>>>> remaining -= n
>>>> offset += n
>>>> fmt.Println(d, n)
>>>>
>>>> // Actual output
>>>> // 7 1
>>>> // 23 1
>>>> // 27 1
>>>> // 27 1
>>>> // 1 1
>>>> // 9089 2
>>>> }
>>>> }
>>>>
>>>> I now see why I get the 9089 figure after looking at Uvarint source
>>>> code (
>>>> https://cs.opensource.google/go/go/+/refs/tags/go1.25.1:src/encoding/binary/varint.go
>>>> ):
>>>>
>>>> func Uvarint(buf []byte) (uint64, int) {
>>>> var x uint64
>>>> var s uint
>>>> for i, b := range buf {
>>>> if i == MaxVarintLen64 {
>>>> // Catch byte reads past MaxVarintLen64.
>>>> // See issue https://golang.org/issues/41185
>>>> return 0, -(i + 1) // overflow
>>>> }
>>>> if b < 0x80 {
>>>> if i == MaxVarintLen64-1 && b > 1 {
>>>> return 0, -(i + 1) // overflow
>>>> }
>>>> return x | uint64(b)<<s, i + 1
>>>> }
>>>> x |= uint64(b&0x7f) << s
>>>> s += 7
>>>> }
>>>> return 0, 0
>>>> }
>>>>
>>>> Here I see the bits after the first byte are left-shifted by 7 before
>>>> concatenating and left-padding.
>>>> My solution so far has been to create custom uvarint function that
>>>> performs the left-shift before the concat, preserving the byte order.
>>>>
>>>> func Uvarint(buf []byte) (uint64, int) {
>>>> var x uint64
>>>> var s uint
>>>> for i, b := range buf {
>>>> if i == MaxVarintLen64 {
>>>> // Catch byte reads past MaxVarintLen64.
>>>> // See issue https://golang.org/issues/41185
>>>> return 0, -(i + 1) // overflow
>>>> }
>>>> if b < 0x80 {
>>>> if i == MaxVarintLen64-1 && b > 1 {
>>>> return 0, -(i + 1) // overflow
>>>> }
>>>> x <<= s
>>>> return x | uint64(b), i + 1
>>>> }
>>>> x <<= s
>>>> x |= uint64(b&0x7f)
>>>> s += 7
>>>> }
>>>> return 0, 0
>>>> }
>>>>
>>>> I would prefer to use the go library's functions if at all possible
>>>> rather than make my own but so far I haven't found alternatives or even
>>>> discussions on this topic. If anything's unclear let me know. Cheers.
>>>>
>>>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/golang-nuts/9484e18f-96e1-4acd-bf95-58883ed3b993n%40googlegroups.com.