update vendor

This commit is contained in:
ston1th 2025-10-15 22:37:57 +02:00
commit 8675bc4b7a
328 changed files with 27166 additions and 6537 deletions

View file

@ -1,4 +1,4 @@
Copyright (C) 2018 Marius Schellenberger Copyright (C) 2025 Marius Schellenberger
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
package jwt package jwt

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
package jwt package jwt
@ -30,37 +30,28 @@ func (c Claims) GetBool(key string) (b bool) {
// GetInt returns an int from the claims map // GetInt returns an int from the claims map
func (c Claims) GetInt(key string) (i int) { func (c Claims) GetInt(key string) (i int) {
if f, ok := c.getFloat64(key); ok { i = int(c.GetInt64(key))
return int(f)
}
if v, ok := c.Get(key); ok && v != nil {
i, _ = v.(int)
}
return return
} }
// GetInt64 returns an int64 from the claims map // GetInt64 returns an int64 from the claims map
func (c Claims) GetInt64(key string) (i int64) { func (c Claims) GetInt64(key string) (i int64) {
if f, ok := c.getFloat64(key); ok {
return int64(f)
}
if v, ok := c.Get(key); ok && v != nil { if v, ok := c.Get(key); ok && v != nil {
i, _ = v.(int64) switch val := v.(type) {
case int64:
i = val
case float64:
i = int64(val)
}
} }
return return
} }
// getFloat64 returns a float64 and ok from the claims map // getFloat64 returns a float64 and ok from the claims map
func (c Claims) getFloat64(key string) (f float64, fok bool) {
if v, ok := c.Get(key); ok && v != nil {
f, fok = v.(float64)
}
return
}
// GetFloat64 returns a float64 from the claims map
func (c Claims) GetFloat64(key string) (f float64) { func (c Claims) GetFloat64(key string) (f float64) {
f, _ = c.getFloat64(key) if v, ok := c.Get(key); ok && v != nil {
f, _ = v.(float64)
}
return return
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
package jwt package jwt

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
package jwt package jwt
@ -14,19 +14,25 @@ const (
HS512Name = "HS512" HS512Name = "HS512"
) )
// ParseHash returns the Hash type equal to the input string // NewHash returns the Hash type equal to the input string
func ParseHash(alg string) Hash { func NewHash(alg string) Hash {
switch alg { switch alg {
case HS256Name: case HS256Name:
return NewHS256() return hs256
case HS384Name: case HS384Name:
return NewHS384() return hs384
case HS512Name: case HS512Name:
return NewHS512() return hs512
} }
return nil return nil
} }
var (
hs256 = HS256{}
hs384 = HS384{}
hs512 = HS512{}
)
// Hash is the hashsum interface for signing the jwt // Hash is the hashsum interface for signing the jwt
type Hash interface { type Hash interface {
Hash() hash.Hash Hash() hash.Hash
@ -36,11 +42,6 @@ type Hash interface {
// HS256 implements the Hash interface with SHA256 // HS256 implements the Hash interface with SHA256
type HS256 struct{} type HS256 struct{}
// NewHS256 returns a new HS256 instance
func NewHS256() Hash {
return HS256{}
}
// Alg returns the algorithm name "HS256" // Alg returns the algorithm name "HS256"
func (HS256) Alg() string { func (HS256) Alg() string {
return HS256Name return HS256Name
@ -54,11 +55,6 @@ func (HS256) Hash() hash.Hash {
// HS384 implements the Hash interface with SHA384 // HS384 implements the Hash interface with SHA384
type HS384 struct{} type HS384 struct{}
// NewHS384 returns a new HS384 instance
func NewHS384() Hash {
return HS384{}
}
// Alg returns the algorithm name "HS384" // Alg returns the algorithm name "HS384"
func (HS384) Alg() string { func (HS384) Alg() string {
return HS384Name return HS384Name
@ -72,11 +68,6 @@ func (HS384) Hash() hash.Hash {
// HS512 implements the Hash interface with SHA512 // HS512 implements the Hash interface with SHA512
type HS512 struct{} type HS512 struct{}
// NewHS512 returns a new HS512 instance
func NewHS512() Hash {
return HS512{}
}
// Alg returns the algorithm name "HS512" // Alg returns the algorithm name "HS512"
func (HS512) Alg() string { func (HS512) Alg() string {
return HS512Name return HS512Name

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
// Package jwt provides a easy to use JSON Web Token and blacklisting library // Package jwt provides a easy to use JSON Web Token and blacklisting library
package jwt package jwt
@ -31,6 +31,8 @@ const (
ExpClaim = "exp" ExpClaim = "exp"
// NbfClaim is the nbf claim name // NbfClaim is the nbf claim name
NbfClaim = "nbf" NbfClaim = "nbf"
// NonceClaim
NonceClaim = "nonce"
// TokenSeparator is the tokens separator char // TokenSeparator is the tokens separator char
TokenSeparator = "." TokenSeparator = "."
) )
@ -57,12 +59,37 @@ var (
ErrInvalidKeySize = errors.New(jwtErr + "invalid secret key size") ErrInvalidKeySize = errors.New(jwtErr + "invalid secret key size")
) )
type JWTOption func(*JWT)
func WithExpiry(expiry time.Duration) JWTOption {
return func(jwt *JWT) {
jwt.expiry = expiry
}
}
func WithBlacklist(blacklist Blacklist) JWTOption {
return func(jwt *JWT) {
jwt.blacklist = blacklist
}
}
func WithSecret(secret io.Reader) JWTOption {
return func(jwt *JWT) {
jwt.secretReader = secret
}
}
func WithNonce() JWTOption {
return func(jwt *JWT) {
jwt.nonce = true
}
}
// JWT represents the JSON Web Token signing and blacklisting infrastructure // JWT represents the JSON Web Token signing and blacklisting infrastructure
type JWT struct { type JWT struct {
secretReader io.Reader
key []byte key []byte
expiry time.Duration expiry time.Duration
blacklist Blacklist blacklist Blacklist
nonce bool
stopOnce sync.Once stopOnce sync.Once
done chan struct{} done chan struct{}
} }
@ -73,14 +100,18 @@ type JWT struct {
// If secret is nil the DefaultSecretReader is used. // If secret is nil the DefaultSecretReader is used.
// If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens. // If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens.
// Call the Stop() method to exit the goroutine. // Call the Stop() method to exit the goroutine.
func New(expiry time.Duration, blacklist Blacklist, secret io.Reader) (*JWT, error) { func New(options ...JWTOption) (*JWT, error) {
if expiry <= 0 { jwt := &JWT{}
expiry = DefaultExpiry for _, option := range options {
option(jwt)
} }
if secret == nil { if jwt.expiry <= 0 {
secret = DefaultSecretReader jwt.expiry = DefaultExpiry
} }
secret = io.LimitReader(secret, KeySize) if jwt.secretReader == nil {
jwt.secretReader = DefaultSecretReader
}
secret := io.LimitReader(jwt.secretReader, KeySize)
key := make([]byte, KeySize) key := make([]byte, KeySize)
i, err := secret.Read(key) i, err := secret.Read(key)
if err != nil { if err != nil {
@ -89,12 +120,8 @@ func New(expiry time.Duration, blacklist Blacklist, secret io.Reader) (*JWT, err
if i < KeySize { if i < KeySize {
return nil, ErrInvalidKeySize return nil, ErrInvalidKeySize
} }
jwt := &JWT{ jwt.key = key
key: key, if jwt.blacklist != nil {
expiry: expiry,
blacklist: blacklist,
}
if blacklist != nil {
jwt.done = make(chan struct{}) jwt.done = make(chan struct{})
go jwt.clean() go jwt.clean()
} }
@ -128,7 +155,7 @@ func (jwt *JWT) Invalidate(t *Token) error {
if t.Header.GetString(TypClaim) != Typ { if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT return ErrNoJWT
} }
h := ParseHash(t.Header.GetString(AlgClaim)) h := NewHash(t.Header.GetString(AlgClaim))
if h == nil { if h == nil {
return ErrUnsupportedAlg return ErrUnsupportedAlg
} }
@ -190,7 +217,7 @@ func (jwt *JWT) Sign(t *Token) (err error) {
if t.Header.GetString(TypClaim) != Typ { if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT return ErrNoJWT
} }
h := ParseHash(t.Header.GetString(AlgClaim)) h := NewHash(t.Header.GetString(AlgClaim))
if h == nil { if h == nil {
return ErrUnsupportedAlg return ErrUnsupportedAlg
} }
@ -201,6 +228,9 @@ func (jwt *JWT) Sign(t *Token) (err error) {
if _, ok := t.Claims.Get(NbfClaim); !ok { if _, ok := t.Claims.Get(NbfClaim); !ok {
t.Claims.Set(NbfClaim, NewNbf(now)) t.Claims.Set(NbfClaim, NewNbf(now))
} }
if jwt.nonce {
t.Claims.Set(NonceClaim, enc.EncodeToString(key16()))
}
head, err := json.Marshal(t.Header) head, err := json.Marshal(t.Header)
if err != nil { if err != nil {
return return
@ -230,7 +260,7 @@ func (jwt *JWT) Verify(t *Token) error {
if t.Header.GetString(TypClaim) != Typ { if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT return ErrNoJWT
} }
h := ParseHash(t.Header.GetString(AlgClaim)) h := NewHash(t.Header.GetString(AlgClaim))
if h == nil { if h == nil {
return ErrUnsupportedAlg return ErrUnsupportedAlg
} }

View file

@ -0,0 +1,11 @@
// Copyright (C) 2025 Marius Schellenberger
package jwt
import "crypto/rand"
func key16() []byte {
b := make([]byte, 16)
rand.Read(b)
return b
}

View file

@ -1,3 +1,5 @@
// Copyright (C) 2025 Marius Schellenberger
package jwt package jwt
import "time" import "time"

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Marius Schellenberger // Copyright (C) 2025 Marius Schellenberger
package jwt package jwt
@ -45,7 +45,7 @@ func NewToken(claims Claims, hash Hash) *Token {
claims = make(Claims) claims = make(Claims)
} }
if hash == nil { if hash == nil {
hash = NewHS256() hash = NewHash(HS256Name)
} }
return &Token{ return &Token{
Header: Claims{ Header: Claims{

View file

@ -11,7 +11,6 @@ steps:
commands: commands:
- go get -t - go get -t
- go test - go test
- go test -race -run TestConcurrent*
- go build -tags appengine - go build -tags appengine
- go test -tags appengine - go test -tags appengine
- GOARCH=386 go build - GOARCH=386 go build

View file

@ -1,107 +0,0 @@
.PHONY: help all test format fmtcheck vet lint qa deps clean nuke ser fetch-real-roaring-datasets
# Display general help about this command
help:
@echo ""
@echo "The following commands are available:"
@echo ""
@echo " make qa : Run all the tests"
@echo " make test : Run the unit tests"
@echo ""
@echo " make format : Format the source code"
@echo " make fmtcheck : Check if the source code has been formatted"
@echo " make vet : Check for suspicious constructs"
@echo " make lint : Check for style errors"
@echo ""
@echo " make deps : Get the dependencies"
@echo " make clean : Remove any build artifact"
@echo " make nuke : Deletes any intermediate file"
@echo ""
@echo " make fuzz-smat : Fuzzy testing with smat"
@echo " make fuzz-stream : Fuzzy testing with stream deserialization"
@echo " make fuzz-buffer : Fuzzy testing with buffer deserialization"
@echo ""
# Alias for help target
all: help
test:
go test
go test -race -run TestConcurrent*
# Format the source code
format:
@find ./ -type f -name "*.go" -exec gofmt -w {} \;
# Check if the source code has been formatted
fmtcheck:
@mkdir -p target
@find ./ -type f -name "*.go" -exec gofmt -d {} \; | tee target/format.diff
@test ! -s target/format.diff || { echo "ERROR: the source code has not been formatted - please use 'make format' or 'gofmt'"; exit 1; }
# Check for syntax errors
vet:
GOPATH=$(GOPATH) go vet ./...
# Check for style errors
lint:
GOPATH=$(GOPATH) PATH=$(GOPATH)/bin:$(PATH) golint ./...
# Alias to run all quality-assurance checks
qa: fmtcheck test vet lint
# --- INSTALL ---
# Get the dependencies
deps:
GOPATH=$(GOPATH) go get github.com/stretchr/testify
GOPATH=$(GOPATH) go get github.com/bits-and-blooms/bitset
GOPATH=$(GOPATH) go get github.com/golang/lint/golint
GOPATH=$(GOPATH) go get github.com/mschoch/smat
GOPATH=$(GOPATH) go get github.com/dvyukov/go-fuzz/go-fuzz
GOPATH=$(GOPATH) go get github.com/dvyukov/go-fuzz/go-fuzz-build
GOPATH=$(GOPATH) go get github.com/glycerine/go-unsnap-stream
GOPATH=$(GOPATH) go get github.com/philhofer/fwd
GOPATH=$(GOPATH) go get github.com/jtolds/gls
fuzz-smat:
go test -tags=gofuzz -run=TestGenerateSmatCorpus
go-fuzz-build -func FuzzSmat github.com/RoaringBitmap/roaring
go-fuzz -bin=./roaring-fuzz.zip -workdir=workdir/ -timeout=200
fuzz-stream:
go-fuzz-build -func FuzzSerializationStream github.com/RoaringBitmap/roaring
go-fuzz -bin=./roaring-fuzz.zip -workdir=workdir/ -timeout=200
fuzz-buffer:
go-fuzz-build -func FuzzSerializationBuffer github.com/RoaringBitmap/roaring
go-fuzz -bin=./roaring-fuzz.zip -workdir=workdir/ -timeout=200
# Remove any build artifact
clean:
GOPATH=$(GOPATH) go clean ./...
# Deletes any intermediate file
nuke:
rm -rf ./target
GOPATH=$(GOPATH) go clean -i ./...
cover:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
fetch-real-roaring-datasets:
# pull github.com/RoaringBitmap/real-roaring-datasets -> testdata/real-roaring-datasets
git submodule init
git submodule update

View file

@ -1,5 +1,7 @@
roaring [![GoDoc](https://godoc.org/github.com/RoaringBitmap/roaring/roaring64?status.svg)](https://godoc.org/github.com/RoaringBitmap/roaring/roaring64) [![Go Report Card](https://goreportcard.com/badge/RoaringBitmap/roaring)](https://goreportcard.com/report/github.com/RoaringBitmap/roaring) # roaring
[![Build Status](https://cloud.drone.io/api/badges/RoaringBitmap/roaring/status.svg)](https://cloud.drone.io/RoaringBitmap/roaring)
[![GoDoc](https://godoc.org/github.com/RoaringBitmap/roaring?status.svg)](https://godoc.org/github.com/RoaringBitmap/roaring) [![Go Report Card](https://goreportcard.com/badge/RoaringBitmap/roaring)](https://goreportcard.com/report/github.com/RoaringBitmap/roaring)
![Go-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-CI/badge.svg) ![Go-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-CI/badge.svg)
![Go-ARM-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-ARM-CI/badge.svg) ![Go-ARM-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-ARM-CI/badge.svg)
![Go-Windows-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-Windows-CI/badge.svg) ![Go-Windows-CI](https://github.com/RoaringBitmap/roaring/workflows/Go-Windows-CI/badge.svg)
@ -8,7 +10,7 @@ roaring [![GoDoc](https://godoc.org/github.com/RoaringBitmap/roaring/roaring64?s
This is a go version of the Roaring bitmap data structure. This is a go version of the Roaring bitmap data structure.
Roaring bitmaps are used by several major systems such as [Apache Lucene][lucene] and derivative systems such as [Solr][solr] and Roaring bitmaps are used by several major systems such as [Apache Lucene][lucene] and derivative systems such as [Solr][solr] and
[Elasticsearch][elasticsearch], [Apache Druid (Incubating)][druid], [LinkedIn Pinot][pinot], [Netflix Atlas][atlas], [Apache Spark][spark], [OpenSearchServer][opensearchserver], [anacrolix/torrent][anacrolix/torrent], [Whoosh][whoosh], [Pilosa][pilosa], [Microsoft Visual Studio Team Services (VSTS)][vsts], and eBay's [Apache Kylin][kylin]. The YouTube SQL Engine, [Google Procella](https://research.google/pubs/pub48388/), uses Roaring bitmaps for indexing. [Elasticsearch][elasticsearch], [Apache Druid (Incubating)][druid], [LinkedIn Pinot][pinot], [Netflix Atlas][atlas], [Apache Spark][spark], [OpenSearchServer][opensearchserver], [anacrolix/torrent][anacrolix/torrent], [Whoosh][whoosh], [Redpanda](https://github.com/redpanda-data/redpanda), [Pilosa][pilosa], [Microsoft Visual Studio Team Services (VSTS)][vsts], and eBay's [Apache Kylin][kylin]. The YouTube SQL Engine, [Google Procella](https://research.google/pubs/pub48388/), uses Roaring bitmaps for indexing.
[lucene]: https://lucene.apache.org/ [lucene]: https://lucene.apache.org/
[solr]: https://lucene.apache.org/solr/ [solr]: https://lucene.apache.org/solr/
@ -31,17 +33,17 @@ Roaring bitmaps are found to work well in many important applications:
The ``roaring`` Go library is used by The ``roaring`` Go library is used by
* [anacrolix/torrent] * [anacrolix/torrent]
* [runv](https://github.com/hyperhq/runv)
* [InfluxDB](https://www.influxdata.com) * [InfluxDB](https://www.influxdata.com)
* [Pilosa](https://www.pilosa.com/) * [Pilosa](https://www.pilosa.com/)
* [Bleve](http://www.blevesearch.com) * [Bleve](http://www.blevesearch.com)
* [Weaviate](https://github.com/weaviate/weaviate)
* [lindb](https://github.com/lindb/lindb) * [lindb](https://github.com/lindb/lindb)
* [Elasticell](https://github.com/deepfabric/elasticell) * [Elasticell](https://github.com/deepfabric/elasticell)
* [SourceGraph](https://github.com/sourcegraph/sourcegraph) * [SourceGraph](https://github.com/sourcegraph/sourcegraph)
* [M3](https://github.com/m3db/m3) * [M3](https://github.com/m3db/m3)
* [trident](https://github.com/NetApp/trident) * [trident](https://github.com/NetApp/trident)
* [Husky](https://www.datadoghq.com/blog/engineering/introducing-husky/) * [Husky](https://www.datadoghq.com/blog/engineering/introducing-husky/)
* [FrostDB](https://github.com/polarsignals/frostdb)
This library is used in production in several systems, it is part of the [Awesome Go collection](https://awesome-go.com). This library is used in production in several systems, it is part of the [Awesome Go collection](https://awesome-go.com).
@ -99,7 +101,7 @@ whether you like it or not. That can become very wasteful.
This being said, there are definitively cases where attempting to use compressed bitmaps is wasteful. This being said, there are definitively cases where attempting to use compressed bitmaps is wasteful.
For example, if you have a small universe size. E.g., your bitmaps represent sets of integers For example, if you have a small universe size. E.g., your bitmaps represent sets of integers
from [0,n) where n is small (e.g., n=64 or n=128). If you are able to uncompressed BitSet and from [0,n) where n is small (e.g., n=64 or n=128). If you can use uncompressed BitSet and
it does not blow up your memory usage, then compressed bitmaps are probably not useful it does not blow up your memory usage, then compressed bitmaps are probably not useful
to you. In fact, if you do not need compression, then a BitSet offers remarkable speed. to you. In fact, if you do not need compression, then a BitSet offers remarkable speed.
@ -134,7 +136,7 @@ There is a big problem with these formats however that can hurt you badly in som
Roaring solves this problem. It works in the following manner. It divides the data into chunks of 2<sup>16</sup> integers Roaring solves this problem. It works in the following manner. It divides the data into chunks of 2<sup>16</sup> integers
(e.g., [0, 2<sup>16</sup>), [2<sup>16</sup>, 2 x 2<sup>16</sup>), ...). Within a chunk, it can use an uncompressed bitmap, a simple list of integers, (e.g., [0, 2<sup>16</sup>), [2<sup>16</sup>, 2 x 2<sup>16</sup>), ...). Within a chunk, it can use an uncompressed bitmap, a simple list of integers,
or a list of runs. Whatever format it uses, they all allow you to check for the present of any one value quickly or a list of runs. Whatever format it uses, they all allow you to check for the presence of any one value quickly
(e.g., with a binary search). The net result is that Roaring can compute many operations much faster than run-length-encoded (e.g., with a binary search). The net result is that Roaring can compute many operations much faster than run-length-encoded
formats like WAH, EWAH, Concise... Maybe surprisingly, Roaring also generally offers better compression ratios. formats like WAH, EWAH, Concise... Maybe surprisingly, Roaring also generally offers better compression ratios.

View file

@ -17,8 +17,17 @@ func (ac *arrayContainer) String() string {
} }
func (ac *arrayContainer) fillLeastSignificant16bits(x []uint32, i int, mask uint32) int { func (ac *arrayContainer) fillLeastSignificant16bits(x []uint32, i int, mask uint32) int {
if i < 0 {
panic("negative index")
}
if len(ac.content) == 0 {
return i
}
_ = x[len(ac.content)-1+i]
_ = ac.content[len(ac.content)-1]
for k := 0; k < len(ac.content); k++ { for k := 0; k < len(ac.content); k++ {
x[k+i] = uint32(ac.content[k]) | mask x[k+i] =
uint32(ac.content[k]) | mask
} }
return i + len(ac.content) return i + len(ac.content)
} }
@ -655,10 +664,54 @@ func (ac *arrayContainer) iandNot(a container) container {
} }
func (ac *arrayContainer) iandNotRun16(rc *runContainer16) container { func (ac *arrayContainer) iandNotRun16(rc *runContainer16) container {
rcb := rc.toBitmapContainer() // Fast path: if either the array container or the run container is empty, the result is the array.
acb := ac.toBitmapContainer() if ac.isEmpty() || rc.isEmpty() {
acb.iandNotBitmapSurely(rcb) // Empty
*ac = *(acb.toArrayContainer()) return ac
}
// Fast path: if the run container is full, the result is empty.
if rc.isFull() {
ac.content = ac.content[:0]
return ac
}
current_run := 0
// All values in [start_run, end_end] are part of the run
start_run := rc.iv[current_run].start
end_end := start_run + rc.iv[current_run].length
// We are going to read values in the array at index i, and we are
// going to write them at index pos. So we do in-place processing.
// We always have that pos <= i by construction. So we can either
// overwrite a value just read, or a value that was previous read.
pos := 0
i := 0
for ; i < len(ac.content); i++ {
if ac.content[i] < start_run {
// the value in the array appears before the run [start_run, end_end]
ac.content[pos] = ac.content[i]
pos++
} else if ac.content[i] <= end_end {
// nothing to do, the value is in the array but also in the run.
} else {
// We have the value in the array after the run. We cannot tell
// whether we need to keep it or not. So let us move to another run.
if current_run+1 < len(rc.iv) {
current_run++
start_run = rc.iv[current_run].start
end_end = start_run + rc.iv[current_run].length
i-- // retry with the same i
} else {
// We have exhausted the number of runs. We can keep the rest of the values
// from i to len(ac.content) - 1 inclusively.
break // We are done, the rest of the array will be kept
}
}
}
for ; i < len(ac.content); i++ {
ac.content[pos] = ac.content[i]
pos++
}
// We 'shink' the slice.
ac.content = ac.content[:pos]
return ac return ac
} }

View file

@ -888,13 +888,67 @@ func (bc *bitmapContainer) iandNot(a container) container {
} }
func (bc *bitmapContainer) iandNotArray(ac *arrayContainer) container { func (bc *bitmapContainer) iandNotArray(ac *arrayContainer) container {
acb := ac.toBitmapContainer() if ac.isEmpty() || bc.isEmpty() {
return bc.iandNotBitmapSurely(acb) // Nothing to do.
return bc
}
// Word by word, we remove the elements in ac from bc. The approach is to build
// a mask of the elements to remove, and then apply it to the bitmap.
wordIdx := uint16(0)
mask := uint64(0)
for i, v := range ac.content {
if v/64 != wordIdx {
// Flush the current word.
if i != 0 {
// We're removing bits that are set in the mask and in the current word.
// To figure out the cardinality change, we count the number of bits that
// are set in the mask and in the current word.
mask &= bc.bitmap[wordIdx]
bc.bitmap[wordIdx] &= ^mask
bc.cardinality -= int(popcount(mask))
}
wordIdx = v / 64
mask = 0
}
mask |= 1 << (v % 64)
}
// Flush the last word.
mask &= bc.bitmap[wordIdx]
bc.bitmap[wordIdx] &= ^mask
bc.cardinality -= int(popcount(mask))
if bc.getCardinality() <= arrayDefaultMaxSize {
return bc.toArrayContainer()
}
return bc
} }
func (bc *bitmapContainer) iandNotRun16(rc *runContainer16) container { func (bc *bitmapContainer) iandNotRun16(rc *runContainer16) container {
rcb := rc.toBitmapContainer() if rc.isEmpty() || bc.isEmpty() {
return bc.iandNotBitmapSurely(rcb) // Nothing to do.
return bc
}
wordRangeStart := rc.iv[0].start / 64
wordRangeEnd := (rc.iv[len(rc.iv)-1].last()) / 64 // inclusive
cardinalityChange := popcntSlice(bc.bitmap[wordRangeStart : wordRangeEnd+1]) // before cardinality - after cardinality (for word range)
for _, iv := range rc.iv {
resetBitmapRange(bc.bitmap, int(iv.start), int(iv.last())+1)
}
cardinalityChange -= popcntSlice(bc.bitmap[wordRangeStart : wordRangeEnd+1])
bc.cardinality -= int(cardinalityChange)
if bc.getCardinality() <= arrayDefaultMaxSize {
return bc.toArrayContainer()
}
return bc
} }
func (bc *bitmapContainer) andNotArray(value2 *arrayContainer) container { func (bc *bitmapContainer) andNotArray(value2 *arrayContainer) container {
@ -1062,7 +1116,6 @@ func (bc *bitmapContainer) PrevSetBit(i int) int {
// reference the java implementation // reference the java implementation
// https://github.com/RoaringBitmap/RoaringBitmap/blob/master/src/main/java/org/roaringbitmap/BitmapContainer.java#L875-L892 // https://github.com/RoaringBitmap/RoaringBitmap/blob/master/src/main/java/org/roaringbitmap/BitmapContainer.java#L875-L892
//
func (bc *bitmapContainer) numberOfRuns() int { func (bc *bitmapContainer) numberOfRuns() int {
if bc.cardinality == 0 { if bc.cardinality == 0 {
return 0 return 0

View file

@ -10,6 +10,11 @@ type ByteInput interface {
// Next returns a slice containing the next n bytes from the buffer, // Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by Read. // advancing the buffer as if the bytes had been returned by Read.
Next(n int) ([]byte, error) Next(n int) ([]byte, error)
// NextReturnsSafeSlice returns true if Next() returns a safe slice as opposed
// to a slice that points to an underlying buffer possibly owned by another system.
// When NextReturnsSafeSlice returns false, the result from Next() should be copied
// before it is modified (i.e., it is immutable).
NextReturnsSafeSlice() bool
// ReadUInt32 reads uint32 with LittleEndian order // ReadUInt32 reads uint32 with LittleEndian order
ReadUInt32() (uint32, error) ReadUInt32() (uint32, error)
// ReadUInt16 reads uint16 with LittleEndian order // ReadUInt16 reads uint16 with LittleEndian order
@ -42,6 +47,25 @@ type ByteBuffer struct {
off int off int
} }
// NewByteBuffer creates a new ByteBuffer.
func NewByteBuffer(buf []byte) *ByteBuffer {
return &ByteBuffer{
buf: buf,
}
}
var _ io.Reader = (*ByteBuffer)(nil)
// Read implements io.Reader.
func (b *ByteBuffer) Read(p []byte) (int, error) {
data, err := b.Next(len(p))
if err != nil {
return 0, err
}
copy(p, data)
return len(data), nil
}
// Next returns a slice containing the next n bytes from the reader // Next returns a slice containing the next n bytes from the reader
// If there are fewer bytes than the given n, io.ErrUnexpectedEOF will be returned // If there are fewer bytes than the given n, io.ErrUnexpectedEOF will be returned
func (b *ByteBuffer) Next(n int) ([]byte, error) { func (b *ByteBuffer) Next(n int) ([]byte, error) {
@ -57,6 +81,12 @@ func (b *ByteBuffer) Next(n int) ([]byte, error) {
return data, nil return data, nil
} }
// NextReturnsSafeSlice returns false since ByteBuffer might hold
// an array owned by some other systems.
func (b *ByteBuffer) NextReturnsSafeSlice() bool {
return false
}
// ReadUInt32 reads uint32 with LittleEndian order // ReadUInt32 reads uint32 with LittleEndian order
func (b *ByteBuffer) ReadUInt32() (uint32, error) { func (b *ByteBuffer) ReadUInt32() (uint32, error) {
if len(b.buf)-b.off < 4 { if len(b.buf)-b.off < 4 {
@ -109,26 +139,45 @@ func (b *ByteBuffer) Reset(buf []byte) {
type ByteInputAdapter struct { type ByteInputAdapter struct {
r io.Reader r io.Reader
readBytes int readBytes int
buf [4]byte
}
var _ io.Reader = (*ByteInputAdapter)(nil)
// Read implements io.Reader.
func (b *ByteInputAdapter) Read(buf []byte) (int, error) {
m, err := io.ReadAtLeast(b.r, buf, len(buf))
b.readBytes += m
if err != nil {
return 0, err
}
return m, nil
} }
// Next returns a slice containing the next n bytes from the buffer, // Next returns a slice containing the next n bytes from the buffer,
// advancing the buffer as if the bytes had been returned by Read. // advancing the buffer as if the bytes had been returned by Read.
func (b *ByteInputAdapter) Next(n int) ([]byte, error) { func (b *ByteInputAdapter) Next(n int) ([]byte, error) {
buf := make([]byte, n) buf := make([]byte, n)
m, err := io.ReadAtLeast(b.r, buf, n) _, err := b.Read(buf)
b.readBytes += m
if err != nil { if err != nil {
return nil, err return nil, err
} }
return buf, nil return buf, nil
} }
// NextReturnsSafeSlice returns true since ByteInputAdapter always returns a slice
// allocated with make([]byte, ...)
func (b *ByteInputAdapter) NextReturnsSafeSlice() bool {
return true
}
// ReadUInt32 reads uint32 with LittleEndian order // ReadUInt32 reads uint32 with LittleEndian order
func (b *ByteInputAdapter) ReadUInt32() (uint32, error) { func (b *ByteInputAdapter) ReadUInt32() (uint32, error) {
buf, err := b.Next(4) buf := b.buf[:4]
_, err := b.Read(buf)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -138,8 +187,8 @@ func (b *ByteInputAdapter) ReadUInt32() (uint32, error) {
// ReadUInt16 reads uint16 with LittleEndian order // ReadUInt16 reads uint16 with LittleEndian order
func (b *ByteInputAdapter) ReadUInt16() (uint16, error) { func (b *ByteInputAdapter) ReadUInt16() (uint16, error) {
buf, err := b.Next(2) buf := b.buf[:2]
_, err := b.Read(buf)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -13,6 +13,7 @@ import (
"strconv" "strconv"
"github.com/RoaringBitmap/roaring/internal" "github.com/RoaringBitmap/roaring/internal"
"github.com/bits-and-blooms/bitset"
) )
// Bitmap represents a compressed bitmap where you can add integers. // Bitmap represents a compressed bitmap where you can add integers.
@ -53,13 +54,182 @@ func (rb *Bitmap) ToBytes() ([]byte, error) {
return rb.highlowcontainer.toBytes() return rb.highlowcontainer.toBytes()
} }
const wordSize = uint64(64)
const log2WordSize = uint64(6)
const capacity = ^uint64(0)
const bitmapContainerSize = (1 << 16) / 64 // bitmap size in words
// DenseSize returns the size of the bitmap when stored as a dense bitmap.
func (rb *Bitmap) DenseSize() uint64 {
if rb.highlowcontainer.size() == 0 {
return 0
}
maximum := 1 + uint64(rb.Maximum())
if maximum > (capacity - wordSize + 1) {
return uint64(capacity >> log2WordSize)
}
return uint64((maximum + (wordSize - 1)) >> log2WordSize)
}
// ToDense returns a slice of uint64s representing the bitmap as a dense bitmap.
// Useful to convert a roaring bitmap to a format that can be used by other libraries
// like https://github.com/bits-and-blooms/bitset or https://github.com/kelindar/bitmap
func (rb *Bitmap) ToDense() []uint64 {
sz := rb.DenseSize()
if sz == 0 {
return nil
}
bitmap := make([]uint64, sz)
rb.WriteDenseTo(bitmap)
return bitmap
}
// FromDense creates a bitmap from a slice of uint64s representing the bitmap as a dense bitmap.
// Useful to convert bitmaps from libraries like https://github.com/bits-and-blooms/bitset or
// https://github.com/kelindar/bitmap into roaring bitmaps fast and with convenience.
//
// This function will not create any run containers, only array and bitmap containers. It's up to
// the caller to call RunOptimize if they want to further compress the runs of consecutive values.
//
// When doCopy is true, the bitmap is copied into a new slice for each bitmap container.
// This is useful when the bitmap is going to be modified after this function returns or if it's
// undesirable to hold references to large bitmaps which the GC would not be able to collect.
// One copy can still happen even when doCopy is false if the bitmap length is not divisible
// by bitmapContainerSize.
//
// See also FromBitSet.
func FromDense(bitmap []uint64, doCopy bool) *Bitmap {
sz := (len(bitmap) + bitmapContainerSize - 1) / bitmapContainerSize // round up
rb := &Bitmap{
highlowcontainer: roaringArray{
containers: make([]container, 0, sz),
keys: make([]uint16, 0, sz),
needCopyOnWrite: make([]bool, 0, sz),
},
}
rb.FromDense(bitmap, doCopy)
return rb
}
// FromDense unmarshalls from a slice of uint64s representing the bitmap as a dense bitmap.
// Useful to convert bitmaps from libraries like https://github.com/bits-and-blooms/bitset or
// https://github.com/kelindar/bitmap into roaring bitmaps fast and with convenience.
// Callers are responsible for ensuring that the bitmap is empty before calling this function.
//
// This function will not create any run containers, only array and bitmap containers. It is up to
// the caller to call RunOptimize if they want to further compress the runs of consecutive values.
//
// When doCopy is true, the bitmap is copied into a new slice for each bitmap container.
// This is useful when the bitmap is going to be modified after this function returns or if it's
// undesirable to hold references to large bitmaps which the GC would not be able to collect.
// One copy can still happen even when doCopy is false if the bitmap length is not divisible
// by bitmapContainerSize.
//
// See FromBitSet.
func (rb *Bitmap) FromDense(bitmap []uint64, doCopy bool) {
if len(bitmap) == 0 {
return
}
var k uint16
const size = bitmapContainerSize
for len(bitmap) > 0 {
hi := size
if len(bitmap) < size {
hi = len(bitmap)
}
words := bitmap[:hi]
count := int(popcntSlice(words))
switch {
case count > arrayDefaultMaxSize:
c := &bitmapContainer{cardinality: count, bitmap: words}
cow := true
if doCopy || len(words) < size {
c.bitmap = make([]uint64, size)
copy(c.bitmap, words)
cow = false
}
rb.highlowcontainer.appendContainer(k, c, cow)
case count > 0:
c := &arrayContainer{content: make([]uint16, count)}
var pos, base int
for _, w := range words {
for w != 0 {
t := w & -w
c.content[pos] = uint16(base + int(popcount(t-1)))
pos++
w ^= t
}
base += 64
}
rb.highlowcontainer.appendContainer(k, c, false)
}
bitmap = bitmap[hi:]
k++
}
}
// WriteDenseTo writes to a slice of uint64s representing the bitmap as a dense bitmap.
// Callers are responsible for allocating enough space in the bitmap using DenseSize.
// Useful to convert a roaring bitmap to a format that can be used by other libraries
// like https://github.com/bits-and-blooms/bitset or https://github.com/kelindar/bitmap
func (rb *Bitmap) WriteDenseTo(bitmap []uint64) {
for i, ct := range rb.highlowcontainer.containers {
hb := uint32(rb.highlowcontainer.keys[i]) << 16
switch c := ct.(type) {
case *arrayContainer:
for _, x := range c.content {
n := int(hb | uint32(x))
bitmap[n>>log2WordSize] |= uint64(1) << uint(x%64)
}
case *bitmapContainer:
copy(bitmap[int(hb)>>log2WordSize:], c.bitmap)
case *runContainer16:
for j := range c.iv {
start := uint32(c.iv[j].start)
end := start + uint32(c.iv[j].length) + 1
lo := int(hb|start) >> log2WordSize
hi := int(hb|(end-1)) >> log2WordSize
if lo == hi {
bitmap[lo] |= (^uint64(0) << uint(start%64)) &
(^uint64(0) >> (uint(-end) % 64))
continue
}
bitmap[lo] |= ^uint64(0) << uint(start%64)
for n := lo + 1; n < hi; n++ {
bitmap[n] = ^uint64(0)
}
bitmap[hi] |= ^uint64(0) >> (uint(-end) % 64)
}
default:
panic("unsupported container type")
}
}
}
// Checksum computes a hash (currently FNV-1a) for a bitmap that is suitable for // Checksum computes a hash (currently FNV-1a) for a bitmap that is suitable for
// using bitmaps as elements in hash sets or as keys in hash maps, as well as // using bitmaps as elements in hash sets or as keys in hash maps, as well as
// generally quicker comparisons. // generally quicker comparisons.
// The implementation is biased towards efficiency in little endian machines, so // The implementation is biased towards efficiency in little endian machines, so
// expect some extra CPU cycles and memory to be used if your machine is big endian. // expect some extra CPU cycles and memory to be used if your machine is big endian.
// Likewise, don't use this to verify integrity unless you're certain you'll load // Likewise, do not use this to verify integrity unless you are certain you will load
// the bitmap on a machine with the same endianess used to create it. // the bitmap on a machine with the same endianess used to create it. (Thankfully
// very few people use big endian machines these days.)
func (rb *Bitmap) Checksum() uint64 { func (rb *Bitmap) Checksum() uint64 {
const ( const (
offset = 14695981039346656037 offset = 14695981039346656037
@ -106,6 +276,20 @@ func (rb *Bitmap) Checksum() uint64 {
return hash return hash
} }
// FromUnsafeBytes reads a serialized version of this bitmap from the byte buffer without copy.
// It is the caller's responsibility to ensure that the input data is not modified and remains valid for the entire lifetime of this bitmap.
// This method avoids small allocations but holds references to the input data buffer. It is GC-friendly, but it may consume more memory eventually.
// The containers in the resulting bitmap are immutable containers tied to the provided byte array and they rely on
// copy-on-write which means that modifying them creates copies. Thus FromUnsafeBytes is more likely to be appropriate for read-only use cases,
// when the resulting bitmap can be considered immutable.
//
// See also the FromBuffer function.
// See https://github.com/RoaringBitmap/roaring/pull/395 for more details.
func (rb *Bitmap) FromUnsafeBytes(data []byte, cookieHeader ...byte) (p int64, err error) {
stream := internal.NewByteBuffer(data)
return rb.ReadFrom(stream)
}
// ReadFrom reads a serialized version of this bitmap from stream. // ReadFrom reads a serialized version of this bitmap from stream.
// The format is compatible with other RoaringBitmap // The format is compatible with other RoaringBitmap
// implementations (Java, C) and is documented here: // implementations (Java, C) and is documented here:
@ -114,12 +298,18 @@ func (rb *Bitmap) Checksum() uint64 {
// So add cookieHeader to accept the 4-byte data that has been read in roaring64.ReadFrom. // So add cookieHeader to accept the 4-byte data that has been read in roaring64.ReadFrom.
// It is not necessary to pass cookieHeader when call roaring.ReadFrom to read the roaring32 data directly. // It is not necessary to pass cookieHeader when call roaring.ReadFrom to read the roaring32 data directly.
func (rb *Bitmap) ReadFrom(reader io.Reader, cookieHeader ...byte) (p int64, err error) { func (rb *Bitmap) ReadFrom(reader io.Reader, cookieHeader ...byte) (p int64, err error) {
stream := internal.ByteInputAdapterPool.Get().(*internal.ByteInputAdapter) stream, ok := reader.(internal.ByteInput)
stream.Reset(reader) if !ok {
byteInputAdapter := internal.ByteInputAdapterPool.Get().(*internal.ByteInputAdapter)
byteInputAdapter.Reset(reader)
stream = byteInputAdapter
}
p, err = rb.highlowcontainer.readFrom(stream, cookieHeader...) p, err = rb.highlowcontainer.readFrom(stream, cookieHeader...)
internal.ByteInputAdapterPool.Put(stream)
if !ok {
internal.ByteInputAdapterPool.Put(stream.(*internal.ByteInputAdapter))
}
return return
} }
@ -139,12 +329,17 @@ func (rb *Bitmap) ReadFrom(reader io.Reader, cookieHeader ...byte) (p int64, err
// You should *not* change the copy-on-write status of the resulting // You should *not* change the copy-on-write status of the resulting
// bitmaps (SetCopyOnWrite). // bitmaps (SetCopyOnWrite).
// //
// Thus FromBuffer is more likely to be appropriate for read-only use cases,
// when the resulting bitmap can be considered immutable.
//
// If buf becomes unavailable, then a bitmap created with // If buf becomes unavailable, then a bitmap created with
// FromBuffer would be effectively broken. Furthermore, any // FromBuffer would be effectively broken. Furthermore, any
// bitmap derived from this bitmap (e.g., via Or, And) might // bitmap derived from this bitmap (e.g., via Or, And) might
// also be broken. Thus, before making buf unavailable, you should // also be broken. Thus, before making buf unavailable, you should
// call CloneCopyOnWriteContainers on all such bitmaps. // call CloneCopyOnWriteContainers on all such bitmaps.
// //
// See also the FromUnsafeBytes function which can have better performance
// in some cases.
func (rb *Bitmap) FromBuffer(buf []byte) (p int64, err error) { func (rb *Bitmap) FromBuffer(buf []byte) (p int64, err error) {
stream := internal.ByteBufferPool.Get().(*internal.ByteBuffer) stream := internal.ByteBufferPool.Get().(*internal.ByteBuffer)
stream.Reset(buf) stream.Reset(buf)
@ -194,6 +389,16 @@ func (rb *Bitmap) Clear() {
rb.highlowcontainer.clear() rb.highlowcontainer.clear()
} }
// ToBitSet copies the content of the RoaringBitmap into a bitset.BitSet instance
func (rb *Bitmap) ToBitSet() *bitset.BitSet {
return bitset.From(rb.ToDense())
}
// FromBitSet creates a new RoaringBitmap from a bitset.BitSet instance
func FromBitSet(bitset *bitset.BitSet) *Bitmap {
return FromDense(bitset.Bytes(), false)
}
// ToArray creates a new slice containing all of the integers stored in the Bitmap in sorted order // ToArray creates a new slice containing all of the integers stored in the Bitmap in sorted order
func (rb *Bitmap) ToArray() []uint32 { func (rb *Bitmap) ToArray() []uint32 {
array := make([]uint32, rb.GetCardinality()) array := make([]uint32, rb.GetCardinality())
@ -233,7 +438,7 @@ func BoundSerializedSizeInBytes(cardinality uint64, universeSize uint64) uint64
contnbr := (universeSize + uint64(65535)) / uint64(65536) contnbr := (universeSize + uint64(65535)) / uint64(65536)
if contnbr > cardinality { if contnbr > cardinality {
contnbr = cardinality contnbr = cardinality
// we can't have more containers than we have values // we cannot have more containers than we have values
} }
headermax := 8*contnbr + 4 headermax := 8*contnbr + 4
if 4 > (contnbr+7)/8 { if 4 > (contnbr+7)/8 {
@ -341,14 +546,13 @@ func (ii *intIterator) AdvanceIfNeeded(minval uint32) {
// IntIterator is meant to allow you to iterate through the values of a bitmap, see Initialize(a *Bitmap) // IntIterator is meant to allow you to iterate through the values of a bitmap, see Initialize(a *Bitmap)
type IntIterator = intIterator type IntIterator = intIterator
// Initialize configures the existing iterator so that it can iterate through the values of // Initialize configures the existing iterator so that it can iterate through the values of
// the provided bitmap. // the provided bitmap.
// The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove). // The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove).
func (p *intIterator) Initialize(a *Bitmap) { func (ii *intIterator) Initialize(a *Bitmap) {
p.pos = 0 ii.pos = 0
p.highlowcontainer = &a.highlowcontainer ii.highlowcontainer = &a.highlowcontainer
p.init() ii.init()
} }
type intReverseIterator struct { type intReverseIterator struct {
@ -414,10 +618,10 @@ type IntReverseIterator = intReverseIterator
// Initialize configures the existing iterator so that it can iterate through the values of // Initialize configures the existing iterator so that it can iterate through the values of
// the provided bitmap. // the provided bitmap.
// The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove). // The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove).
func (p *intReverseIterator) Initialize(a *Bitmap) { func (ii *intReverseIterator) Initialize(a *Bitmap) {
p.highlowcontainer = &a.highlowcontainer ii.highlowcontainer = &a.highlowcontainer
p.pos = a.highlowcontainer.size() - 1 ii.pos = a.highlowcontainer.size() - 1
p.init() ii.init()
} }
// ManyIntIterable allows you to iterate over the values in a Bitmap // ManyIntIterable allows you to iterate over the values in a Bitmap
@ -495,17 +699,16 @@ func (ii *manyIntIterator) NextMany64(hs64 uint64, buf []uint64) int {
return n return n
} }
// ManyIntIterator is meant to allow you to iterate through the values of a bitmap, see Initialize(a *Bitmap) // ManyIntIterator is meant to allow you to iterate through the values of a bitmap, see Initialize(a *Bitmap)
type ManyIntIterator = manyIntIterator type ManyIntIterator = manyIntIterator
// Initialize configures the existing iterator so that it can iterate through the values of // Initialize configures the existing iterator so that it can iterate through the values of
// the provided bitmap. // the provided bitmap.
// The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove). // The iteration results are undefined if the bitmap is modified (e.g., with Add or Remove).
func (p *manyIntIterator) Initialize(a *Bitmap) { func (ii *manyIntIterator) Initialize(a *Bitmap) {
p.pos = 0 ii.pos = 0
p.highlowcontainer = &a.highlowcontainer ii.highlowcontainer = &a.highlowcontainer
p.init() ii.init()
} }
// String creates a string representation of the Bitmap // String creates a string representation of the Bitmap
@ -847,7 +1050,7 @@ func (rb *Bitmap) Select(x uint32) (uint32, error) {
return uint32(key)<<16 + uint32(c.selectInt(uint16(remaining))), nil return uint32(key)<<16 + uint32(c.selectInt(uint16(remaining))), nil
} }
} }
return 0, fmt.Errorf("can't find %dth integer in a bitmap with only %d items", x, rb.GetCardinality()) return 0, fmt.Errorf("cannot find %dth integer in a bitmap with only %d items", x, rb.GetCardinality())
} }
// And computes the intersection between two bitmaps and stores the result in the current bitmap // And computes the intersection between two bitmaps and stores the result in the current bitmap

View file

@ -4,8 +4,9 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/RoaringBitmap/roaring/internal"
"io" "io"
"github.com/RoaringBitmap/roaring/internal"
) )
type container interface { type container interface {
@ -112,6 +113,7 @@ func newRoaringArray() *roaringArray {
// runOptimize compresses the element containers to minimize space consumed. // runOptimize compresses the element containers to minimize space consumed.
// Q: how does this interact with copyOnWrite and needCopyOnWrite? // Q: how does this interact with copyOnWrite and needCopyOnWrite?
// A: since we aren't changing the logical content, just the representation, // A: since we aren't changing the logical content, just the representation,
//
// we don't bother to check the needCopyOnWrite bits. We replace // we don't bother to check the needCopyOnWrite bits. We replace
// (possibly all) elements of ra.containers in-place with space // (possibly all) elements of ra.containers in-place with space
// optimized versions. // optimized versions.
@ -465,9 +467,7 @@ func (ra *roaringArray) serializedSizeInBytes() uint64 {
return answer return answer
} }
//
// spec: https://github.com/RoaringBitmap/RoaringFormatSpec // spec: https://github.com/RoaringBitmap/RoaringFormatSpec
//
func (ra *roaringArray) writeTo(w io.Writer) (n int64, err error) { func (ra *roaringArray) writeTo(w io.Writer) (n int64, err error) {
hasRun := ra.hasRunCompression() hasRun := ra.hasRunCompression()
isRunSizeInBytes := 0 isRunSizeInBytes := 0
@ -544,15 +544,14 @@ func (ra *roaringArray) writeTo(w io.Writer) (n int64, err error) {
return n, nil return n, nil
} }
//
// spec: https://github.com/RoaringBitmap/RoaringFormatSpec // spec: https://github.com/RoaringBitmap/RoaringFormatSpec
//
func (ra *roaringArray) toBytes() ([]byte, error) { func (ra *roaringArray) toBytes() ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
_, err := ra.writeTo(&buf) _, err := ra.writeTo(&buf)
return buf.Bytes(), err return buf.Bytes(), err
} }
// Reads a serialized roaringArray from a byte slice.
func (ra *roaringArray) readFrom(stream internal.ByteInput, cookieHeader ...byte) (int64, error) { func (ra *roaringArray) readFrom(stream internal.ByteInput, cookieHeader ...byte) (int64, error) {
var cookie uint32 var cookie uint32
var err error var err error
@ -567,6 +566,8 @@ func (ra *roaringArray) readFrom(stream internal.ByteInput, cookieHeader ...byte
return stream.GetReadBytes(), fmt.Errorf("error in roaringArray.readFrom: could not read initial cookie: %s", err) return stream.GetReadBytes(), fmt.Errorf("error in roaringArray.readFrom: could not read initial cookie: %s", err)
} }
} }
// If NextReturnsSafeSlice is false, then willNeedCopyOnWrite should be true
willNeedCopyOnWrite := !stream.NextReturnsSafeSlice()
var size uint32 var size uint32
var isRunBitmap []byte var isRunBitmap []byte
@ -631,7 +632,7 @@ func (ra *roaringArray) readFrom(stream internal.ByteInput, cookieHeader ...byte
key := keycard[2*i] key := keycard[2*i]
card := int(keycard[2*i+1]) + 1 card := int(keycard[2*i+1]) + 1
ra.keys[i] = key ra.keys[i] = key
ra.needCopyOnWrite[i] = true ra.needCopyOnWrite[i] = willNeedCopyOnWrite
if isRunBitmap != nil && isRunBitmap[i/8]&(1<<(i%8)) != 0 { if isRunBitmap != nil && isRunBitmap[i/8]&(1<<(i%8)) != 0 {
// run container // run container

View file

@ -47,6 +47,7 @@ import (
// runContainer16 does run-length encoding of sets of // runContainer16 does run-length encoding of sets of
// uint16 integers. // uint16 integers.
type runContainer16 struct { type runContainer16 struct {
// iv is a slice of sorted, non-overlapping, non-adjacent intervals.
iv []interval16 iv []interval16
} }
@ -253,10 +254,8 @@ func newRunContainer16FromBitmapContainer(bc *bitmapContainer) *runContainer16 {
} }
//
// newRunContainer16FromArray populates a new // newRunContainer16FromArray populates a new
// runContainer16 from the contents of arr. // runContainer16 from the contents of arr.
//
func newRunContainer16FromArray(arr *arrayContainer) *runContainer16 { func newRunContainer16FromArray(arr *arrayContainer) *runContainer16 {
// keep this in sync with newRunContainer16FromVals above // keep this in sync with newRunContainer16FromVals above
@ -851,7 +850,6 @@ func (rc *runContainer16) numIntervals() int {
// //
// The search space is from startIndex to endxIndex. If endxIndex is set to zero, then there // The search space is from startIndex to endxIndex. If endxIndex is set to zero, then there
// no upper bound. // no upper bound.
//
func (rc *runContainer16) searchRange(key int, startIndex int, endxIndex int) (whichInterval16 int, alreadyPresent bool, numCompares int) { func (rc *runContainer16) searchRange(key int, startIndex int, endxIndex int) (whichInterval16 int, alreadyPresent bool, numCompares int) {
n := int(len(rc.iv)) n := int(len(rc.iv))
if n == 0 { if n == 0 {
@ -951,7 +949,6 @@ func (rc *runContainer16) searchRange(key int, startIndex int, endxIndex int) (w
// whichInterval16 is the last interval.) // whichInterval16 is the last interval.)
// //
// runContainer16.search always returns whichInterval16 < len(rc.iv). // runContainer16.search always returns whichInterval16 < len(rc.iv).
//
func (rc *runContainer16) search(key int) (whichInterval16 int, alreadyPresent bool, numCompares int) { func (rc *runContainer16) search(key int) (whichInterval16 int, alreadyPresent bool, numCompares int) {
return rc.searchRange(key, 0, 0) return rc.searchRange(key, 0, 0)
} }
@ -994,7 +991,6 @@ func newRunContainer16() *runContainer16 {
// newRunContainer16CopyIv creates a run container, initializing // newRunContainer16CopyIv creates a run container, initializing
// with a copy of the supplied iv slice. // with a copy of the supplied iv slice.
//
func newRunContainer16CopyIv(iv []interval16) *runContainer16 { func newRunContainer16CopyIv(iv []interval16) *runContainer16 {
rc := &runContainer16{ rc := &runContainer16{
iv: make([]interval16, len(iv)), iv: make([]interval16, len(iv)),
@ -1011,7 +1007,6 @@ func (rc *runContainer16) Clone() *runContainer16 {
// newRunContainer16TakeOwnership returns a new runContainer16 // newRunContainer16TakeOwnership returns a new runContainer16
// backed by the provided iv slice, which we will // backed by the provided iv slice, which we will
// assume exclusive control over from now on. // assume exclusive control over from now on.
//
func newRunContainer16TakeOwnership(iv []interval16) *runContainer16 { func newRunContainer16TakeOwnership(iv []interval16) *runContainer16 {
rc := &runContainer16{ rc := &runContainer16{
iv: iv, iv: iv,
@ -2006,7 +2001,6 @@ func (rc *runContainer16) not(firstOfRange, endx int) container {
// Current routine is correct but // Current routine is correct but
// makes 2 more passes through the arrays than should be // makes 2 more passes through the arrays than should be
// strictly necessary. Measure both ways though--this may not matter. // strictly necessary. Measure both ways though--this may not matter.
//
func (rc *runContainer16) Not(firstOfRange, endx int) *runContainer16 { func (rc *runContainer16) Not(firstOfRange, endx int) *runContainer16 {
if firstOfRange > endx { if firstOfRange > endx {
@ -2329,7 +2323,6 @@ func runArrayUnionToRuns(rc *runContainer16, ac *arrayContainer) ([]interval16,
// the backing array, and then you write // the backing array, and then you write
// the answer at the beginning. What this // the answer at the beginning. What this
// trick does is minimize memory allocations. // trick does is minimize memory allocations.
//
func (rc *runContainer16) lazyIOR(a container) container { func (rc *runContainer16) lazyIOR(a container) container {
// not lazy at the moment // not lazy at the moment
return rc.ior(a) return rc.ior(a)

View file

@ -7,7 +7,6 @@ import (
// writeTo for runContainer16 follows this // writeTo for runContainer16 follows this
// spec: https://github.com/RoaringBitmap/RoaringFormatSpec // spec: https://github.com/RoaringBitmap/RoaringFormatSpec
//
func (b *runContainer16) writeTo(stream io.Writer) (int, error) { func (b *runContainer16) writeTo(stream io.Writer) (int, error) {
buf := make([]byte, 2+4*len(b.iv)) buf := make([]byte, 2+4*len(b.iv))
binary.LittleEndian.PutUint16(buf[0:], uint16(len(b.iv))) binary.LittleEndian.PutUint16(buf[0:], uint16(len(b.iv)))

View file

@ -295,7 +295,6 @@ func byteSliceAsBoolSlice(slice []byte) (result []bool) {
// bitmap derived from this bitmap (e.g., via Or, And) might // bitmap derived from this bitmap (e.g., via Or, And) might
// also be broken. Thus, before making buf unavailable, you should // also be broken. Thus, before making buf unavailable, you should
// call CloneCopyOnWriteContainers on all such bitmaps. // call CloneCopyOnWriteContainers on all such bitmaps.
//
func (rb *Bitmap) FrozenView(buf []byte) error { func (rb *Bitmap) FrozenView(buf []byte) error {
return rb.highlowcontainer.frozenView(buf) return rb.highlowcontainer.frozenView(buf)
} }
@ -313,7 +312,7 @@ func (rb *Bitmap) FrozenView(buf []byte) error {
* <typecodes> uint8_t[num_containers] * <typecodes> uint8_t[num_containers]
* <header> uint32_t * <header> uint32_t
* *
* <header> is a 4-byte value which is a bit union of FROZEN_COOKIE (15 bits) * <header> is a 4-byte value which is a bit union of frozenCookie (15 bits)
* and the number of containers (17 bits). * and the number of containers (17 bits).
* *
* <counts> stores number of elements for every container. * <counts> stores number of elements for every container.
@ -329,43 +328,50 @@ func (rb *Bitmap) FrozenView(buf []byte) error {
* All members have their native alignments during deserilization except <header>, * All members have their native alignments during deserilization except <header>,
* which is not guaranteed to be aligned by 4 bytes. * which is not guaranteed to be aligned by 4 bytes.
*/ */
const FROZEN_COOKIE = 13766 const frozenCookie = 13766
var ( var (
FrozenBitmapInvalidCookie = errors.New("header does not contain the FROZEN_COOKIE") // ErrFrozenBitmapInvalidCookie is returned when the header does not contain the frozenCookie.
FrozenBitmapBigEndian = errors.New("loading big endian frozen bitmaps is not supported") ErrFrozenBitmapInvalidCookie = errors.New("header does not contain the frozenCookie")
FrozenBitmapIncomplete = errors.New("input buffer too small to contain a frozen bitmap") // ErrFrozenBitmapBigEndian is returned when the header is big endian.
FrozenBitmapOverpopulated = errors.New("too many containers") ErrFrozenBitmapBigEndian = errors.New("loading big endian frozen bitmaps is not supported")
FrozenBitmapUnexpectedData = errors.New("spurious data in input") // ErrFrozenBitmapIncomplete is returned when the buffer is too small to contain a frozen bitmap.
FrozenBitmapInvalidTypecode = errors.New("unrecognized typecode") ErrFrozenBitmapIncomplete = errors.New("input buffer too small to contain a frozen bitmap")
FrozenBitmapBufferTooSmall = errors.New("buffer too small") // ErrFrozenBitmapOverpopulated is returned when the number of containers is too large.
ErrFrozenBitmapOverpopulated = errors.New("too many containers")
// ErrFrozenBitmapUnexpectedData is returned when the buffer contains unexpected data.
ErrFrozenBitmapUnexpectedData = errors.New("spurious data in input")
// ErrFrozenBitmapInvalidTypecode is returned when the typecode is invalid.
ErrFrozenBitmapInvalidTypecode = errors.New("unrecognized typecode")
// ErrFrozenBitmapBufferTooSmall is returned when the buffer is too small.
ErrFrozenBitmapBufferTooSmall = errors.New("buffer too small")
) )
func (ra *roaringArray) frozenView(buf []byte) error { func (ra *roaringArray) frozenView(buf []byte) error {
if len(buf) < 4 { if len(buf) < 4 {
return FrozenBitmapIncomplete return ErrFrozenBitmapIncomplete
} }
headerBE := binary.BigEndian.Uint32(buf[len(buf)-4:]) headerBE := binary.BigEndian.Uint32(buf[len(buf)-4:])
if headerBE&0x7fff == FROZEN_COOKIE { if headerBE&0x7fff == frozenCookie {
return FrozenBitmapBigEndian return ErrFrozenBitmapBigEndian
} }
header := binary.LittleEndian.Uint32(buf[len(buf)-4:]) header := binary.LittleEndian.Uint32(buf[len(buf)-4:])
buf = buf[:len(buf)-4] buf = buf[:len(buf)-4]
if header&0x7fff != FROZEN_COOKIE { if header&0x7fff != frozenCookie {
return FrozenBitmapInvalidCookie return ErrFrozenBitmapInvalidCookie
} }
nCont := int(header >> 15) nCont := int(header >> 15)
if nCont > (1 << 16) { if nCont > (1 << 16) {
return FrozenBitmapOverpopulated return ErrFrozenBitmapOverpopulated
} }
// 1 byte per type, 2 bytes per key, 2 bytes per count. // 1 byte per type, 2 bytes per key, 2 bytes per count.
if len(buf) < 5*nCont { if len(buf) < 5*nCont {
return FrozenBitmapIncomplete return ErrFrozenBitmapIncomplete
} }
types := buf[len(buf)-nCont:] types := buf[len(buf)-nCont:]
@ -390,12 +396,12 @@ func (ra *roaringArray) frozenView(buf []byte) error {
nRun++ nRun++
nRunEl += int(counts[i]) nRunEl += int(counts[i])
default: default:
return FrozenBitmapInvalidTypecode return ErrFrozenBitmapInvalidTypecode
} }
} }
if len(buf) < (1<<13)*nBitmap+4*nRunEl+2*nArrayEl { if len(buf) < (1<<13)*nBitmap+4*nRunEl+2*nArrayEl {
return FrozenBitmapIncomplete return ErrFrozenBitmapIncomplete
} }
bitsetsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBitmap]) bitsetsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBitmap])
@ -408,7 +414,7 @@ func (ra *roaringArray) frozenView(buf []byte) error {
buf = buf[2*nArrayEl:] buf = buf[2*nArrayEl:]
if len(buf) != 0 { if len(buf) != 0 {
return FrozenBitmapUnexpectedData return ErrFrozenBitmapUnexpectedData
} }
var c container var c container
@ -475,9 +481,10 @@ func (ra *roaringArray) frozenView(buf []byte) error {
return nil return nil
} }
func (bm *Bitmap) GetFrozenSizeInBytes() uint64 { // GetFrozenSizeInBytes returns the size in bytes of the frozen bitmap.
func (rb *Bitmap) GetFrozenSizeInBytes() uint64 {
nBits, nArrayEl, nRunEl := uint64(0), uint64(0), uint64(0) nBits, nArrayEl, nRunEl := uint64(0), uint64(0), uint64(0)
for _, c := range bm.highlowcontainer.containers { for _, c := range rb.highlowcontainer.containers {
switch v := c.(type) { switch v := c.(type) {
case *bitmapContainer: case *bitmapContainer:
nBits++ nBits++
@ -487,19 +494,21 @@ func (bm *Bitmap) GetFrozenSizeInBytes() uint64 {
nRunEl += uint64(len(v.iv)) nRunEl += uint64(len(v.iv))
} }
} }
return 4 + 5*uint64(len(bm.highlowcontainer.containers)) + return 4 + 5*uint64(len(rb.highlowcontainer.containers)) +
(nBits << 13) + 2*nArrayEl + 4*nRunEl (nBits << 13) + 2*nArrayEl + 4*nRunEl
} }
func (bm *Bitmap) Freeze() ([]byte, error) { // Freeze serializes the bitmap in the CRoaring's frozen format.
sz := bm.GetFrozenSizeInBytes() func (rb *Bitmap) Freeze() ([]byte, error) {
sz := rb.GetFrozenSizeInBytes()
buf := make([]byte, sz) buf := make([]byte, sz)
_, err := bm.FreezeTo(buf) _, err := rb.FreezeTo(buf)
return buf, err return buf, err
} }
func (bm *Bitmap) FreezeTo(buf []byte) (int, error) { // FreezeTo serializes the bitmap in the CRoaring's frozen format.
containers := bm.highlowcontainer.containers func (rb *Bitmap) FreezeTo(buf []byte) (int, error) {
containers := rb.highlowcontainer.containers
nCont := len(containers) nCont := len(containers)
nBits, nArrayEl, nRunEl := 0, 0, 0 nBits, nArrayEl, nRunEl := 0, 0, 0
@ -516,7 +525,7 @@ func (bm *Bitmap) FreezeTo(buf []byte) (int, error) {
serialSize := 4 + 5*nCont + (1<<13)*nBits + 4*nRunEl + 2*nArrayEl serialSize := 4 + 5*nCont + (1<<13)*nBits + 4*nRunEl + 2*nArrayEl
if len(buf) < serialSize { if len(buf) < serialSize {
return 0, FrozenBitmapBufferTooSmall return 0, ErrFrozenBitmapBufferTooSmall
} }
bitsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBits]) bitsArena := byteSliceAsUint64Slice(buf[:(1<<13)*nBits])
@ -537,10 +546,10 @@ func (bm *Bitmap) FreezeTo(buf []byte) (int, error) {
types := buf[:nCont] types := buf[:nCont]
buf = buf[nCont:] buf = buf[nCont:]
header := uint32(FROZEN_COOKIE | (nCont << 15)) header := uint32(frozenCookie | (nCont << 15))
binary.LittleEndian.PutUint32(buf[:4], header) binary.LittleEndian.PutUint32(buf[:4], header)
copy(keys, bm.highlowcontainer.keys[:]) copy(keys, rb.highlowcontainer.keys[:])
for i, c := range containers { for i, c := range containers {
switch v := c.(type) { switch v := c.(type) {
@ -567,11 +576,12 @@ func (bm *Bitmap) FreezeTo(buf []byte) (int, error) {
return serialSize, nil return serialSize, nil
} }
func (bm *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) { // WriteFrozenTo serializes the bitmap in the CRoaring's frozen format.
func (rb *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) {
// FIXME: this is a naive version that iterates 4 times through the // FIXME: this is a naive version that iterates 4 times through the
// containers and allocates 3*len(containers) bytes; it's quite likely // containers and allocates 3*len(containers) bytes; it's quite likely
// it can be done more efficiently. // it can be done more efficiently.
containers := bm.highlowcontainer.containers containers := rb.highlowcontainer.containers
written := 0 written := 0
for _, c := range containers { for _, c := range containers {
@ -610,7 +620,7 @@ func (bm *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) {
} }
} }
n, err := wr.Write(uint16SliceAsByteSlice(bm.highlowcontainer.keys)) n, err := wr.Write(uint16SliceAsByteSlice(rb.highlowcontainer.keys))
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
@ -642,7 +652,7 @@ func (bm *Bitmap) WriteFrozenTo(wr io.Writer) (int, error) {
return written, err return written, err
} }
header := uint32(FROZEN_COOKIE | (len(containers) << 15)) header := uint32(frozenCookie | (len(containers) << 15))
if err := binary.Write(wr, binary.LittleEndian, header); err != nil { if err := binary.Write(wr, binary.LittleEndian, header); err != nil {
return written, err return written, err
} }

View file

@ -7,6 +7,15 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bits-and-blooms/bitset?tab=doc)](https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bits-and-blooms/bitset?tab=doc)](https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc)
This library is part of the [awesome go collection](https://github.com/avelino/awesome-go). It is used in production by several important systems:
* [beego](https://github.com/beego/beego)
* [CubeFS](https://github.com/cubefs/cubefs)
* [Amazon EKS Distro](https://github.com/aws/eks-distro)
* [sourcegraph](https://github.com/sourcegraph/sourcegraph-public-snapshot)
* [torrent](https://github.com/anacrolix/torrent)
## Description ## Description
Package bitset implements bitsets, a mapping between non-negative integers and boolean values. Package bitset implements bitsets, a mapping between non-negative integers and boolean values.
@ -16,7 +25,7 @@ It provides methods for setting, clearing, flipping, and testing individual inte
But it also provides set intersection, union, difference, complement, and symmetric operations, as well as tests to check whether any, all, or no bits are set, and querying a bitset's current length and number of positive bits. But it also provides set intersection, union, difference, complement, and symmetric operations, as well as tests to check whether any, all, or no bits are set, and querying a bitset's current length and number of positive bits.
BitSets are expanded to the size of the largest set bit; the memory allocation is approximately Max bits, where Max is the largest set bit. BitSets are never shrunk. On creation, a hint can be given for the number of bits that will be used. BitSets are expanded to the size of the largest set bit; the memory allocation is approximately Max bits, where Max is the largest set bit. BitSets are never shrunk automatically, but `Shrink` and `Compact` methods are available. On creation, a hint can be given for the number of bits that will be used.
Many of the methods, including Set, Clear, and Flip, return a BitSet pointer, which allows for chaining. Many of the methods, including Set, Clear, and Flip, return a BitSet pointer, which allows for chaining.
@ -60,19 +69,83 @@ func main() {
} }
``` ```
As an alternative to BitSets, one should check out the 'big' package, which provides a (less set-theoretical) view of bitsets. If you have Go 1.23 or better, you can iterate over the set bits like so:
```go
for i := range b.EachSet() {}
```
Package documentation is at: https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc Package documentation is at: https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc
## Serialization
You may serialize a bitset safely and portably to a stream
of bytes as follows:
```Go
const length = 9585
const oneEvery = 97
bs := bitset.New(length)
// Add some bits
for i := uint(0); i < length; i += oneEvery {
bs = bs.Set(i)
}
var buf bytes.Buffer
n, err := bs.WriteTo(&buf)
if err != nil {
// failure
}
// Here n == buf.Len()
```
You can later deserialize the result as follows:
```Go
// Read back from buf
bs = bitset.New()
n, err = bs.ReadFrom(&buf)
if err != nil {
// error
}
// n is the number of bytes read
```
The `ReadFrom` function attempts to read the data into the existing
BitSet instance, to minimize memory allocations.
*Performance tip*:
When reading and writing to a file or a network connection, you may get better performance by
wrapping your streams with `bufio` instances.
E.g.,
```Go
f, err := os.Create("myfile")
w := bufio.NewWriter(f)
```
```Go
f, err := os.Open("myfile")
r := bufio.NewReader(f)
```
## Memory Usage ## Memory Usage
The memory usage of a bitset using N bits is at least N/8 bytes. The number of bits in a bitset is at least as large as one plus the greatest bit index you have accessed. Thus it is possible to run out of memory while using a bitset. If you have lots of bits, you might prefer compressed bitsets, like the [Roaring bitmaps](http://roaringbitmap.org) and its [Go implementation](https://github.com/RoaringBitmap/roaring). The memory usage of a bitset using `N` bits is at least `N/8` bytes. The number of bits in a bitset is at least as large as one plus the greatest bit index you have accessed. Thus it is possible to run out of memory while using a bitset. If you have lots of bits, you might prefer compressed bitsets, like the [Roaring bitmaps](https://roaringbitmap.org) and its [Go implementation](https://github.com/RoaringBitmap/roaring).
## Implementation Note The `roaring` library allows you to go back and forth between compressed Roaring bitmaps and the conventional bitset instances:
```Go
mybitset := roaringbitmap.ToBitSet()
newroaringbitmap := roaring.FromBitSet(mybitset)
```
Go 1.9 introduced a native `math/bits` library. We provide backward compatibility to Go 1.7, which might be removed.
It is possible that a later version will match the `math/bits` return signature for counts (which is `int`, rather than our library's `unit64`). If so, the version will be bumped. ### Goroutine safety
In general, it's not safe to access the same BitSet using different goroutines--they are unsynchronized for performance.
Should you want to access a BitSet from more than one goroutine, you should provide synchronization. Typically this is done by using channels to pass the *BitSet around (in Go style; so there is only ever one owner), or by using `sync.Mutex` to serialize operations on BitSets.
## Installation ## Installation
@ -91,3 +164,13 @@ Before committing the code, please check if it passes tests, has adequate covera
go test go test
go test -cover go test -cover
``` ```
## Stars
[![Star History Chart](https://api.star-history.com/svg?repos=bits-and-blooms/bitset&type=Date)](https://www.star-history.com/#bits-and-blooms/bitset&Date)
## Further reading
<p>Mastering Programming: From Testing to Performance in Go</p>
<div><a href="https://www.amazon.com/dp/B0FMPGSWR5"><img style="margin-left: auto; margin-right: auto;" src="https://m.media-amazon.com/images/I/61feneHS7kL._SL1499_.jpg" alt="" width="250px" /></a></div>

5
vendor/github.com/bits-and-blooms/bitset/SECURITY.md generated vendored Normal file
View file

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
You can report privately a vulnerability by email at daniel@lemire.me (current maintainer).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
//go:build go1.23
// +build go1.23
package bitset
import (
"iter"
"math/bits"
)
func (b *BitSet) EachSet() iter.Seq[uint] {
return func(yield func(uint) bool) {
for wordIndex, word := range b.set {
idx := 0
for trail := bits.TrailingZeros64(word); trail != 64; trail = bits.TrailingZeros64(word >> idx) {
if !yield(uint(wordIndex<<log2WordSize + idx + trail)) {
return
}
idx += trail + 1
}
}
}
}

8866
vendor/github.com/bits-and-blooms/bitset/pext.gen.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,53 +1,52 @@
package bitset package bitset
// bit population count, take from import "math/bits"
// https://code.google.com/p/go/issues/detail?id=4988#c11
// credit: https://code.google.com/u/arnehormann/
func popcount(x uint64) (n uint64) {
x -= (x >> 1) & 0x5555555555555555
x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
x += x >> 4
x &= 0x0f0f0f0f0f0f0f0f
x *= 0x0101010101010101
return x >> 56
}
func popcntSliceGo(s []uint64) uint64 { func popcntSlice(s []uint64) (cnt uint64) {
cnt := uint64(0)
for _, x := range s { for _, x := range s {
cnt += popcount(x) cnt += uint64(bits.OnesCount64(x))
} }
return cnt return
} }
func popcntMaskSliceGo(s, m []uint64) uint64 { func popcntMaskSlice(s, m []uint64) (cnt uint64) {
cnt := uint64(0) // The next line is to help the bounds checker, it matters!
_ = m[len(s)-1] // BCE
for i := range s { for i := range s {
cnt += popcount(s[i] &^ m[i]) cnt += uint64(bits.OnesCount64(s[i] &^ m[i]))
} }
return cnt return
} }
func popcntAndSliceGo(s, m []uint64) uint64 { // popcntAndSlice computes the population count of the AND of two slices.
cnt := uint64(0) // It assumes that len(m) >= len(s) > 0.
func popcntAndSlice(s, m []uint64) (cnt uint64) {
// The next line is to help the bounds checker, it matters!
_ = m[len(s)-1] // BCE
for i := range s { for i := range s {
cnt += popcount(s[i] & m[i]) cnt += uint64(bits.OnesCount64(s[i] & m[i]))
} }
return cnt return
} }
func popcntOrSliceGo(s, m []uint64) uint64 { // popcntOrSlice computes the population count of the OR of two slices.
cnt := uint64(0) // It assumes that len(m) >= len(s) > 0.
func popcntOrSlice(s, m []uint64) (cnt uint64) {
// The next line is to help the bounds checker, it matters!
_ = m[len(s)-1] // BCE
for i := range s { for i := range s {
cnt += popcount(s[i] | m[i]) cnt += uint64(bits.OnesCount64(s[i] | m[i]))
} }
return cnt return
} }
func popcntXorSliceGo(s, m []uint64) uint64 { // popcntXorSlice computes the population count of the XOR of two slices.
cnt := uint64(0) // It assumes that len(m) >= len(s) > 0.
func popcntXorSlice(s, m []uint64) (cnt uint64) {
// The next line is to help the bounds checker, it matters!
_ = m[len(s)-1] // BCE
for i := range s { for i := range s {
cnt += popcount(s[i] ^ m[i]) cnt += uint64(bits.OnesCount64(s[i] ^ m[i]))
} }
return cnt return
} }

View file

@ -1,45 +0,0 @@
// +build go1.9
package bitset
import "math/bits"
func popcntSlice(s []uint64) uint64 {
var cnt int
for _, x := range s {
cnt += bits.OnesCount64(x)
}
return uint64(cnt)
}
func popcntMaskSlice(s, m []uint64) uint64 {
var cnt int
for i := range s {
cnt += bits.OnesCount64(s[i] &^ m[i])
}
return uint64(cnt)
}
func popcntAndSlice(s, m []uint64) uint64 {
var cnt int
for i := range s {
cnt += bits.OnesCount64(s[i] & m[i])
}
return uint64(cnt)
}
func popcntOrSlice(s, m []uint64) uint64 {
var cnt int
for i := range s {
cnt += bits.OnesCount64(s[i] | m[i])
}
return uint64(cnt)
}
func popcntXorSlice(s, m []uint64) uint64 {
var cnt int
for i := range s {
cnt += bits.OnesCount64(s[i] ^ m[i])
}
return uint64(cnt)
}

View file

@ -1,68 +0,0 @@
// +build !go1.9
// +build amd64,!appengine
package bitset
// *** the following functions are defined in popcnt_amd64.s
//go:noescape
func hasAsm() bool
// useAsm is a flag used to select the GO or ASM implementation of the popcnt function
var useAsm = hasAsm()
//go:noescape
func popcntSliceAsm(s []uint64) uint64
//go:noescape
func popcntMaskSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntAndSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntOrSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntXorSliceAsm(s, m []uint64) uint64
func popcntSlice(s []uint64) uint64 {
if useAsm {
return popcntSliceAsm(s)
}
return popcntSliceGo(s)
}
func popcntMaskSlice(s, m []uint64) uint64 {
if useAsm {
return popcntMaskSliceAsm(s, m)
}
return popcntMaskSliceGo(s, m)
}
func popcntAndSlice(s, m []uint64) uint64 {
if useAsm {
return popcntAndSliceAsm(s, m)
}
return popcntAndSliceGo(s, m)
}
func popcntOrSlice(s, m []uint64) uint64 {
if useAsm {
return popcntOrSliceAsm(s, m)
}
return popcntOrSliceGo(s, m)
}
func popcntXorSlice(s, m []uint64) uint64 {
if useAsm {
return popcntXorSliceAsm(s, m)
}
return popcntXorSliceGo(s, m)
}

View file

@ -1,104 +0,0 @@
// +build !go1.9
// +build amd64,!appengine
TEXT ·hasAsm(SB),4,$0-1
MOVQ $1, AX
CPUID
SHRQ $23, CX
ANDQ $1, CX
MOVB CX, ret+0(FP)
RET
#define POPCNTQ_DX_DX BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0xd2
TEXT ·popcntSliceAsm(SB),4,$0-32
XORQ AX, AX
MOVQ s+0(FP), SI
MOVQ s_len+8(FP), CX
TESTQ CX, CX
JZ popcntSliceEnd
popcntSliceLoop:
BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0x16 // POPCNTQ (SI), DX
ADDQ DX, AX
ADDQ $8, SI
LOOP popcntSliceLoop
popcntSliceEnd:
MOVQ AX, ret+24(FP)
RET
TEXT ·popcntMaskSliceAsm(SB),4,$0-56
XORQ AX, AX
MOVQ s+0(FP), SI
MOVQ s_len+8(FP), CX
TESTQ CX, CX
JZ popcntMaskSliceEnd
MOVQ m+24(FP), DI
popcntMaskSliceLoop:
MOVQ (DI), DX
NOTQ DX
ANDQ (SI), DX
POPCNTQ_DX_DX
ADDQ DX, AX
ADDQ $8, SI
ADDQ $8, DI
LOOP popcntMaskSliceLoop
popcntMaskSliceEnd:
MOVQ AX, ret+48(FP)
RET
TEXT ·popcntAndSliceAsm(SB),4,$0-56
XORQ AX, AX
MOVQ s+0(FP), SI
MOVQ s_len+8(FP), CX
TESTQ CX, CX
JZ popcntAndSliceEnd
MOVQ m+24(FP), DI
popcntAndSliceLoop:
MOVQ (DI), DX
ANDQ (SI), DX
POPCNTQ_DX_DX
ADDQ DX, AX
ADDQ $8, SI
ADDQ $8, DI
LOOP popcntAndSliceLoop
popcntAndSliceEnd:
MOVQ AX, ret+48(FP)
RET
TEXT ·popcntOrSliceAsm(SB),4,$0-56
XORQ AX, AX
MOVQ s+0(FP), SI
MOVQ s_len+8(FP), CX
TESTQ CX, CX
JZ popcntOrSliceEnd
MOVQ m+24(FP), DI
popcntOrSliceLoop:
MOVQ (DI), DX
ORQ (SI), DX
POPCNTQ_DX_DX
ADDQ DX, AX
ADDQ $8, SI
ADDQ $8, DI
LOOP popcntOrSliceLoop
popcntOrSliceEnd:
MOVQ AX, ret+48(FP)
RET
TEXT ·popcntXorSliceAsm(SB),4,$0-56
XORQ AX, AX
MOVQ s+0(FP), SI
MOVQ s_len+8(FP), CX
TESTQ CX, CX
JZ popcntXorSliceEnd
MOVQ m+24(FP), DI
popcntXorSliceLoop:
MOVQ (DI), DX
XORQ (SI), DX
POPCNTQ_DX_DX
ADDQ DX, AX
ADDQ $8, SI
ADDQ $8, DI
LOOP popcntXorSliceLoop
popcntXorSliceEnd:
MOVQ AX, ret+48(FP)
RET

View file

@ -1,24 +0,0 @@
// +build !go1.9
// +build !amd64 appengine
package bitset
func popcntSlice(s []uint64) uint64 {
return popcntSliceGo(s)
}
func popcntMaskSlice(s, m []uint64) uint64 {
return popcntMaskSliceGo(s, m)
}
func popcntAndSlice(s, m []uint64) uint64 {
return popcntAndSliceGo(s, m)
}
func popcntOrSlice(s, m []uint64) uint64 {
return popcntOrSliceGo(s, m)
}
func popcntXorSlice(s, m []uint64) uint64 {
return popcntXorSliceGo(s, m)
}

47
vendor/github.com/bits-and-blooms/bitset/select.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
package bitset
import "math/bits"
func select64(w uint64, j uint) uint {
seen := 0
// Divide 64bit
part := w & 0xFFFFFFFF
n := uint(bits.OnesCount64(part))
if n <= j {
part = w >> 32
seen += 32
j -= n
}
ww := part
// Divide 32bit
part = ww & 0xFFFF
n = uint(bits.OnesCount64(part))
if n <= j {
part = ww >> 16
seen += 16
j -= n
}
ww = part
// Divide 16bit
part = ww & 0xFF
n = uint(bits.OnesCount64(part))
if n <= j {
part = ww >> 8
seen += 8
j -= n
}
ww = part
// Lookup in final byte
counter := 0
for ; counter < 8; counter++ {
j -= uint((ww >> counter) & 1)
if j+1 == 0 {
break
}
}
return uint(seen + counter)
}

View file

@ -1,14 +0,0 @@
// +build !go1.9
package bitset
var deBruijn = [...]byte{
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
}
func trailingZeroes64(v uint64) uint {
return uint(deBruijn[((v&-v)*0x03f79d71b4ca8b09)>>58])
}

View file

@ -1,9 +0,0 @@
// +build go1.9
package bitset
import "math/bits"
func trailingZeroes64(v uint64) uint {
return uint(bits.TrailingZeros64(v))
}

View file

@ -1,6 +1,8 @@
package barcode package barcode
import "image" import (
"image"
)
const ( const (
TypeAztec = "Aztec" TypeAztec = "Aztec"
@ -40,3 +42,7 @@ type BarcodeIntCS interface {
Barcode Barcode
CheckSum() int CheckSum() int
} }
type BarcodeColor interface {
ColorScheme() ColorScheme
}

39
vendor/github.com/boombuler/barcode/color_scheme.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
package barcode
import "image/color"
// ColorScheme defines a structure for color schemes used in barcode rendering.
// It includes the color model, background color, and foreground color.
type ColorScheme struct {
Model color.Model // Color model to be used (e.g., grayscale, RGB, RGBA)
Background color.Color // Color of the background
Foreground color.Color // Color of the foreground (e.g., bars in a barcode)
}
// ColorScheme8 represents a color scheme with 8-bit grayscale colors.
var ColorScheme8 = ColorScheme{
Model: color.GrayModel,
Background: color.Gray{Y: 255},
Foreground: color.Gray{Y: 0},
}
// ColorScheme16 represents a color scheme with 16-bit grayscale colors.
var ColorScheme16 = ColorScheme{
Model: color.Gray16Model,
Background: color.White,
Foreground: color.Black,
}
// ColorScheme24 represents a color scheme with 24-bit RGB colors.
var ColorScheme24 = ColorScheme{
Model: color.RGBAModel,
Background: color.RGBA{255, 255, 255, 255},
Foreground: color.RGBA{0, 0, 0, 255},
}
// ColorScheme32 represents a color scheme with 32-bit RGBA colors, which is similar to ColorScheme24 but typically includes alpha for transparency.
var ColorScheme32 = ColorScheme{
Model: color.RGBAModel,
Background: color.RGBA{255, 255, 255, 255},
Foreground: color.RGBA{0, 0, 0, 255},
}

View file

@ -54,8 +54,8 @@ func (e Encoding) String() string {
return "" return ""
} }
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding // Encode returns a QR barcode with the given content and color scheme, error correction level and uses the given encoding
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) { func EncodeWithColor(content string, level ErrorCorrectionLevel, mode Encoding, color barcode.ColorScheme) (barcode.Barcode, error) {
bits, vi, err := mode.getEncoder()(content, level) bits, vi, err := mode.getEncoder()(content, level)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,19 +63,23 @@ func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.
blocks := splitToBlocks(bits.IterateBytes(), vi) blocks := splitToBlocks(bits.IterateBytes(), vi)
data := blocks.interleave(vi) data := blocks.interleave(vi)
result := render(data, vi) result := render(data, vi, color)
result.content = content result.content = content
return result, nil return result, nil
} }
func render(data []byte, vi *versionInfo) *qrcode { func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
return EncodeWithColor(content, level, mode, barcode.ColorScheme16)
}
func render(data []byte, vi *versionInfo, color barcode.ColorScheme) *qrcode {
dim := vi.modulWidth() dim := vi.modulWidth()
results := make([]*qrcode, 8) results := make([]*qrcode, 8)
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
results[i] = newBarcode(dim) results[i] = newBarCodeWithColor(dim, color)
} }
occupied := newBarcode(dim) occupied := newBarCodeWithColor(dim, color)
setAll := func(x int, y int, val bool) { setAll := func(x int, y int, val bool) {
occupied.Set(x, y, true) occupied.Set(x, y, true)

View file

@ -13,6 +13,7 @@ type qrcode struct {
dimension int dimension int
data *utils.BitList data *utils.BitList
content string content string
color barcode.ColorScheme
} }
func (qr *qrcode) Content() string { func (qr *qrcode) Content() string {
@ -24,7 +25,11 @@ func (qr *qrcode) Metadata() barcode.Metadata {
} }
func (qr *qrcode) ColorModel() color.Model { func (qr *qrcode) ColorModel() color.Model {
return color.Gray16Model return qr.color.Model
}
func (c *qrcode) ColorScheme() barcode.ColorScheme {
return c.color
} }
func (qr *qrcode) Bounds() image.Rectangle { func (qr *qrcode) Bounds() image.Rectangle {
@ -33,9 +38,9 @@ func (qr *qrcode) Bounds() image.Rectangle {
func (qr *qrcode) At(x, y int) color.Color { func (qr *qrcode) At(x, y int) color.Color {
if qr.Get(x, y) { if qr.Get(x, y) {
return color.Black return qr.color.Foreground
} }
return color.White return qr.color.Background
} }
func (qr *qrcode) Get(x, y int) bool { func (qr *qrcode) Get(x, y int) bool {
@ -158,9 +163,14 @@ func (qr *qrcode) calcPenaltyRule4() uint {
return uint(math.Min(floor, ceil) * 10) return uint(math.Min(floor, ceil) * 10)
} }
func newBarcode(dim int) *qrcode { func newBarCodeWithColor(dim int, color barcode.ColorScheme) *qrcode {
res := new(qrcode) res := new(qrcode)
res.dimension = dim res.dimension = dim
res.data = utils.NewBitList(dim * dim) res.data = utils.NewBitList(dim * dim)
res.color = color
return res return res
} }
func newBarcode(dim int) *qrcode {
return newBarCodeWithColor(dim, barcode.ColorScheme16)
}

View file

@ -49,11 +49,22 @@ func (bc *intCSscaledBC) CheckSum() int {
// Scale returns a resized barcode with the given width and height. // Scale returns a resized barcode with the given width and height.
func Scale(bc Barcode, width, height int) (Barcode, error) { func Scale(bc Barcode, width, height int) (Barcode, error) {
var fill color.Color
if v, ok := bc.(BarcodeColor); ok {
fill = v.ColorScheme().Background
} else {
fill = color.White
}
return ScaleWithFill(bc, width, height, fill)
}
// Scale returns a resized barcode with the given width, height and fill color.
func ScaleWithFill(bc Barcode, width, height int, fill color.Color) (Barcode, error) {
switch bc.Metadata().Dimensions { switch bc.Metadata().Dimensions {
case 1: case 1:
return scale1DCode(bc, width, height) return scale1DCode(bc, width, height, fill)
case 2: case 2:
return scale2DCode(bc, width, height) return scale2DCode(bc, width, height, fill)
} }
return nil, errors.New("unsupported barcode format") return nil, errors.New("unsupported barcode format")
@ -72,7 +83,7 @@ func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Ba
return result return result
} }
func scale2DCode(bc Barcode, width, height int) (Barcode, error) { func scale2DCode(bc Barcode, width, height int, fill color.Color) (Barcode, error) {
orgBounds := bc.Bounds() orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X orgWidth := orgBounds.Max.X - orgBounds.Min.X
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
@ -87,12 +98,12 @@ func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
wrap := func(x, y int) color.Color { wrap := func(x, y int) color.Color {
if x < offsetX || y < offsetY { if x < offsetX || y < offsetY {
return color.White return fill
} }
x = (x - offsetX) / factor x = (x - offsetX) / factor
y = (y - offsetY) / factor y = (y - offsetY) / factor
if x >= orgWidth || y >= orgHeight { if x >= orgWidth || y >= orgHeight {
return color.White return fill
} }
return bc.At(x, y) return bc.At(x, y)
} }
@ -104,7 +115,7 @@ func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
), nil ), nil
} }
func scale1DCode(bc Barcode, width, height int) (Barcode, error) { func scale1DCode(bc Barcode, width, height int, fill color.Color) (Barcode, error) {
orgBounds := bc.Bounds() orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X orgWidth := orgBounds.Max.X - orgBounds.Min.X
factor := int(float64(width) / float64(orgWidth)) factor := int(float64(width) / float64(orgWidth))
@ -116,12 +127,12 @@ func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
wrap := func(x, y int) color.Color { wrap := func(x, y int) color.Color {
if x < offsetX { if x < offsetX {
return color.White return fill
} }
x = (x - offsetX) / factor x = (x - offsetX) / factor
if x >= orgWidth { if x >= orgWidth {
return color.White return fill
} }
return bc.At(x, 0) return bc.At(x, 0)
} }

View file

@ -12,6 +12,7 @@ type base1DCode struct {
*BitList *BitList
kind string kind string
content string content string
color barcode.ColorScheme
} }
type base1DCodeIntCS struct { type base1DCodeIntCS struct {
@ -28,7 +29,11 @@ func (c *base1DCode) Metadata() barcode.Metadata {
} }
func (c *base1DCode) ColorModel() color.Model { func (c *base1DCode) ColorModel() color.Model {
return color.Gray16Model return c.color.Model
}
func (c *base1DCode) ColorScheme() barcode.ColorScheme {
return c.color
} }
func (c *base1DCode) Bounds() image.Rectangle { func (c *base1DCode) Bounds() image.Rectangle {
@ -37,9 +42,9 @@ func (c *base1DCode) Bounds() image.Rectangle {
func (c *base1DCode) At(x, y int) color.Color { func (c *base1DCode) At(x, y int) color.Color {
if c.GetBit(x) { if c.GetBit(x) {
return color.Black return c.color.Foreground
} }
return color.White return c.color.Background
} }
func (c *base1DCodeIntCS) CheckSum() int { func (c *base1DCodeIntCS) CheckSum() int {
@ -48,10 +53,20 @@ func (c *base1DCodeIntCS) CheckSum() int {
// New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList // New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS { func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum} return &base1DCodeIntCS{base1DCode{bars, codeKind, content, barcode.ColorScheme16}, checksum}
}
// New1DCodeIntCheckSum creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeIntCheckSumWithColor(codeKind, content string, bars *BitList, checksum int, color barcode.ColorScheme) barcode.BarcodeIntCS {
return &base1DCodeIntCS{base1DCode{bars, codeKind, content, color}, checksum}
} }
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList // New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode { func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
return &base1DCode{bars, codeKind, content} return &base1DCode{bars, codeKind, content, barcode.ColorScheme16}
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeWithColor(codeKind, content string, bars *BitList, color barcode.ColorScheme) barcode.Barcode {
return &base1DCode{bars, codeKind, content, color}
} }

View file

@ -0,0 +1,62 @@
package md2man
import (
"fmt"
"io"
"os"
"strings"
"github.com/russross/blackfriday/v2"
)
func fmtListFlags(flags blackfriday.ListType) string {
knownFlags := []struct {
name string
flag blackfriday.ListType
}{
{"ListTypeOrdered", blackfriday.ListTypeOrdered},
{"ListTypeDefinition", blackfriday.ListTypeDefinition},
{"ListTypeTerm", blackfriday.ListTypeTerm},
{"ListItemContainsBlock", blackfriday.ListItemContainsBlock},
{"ListItemBeginningOfList", blackfriday.ListItemBeginningOfList},
{"ListItemEndOfList", blackfriday.ListItemEndOfList},
}
var f []string
for _, kf := range knownFlags {
if flags&kf.flag != 0 {
f = append(f, kf.name)
flags &^= kf.flag
}
}
if flags != 0 {
f = append(f, fmt.Sprintf("Unknown(%#x)", flags))
}
return strings.Join(f, "|")
}
type debugDecorator struct {
blackfriday.Renderer
}
func depth(node *blackfriday.Node) int {
d := 0
for n := node.Parent; n != nil; n = n.Parent {
d++
}
return d
}
func (d *debugDecorator) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
fmt.Fprintf(os.Stderr, "%s%s %v %v\n",
strings.Repeat(" ", depth(node)),
map[bool]string{true: "+", false: "-"}[entering],
node,
fmtListFlags(node.ListFlags))
var b strings.Builder
status := d.Renderer.RenderNode(io.MultiWriter(&b, w), node, entering)
if b.Len() > 0 {
fmt.Fprintf(os.Stderr, ">> %q\n", b.String())
}
return status
}

View file

@ -1,14 +1,24 @@
// Package md2man aims in converting markdown into roff (man pages).
package md2man package md2man
import ( import (
"os"
"strconv"
"github.com/russross/blackfriday/v2" "github.com/russross/blackfriday/v2"
) )
// Render converts a markdown document into a roff formatted document. // Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte { func Render(doc []byte) []byte {
renderer := NewRoffRenderer() renderer := NewRoffRenderer()
var r blackfriday.Renderer = renderer
if v, _ := strconv.ParseBool(os.Getenv("MD2MAN_DEBUG")); v {
r = &debugDecorator{Renderer: r}
}
return blackfriday.Run(doc, return blackfriday.Run(doc,
[]blackfriday.Option{blackfriday.WithRenderer(renderer), []blackfriday.Option{
blackfriday.WithExtensions(renderer.GetExtensions())}...) blackfriday.WithRenderer(r),
blackfriday.WithExtensions(renderer.GetExtensions()),
}...)
} }

View file

@ -1,6 +1,8 @@
package md2man package md2man
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -12,10 +14,8 @@ import (
// roffRenderer implements the blackfriday.Renderer interface for creating // roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text // roff format (manpages) from markdown text
type roffRenderer struct { type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int listCounters []int
firstHeader bool firstHeader bool
firstDD bool
listDepth int listDepth int
} }
@ -34,46 +34,52 @@ const (
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n" hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
linkTag = "\n\\[la]" linkTag = "\n\\[la]"
linkCloseTag = "\\[ra]" linkCloseTag = "\\[ra]"
codespanTag = "\\fB\\fC" codespanTag = "\\fB"
codespanCloseTag = "\\fR" codespanCloseTag = "\\fR"
codeTag = "\n.PP\n.RS\n\n.nf\n" codeTag = "\n.EX\n"
codeCloseTag = "\n.fi\n.RE\n" codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on).
quoteTag = "\n.PP\n.RS\n" quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n" quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n" listTag = "\n.RS\n"
listCloseTag = "\n.RE\n" listCloseTag = ".RE\n"
dtTag = "\n.TP\n" dtTag = "\n.TP\n"
dd2Tag = "\n" dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n" tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n" tableEnd = ".TE\n"
tableCellStart = "T{\n" tableCellStart = "T{\n"
tableCellEnd = "\nT}\n" tableCellEnd = "\nT}"
tablePreprocessor = `'\" t`
) )
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents // NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown // from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint func NewRoffRenderer() *roffRenderer {
var extensions blackfriday.Extensions return &roffRenderer{}
extensions |= blackfriday.NoIntraEmphasis
extensions |= blackfriday.Tables
extensions |= blackfriday.FencedCode
extensions |= blackfriday.SpaceHeadings
extensions |= blackfriday.Footnotes
extensions |= blackfriday.Titleblock
extensions |= blackfriday.DefinitionLists
return &roffRenderer{
extensions: extensions,
}
} }
// GetExtensions returns the list of extensions used by this renderer implementation // GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions { func (*roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions return blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.SpaceHeadings |
blackfriday.Footnotes |
blackfriday.Titleblock |
blackfriday.DefinitionLists
} }
// RenderHeader handles outputting the header at document start // RenderHeader handles outputting the header at document start
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) { func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
// We need to walk the tree to check if there are any tables.
// If there are, we need to enable the roff table preprocessor.
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if node.Type == blackfriday.Table {
out(w, tablePreprocessor+"\n")
return blackfriday.Terminate
}
return blackfriday.GoToNext
})
// disable hyphenation // disable hyphenation
out(w, ".nh\n") out(w, ".nh\n")
} }
@ -86,12 +92,27 @@ func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
// RenderNode is called for each node in a markdown document; based on the node // RenderNode is called for each node in a markdown document; based on the node
// type the equivalent roff output is sent to the writer // type the equivalent roff output is sent to the writer
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
walkAction := blackfriday.GoToNext
var walkAction = blackfriday.GoToNext
switch node.Type { switch node.Type {
case blackfriday.Text: case blackfriday.Text:
// Special case: format the NAME section as required for proper whatis parsing.
// Refer to the lexgrog(1) and groff_man(7) manual pages for details.
if node.Parent != nil &&
node.Parent.Type == blackfriday.Paragraph &&
node.Parent.Prev != nil &&
node.Parent.Prev.Type == blackfriday.Heading &&
node.Parent.Prev.FirstChild != nil &&
bytes.EqualFold(node.Parent.Prev.FirstChild.Literal, []byte("NAME")) {
before, after, found := bytesCut(node.Literal, []byte(" - "))
escapeSpecialChars(w, before)
if found {
out(w, ` \- `)
escapeSpecialChars(w, after)
}
} else {
escapeSpecialChars(w, node.Literal) escapeSpecialChars(w, node.Literal)
}
case blackfriday.Softbreak: case blackfriday.Softbreak:
out(w, crTag) out(w, crTag)
case blackfriday.Hardbreak: case blackfriday.Hardbreak:
@ -109,9 +130,16 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
out(w, strongCloseTag) out(w, strongCloseTag)
} }
case blackfriday.Link: case blackfriday.Link:
if !entering { // Don't render the link text for automatic links, because this
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag) // will only duplicate the URL in the roff output.
// See https://daringfireball.net/projects/markdown/syntax#autolink
if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
out(w, string(node.FirstChild.Literal))
} }
// Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
out(w, linkTag+escapedLink+linkCloseTag)
walkAction = blackfriday.SkipChildren
case blackfriday.Image: case blackfriday.Image:
// ignore images // ignore images
walkAction = blackfriday.SkipChildren walkAction = blackfriday.SkipChildren
@ -122,15 +150,26 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
case blackfriday.Document: case blackfriday.Document:
break break
case blackfriday.Paragraph: case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering { if entering {
out(w, paraTag) if r.listDepth > 0 {
// roff .PP markers break lists
if node.Prev != nil { // continued paragraph
if node.Prev.Type == blackfriday.List && node.Prev.ListFlags&blackfriday.ListTypeDefinition == 0 {
out(w, ".IP\n")
} else { } else {
out(w, crTag) out(w, crTag)
} }
}
} else if node.Prev != nil && node.Prev.Type == blackfriday.Heading {
out(w, crTag)
} else {
out(w, paraTag)
}
} else {
if node.Next == nil || node.Next.Type != blackfriday.List {
out(w, crTag)
}
}
case blackfriday.BlockQuote: case blackfriday.BlockQuote:
if entering { if entering {
out(w, quoteTag) out(w, quoteTag)
@ -160,6 +199,11 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
r.handleTableCell(w, node, entering) r.handleTableCell(w, node, entering)
case blackfriday.HTMLSpan: case blackfriday.HTMLSpan:
// ignore other HTML tags // ignore other HTML tags
case blackfriday.HTMLBlock:
if bytes.HasPrefix(node.Literal, []byte("<!--")) {
break // ignore comments, no warning
}
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
default: default:
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
} }
@ -187,6 +231,10 @@ func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, enteri
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) { func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag openTag := listTag
closeTag := listCloseTag closeTag := listCloseTag
if (entering && r.listDepth == 0) || (!entering && r.listDepth == 1) {
openTag = crTag
closeTag = ""
}
if node.ListFlags&blackfriday.ListTypeDefinition != 0 { if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node // tags for definition lists handled within Item node
openTag = "" openTag = ""
@ -215,23 +263,25 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 { } else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below). // DT (definition term): line just before DD (see below).
out(w, dtTag) out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// DD (definition description): line that starts with ": ". // DD (definition description): line that starts with ": ".
// //
// We have to distinguish between the first DD and the // We have to distinguish between the first DD and the
// subsequent ones, as there should be no vertical // subsequent ones, as there should be no vertical
// whitespace between the DT and the first DD. // whitespace between the DT and the first DD.
if r.firstDD { if node.Prev != nil && node.Prev.ListFlags&(blackfriday.ListTypeTerm|blackfriday.ListTypeDefinition) == blackfriday.ListTypeDefinition {
r.firstDD = false if node.Prev.Type == blackfriday.Item &&
node.Prev.LastChild != nil &&
node.Prev.LastChild.Type == blackfriday.List &&
node.Prev.LastChild.ListFlags&blackfriday.ListTypeDefinition == 0 {
out(w, ".IP\n")
} else { } else {
out(w, dd2Tag) out(w, dd2Tag)
} }
}
} else { } else {
out(w, ".IP \\(bu 2\n") out(w, ".IP \\(bu 2\n")
} }
} else {
out(w, "\n")
} }
} }
@ -254,7 +304,7 @@ func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, ente
start = "\t" start = "\t"
} }
if node.IsHeader { if node.IsHeader {
start += codespanTag start += strongTag
} else if nodeLiteralSize(node) > 30 { } else if nodeLiteralSize(node) > 30 {
start += tableCellStart start += tableCellStart
} }
@ -262,13 +312,12 @@ func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, ente
} else { } else {
var end string var end string
if node.IsHeader { if node.IsHeader {
end = codespanCloseTag end = strongCloseTag
} else if nodeLiteralSize(node) > 30 { } else if nodeLiteralSize(node) > 30 {
end = tableCellEnd end = tableCellEnd
} }
if node.Next == nil && end != tableCellEnd { if node.Next == nil {
// Last cell: need to carriage return if we are at the end of the // Last cell: need to carriage return if we are at the end of the header row.
// header row and content isn't wrapped in a "tablecell"
end += crTag end += crTag
} }
out(w, end) out(w, end)
@ -310,6 +359,28 @@ func out(w io.Writer, output string) {
} }
func escapeSpecialChars(w io.Writer, text []byte) { func escapeSpecialChars(w io.Writer, text []byte) {
scanner := bufio.NewScanner(bytes.NewReader(text))
// count the number of lines in the text
// we need to know this to avoid adding a newline after the last line
n := bytes.Count(text, []byte{'\n'})
idx := 0
for scanner.Scan() {
dt := scanner.Bytes()
if idx < n {
idx++
dt = append(dt, '\n')
}
escapeSpecialCharsLine(w, dt)
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func escapeSpecialCharsLine(w io.Writer, text []byte) {
for i := 0; i < len(text); i++ { for i := 0; i < len(text); i++ {
// escape initial apostrophe or period // escape initial apostrophe or period
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
@ -334,3 +405,12 @@ func escapeSpecialChars(w io.Writer, text []byte) {
w.Write([]byte{'\\', text[i]}) //nolint:errcheck w.Write([]byte{'\\', text[i]}) //nolint:errcheck
} }
} }
// bytesCut is a copy of [bytes.Cut] to provide compatibility with go1.17
// and older. We can remove this once we drop support for go1.17 and older.
func bytesCut(s, sep []byte) (before, after []byte, found bool) {
if i := bytes.Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, nil, false
}

View file

@ -1,8 +1,13 @@
The Snappy compression format in the Go programming language. The Snappy compression format in the Go programming language.
To download and install from source: To use as a library:
$ go get github.com/golang/snappy $ go get github.com/golang/snappy
To use as a binary:
$ go install github.com/golang/snappy/cmd/snappytool@latest
$ cat decoded | ~/go/bin/snappytool -e > encoded
$ cat encoded | ~/go/bin/snappytool -d > decoded
Unless otherwise noted, the Snappy-Go source files are distributed Unless otherwise noted, the Snappy-Go source files are distributed
under the BSD-style license found in the LICENSE file. under the BSD-style license found in the LICENSE file.

View file

@ -27,7 +27,7 @@
// The unusual register allocation of local variables, such as R10 for the // The unusual register allocation of local variables, such as R10 for the
// source pointer, matches the allocation used at the call site in encodeBlock, // source pointer, matches the allocation used at the call site in encodeBlock,
// which makes it easier to manually inline this function. // which makes it easier to manually inline this function.
TEXT ·emitLiteral(SB), NOSPLIT, $32-56 TEXT ·emitLiteral(SB), NOSPLIT, $40-56
MOVD dst_base+0(FP), R8 MOVD dst_base+0(FP), R8
MOVD lit_base+24(FP), R10 MOVD lit_base+24(FP), R10
MOVD lit_len+32(FP), R3 MOVD lit_len+32(FP), R3
@ -261,7 +261,7 @@ extendMatchEnd:
// "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An // "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An
// extra 64 bytes, to call other functions, and an extra 64 bytes, to spill // extra 64 bytes, to call other functions, and an extra 64 bytes, to spill
// local variables (registers) during calls gives 32768 + 64 + 64 = 32896. // local variables (registers) during calls gives 32768 + 64 + 64 = 32896.
TEXT ·encodeBlock(SB), 0, $32896-56 TEXT ·encodeBlock(SB), 0, $32904-56
MOVD dst_base+0(FP), R8 MOVD dst_base+0(FP), R8
MOVD src_base+24(FP), R7 MOVD src_base+24(FP), R7
MOVD src_len+32(FP), R14 MOVD src_len+32(FP), R14

20
vendor/github.com/gorilla/mux/.editorconfig generated vendored Normal file
View file

@ -0,0 +1,20 @@
; https://editorconfig.org/
root = true
[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4
[*.md]
indent_size = 4
trim_trailing_whitespace = false
eclint_indent_style = unset

1
vendor/github.com/gorilla/mux/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
coverage.coverprofile

View file

@ -1,8 +0,0 @@
# This is the official list of gorilla/mux authors for copyright purposes.
#
# Please keep the list sorted.
Google LLC (https://opensource.google.com/)
Kamil Kisielk <kamil@kamilkisiel.net>
Matt Silverlock <matt@eatsleeprepeat.net>
Rodrigo Moraes (https://github.com/moraes)

View file

@ -1,4 +1,4 @@
Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. Copyright (c) 2023 The Gorilla Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are

34
vendor/github.com/gorilla/mux/Makefile generated vendored Normal file
View file

@ -0,0 +1,34 @@
GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
GO_SEC=$(shell which gosec 2> /dev/null || echo '')
GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
.PHONY: golangci-lint
golangci-lint:
$(if $(GO_LINT), ,go install $(GO_LINT_URI))
@echo "##### Running golangci-lint"
golangci-lint run -v
.PHONY: gosec
gosec:
$(if $(GO_SEC), ,go install $(GO_SEC_URI))
@echo "##### Running gosec"
gosec ./...
.PHONY: govulncheck
govulncheck:
$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
@echo "##### Running govulncheck"
govulncheck ./...
.PHONY: verify
verify: golangci-lint gosec govulncheck
.PHONY: test
test:
@echo "##### Running tests"
go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...

View file

@ -1,12 +1,12 @@
# gorilla/mux # gorilla/mux
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) ![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)
[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) [![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) [![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png)
https://www.gorillatoolkit.org/pkg/mux ![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
their respective handler. their respective handler.
@ -247,32 +247,25 @@ type spaHandler struct {
// file located at the index path on the SPA handler will be served. This // file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application). // is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the absolute path to prevent directory traversal // Join internally call path.Clean to prevent directory traversal
path, err := filepath.Abs(r.URL.Path) path := filepath.Join(h.staticPath, r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request // check whether a file exists or is a directory at the given path
// and stop fi, err := os.Stat(path)
http.Error(w, err.Error(), http.StatusBadRequest) if os.IsNotExist(err) || fi.IsDir() {
// file does not exist or path is a directory, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return return
} }
// prepend the path with the path to the static directory if err != nil {
path = filepath.Join(h.staticPath, path)
// check whether a file exists at the given path
_, err = os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
} else if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the // if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop // file, return a 500 internal server error and stop
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// otherwise, use http.FileServer to serve the static dir // otherwise, use http.FileServer to serve the static file
http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
} }
@ -375,6 +368,19 @@ url, err := r.Get("article").URL("subdomain", "news",
"id", "42") "id", "42")
``` ```
To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available:
```go
r := mux.NewRouter()
r.Host("{domain}").
Path("/{group}/{item_id}").
Queries("some_data1", "{some_data1}").
Queries("some_data2", "{some_data2}").
Name("article")
// Will print [domain group item_id some_data1 some_data2] <nil>
fmt.Println(r.Get("article").GetVarNames())
```
### Walking Routes ### Walking Routes
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
@ -572,7 +578,7 @@ func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/", handler) r.HandleFunc("/", handler)
amw := authenticationMiddleware{} amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate() amw.Populate()
r.Use(amw.Middleware) r.Use(amw.Middleware)
@ -758,7 +764,8 @@ func TestMetricsHandler(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
// Need to create a router that we can pass the request through so that the vars will be added to the context // To add the vars to the context,
// we need to create a router through which we can pass the request.
router := mux.NewRouter() router := mux.NewRouter()
router.HandleFunc("/metrics/{type}", MetricsHandler) router.HandleFunc("/metrics/{type}", MetricsHandler)
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)

11
vendor/github.com/gorilla/mux/doc.go generated vendored
View file

@ -10,17 +10,17 @@ http.ServeMux, mux.Router matches incoming requests against a list of
registered routes and calls a handler for the route that matches the URL registered routes and calls a handler for the route that matches the URL
or other conditions. The main features are: or other conditions. The main features are:
* Requests can be matched based on URL host, path, path prefix, schemes, - Requests can be matched based on URL host, path, path prefix, schemes,
header and query values, HTTP methods or using custom matchers. header and query values, HTTP methods or using custom matchers.
* URL hosts, paths and query values can have variables with an optional - URL hosts, paths and query values can have variables with an optional
regular expression. regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining - Registered URLs can be built, or "reversed", which helps maintaining
references to resources. references to resources.
* Routes can be used as subrouters: nested routes are only tested if the - Routes can be used as subrouters: nested routes are only tested if the
parent route matches. This is useful to define groups of routes that parent route matches. This is useful to define groups of routes that
share common conditions like a host, a path prefix or other repeated share common conditions like a host, a path prefix or other repeated
attributes. As a bonus, this optimizes request matching. attributes. As a bonus, this optimizes request matching.
* It implements the http.Handler interface so it is compatible with the - It implements the http.Handler interface so it is compatible with the
standard http.ServeMux. standard http.ServeMux.
Let's start registering a couple of URL paths and handlers: Let's start registering a couple of URL paths and handlers:
@ -301,6 +301,5 @@ A more complex authentication middleware, which maps session token to users, cou
r.Use(amw.Middleware) r.Use(amw.Middleware)
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
*/ */
package mux package mux

View file

@ -46,9 +46,11 @@ func NewRouter() *Router {
// This will send all incoming requests to the router. // This will send all incoming requests to the router.
type Router struct { type Router struct {
// Configurable Handler to be used when no route matches. // Configurable Handler to be used when no route matches.
// This can be used to render your own 404 Not Found errors.
NotFoundHandler http.Handler NotFoundHandler http.Handler
// Configurable Handler to be used when the request method does not match the route. // Configurable Handler to be used when the request method does not match the route.
// This can be used to render your own 405 Method Not Allowed errors.
MethodNotAllowedHandler http.Handler MethodNotAllowedHandler http.Handler
// Routes to be matched, in order. // Routes to be matched, in order.

View file

@ -22,10 +22,10 @@ type routeRegexpOptions struct {
type regexpType int type regexpType int
const ( const (
regexpTypePath regexpType = 0 regexpTypePath regexpType = iota
regexpTypeHost regexpType = 1 regexpTypeHost
regexpTypePrefix regexpType = 2 regexpTypePrefix
regexpTypeQuery regexpType = 3 regexpTypeQuery
) )
// newRouteRegexp parses a route template and returns a routeRegexp, // newRouteRegexp parses a route template and returns a routeRegexp,
@ -195,7 +195,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
// url builds a URL part using the given values. // url builds a URL part using the given values.
func (r *routeRegexp) url(values map[string]string) (string, error) { func (r *routeRegexp) url(values map[string]string) (string, error) {
urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) urlValues := make([]interface{}, len(r.varsN))
for k, v := range r.varsN { for k, v := range r.varsN {
value, ok := values[v] value, ok := values[v]
if !ok { if !ok {

View file

@ -64,8 +64,18 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
match.MatchErr = nil match.MatchErr = nil
} }
matchErr = nil matchErr = nil // nolint:ineffassign
return false return false
} else {
// Multiple routes may share the same path but use different HTTP methods. For instance:
// Route 1: POST "/users/{id}".
// Route 2: GET "/users/{id}", parameters: "id": "[0-9]+".
//
// The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2",
// The router should return a "Not Found" error as no route fully matches this request.
if match.MatchErr == ErrMethodMismatch {
match.MatchErr = nil
}
} }
} }
@ -230,7 +240,7 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
// Headers adds a matcher for request header values. // Headers adds a matcher for request header values.
// It accepts a sequence of key/value pairs to be matched. For example: // It accepts a sequence of key/value pairs to be matched. For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// r.Headers("Content-Type", "application/json", // r.Headers("Content-Type", "application/json",
// "X-Requested-With", "XMLHttpRequest") // "X-Requested-With", "XMLHttpRequest")
// //
@ -255,7 +265,7 @@ func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
// support. For example: // support. For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// r.HeadersRegexp("Content-Type", "application/(text|json)", // r.HeadersRegexp("Content-Type", "application/(text|json)",
// "X-Requested-With", "XMLHttpRequest") // "X-Requested-With", "XMLHttpRequest")
// //
@ -283,7 +293,7 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route {
// //
// For example: // For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// r.Host("www.example.com") // r.Host("www.example.com")
// r.Host("{subdomain}.domain.com") // r.Host("{subdomain}.domain.com")
// r.Host("{subdomain:[a-z]+}.domain.com") // r.Host("{subdomain:[a-z]+}.domain.com")
@ -342,7 +352,7 @@ func (r *Route) Methods(methods ...string) *Route {
// //
// For example: // For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// r.Path("/products/").Handler(ProductsHandler) // r.Path("/products/").Handler(ProductsHandler)
// r.Path("/products/{key}").Handler(ProductsHandler) // r.Path("/products/{key}").Handler(ProductsHandler)
// r.Path("/articles/{category}/{id:[0-9]+}"). // r.Path("/articles/{category}/{id:[0-9]+}").
@ -377,7 +387,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
// It accepts a sequence of key/value pairs. Values may define variables. // It accepts a sequence of key/value pairs. Values may define variables.
// For example: // For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// r.Queries("foo", "bar", "id", "{id:[0-9]+}") // r.Queries("foo", "bar", "id", "{id:[0-9]+}")
// //
// The above route will only match if the URL contains the defined queries // The above route will only match if the URL contains the defined queries
@ -473,7 +483,7 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
// //
// It will test the inner routes only if the parent route matched. For example: // It will test the inner routes only if the parent route matched. For example:
// //
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// s := r.Host("www.example.com").Subrouter() // s := r.Host("www.example.com").Subrouter()
// s.HandleFunc("/products/", ProductsHandler) // s.HandleFunc("/products/", ProductsHandler)
// s.HandleFunc("/products/{key}", ProductHandler) // s.HandleFunc("/products/{key}", ProductHandler)
@ -524,7 +534,7 @@ func (r *Route) Subrouter() *Router {
// The scheme of the resulting url will be the first argument that was passed to Schemes: // The scheme of the resulting url will be the first argument that was passed to Schemes:
// //
// // url.String() will be "https://example.com" // // url.String() will be "https://example.com"
// r := mux.NewRouter() // r := mux.NewRouter().NewRoute()
// url, err := r.Host("example.com") // url, err := r.Host("example.com")
// .Schemes("https", "http").URL() // .Schemes("https", "http").URL()
// //
@ -718,6 +728,25 @@ func (r *Route) GetHostTemplate() (string, error) {
return r.regexp.host.template, nil return r.regexp.host.template, nil
} }
// GetVarNames returns the names of all variables added by regexp matchers
// These can be used to know which route variables should be passed into r.URL()
func (r *Route) GetVarNames() ([]string, error) {
if r.err != nil {
return nil, r.err
}
var varNames []string
if r.regexp.host != nil {
varNames = append(varNames, r.regexp.host.varsN...)
}
if r.regexp.path != nil {
varNames = append(varNames, r.regexp.path.varsN...)
}
for _, regx := range r.regexp.queries {
varNames = append(varNames, regx.varsN...)
}
return varNames, nil
}
// prepareVars converts the route variable pairs into a map. If the route has a // prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked. // BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {

View file

@ -57,6 +57,8 @@ type ValidateOpts struct {
Digits otp.Digits Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1. // Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm Algorithm otp.Algorithm
// Encoder to use for output code.
Encoder otp.Encoder
} }
// GenerateCode creates a HOTP passcode given a counter and secret. // GenerateCode creates a HOTP passcode given a counter and secret.
@ -112,6 +114,8 @@ func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passc
(int(sum[offset+3]) & 0xff)) (int(sum[offset+3]) & 0xff))
l := opts.Digits.Length() l := opts.Digits.Length()
switch opts.Encoder {
case otp.EncoderDefault:
mod := int32(value % int64(math.Pow10(l))) mod := int32(value % int64(math.Pow10(l)))
if debug { if debug {
@ -119,8 +123,25 @@ func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passc
fmt.Printf("value=%v\n", value) fmt.Printf("value=%v\n", value)
fmt.Printf("mod'ed=%v\n", mod) fmt.Printf("mod'ed=%v\n", mod)
} }
passcode = opts.Digits.Format(mod)
case otp.EncoderSteam:
// Define the character set used by Steam Guard codes.
alphabet := []byte{
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'T', 'V', 'W', 'X', 'Y',
}
radix := int64(len(alphabet))
return opts.Digits.Format(mod), nil for i := 0; i < l; i++ {
digit := value % radix
value /= radix
c := alphabet[digit]
passcode += string(c)
}
}
return
} }
// ValidateCustom validates an HOTP with customizable options. Most users should // ValidateCustom validates an HOTP with customizable options. Most users should
@ -194,7 +215,7 @@ func Generate(opts GenerateOpts) (*otp.Key, error) {
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret)) v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
} else { } else {
secret := make([]byte, opts.SecretSize) secret := make([]byte, opts.SecretSize)
_, err := opts.Rand.Read(secret) _, err := io.ReadFull(opts.Rand, secret)
if err != nil { if err != nil {
return nil, err return nil, err
} }

27
vendor/github.com/pquerna/otp/otp.go generated vendored
View file

@ -154,12 +154,7 @@ func (k *Key) Digits() Digits {
q := k.url.Query() q := k.url.Query()
if u, err := strconv.ParseUint(q.Get("digits"), 10, 64); err == nil { if u, err := strconv.ParseUint(q.Get("digits"), 10, 64); err == nil {
switch u { return Digits(u)
case 8:
return DigitsEight
default:
return DigitsSix
}
} }
// Six is the most common value. // Six is the most common value.
@ -183,6 +178,19 @@ func (k *Key) Algorithm() Algorithm {
} }
} }
// Encoder returns the encoder used or the default ("")
func (k *Key) Encoder() Encoder {
q := k.url.Query()
a := strings.ToLower(q.Get("encoder"))
switch a {
case "steam":
return EncoderSteam
default:
return EncoderDefault
}
}
// URL returns the OTP URL as a string // URL returns the OTP URL as a string
func (k *Key) URL() string { func (k *Key) URL() string {
return k.url.String() return k.url.String()
@ -253,3 +261,10 @@ func (d Digits) Length() int {
func (d Digits) String() string { func (d Digits) String() string {
return fmt.Sprintf("%d", d) return fmt.Sprintf("%d", d)
} }
type Encoder string
const (
EncoderDefault Encoder = ""
EncoderSteam Encoder = "steam"
)

View file

@ -73,6 +73,8 @@ type ValidateOpts struct {
Digits otp.Digits Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1. // Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm Algorithm otp.Algorithm
// Encoder to use for output code.
Encoder otp.Encoder
} }
// GenerateCodeCustom takes a timepoint and produces a passcode using a // GenerateCodeCustom takes a timepoint and produces a passcode using a
@ -86,6 +88,7 @@ func GenerateCodeCustom(secret string, t time.Time, opts ValidateOpts) (passcode
passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{ passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{
Digits: opts.Digits, Digits: opts.Digits,
Algorithm: opts.Algorithm, Algorithm: opts.Algorithm,
Encoder: opts.Encoder,
}) })
if err != nil { if err != nil {
return "", err return "", err
@ -113,8 +116,8 @@ func ValidateCustom(passcode string, secret string, t time.Time, opts ValidateOp
rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{ rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{
Digits: opts.Digits, Digits: opts.Digits,
Algorithm: opts.Algorithm, Algorithm: opts.Algorithm,
Encoder: opts.Encoder,
}) })
if err != nil { if err != nil {
return false, err return false, err
} }
@ -184,7 +187,7 @@ func Generate(opts GenerateOpts) (*otp.Key, error) {
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret)) v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
} else { } else {
secret := make([]byte, opts.SecretSize) secret := make([]byte, opts.SecretSize)
_, err := opts.Rand.Read(secret) _, err := io.ReadFull(opts.Rand, secret)
if err != nil { if err != nil {
return nil, err return nil, err
} }

2
vendor/go.etcd.io/bbolt/.gitignore generated vendored
View file

@ -6,5 +6,7 @@ cover.out
cover-*.out cover-*.out
/.idea /.idea
*.iml *.iml
/bbolt
/cmd/bbolt/bbolt /cmd/bbolt/bbolt
.DS_Store

View file

@ -1 +1 @@
1.22.6 1.23.12

60
vendor/go.etcd.io/bbolt/Makefile generated vendored
View file

@ -1,6 +1,7 @@
BRANCH=`git rev-parse --abbrev-ref HEAD` BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMIT=`git rev-parse --short HEAD` COMMIT=`git rev-parse --short HEAD`
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
GOFILES = $(shell find . -name \*.go)
TESTFLAGS_RACE=-race=false TESTFLAGS_RACE=-race=false
ifdef ENABLE_RACE ifdef ENABLE_RACE
@ -13,9 +14,26 @@ ifdef CPU
endif endif
TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS) TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS)
TESTFLAGS_TIMEOUT=30m
ifdef TIMEOUT
TESTFLAGS_TIMEOUT=$(TIMEOUT)
endif
TESTFLAGS_ENABLE_STRICT_MODE=false
ifdef ENABLE_STRICT_MODE
TESTFLAGS_ENABLE_STRICT_MODE=$(ENABLE_STRICT_MODE)
endif
.EXPORT_ALL_VARIABLES:
TEST_ENABLE_STRICT_MODE=${TESTFLAGS_ENABLE_STRICT_MODE}
.PHONY: fmt .PHONY: fmt
fmt: fmt:
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]') @echo "Verifying gofmt, failures can be fixed with ./scripts/fix.sh"
@!(gofmt -l -s -d ${GOFILES} | grep '[a-z]')
@echo "Verifying goimports, failures can be fixed with ./scripts/fix.sh"
@!(go run golang.org/x/tools/cmd/goimports@latest -l -d ${GOFILES} | grep '[a-z]')
.PHONY: lint .PHONY: lint
lint: lint:
@ -24,21 +42,23 @@ lint:
.PHONY: test .PHONY: test
test: test:
@echo "hashmap freelist test" @echo "hashmap freelist test"
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./internal/...
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
@echo "array freelist test" @echo "array freelist test"
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./internal/...
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
.PHONY: coverage .PHONY: coverage
coverage: coverage:
@echo "hashmap freelist test" @echo "hashmap freelist test"
TEST_FREELIST_TYPE=hashmap go test -v -timeout 30m \ TEST_FREELIST_TYPE=hashmap go test -v -timeout ${TESTFLAGS_TIMEOUT} \
-coverprofile cover-freelist-hashmap.out -covermode atomic -coverprofile cover-freelist-hashmap.out -covermode atomic
@echo "array freelist test" @echo "array freelist test"
TEST_FREELIST_TYPE=array go test -v -timeout 30m \ TEST_FREELIST_TYPE=array go test -v -timeout ${TESTFLAGS_TIMEOUT} \
-coverprofile cover-freelist-array.out -covermode atomic -coverprofile cover-freelist-array.out -covermode atomic
BOLT_CMD=bbolt BOLT_CMD=bbolt
@ -55,7 +75,7 @@ gofail-enable: install-gofail
gofail enable . gofail enable .
.PHONY: gofail-disable .PHONY: gofail-disable
gofail-disable: gofail-disable: install-gofail
gofail disable . gofail disable .
.PHONY: install-gofail .PHONY: install-gofail
@ -65,12 +85,24 @@ install-gofail:
.PHONY: test-failpoint .PHONY: test-failpoint
test-failpoint: test-failpoint:
@echo "[failpoint] hashmap freelist test" @echo "[failpoint] hashmap freelist test"
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
@echo "[failpoint] array freelist test" @echo "[failpoint] array freelist test"
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
.PHONY: test-robustness # Running robustness tests requires root permission .PHONY: test-robustness # Running robustness tests requires root permission for now
test-robustness: # TODO: Remove sudo once we fully migrate to the prow infrastructure
go test -v ${TESTFLAGS} ./tests/dmflakey -test.root test-robustness: gofail-enable build
go test -v ${TESTFLAGS} ./tests/robustness -test.root sudo env PATH=$$PATH go test -v ${TESTFLAGS} ./tests/dmflakey -test.root
sudo env PATH=$(PWD)/bin:$$PATH go test -v ${TESTFLAGS} ${ROBUSTNESS_TESTFLAGS} ./tests/robustness -test.root
.PHONY: test-benchmark-compare
# Runs benchmark tests on the current git ref and the given REF, and compares
# the two.
test-benchmark-compare: install-benchstat
@git fetch
./scripts/compare_benchmarks.sh $(REF)
.PHONY: install-benchstat
install-benchstat:
go install golang.org/x/perf/cmd/benchstat@latest

10
vendor/go.etcd.io/bbolt/OWNERS generated vendored Normal file
View file

@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- ahrtr # Benjamin Wang <benjamin.ahrtr@gmail.com> <benjamin.wang@broadcom.com>
- serathius # Marek Siarkowicz <siarkowicz@google.com> <marek.siarkowicz@gmail.com>
- ptabor # Piotr Tabor <piotr.tabor@gmail.com>
- spzala # Sahdev Zala <spzala@us.ibm.com>
reviewers:
- fuweid # Wei Fu <fuweid89@gmail.com>
- tjungblu # Thomas Jungblut <tjungblu@redhat.com>

68
vendor/go.etcd.io/bbolt/README.md generated vendored
View file

@ -1,10 +1,8 @@
bbolt bbolt
===== =====
[![Go Report Card](https://goreportcard.com/badge/github.com/etcd-io/bbolt?style=flat-square)](https://goreportcard.com/report/github.com/etcd-io/bbolt) [![Go Report Card](https://goreportcard.com/badge/go.etcd.io/bbolt?style=flat-square)](https://goreportcard.com/report/go.etcd.io/bbolt)
[![Coverage](https://codecov.io/gh/etcd-io/bbolt/branch/master/graph/badge.svg)](https://codecov.io/gh/etcd-io/bbolt) [![Go Reference](https://pkg.go.dev/badge/go.etcd.io/bbolt.svg)](https://pkg.go.dev/go.etcd.io/bbolt)
[![Build Status Travis](https://img.shields.io/travis/etcd-io/bboltlabs.svg?style=flat-square&&branch=master)](https://travis-ci.com/etcd-io/bbolt)
[![Godoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/etcd-io/bbolt)
[![Releases](https://img.shields.io/github/release/etcd-io/bbolt/all.svg?style=flat-square)](https://github.com/etcd-io/bbolt/releases) [![Releases](https://img.shields.io/github/release/etcd-io/bbolt/all.svg?style=flat-square)](https://github.com/etcd-io/bbolt/releases)
[![LICENSE](https://img.shields.io/github/license/etcd-io/bbolt.svg?style=flat-square)](https://github.com/etcd-io/bbolt/blob/master/LICENSE) [![LICENSE](https://img.shields.io/github/license/etcd-io/bbolt.svg?style=flat-square)](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
@ -71,13 +69,14 @@ New minor versions may add additional features to the API.
- [LMDB](#lmdb) - [LMDB](#lmdb)
- [Caveats & Limitations](#caveats--limitations) - [Caveats & Limitations](#caveats--limitations)
- [Reading the Source](#reading-the-source) - [Reading the Source](#reading-the-source)
- [Known Issues](#known-issues)
- [Other Projects Using Bolt](#other-projects-using-bolt) - [Other Projects Using Bolt](#other-projects-using-bolt)
## Getting Started ## Getting Started
### Installing ### Installing
To start using Bolt, install Go and run `go get`: To start using `bbolt`, install Go and run `go get`:
```sh ```sh
$ go get go.etcd.io/bbolt@latest $ go get go.etcd.io/bbolt@latest
``` ```
@ -103,7 +102,7 @@ To use bbolt as an embedded key-value store, import as:
```go ```go
import bolt "go.etcd.io/bbolt" import bolt "go.etcd.io/bbolt"
db, err := bolt.Open(path, 0666, nil) db, err := bolt.Open(path, 0600, nil)
if err != nil { if err != nil {
return err return err
} }
@ -298,6 +297,17 @@ db.Update(func(tx *bolt.Tx) error {
}) })
``` ```
You can retrieve an existing bucket using the `Tx.Bucket()` function:
```go
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
if b == nil {
return errors.New("bucket does not exist")
}
return nil
})
```
You can also create a bucket only if it doesn't exist by using the You can also create a bucket only if it doesn't exist by using the
`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this `Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this
function for all your top-level buckets after you open your database so you can function for all your top-level buckets after you open your database so you can
@ -305,6 +315,17 @@ guarantee that they exist for future transactions.
To delete a bucket, simply call the `Tx.DeleteBucket()` function. To delete a bucket, simply call the `Tx.DeleteBucket()` function.
You can also iterate over all existing top-level buckets with `Tx.ForEach()`:
```go
db.View(func(tx *bolt.Tx) error {
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
fmt.Println(string(name))
return nil
})
return nil
})
```
### Using key/value pairs ### Using key/value pairs
@ -336,7 +357,17 @@ exists then it will return its byte slice value. If it doesn't exist then it
will return `nil`. It's important to note that you can have a zero-length value will return `nil`. It's important to note that you can have a zero-length value
set to a key which is different than the key not existing. set to a key which is different than the key not existing.
Use the `Bucket.Delete()` function to delete a key from the bucket. Use the `Bucket.Delete()` function to delete a key from the bucket:
```go
db.Update(func (tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Delete([]byte("answer"))
return err
})
```
This will delete the key `answers` from the bucket `MyBucket`.
Please note that values returned from `Get()` are only valid while the Please note that values returned from `Get()` are only valid while the
transaction is open. If you need to use a value outside of the transaction transaction is open. If you need to use a value outside of the transaction
@ -654,7 +685,7 @@ uses a shared lock to allow multiple processes to read from the database but
it will block any processes from opening the database in read-write mode. it will block any processes from opening the database in read-write mode.
```go ```go
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true}) db, err := bolt.Open("my.db", 0600, &bolt.Options{ReadOnly: true})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -890,7 +921,7 @@ The best places to start are the main entry points into Bolt:
- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the - `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
arguments, a cursor is used to traverse the B+tree to the page and position arguments, a cursor is used to traverse the B+tree to the page and position
where they key & value will be written. Once the position is found, the bucket where the key & value will be written. Once the position is found, the bucket
materializes the underlying page and the page's parent pages into memory as materializes the underlying page and the page's parent pages into memory as
"nodes". These nodes are where mutations occur during read-write transactions. "nodes". These nodes are where mutations occur during read-write transactions.
These changes get flushed to disk during commit. These changes get flushed to disk during commit.
@ -919,6 +950,21 @@ The best places to start are the main entry points into Bolt:
If you have additional notes that could be helpful for others, please submit If you have additional notes that could be helpful for others, please submit
them via pull request. them via pull request.
## Known Issues
- bbolt might run into data corruption issue on Linux when the feature
[ext4: fast commit](https://lwn.net/Articles/842385/), which was introduced in
linux kernel version v5.10, is enabled. The fixes to the issue were included in
linux kernel version v5.17, please refer to links below,
* [ext4: fast commit may miss tracking unwritten range during ftruncate](https://lore.kernel.org/linux-ext4/20211223032337.5198-3-yinxin.x@bytedance.com/)
* [ext4: fast commit may not fallback for ineligible commit](https://lore.kernel.org/lkml/202201091544.W5HHEXAp-lkp@intel.com/T/#ma0768815e4b5f671e9e451d578256ef9a76fe30e)
* [ext4 updates for 5.17](https://lore.kernel.org/lkml/YdyxjTFaLWif6BCM@mit.edu/)
Please also refer to the discussion in https://github.com/etcd-io/bbolt/issues/562.
- Writing a value with a length of 0 will always result in reading back an empty `[]byte{}` value.
Please refer to [issues/726#issuecomment-2061694802](https://github.com/etcd-io/bbolt/issues/726#issuecomment-2061694802).
## Other Projects Using Bolt ## Other Projects Using Bolt
@ -934,13 +980,16 @@ Below is a list of public, open source projects that use Bolt:
* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files. * [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
* [BoltDB Viewer](https://github.com/zc310/rich_boltdb) - A BoltDB Viewer Can run on Windows、Linux、Android system. * [BoltDB Viewer](https://github.com/zc310/rich_boltdb) - A BoltDB Viewer Can run on Windows、Linux、Android system.
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. * [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
* [bstore](https://github.com/mjl-/bstore) - Database library storing Go values, with referential/unique/nonzero constraints, indices, automatic schema management with struct tags, and a query API.
* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet. * [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining * [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
simple tx and key scans. simple tx and key scans.
* [Buildkit](https://github.com/moby/buildkit) - concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. * [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations. * [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
* [🌰 Chestnut](https://github.com/jrapoport/chestnut) - Chestnut is encrypted storage for Go. * [🌰 Chestnut](https://github.com/jrapoport/chestnut) - Chestnut is encrypted storage for Go.
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware. * [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
* [Containerd](https://github.com/containerd/containerd) - An open and reliable container runtime
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. * [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency. * [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems. * [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
@ -964,6 +1013,7 @@ Below is a list of public, open source projects that use Bolt:
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite. * [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files. * [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
* [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage. * [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage.
* [Portainer](https://github.com/portainer/portainer) - A lightweight service delivery platform for containerized applications that can be used to manage Docker, Swarm, Kubernetes and ACI environments.
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system. * [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
* [Rain](https://github.com/cenkalti/rain) - BitTorrent client and library. * [Rain](https://github.com/cenkalti/rain) - BitTorrent client and library.
* [reef-pi](https://github.com/reef-pi/reef-pi) - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi. * [reef-pi](https://github.com/reef-pi/reef-pi) - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi.

View file

@ -1,7 +0,0 @@
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF

View file

@ -1,3 +1,5 @@
//go:build aix
package bbolt package bbolt
import ( import (
@ -7,6 +9,8 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"go.etcd.io/bbolt/internal/common"
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
@ -67,7 +71,7 @@ func mmap(db *DB, sz int) error {
// Save the original byte slice and convert to a byte array pointer. // Save the original byte slice and convert to a byte array pointer.
db.dataref = b db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz db.datasz = sz
return nil return nil
} }

View file

@ -1,7 +0,0 @@
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

92
vendor/go.etcd.io/bbolt/bolt_android.go generated vendored Normal file
View file

@ -0,0 +1,92 @@
package bbolt
import (
"fmt"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/unix"
"go.etcd.io/bbolt/internal/common"
)
// flock acquires an advisory lock on a file descriptor.
func flock(db *DB, exclusive bool, timeout time.Duration) error {
var t time.Time
if timeout != 0 {
t = time.Now()
}
fd := db.file.Fd()
var lockType int16
if exclusive {
lockType = syscall.F_WRLCK
} else {
lockType = syscall.F_RDLCK
}
for {
// Attempt to obtain an exclusive lock.
lock := syscall.Flock_t{Type: lockType}
err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
if err == nil {
return nil
} else if err != syscall.EAGAIN {
return err
}
// If we timed out then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout
}
// Wait for a bit and try again.
time.Sleep(flockRetryTimeout)
}
}
// funlock releases an advisory lock on a file descriptor.
func funlock(db *DB) error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Type = syscall.F_UNLCK
lock.Whence = 0
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
}
// mmap memory maps a DB's data file.
func mmap(db *DB, sz int) error {
// Map the data file to memory.
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
if err != nil {
return err
}
// Advise the kernel that the mmap is accessed randomly.
err = unix.Madvise(b, syscall.MADV_RANDOM)
if err != nil && err != syscall.ENOSYS {
// Ignore not implemented error in kernel because it still works.
return fmt.Errorf("madvise: %s", err)
}
// Save the original byte slice and convert to a byte array pointer.
db.dataref = b
db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz
return nil
}
// munmap unmaps a DB's data file from memory.
func munmap(db *DB) error {
// Ignore the unmap if we have no mapped data.
if db.dataref == nil {
return nil
}
// Unmap using the original byte slice.
err := unix.Munmap(db.dataref)
db.dataref = nil
db.data = nil
db.datasz = 0
return err
}

View file

@ -1,7 +0,0 @@
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF

View file

@ -1,10 +0,0 @@
//go:build arm64
// +build arm64
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build loong64
// +build loong64
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build mips64 || mips64le
// +build mips64 mips64le
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x8000000000 // 512GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build mips || mipsle
// +build mips mipsle
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x40000000 // 1GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF

10
vendor/go.etcd.io/bbolt/bolt_ppc.go generated vendored
View file

@ -1,10 +0,0 @@
//go:build ppc
// +build ppc
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF

View file

@ -1,10 +0,0 @@
//go:build ppc64
// +build ppc64
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build ppc64le
// +build ppc64le
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build riscv64
// +build riscv64
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,10 +0,0 @@
//go:build s390x
// +build s390x
package bbolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF

View file

@ -1,6 +1,3 @@
//go:build aix
// +build aix
package bbolt package bbolt
import ( import (
@ -10,6 +7,8 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"go.etcd.io/bbolt/internal/common"
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
@ -70,7 +69,7 @@ func mmap(db *DB, sz int) error {
// Save the original byte slice and convert to a byte array pointer. // Save the original byte slice and convert to a byte array pointer.
db.dataref = b db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz db.datasz = sz
return nil return nil
} }

10
vendor/go.etcd.io/bbolt/bolt_unix.go generated vendored
View file

@ -1,5 +1,4 @@
//go:build !windows && !plan9 && !solaris && !aix //go:build !windows && !plan9 && !solaris && !aix && !android
// +build !windows,!plan9,!solaris,!aix
package bbolt package bbolt
@ -10,6 +9,9 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
@ -36,7 +38,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
// If we timed out then return an error. // If we timed out then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout return errors.ErrTimeout
} }
// Wait for a bit and try again. // Wait for a bit and try again.
@ -66,7 +68,7 @@ func mmap(db *DB, sz int) error {
// Save the original byte slice and convert to a byte array pointer. // Save the original byte slice and convert to a byte array pointer.
db.dataref = b db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz db.datasz = sz
return nil return nil
} }

View file

@ -8,6 +8,9 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
) )
// fdatasync flushes written data to a file descriptor. // fdatasync flushes written data to a file descriptor.
@ -42,7 +45,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
// If we timed oumercit then return an error. // If we timed oumercit then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout return errors.ErrTimeout
} }
// Wait for a bit and try again. // Wait for a bit and try again.
@ -70,7 +73,7 @@ func mmap(db *DB, sz int) error {
return fmt.Errorf("truncate: %s", err) return fmt.Errorf("truncate: %s", err)
} }
sizehi = uint32(sz >> 32) sizehi = uint32(sz >> 32)
sizelo = uint32(sz) & 0xffffffff sizelo = uint32(sz)
} }
// Open a file mapping handle. // Open a file mapping handle.
@ -93,7 +96,7 @@ func mmap(db *DB, sz int) error {
} }
// Convert to a byte array. // Convert to a byte array.
db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr))) db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(addr))
db.datasz = sz db.datasz = sz
return nil return nil

View file

@ -1,5 +1,4 @@
//go:build !windows && !plan9 && !linux && !openbsd //go:build !windows && !plan9 && !linux && !openbsd
// +build !windows,!plan9,!linux,!openbsd
package bbolt package bbolt

484
vendor/go.etcd.io/bbolt/bucket.go generated vendored
View file

@ -4,6 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"unsafe" "unsafe"
"go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
) )
const ( const (
@ -14,8 +17,6 @@ const (
MaxValueSize = (1 << 31) - 2 MaxValueSize = (1 << 31) - 2
) )
const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
const ( const (
minFillPercent = 0.1 minFillPercent = 0.1
maxFillPercent = 1.0 maxFillPercent = 1.0
@ -27,12 +28,12 @@ const DefaultFillPercent = 0.5
// Bucket represents a collection of key/value pairs inside the database. // Bucket represents a collection of key/value pairs inside the database.
type Bucket struct { type Bucket struct {
*bucket *common.InBucket
tx *Tx // the associated transaction tx *Tx // the associated transaction
buckets map[string]*Bucket // subbucket cache buckets map[string]*Bucket // subbucket cache
page *page // inline page reference page *common.Page // inline page reference
rootNode *node // materialized node for the root page. rootNode *node // materialized node for the root page.
nodes map[pgid]*node // node cache nodes map[common.Pgid]*node // node cache
// Sets the threshold for filling nodes when they split. By default, // Sets the threshold for filling nodes when they split. By default,
// the bucket will fill to 50% but it can be useful to increase this // the bucket will fill to 50% but it can be useful to increase this
@ -42,21 +43,12 @@ type Bucket struct {
FillPercent float64 FillPercent float64
} }
// bucket represents the on-file representation of a bucket.
// This is stored as the "value" of a bucket key. If the bucket is small enough,
// then its root page can be stored inline in the "value", after the bucket
// header. In the case of inline buckets, the "root" will be 0.
type bucket struct {
root pgid // page id of the bucket's root-level page
sequence uint64 // monotonically incrementing, used by NextSequence()
}
// newBucket returns a new bucket associated with a transaction. // newBucket returns a new bucket associated with a transaction.
func newBucket(tx *Tx) Bucket { func newBucket(tx *Tx) Bucket {
var b = Bucket{tx: tx, FillPercent: DefaultFillPercent} var b = Bucket{tx: tx, FillPercent: DefaultFillPercent}
if tx.writable { if tx.writable {
b.buckets = make(map[string]*Bucket) b.buckets = make(map[string]*Bucket)
b.nodes = make(map[pgid]*node) b.nodes = make(map[common.Pgid]*node)
} }
return b return b
} }
@ -67,8 +59,8 @@ func (b *Bucket) Tx() *Tx {
} }
// Root returns the root of the bucket. // Root returns the root of the bucket.
func (b *Bucket) Root() pgid { func (b *Bucket) Root() common.Pgid {
return b.root return b.RootPage()
} }
// Writable returns whether the bucket is writable. // Writable returns whether the bucket is writable.
@ -105,7 +97,7 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
k, v, flags := c.seek(name) k, v, flags := c.seek(name)
// Return nil if the key doesn't exist or it is not a bucket. // Return nil if the key doesn't exist or it is not a bucket.
if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 { if !bytes.Equal(name, k) || (flags&common.BucketLeafFlag) == 0 {
return nil return nil
} }
@ -125,8 +117,8 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
// Unaligned access requires a copy to be made. // Unaligned access requires a copy to be made.
const unalignedMask = unsafe.Alignof(struct { const unalignedMask = unsafe.Alignof(struct {
bucket common.InBucket
page common.Page
}{}) - 1 }{}) - 1
unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0 unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0
if unaligned { if unaligned {
@ -136,15 +128,15 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
// If this is a writable transaction then we need to copy the bucket entry. // If this is a writable transaction then we need to copy the bucket entry.
// Read-only transactions can point directly at the mmap entry. // Read-only transactions can point directly at the mmap entry.
if b.tx.writable && !unaligned { if b.tx.writable && !unaligned {
child.bucket = &bucket{} child.InBucket = &common.InBucket{}
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) *child.InBucket = *(*common.InBucket)(unsafe.Pointer(&value[0]))
} else { } else {
child.bucket = (*bucket)(unsafe.Pointer(&value[0])) child.InBucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
} }
// Save a reference to the inline page if the bucket is inline. // Save a reference to the inline page if the bucket is inline.
if child.root == 0 { if child.RootPage() == 0 {
child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) child.page = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
} }
return &child return &child
@ -153,13 +145,23 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
// CreateBucket creates a new bucket at the given key and returns the new bucket. // CreateBucket creates a new bucket at the given key and returns the new bucket.
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long. // Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
// The bucket instance is only valid for the lifetime of the transaction. // The bucket instance is only valid for the lifetime of the transaction.
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) { func (b *Bucket) CreateBucket(key []byte) (rb *Bucket, err error) {
if lg := b.tx.db.Logger(); lg != discardLogger {
lg.Debugf("Creating bucket %q", key)
defer func() {
if err != nil {
lg.Errorf("Creating bucket %q failed: %v", key, err)
} else {
lg.Debugf("Creating bucket %q successfully", key)
}
}()
}
if b.tx.db == nil { if b.tx.db == nil {
return nil, ErrTxClosed return nil, errors.ErrTxClosed
} else if !b.tx.writable { } else if !b.tx.writable {
return nil, ErrTxNotWritable return nil, errors.ErrTxNotWritable
} else if len(key) == 0 { } else if len(key) == 0 {
return nil, ErrBucketNameRequired return nil, errors.ErrBucketNameRequired
} }
// Insert into node. // Insert into node.
@ -173,21 +175,21 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
// Return an error if there is an existing key. // Return an error if there is an existing key.
if bytes.Equal(newKey, k) { if bytes.Equal(newKey, k) {
if (flags & bucketLeafFlag) != 0 { if (flags & common.BucketLeafFlag) != 0 {
return nil, ErrBucketExists return nil, errors.ErrBucketExists
} }
return nil, ErrIncompatibleValue return nil, errors.ErrIncompatibleValue
} }
// Create empty, inline bucket. // Create empty, inline bucket.
var bucket = Bucket{ var bucket = Bucket{
bucket: &bucket{}, InBucket: &common.InBucket{},
rootNode: &node{isLeaf: true}, rootNode: &node{isLeaf: true},
FillPercent: DefaultFillPercent, FillPercent: DefaultFillPercent,
} }
var value = bucket.write() var value = bucket.write()
c.node().put(newKey, newKey, value, 0, bucketLeafFlag) c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
// Since subbuckets are not allowed on inline buckets, we need to // Since subbuckets are not allowed on inline buckets, we need to
// dereference the inline page, if it exists. This will cause the bucket // dereference the inline page, if it exists. This will cause the bucket
@ -200,39 +202,108 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it. // CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
// Returns an error if the bucket name is blank, or if the bucket name is too long. // Returns an error if the bucket name is blank, or if the bucket name is too long.
// The bucket instance is only valid for the lifetime of the transaction. // The bucket instance is only valid for the lifetime of the transaction.
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) { func (b *Bucket) CreateBucketIfNotExists(key []byte) (rb *Bucket, err error) {
child, err := b.CreateBucket(key) if lg := b.tx.db.Logger(); lg != discardLogger {
if err == ErrBucketExists { lg.Debugf("Creating bucket if not exist %q", key)
return b.Bucket(key), nil defer func() {
} else if err != nil { if err != nil {
return nil, err lg.Errorf("Creating bucket if not exist %q failed: %v", key, err)
} else {
lg.Debugf("Creating bucket if not exist %q successfully", key)
} }
return child, nil }()
} }
// DeleteBucket deletes a bucket at the given key.
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
func (b *Bucket) DeleteBucket(key []byte) error {
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return nil, errors.ErrTxClosed
} else if !b.Writable() { } else if !b.tx.writable {
return ErrTxNotWritable return nil, errors.ErrTxNotWritable
} else if len(key) == 0 {
return nil, errors.ErrBucketNameRequired
}
// Insert into node.
// Tip: Use a new variable `newKey` instead of reusing the existing `key` to prevent
// it from being marked as leaking, and accordingly cannot be allocated on stack.
newKey := cloneBytes(key)
if b.buckets != nil {
if child := b.buckets[string(newKey)]; child != nil {
return child, nil
}
} }
// Move cursor to correct position. // Move cursor to correct position.
c := b.Cursor() c := b.Cursor()
k, _, flags := c.seek(key) k, v, flags := c.seek(newKey)
// Return an error if there is an existing non-bucket key.
if bytes.Equal(newKey, k) {
if (flags & common.BucketLeafFlag) != 0 {
var child = b.openBucket(v)
if b.buckets != nil {
b.buckets[string(newKey)] = child
}
return child, nil
}
return nil, errors.ErrIncompatibleValue
}
// Create empty, inline bucket.
var bucket = Bucket{
InBucket: &common.InBucket{},
rootNode: &node{isLeaf: true},
FillPercent: DefaultFillPercent,
}
var value = bucket.write()
c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
// Since subbuckets are not allowed on inline buckets, we need to
// dereference the inline page, if it exists. This will cause the bucket
// to be treated as a regular, non-inline bucket for the rest of the tx.
b.page = nil
return b.Bucket(newKey), nil
}
// DeleteBucket deletes a bucket at the given key.
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
func (b *Bucket) DeleteBucket(key []byte) (err error) {
if lg := b.tx.db.Logger(); lg != discardLogger {
lg.Debugf("Deleting bucket %q", key)
defer func() {
if err != nil {
lg.Errorf("Deleting bucket %q failed: %v", key, err)
} else {
lg.Debugf("Deleting bucket %q successfully", key)
}
}()
}
if b.tx.db == nil {
return errors.ErrTxClosed
} else if !b.Writable() {
return errors.ErrTxNotWritable
}
newKey := cloneBytes(key)
// Move cursor to correct position.
c := b.Cursor()
k, _, flags := c.seek(newKey)
// Return an error if bucket doesn't exist or is not a bucket. // Return an error if bucket doesn't exist or is not a bucket.
if !bytes.Equal(key, k) { if !bytes.Equal(newKey, k) {
return ErrBucketNotFound return errors.ErrBucketNotFound
} else if (flags & bucketLeafFlag) == 0 { } else if (flags & common.BucketLeafFlag) == 0 {
return ErrIncompatibleValue return errors.ErrIncompatibleValue
} }
// Recursively delete all child buckets. // Recursively delete all child buckets.
child := b.Bucket(key) child := b.Bucket(newKey)
err := child.ForEachBucket(func(k []byte) error { err = child.ForEachBucket(func(k []byte) error {
if err := child.DeleteBucket(k); err != nil { if err := child.DeleteBucket(k); err != nil {
return fmt.Errorf("delete bucket: %s", err) return fmt.Errorf("delete bucket: %s", err)
} }
@ -243,7 +314,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
} }
// Remove cached copy. // Remove cached copy.
delete(b.buckets, string(key)) delete(b.buckets, string(newKey))
// Release all bucket pages to freelist. // Release all bucket pages to freelist.
child.nodes = nil child.nodes = nil
@ -251,19 +322,119 @@ func (b *Bucket) DeleteBucket(key []byte) error {
child.free() child.free()
// Delete the node if we have a matching key. // Delete the node if we have a matching key.
c.node().del(key) c.node().del(newKey)
return nil return nil
} }
// MoveBucket moves a sub-bucket from the source bucket to the destination bucket.
// Returns an error if
// 1. the sub-bucket cannot be found in the source bucket;
// 2. or the key already exists in the destination bucket;
// 3. or the key represents a non-bucket value;
// 4. the source and destination buckets are the same.
func (b *Bucket) MoveBucket(key []byte, dstBucket *Bucket) (err error) {
lg := b.tx.db.Logger()
if lg != discardLogger {
lg.Debugf("Moving bucket %q", key)
defer func() {
if err != nil {
lg.Errorf("Moving bucket %q failed: %v", key, err)
} else {
lg.Debugf("Moving bucket %q successfully", key)
}
}()
}
if b.tx.db == nil || dstBucket.tx.db == nil {
return errors.ErrTxClosed
} else if !b.Writable() || !dstBucket.Writable() {
return errors.ErrTxNotWritable
}
if b.tx.db.Path() != dstBucket.tx.db.Path() || b.tx != dstBucket.tx {
lg.Errorf("The source and target buckets are not in the same db file, source bucket in %s and target bucket in %s", b.tx.db.Path(), dstBucket.tx.db.Path())
return errors.ErrDifferentDB
}
newKey := cloneBytes(key)
// Move cursor to correct position.
c := b.Cursor()
k, v, flags := c.seek(newKey)
// Return an error if bucket doesn't exist or is not a bucket.
if !bytes.Equal(newKey, k) {
return errors.ErrBucketNotFound
} else if (flags & common.BucketLeafFlag) == 0 {
lg.Errorf("An incompatible key %s exists in the source bucket", newKey)
return errors.ErrIncompatibleValue
}
// Do nothing (return true directly) if the source bucket and the
// destination bucket are actually the same bucket.
if b == dstBucket || (b.RootPage() == dstBucket.RootPage() && b.RootPage() != 0) {
lg.Errorf("The source bucket (%s) and the target bucket (%s) are the same bucket", b, dstBucket)
return errors.ErrSameBuckets
}
// check whether the key already exists in the destination bucket
curDst := dstBucket.Cursor()
k, _, flags = curDst.seek(newKey)
// Return an error if there is an existing key in the destination bucket.
if bytes.Equal(newKey, k) {
if (flags & common.BucketLeafFlag) != 0 {
return errors.ErrBucketExists
}
lg.Errorf("An incompatible key %s exists in the target bucket", newKey)
return errors.ErrIncompatibleValue
}
// remove the sub-bucket from the source bucket
delete(b.buckets, string(newKey))
c.node().del(newKey)
// add te sub-bucket to the destination bucket
newValue := cloneBytes(v)
curDst.node().put(newKey, newKey, newValue, 0, common.BucketLeafFlag)
return nil
}
// Inspect returns the structure of the bucket.
func (b *Bucket) Inspect() BucketStructure {
return b.recursivelyInspect([]byte("root"))
}
func (b *Bucket) recursivelyInspect(name []byte) BucketStructure {
bs := BucketStructure{Name: string(name)}
keyN := 0
c := b.Cursor()
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
if flags&common.BucketLeafFlag != 0 {
childBucket := b.Bucket(k)
childBS := childBucket.recursivelyInspect(k)
bs.Children = append(bs.Children, childBS)
} else {
keyN++
}
}
bs.KeyN = keyN
return bs
}
// Get retrieves the value for a key in the bucket. // Get retrieves the value for a key in the bucket.
// Returns a nil value if the key does not exist or if the key is a nested bucket. // Returns a nil value if the key does not exist or if the key is a nested bucket.
// The returned value is only valid for the life of the transaction. // The returned value is only valid for the life of the transaction.
// The returned memory is owned by bbolt and must never be modified; writing to this memory might corrupt the database.
func (b *Bucket) Get(key []byte) []byte { func (b *Bucket) Get(key []byte) []byte {
k, v, flags := b.Cursor().seek(key) k, v, flags := b.Cursor().seek(key)
// Return nil if this is a bucket. // Return nil if this is a bucket.
if (flags & bucketLeafFlag) != 0 { if (flags & common.BucketLeafFlag) != 0 {
return nil return nil
} }
@ -278,17 +449,27 @@ func (b *Bucket) Get(key []byte) []byte {
// If the key exist then its previous value will be overwritten. // If the key exist then its previous value will be overwritten.
// Supplied value must remain valid for the life of the transaction. // Supplied value must remain valid for the life of the transaction.
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large. // Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
func (b *Bucket) Put(key []byte, value []byte) error { func (b *Bucket) Put(key []byte, value []byte) (err error) {
if lg := b.tx.db.Logger(); lg != discardLogger {
lg.Debugf("Putting key %q", key)
defer func() {
if err != nil {
lg.Errorf("Putting key %q failed: %v", key, err)
} else {
lg.Debugf("Putting key %q successfully", key)
}
}()
}
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} else if !b.Writable() { } else if !b.Writable() {
return ErrTxNotWritable return errors.ErrTxNotWritable
} else if len(key) == 0 { } else if len(key) == 0 {
return ErrKeyRequired return errors.ErrKeyRequired
} else if len(key) > MaxKeySize { } else if len(key) > MaxKeySize {
return ErrKeyTooLarge return errors.ErrKeyTooLarge
} else if int64(len(value)) > MaxValueSize { } else if int64(len(value)) > MaxValueSize {
return ErrValueTooLarge return errors.ErrValueTooLarge
} }
// Insert into node. // Insert into node.
@ -301,8 +482,8 @@ func (b *Bucket) Put(key []byte, value []byte) error {
k, _, flags := c.seek(newKey) k, _, flags := c.seek(newKey)
// Return an error if there is an existing key with a bucket value. // Return an error if there is an existing key with a bucket value.
if bytes.Equal(newKey, k) && (flags&bucketLeafFlag) != 0 { if bytes.Equal(newKey, k) && (flags&common.BucketLeafFlag) != 0 {
return ErrIncompatibleValue return errors.ErrIncompatibleValue
} }
// gofail: var beforeBucketPut struct{} // gofail: var beforeBucketPut struct{}
@ -315,11 +496,22 @@ func (b *Bucket) Put(key []byte, value []byte) error {
// Delete removes a key from the bucket. // Delete removes a key from the bucket.
// If the key does not exist then nothing is done and a nil error is returned. // If the key does not exist then nothing is done and a nil error is returned.
// Returns an error if the bucket was created from a read-only transaction. // Returns an error if the bucket was created from a read-only transaction.
func (b *Bucket) Delete(key []byte) error { func (b *Bucket) Delete(key []byte) (err error) {
if lg := b.tx.db.Logger(); lg != discardLogger {
lg.Debugf("Deleting key %q", key)
defer func() {
if err != nil {
lg.Errorf("Deleting key %q failed: %v", key, err)
} else {
lg.Debugf("Deleting key %q successfully", key)
}
}()
}
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} else if !b.Writable() { } else if !b.Writable() {
return ErrTxNotWritable return errors.ErrTxNotWritable
} }
// Move cursor to correct position. // Move cursor to correct position.
@ -332,8 +524,8 @@ func (b *Bucket) Delete(key []byte) error {
} }
// Return an error if there is already existing bucket value. // Return an error if there is already existing bucket value.
if (flags & bucketLeafFlag) != 0 { if (flags & common.BucketLeafFlag) != 0 {
return ErrIncompatibleValue return errors.ErrIncompatibleValue
} }
// Delete the node if we have a matching key. // Delete the node if we have a matching key.
@ -343,44 +535,46 @@ func (b *Bucket) Delete(key []byte) error {
} }
// Sequence returns the current integer for the bucket without incrementing it. // Sequence returns the current integer for the bucket without incrementing it.
func (b *Bucket) Sequence() uint64 { return b.bucket.sequence } func (b *Bucket) Sequence() uint64 {
return b.InSequence()
}
// SetSequence updates the sequence number for the bucket. // SetSequence updates the sequence number for the bucket.
func (b *Bucket) SetSequence(v uint64) error { func (b *Bucket) SetSequence(v uint64) error {
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} else if !b.Writable() { } else if !b.Writable() {
return ErrTxNotWritable return errors.ErrTxNotWritable
} }
// Materialize the root node if it hasn't been already so that the // Materialize the root node if it hasn't been already so that the
// bucket will be saved during commit. // bucket will be saved during commit.
if b.rootNode == nil { if b.rootNode == nil {
_ = b.node(b.root, nil) _ = b.node(b.RootPage(), nil)
} }
// Set the sequence. // Set the sequence.
b.bucket.sequence = v b.SetInSequence(v)
return nil return nil
} }
// NextSequence returns an autoincrementing integer for the bucket. // NextSequence returns an autoincrementing integer for the bucket.
func (b *Bucket) NextSequence() (uint64, error) { func (b *Bucket) NextSequence() (uint64, error) {
if b.tx.db == nil { if b.tx.db == nil {
return 0, ErrTxClosed return 0, errors.ErrTxClosed
} else if !b.Writable() { } else if !b.Writable() {
return 0, ErrTxNotWritable return 0, errors.ErrTxNotWritable
} }
// Materialize the root node if it hasn't been already so that the // Materialize the root node if it hasn't been already so that the
// bucket will be saved during commit. // bucket will be saved during commit.
if b.rootNode == nil { if b.rootNode == nil {
_ = b.node(b.root, nil) _ = b.node(b.RootPage(), nil)
} }
// Increment and return the sequence. // Increment and return the sequence.
b.bucket.sequence++ b.IncSequence()
return b.bucket.sequence, nil return b.Sequence(), nil
} }
// ForEach executes a function for each key/value pair in a bucket. // ForEach executes a function for each key/value pair in a bucket.
@ -390,7 +584,7 @@ func (b *Bucket) NextSequence() (uint64, error) {
// the bucket; this will result in undefined behavior. // the bucket; this will result in undefined behavior.
func (b *Bucket) ForEach(fn func(k, v []byte) error) error { func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} }
c := b.Cursor() c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() { for k, v := c.First(); k != nil; k, v = c.Next() {
@ -403,11 +597,11 @@ func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
func (b *Bucket) ForEachBucket(fn func(k []byte) error) error { func (b *Bucket) ForEachBucket(fn func(k []byte) error) error {
if b.tx.db == nil { if b.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} }
c := b.Cursor() c := b.Cursor()
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() { for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
if flags&bucketLeafFlag != 0 { if flags&common.BucketLeafFlag != 0 {
if err := fn(k); err != nil { if err := fn(k); err != nil {
return err return err
} }
@ -421,64 +615,64 @@ func (b *Bucket) Stats() BucketStats {
var s, subStats BucketStats var s, subStats BucketStats
pageSize := b.tx.db.pageSize pageSize := b.tx.db.pageSize
s.BucketN += 1 s.BucketN += 1
if b.root == 0 { if b.RootPage() == 0 {
s.InlineBucketN += 1 s.InlineBucketN += 1
} }
b.forEachPage(func(p *page, depth int, pgstack []pgid) { b.forEachPage(func(p *common.Page, depth int, pgstack []common.Pgid) {
if (p.flags & leafPageFlag) != 0 { if p.IsLeafPage() {
s.KeyN += int(p.count) s.KeyN += int(p.Count())
// used totals the used bytes for the page // used totals the used bytes for the page
used := pageHeaderSize used := common.PageHeaderSize
if p.count != 0 { if p.Count() != 0 {
// If page has any elements, add all element headers. // If page has any elements, add all element headers.
used += leafPageElementSize * uintptr(p.count-1) used += common.LeafPageElementSize * uintptr(p.Count()-1)
// Add all element key, value sizes. // Add all element key, value sizes.
// The computation takes advantage of the fact that the position // The computation takes advantage of the fact that the position
// of the last element's key/value equals to the total of the sizes // of the last element's key/value equals to the total of the sizes
// of all previous elements' keys and values. // of all previous elements' keys and values.
// It also includes the last element's header. // It also includes the last element's header.
lastElement := p.leafPageElement(p.count - 1) lastElement := p.LeafPageElement(p.Count() - 1)
used += uintptr(lastElement.pos + lastElement.ksize + lastElement.vsize) used += uintptr(lastElement.Pos() + lastElement.Ksize() + lastElement.Vsize())
} }
if b.root == 0 { if b.RootPage() == 0 {
// For inlined bucket just update the inline stats // For inlined bucket just update the inline stats
s.InlineBucketInuse += int(used) s.InlineBucketInuse += int(used)
} else { } else {
// For non-inlined bucket update all the leaf stats // For non-inlined bucket update all the leaf stats
s.LeafPageN++ s.LeafPageN++
s.LeafInuse += int(used) s.LeafInuse += int(used)
s.LeafOverflowN += int(p.overflow) s.LeafOverflowN += int(p.Overflow())
// Collect stats from sub-buckets. // Collect stats from sub-buckets.
// Do that by iterating over all element headers // Do that by iterating over all element headers
// looking for the ones with the bucketLeafFlag. // looking for the ones with the bucketLeafFlag.
for i := uint16(0); i < p.count; i++ { for i := uint16(0); i < p.Count(); i++ {
e := p.leafPageElement(i) e := p.LeafPageElement(i)
if (e.flags & bucketLeafFlag) != 0 { if (e.Flags() & common.BucketLeafFlag) != 0 {
// For any bucket element, open the element value // For any bucket element, open the element value
// and recursively call Stats on the contained bucket. // and recursively call Stats on the contained bucket.
subStats.Add(b.openBucket(e.value()).Stats()) subStats.Add(b.openBucket(e.Value()).Stats())
} }
} }
} }
} else if (p.flags & branchPageFlag) != 0 { } else if p.IsBranchPage() {
s.BranchPageN++ s.BranchPageN++
lastElement := p.branchPageElement(p.count - 1) lastElement := p.BranchPageElement(p.Count() - 1)
// used totals the used bytes for the page // used totals the used bytes for the page
// Add header and all element headers. // Add header and all element headers.
used := pageHeaderSize + (branchPageElementSize * uintptr(p.count-1)) used := common.PageHeaderSize + (common.BranchPageElementSize * uintptr(p.Count()-1))
// Add size of all keys and values. // Add size of all keys and values.
// Again, use the fact that last element's position equals to // Again, use the fact that last element's position equals to
// the total of key, value sizes of all previous elements. // the total of key, value sizes of all previous elements.
used += uintptr(lastElement.pos + lastElement.ksize) used += uintptr(lastElement.Pos() + lastElement.Ksize())
s.BranchInuse += int(used) s.BranchInuse += int(used)
s.BranchOverflowN += int(p.overflow) s.BranchOverflowN += int(p.Overflow())
} }
// Keep track of maximum page depth. // Keep track of maximum page depth.
@ -499,29 +693,29 @@ func (b *Bucket) Stats() BucketStats {
} }
// forEachPage iterates over every page in a bucket, including inline pages. // forEachPage iterates over every page in a bucket, including inline pages.
func (b *Bucket) forEachPage(fn func(*page, int, []pgid)) { func (b *Bucket) forEachPage(fn func(*common.Page, int, []common.Pgid)) {
// If we have an inline page then just use that. // If we have an inline page then just use that.
if b.page != nil { if b.page != nil {
fn(b.page, 0, []pgid{b.root}) fn(b.page, 0, []common.Pgid{b.RootPage()})
return return
} }
// Otherwise traverse the page hierarchy. // Otherwise traverse the page hierarchy.
b.tx.forEachPage(b.root, fn) b.tx.forEachPage(b.RootPage(), fn)
} }
// forEachPageNode iterates over every page (or node) in a bucket. // forEachPageNode iterates over every page (or node) in a bucket.
// This also includes inline pages. // This also includes inline pages.
func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) { func (b *Bucket) forEachPageNode(fn func(*common.Page, *node, int)) {
// If we have an inline page or root node then just use that. // If we have an inline page or root node then just use that.
if b.page != nil { if b.page != nil {
fn(b.page, nil, 0) fn(b.page, nil, 0)
return return
} }
b._forEachPageNode(b.root, 0, fn) b._forEachPageNode(b.RootPage(), 0, fn)
} }
func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, int)) { func (b *Bucket) _forEachPageNode(pgId common.Pgid, depth int, fn func(*common.Page, *node, int)) {
var p, n = b.pageNode(pgId) var p, n = b.pageNode(pgId)
// Execute function. // Execute function.
@ -529,16 +723,16 @@ func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, in
// Recursively loop over children. // Recursively loop over children.
if p != nil { if p != nil {
if (p.flags & branchPageFlag) != 0 { if p.IsBranchPage() {
for i := 0; i < int(p.count); i++ { for i := 0; i < int(p.Count()); i++ {
elem := p.branchPageElement(uint16(i)) elem := p.BranchPageElement(uint16(i))
b._forEachPageNode(elem.pgid, depth+1, fn) b._forEachPageNode(elem.Pgid(), depth+1, fn)
} }
} }
} else { } else {
if !n.isLeaf { if !n.isLeaf {
for _, inode := range n.inodes { for _, inode := range n.inodes {
b._forEachPageNode(inode.pgid, depth+1, fn) b._forEachPageNode(inode.Pgid(), depth+1, fn)
} }
} }
} }
@ -561,9 +755,9 @@ func (b *Bucket) spill() error {
} }
// Update the child bucket header in this bucket. // Update the child bucket header in this bucket.
value = make([]byte, unsafe.Sizeof(bucket{})) value = make([]byte, unsafe.Sizeof(common.InBucket{}))
var bucket = (*bucket)(unsafe.Pointer(&value[0])) var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
*bucket = *child.bucket *bucket = *child.InBucket
} }
// Skip writing the bucket if there are no materialized nodes. // Skip writing the bucket if there are no materialized nodes.
@ -577,10 +771,10 @@ func (b *Bucket) spill() error {
if !bytes.Equal([]byte(name), k) { if !bytes.Equal([]byte(name), k) {
panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k)) panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k))
} }
if flags&bucketLeafFlag == 0 { if flags&common.BucketLeafFlag == 0 {
panic(fmt.Sprintf("unexpected bucket header flag: %x", flags)) panic(fmt.Sprintf("unexpected bucket header flag: %x", flags))
} }
c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag) c.node().put([]byte(name), []byte(name), value, 0, common.BucketLeafFlag)
} }
// Ignore if there's not a materialized root node. // Ignore if there's not a materialized root node.
@ -595,16 +789,16 @@ func (b *Bucket) spill() error {
b.rootNode = b.rootNode.root() b.rootNode = b.rootNode.root()
// Update the root node for this bucket. // Update the root node for this bucket.
if b.rootNode.pgid >= b.tx.meta.pgid { if b.rootNode.pgid >= b.tx.meta.Pgid() {
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid)) panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.Pgid()))
} }
b.root = b.rootNode.pgid b.SetRootPage(b.rootNode.pgid)
return nil return nil
} }
// inlineable returns true if a bucket is small enough to be written inline // inlineable returns true if a bucket is small enough to be written inline
// and if it contains no subbuckets. Otherwise returns false. // and if it contains no subbuckets. Otherwise, returns false.
func (b *Bucket) inlineable() bool { func (b *Bucket) inlineable() bool {
var n = b.rootNode var n = b.rootNode
@ -615,11 +809,11 @@ func (b *Bucket) inlineable() bool {
// Bucket is not inlineable if it contains subbuckets or if it goes beyond // Bucket is not inlineable if it contains subbuckets or if it goes beyond
// our threshold for inline bucket size. // our threshold for inline bucket size.
var size = pageHeaderSize var size = common.PageHeaderSize
for _, inode := range n.inodes { for _, inode := range n.inodes {
size += leafPageElementSize + uintptr(len(inode.key)) + uintptr(len(inode.value)) size += common.LeafPageElementSize + uintptr(len(inode.Key())) + uintptr(len(inode.Value()))
if inode.flags&bucketLeafFlag != 0 { if inode.Flags()&common.BucketLeafFlag != 0 {
return false return false
} else if size > b.maxInlineBucketSize() { } else if size > b.maxInlineBucketSize() {
return false return false
@ -638,14 +832,14 @@ func (b *Bucket) maxInlineBucketSize() uintptr {
func (b *Bucket) write() []byte { func (b *Bucket) write() []byte {
// Allocate the appropriate size. // Allocate the appropriate size.
var n = b.rootNode var n = b.rootNode
var value = make([]byte, bucketHeaderSize+n.size()) var value = make([]byte, common.BucketHeaderSize+n.size())
// Write a bucket header. // Write a bucket header.
var bucket = (*bucket)(unsafe.Pointer(&value[0])) var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
*bucket = *b.bucket *bucket = *b.InBucket
// Convert byte slice to a fake page and write the root node. // Convert byte slice to a fake page and write the root node.
var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) var p = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
n.write(p) n.write(p)
return value return value
@ -662,8 +856,8 @@ func (b *Bucket) rebalance() {
} }
// node creates a node from a page and associates it with a given parent. // node creates a node from a page and associates it with a given parent.
func (b *Bucket) node(pgId pgid, parent *node) *node { func (b *Bucket) node(pgId common.Pgid, parent *node) *node {
_assert(b.nodes != nil, "nodes map expected") common.Assert(b.nodes != nil, "nodes map expected")
// Retrieve node if it's already been created. // Retrieve node if it's already been created.
if n := b.nodes[pgId]; n != nil { if n := b.nodes[pgId]; n != nil {
@ -682,6 +876,12 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
var p = b.page var p = b.page
if p == nil { if p == nil {
p = b.tx.page(pgId) p = b.tx.page(pgId)
} else {
// if p isn't nil, then it's an inline bucket.
// The pgId must be 0 in this case.
common.Verify(func() {
common.Assert(pgId == 0, "The page ID (%d) isn't 0 for an inline bucket", pgId)
})
} }
// Read the page into the node and cache it. // Read the page into the node and cache it.
@ -696,19 +896,19 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
// free recursively frees all pages in the bucket. // free recursively frees all pages in the bucket.
func (b *Bucket) free() { func (b *Bucket) free() {
if b.root == 0 { if b.RootPage() == 0 {
return return
} }
var tx = b.tx var tx = b.tx
b.forEachPageNode(func(p *page, n *node, _ int) { b.forEachPageNode(func(p *common.Page, n *node, _ int) {
if p != nil { if p != nil {
tx.db.freelist.free(tx.meta.txid, p) tx.db.freelist.Free(tx.meta.Txid(), p)
} else { } else {
n.free() n.free()
} }
}) })
b.root = 0 b.SetRootPage(0)
} }
// dereference removes all references to the old mmap. // dereference removes all references to the old mmap.
@ -723,11 +923,11 @@ func (b *Bucket) dereference() {
} }
// pageNode returns the in-memory node, if it exists. // pageNode returns the in-memory node, if it exists.
// Otherwise returns the underlying page. // Otherwise, returns the underlying page.
func (b *Bucket) pageNode(id pgid) (*page, *node) { func (b *Bucket) pageNode(id common.Pgid) (*common.Page, *node) {
// Inline buckets have a fake page embedded in their value so treat them // Inline buckets have a fake page embedded in their value so treat them
// differently. We'll return the rootNode (if available) or the fake page. // differently. We'll return the rootNode (if available) or the fake page.
if b.root == 0 { if b.RootPage() == 0 {
if id != 0 { if id != 0 {
panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id)) panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id))
} }
@ -797,3 +997,9 @@ func cloneBytes(v []byte) []byte {
copy(clone, v) copy(clone, v)
return clone return clone
} }
type BucketStructure struct {
Name string `json:"name"` // name of the bucket
KeyN int `json:"keyN"` // number of key/value pairs
Children []BucketStructure `json:"buckets,omitempty"` // child buckets
}

99
vendor/go.etcd.io/bbolt/cursor.go generated vendored
View file

@ -4,6 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"sort" "sort"
"go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
) )
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket // Cursor represents an iterator that can traverse over all key/value pairs in a bucket
@ -30,9 +33,9 @@ func (c *Cursor) Bucket() *Bucket {
// If the bucket is empty then a nil key and value are returned. // If the bucket is empty then a nil key and value are returned.
// The returned key and value are only valid for the life of the transaction. // The returned key and value are only valid for the life of the transaction.
func (c *Cursor) First() (key []byte, value []byte) { func (c *Cursor) First() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed") common.Assert(c.bucket.tx.db != nil, "tx closed")
k, v, flags := c.first() k, v, flags := c.first()
if (flags & uint32(bucketLeafFlag)) != 0 { if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil return k, nil
} }
return k, v return k, v
@ -40,7 +43,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
func (c *Cursor) first() (key []byte, value []byte, flags uint32) { func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
c.stack = c.stack[:0] c.stack = c.stack[:0]
p, n := c.bucket.pageNode(c.bucket.root) p, n := c.bucket.pageNode(c.bucket.RootPage())
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
c.goToFirstElementOnTheStack() c.goToFirstElementOnTheStack()
@ -51,7 +54,7 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
} }
k, v, flags := c.keyValue() k, v, flags := c.keyValue()
if (flags & uint32(bucketLeafFlag)) != 0 { if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil, flags return k, nil, flags
} }
return k, v, flags return k, v, flags
@ -61,9 +64,9 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
// If the bucket is empty then a nil key and value are returned. // If the bucket is empty then a nil key and value are returned.
// The returned key and value are only valid for the life of the transaction. // The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Last() (key []byte, value []byte) { func (c *Cursor) Last() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed") common.Assert(c.bucket.tx.db != nil, "tx closed")
c.stack = c.stack[:0] c.stack = c.stack[:0]
p, n := c.bucket.pageNode(c.bucket.root) p, n := c.bucket.pageNode(c.bucket.RootPage())
ref := elemRef{page: p, node: n} ref := elemRef{page: p, node: n}
ref.index = ref.count() - 1 ref.index = ref.count() - 1
c.stack = append(c.stack, ref) c.stack = append(c.stack, ref)
@ -80,7 +83,7 @@ func (c *Cursor) Last() (key []byte, value []byte) {
} }
k, v, flags := c.keyValue() k, v, flags := c.keyValue()
if (flags & uint32(bucketLeafFlag)) != 0 { if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil return k, nil
} }
return k, v return k, v
@ -90,9 +93,9 @@ func (c *Cursor) Last() (key []byte, value []byte) {
// If the cursor is at the end of the bucket then a nil key and value are returned. // If the cursor is at the end of the bucket then a nil key and value are returned.
// The returned key and value are only valid for the life of the transaction. // The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Next() (key []byte, value []byte) { func (c *Cursor) Next() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed") common.Assert(c.bucket.tx.db != nil, "tx closed")
k, v, flags := c.next() k, v, flags := c.next()
if (flags & uint32(bucketLeafFlag)) != 0 { if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil return k, nil
} }
return k, v return k, v
@ -102,9 +105,9 @@ func (c *Cursor) Next() (key []byte, value []byte) {
// If the cursor is at the beginning of the bucket then a nil key and value are returned. // If the cursor is at the beginning of the bucket then a nil key and value are returned.
// The returned key and value are only valid for the life of the transaction. // The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Prev() (key []byte, value []byte) { func (c *Cursor) Prev() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed") common.Assert(c.bucket.tx.db != nil, "tx closed")
k, v, flags := c.prev() k, v, flags := c.prev()
if (flags & uint32(bucketLeafFlag)) != 0 { if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil return k, nil
} }
return k, v return k, v
@ -115,7 +118,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) {
// follow, a nil key is returned. // follow, a nil key is returned.
// The returned key and value are only valid for the life of the transaction. // The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) { func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed") common.Assert(c.bucket.tx.db != nil, "tx closed")
k, v, flags := c.seek(seek) k, v, flags := c.seek(seek)
@ -126,7 +129,7 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
if k == nil { if k == nil {
return nil, nil return nil, nil
} else if (flags & uint32(bucketLeafFlag)) != 0 { } else if (flags & uint32(common.BucketLeafFlag)) != 0 {
return k, nil return k, nil
} }
return k, v return k, v
@ -136,15 +139,15 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
// Delete fails if current key/value is a bucket or if the transaction is not writable. // Delete fails if current key/value is a bucket or if the transaction is not writable.
func (c *Cursor) Delete() error { func (c *Cursor) Delete() error {
if c.bucket.tx.db == nil { if c.bucket.tx.db == nil {
return ErrTxClosed return errors.ErrTxClosed
} else if !c.bucket.Writable() { } else if !c.bucket.Writable() {
return ErrTxNotWritable return errors.ErrTxNotWritable
} }
key, _, flags := c.keyValue() key, _, flags := c.keyValue()
// Return an error if current value is a bucket. // Return an error if current value is a bucket.
if (flags & bucketLeafFlag) != 0 { if (flags & common.BucketLeafFlag) != 0 {
return ErrIncompatibleValue return errors.ErrIncompatibleValue
} }
c.node().del(key) c.node().del(key)
@ -156,7 +159,7 @@ func (c *Cursor) Delete() error {
func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) { func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
// Start from root page/node and traverse to correct page. // Start from root page/node and traverse to correct page.
c.stack = c.stack[:0] c.stack = c.stack[:0]
c.search(seek, c.bucket.root) c.search(seek, c.bucket.RootPage())
// If this is a bucket then return a nil value. // If this is a bucket then return a nil value.
return c.keyValue() return c.keyValue()
@ -172,11 +175,11 @@ func (c *Cursor) goToFirstElementOnTheStack() {
} }
// Keep adding pages pointing to the first element to the stack. // Keep adding pages pointing to the first element to the stack.
var pgId pgid var pgId common.Pgid
if ref.node != nil { if ref.node != nil {
pgId = ref.node.inodes[ref.index].pgid pgId = ref.node.inodes[ref.index].Pgid()
} else { } else {
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
} }
p, n := c.bucket.pageNode(pgId) p, n := c.bucket.pageNode(pgId)
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
@ -193,11 +196,11 @@ func (c *Cursor) last() {
} }
// Keep adding pages pointing to the last element in the stack. // Keep adding pages pointing to the last element in the stack.
var pgId pgid var pgId common.Pgid
if ref.node != nil { if ref.node != nil {
pgId = ref.node.inodes[ref.index].pgid pgId = ref.node.inodes[ref.index].Pgid()
} else { } else {
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
} }
p, n := c.bucket.pageNode(pgId) p, n := c.bucket.pageNode(pgId)
@ -277,10 +280,10 @@ func (c *Cursor) prev() (key []byte, value []byte, flags uint32) {
} }
// search recursively performs a binary search against a given page/node until it finds a given key. // search recursively performs a binary search against a given page/node until it finds a given key.
func (c *Cursor) search(key []byte, pgId pgid) { func (c *Cursor) search(key []byte, pgId common.Pgid) {
p, n := c.bucket.pageNode(pgId) p, n := c.bucket.pageNode(pgId)
if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 { if p != nil && !p.IsBranchPage() && !p.IsLeafPage() {
panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags)) panic(fmt.Sprintf("invalid page type: %d: %x", p.Id(), p.Flags()))
} }
e := elemRef{page: p, node: n} e := elemRef{page: p, node: n}
c.stack = append(c.stack, e) c.stack = append(c.stack, e)
@ -303,7 +306,7 @@ func (c *Cursor) searchNode(key []byte, n *node) {
index := sort.Search(len(n.inodes), func(i int) bool { index := sort.Search(len(n.inodes), func(i int) bool {
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
// sort.Search() finds the lowest index where f() != -1 but we need the highest index. // sort.Search() finds the lowest index where f() != -1 but we need the highest index.
ret := bytes.Compare(n.inodes[i].key, key) ret := bytes.Compare(n.inodes[i].Key(), key)
if ret == 0 { if ret == 0 {
exact = true exact = true
} }
@ -315,18 +318,18 @@ func (c *Cursor) searchNode(key []byte, n *node) {
c.stack[len(c.stack)-1].index = index c.stack[len(c.stack)-1].index = index
// Recursively search to the next page. // Recursively search to the next page.
c.search(key, n.inodes[index].pgid) c.search(key, n.inodes[index].Pgid())
} }
func (c *Cursor) searchPage(key []byte, p *page) { func (c *Cursor) searchPage(key []byte, p *common.Page) {
// Binary search for the correct range. // Binary search for the correct range.
inodes := p.branchPageElements() inodes := p.BranchPageElements()
var exact bool var exact bool
index := sort.Search(int(p.count), func(i int) bool { index := sort.Search(int(p.Count()), func(i int) bool {
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
// sort.Search() finds the lowest index where f() != -1 but we need the highest index. // sort.Search() finds the lowest index where f() != -1 but we need the highest index.
ret := bytes.Compare(inodes[i].key(), key) ret := bytes.Compare(inodes[i].Key(), key)
if ret == 0 { if ret == 0 {
exact = true exact = true
} }
@ -338,7 +341,7 @@ func (c *Cursor) searchPage(key []byte, p *page) {
c.stack[len(c.stack)-1].index = index c.stack[len(c.stack)-1].index = index
// Recursively search to the next page. // Recursively search to the next page.
c.search(key, inodes[index].pgid) c.search(key, inodes[index].Pgid())
} }
// nsearch searches the leaf node on the top of the stack for a key. // nsearch searches the leaf node on the top of the stack for a key.
@ -349,16 +352,16 @@ func (c *Cursor) nsearch(key []byte) {
// If we have a node then search its inodes. // If we have a node then search its inodes.
if n != nil { if n != nil {
index := sort.Search(len(n.inodes), func(i int) bool { index := sort.Search(len(n.inodes), func(i int) bool {
return bytes.Compare(n.inodes[i].key, key) != -1 return bytes.Compare(n.inodes[i].Key(), key) != -1
}) })
e.index = index e.index = index
return return
} }
// If we have a page then search its leaf elements. // If we have a page then search its leaf elements.
inodes := p.leafPageElements() inodes := p.LeafPageElements()
index := sort.Search(int(p.count), func(i int) bool { index := sort.Search(int(p.Count()), func(i int) bool {
return bytes.Compare(inodes[i].key(), key) != -1 return bytes.Compare(inodes[i].Key(), key) != -1
}) })
e.index = index e.index = index
} }
@ -375,17 +378,17 @@ func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
// Retrieve value from node. // Retrieve value from node.
if ref.node != nil { if ref.node != nil {
inode := &ref.node.inodes[ref.index] inode := &ref.node.inodes[ref.index]
return inode.key, inode.value, inode.flags return inode.Key(), inode.Value(), inode.Flags()
} }
// Or retrieve value from page. // Or retrieve value from page.
elem := ref.page.leafPageElement(uint16(ref.index)) elem := ref.page.LeafPageElement(uint16(ref.index))
return elem.key(), elem.value(), elem.flags return elem.Key(), elem.Value(), elem.Flags()
} }
// node returns the node that the cursor is currently positioned on. // node returns the node that the cursor is currently positioned on.
func (c *Cursor) node() *node { func (c *Cursor) node() *node {
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") common.Assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
// If the top of the stack is a leaf node then just return it. // If the top of the stack is a leaf node then just return it.
if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() { if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
@ -395,19 +398,19 @@ func (c *Cursor) node() *node {
// Start from root and traverse down the hierarchy. // Start from root and traverse down the hierarchy.
var n = c.stack[0].node var n = c.stack[0].node
if n == nil { if n == nil {
n = c.bucket.node(c.stack[0].page.id, nil) n = c.bucket.node(c.stack[0].page.Id(), nil)
} }
for _, ref := range c.stack[:len(c.stack)-1] { for _, ref := range c.stack[:len(c.stack)-1] {
_assert(!n.isLeaf, "expected branch node") common.Assert(!n.isLeaf, "expected branch node")
n = n.childAt(ref.index) n = n.childAt(ref.index)
} }
_assert(n.isLeaf, "expected leaf node") common.Assert(n.isLeaf, "expected leaf node")
return n return n
} }
// elemRef represents a reference to an element on a given page/node. // elemRef represents a reference to an element on a given page/node.
type elemRef struct { type elemRef struct {
page *page page *common.Page
node *node node *node
index int index int
} }
@ -417,7 +420,7 @@ func (r *elemRef) isLeaf() bool {
if r.node != nil { if r.node != nil {
return r.node.isLeaf return r.node.isLeaf
} }
return (r.page.flags & leafPageFlag) != 0 return r.page.IsLeafPage()
} }
// count returns the number of inodes or page elements. // count returns the number of inodes or page elements.
@ -425,5 +428,5 @@ func (r *elemRef) count() int {
if r.node != nil { if r.node != nil {
return len(r.node.inodes) return len(r.node.inodes)
} }
return int(r.page.count) return int(r.page.Count())
} }

467
vendor/go.etcd.io/bbolt/db.go generated vendored
View file

@ -3,49 +3,28 @@ package bbolt
import ( import (
"errors" "errors"
"fmt" "fmt"
"hash/fnv"
"io" "io"
"os" "os"
"runtime" "runtime"
"sort"
"sync" "sync"
"time" "time"
"unsafe" "unsafe"
berrors "go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/common"
fl "go.etcd.io/bbolt/internal/freelist"
) )
// The largest step that can be taken when remapping the mmap.
const maxMmapStep = 1 << 30 // 1GB
// The data file format version.
const version = 2
// Represents a marker value to indicate that a file is a Bolt DB.
const magic uint32 = 0xED0CDAED
const pgidNoFreelist pgid = 0xffffffffffffffff
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
// syncing changes to a file. This is required as some operating systems,
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
// must be synchronized using the msync(2) syscall.
const IgnoreNoSync = runtime.GOOS == "openbsd"
// Default values if not set in a DB instance.
const (
DefaultMaxBatchSize int = 1000
DefaultMaxBatchDelay = 10 * time.Millisecond
DefaultAllocSize = 16 * 1024 * 1024
)
// default page size for db is set to the OS page size.
var defaultPageSize = os.Getpagesize()
// The time elapsed between consecutive file locking attempts. // The time elapsed between consecutive file locking attempts.
const flockRetryTimeout = 50 * time.Millisecond const flockRetryTimeout = 50 * time.Millisecond
// FreelistType is the type of the freelist backend // FreelistType is the type of the freelist backend
type FreelistType string type FreelistType string
// TODO(ahrtr): eventually we should (step by step)
// 1. default to `FreelistMapType`;
// 2. remove the `FreelistArrayType`, do not export `FreelistMapType`
// and remove field `FreelistType' from both `DB` and `Options`;
const ( const (
// FreelistArrayType indicates backend freelist type is array // FreelistArrayType indicates backend freelist type is array
FreelistArrayType = FreelistType("array") FreelistArrayType = FreelistType("array")
@ -137,6 +116,8 @@ type DB struct {
// Supported only on Unix via mlock/munlock syscalls. // Supported only on Unix via mlock/munlock syscalls.
Mlock bool Mlock bool
logger Logger
path string path string
openFile func(string, int, os.FileMode) (*os.File, error) openFile func(string, int, os.FileMode) (*os.File, error)
file *os.File file *os.File
@ -144,17 +125,16 @@ type DB struct {
// always fails on Windows platform. // always fails on Windows platform.
//nolint //nolint
dataref []byte // mmap'ed readonly, write throws SEGV dataref []byte // mmap'ed readonly, write throws SEGV
data *[maxMapSize]byte data *[common.MaxMapSize]byte
datasz int datasz int
filesz int // current on disk file size meta0 *common.Meta
meta0 *meta meta1 *common.Meta
meta1 *meta
pageSize int pageSize int
opened bool opened bool
rwtx *Tx rwtx *Tx
txs []*Tx txs []*Tx
freelist *freelist freelist fl.Interface
freelistLoad sync.Once freelistLoad sync.Once
pagePool sync.Pool pagePool sync.Pool
@ -191,13 +171,15 @@ func (db *DB) String() string {
return fmt.Sprintf("DB<%q>", db.path) return fmt.Sprintf("DB<%q>", db.path)
} }
// Open creates and opens a database at the given path. // Open creates and opens a database at the given path with a given file mode.
// If the file does not exist then it will be created automatically. // If the file does not exist then it will be created automatically with a given file mode.
// Passing in nil options will cause Bolt to open the database with the default options. // Passing in nil options will cause Bolt to open the database with the default options.
func Open(path string, mode os.FileMode, options *Options) (*DB, error) { // Note: For read/write transactions, ensure the owner has write permission on the created/opened database file, e.g. 0600
db := &DB{ func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
db = &DB{
opened: true, opened: true,
} }
// Set default options if no options are provided. // Set default options if no options are provided.
if options == nil { if options == nil {
options = DefaultOptions options = DefaultOptions
@ -211,9 +193,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
db.Mlock = options.Mlock db.Mlock = options.Mlock
// Set default values for later DB operations. // Set default values for later DB operations.
db.MaxBatchSize = DefaultMaxBatchSize db.MaxBatchSize = common.DefaultMaxBatchSize
db.MaxBatchDelay = DefaultMaxBatchDelay db.MaxBatchDelay = common.DefaultMaxBatchDelay
db.AllocSize = DefaultAllocSize db.AllocSize = common.DefaultAllocSize
if options.Logger == nil {
db.logger = getDiscardLogger()
} else {
db.logger = options.Logger
}
lg := db.Logger()
if lg != discardLogger {
lg.Infof("Opening db file (%s) with mode %s and with options: %s", path, mode, options)
defer func() {
if err != nil {
lg.Errorf("Opening bbolt db (%s) failed: %v", path, err)
} else {
lg.Infof("Opening bbolt db (%s) successfully", path)
}
}()
}
flag := os.O_RDWR flag := os.O_RDWR
if options.ReadOnly { if options.ReadOnly {
@ -222,6 +222,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
} else { } else {
// always load free pages in write mode // always load free pages in write mode
db.PreLoadFreelist = true db.PreLoadFreelist = true
flag |= os.O_CREATE
} }
db.openFile = options.OpenFile db.openFile = options.OpenFile
@ -230,9 +231,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
} }
// Open data file and separate sync handler for metadata writes. // Open data file and separate sync handler for metadata writes.
var err error if db.file, err = db.openFile(path, flag, mode); err != nil {
if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil {
_ = db.close() _ = db.close()
lg.Errorf("failed to open db file (%s): %v", path, err)
return nil, err return nil, err
} }
db.path = db.file.Name() db.path = db.file.Name()
@ -244,8 +245,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// if !options.ReadOnly. // if !options.ReadOnly.
// The database file is locked using the shared lock (more than one process may // The database file is locked using the shared lock (more than one process may
// hold a lock at the same time) otherwise (options.ReadOnly is set). // hold a lock at the same time) otherwise (options.ReadOnly is set).
if err := flock(db, !db.readOnly, options.Timeout); err != nil { if err = flock(db, !db.readOnly, options.Timeout); err != nil {
_ = db.close() _ = db.close()
lg.Errorf("failed to lock db file (%s), readonly: %t, error: %v", path, db.readOnly, err)
return nil, err return nil, err
} }
@ -254,27 +256,28 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
if db.pageSize = options.PageSize; db.pageSize == 0 { if db.pageSize = options.PageSize; db.pageSize == 0 {
// Set the default page size to the OS page size. // Set the default page size to the OS page size.
db.pageSize = defaultPageSize db.pageSize = common.DefaultPageSize
} }
// Initialize the database if it doesn't exist. // Initialize the database if it doesn't exist.
if info, err := db.file.Stat(); err != nil { if info, statErr := db.file.Stat(); statErr != nil {
_ = db.close() _ = db.close()
return nil, err lg.Errorf("failed to get db file's stats (%s): %v", path, err)
return nil, statErr
} else if info.Size() == 0 { } else if info.Size() == 0 {
// Initialize new files with meta pages. // Initialize new files with meta pages.
if err := db.init(); err != nil { if err = db.init(); err != nil {
// clean up file descriptor on initialization fail // clean up file descriptor on initialization fail
_ = db.close() _ = db.close()
lg.Errorf("failed to initialize db file (%s): %v", path, err)
return nil, err return nil, err
} }
} else { } else {
// try to get the page size from the metadata pages // try to get the page size from the metadata pages
if pgSize, err := db.getPageSize(); err == nil { if db.pageSize, err = db.getPageSize(); err != nil {
db.pageSize = pgSize
} else {
_ = db.close() _ = db.close()
return nil, ErrInvalid lg.Errorf("failed to get page size from db file (%s): %v", path, err)
return nil, err
} }
} }
@ -286,8 +289,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
} }
// Memory map the data file. // Memory map the data file.
if err := db.mmap(options.InitialMmapSize); err != nil { if err = db.mmap(options.InitialMmapSize); err != nil {
_ = db.close() _ = db.close()
lg.Errorf("failed to map db file (%s): %v", path, err)
return nil, err return nil, err
} }
@ -302,13 +306,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// Flush freelist when transitioning from no sync to sync so // Flush freelist when transitioning from no sync to sync so
// NoFreelistSync unaware boltdb can open the db later. // NoFreelistSync unaware boltdb can open the db later.
if !db.NoFreelistSync && !db.hasSyncedFreelist() { if !db.NoFreelistSync && !db.hasSyncedFreelist() {
tx, err := db.Begin(true) tx, txErr := db.Begin(true)
if tx != nil { if tx != nil {
err = tx.Commit() txErr = tx.Commit()
} }
if err != nil { if txErr != nil {
lg.Errorf("starting readwrite transaction failed: %v", txErr)
_ = db.close() _ = db.close()
return nil, err return nil, txErr
} }
} }
@ -352,7 +357,7 @@ func (db *DB) getPageSize() (int, error) {
return db.pageSize, nil return db.pageSize, nil
} }
return 0, ErrInvalid return 0, berrors.ErrInvalid
} }
// getPageSizeFromFirstMeta reads the pageSize from the first meta page // getPageSizeFromFirstMeta reads the pageSize from the first meta page
@ -361,11 +366,11 @@ func (db *DB) getPageSizeFromFirstMeta() (int, bool, error) {
var metaCanRead bool var metaCanRead bool
if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) { if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) {
metaCanRead = true metaCanRead = true
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil { if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
return int(m.pageSize), metaCanRead, nil return int(m.PageSize()), metaCanRead, nil
} }
} }
return 0, metaCanRead, ErrInvalid return 0, metaCanRead, berrors.ErrInvalid
} }
// getPageSizeFromSecondMeta reads the pageSize from the second meta page // getPageSizeFromSecondMeta reads the pageSize from the second meta page
@ -397,13 +402,13 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
bw, err := db.file.ReadAt(buf[:], pos) bw, err := db.file.ReadAt(buf[:], pos)
if (err == nil && bw == len(buf)) || (err == io.EOF && int64(bw) == (fileSize-pos)) { if (err == nil && bw == len(buf)) || (err == io.EOF && int64(bw) == (fileSize-pos)) {
metaCanRead = true metaCanRead = true
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil { if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
return int(m.pageSize), metaCanRead, nil return int(m.PageSize()), metaCanRead, nil
} }
} }
} }
return 0, metaCanRead, ErrInvalid return 0, metaCanRead, berrors.ErrInvalid
} }
// loadFreelist reads the freelist if it is synced, or reconstructs it // loadFreelist reads the freelist if it is synced, or reconstructs it
@ -414,17 +419,29 @@ func (db *DB) loadFreelist() {
db.freelist = newFreelist(db.FreelistType) db.freelist = newFreelist(db.FreelistType)
if !db.hasSyncedFreelist() { if !db.hasSyncedFreelist() {
// Reconstruct free list by scanning the DB. // Reconstruct free list by scanning the DB.
db.freelist.readIDs(db.freepages()) db.freelist.Init(db.freepages())
} else { } else {
// Read free list from freelist page. // Read free list from freelist page.
db.freelist.read(db.page(db.meta().freelist)) db.freelist.Read(db.page(db.meta().Freelist()))
} }
db.stats.FreePageN = db.freelist.free_count() db.stats.FreePageN = db.freelist.FreeCount()
}) })
} }
func (db *DB) hasSyncedFreelist() bool { func (db *DB) hasSyncedFreelist() bool {
return db.meta().freelist != pgidNoFreelist return db.meta().Freelist() != common.PgidNoFreelist
}
func (db *DB) fileSize() (int, error) {
info, err := db.file.Stat()
if err != nil {
return 0, fmt.Errorf("file stat error: %w", err)
}
sz := int(info.Size())
if sz < db.pageSize*2 {
return 0, fmt.Errorf("file size too small %d", sz)
}
return sz, nil
} }
// mmap opens the underlying memory-mapped file and initializes the meta references. // mmap opens the underlying memory-mapped file and initializes the meta references.
@ -433,21 +450,22 @@ func (db *DB) mmap(minsz int) (err error) {
db.mmaplock.Lock() db.mmaplock.Lock()
defer db.mmaplock.Unlock() defer db.mmaplock.Unlock()
info, err := db.file.Stat() lg := db.Logger()
if err != nil {
return fmt.Errorf("mmap stat error: %s", err)
} else if int(info.Size()) < db.pageSize*2 {
return fmt.Errorf("file size too small")
}
// Ensure the size is at least the minimum size. // Ensure the size is at least the minimum size.
fileSize := int(info.Size()) var fileSize int
fileSize, err = db.fileSize()
if err != nil {
lg.Errorf("getting file size failed: %w", err)
return err
}
var size = fileSize var size = fileSize
if size < minsz { if size < minsz {
size = minsz size = minsz
} }
size, err = db.mmapSize(size) size, err = db.mmapSize(size)
if err != nil { if err != nil {
lg.Errorf("getting map size failed: %w", err)
return err return err
} }
@ -472,6 +490,7 @@ func (db *DB) mmap(minsz int) (err error) {
// gofail: var mapError string // gofail: var mapError string
// return errors.New(mapError) // return errors.New(mapError)
if err = mmap(db, size); err != nil { if err = mmap(db, size); err != nil {
lg.Errorf("[GOOS: %s, GOARCH: %s] mmap failed, size: %d, error: %v", runtime.GOOS, runtime.GOARCH, size, err)
return err return err
} }
@ -493,15 +512,16 @@ func (db *DB) mmap(minsz int) (err error) {
} }
// Save references to the meta pages. // Save references to the meta pages.
db.meta0 = db.page(0).meta() db.meta0 = db.page(0).Meta()
db.meta1 = db.page(1).meta() db.meta1 = db.page(1).Meta()
// Validate the meta pages. We only return an error if both meta pages fail // Validate the meta pages. We only return an error if both meta pages fail
// validation, since meta0 failing validation means that it wasn't saved // validation, since meta0 failing validation means that it wasn't saved
// properly -- but we can recover using meta1. And vice-versa. // properly -- but we can recover using meta1. And vice-versa.
err0 := db.meta0.validate() err0 := db.meta0.Validate()
err1 := db.meta1.validate() err1 := db.meta1.Validate()
if err0 != nil && err1 != nil { if err0 != nil && err1 != nil {
lg.Errorf("both meta pages are invalid, meta0: %v, meta1: %v", err0, err1)
return err0 return err0
} }
@ -524,6 +544,7 @@ func (db *DB) munmap() error {
// gofail: var unmapError string // gofail: var unmapError string
// return errors.New(unmapError) // return errors.New(unmapError)
if err := munmap(db); err != nil { if err := munmap(db); err != nil {
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munmap failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
return fmt.Errorf("unmap error: %v", err.Error()) return fmt.Errorf("unmap error: %v", err.Error())
} }
@ -542,14 +563,14 @@ func (db *DB) mmapSize(size int) (int, error) {
} }
// Verify the requested size is not above the maximum allowed. // Verify the requested size is not above the maximum allowed.
if size > maxMapSize { if size > common.MaxMapSize {
return 0, fmt.Errorf("mmap too large") return 0, errors.New("mmap too large")
} }
// If larger than 1GB then grow by 1GB at a time. // If larger than 1GB then grow by 1GB at a time.
sz := int64(size) sz := int64(size)
if remainder := sz % int64(maxMmapStep); remainder > 0 { if remainder := sz % int64(common.MaxMmapStep); remainder > 0 {
sz += int64(maxMmapStep) - remainder sz += int64(common.MaxMmapStep) - remainder
} }
// Ensure that the mmap size is a multiple of the page size. // Ensure that the mmap size is a multiple of the page size.
@ -560,8 +581,8 @@ func (db *DB) mmapSize(size int) (int, error) {
} }
// If we've exceeded the max size then only grow up to the max size. // If we've exceeded the max size then only grow up to the max size.
if sz > maxMapSize { if sz > common.MaxMapSize {
sz = maxMapSize sz = common.MaxMapSize
} }
return int(sz), nil return int(sz), nil
@ -571,6 +592,7 @@ func (db *DB) munlock(fileSize int) error {
// gofail: var munlockError string // gofail: var munlockError string
// return errors.New(munlockError) // return errors.New(munlockError)
if err := munlock(db, fileSize); err != nil { if err := munlock(db, fileSize); err != nil {
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
return fmt.Errorf("munlock error: %v", err.Error()) return fmt.Errorf("munlock error: %v", err.Error())
} }
return nil return nil
@ -580,6 +602,7 @@ func (db *DB) mlock(fileSize int) error {
// gofail: var mlockError string // gofail: var mlockError string
// return errors.New(mlockError) // return errors.New(mlockError)
if err := mlock(db, fileSize); err != nil { if err := mlock(db, fileSize); err != nil {
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] mlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
return fmt.Errorf("mlock error: %v", err.Error()) return fmt.Errorf("mlock error: %v", err.Error())
} }
return nil return nil
@ -600,42 +623,43 @@ func (db *DB) init() error {
// Create two meta pages on a buffer. // Create two meta pages on a buffer.
buf := make([]byte, db.pageSize*4) buf := make([]byte, db.pageSize*4)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
p := db.pageInBuffer(buf, pgid(i)) p := db.pageInBuffer(buf, common.Pgid(i))
p.id = pgid(i) p.SetId(common.Pgid(i))
p.flags = metaPageFlag p.SetFlags(common.MetaPageFlag)
// Initialize the meta page. // Initialize the meta page.
m := p.meta() m := p.Meta()
m.magic = magic m.SetMagic(common.Magic)
m.version = version m.SetVersion(common.Version)
m.pageSize = uint32(db.pageSize) m.SetPageSize(uint32(db.pageSize))
m.freelist = 2 m.SetFreelist(2)
m.root = bucket{root: 3} m.SetRootBucket(common.NewInBucket(3, 0))
m.pgid = 4 m.SetPgid(4)
m.txid = txid(i) m.SetTxid(common.Txid(i))
m.checksum = m.sum64() m.SetChecksum(m.Sum64())
} }
// Write an empty freelist at page 3. // Write an empty freelist at page 3.
p := db.pageInBuffer(buf, pgid(2)) p := db.pageInBuffer(buf, common.Pgid(2))
p.id = pgid(2) p.SetId(2)
p.flags = freelistPageFlag p.SetFlags(common.FreelistPageFlag)
p.count = 0 p.SetCount(0)
// Write an empty leaf page at page 4. // Write an empty leaf page at page 4.
p = db.pageInBuffer(buf, pgid(3)) p = db.pageInBuffer(buf, common.Pgid(3))
p.id = pgid(3) p.SetId(3)
p.flags = leafPageFlag p.SetFlags(common.LeafPageFlag)
p.count = 0 p.SetCount(0)
// Write the buffer to our data file. // Write the buffer to our data file.
if _, err := db.ops.writeAt(buf, 0); err != nil { if _, err := db.ops.writeAt(buf, 0); err != nil {
db.Logger().Errorf("writeAt failed: %w", err)
return err return err
} }
if err := fdatasync(db); err != nil { if err := fdatasync(db); err != nil {
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
return err return err
} }
db.filesz = len(buf)
return nil return nil
} }
@ -716,13 +740,31 @@ func (db *DB) close() error {
// //
// IMPORTANT: You must close read-only transactions after you are finished or // IMPORTANT: You must close read-only transactions after you are finished or
// else the database will not reclaim old pages. // else the database will not reclaim old pages.
func (db *DB) Begin(writable bool) (*Tx, error) { func (db *DB) Begin(writable bool) (t *Tx, err error) {
if lg := db.Logger(); lg != discardLogger {
lg.Debugf("Starting a new transaction [writable: %t]", writable)
defer func() {
if err != nil {
lg.Errorf("Starting a new transaction [writable: %t] failed: %v", writable, err)
} else {
lg.Debugf("Starting a new transaction [writable: %t] successfully", writable)
}
}()
}
if writable { if writable {
return db.beginRWTx() return db.beginRWTx()
} }
return db.beginTx() return db.beginTx()
} }
func (db *DB) Logger() Logger {
if db == nil || db.logger == nil {
return getDiscardLogger()
}
return db.logger
}
func (db *DB) beginTx() (*Tx, error) { func (db *DB) beginTx() (*Tx, error) {
// Lock the meta pages while we initialize the transaction. We obtain // Lock the meta pages while we initialize the transaction. We obtain
// the meta lock before the mmap lock because that's the order that the // the meta lock before the mmap lock because that's the order that the
@ -738,14 +780,14 @@ func (db *DB) beginTx() (*Tx, error) {
if !db.opened { if !db.opened {
db.mmaplock.RUnlock() db.mmaplock.RUnlock()
db.metalock.Unlock() db.metalock.Unlock()
return nil, ErrDatabaseNotOpen return nil, berrors.ErrDatabaseNotOpen
} }
// Exit if the database is not correctly mapped. // Exit if the database is not correctly mapped.
if db.data == nil { if db.data == nil {
db.mmaplock.RUnlock() db.mmaplock.RUnlock()
db.metalock.Unlock() db.metalock.Unlock()
return nil, ErrInvalidMapping return nil, berrors.ErrInvalidMapping
} }
// Create a transaction associated with the database. // Create a transaction associated with the database.
@ -755,6 +797,9 @@ func (db *DB) beginTx() (*Tx, error) {
// Keep track of transaction until it closes. // Keep track of transaction until it closes.
db.txs = append(db.txs, t) db.txs = append(db.txs, t)
n := len(db.txs) n := len(db.txs)
if db.freelist != nil {
db.freelist.AddReadonlyTXID(t.meta.Txid())
}
// Unlock the meta pages. // Unlock the meta pages.
db.metalock.Unlock() db.metalock.Unlock()
@ -771,7 +816,7 @@ func (db *DB) beginTx() (*Tx, error) {
func (db *DB) beginRWTx() (*Tx, error) { func (db *DB) beginRWTx() (*Tx, error) {
// If the database was opened with Options.ReadOnly, return an error. // If the database was opened with Options.ReadOnly, return an error.
if db.readOnly { if db.readOnly {
return nil, ErrDatabaseReadOnly return nil, berrors.ErrDatabaseReadOnly
} }
// Obtain writer lock. This is released by the transaction when it closes. // Obtain writer lock. This is released by the transaction when it closes.
@ -786,49 +831,23 @@ func (db *DB) beginRWTx() (*Tx, error) {
// Exit if the database is not open yet. // Exit if the database is not open yet.
if !db.opened { if !db.opened {
db.rwlock.Unlock() db.rwlock.Unlock()
return nil, ErrDatabaseNotOpen return nil, berrors.ErrDatabaseNotOpen
} }
// Exit if the database is not correctly mapped. // Exit if the database is not correctly mapped.
if db.data == nil { if db.data == nil {
db.rwlock.Unlock() db.rwlock.Unlock()
return nil, ErrInvalidMapping return nil, berrors.ErrInvalidMapping
} }
// Create a transaction associated with the database. // Create a transaction associated with the database.
t := &Tx{writable: true} t := &Tx{writable: true}
t.init(db) t.init(db)
db.rwtx = t db.rwtx = t
db.freePages() db.freelist.ReleasePendingPages()
return t, nil return t, nil
} }
// freePages releases any pages associated with closed read-only transactions.
func (db *DB) freePages() {
// Free all pending pages prior to earliest open transaction.
sort.Sort(txsById(db.txs))
minid := txid(0xFFFFFFFFFFFFFFFF)
if len(db.txs) > 0 {
minid = db.txs[0].meta.txid
}
if minid > 0 {
db.freelist.release(minid - 1)
}
// Release unused txid extents.
for _, t := range db.txs {
db.freelist.releaseRange(minid, t.meta.txid-1)
minid = t.meta.txid + 1
}
db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF))
// Any page both allocated and freed in an extent is safe to release.
}
type txsById []*Tx
func (t txsById) Len() int { return len(t) }
func (t txsById) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t txsById) Less(i, j int) bool { return t[i].meta.txid < t[j].meta.txid }
// removeTx removes a transaction from the database. // removeTx removes a transaction from the database.
func (db *DB) removeTx(tx *Tx) { func (db *DB) removeTx(tx *Tx) {
// Release the read lock on the mmap. // Release the read lock on the mmap.
@ -848,6 +867,9 @@ func (db *DB) removeTx(tx *Tx) {
} }
} }
n := len(db.txs) n := len(db.txs)
if db.freelist != nil {
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
}
// Unlock the meta pages. // Unlock the meta pages.
db.metalock.Unlock() db.metalock.Unlock()
@ -1056,7 +1078,20 @@ func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
// //
// This is not necessary under normal operation, however, if you use NoSync // This is not necessary under normal operation, however, if you use NoSync
// then it allows you to force the database file to sync against the disk. // then it allows you to force the database file to sync against the disk.
func (db *DB) Sync() error { return fdatasync(db) } func (db *DB) Sync() (err error) {
if lg := db.Logger(); lg != discardLogger {
lg.Debugf("Syncing bbolt db (%s)", db.path)
defer func() {
if err != nil {
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing bbolt db (%s) failed: %v", runtime.GOOS, runtime.GOARCH, db.path, err)
} else {
lg.Debugf("Syncing bbolt db (%s) successfully", db.path)
}
}()
}
return fdatasync(db)
}
// Stats retrieves ongoing performance stats for the database. // Stats retrieves ongoing performance stats for the database.
// This is only updated when a transaction closes. // This is only updated when a transaction closes.
@ -1069,37 +1104,37 @@ func (db *DB) Stats() Stats {
// This is for internal access to the raw data bytes from the C cursor, use // This is for internal access to the raw data bytes from the C cursor, use
// carefully, or not at all. // carefully, or not at all.
func (db *DB) Info() *Info { func (db *DB) Info() *Info {
_assert(db.data != nil, "database file isn't correctly mapped") common.Assert(db.data != nil, "database file isn't correctly mapped")
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize} return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
} }
// page retrieves a page reference from the mmap based on the current page size. // page retrieves a page reference from the mmap based on the current page size.
func (db *DB) page(id pgid) *page { func (db *DB) page(id common.Pgid) *common.Page {
pos := id * pgid(db.pageSize) pos := id * common.Pgid(db.pageSize)
return (*page)(unsafe.Pointer(&db.data[pos])) return (*common.Page)(unsafe.Pointer(&db.data[pos]))
} }
// pageInBuffer retrieves a page reference from a given byte array based on the current page size. // pageInBuffer retrieves a page reference from a given byte array based on the current page size.
func (db *DB) pageInBuffer(b []byte, id pgid) *page { func (db *DB) pageInBuffer(b []byte, id common.Pgid) *common.Page {
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)])) return (*common.Page)(unsafe.Pointer(&b[id*common.Pgid(db.pageSize)]))
} }
// meta retrieves the current meta page reference. // meta retrieves the current meta page reference.
func (db *DB) meta() *meta { func (db *DB) meta() *common.Meta {
// We have to return the meta with the highest txid which doesn't fail // We have to return the meta with the highest txid which doesn't fail
// validation. Otherwise, we can cause errors when in fact the database is // validation. Otherwise, we can cause errors when in fact the database is
// in a consistent state. metaA is the one with the higher txid. // in a consistent state. metaA is the one with the higher txid.
metaA := db.meta0 metaA := db.meta0
metaB := db.meta1 metaB := db.meta1
if db.meta1.txid > db.meta0.txid { if db.meta1.Txid() > db.meta0.Txid() {
metaA = db.meta1 metaA = db.meta1
metaB = db.meta0 metaB = db.meta0
} }
// Use higher meta page if valid. Otherwise, fallback to previous, if valid. // Use higher meta page if valid. Otherwise, fallback to previous, if valid.
if err := metaA.validate(); err == nil { if err := metaA.Validate(); err == nil {
return metaA return metaA
} else if err := metaB.validate(); err == nil { } else if err := metaB.Validate(); err == nil {
return metaB return metaB
} }
@ -1109,7 +1144,7 @@ func (db *DB) meta() *meta {
} }
// allocate returns a contiguous block of memory starting at a given page. // allocate returns a contiguous block of memory starting at a given page.
func (db *DB) allocate(txid txid, count int) (*page, error) { func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
// Allocate a temporary buffer for the page. // Allocate a temporary buffer for the page.
var buf []byte var buf []byte
if count == 1 { if count == 1 {
@ -1117,17 +1152,18 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
} else { } else {
buf = make([]byte, count*db.pageSize) buf = make([]byte, count*db.pageSize)
} }
p := (*page)(unsafe.Pointer(&buf[0])) p := (*common.Page)(unsafe.Pointer(&buf[0]))
p.overflow = uint32(count - 1) p.SetOverflow(uint32(count - 1))
// Use pages from the freelist if they are available. // Use pages from the freelist if they are available.
if p.id = db.freelist.allocate(txid, count); p.id != 0 { p.SetId(db.freelist.Allocate(txid, count))
if p.Id() != 0 {
return p, nil return p, nil
} }
// Resize mmap() if we're at the end. // Resize mmap() if we're at the end.
p.id = db.rwtx.meta.pgid p.SetId(db.rwtx.meta.Pgid())
var minsz = int((p.id+pgid(count))+1) * db.pageSize var minsz = int((p.Id()+common.Pgid(count))+1) * db.pageSize
if minsz >= db.datasz { if minsz >= db.datasz {
if err := db.mmap(minsz); err != nil { if err := db.mmap(minsz); err != nil {
return nil, fmt.Errorf("mmap allocate error: %s", err) return nil, fmt.Errorf("mmap allocate error: %s", err)
@ -1135,7 +1171,8 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
} }
// Move the page id high water mark. // Move the page id high water mark.
db.rwtx.meta.pgid += pgid(count) curPgid := db.rwtx.meta.Pgid()
db.rwtx.meta.SetPgid(curPgid + common.Pgid(count))
return p, nil return p, nil
} }
@ -1143,7 +1180,13 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
// grow grows the size of the database to the given sz. // grow grows the size of the database to the given sz.
func (db *DB) grow(sz int) error { func (db *DB) grow(sz int) error {
// Ignore if the new size is less than available file size. // Ignore if the new size is less than available file size.
if sz <= db.filesz { lg := db.Logger()
fileSize, err := db.fileSize()
if err != nil {
lg.Errorf("getting file size failed: %w", err)
return err
}
if sz <= fileSize {
return nil return nil
} }
@ -1162,21 +1205,22 @@ func (db *DB) grow(sz int) error {
// gofail: var resizeFileError string // gofail: var resizeFileError string
// return errors.New(resizeFileError) // return errors.New(resizeFileError)
if err := db.file.Truncate(int64(sz)); err != nil { if err := db.file.Truncate(int64(sz)); err != nil {
lg.Errorf("[GOOS: %s, GOARCH: %s] truncating file failed, size: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, sz, db.datasz, err)
return fmt.Errorf("file resize error: %s", err) return fmt.Errorf("file resize error: %s", err)
} }
} }
if err := db.file.Sync(); err != nil { if err := db.file.Sync(); err != nil {
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing file failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
return fmt.Errorf("file sync error: %s", err) return fmt.Errorf("file sync error: %s", err)
} }
if db.Mlock { if db.Mlock {
// unlock old file and lock new one // unlock old file and lock new one
if err := db.mrelock(db.filesz, sz); err != nil { if err := db.mrelock(fileSize, sz); err != nil {
return fmt.Errorf("mlock/munlock error: %s", err) return fmt.Errorf("mlock/munlock error: %s", err)
} }
} }
} }
db.filesz = sz
return nil return nil
} }
@ -1184,7 +1228,7 @@ func (db *DB) IsReadOnly() bool {
return db.readOnly return db.readOnly
} }
func (db *DB) freepages() []pgid { func (db *DB) freepages() []common.Pgid {
tx, err := db.beginTx() tx, err := db.beginTx()
defer func() { defer func() {
err = tx.Rollback() err = tx.Rollback()
@ -1196,21 +1240,21 @@ func (db *DB) freepages() []pgid {
panic("freepages: failed to open read only tx") panic("freepages: failed to open read only tx")
} }
reachable := make(map[pgid]*page) reachable := make(map[common.Pgid]*common.Page)
nofreed := make(map[pgid]bool) nofreed := make(map[common.Pgid]bool)
ech := make(chan error) ech := make(chan error)
go func() { go func() {
for e := range ech { for e := range ech {
panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e)) panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e))
} }
}() }()
tx.checkBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech) tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
close(ech) close(ech)
// TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages. // TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages.
var fids []pgid var fids []common.Pgid
for i := pgid(2); i < db.meta().pgid; i++ { for i := common.Pgid(2); i < db.meta().Pgid(); i++ {
if _, ok := reachable[i]; !ok { if _, ok := reachable[i]; !ok {
fids = append(fids, i) fids = append(fids, i)
} }
@ -1218,11 +1262,17 @@ func (db *DB) freepages() []pgid {
return fids return fids
} }
func newFreelist(freelistType FreelistType) fl.Interface {
if freelistType == FreelistMapType {
return fl.NewHashMapFreelist()
}
return fl.NewArrayFreelist()
}
// Options represents the options that can be set when opening a database. // Options represents the options that can be set when opening a database.
type Options struct { type Options struct {
// Timeout is the amount of time to wait to obtain a file lock. // Timeout is the amount of time to wait to obtain a file lock.
// When set to zero it will wait indefinitely. This option is only // When set to zero it will wait indefinitely.
// available on Darwin and Linux.
Timeout time.Duration Timeout time.Duration
// Sets the DB.NoGrowSync flag before memory mapping the file. // Sets the DB.NoGrowSync flag before memory mapping the file.
@ -1259,6 +1309,12 @@ type Options struct {
// If <=0, the initial map size is 0. // If <=0, the initial map size is 0.
// If initialMmapSize is smaller than the previous database size, // If initialMmapSize is smaller than the previous database size,
// it takes no effect. // it takes no effect.
//
// Note: On Windows, due to platform limitations, the database file size
// will be immediately resized to match `InitialMmapSize` (aligned to page size)
// when the DB is opened. On non-Windows platforms, the file size will grow
// dynamically based on the actual amount of written data, regardless of `InitialMmapSize`.
// Refer to https://github.com/etcd-io/bbolt/issues/378#issuecomment-1378121966.
InitialMmapSize int InitialMmapSize int
// PageSize overrides the default OS page size. // PageSize overrides the default OS page size.
@ -1277,6 +1333,19 @@ type Options struct {
// It prevents potential page faults, however // It prevents potential page faults, however
// used memory can't be reclaimed. (UNIX only) // used memory can't be reclaimed. (UNIX only)
Mlock bool Mlock bool
// Logger is the logger used for bbolt.
Logger Logger
}
func (o *Options) String() string {
if o == nil {
return "{}"
}
return fmt.Sprintf("{Timeout: %s, NoGrowSync: %t, NoFreelistSync: %t, PreLoadFreelist: %t, FreelistType: %s, ReadOnly: %t, MmapFlags: %x, InitialMmapSize: %d, PageSize: %d, NoSync: %t, OpenFile: %p, Mlock: %t, Logger: %p}",
o.Timeout, o.NoGrowSync, o.NoFreelistSync, o.PreLoadFreelist, o.FreelistType, o.ReadOnly, o.MmapFlags, o.InitialMmapSize, o.PageSize, o.NoSync, o.OpenFile, o.Mlock, o.Logger)
} }
// DefaultOptions represent the options used if nil options are passed into Open(). // DefaultOptions represent the options used if nil options are passed into Open().
@ -1327,65 +1396,3 @@ type Info struct {
Data uintptr Data uintptr
PageSize int PageSize int
} }
type meta struct {
magic uint32
version uint32
pageSize uint32
flags uint32
root bucket
freelist pgid
pgid pgid
txid txid
checksum uint64
}
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
func (m *meta) validate() error {
if m.magic != magic {
return ErrInvalid
} else if m.version != version {
return ErrVersionMismatch
} else if m.checksum != m.sum64() {
return ErrChecksum
}
return nil
}
// copy copies one meta object to another.
func (m *meta) copy(dest *meta) {
*dest = *m
}
// write writes the meta onto a page.
func (m *meta) write(p *page) {
if m.root.root >= m.pgid {
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
} else if m.freelist >= m.pgid && m.freelist != pgidNoFreelist {
// TODO: reject pgidNoFreeList if !NoFreelistSync
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
}
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
p.id = pgid(m.txid % 2)
p.flags |= metaPageFlag
// Calculate the checksum.
m.checksum = m.sum64()
m.copy(p.meta())
}
// generates the checksum for the meta.
func (m *meta) sum64() uint64 {
var h = fnv.New64a()
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
return h.Sum64()
}
// _assert will panic with a given formatted message if the given condition is false.
func _assert(condition bool, msg string, v ...interface{}) {
if !condition {
panic(fmt.Sprintf("assertion failed: "+msg, v...))
}
}

76
vendor/go.etcd.io/bbolt/errors.go generated vendored
View file

@ -1,78 +1,108 @@
package bbolt package bbolt
import "errors" import "go.etcd.io/bbolt/errors"
// These errors can be returned when opening or calling methods on a DB. // These errors can be returned when opening or calling methods on a DB.
var ( var (
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it // ErrDatabaseNotOpen is returned when a DB instance is accessed before it
// is opened or after it is closed. // is opened or after it is closed.
ErrDatabaseNotOpen = errors.New("database not open") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
// ErrDatabaseOpen is returned when opening a database that is ErrDatabaseNotOpen = errors.ErrDatabaseNotOpen
// already open.
ErrDatabaseOpen = errors.New("database already open")
// ErrInvalid is returned when both meta pages on a database are invalid. // ErrInvalid is returned when both meta pages on a database are invalid.
// This typically occurs when a file is not a bolt database. // This typically occurs when a file is not a bolt database.
ErrInvalid = errors.New("invalid database") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrInvalid = errors.ErrInvalid
// ErrInvalidMapping is returned when the database file fails to get mapped. // ErrInvalidMapping is returned when the database file fails to get mapped.
ErrInvalidMapping = errors.New("database isn't correctly mapped") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrInvalidMapping = errors.ErrInvalidMapping
// ErrVersionMismatch is returned when the data file was created with a // ErrVersionMismatch is returned when the data file was created with a
// different version of Bolt. // different version of Bolt.
ErrVersionMismatch = errors.New("version mismatch") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrVersionMismatch = errors.ErrVersionMismatch
// ErrChecksum is returned when either meta page checksum does not match. // ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
ErrChecksum = errors.New("checksum error") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrChecksum = errors.ErrChecksum
// ErrTimeout is returned when a database cannot obtain an exclusive lock // ErrTimeout is returned when a database cannot obtain an exclusive lock
// on the data file after the timeout passed to Open(). // on the data file after the timeout passed to Open().
ErrTimeout = errors.New("timeout") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrTimeout = errors.ErrTimeout
) )
// These errors can occur when beginning or committing a Tx. // These errors can occur when beginning or committing a Tx.
var ( var (
// ErrTxNotWritable is returned when performing a write operation on a // ErrTxNotWritable is returned when performing a write operation on a
// read-only transaction. // read-only transaction.
ErrTxNotWritable = errors.New("tx not writable") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrTxNotWritable = errors.ErrTxNotWritable
// ErrTxClosed is returned when committing or rolling back a transaction // ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back. // that has already been committed or rolled back.
ErrTxClosed = errors.New("tx closed") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrTxClosed = errors.ErrTxClosed
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a // ErrDatabaseReadOnly is returned when a mutating transaction is started on a
// read-only database. // read-only database.
ErrDatabaseReadOnly = errors.New("database is in read-only mode") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrDatabaseReadOnly = errors.ErrDatabaseReadOnly
// ErrFreePagesNotLoaded is returned when a readonly transaction without // ErrFreePagesNotLoaded is returned when a readonly transaction without
// preloading the free pages is trying to access the free pages. // preloading the free pages is trying to access the free pages.
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrFreePagesNotLoaded = errors.ErrFreePagesNotLoaded
) )
// These errors can occur when putting or deleting a value or a bucket. // These errors can occur when putting or deleting a value or a bucket.
var ( var (
// ErrBucketNotFound is returned when trying to access a bucket that has // ErrBucketNotFound is returned when trying to access a bucket that has
// not been created yet. // not been created yet.
ErrBucketNotFound = errors.New("bucket not found") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrBucketNotFound = errors.ErrBucketNotFound
// ErrBucketExists is returned when creating a bucket that already exists. // ErrBucketExists is returned when creating a bucket that already exists.
ErrBucketExists = errors.New("bucket already exists") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrBucketExists = errors.ErrBucketExists
// ErrBucketNameRequired is returned when creating a bucket with a blank name. // ErrBucketNameRequired is returned when creating a bucket with a blank name.
ErrBucketNameRequired = errors.New("bucket name required") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrBucketNameRequired = errors.ErrBucketNameRequired
// ErrKeyRequired is returned when inserting a zero-length key. // ErrKeyRequired is returned when inserting a zero-length key.
ErrKeyRequired = errors.New("key required") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrKeyRequired = errors.ErrKeyRequired
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize. // ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
ErrKeyTooLarge = errors.New("key too large") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrKeyTooLarge = errors.ErrKeyTooLarge
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize. // ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
ErrValueTooLarge = errors.New("value too large") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrValueTooLarge = errors.ErrValueTooLarge
// ErrIncompatibleValue is returned when trying create or delete a bucket // ErrIncompatibleValue is returned when trying create or delete a bucket
// on an existing non-bucket key or when trying to create or delete a // on an existing non-bucket key or when trying to create or delete a
// non-bucket key on an existing bucket key. // non-bucket key on an existing bucket key.
ErrIncompatibleValue = errors.New("incompatible value") //
// Deprecated: Use the error variables defined in the bbolt/errors package.
ErrIncompatibleValue = errors.ErrIncompatibleValue
) )

84
vendor/go.etcd.io/bbolt/errors/errors.go generated vendored Normal file
View file

@ -0,0 +1,84 @@
// Package errors defines the error variables that may be returned
// during bbolt operations.
package errors
import "errors"
// These errors can be returned when opening or calling methods on a DB.
var (
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
// is opened or after it is closed.
ErrDatabaseNotOpen = errors.New("database not open")
// ErrInvalid is returned when both meta pages on a database are invalid.
// This typically occurs when a file is not a bolt database.
ErrInvalid = errors.New("invalid database")
// ErrInvalidMapping is returned when the database file fails to get mapped.
ErrInvalidMapping = errors.New("database isn't correctly mapped")
// ErrVersionMismatch is returned when the data file was created with a
// different version of Bolt.
ErrVersionMismatch = errors.New("version mismatch")
// ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
ErrChecksum = errors.New("checksum error")
// ErrTimeout is returned when a database cannot obtain an exclusive lock
// on the data file after the timeout passed to Open().
ErrTimeout = errors.New("timeout")
)
// These errors can occur when beginning or committing a Tx.
var (
// ErrTxNotWritable is returned when performing a write operation on a
// read-only transaction.
ErrTxNotWritable = errors.New("tx not writable")
// ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back.
ErrTxClosed = errors.New("tx closed")
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
// read-only database.
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
// ErrFreePagesNotLoaded is returned when a readonly transaction without
// preloading the free pages is trying to access the free pages.
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
)
// These errors can occur when putting or deleting a value or a bucket.
var (
// ErrBucketNotFound is returned when trying to access a bucket that has
// not been created yet.
ErrBucketNotFound = errors.New("bucket not found")
// ErrBucketExists is returned when creating a bucket that already exists.
ErrBucketExists = errors.New("bucket already exists")
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
ErrBucketNameRequired = errors.New("bucket name required")
// ErrKeyRequired is returned when inserting a zero-length key.
ErrKeyRequired = errors.New("key required")
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
ErrKeyTooLarge = errors.New("key too large")
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
ErrValueTooLarge = errors.New("value too large")
// ErrIncompatibleValue is returned when trying to create or delete a bucket
// on an existing non-bucket key or when trying to create or delete a
// non-bucket key on an existing bucket key.
ErrIncompatibleValue = errors.New("incompatible value")
// ErrSameBuckets is returned when trying to move a sub-bucket between
// source and target buckets, while source and target buckets are the same.
ErrSameBuckets = errors.New("the source and target are the same bucket")
// ErrDifferentDB is returned when trying to move a sub-bucket between
// source and target buckets, while source and target buckets are in different database files.
ErrDifferentDB = errors.New("the source and target buckets are in different database files")
)

410
vendor/go.etcd.io/bbolt/freelist.go generated vendored
View file

@ -1,410 +0,0 @@
package bbolt
import (
"fmt"
"sort"
"unsafe"
)
// txPending holds a list of pgids and corresponding allocation txns
// that are pending to be freed.
type txPending struct {
ids []pgid
alloctx []txid // txids allocating the ids
lastReleaseBegin txid // beginning txid of last matching releaseRange
}
// pidSet holds the set of starting pgids which have the same span size
type pidSet map[pgid]struct{}
// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
freelistType FreelistType // freelist type
ids []pgid // all free and available free page ids.
allocs map[pgid]txid // mapping of txid that allocated a pgid.
pending map[txid]*txPending // mapping of soon-to-be free page ids by tx.
cache map[pgid]struct{} // fast lookup of all free and pending page ids.
freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
forwardMap map[pgid]uint64 // key is start pgid, value is its span size
backwardMap map[pgid]uint64 // key is end pgid, value is its span size
allocate func(txid txid, n int) pgid // the freelist allocate func
free_count func() int // the function which gives you free page number
mergeSpans func(ids pgids) // the mergeSpan func
getFreePageIDs func() []pgid // get free pgids func
readIDs func(pgids []pgid) // readIDs func reads list of pages and init the freelist
}
// newFreelist returns an empty, initialized freelist.
func newFreelist(freelistType FreelistType) *freelist {
f := &freelist{
freelistType: freelistType,
allocs: make(map[pgid]txid),
pending: make(map[txid]*txPending),
cache: make(map[pgid]struct{}),
freemaps: make(map[uint64]pidSet),
forwardMap: make(map[pgid]uint64),
backwardMap: make(map[pgid]uint64),
}
if freelistType == FreelistMapType {
f.allocate = f.hashmapAllocate
f.free_count = f.hashmapFreeCount
f.mergeSpans = f.hashmapMergeSpans
f.getFreePageIDs = f.hashmapGetFreePageIDs
f.readIDs = f.hashmapReadIDs
} else {
f.allocate = f.arrayAllocate
f.free_count = f.arrayFreeCount
f.mergeSpans = f.arrayMergeSpans
f.getFreePageIDs = f.arrayGetFreePageIDs
f.readIDs = f.arrayReadIDs
}
return f
}
// size returns the size of the page after serialization.
func (f *freelist) size() int {
n := f.count()
if n >= 0xFFFF {
// The first element will be used to store the count. See freelist.write.
n++
}
return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n)
}
// count returns count of pages on the freelist
func (f *freelist) count() int {
return f.free_count() + f.pending_count()
}
// arrayFreeCount returns count of free pages(array version)
func (f *freelist) arrayFreeCount() int {
return len(f.ids)
}
// pending_count returns count of pending pages
func (f *freelist) pending_count() int {
var count int
for _, txp := range f.pending {
count += len(txp.ids)
}
return count
}
// copyall copies a list of all free ids and all pending ids in one sorted list.
// f.count returns the minimum length required for dst.
func (f *freelist) copyall(dst []pgid) {
m := make(pgids, 0, f.pending_count())
for _, txp := range f.pending {
m = append(m, txp.ids...)
}
sort.Sort(m)
mergepgids(dst, f.getFreePageIDs(), m)
}
// arrayAllocate returns the starting page id of a contiguous list of pages of a given size.
// If a contiguous block cannot be found then 0 is returned.
func (f *freelist) arrayAllocate(txid txid, n int) pgid {
if len(f.ids) == 0 {
return 0
}
var initial, previd pgid
for i, id := range f.ids {
if id <= 1 {
panic(fmt.Sprintf("invalid page allocation: %d", id))
}
// Reset initial page if this is not contiguous.
if previd == 0 || id-previd != 1 {
initial = id
}
// If we found a contiguous block then remove it and return it.
if (id-initial)+1 == pgid(n) {
// If we're allocating off the beginning then take the fast path
// and just adjust the existing slice. This will use extra memory
// temporarily but the append() in free() will realloc the slice
// as is necessary.
if (i + 1) == n {
f.ids = f.ids[i+1:]
} else {
copy(f.ids[i-n+1:], f.ids[i+1:])
f.ids = f.ids[:len(f.ids)-n]
}
// Remove from the free cache.
for i := pgid(0); i < pgid(n); i++ {
delete(f.cache, initial+i)
}
f.allocs[initial] = txid
return initial
}
previd = id
}
return 0
}
// free releases a page and its overflow for a given transaction id.
// If the page is already free then a panic will occur.
func (f *freelist) free(txid txid, p *page) {
if p.id <= 1 {
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
}
// Free page and all its overflow pages.
txp := f.pending[txid]
if txp == nil {
txp = &txPending{}
f.pending[txid] = txp
}
allocTxid, ok := f.allocs[p.id]
if ok {
delete(f.allocs, p.id)
} else if (p.flags & freelistPageFlag) != 0 {
// Freelist is always allocated by prior tx.
allocTxid = txid - 1
}
for id := p.id; id <= p.id+pgid(p.overflow); id++ {
// Verify that page is not already free.
if _, ok := f.cache[id]; ok {
panic(fmt.Sprintf("page %d already freed", id))
}
// Add to the freelist and cache.
txp.ids = append(txp.ids, id)
txp.alloctx = append(txp.alloctx, allocTxid)
f.cache[id] = struct{}{}
}
}
// release moves all page ids for a transaction id (or older) to the freelist.
func (f *freelist) release(txid txid) {
m := make(pgids, 0)
for tid, txp := range f.pending {
if tid <= txid {
// Move transaction's pending pages to the available freelist.
// Don't remove from the cache since the page is still free.
m = append(m, txp.ids...)
delete(f.pending, tid)
}
}
f.mergeSpans(m)
}
// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
func (f *freelist) releaseRange(begin, end txid) {
if begin > end {
return
}
var m pgids
for tid, txp := range f.pending {
if tid < begin || tid > end {
continue
}
// Don't recompute freed pages if ranges haven't updated.
if txp.lastReleaseBegin == begin {
continue
}
for i := 0; i < len(txp.ids); i++ {
if atx := txp.alloctx[i]; atx < begin || atx > end {
continue
}
m = append(m, txp.ids[i])
txp.ids[i] = txp.ids[len(txp.ids)-1]
txp.ids = txp.ids[:len(txp.ids)-1]
txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
i--
}
txp.lastReleaseBegin = begin
if len(txp.ids) == 0 {
delete(f.pending, tid)
}
}
f.mergeSpans(m)
}
// rollback removes the pages from a given pending tx.
func (f *freelist) rollback(txid txid) {
// Remove page ids from cache.
txp := f.pending[txid]
if txp == nil {
return
}
var m pgids
for i, pgid := range txp.ids {
delete(f.cache, pgid)
tx := txp.alloctx[i]
if tx == 0 {
continue
}
if tx != txid {
// Pending free aborted; restore page back to alloc list.
f.allocs[pgid] = tx
} else {
// Freed page was allocated by this txn; OK to throw away.
m = append(m, pgid)
}
}
// Remove pages from pending list and mark as free if allocated by txid.
delete(f.pending, txid)
// Remove pgids which are allocated by this txid
for pgid, tid := range f.allocs {
if tid == txid {
delete(f.allocs, pgid)
}
}
f.mergeSpans(m)
}
// freed returns whether a given page is in the free list.
func (f *freelist) freed(pgId pgid) bool {
_, ok := f.cache[pgId]
return ok
}
// read initializes the freelist from a freelist page.
func (f *freelist) read(p *page) {
if (p.flags & freelistPageFlag) == 0 {
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ()))
}
// If the page.count is at the max uint16 value (64k) then it's considered
// an overflow and the size of the freelist is stored as the first element.
var idx, count = 0, int(p.count)
if count == 0xFFFF {
idx = 1
c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
count = int(c)
if count < 0 {
panic(fmt.Sprintf("leading element count %d overflows int", c))
}
}
// Copy the list of page ids from the freelist.
if count == 0 {
f.ids = nil
} else {
data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(pgid(0)), idx)
ids := unsafe.Slice((*pgid)(data), count)
// copy the ids, so we don't modify on the freelist page directly
idsCopy := make([]pgid, count)
copy(idsCopy, ids)
// Make sure they're sorted.
sort.Sort(pgids(idsCopy))
f.readIDs(idsCopy)
}
}
// arrayReadIDs initializes the freelist from a given list of ids.
func (f *freelist) arrayReadIDs(ids []pgid) {
f.ids = ids
f.reindex()
}
func (f *freelist) arrayGetFreePageIDs() []pgid {
return f.ids
}
// write writes the page ids onto a freelist page. All free and pending ids are
// saved to disk since in the event of a program crash, all pending ids will
// become free.
func (f *freelist) write(p *page) error {
// Combine the old free pgids and pgids waiting on an open transaction.
// Update the header flag.
p.flags |= freelistPageFlag
// The page.count can only hold up to 64k elements so if we overflow that
// number then we handle it by putting the size in the first element.
l := f.count()
if l == 0 {
p.count = uint16(l)
} else if l < 0xFFFF {
p.count = uint16(l)
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
ids := unsafe.Slice((*pgid)(data), l)
f.copyall(ids)
} else {
p.count = 0xFFFF
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
ids := unsafe.Slice((*pgid)(data), l+1)
ids[0] = pgid(l)
f.copyall(ids[1:])
}
return nil
}
// reload reads the freelist from a page and filters out pending items.
func (f *freelist) reload(p *page) {
f.read(p)
// Build a cache of only pending pages.
pcache := make(map[pgid]bool)
for _, txp := range f.pending {
for _, pendingID := range txp.ids {
pcache[pendingID] = true
}
}
// Check each page in the freelist and build a new available freelist
// with any pages not in the pending lists.
var a []pgid
for _, id := range f.getFreePageIDs() {
if !pcache[id] {
a = append(a, id)
}
}
f.readIDs(a)
}
// noSyncReload reads the freelist from pgids and filters out pending items.
func (f *freelist) noSyncReload(pgids []pgid) {
// Build a cache of only pending pages.
pcache := make(map[pgid]bool)
for _, txp := range f.pending {
for _, pendingID := range txp.ids {
pcache[pendingID] = true
}
}
// Check each page in the freelist and build a new available freelist
// with any pages not in the pending lists.
var a []pgid
for _, id := range pgids {
if !pcache[id] {
a = append(a, id)
}
}
f.readIDs(a)
}
// reindex rebuilds the free cache based on available and pending free lists.
func (f *freelist) reindex() {
ids := f.getFreePageIDs()
f.cache = make(map[pgid]struct{}, len(ids))
for _, id := range ids {
f.cache[id] = struct{}{}
}
for _, txp := range f.pending {
for _, pendingID := range txp.ids {
f.cache[pendingID] = struct{}{}
}
}
}
// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array
func (f *freelist) arrayMergeSpans(ids pgids) {
sort.Sort(ids)
f.ids = pgids(f.ids).merge(ids)
}

View file

@ -1,178 +0,0 @@
package bbolt
import "sort"
// hashmapFreeCount returns count of free pages(hashmap version)
func (f *freelist) hashmapFreeCount() int {
// use the forwardMap to get the total count
count := 0
for _, size := range f.forwardMap {
count += int(size)
}
return count
}
// hashmapAllocate serves the same purpose as arrayAllocate, but use hashmap as backend
func (f *freelist) hashmapAllocate(txid txid, n int) pgid {
if n == 0 {
return 0
}
// if we have a exact size match just return short path
if bm, ok := f.freemaps[uint64(n)]; ok {
for pid := range bm {
// remove the span
f.delSpan(pid, uint64(n))
f.allocs[pid] = txid
for i := pgid(0); i < pgid(n); i++ {
delete(f.cache, pid+i)
}
return pid
}
}
// lookup the map to find larger span
for size, bm := range f.freemaps {
if size < uint64(n) {
continue
}
for pid := range bm {
// remove the initial
f.delSpan(pid, size)
f.allocs[pid] = txid
remain := size - uint64(n)
// add remain span
f.addSpan(pid+pgid(n), remain)
for i := pgid(0); i < pgid(n); i++ {
delete(f.cache, pid+i)
}
return pid
}
}
return 0
}
// hashmapReadIDs reads pgids as input an initial the freelist(hashmap version)
func (f *freelist) hashmapReadIDs(pgids []pgid) {
f.init(pgids)
// Rebuild the page cache.
f.reindex()
}
// hashmapGetFreePageIDs returns the sorted free page ids
func (f *freelist) hashmapGetFreePageIDs() []pgid {
count := f.free_count()
if count == 0 {
return nil
}
m := make([]pgid, 0, count)
for start, size := range f.forwardMap {
for i := 0; i < int(size); i++ {
m = append(m, start+pgid(i))
}
}
sort.Sort(pgids(m))
return m
}
// hashmapMergeSpans try to merge list of pages(represented by pgids) with existing spans
func (f *freelist) hashmapMergeSpans(ids pgids) {
for _, id := range ids {
// try to see if we can merge and update
f.mergeWithExistingSpan(id)
}
}
// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward
func (f *freelist) mergeWithExistingSpan(pid pgid) {
prev := pid - 1
next := pid + 1
preSize, mergeWithPrev := f.backwardMap[prev]
nextSize, mergeWithNext := f.forwardMap[next]
newStart := pid
newSize := uint64(1)
if mergeWithPrev {
//merge with previous span
start := prev + 1 - pgid(preSize)
f.delSpan(start, preSize)
newStart -= pgid(preSize)
newSize += preSize
}
if mergeWithNext {
// merge with next span
f.delSpan(next, nextSize)
newSize += nextSize
}
f.addSpan(newStart, newSize)
}
func (f *freelist) addSpan(start pgid, size uint64) {
f.backwardMap[start-1+pgid(size)] = size
f.forwardMap[start] = size
if _, ok := f.freemaps[size]; !ok {
f.freemaps[size] = make(map[pgid]struct{})
}
f.freemaps[size][start] = struct{}{}
}
func (f *freelist) delSpan(start pgid, size uint64) {
delete(f.forwardMap, start)
delete(f.backwardMap, start+pgid(size-1))
delete(f.freemaps[size], start)
if len(f.freemaps[size]) == 0 {
delete(f.freemaps, size)
}
}
// initial from pgids using when use hashmap version
// pgids must be sorted
func (f *freelist) init(pgids []pgid) {
if len(pgids) == 0 {
return
}
size := uint64(1)
start := pgids[0]
if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) {
panic("pgids not sorted")
}
f.freemaps = make(map[uint64]pidSet)
f.forwardMap = make(map[pgid]uint64)
f.backwardMap = make(map[pgid]uint64)
for i := 1; i < len(pgids); i++ {
// continuous page
if pgids[i] == pgids[i-1]+1 {
size++
} else {
f.addSpan(start, size)
size = 1
start = pgids[i]
}
}
// init the tail
if size != 0 && start != 0 {
f.addSpan(start, size)
}
}

7
vendor/go.etcd.io/bbolt/internal/common/bolt_386.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0x7FFFFFFF // 2GB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0xFFFFFFF

View file

@ -0,0 +1,7 @@
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

7
vendor/go.etcd.io/bbolt/internal/common/bolt_arm.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0x7FFFFFFF // 2GB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0xFFFFFFF

View file

@ -0,0 +1,9 @@
//go:build arm64
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build loong64
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build mips64 || mips64le
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0x8000000000 // 512GB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build mips || mipsle
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0x40000000 // 1GB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0xFFFFFFF

9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
//go:build ppc
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0x7FFFFFFF // 2GB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0xFFFFFFF

View file

@ -0,0 +1,9 @@
//go:build ppc64
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build ppc64le
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build riscv64
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

View file

@ -0,0 +1,9 @@
//go:build s390x
package common
// MaxMapSize represents the largest mmap size supported by Bolt.
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
// MaxAllocSize is the size used when creating array pointers.
const MaxAllocSize = 0x7FFFFFFF

54
vendor/go.etcd.io/bbolt/internal/common/bucket.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
package common
import (
"fmt"
"unsafe"
)
const BucketHeaderSize = int(unsafe.Sizeof(InBucket{}))
// InBucket represents the on-file representation of a bucket.
// This is stored as the "value" of a bucket key. If the bucket is small enough,
// then its root page can be stored inline in the "value", after the bucket
// header. In the case of inline buckets, the "root" will be 0.
type InBucket struct {
root Pgid // page id of the bucket's root-level page
sequence uint64 // monotonically incrementing, used by NextSequence()
}
func NewInBucket(root Pgid, seq uint64) InBucket {
return InBucket{
root: root,
sequence: seq,
}
}
func (b *InBucket) RootPage() Pgid {
return b.root
}
func (b *InBucket) SetRootPage(id Pgid) {
b.root = id
}
// InSequence returns the sequence. The reason why not naming it `Sequence`
// is to avoid duplicated name as `(*Bucket) Sequence()`
func (b *InBucket) InSequence() uint64 {
return b.sequence
}
func (b *InBucket) SetInSequence(v uint64) {
b.sequence = v
}
func (b *InBucket) IncSequence() {
b.sequence++
}
func (b *InBucket) InlinePage(v []byte) *Page {
return (*Page)(unsafe.Pointer(&v[BucketHeaderSize]))
}
func (b *InBucket) String() string {
return fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
}

Some files were not shown because too many files have changed in this diff Show more