update vendor
This commit is contained in:
parent
824f46979b
commit
8675bc4b7a
328 changed files with 27166 additions and 6537 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (C) 2018 Marius Schellenberger
|
// Copyright (C) 2025 Marius Schellenberger
|
||||||
|
|
||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
|
|
|
||||||
31
vendor/git.giftfish.de/ston1th/jwt/v3/claims.go
vendored
31
vendor/git.giftfish.de/ston1th/jwt/v3/claims.go
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (C) 2018 Marius Schellenberger
|
// Copyright (C) 2025 Marius Schellenberger
|
||||||
|
|
||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
|
|
|
||||||
33
vendor/git.giftfish.de/ston1th/jwt/v3/hash.go
vendored
33
vendor/git.giftfish.de/ston1th/jwt/v3/hash.go
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
64
vendor/git.giftfish.de/ston1th/jwt/v3/jwt.go
vendored
64
vendor/git.giftfish.de/ston1th/jwt/v3/jwt.go
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
vendor/git.giftfish.de/ston1th/jwt/v3/rand.go
vendored
Normal file
11
vendor/git.giftfish.de/ston1th/jwt/v3/rand.go
vendored
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2025 Marius Schellenberger
|
||||||
|
|
||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
1
vendor/github.com/RoaringBitmap/roaring/.drone.yml
generated
vendored
1
vendor/github.com/RoaringBitmap/roaring/.drone.yml
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
107
vendor/github.com/RoaringBitmap/roaring/Makefile
generated
vendored
107
vendor/github.com/RoaringBitmap/roaring/Makefile
generated
vendored
|
|
@ -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
|
|
||||||
16
vendor/github.com/RoaringBitmap/roaring/README.md
generated
vendored
16
vendor/github.com/RoaringBitmap/roaring/README.md
generated
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
roaring [](https://godoc.org/github.com/RoaringBitmap/roaring/roaring64) [](https://goreportcard.com/report/github.com/RoaringBitmap/roaring)
|
# roaring
|
||||||
[](https://cloud.drone.io/RoaringBitmap/roaring)
|
|
||||||
|
[](https://godoc.org/github.com/RoaringBitmap/roaring) [](https://goreportcard.com/report/github.com/RoaringBitmap/roaring)
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
@ -8,7 +10,7 @@ roaring [][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.
|
||||||
|
|
||||||
|
|
|
||||||
63
vendor/github.com/RoaringBitmap/roaring/arraycontainer.go
generated
vendored
63
vendor/github.com/RoaringBitmap/roaring/arraycontainer.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
63
vendor/github.com/RoaringBitmap/roaring/bitmapcontainer.go
generated
vendored
63
vendor/github.com/RoaringBitmap/roaring/bitmapcontainer.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
63
vendor/github.com/RoaringBitmap/roaring/internal/byte_input.go
generated
vendored
63
vendor/github.com/RoaringBitmap/roaring/internal/byte_input.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
245
vendor/github.com/RoaringBitmap/roaring/roaring.go
generated
vendored
245
vendor/github.com/RoaringBitmap/roaring/roaring.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
13
vendor/github.com/RoaringBitmap/roaring/roaringarray.go
generated
vendored
13
vendor/github.com/RoaringBitmap/roaring/roaringarray.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
9
vendor/github.com/RoaringBitmap/roaring/runcontainer.go
generated
vendored
9
vendor/github.com/RoaringBitmap/roaring/runcontainer.go
generated
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
1
vendor/github.com/RoaringBitmap/roaring/serialization.go
generated
vendored
1
vendor/github.com/RoaringBitmap/roaring/serialization.go
generated
vendored
|
|
@ -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)))
|
||||||
|
|
|
||||||
80
vendor/github.com/RoaringBitmap/roaring/serialization_littleendian.go
generated
vendored
80
vendor/github.com/RoaringBitmap/roaring/serialization_littleendian.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
vendor/github.com/bits-and-blooms/bitset/README.md
generated
vendored
95
vendor/github.com/bits-and-blooms/bitset/README.md
generated
vendored
|
|
@ -7,6 +7,15 @@
|
||||||
[](https://pkg.go.dev/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
|
||||||
|
|
||||||
|
|
||||||
|
[](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
5
vendor/github.com/bits-and-blooms/bitset/SECURITY.md
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
You can report privately a vulnerability by email at daniel@lemire.me (current maintainer).
|
||||||
1020
vendor/github.com/bits-and-blooms/bitset/bitset.go
generated
vendored
1020
vendor/github.com/bits-and-blooms/bitset/bitset.go
generated
vendored
File diff suppressed because it is too large
Load diff
23
vendor/github.com/bits-and-blooms/bitset/bitset_iter.go
generated
vendored
Normal file
23
vendor/github.com/bits-and-blooms/bitset/bitset_iter.go
generated
vendored
Normal 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
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
61
vendor/github.com/bits-and-blooms/bitset/popcnt.go
generated
vendored
61
vendor/github.com/bits-and-blooms/bitset/popcnt.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
vendor/github.com/bits-and-blooms/bitset/popcnt_19.go
generated
vendored
45
vendor/github.com/bits-and-blooms/bitset/popcnt_19.go
generated
vendored
|
|
@ -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)
|
|
||||||
}
|
|
||||||
68
vendor/github.com/bits-and-blooms/bitset/popcnt_amd64.go
generated
vendored
68
vendor/github.com/bits-and-blooms/bitset/popcnt_amd64.go
generated
vendored
|
|
@ -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)
|
|
||||||
}
|
|
||||||
104
vendor/github.com/bits-and-blooms/bitset/popcnt_amd64.s
generated
vendored
104
vendor/github.com/bits-and-blooms/bitset/popcnt_amd64.s
generated
vendored
|
|
@ -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
|
|
||||||
24
vendor/github.com/bits-and-blooms/bitset/popcnt_generic.go
generated
vendored
24
vendor/github.com/bits-and-blooms/bitset/popcnt_generic.go
generated
vendored
|
|
@ -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
47
vendor/github.com/bits-and-blooms/bitset/select.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
14
vendor/github.com/bits-and-blooms/bitset/trailing_zeros_18.go
generated
vendored
14
vendor/github.com/bits-and-blooms/bitset/trailing_zeros_18.go
generated
vendored
|
|
@ -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])
|
|
||||||
}
|
|
||||||
9
vendor/github.com/bits-and-blooms/bitset/trailing_zeros_19.go
generated
vendored
9
vendor/github.com/bits-and-blooms/bitset/trailing_zeros_19.go
generated
vendored
|
|
@ -1,9 +0,0 @@
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package bitset
|
|
||||||
|
|
||||||
import "math/bits"
|
|
||||||
|
|
||||||
func trailingZeroes64(v uint64) uint {
|
|
||||||
return uint(bits.TrailingZeros64(v))
|
|
||||||
}
|
|
||||||
8
vendor/github.com/boombuler/barcode/barcode.go
generated
vendored
8
vendor/github.com/boombuler/barcode/barcode.go
generated
vendored
|
|
@ -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
39
vendor/github.com/boombuler/barcode/color_scheme.go
generated
vendored
Normal 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},
|
||||||
|
}
|
||||||
16
vendor/github.com/boombuler/barcode/qr/encoder.go
generated
vendored
16
vendor/github.com/boombuler/barcode/qr/encoder.go
generated
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
18
vendor/github.com/boombuler/barcode/qr/qrcode.go
generated
vendored
18
vendor/github.com/boombuler/barcode/qr/qrcode.go
generated
vendored
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
27
vendor/github.com/boombuler/barcode/scaledbarcode.go
generated
vendored
27
vendor/github.com/boombuler/barcode/scaledbarcode.go
generated
vendored
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
vendor/github.com/boombuler/barcode/utils/base1dcode.go
generated
vendored
25
vendor/github.com/boombuler/barcode/utils/base1dcode.go
generated
vendored
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
vendor/github.com/cpuguy83/go-md2man/v2/md2man/debug.go
generated
vendored
Normal file
62
vendor/github.com/cpuguy83/go-md2man/v2/md2man/debug.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
14
vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
generated
vendored
14
vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
generated
vendored
|
|
@ -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()),
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
162
vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
generated
vendored
162
vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
generated
vendored
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
7
vendor/github.com/golang/snappy/README
generated
vendored
7
vendor/github.com/golang/snappy/README
generated
vendored
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
4
vendor/github.com/golang/snappy/encode_arm64.s
generated
vendored
4
vendor/github.com/golang/snappy/encode_arm64.s
generated
vendored
|
|
@ -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
20
vendor/github.com/gorilla/mux/.editorconfig
generated
vendored
Normal 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
1
vendor/github.com/gorilla/mux/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
coverage.coverprofile
|
||||||
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
8
vendor/github.com/gorilla/mux/AUTHORS
generated
vendored
|
|
@ -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)
|
|
||||||
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
2
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
|
|
@ -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
34
vendor/github.com/gorilla/mux/Makefile
generated
vendored
Normal 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 ./...
|
||||||
55
vendor/github.com/gorilla/mux/README.md
generated
vendored
55
vendor/github.com/gorilla/mux/README.md
generated
vendored
|
|
@ -1,12 +1,12 @@
|
||||||
# gorilla/mux
|
# gorilla/mux
|
||||||
|
|
||||||
[](https://godoc.org/github.com/gorilla/mux)
|

|
||||||
[](https://circleci.com/gh/gorilla/mux)
|
[](https://codecov.io/github/gorilla/mux)
|
||||||
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
[](https://godoc.org/github.com/gorilla/mux)
|
||||||
|
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
https://www.gorillatoolkit.org/pkg/mux
|

|
||||||
|
|
||||||
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
11
vendor/github.com/gorilla/mux/doc.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
2
vendor/github.com/gorilla/mux/mux.go
generated
vendored
2
vendor/github.com/gorilla/mux/mux.go
generated
vendored
|
|
@ -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.
|
||||||
|
|
|
||||||
10
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
10
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
|
|
@ -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 {
|
||||||
|
|
|
||||||
45
vendor/github.com/gorilla/mux/route.go
generated
vendored
45
vendor/github.com/gorilla/mux/route.go
generated
vendored
|
|
@ -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) {
|
||||||
|
|
|
||||||
25
vendor/github.com/pquerna/otp/hotp/hotp.go
generated
vendored
25
vendor/github.com/pquerna/otp/hotp/hotp.go
generated
vendored
|
|
@ -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
27
vendor/github.com/pquerna/otp/otp.go
generated
vendored
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
|
||||||
7
vendor/github.com/pquerna/otp/totp/totp.go
generated
vendored
7
vendor/github.com/pquerna/otp/totp/totp.go
generated
vendored
|
|
@ -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
2
vendor/go.etcd.io/bbolt/.gitignore
generated
vendored
|
|
@ -6,5 +6,7 @@ cover.out
|
||||||
cover-*.out
|
cover-*.out
|
||||||
/.idea
|
/.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
/bbolt
|
||||||
/cmd/bbolt/bbolt
|
/cmd/bbolt/bbolt
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
|
||||||
2
vendor/go.etcd.io/bbolt/.go-version
generated
vendored
2
vendor/go.etcd.io/bbolt/.go-version
generated
vendored
|
|
@ -1 +1 @@
|
||||||
1.22.6
|
1.23.12
|
||||||
|
|
|
||||||
60
vendor/go.etcd.io/bbolt/Makefile
generated
vendored
60
vendor/go.etcd.io/bbolt/Makefile
generated
vendored
|
|
@ -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
10
vendor/go.etcd.io/bbolt/OWNERS
generated
vendored
Normal 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
68
vendor/go.etcd.io/bbolt/README.md
generated
vendored
|
|
@ -1,10 +1,8 @@
|
||||||
bbolt
|
bbolt
|
||||||
=====
|
=====
|
||||||
|
|
||||||
[](https://goreportcard.com/report/github.com/etcd-io/bbolt)
|
[](https://goreportcard.com/report/go.etcd.io/bbolt)
|
||||||
[](https://codecov.io/gh/etcd-io/bbolt)
|
[](https://pkg.go.dev/go.etcd.io/bbolt)
|
||||||
[](https://travis-ci.com/etcd-io/bbolt)
|
|
||||||
[](https://godoc.org/github.com/etcd-io/bbolt)
|
|
||||||
[](https://github.com/etcd-io/bbolt/releases)
|
[](https://github.com/etcd-io/bbolt/releases)
|
||||||
[](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
|
[](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.
|
||||||
|
|
|
||||||
7
vendor/go.etcd.io/bbolt/bolt_386.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_386.go
generated
vendored
|
|
@ -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
|
|
||||||
6
vendor/go.etcd.io/bbolt/bolt_unix_solaris.go → vendor/go.etcd.io/bbolt/bolt_aix.go
generated
vendored
6
vendor/go.etcd.io/bbolt/bolt_unix_solaris.go → vendor/go.etcd.io/bbolt/bolt_aix.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
7
vendor/go.etcd.io/bbolt/bolt_amd64.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_amd64.go
generated
vendored
|
|
@ -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
92
vendor/go.etcd.io/bbolt/bolt_android.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
7
vendor/go.etcd.io/bbolt/bolt_arm.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_arm.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_arm64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_arm64.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_loong64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_loong64.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_mips64x.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_mips64x.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_mipsx.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_mipsx.go
generated
vendored
|
|
@ -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
10
vendor/go.etcd.io/bbolt/bolt_ppc.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_ppc64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_ppc64.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_ppc64le.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_ppc64le.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_riscv64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_riscv64.go
generated
vendored
|
|
@ -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
|
|
||||||
10
vendor/go.etcd.io/bbolt/bolt_s390x.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_s390x.go
generated
vendored
|
|
@ -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
|
|
||||||
7
vendor/go.etcd.io/bbolt/bolt_unix_aix.go → vendor/go.etcd.io/bbolt/bolt_solaris.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_unix_aix.go → vendor/go.etcd.io/bbolt/bolt_solaris.go
generated
vendored
|
|
@ -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
10
vendor/go.etcd.io/bbolt/bolt_unix.go
generated
vendored
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
vendor/go.etcd.io/bbolt/bolt_windows.go
generated
vendored
9
vendor/go.etcd.io/bbolt/bolt_windows.go
generated
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
1
vendor/go.etcd.io/bbolt/boltsync_unix.go
generated
vendored
1
vendor/go.etcd.io/bbolt/boltsync_unix.go
generated
vendored
|
|
@ -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
484
vendor/go.etcd.io/bbolt/bucket.go
generated
vendored
|
|
@ -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
99
vendor/go.etcd.io/bbolt/cursor.go
generated
vendored
|
|
@ -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
467
vendor/go.etcd.io/bbolt/db.go
generated
vendored
|
|
@ -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
76
vendor/go.etcd.io/bbolt/errors.go
generated
vendored
|
|
@ -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
84
vendor/go.etcd.io/bbolt/errors/errors.go
generated
vendored
Normal 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
410
vendor/go.etcd.io/bbolt/freelist.go
generated
vendored
|
|
@ -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)
|
|
||||||
}
|
|
||||||
178
vendor/go.etcd.io/bbolt/freelist_hmap.go
generated
vendored
178
vendor/go.etcd.io/bbolt/freelist_hmap.go
generated
vendored
|
|
@ -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
7
vendor/go.etcd.io/bbolt/internal/common/bolt_386.go
generated
vendored
Normal 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
|
||||||
7
vendor/go.etcd.io/bbolt/internal/common/bolt_amd64.go
generated
vendored
Normal file
7
vendor/go.etcd.io/bbolt/internal/common/bolt_amd64.go
generated
vendored
Normal 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
7
vendor/go.etcd.io/bbolt/internal/common/bolt_arm.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_arm64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_arm64.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_loong64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_loong64.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mips64x.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mips64x.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mipsx.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mipsx.go
generated
vendored
Normal 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
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64le.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64le.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_riscv64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_riscv64.go
generated
vendored
Normal 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
|
||||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_s390x.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_s390x.go
generated
vendored
Normal 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
54
vendor/go.etcd.io/bbolt/internal/common/bucket.go
generated
vendored
Normal 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
Loading…
Add table
Add a link
Reference in a new issue