mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 16:36:41 +00:00 
			
		
		
		
	Integrate public as bindata optionally (#293)
* Dropped unused codekit config * Integrated dynamic and static bindata for public * Ignore public bindata * Add a general generate make task * Integrated flexible public assets into web command * Updated vendoring, added all missiong govendor deps * Made the linter happy with the bindata and dynamic code * Moved public bindata definition to modules directory * Ignoring the new bindata path now * Updated to the new public modules import path * Updated public bindata command and drop the new prefix
This commit is contained in:
		
				
					committed by
					
						 Lunny Xiao
						Lunny Xiao
					
				
			
			
				
	
			
			
			
						parent
						
							4680c349dd
						
					
				
				
					commit
					b6a95a8cb3
				
			
							
								
								
									
										20
									
								
								vendor/github.com/boltdb/bolt/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/boltdb/bolt/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2013 Ben Johnson | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										18
									
								
								vendor/github.com/boltdb/bolt/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/boltdb/bolt/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| BRANCH=`git rev-parse --abbrev-ref HEAD` | ||||
| COMMIT=`git rev-parse --short HEAD` | ||||
| GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" | ||||
|  | ||||
| default: build | ||||
|  | ||||
| race: | ||||
| 	@go test -v -race -test.run="TestSimulate_(100op|1000op)" | ||||
|  | ||||
| # go get github.com/kisielk/errcheck | ||||
| errcheck: | ||||
| 	@errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt | ||||
|  | ||||
| test:  | ||||
| 	@go test -v -cover . | ||||
| 	@go test -v ./cmd/bolt | ||||
|  | ||||
| .PHONY: fmt test | ||||
							
								
								
									
										848
									
								
								vendor/github.com/boltdb/bolt/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										848
									
								
								vendor/github.com/boltdb/bolt/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,848 @@ | ||||
| Bolt [](https://coveralls.io/r/boltdb/bolt?branch=master) [](https://godoc.org/github.com/boltdb/bolt)  | ||||
| ==== | ||||
|  | ||||
| Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] | ||||
| [LMDB project][lmdb]. The goal of the project is to provide a simple, | ||||
| fast, and reliable database for projects that don't require a full database | ||||
| server such as Postgres or MySQL. | ||||
|  | ||||
| Since Bolt is meant to be used as such a low-level piece of functionality, | ||||
| simplicity is key. The API will be small and only focus on getting values | ||||
| and setting values. That's it. | ||||
|  | ||||
| [hyc_symas]: https://twitter.com/hyc_symas | ||||
| [lmdb]: http://symas.com/mdb/ | ||||
|  | ||||
| ## Project Status | ||||
|  | ||||
| Bolt is stable and the API is fixed. Full unit test coverage and randomized | ||||
| black box testing are used to ensure database consistency and thread safety. | ||||
| Bolt is currently in high-load production environments serving databases as | ||||
| large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed | ||||
| services every day. | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| - [Getting Started](#getting-started) | ||||
|   - [Installing](#installing) | ||||
|   - [Opening a database](#opening-a-database) | ||||
|   - [Transactions](#transactions) | ||||
|     - [Read-write transactions](#read-write-transactions) | ||||
|     - [Read-only transactions](#read-only-transactions) | ||||
|     - [Batch read-write transactions](#batch-read-write-transactions) | ||||
|     - [Managing transactions manually](#managing-transactions-manually) | ||||
|   - [Using buckets](#using-buckets) | ||||
|   - [Using key/value pairs](#using-keyvalue-pairs) | ||||
|   - [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket) | ||||
|   - [Iterating over keys](#iterating-over-keys) | ||||
|     - [Prefix scans](#prefix-scans) | ||||
|     - [Range scans](#range-scans) | ||||
|     - [ForEach()](#foreach) | ||||
|   - [Nested buckets](#nested-buckets) | ||||
|   - [Database backups](#database-backups) | ||||
|   - [Statistics](#statistics) | ||||
|   - [Read-Only Mode](#read-only-mode) | ||||
|   - [Mobile Use (iOS/Android)](#mobile-use-iosandroid) | ||||
| - [Resources](#resources) | ||||
| - [Comparison with other databases](#comparison-with-other-databases) | ||||
|   - [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases) | ||||
|   - [LevelDB, RocksDB](#leveldb-rocksdb) | ||||
|   - [LMDB](#lmdb) | ||||
| - [Caveats & Limitations](#caveats--limitations) | ||||
| - [Reading the Source](#reading-the-source) | ||||
| - [Other Projects Using Bolt](#other-projects-using-bolt) | ||||
|  | ||||
| ## Getting Started | ||||
|  | ||||
| ### Installing | ||||
|  | ||||
| To start using Bolt, install Go and run `go get`: | ||||
|  | ||||
| ```sh | ||||
| $ go get github.com/boltdb/bolt/... | ||||
| ``` | ||||
|  | ||||
| This will retrieve the library and install the `bolt` command line utility into | ||||
| your `$GOBIN` path. | ||||
|  | ||||
|  | ||||
| ### Opening a database | ||||
|  | ||||
| The top-level object in Bolt is a `DB`. It is represented as a single file on | ||||
| your disk and represents a consistent snapshot of your data. | ||||
|  | ||||
| To open your database, simply use the `bolt.Open()` function: | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/boltdb/bolt" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Open the my.db data file in your current directory. | ||||
| 	// It will be created if it doesn't exist. | ||||
| 	db, err := bolt.Open("my.db", 0600, nil) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
|  | ||||
| 	... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Please note that Bolt obtains a file lock on the data file so multiple processes | ||||
| cannot open the same database at the same time. Opening an already open Bolt | ||||
| database will cause it to hang until the other process closes it. To prevent | ||||
| an indefinite wait you can pass a timeout option to the `Open()` function: | ||||
|  | ||||
| ```go | ||||
| db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second}) | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### Transactions | ||||
|  | ||||
| Bolt allows only one read-write transaction at a time but allows as many | ||||
| read-only transactions as you want at a time. Each transaction has a consistent | ||||
| view of the data as it existed when the transaction started. | ||||
|  | ||||
| Individual transactions and all objects created from them (e.g. buckets, keys) | ||||
| are not thread safe. To work with data in multiple goroutines you must start | ||||
| a transaction for each one or use locking to ensure only one goroutine accesses | ||||
| a transaction at a time. Creating transaction from the `DB` is thread safe. | ||||
|  | ||||
| Read-only transactions and read-write transactions should not depend on one | ||||
| another and generally shouldn't be opened simultaneously in the same goroutine. | ||||
| This can cause a deadlock as the read-write transaction needs to periodically | ||||
| re-map the data file but it cannot do so while a read-only transaction is open. | ||||
|  | ||||
|  | ||||
| #### Read-write transactions | ||||
|  | ||||
| To start a read-write transaction, you can use the `DB.Update()` function: | ||||
|  | ||||
| ```go | ||||
| err := db.Update(func(tx *bolt.Tx) error { | ||||
| 	... | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| Inside the closure, you have a consistent view of the database. You commit the | ||||
| transaction by returning `nil` at the end. You can also rollback the transaction | ||||
| at any point by returning an error. All database operations are allowed inside | ||||
| a read-write transaction. | ||||
|  | ||||
| Always check the return error as it will report any disk failures that can cause | ||||
| your transaction to not complete. If you return an error within your closure | ||||
| it will be passed through. | ||||
|  | ||||
|  | ||||
| #### Read-only transactions | ||||
|  | ||||
| To start a read-only transaction, you can use the `DB.View()` function: | ||||
|  | ||||
| ```go | ||||
| err := db.View(func(tx *bolt.Tx) error { | ||||
| 	... | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| You also get a consistent view of the database within this closure, however, | ||||
| no mutating operations are allowed within a read-only transaction. You can only | ||||
| retrieve buckets, retrieve values, and copy the database within a read-only | ||||
| transaction. | ||||
|  | ||||
|  | ||||
| #### Batch read-write transactions | ||||
|  | ||||
| Each `DB.Update()` waits for disk to commit the writes. This overhead | ||||
| can be minimized by combining multiple updates with the `DB.Batch()` | ||||
| function: | ||||
|  | ||||
| ```go | ||||
| err := db.Batch(func(tx *bolt.Tx) error { | ||||
| 	... | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| Concurrent Batch calls are opportunistically combined into larger | ||||
| transactions. Batch is only useful when there are multiple goroutines | ||||
| calling it. | ||||
|  | ||||
| The trade-off is that `Batch` can call the given | ||||
| function multiple times, if parts of the transaction fail. The | ||||
| function must be idempotent and side effects must take effect only | ||||
| after a successful return from `DB.Batch()`. | ||||
|  | ||||
| For example: don't display messages from inside the function, instead | ||||
| set variables in the enclosing scope: | ||||
|  | ||||
| ```go | ||||
| var id uint64 | ||||
| err := db.Batch(func(tx *bolt.Tx) error { | ||||
| 	// Find last key in bucket, decode as bigendian uint64, increment | ||||
| 	// by one, encode back to []byte, and add new key. | ||||
| 	... | ||||
| 	id = newValue | ||||
| 	return nil | ||||
| }) | ||||
| if err != nil { | ||||
| 	return ... | ||||
| } | ||||
| fmt.Println("Allocated ID %d", id) | ||||
| ``` | ||||
|  | ||||
|  | ||||
| #### Managing transactions manually | ||||
|  | ||||
| The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()` | ||||
| function. These helper functions will start the transaction, execute a function, | ||||
| and then safely close your transaction if an error is returned. This is the | ||||
| recommended way to use Bolt transactions. | ||||
|  | ||||
| However, sometimes you may want to manually start and end your transactions. | ||||
| You can use the `Tx.Begin()` function directly but **please** be sure to close | ||||
| the transaction. | ||||
|  | ||||
| ```go | ||||
| // Start a writable transaction. | ||||
| tx, err := db.Begin(true) | ||||
| if err != nil { | ||||
|     return err | ||||
| } | ||||
| defer tx.Rollback() | ||||
|  | ||||
| // Use the transaction... | ||||
| _, err := tx.CreateBucket([]byte("MyBucket")) | ||||
| if err != nil { | ||||
|     return err | ||||
| } | ||||
|  | ||||
| // Commit the transaction and check for error. | ||||
| if err := tx.Commit(); err != nil { | ||||
|     return err | ||||
| } | ||||
| ``` | ||||
|  | ||||
| The first argument to `DB.Begin()` is a boolean stating if the transaction | ||||
| should be writable. | ||||
|  | ||||
|  | ||||
| ### Using buckets | ||||
|  | ||||
| Buckets are collections of key/value pairs within the database. All keys in a | ||||
| bucket must be unique. You can create a bucket using the `DB.CreateBucket()` | ||||
| function: | ||||
|  | ||||
| ```go | ||||
| db.Update(func(tx *bolt.Tx) error { | ||||
| 	b, err := tx.CreateBucket([]byte("MyBucket")) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("create bucket: %s", err) | ||||
| 	} | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| 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 | ||||
| function for all your top-level buckets after you open your database so you can | ||||
| guarantee that they exist for future transactions. | ||||
|  | ||||
| To delete a bucket, simply call the `Tx.DeleteBucket()` function. | ||||
|  | ||||
|  | ||||
| ### Using key/value pairs | ||||
|  | ||||
| To save a key/value pair to a bucket, use the `Bucket.Put()` function: | ||||
|  | ||||
| ```go | ||||
| db.Update(func(tx *bolt.Tx) error { | ||||
| 	b := tx.Bucket([]byte("MyBucket")) | ||||
| 	err := b.Put([]byte("answer"), []byte("42")) | ||||
| 	return err | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| This will set the value of the `"answer"` key to `"42"` in the `MyBucket` | ||||
| bucket. To retrieve this value, we can use the `Bucket.Get()` function: | ||||
|  | ||||
| ```go | ||||
| db.View(func(tx *bolt.Tx) error { | ||||
| 	b := tx.Bucket([]byte("MyBucket")) | ||||
| 	v := b.Get([]byte("answer")) | ||||
| 	fmt.Printf("The answer is: %s\n", v) | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| The `Get()` function does not return an error because its operation is | ||||
| guaranteed to work (unless there is some kind of system failure). If the key | ||||
| 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 | ||||
| set to a key which is different than the key not existing. | ||||
|  | ||||
| Use the `Bucket.Delete()` function to delete a key from the bucket. | ||||
|  | ||||
| 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 | ||||
| then you must use `copy()` to copy it to another byte slice. | ||||
|  | ||||
|  | ||||
| ### Autoincrementing integer for the bucket | ||||
| By using the `NextSequence()` function, you can let Bolt determine a sequence | ||||
| which can be used as the unique identifier for your key/value pairs. See the | ||||
| example below. | ||||
|  | ||||
| ```go | ||||
| // CreateUser saves u to the store. The new user ID is set on u once the data is persisted. | ||||
| func (s *Store) CreateUser(u *User) error { | ||||
|     return s.db.Update(func(tx *bolt.Tx) error { | ||||
|         // Retrieve the users bucket. | ||||
|         // This should be created when the DB is first opened. | ||||
|         b := tx.Bucket([]byte("users")) | ||||
|  | ||||
|         // Generate ID for the user. | ||||
|         // This returns an error only if the Tx is closed or not writeable. | ||||
|         // That can't happen in an Update() call so I ignore the error check. | ||||
|         id, _ = b.NextSequence() | ||||
|         u.ID = int(id) | ||||
|  | ||||
|         // Marshal user data into bytes. | ||||
|         buf, err := json.Marshal(u) | ||||
|         if err != nil { | ||||
|             return err | ||||
|         } | ||||
|  | ||||
|         // Persist bytes to users bucket. | ||||
|         return b.Put(itob(u.ID), buf) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| // itob returns an 8-byte big endian representation of v. | ||||
| func itob(v int) []byte { | ||||
|     b := make([]byte, 8) | ||||
|     binary.BigEndian.PutUint64(b, uint64(v)) | ||||
|     return b | ||||
| } | ||||
|  | ||||
| type User struct { | ||||
|     ID int | ||||
|     ... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Iterating over keys | ||||
|  | ||||
| Bolt stores its keys in byte-sorted order within a bucket. This makes sequential | ||||
| iteration over these keys extremely fast. To iterate over keys we'll use a | ||||
| `Cursor`: | ||||
|  | ||||
| ```go | ||||
| db.View(func(tx *bolt.Tx) error { | ||||
| 	// Assume bucket exists and has keys | ||||
| 	b := tx.Bucket([]byte("MyBucket")) | ||||
|  | ||||
| 	c := b.Cursor() | ||||
|  | ||||
| 	for k, v := c.First(); k != nil; k, v = c.Next() { | ||||
| 		fmt.Printf("key=%s, value=%s\n", k, v) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| The cursor allows you to move to a specific point in the list of keys and move | ||||
| forward or backward through the keys one at a time. | ||||
|  | ||||
| The following functions are available on the cursor: | ||||
|  | ||||
| ``` | ||||
| First()  Move to the first key. | ||||
| Last()   Move to the last key. | ||||
| Seek()   Move to a specific key. | ||||
| Next()   Move to the next key. | ||||
| Prev()   Move to the previous key. | ||||
| ``` | ||||
|  | ||||
| Each of those functions has a return signature of `(key []byte, value []byte)`. | ||||
| When you have iterated to the end of the cursor then `Next()` will return a | ||||
| `nil` key.  You must seek to a position using `First()`, `Last()`, or `Seek()` | ||||
| before calling `Next()` or `Prev()`. If you do not seek to a position then | ||||
| these functions will return a `nil` key. | ||||
|  | ||||
| During iteration, if the key is non-`nil` but the value is `nil`, that means | ||||
| the key refers to a bucket rather than a value.  Use `Bucket.Bucket()` to | ||||
| access the sub-bucket. | ||||
|  | ||||
|  | ||||
| #### Prefix scans | ||||
|  | ||||
| To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`: | ||||
|  | ||||
| ```go | ||||
| db.View(func(tx *bolt.Tx) error { | ||||
| 	// Assume bucket exists and has keys | ||||
| 	c := tx.Bucket([]byte("MyBucket")).Cursor() | ||||
|  | ||||
| 	prefix := []byte("1234") | ||||
| 	for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { | ||||
| 		fmt.Printf("key=%s, value=%s\n", k, v) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| #### Range scans | ||||
|  | ||||
| Another common use case is scanning over a range such as a time range. If you | ||||
| use a sortable time encoding such as RFC3339 then you can query a specific | ||||
| date range like this: | ||||
|  | ||||
| ```go | ||||
| db.View(func(tx *bolt.Tx) error { | ||||
| 	// Assume our events bucket exists and has RFC3339 encoded time keys. | ||||
| 	c := tx.Bucket([]byte("Events")).Cursor() | ||||
|  | ||||
| 	// Our time range spans the 90's decade. | ||||
| 	min := []byte("1990-01-01T00:00:00Z") | ||||
| 	max := []byte("2000-01-01T00:00:00Z") | ||||
|  | ||||
| 	// Iterate over the 90's. | ||||
| 	for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { | ||||
| 		fmt.Printf("%s: %s\n", k, v) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable. | ||||
|  | ||||
|  | ||||
| #### ForEach() | ||||
|  | ||||
| You can also use the function `ForEach()` if you know you'll be iterating over | ||||
| all the keys in a bucket: | ||||
|  | ||||
| ```go | ||||
| db.View(func(tx *bolt.Tx) error { | ||||
| 	// Assume bucket exists and has keys | ||||
| 	b := tx.Bucket([]byte("MyBucket")) | ||||
|  | ||||
| 	b.ForEach(func(k, v []byte) error { | ||||
| 		fmt.Printf("key=%s, value=%s\n", k, v) | ||||
| 		return nil | ||||
| 	}) | ||||
| 	return nil | ||||
| }) | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### Nested buckets | ||||
|  | ||||
| You can also store a bucket in a key to create nested buckets. The API is the | ||||
| same as the bucket management API on the `DB` object: | ||||
|  | ||||
| ```go | ||||
| func (*Bucket) CreateBucket(key []byte) (*Bucket, error) | ||||
| func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) | ||||
| func (*Bucket) DeleteBucket(key []byte) error | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### Database backups | ||||
|  | ||||
| Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()` | ||||
| function to write a consistent view of the database to a writer. If you call | ||||
| this from a read-only transaction, it will perform a hot backup and not block | ||||
| your other database reads and writes. | ||||
|  | ||||
| By default, it will use a regular file handle which will utilize the operating | ||||
| system's page cache. See the [`Tx`](https://godoc.org/github.com/boltdb/bolt#Tx) | ||||
| documentation for information about optimizing for larger-than-RAM datasets. | ||||
|  | ||||
| One common use case is to backup over HTTP so you can use tools like `cURL` to | ||||
| do database backups: | ||||
|  | ||||
| ```go | ||||
| func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { | ||||
| 	err := db.View(func(tx *bolt.Tx) error { | ||||
| 		w.Header().Set("Content-Type", "application/octet-stream") | ||||
| 		w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) | ||||
| 		w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) | ||||
| 		_, err := tx.WriteTo(w) | ||||
| 		return err | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), http.StatusInternalServerError) | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Then you can backup using this command: | ||||
|  | ||||
| ```sh | ||||
| $ curl http://localhost/backup > my.db | ||||
| ``` | ||||
|  | ||||
| Or you can open your browser to `http://localhost/backup` and it will download | ||||
| automatically. | ||||
|  | ||||
| If you want to backup to another file you can use the `Tx.CopyFile()` helper | ||||
| function. | ||||
|  | ||||
|  | ||||
| ### Statistics | ||||
|  | ||||
| The database keeps a running count of many of the internal operations it | ||||
| performs so you can better understand what's going on. By grabbing a snapshot | ||||
| of these stats at two points in time we can see what operations were performed | ||||
| in that time range. | ||||
|  | ||||
| For example, we could start a goroutine to log stats every 10 seconds: | ||||
|  | ||||
| ```go | ||||
| go func() { | ||||
| 	// Grab the initial stats. | ||||
| 	prev := db.Stats() | ||||
|  | ||||
| 	for { | ||||
| 		// Wait for 10s. | ||||
| 		time.Sleep(10 * time.Second) | ||||
|  | ||||
| 		// Grab the current stats and diff them. | ||||
| 		stats := db.Stats() | ||||
| 		diff := stats.Sub(&prev) | ||||
|  | ||||
| 		// Encode stats to JSON and print to STDERR. | ||||
| 		json.NewEncoder(os.Stderr).Encode(diff) | ||||
|  | ||||
| 		// Save stats for the next loop. | ||||
| 		prev = stats | ||||
| 	} | ||||
| }() | ||||
| ``` | ||||
|  | ||||
| It's also useful to pipe these stats to a service such as statsd for monitoring | ||||
| or to provide an HTTP endpoint that will perform a fixed-length sample. | ||||
|  | ||||
|  | ||||
| ### Read-Only Mode | ||||
|  | ||||
| Sometimes it is useful to create a shared, read-only Bolt database. To this, | ||||
| set the `Options.ReadOnly` flag when opening your database. Read-only mode | ||||
| 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. | ||||
|  | ||||
| ```go | ||||
| db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true}) | ||||
| if err != nil { | ||||
| 	log.Fatal(err) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Mobile Use (iOS/Android) | ||||
|  | ||||
| Bolt is able to run on mobile devices by leveraging the binding feature of the | ||||
| [gomobile](https://github.com/golang/mobile) tool. Create a struct that will | ||||
| contain your database logic and a reference to a `*bolt.DB` with a initializing | ||||
| contstructor that takes in a filepath where the database file will be stored. | ||||
| Neither Android nor iOS require extra permissions or cleanup from using this method. | ||||
|  | ||||
| ```go | ||||
| func NewBoltDB(filepath string) *BoltDB { | ||||
| 	db, err := bolt.Open(filepath+"/demo.db", 0600, nil) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	return &BoltDB{db} | ||||
| } | ||||
|  | ||||
| type BoltDB struct { | ||||
| 	db *bolt.DB | ||||
| 	... | ||||
| } | ||||
|  | ||||
| func (b *BoltDB) Path() string { | ||||
| 	return b.db.Path() | ||||
| } | ||||
|  | ||||
| func (b *BoltDB) Close() { | ||||
| 	b.db.Close() | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Database logic should be defined as methods on this wrapper struct. | ||||
|  | ||||
| To initialize this struct from the native language (both platforms now sync | ||||
| their local storage to the cloud. These snippets disable that functionality for the | ||||
| database file): | ||||
|  | ||||
| #### Android | ||||
|  | ||||
| ```java | ||||
| String path; | ||||
| if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){ | ||||
|     path = getNoBackupFilesDir().getAbsolutePath(); | ||||
| } else{ | ||||
|     path = getFilesDir().getAbsolutePath(); | ||||
| } | ||||
| Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path) | ||||
| ``` | ||||
|  | ||||
| #### iOS | ||||
|  | ||||
| ```objc | ||||
| - (void)demo { | ||||
|     NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, | ||||
|                                                           NSUserDomainMask, | ||||
|                                                           YES) objectAtIndex:0]; | ||||
| 	GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path); | ||||
| 	[self addSkipBackupAttributeToItemAtPath:demo.path]; | ||||
| 	//Some DB Logic would go here | ||||
| 	[demo close]; | ||||
| } | ||||
|  | ||||
| - (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString | ||||
| { | ||||
|     NSURL* URL= [NSURL fileURLWithPath: filePathString]; | ||||
|     assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); | ||||
|  | ||||
|     NSError *error = nil; | ||||
|     BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] | ||||
|                                   forKey: NSURLIsExcludedFromBackupKey error: &error]; | ||||
|     if(!success){ | ||||
|         NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); | ||||
|     } | ||||
|     return success; | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ## Resources | ||||
|  | ||||
| For more information on getting started with Bolt, check out the following articles: | ||||
|  | ||||
| * [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch). | ||||
| * [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville | ||||
|  | ||||
|  | ||||
| ## Comparison with other databases | ||||
|  | ||||
| ### Postgres, MySQL, & other relational databases | ||||
|  | ||||
| Relational databases structure data into rows and are only accessible through | ||||
| the use of SQL. This approach provides flexibility in how you store and query | ||||
| your data but also incurs overhead in parsing and planning SQL statements. Bolt | ||||
| accesses all data by a byte slice key. This makes Bolt fast to read and write | ||||
| data by key but provides no built-in support for joining values together. | ||||
|  | ||||
| Most relational databases (with the exception of SQLite) are standalone servers | ||||
| that run separately from your application. This gives your systems | ||||
| flexibility to connect multiple application servers to a single database | ||||
| server but also adds overhead in serializing and transporting data over the | ||||
| network. Bolt runs as a library included in your application so all data access | ||||
| has to go through your application's process. This brings data closer to your | ||||
| application but limits multi-process access to the data. | ||||
|  | ||||
|  | ||||
| ### LevelDB, RocksDB | ||||
|  | ||||
| LevelDB and its derivatives (RocksDB, HyperLevelDB) are similar to Bolt in that | ||||
| they are libraries bundled into the application, however, their underlying | ||||
| structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes | ||||
| random writes by using a write ahead log and multi-tiered, sorted files called | ||||
| SSTables. Bolt uses a B+tree internally and only a single file. Both approaches | ||||
| have trade-offs. | ||||
|  | ||||
| If you require a high random write throughput (>10,000 w/sec) or you need to use | ||||
| spinning disks then LevelDB could be a good choice. If your application is | ||||
| read-heavy or does a lot of range scans then Bolt could be a good choice. | ||||
|  | ||||
| One other important consideration is that LevelDB does not have transactions. | ||||
| It supports batch writing of key/values pairs and it supports read snapshots | ||||
| but it will not give you the ability to do a compare-and-swap operation safely. | ||||
| Bolt supports fully serializable ACID transactions. | ||||
|  | ||||
|  | ||||
| ### LMDB | ||||
|  | ||||
| Bolt was originally a port of LMDB so it is architecturally similar. Both use | ||||
| a B+tree, have ACID semantics with fully serializable transactions, and support | ||||
| lock-free MVCC using a single writer and multiple readers. | ||||
|  | ||||
| The two projects have somewhat diverged. LMDB heavily focuses on raw performance | ||||
| while Bolt has focused on simplicity and ease of use. For example, LMDB allows | ||||
| several unsafe actions such as direct writes for the sake of performance. Bolt | ||||
| opts to disallow actions which can leave the database in a corrupted state. The | ||||
| only exception to this in Bolt is `DB.NoSync`. | ||||
|  | ||||
| There are also a few differences in API. LMDB requires a maximum mmap size when | ||||
| opening an `mdb_env` whereas Bolt will handle incremental mmap resizing | ||||
| automatically. LMDB overloads the getter and setter functions with multiple | ||||
| flags whereas Bolt splits these specialized cases into their own functions. | ||||
|  | ||||
|  | ||||
| ## Caveats & Limitations | ||||
|  | ||||
| It's important to pick the right tool for the job and Bolt is no exception. | ||||
| Here are a few things to note when evaluating and using Bolt: | ||||
|  | ||||
| * Bolt is good for read intensive workloads. Sequential write performance is | ||||
|   also fast but random writes can be slow. You can use `DB.Batch()` or add a | ||||
|   write-ahead log to help mitigate this issue. | ||||
|  | ||||
| * Bolt uses a B+tree internally so there can be a lot of random page access. | ||||
|   SSDs provide a significant performance boost over spinning disks. | ||||
|  | ||||
| * Try to avoid long running read transactions. Bolt uses copy-on-write so | ||||
|   old pages cannot be reclaimed while an old transaction is using them. | ||||
|  | ||||
| * Byte slices returned from Bolt are only valid during a transaction. Once the | ||||
|   transaction has been committed or rolled back then the memory they point to | ||||
|   can be reused by a new page or can be unmapped from virtual memory and you'll | ||||
|   see an `unexpected fault address` panic when accessing it. | ||||
|  | ||||
| * Be careful when using `Bucket.FillPercent`. Setting a high fill percent for | ||||
|   buckets that have random inserts will cause your database to have very poor | ||||
|   page utilization. | ||||
|  | ||||
| * Use larger buckets in general. Smaller buckets causes poor page utilization | ||||
|   once they become larger than the page size (typically 4KB). | ||||
|  | ||||
| * Bulk loading a lot of random writes into a new bucket can be slow as the | ||||
|   page will not split until the transaction is committed. Randomly inserting | ||||
|   more than 100,000 key/value pairs into a single new bucket in a single | ||||
|   transaction is not advised. | ||||
|  | ||||
| * Bolt uses a memory-mapped file so the underlying operating system handles the | ||||
|   caching of the data. Typically, the OS will cache as much of the file as it | ||||
|   can in memory and will release memory as needed to other processes. This means | ||||
|   that Bolt can show very high memory usage when working with large databases. | ||||
|   However, this is expected and the OS will release memory as needed. Bolt can | ||||
|   handle databases much larger than the available physical RAM, provided its | ||||
|   memory-map fits in the process virtual address space. It may be problematic | ||||
|   on 32-bits systems. | ||||
|  | ||||
| * The data structures in the Bolt database are memory mapped so the data file | ||||
|   will be endian specific. This means that you cannot copy a Bolt file from a | ||||
|   little endian machine to a big endian machine and have it work. For most | ||||
|   users this is not a concern since most modern CPUs are little endian. | ||||
|  | ||||
| * Because of the way pages are laid out on disk, Bolt cannot truncate data files | ||||
|   and return free pages back to the disk. Instead, Bolt maintains a free list | ||||
|   of unused pages within its data file. These free pages can be reused by later | ||||
|   transactions. This works well for many use cases as databases generally tend | ||||
|   to grow. However, it's important to note that deleting large chunks of data | ||||
|   will not allow you to reclaim that space on disk. | ||||
|  | ||||
|   For more information on page allocation, [see this comment][page-allocation]. | ||||
|  | ||||
| [page-allocation]: https://github.com/boltdb/bolt/issues/308#issuecomment-74811638 | ||||
|  | ||||
|  | ||||
| ## Reading the Source | ||||
|  | ||||
| Bolt is a relatively small code base (<3KLOC) for an embedded, serializable, | ||||
| transactional key/value database so it can be a good starting point for people | ||||
| interested in how databases work. | ||||
|  | ||||
| The best places to start are the main entry points into Bolt: | ||||
|  | ||||
| - `Open()` - Initializes the reference to the database. It's responsible for | ||||
|   creating the database if it doesn't exist, obtaining an exclusive lock on the | ||||
|   file, reading the meta pages, & memory-mapping the file. | ||||
|  | ||||
| - `DB.Begin()` - Starts a read-only or read-write transaction depending on the | ||||
|   value of the `writable` argument. This requires briefly obtaining the "meta" | ||||
|   lock to keep track of open transactions. Only one read-write transaction can | ||||
|   exist at a time so the "rwlock" is acquired during the life of a read-write | ||||
|   transaction. | ||||
|  | ||||
| - `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 | ||||
|   where they 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 | ||||
|   "nodes". These nodes are where mutations occur during read-write transactions. | ||||
|   These changes get flushed to disk during commit. | ||||
|  | ||||
| - `Bucket.Get()` - Retrieves a key/value pair from a bucket. This uses a cursor | ||||
|   to move to the page & position of a key/value pair. During a read-only | ||||
|   transaction, the key and value data is returned as a direct reference to the | ||||
|   underlying mmap file so there's no allocation overhead. For read-write | ||||
|   transactions, this data may reference the mmap file or one of the in-memory | ||||
|   node values. | ||||
|  | ||||
| - `Cursor` - This object is simply for traversing the B+tree of on-disk pages | ||||
|   or in-memory nodes. It can seek to a specific key, move to the first or last | ||||
|   value, or it can move forward or backward. The cursor handles the movement up | ||||
|   and down the B+tree transparently to the end user. | ||||
|  | ||||
| - `Tx.Commit()` - Converts the in-memory dirty nodes and the list of free pages | ||||
|   into pages to be written to disk. Writing to disk then occurs in two phases. | ||||
|   First, the dirty pages are written to disk and an `fsync()` occurs. Second, a | ||||
|   new meta page with an incremented transaction ID is written and another | ||||
|   `fsync()` occurs. This two phase write ensures that partially written data | ||||
|   pages are ignored in the event of a crash since the meta page pointing to them | ||||
|   is never written. Partially written meta pages are invalidated because they | ||||
|   are written with a checksum. | ||||
|  | ||||
| If you have additional notes that could be helpful for others, please submit | ||||
| them via pull request. | ||||
|  | ||||
|  | ||||
| ## Other Projects Using Bolt | ||||
|  | ||||
| Below is a list of public, open source projects that use Bolt: | ||||
|  | ||||
| * [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard. | ||||
| * [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside. | ||||
| * [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. | ||||
| * [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics. | ||||
| * [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects. | ||||
| * [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday. | ||||
| * [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations. | ||||
| * [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite. | ||||
| * [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin". | ||||
| * [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka. | ||||
| * [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed. | ||||
| * [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt. | ||||
| * [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site. | ||||
| * [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage. | ||||
| * [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters. | ||||
| * [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. | ||||
| * [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. | ||||
| * [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. | ||||
| * [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database. | ||||
| * [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read. | ||||
| * [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics. | ||||
| * [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data. | ||||
| * [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system. | ||||
| * [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware. | ||||
| * [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs. | ||||
| * [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems. | ||||
| * [stow](https://github.com/djherbis/stow) -  a persistence manager for objects | ||||
|   backed by boltdb. | ||||
| * [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining | ||||
|   simple tx and key scans. | ||||
| * [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets. | ||||
| * [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service | ||||
| * [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. | ||||
| * [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. | ||||
| * [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. | ||||
| * [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB. | ||||
| * [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB. | ||||
|  | ||||
| If you are using Bolt in a project please send a pull request to add it to the list. | ||||
							
								
								
									
										18
									
								
								vendor/github.com/boltdb/bolt/appveyor.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/boltdb/bolt/appveyor.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| version: "{build}" | ||||
|  | ||||
| os: Windows Server 2012 R2 | ||||
|  | ||||
| clone_folder: c:\gopath\src\github.com\boltdb\bolt | ||||
|  | ||||
| environment: | ||||
|   GOPATH: c:\gopath | ||||
|  | ||||
| install: | ||||
|   - echo %PATH% | ||||
|   - echo %GOPATH% | ||||
|   - go version | ||||
|   - go env | ||||
|   - go get -v -t ./... | ||||
|  | ||||
| build_script: | ||||
|   - go test -v ./... | ||||
							
								
								
									
										7
									
								
								vendor/github.com/boltdb/bolt/bolt_386.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/boltdb/bolt/bolt_386.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_amd64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/boltdb/bolt/bolt_amd64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_arm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/boltdb/bolt/bolt_arm.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_arm64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_arm64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| // +build arm64 | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/boltdb/bolt/bolt_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| // fdatasync flushes written data to a file descriptor. | ||||
| func fdatasync(db *DB) error { | ||||
| 	return syscall.Fdatasync(int(db.file.Fd())) | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/boltdb/bolt/bolt_openbsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/boltdb/bolt/bolt_openbsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	msAsync      = 1 << iota // perform asynchronous writes | ||||
| 	msSync                   // perform synchronous writes | ||||
| 	msInvalidate             // invalidate cached data | ||||
| ) | ||||
|  | ||||
| func msync(db *DB) error { | ||||
| 	_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate) | ||||
| 	if errno != 0 { | ||||
| 		return errno | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func fdatasync(db *DB) error { | ||||
| 	if db.data != nil { | ||||
| 		return msync(db) | ||||
| 	} | ||||
| 	return db.file.Sync() | ||||
| } | ||||
							
								
								
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_ppc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_ppc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| // +build ppc | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_ppc64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_ppc64.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| // +build ppc64 | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_ppc64le.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_ppc64le.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| // +build ppc64le | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // 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/github.com/boltdb/bolt/bolt_s390x.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/boltdb/bolt/bolt_s390x.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| // +build s390x | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // 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 | ||||
							
								
								
									
										89
									
								
								vendor/github.com/boltdb/bolt/bolt_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/boltdb/bolt/bolt_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| // +build !windows,!plan9,!solaris | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // flock acquires an advisory lock on a file descriptor. | ||||
| func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { | ||||
| 	var t time.Time | ||||
| 	for { | ||||
| 		// If we're beyond our timeout then return an error. | ||||
| 		// This can only occur after we've attempted a flock once. | ||||
| 		if t.IsZero() { | ||||
| 			t = time.Now() | ||||
| 		} else if timeout > 0 && time.Since(t) > timeout { | ||||
| 			return ErrTimeout | ||||
| 		} | ||||
| 		flag := syscall.LOCK_SH | ||||
| 		if exclusive { | ||||
| 			flag = syscall.LOCK_EX | ||||
| 		} | ||||
|  | ||||
| 		// Otherwise attempt to obtain an exclusive lock. | ||||
| 		err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB) | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} else if err != syscall.EWOULDBLOCK { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Wait for a bit and try again. | ||||
| 		time.Sleep(50 * time.Millisecond) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // funlock releases an advisory lock on a file descriptor. | ||||
| func funlock(db *DB) error { | ||||
| 	return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN) | ||||
| } | ||||
|  | ||||
| // mmap memory maps a DB's data file. | ||||
| func mmap(db *DB, sz int) error { | ||||
| 	// Map the data file to memory. | ||||
| 	b, err := syscall.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. | ||||
| 	if err := madvise(b, syscall.MADV_RANDOM); err != nil { | ||||
| 		return fmt.Errorf("madvise: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Save the original byte slice and convert to a byte array pointer. | ||||
| 	db.dataref = b | ||||
| 	db.data = (*[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 := syscall.Munmap(db.dataref) | ||||
| 	db.dataref = nil | ||||
| 	db.data = nil | ||||
| 	db.datasz = 0 | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // NOTE: This function is copied from stdlib because it is not available on darwin. | ||||
| func madvise(b []byte, advice int) (err error) { | ||||
| 	_, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice)) | ||||
| 	if e1 != 0 { | ||||
| 		err = e1 | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										90
									
								
								vendor/github.com/boltdb/bolt/bolt_unix_solaris.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								vendor/github.com/boltdb/bolt/bolt_unix_solaris.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| // flock acquires an advisory lock on a file descriptor. | ||||
| func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { | ||||
| 	var t time.Time | ||||
| 	for { | ||||
| 		// If we're beyond our timeout then return an error. | ||||
| 		// This can only occur after we've attempted a flock once. | ||||
| 		if t.IsZero() { | ||||
| 			t = time.Now() | ||||
| 		} else if timeout > 0 && time.Since(t) > timeout { | ||||
| 			return ErrTimeout | ||||
| 		} | ||||
| 		var lock syscall.Flock_t | ||||
| 		lock.Start = 0 | ||||
| 		lock.Len = 0 | ||||
| 		lock.Pid = 0 | ||||
| 		lock.Whence = 0 | ||||
| 		lock.Pid = 0 | ||||
| 		if exclusive { | ||||
| 			lock.Type = syscall.F_WRLCK | ||||
| 		} else { | ||||
| 			lock.Type = syscall.F_RDLCK | ||||
| 		} | ||||
| 		err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock) | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} else if err != syscall.EAGAIN { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Wait for a bit and try again. | ||||
| 		time.Sleep(50 * time.Millisecond) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| 	if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil { | ||||
| 		return fmt.Errorf("madvise: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Save the original byte slice and convert to a byte array pointer. | ||||
| 	db.dataref = b | ||||
| 	db.data = (*[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 | ||||
| } | ||||
							
								
								
									
										144
									
								
								vendor/github.com/boltdb/bolt/bolt_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								vendor/github.com/boltdb/bolt/bolt_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1 | ||||
| var ( | ||||
| 	modkernel32      = syscall.NewLazyDLL("kernel32.dll") | ||||
| 	procLockFileEx   = modkernel32.NewProc("LockFileEx") | ||||
| 	procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	lockExt = ".lock" | ||||
|  | ||||
| 	// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx | ||||
| 	flagLockExclusive       = 2 | ||||
| 	flagLockFailImmediately = 1 | ||||
|  | ||||
| 	// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx | ||||
| 	errLockViolation syscall.Errno = 0x21 | ||||
| ) | ||||
|  | ||||
| func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { | ||||
| 	r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol))) | ||||
| 	if r == 0 { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { | ||||
| 	r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0) | ||||
| 	if r == 0 { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // fdatasync flushes written data to a file descriptor. | ||||
| func fdatasync(db *DB) error { | ||||
| 	return db.file.Sync() | ||||
| } | ||||
|  | ||||
| // flock acquires an advisory lock on a file descriptor. | ||||
| func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { | ||||
| 	// Create a separate lock file on windows because a process | ||||
| 	// cannot share an exclusive lock on the same file. This is | ||||
| 	// needed during Tx.WriteTo(). | ||||
| 	f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	db.lockfile = f | ||||
|  | ||||
| 	var t time.Time | ||||
| 	for { | ||||
| 		// If we're beyond our timeout then return an error. | ||||
| 		// This can only occur after we've attempted a flock once. | ||||
| 		if t.IsZero() { | ||||
| 			t = time.Now() | ||||
| 		} else if timeout > 0 && time.Since(t) > timeout { | ||||
| 			return ErrTimeout | ||||
| 		} | ||||
|  | ||||
| 		var flag uint32 = flagLockFailImmediately | ||||
| 		if exclusive { | ||||
| 			flag |= flagLockExclusive | ||||
| 		} | ||||
|  | ||||
| 		err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}) | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} else if err != errLockViolation { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Wait for a bit and try again. | ||||
| 		time.Sleep(50 * time.Millisecond) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // funlock releases an advisory lock on a file descriptor. | ||||
| func funlock(db *DB) error { | ||||
| 	err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{}) | ||||
| 	db.lockfile.Close() | ||||
| 	os.Remove(db.path+lockExt) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // mmap memory maps a DB's data file. | ||||
| // Based on: https://github.com/edsrzf/mmap-go | ||||
| func mmap(db *DB, sz int) error { | ||||
| 	if !db.readOnly { | ||||
| 		// Truncate the database to the size of the mmap. | ||||
| 		if err := db.file.Truncate(int64(sz)); err != nil { | ||||
| 			return fmt.Errorf("truncate: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Open a file mapping handle. | ||||
| 	sizelo := uint32(sz >> 32) | ||||
| 	sizehi := uint32(sz) & 0xffffffff | ||||
| 	h, errno := syscall.CreateFileMapping(syscall.Handle(db.file.Fd()), nil, syscall.PAGE_READONLY, sizelo, sizehi, nil) | ||||
| 	if h == 0 { | ||||
| 		return os.NewSyscallError("CreateFileMapping", errno) | ||||
| 	} | ||||
|  | ||||
| 	// Create the memory map. | ||||
| 	addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(sz)) | ||||
| 	if addr == 0 { | ||||
| 		return os.NewSyscallError("MapViewOfFile", errno) | ||||
| 	} | ||||
|  | ||||
| 	// Close mapping handle. | ||||
| 	if err := syscall.CloseHandle(syscall.Handle(h)); err != nil { | ||||
| 		return os.NewSyscallError("CloseHandle", err) | ||||
| 	} | ||||
|  | ||||
| 	// Convert to a byte array. | ||||
| 	db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr))) | ||||
| 	db.datasz = sz | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // munmap unmaps a pointer from a file. | ||||
| // Based on: https://github.com/edsrzf/mmap-go | ||||
| func munmap(db *DB) error { | ||||
| 	if db.data == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	addr := (uintptr)(unsafe.Pointer(&db.data[0])) | ||||
| 	if err := syscall.UnmapViewOfFile(addr); err != nil { | ||||
| 		return os.NewSyscallError("UnmapViewOfFile", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										8
									
								
								vendor/github.com/boltdb/bolt/boltsync_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/boltdb/bolt/boltsync_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // +build !windows,!plan9,!linux,!openbsd | ||||
|  | ||||
| package bolt | ||||
|  | ||||
| // fdatasync flushes written data to a file descriptor. | ||||
| func fdatasync(db *DB) error { | ||||
| 	return db.file.Sync() | ||||
| } | ||||
							
								
								
									
										748
									
								
								vendor/github.com/boltdb/bolt/bucket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										748
									
								
								vendor/github.com/boltdb/bolt/bucket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,748 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// MaxKeySize is the maximum length of a key, in bytes. | ||||
| 	MaxKeySize = 32768 | ||||
|  | ||||
| 	// MaxValueSize is the maximum length of a value, in bytes. | ||||
| 	MaxValueSize = (1 << 31) - 2 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	maxUint = ^uint(0) | ||||
| 	minUint = 0 | ||||
| 	maxInt  = int(^uint(0) >> 1) | ||||
| 	minInt  = -maxInt - 1 | ||||
| ) | ||||
|  | ||||
| const bucketHeaderSize = int(unsafe.Sizeof(bucket{})) | ||||
|  | ||||
| const ( | ||||
| 	minFillPercent = 0.1 | ||||
| 	maxFillPercent = 1.0 | ||||
| ) | ||||
|  | ||||
| // DefaultFillPercent is the percentage that split pages are filled. | ||||
| // This value can be changed by setting Bucket.FillPercent. | ||||
| const DefaultFillPercent = 0.5 | ||||
|  | ||||
| // Bucket represents a collection of key/value pairs inside the database. | ||||
| type Bucket struct { | ||||
| 	*bucket | ||||
| 	tx       *Tx                // the associated transaction | ||||
| 	buckets  map[string]*Bucket // subbucket cache | ||||
| 	page     *page              // inline page reference | ||||
| 	rootNode *node              // materialized node for the root page. | ||||
| 	nodes    map[pgid]*node     // node cache | ||||
|  | ||||
| 	// 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 | ||||
| 	// amount if you know that your write workloads are mostly append-only. | ||||
| 	// | ||||
| 	// This is non-persisted across transactions so it must be set in every Tx. | ||||
| 	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. | ||||
| func newBucket(tx *Tx) Bucket { | ||||
| 	var b = Bucket{tx: tx, FillPercent: DefaultFillPercent} | ||||
| 	if tx.writable { | ||||
| 		b.buckets = make(map[string]*Bucket) | ||||
| 		b.nodes = make(map[pgid]*node) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // Tx returns the tx of the bucket. | ||||
| func (b *Bucket) Tx() *Tx { | ||||
| 	return b.tx | ||||
| } | ||||
|  | ||||
| // Root returns the root of the bucket. | ||||
| func (b *Bucket) Root() pgid { | ||||
| 	return b.root | ||||
| } | ||||
|  | ||||
| // Writable returns whether the bucket is writable. | ||||
| func (b *Bucket) Writable() bool { | ||||
| 	return b.tx.writable | ||||
| } | ||||
|  | ||||
| // Cursor creates a cursor associated with the bucket. | ||||
| // The cursor is only valid as long as the transaction is open. | ||||
| // Do not use a cursor after the transaction is closed. | ||||
| func (b *Bucket) Cursor() *Cursor { | ||||
| 	// Update transaction statistics. | ||||
| 	b.tx.stats.CursorCount++ | ||||
|  | ||||
| 	// Allocate and return a cursor. | ||||
| 	return &Cursor{ | ||||
| 		bucket: b, | ||||
| 		stack:  make([]elemRef, 0), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Bucket retrieves a nested bucket by name. | ||||
| // Returns nil if the bucket does not exist. | ||||
| // The bucket instance is only valid for the lifetime of the transaction. | ||||
| func (b *Bucket) Bucket(name []byte) *Bucket { | ||||
| 	if b.buckets != nil { | ||||
| 		if child := b.buckets[string(name)]; child != nil { | ||||
| 			return child | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Move cursor to key. | ||||
| 	c := b.Cursor() | ||||
| 	k, v, flags := c.seek(name) | ||||
|  | ||||
| 	// Return nil if the key doesn't exist or it is not a bucket. | ||||
| 	if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise create a bucket and cache it. | ||||
| 	var child = b.openBucket(v) | ||||
| 	if b.buckets != nil { | ||||
| 		b.buckets[string(name)] = child | ||||
| 	} | ||||
|  | ||||
| 	return child | ||||
| } | ||||
|  | ||||
| // Helper method that re-interprets a sub-bucket value | ||||
| // from a parent into a Bucket | ||||
| func (b *Bucket) openBucket(value []byte) *Bucket { | ||||
| 	var child = newBucket(b.tx) | ||||
|  | ||||
| 	// If this is a writable transaction then we need to copy the bucket entry. | ||||
| 	// Read-only transactions can point directly at the mmap entry. | ||||
| 	if b.tx.writable { | ||||
| 		child.bucket = &bucket{} | ||||
| 		*child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) | ||||
| 	} else { | ||||
| 		child.bucket = (*bucket)(unsafe.Pointer(&value[0])) | ||||
| 	} | ||||
|  | ||||
| 	// Save a reference to the inline page if the bucket is inline. | ||||
| 	if child.root == 0 { | ||||
| 		child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) | ||||
| 	} | ||||
|  | ||||
| 	return &child | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| // The bucket instance is only valid for the lifetime of the transaction. | ||||
| func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) { | ||||
| 	if b.tx.db == nil { | ||||
| 		return nil, ErrTxClosed | ||||
| 	} else if !b.tx.writable { | ||||
| 		return nil, ErrTxNotWritable | ||||
| 	} else if len(key) == 0 { | ||||
| 		return nil, ErrBucketNameRequired | ||||
| 	} | ||||
|  | ||||
| 	// Move cursor to correct position. | ||||
| 	c := b.Cursor() | ||||
| 	k, _, flags := c.seek(key) | ||||
|  | ||||
| 	// Return an error if there is an existing key. | ||||
| 	if bytes.Equal(key, k) { | ||||
| 		if (flags & bucketLeafFlag) != 0 { | ||||
| 			return nil, ErrBucketExists | ||||
| 		} else { | ||||
| 			return nil, ErrIncompatibleValue | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Create empty, inline bucket. | ||||
| 	var bucket = Bucket{ | ||||
| 		bucket:      &bucket{}, | ||||
| 		rootNode:    &node{isLeaf: true}, | ||||
| 		FillPercent: DefaultFillPercent, | ||||
| 	} | ||||
| 	var value = bucket.write() | ||||
|  | ||||
| 	// Insert into node. | ||||
| 	key = cloneBytes(key) | ||||
| 	c.node().put(key, key, value, 0, 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(key), nil | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| // The bucket instance is only valid for the lifetime of the transaction. | ||||
| func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) { | ||||
| 	child, err := b.CreateBucket(key) | ||||
| 	if err == ErrBucketExists { | ||||
| 		return b.Bucket(key), nil | ||||
| 	} else if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return child, nil | ||||
| } | ||||
|  | ||||
| // DeleteBucket deletes a bucket at the given key. | ||||
| // Returns an error if the bucket does not exists, or if the key represents a non-bucket value. | ||||
| func (b *Bucket) DeleteBucket(key []byte) error { | ||||
| 	if b.tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} else if !b.Writable() { | ||||
| 		return ErrTxNotWritable | ||||
| 	} | ||||
|  | ||||
| 	// Move cursor to correct position. | ||||
| 	c := b.Cursor() | ||||
| 	k, _, flags := c.seek(key) | ||||
|  | ||||
| 	// Return an error if bucket doesn't exist or is not a bucket. | ||||
| 	if !bytes.Equal(key, k) { | ||||
| 		return ErrBucketNotFound | ||||
| 	} else if (flags & bucketLeafFlag) == 0 { | ||||
| 		return ErrIncompatibleValue | ||||
| 	} | ||||
|  | ||||
| 	// Recursively delete all child buckets. | ||||
| 	child := b.Bucket(key) | ||||
| 	err := child.ForEach(func(k, v []byte) error { | ||||
| 		if v == nil { | ||||
| 			if err := child.DeleteBucket(k); err != nil { | ||||
| 				return fmt.Errorf("delete bucket: %s", err) | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Remove cached copy. | ||||
| 	delete(b.buckets, string(key)) | ||||
|  | ||||
| 	// Release all bucket pages to freelist. | ||||
| 	child.nodes = nil | ||||
| 	child.rootNode = nil | ||||
| 	child.free() | ||||
|  | ||||
| 	// Delete the node if we have a matching key. | ||||
| 	c.node().del(key) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| // The returned value is only valid for the life of the transaction. | ||||
| func (b *Bucket) Get(key []byte) []byte { | ||||
| 	k, v, flags := b.Cursor().seek(key) | ||||
|  | ||||
| 	// Return nil if this is a bucket. | ||||
| 	if (flags & bucketLeafFlag) != 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// If our target node isn't the same key as what's passed in then return nil. | ||||
| 	if !bytes.Equal(key, k) { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Put sets the value for a key in the bucket. | ||||
| // If the key exist then its previous value will be overwritten. | ||||
| // 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. | ||||
| func (b *Bucket) Put(key []byte, value []byte) error { | ||||
| 	if b.tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} else if !b.Writable() { | ||||
| 		return ErrTxNotWritable | ||||
| 	} else if len(key) == 0 { | ||||
| 		return ErrKeyRequired | ||||
| 	} else if len(key) > MaxKeySize { | ||||
| 		return ErrKeyTooLarge | ||||
| 	} else if int64(len(value)) > MaxValueSize { | ||||
| 		return ErrValueTooLarge | ||||
| 	} | ||||
|  | ||||
| 	// Move cursor to correct position. | ||||
| 	c := b.Cursor() | ||||
| 	k, _, flags := c.seek(key) | ||||
|  | ||||
| 	// Return an error if there is an existing key with a bucket value. | ||||
| 	if bytes.Equal(key, k) && (flags&bucketLeafFlag) != 0 { | ||||
| 		return ErrIncompatibleValue | ||||
| 	} | ||||
|  | ||||
| 	// Insert into node. | ||||
| 	key = cloneBytes(key) | ||||
| 	c.node().put(key, key, value, 0, 0) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Delete removes a key from the bucket. | ||||
| // 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. | ||||
| func (b *Bucket) Delete(key []byte) error { | ||||
| 	if b.tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} else if !b.Writable() { | ||||
| 		return ErrTxNotWritable | ||||
| 	} | ||||
|  | ||||
| 	// Move cursor to correct position. | ||||
| 	c := b.Cursor() | ||||
| 	_, _, flags := c.seek(key) | ||||
|  | ||||
| 	// Return an error if there is already existing bucket value. | ||||
| 	if (flags & bucketLeafFlag) != 0 { | ||||
| 		return ErrIncompatibleValue | ||||
| 	} | ||||
|  | ||||
| 	// Delete the node if we have a matching key. | ||||
| 	c.node().del(key) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NextSequence returns an autoincrementing integer for the bucket. | ||||
| func (b *Bucket) NextSequence() (uint64, error) { | ||||
| 	if b.tx.db == nil { | ||||
| 		return 0, ErrTxClosed | ||||
| 	} else if !b.Writable() { | ||||
| 		return 0, ErrTxNotWritable | ||||
| 	} | ||||
|  | ||||
| 	// Materialize the root node if it hasn't been already so that the | ||||
| 	// bucket will be saved during commit. | ||||
| 	if b.rootNode == nil { | ||||
| 		_ = b.node(b.root, nil) | ||||
| 	} | ||||
|  | ||||
| 	// Increment and return the sequence. | ||||
| 	b.bucket.sequence++ | ||||
| 	return b.bucket.sequence, nil | ||||
| } | ||||
|  | ||||
| // ForEach executes a function for each key/value pair in a bucket. | ||||
| // If the provided function returns an error then the iteration is stopped and | ||||
| // the error is returned to the caller. The provided function must not modify | ||||
| // the bucket; this will result in undefined behavior. | ||||
| func (b *Bucket) ForEach(fn func(k, v []byte) error) error { | ||||
| 	if b.tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} | ||||
| 	c := b.Cursor() | ||||
| 	for k, v := c.First(); k != nil; k, v = c.Next() { | ||||
| 		if err := fn(k, v); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Stat returns stats on a bucket. | ||||
| func (b *Bucket) Stats() BucketStats { | ||||
| 	var s, subStats BucketStats | ||||
| 	pageSize := b.tx.db.pageSize | ||||
| 	s.BucketN += 1 | ||||
| 	if b.root == 0 { | ||||
| 		s.InlineBucketN += 1 | ||||
| 	} | ||||
| 	b.forEachPage(func(p *page, depth int) { | ||||
| 		if (p.flags & leafPageFlag) != 0 { | ||||
| 			s.KeyN += int(p.count) | ||||
|  | ||||
| 			// used totals the used bytes for the page | ||||
| 			used := pageHeaderSize | ||||
|  | ||||
| 			if p.count != 0 { | ||||
| 				// If page has any elements, add all element headers. | ||||
| 				used += leafPageElementSize * int(p.count-1) | ||||
|  | ||||
| 				// Add all element key, value sizes. | ||||
| 				// 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 all previous elements' keys and values. | ||||
| 				// It also includes the last element's header. | ||||
| 				lastElement := p.leafPageElement(p.count - 1) | ||||
| 				used += int(lastElement.pos + lastElement.ksize + lastElement.vsize) | ||||
| 			} | ||||
|  | ||||
| 			if b.root == 0 { | ||||
| 				// For inlined bucket just update the inline stats | ||||
| 				s.InlineBucketInuse += used | ||||
| 			} else { | ||||
| 				// For non-inlined bucket update all the leaf stats | ||||
| 				s.LeafPageN++ | ||||
| 				s.LeafInuse += used | ||||
| 				s.LeafOverflowN += int(p.overflow) | ||||
|  | ||||
| 				// Collect stats from sub-buckets. | ||||
| 				// Do that by iterating over all element headers | ||||
| 				// looking for the ones with the bucketLeafFlag. | ||||
| 				for i := uint16(0); i < p.count; i++ { | ||||
| 					e := p.leafPageElement(i) | ||||
| 					if (e.flags & bucketLeafFlag) != 0 { | ||||
| 						// For any bucket element, open the element value | ||||
| 						// and recursively call Stats on the contained bucket. | ||||
| 						subStats.Add(b.openBucket(e.value()).Stats()) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} else if (p.flags & branchPageFlag) != 0 { | ||||
| 			s.BranchPageN++ | ||||
| 			lastElement := p.branchPageElement(p.count - 1) | ||||
|  | ||||
| 			// used totals the used bytes for the page | ||||
| 			// Add header and all element headers. | ||||
| 			used := pageHeaderSize + (branchPageElementSize * int(p.count-1)) | ||||
|  | ||||
| 			// Add size of all keys and values. | ||||
| 			// Again, use the fact that last element's position equals to | ||||
| 			// the total of key, value sizes of all previous elements. | ||||
| 			used += int(lastElement.pos + lastElement.ksize) | ||||
| 			s.BranchInuse += used | ||||
| 			s.BranchOverflowN += int(p.overflow) | ||||
| 		} | ||||
|  | ||||
| 		// Keep track of maximum page depth. | ||||
| 		if depth+1 > s.Depth { | ||||
| 			s.Depth = (depth + 1) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Alloc stats can be computed from page counts and pageSize. | ||||
| 	s.BranchAlloc = (s.BranchPageN + s.BranchOverflowN) * pageSize | ||||
| 	s.LeafAlloc = (s.LeafPageN + s.LeafOverflowN) * pageSize | ||||
|  | ||||
| 	// Add the max depth of sub-buckets to get total nested depth. | ||||
| 	s.Depth += subStats.Depth | ||||
| 	// Add the stats for all sub-buckets | ||||
| 	s.Add(subStats) | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| // forEachPage iterates over every page in a bucket, including inline pages. | ||||
| func (b *Bucket) forEachPage(fn func(*page, int)) { | ||||
| 	// If we have an inline page then just use that. | ||||
| 	if b.page != nil { | ||||
| 		fn(b.page, 0) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise traverse the page hierarchy. | ||||
| 	b.tx.forEachPage(b.root, 0, fn) | ||||
| } | ||||
|  | ||||
| // forEachPageNode iterates over every page (or node) in a bucket. | ||||
| // This also includes inline pages. | ||||
| func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) { | ||||
| 	// If we have an inline page or root node then just use that. | ||||
| 	if b.page != nil { | ||||
| 		fn(b.page, nil, 0) | ||||
| 		return | ||||
| 	} | ||||
| 	b._forEachPageNode(b.root, 0, fn) | ||||
| } | ||||
|  | ||||
| func (b *Bucket) _forEachPageNode(pgid pgid, depth int, fn func(*page, *node, int)) { | ||||
| 	var p, n = b.pageNode(pgid) | ||||
|  | ||||
| 	// Execute function. | ||||
| 	fn(p, n, depth) | ||||
|  | ||||
| 	// Recursively loop over children. | ||||
| 	if p != nil { | ||||
| 		if (p.flags & branchPageFlag) != 0 { | ||||
| 			for i := 0; i < int(p.count); i++ { | ||||
| 				elem := p.branchPageElement(uint16(i)) | ||||
| 				b._forEachPageNode(elem.pgid, depth+1, fn) | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		if !n.isLeaf { | ||||
| 			for _, inode := range n.inodes { | ||||
| 				b._forEachPageNode(inode.pgid, depth+1, fn) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // spill writes all the nodes for this bucket to dirty pages. | ||||
| func (b *Bucket) spill() error { | ||||
| 	// Spill all child buckets first. | ||||
| 	for name, child := range b.buckets { | ||||
| 		// If the child bucket is small enough and it has no child buckets then | ||||
| 		// write it inline into the parent bucket's page. Otherwise spill it | ||||
| 		// like a normal bucket and make the parent value a pointer to the page. | ||||
| 		var value []byte | ||||
| 		if child.inlineable() { | ||||
| 			child.free() | ||||
| 			value = child.write() | ||||
| 		} else { | ||||
| 			if err := child.spill(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// Update the child bucket header in this bucket. | ||||
| 			value = make([]byte, unsafe.Sizeof(bucket{})) | ||||
| 			var bucket = (*bucket)(unsafe.Pointer(&value[0])) | ||||
| 			*bucket = *child.bucket | ||||
| 		} | ||||
|  | ||||
| 		// Skip writing the bucket if there are no materialized nodes. | ||||
| 		if child.rootNode == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Update parent node. | ||||
| 		var c = b.Cursor() | ||||
| 		k, _, flags := c.seek([]byte(name)) | ||||
| 		if !bytes.Equal([]byte(name), k) { | ||||
| 			panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k)) | ||||
| 		} | ||||
| 		if flags&bucketLeafFlag == 0 { | ||||
| 			panic(fmt.Sprintf("unexpected bucket header flag: %x", flags)) | ||||
| 		} | ||||
| 		c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag) | ||||
| 	} | ||||
|  | ||||
| 	// Ignore if there's not a materialized root node. | ||||
| 	if b.rootNode == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Spill nodes. | ||||
| 	if err := b.rootNode.spill(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	b.rootNode = b.rootNode.root() | ||||
|  | ||||
| 	// Update the root node for this bucket. | ||||
| 	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)) | ||||
| 	} | ||||
| 	b.root = b.rootNode.pgid | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // inlineable returns true if a bucket is small enough to be written inline | ||||
| // and if it contains no subbuckets. Otherwise returns false. | ||||
| func (b *Bucket) inlineable() bool { | ||||
| 	var n = b.rootNode | ||||
|  | ||||
| 	// Bucket must only contain a single leaf node. | ||||
| 	if n == nil || !n.isLeaf { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// Bucket is not inlineable if it contains subbuckets or if it goes beyond | ||||
| 	// our threshold for inline bucket size. | ||||
| 	var size = pageHeaderSize | ||||
| 	for _, inode := range n.inodes { | ||||
| 		size += leafPageElementSize + len(inode.key) + len(inode.value) | ||||
|  | ||||
| 		if inode.flags&bucketLeafFlag != 0 { | ||||
| 			return false | ||||
| 		} else if size > b.maxInlineBucketSize() { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Returns the maximum total size of a bucket to make it a candidate for inlining. | ||||
| func (b *Bucket) maxInlineBucketSize() int { | ||||
| 	return b.tx.db.pageSize / 4 | ||||
| } | ||||
|  | ||||
| // write allocates and writes a bucket to a byte slice. | ||||
| func (b *Bucket) write() []byte { | ||||
| 	// Allocate the appropriate size. | ||||
| 	var n = b.rootNode | ||||
| 	var value = make([]byte, bucketHeaderSize+n.size()) | ||||
|  | ||||
| 	// Write a bucket header. | ||||
| 	var bucket = (*bucket)(unsafe.Pointer(&value[0])) | ||||
| 	*bucket = *b.bucket | ||||
|  | ||||
| 	// Convert byte slice to a fake page and write the root node. | ||||
| 	var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) | ||||
| 	n.write(p) | ||||
|  | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| // rebalance attempts to balance all nodes. | ||||
| func (b *Bucket) rebalance() { | ||||
| 	for _, n := range b.nodes { | ||||
| 		n.rebalance() | ||||
| 	} | ||||
| 	for _, child := range b.buckets { | ||||
| 		child.rebalance() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // node creates a node from a page and associates it with a given parent. | ||||
| func (b *Bucket) node(pgid pgid, parent *node) *node { | ||||
| 	_assert(b.nodes != nil, "nodes map expected") | ||||
|  | ||||
| 	// Retrieve node if it's already been created. | ||||
| 	if n := b.nodes[pgid]; n != nil { | ||||
| 		return n | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise create a node and cache it. | ||||
| 	n := &node{bucket: b, parent: parent} | ||||
| 	if parent == nil { | ||||
| 		b.rootNode = n | ||||
| 	} else { | ||||
| 		parent.children = append(parent.children, n) | ||||
| 	} | ||||
|  | ||||
| 	// Use the inline page if this is an inline bucket. | ||||
| 	var p = b.page | ||||
| 	if p == nil { | ||||
| 		p = b.tx.page(pgid) | ||||
| 	} | ||||
|  | ||||
| 	// Read the page into the node and cache it. | ||||
| 	n.read(p) | ||||
| 	b.nodes[pgid] = n | ||||
|  | ||||
| 	// Update statistics. | ||||
| 	b.tx.stats.NodeCount++ | ||||
|  | ||||
| 	return n | ||||
| } | ||||
|  | ||||
| // free recursively frees all pages in the bucket. | ||||
| func (b *Bucket) free() { | ||||
| 	if b.root == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var tx = b.tx | ||||
| 	b.forEachPageNode(func(p *page, n *node, _ int) { | ||||
| 		if p != nil { | ||||
| 			tx.db.freelist.free(tx.meta.txid, p) | ||||
| 		} else { | ||||
| 			n.free() | ||||
| 		} | ||||
| 	}) | ||||
| 	b.root = 0 | ||||
| } | ||||
|  | ||||
| // dereference removes all references to the old mmap. | ||||
| func (b *Bucket) dereference() { | ||||
| 	if b.rootNode != nil { | ||||
| 		b.rootNode.root().dereference() | ||||
| 	} | ||||
|  | ||||
| 	for _, child := range b.buckets { | ||||
| 		child.dereference() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // pageNode returns the in-memory node, if it exists. | ||||
| // Otherwise returns the underlying page. | ||||
| func (b *Bucket) pageNode(id pgid) (*page, *node) { | ||||
| 	// 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. | ||||
| 	if b.root == 0 { | ||||
| 		if id != 0 { | ||||
| 			panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id)) | ||||
| 		} | ||||
| 		if b.rootNode != nil { | ||||
| 			return nil, b.rootNode | ||||
| 		} | ||||
| 		return b.page, nil | ||||
| 	} | ||||
|  | ||||
| 	// Check the node cache for non-inline buckets. | ||||
| 	if b.nodes != nil { | ||||
| 		if n := b.nodes[id]; n != nil { | ||||
| 			return nil, n | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Finally lookup the page from the transaction if no node is materialized. | ||||
| 	return b.tx.page(id), nil | ||||
| } | ||||
|  | ||||
| // BucketStats records statistics about resources used by a bucket. | ||||
| type BucketStats struct { | ||||
| 	// Page count statistics. | ||||
| 	BranchPageN     int // number of logical branch pages | ||||
| 	BranchOverflowN int // number of physical branch overflow pages | ||||
| 	LeafPageN       int // number of logical leaf pages | ||||
| 	LeafOverflowN   int // number of physical leaf overflow pages | ||||
|  | ||||
| 	// Tree statistics. | ||||
| 	KeyN  int // number of keys/value pairs | ||||
| 	Depth int // number of levels in B+tree | ||||
|  | ||||
| 	// Page size utilization. | ||||
| 	BranchAlloc int // bytes allocated for physical branch pages | ||||
| 	BranchInuse int // bytes actually used for branch data | ||||
| 	LeafAlloc   int // bytes allocated for physical leaf pages | ||||
| 	LeafInuse   int // bytes actually used for leaf data | ||||
|  | ||||
| 	// Bucket statistics | ||||
| 	BucketN           int // total number of buckets including the top bucket | ||||
| 	InlineBucketN     int // total number on inlined buckets | ||||
| 	InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse) | ||||
| } | ||||
|  | ||||
| func (s *BucketStats) Add(other BucketStats) { | ||||
| 	s.BranchPageN += other.BranchPageN | ||||
| 	s.BranchOverflowN += other.BranchOverflowN | ||||
| 	s.LeafPageN += other.LeafPageN | ||||
| 	s.LeafOverflowN += other.LeafOverflowN | ||||
| 	s.KeyN += other.KeyN | ||||
| 	if s.Depth < other.Depth { | ||||
| 		s.Depth = other.Depth | ||||
| 	} | ||||
| 	s.BranchAlloc += other.BranchAlloc | ||||
| 	s.BranchInuse += other.BranchInuse | ||||
| 	s.LeafAlloc += other.LeafAlloc | ||||
| 	s.LeafInuse += other.LeafInuse | ||||
|  | ||||
| 	s.BucketN += other.BucketN | ||||
| 	s.InlineBucketN += other.InlineBucketN | ||||
| 	s.InlineBucketInuse += other.InlineBucketInuse | ||||
| } | ||||
|  | ||||
| // cloneBytes returns a copy of a given slice. | ||||
| func cloneBytes(v []byte) []byte { | ||||
| 	var clone = make([]byte, len(v)) | ||||
| 	copy(clone, v) | ||||
| 	return clone | ||||
| } | ||||
							
								
								
									
										400
									
								
								vendor/github.com/boltdb/bolt/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								vendor/github.com/boltdb/bolt/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| ) | ||||
|  | ||||
| // Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order. | ||||
| // Cursors see nested buckets with value == nil. | ||||
| // Cursors can be obtained from a transaction and are valid as long as the transaction is open. | ||||
| // | ||||
| // Keys and values returned from the cursor are only valid for the life of the transaction. | ||||
| // | ||||
| // Changing data while traversing with a cursor may cause it to be invalidated | ||||
| // and return unexpected keys and/or values. You must reposition your cursor | ||||
| // after mutating data. | ||||
| type Cursor struct { | ||||
| 	bucket *Bucket | ||||
| 	stack  []elemRef | ||||
| } | ||||
|  | ||||
| // Bucket returns the bucket that this cursor was created from. | ||||
| func (c *Cursor) Bucket() *Bucket { | ||||
| 	return c.bucket | ||||
| } | ||||
|  | ||||
| // First moves the cursor to the first item in the bucket and returns its key and value. | ||||
| // 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. | ||||
| func (c *Cursor) First() (key []byte, value []byte) { | ||||
| 	_assert(c.bucket.tx.db != nil, "tx closed") | ||||
| 	c.stack = c.stack[:0] | ||||
| 	p, n := c.bucket.pageNode(c.bucket.root) | ||||
| 	c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) | ||||
| 	c.first() | ||||
|  | ||||
| 	// If we land on an empty page then move to the next value. | ||||
| 	// https://github.com/boltdb/bolt/issues/450 | ||||
| 	if c.stack[len(c.stack)-1].count() == 0 { | ||||
| 		c.next() | ||||
| 	} | ||||
|  | ||||
| 	k, v, flags := c.keyValue() | ||||
| 	if (flags & uint32(bucketLeafFlag)) != 0 { | ||||
| 		return k, nil | ||||
| 	} | ||||
| 	return k, v | ||||
|  | ||||
| } | ||||
|  | ||||
| // Last moves the cursor to the last item in the bucket and returns its key and value. | ||||
| // 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. | ||||
| func (c *Cursor) Last() (key []byte, value []byte) { | ||||
| 	_assert(c.bucket.tx.db != nil, "tx closed") | ||||
| 	c.stack = c.stack[:0] | ||||
| 	p, n := c.bucket.pageNode(c.bucket.root) | ||||
| 	ref := elemRef{page: p, node: n} | ||||
| 	ref.index = ref.count() - 1 | ||||
| 	c.stack = append(c.stack, ref) | ||||
| 	c.last() | ||||
| 	k, v, flags := c.keyValue() | ||||
| 	if (flags & uint32(bucketLeafFlag)) != 0 { | ||||
| 		return k, nil | ||||
| 	} | ||||
| 	return k, v | ||||
| } | ||||
|  | ||||
| // Next moves the cursor to the next item in the bucket and returns its key and value. | ||||
| // 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. | ||||
| func (c *Cursor) Next() (key []byte, value []byte) { | ||||
| 	_assert(c.bucket.tx.db != nil, "tx closed") | ||||
| 	k, v, flags := c.next() | ||||
| 	if (flags & uint32(bucketLeafFlag)) != 0 { | ||||
| 		return k, nil | ||||
| 	} | ||||
| 	return k, v | ||||
| } | ||||
|  | ||||
| // Prev moves the cursor to the previous item in the bucket and returns its key and value. | ||||
| // 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. | ||||
| func (c *Cursor) Prev() (key []byte, value []byte) { | ||||
| 	_assert(c.bucket.tx.db != nil, "tx closed") | ||||
|  | ||||
| 	// Attempt to move back one element until we're successful. | ||||
| 	// Move up the stack as we hit the beginning of each page in our stack. | ||||
| 	for i := len(c.stack) - 1; i >= 0; i-- { | ||||
| 		elem := &c.stack[i] | ||||
| 		if elem.index > 0 { | ||||
| 			elem.index-- | ||||
| 			break | ||||
| 		} | ||||
| 		c.stack = c.stack[:i] | ||||
| 	} | ||||
|  | ||||
| 	// If we've hit the end then return nil. | ||||
| 	if len(c.stack) == 0 { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// Move down the stack to find the last element of the last leaf under this branch. | ||||
| 	c.last() | ||||
| 	k, v, flags := c.keyValue() | ||||
| 	if (flags & uint32(bucketLeafFlag)) != 0 { | ||||
| 		return k, nil | ||||
| 	} | ||||
| 	return k, v | ||||
| } | ||||
|  | ||||
| // Seek moves the cursor to a given key and returns it. | ||||
| // If the key does not exist then the next key is used. If no keys | ||||
| // follow, a nil key is returned. | ||||
| // The returned key and value are only valid for the life of the transaction. | ||||
| func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) { | ||||
| 	k, v, flags := c.seek(seek) | ||||
|  | ||||
| 	// If we ended up after the last element of a page then move to the next one. | ||||
| 	if ref := &c.stack[len(c.stack)-1]; ref.index >= ref.count() { | ||||
| 		k, v, flags = c.next() | ||||
| 	} | ||||
|  | ||||
| 	if k == nil { | ||||
| 		return nil, nil | ||||
| 	} else if (flags & uint32(bucketLeafFlag)) != 0 { | ||||
| 		return k, nil | ||||
| 	} | ||||
| 	return k, v | ||||
| } | ||||
|  | ||||
| // Delete removes the current key/value under the cursor from the bucket. | ||||
| // Delete fails if current key/value is a bucket or if the transaction is not writable. | ||||
| func (c *Cursor) Delete() error { | ||||
| 	if c.bucket.tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} else if !c.bucket.Writable() { | ||||
| 		return ErrTxNotWritable | ||||
| 	} | ||||
|  | ||||
| 	key, _, flags := c.keyValue() | ||||
| 	// Return an error if current value is a bucket. | ||||
| 	if (flags & bucketLeafFlag) != 0 { | ||||
| 		return ErrIncompatibleValue | ||||
| 	} | ||||
| 	c.node().del(key) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // seek moves the cursor to a given key and returns it. | ||||
| // If the key does not exist then the next key is used. | ||||
| func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) { | ||||
| 	_assert(c.bucket.tx.db != nil, "tx closed") | ||||
|  | ||||
| 	// Start from root page/node and traverse to correct page. | ||||
| 	c.stack = c.stack[:0] | ||||
| 	c.search(seek, c.bucket.root) | ||||
| 	ref := &c.stack[len(c.stack)-1] | ||||
|  | ||||
| 	// If the cursor is pointing to the end of page/node then return nil. | ||||
| 	if ref.index >= ref.count() { | ||||
| 		return nil, nil, 0 | ||||
| 	} | ||||
|  | ||||
| 	// If this is a bucket then return a nil value. | ||||
| 	return c.keyValue() | ||||
| } | ||||
|  | ||||
| // first moves the cursor to the first leaf element under the last page in the stack. | ||||
| func (c *Cursor) first() { | ||||
| 	for { | ||||
| 		// Exit when we hit a leaf page. | ||||
| 		var ref = &c.stack[len(c.stack)-1] | ||||
| 		if ref.isLeaf() { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Keep adding pages pointing to the first element to the stack. | ||||
| 		var pgid pgid | ||||
| 		if ref.node != nil { | ||||
| 			pgid = ref.node.inodes[ref.index].pgid | ||||
| 		} else { | ||||
| 			pgid = ref.page.branchPageElement(uint16(ref.index)).pgid | ||||
| 		} | ||||
| 		p, n := c.bucket.pageNode(pgid) | ||||
| 		c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // last moves the cursor to the last leaf element under the last page in the stack. | ||||
| func (c *Cursor) last() { | ||||
| 	for { | ||||
| 		// Exit when we hit a leaf page. | ||||
| 		ref := &c.stack[len(c.stack)-1] | ||||
| 		if ref.isLeaf() { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Keep adding pages pointing to the last element in the stack. | ||||
| 		var pgid pgid | ||||
| 		if ref.node != nil { | ||||
| 			pgid = ref.node.inodes[ref.index].pgid | ||||
| 		} else { | ||||
| 			pgid = ref.page.branchPageElement(uint16(ref.index)).pgid | ||||
| 		} | ||||
| 		p, n := c.bucket.pageNode(pgid) | ||||
|  | ||||
| 		var nextRef = elemRef{page: p, node: n} | ||||
| 		nextRef.index = nextRef.count() - 1 | ||||
| 		c.stack = append(c.stack, nextRef) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // next moves to the next leaf element and returns the key and value. | ||||
| // If the cursor is at the last leaf element then it stays there and returns nil. | ||||
| func (c *Cursor) next() (key []byte, value []byte, flags uint32) { | ||||
| 	for { | ||||
| 		// Attempt to move over one element until we're successful. | ||||
| 		// Move up the stack as we hit the end of each page in our stack. | ||||
| 		var i int | ||||
| 		for i = len(c.stack) - 1; i >= 0; i-- { | ||||
| 			elem := &c.stack[i] | ||||
| 			if elem.index < elem.count()-1 { | ||||
| 				elem.index++ | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// If we've hit the root page then stop and return. This will leave the | ||||
| 		// cursor on the last element of the last page. | ||||
| 		if i == -1 { | ||||
| 			return nil, nil, 0 | ||||
| 		} | ||||
|  | ||||
| 		// Otherwise start from where we left off in the stack and find the | ||||
| 		// first element of the first leaf page. | ||||
| 		c.stack = c.stack[:i+1] | ||||
| 		c.first() | ||||
|  | ||||
| 		// If this is an empty page then restart and move back up the stack. | ||||
| 		// https://github.com/boltdb/bolt/issues/450 | ||||
| 		if c.stack[len(c.stack)-1].count() == 0 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		return c.keyValue() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| 	p, n := c.bucket.pageNode(pgid) | ||||
| 	if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 { | ||||
| 		panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags)) | ||||
| 	} | ||||
| 	e := elemRef{page: p, node: n} | ||||
| 	c.stack = append(c.stack, e) | ||||
|  | ||||
| 	// If we're on a leaf page/node then find the specific node. | ||||
| 	if e.isLeaf() { | ||||
| 		c.nsearch(key) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if n != nil { | ||||
| 		c.searchNode(key, n) | ||||
| 		return | ||||
| 	} | ||||
| 	c.searchPage(key, p) | ||||
| } | ||||
|  | ||||
| func (c *Cursor) searchNode(key []byte, n *node) { | ||||
| 	var exact bool | ||||
| 	index := sort.Search(len(n.inodes), func(i int) bool { | ||||
| 		// 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. | ||||
| 		ret := bytes.Compare(n.inodes[i].key, key) | ||||
| 		if ret == 0 { | ||||
| 			exact = true | ||||
| 		} | ||||
| 		return ret != -1 | ||||
| 	}) | ||||
| 	if !exact && index > 0 { | ||||
| 		index-- | ||||
| 	} | ||||
| 	c.stack[len(c.stack)-1].index = index | ||||
|  | ||||
| 	// Recursively search to the next page. | ||||
| 	c.search(key, n.inodes[index].pgid) | ||||
| } | ||||
|  | ||||
| func (c *Cursor) searchPage(key []byte, p *page) { | ||||
| 	// Binary search for the correct range. | ||||
| 	inodes := p.branchPageElements() | ||||
|  | ||||
| 	var exact bool | ||||
| 	index := sort.Search(int(p.count), func(i int) bool { | ||||
| 		// 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. | ||||
| 		ret := bytes.Compare(inodes[i].key(), key) | ||||
| 		if ret == 0 { | ||||
| 			exact = true | ||||
| 		} | ||||
| 		return ret != -1 | ||||
| 	}) | ||||
| 	if !exact && index > 0 { | ||||
| 		index-- | ||||
| 	} | ||||
| 	c.stack[len(c.stack)-1].index = index | ||||
|  | ||||
| 	// Recursively search to the next page. | ||||
| 	c.search(key, inodes[index].pgid) | ||||
| } | ||||
|  | ||||
| // nsearch searches the leaf node on the top of the stack for a key. | ||||
| func (c *Cursor) nsearch(key []byte) { | ||||
| 	e := &c.stack[len(c.stack)-1] | ||||
| 	p, n := e.page, e.node | ||||
|  | ||||
| 	// If we have a node then search its inodes. | ||||
| 	if n != nil { | ||||
| 		index := sort.Search(len(n.inodes), func(i int) bool { | ||||
| 			return bytes.Compare(n.inodes[i].key, key) != -1 | ||||
| 		}) | ||||
| 		e.index = index | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// If we have a page then search its leaf elements. | ||||
| 	inodes := p.leafPageElements() | ||||
| 	index := sort.Search(int(p.count), func(i int) bool { | ||||
| 		return bytes.Compare(inodes[i].key(), key) != -1 | ||||
| 	}) | ||||
| 	e.index = index | ||||
| } | ||||
|  | ||||
| // keyValue returns the key and value of the current leaf element. | ||||
| func (c *Cursor) keyValue() ([]byte, []byte, uint32) { | ||||
| 	ref := &c.stack[len(c.stack)-1] | ||||
| 	if ref.count() == 0 || ref.index >= ref.count() { | ||||
| 		return nil, nil, 0 | ||||
| 	} | ||||
|  | ||||
| 	// Retrieve value from node. | ||||
| 	if ref.node != nil { | ||||
| 		inode := &ref.node.inodes[ref.index] | ||||
| 		return inode.key, inode.value, inode.flags | ||||
| 	} | ||||
|  | ||||
| 	// Or retrieve value from page. | ||||
| 	elem := ref.page.leafPageElement(uint16(ref.index)) | ||||
| 	return elem.key(), elem.value(), elem.flags | ||||
| } | ||||
|  | ||||
| // node returns the node that the cursor is currently positioned on. | ||||
| func (c *Cursor) node() *node { | ||||
| 	_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 ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() { | ||||
| 		return ref.node | ||||
| 	} | ||||
|  | ||||
| 	// Start from root and traverse down the hierarchy. | ||||
| 	var n = c.stack[0].node | ||||
| 	if n == nil { | ||||
| 		n = c.bucket.node(c.stack[0].page.id, nil) | ||||
| 	} | ||||
| 	for _, ref := range c.stack[:len(c.stack)-1] { | ||||
| 		_assert(!n.isLeaf, "expected branch node") | ||||
| 		n = n.childAt(int(ref.index)) | ||||
| 	} | ||||
| 	_assert(n.isLeaf, "expected leaf node") | ||||
| 	return n | ||||
| } | ||||
|  | ||||
| // elemRef represents a reference to an element on a given page/node. | ||||
| type elemRef struct { | ||||
| 	page  *page | ||||
| 	node  *node | ||||
| 	index int | ||||
| } | ||||
|  | ||||
| // isLeaf returns whether the ref is pointing at a leaf page/node. | ||||
| func (r *elemRef) isLeaf() bool { | ||||
| 	if r.node != nil { | ||||
| 		return r.node.isLeaf | ||||
| 	} | ||||
| 	return (r.page.flags & leafPageFlag) != 0 | ||||
| } | ||||
|  | ||||
| // count returns the number of inodes or page elements. | ||||
| func (r *elemRef) count() int { | ||||
| 	if r.node != nil { | ||||
| 		return len(r.node.inodes) | ||||
| 	} | ||||
| 	return int(r.page.count) | ||||
| } | ||||
							
								
								
									
										993
									
								
								vendor/github.com/boltdb/bolt/db.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										993
									
								
								vendor/github.com/boltdb/bolt/db.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,993 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"hash/fnv" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"runtime/debug" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // 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 | ||||
|  | ||||
| // 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 | ||||
| ) | ||||
|  | ||||
| // DB represents a collection of buckets persisted to a file on disk. | ||||
| // All data access is performed through transactions which can be obtained through the DB. | ||||
| // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. | ||||
| type DB struct { | ||||
| 	// When enabled, the database will perform a Check() after every commit. | ||||
| 	// A panic is issued if the database is in an inconsistent state. This | ||||
| 	// flag has a large performance impact so it should only be used for | ||||
| 	// debugging purposes. | ||||
| 	StrictMode bool | ||||
|  | ||||
| 	// Setting the NoSync flag will cause the database to skip fsync() | ||||
| 	// calls after each commit. This can be useful when bulk loading data | ||||
| 	// into a database and you can restart the bulk load in the event of | ||||
| 	// a system failure or database corruption. Do not set this flag for | ||||
| 	// normal use. | ||||
| 	// | ||||
| 	// If the package global IgnoreNoSync constant is true, this value is | ||||
| 	// ignored.  See the comment on that constant for more details. | ||||
| 	// | ||||
| 	// THIS IS UNSAFE. PLEASE USE WITH CAUTION. | ||||
| 	NoSync bool | ||||
|  | ||||
| 	// When true, skips the truncate call when growing the database. | ||||
| 	// Setting this to true is only safe on non-ext3/ext4 systems. | ||||
| 	// Skipping truncation avoids preallocation of hard drive space and | ||||
| 	// bypasses a truncate() and fsync() syscall on remapping. | ||||
| 	// | ||||
| 	// https://github.com/boltdb/bolt/issues/284 | ||||
| 	NoGrowSync bool | ||||
|  | ||||
| 	// If you want to read the entire database fast, you can set MmapFlag to | ||||
| 	// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead. | ||||
| 	MmapFlags int | ||||
|  | ||||
| 	// MaxBatchSize is the maximum size of a batch. Default value is | ||||
| 	// copied from DefaultMaxBatchSize in Open. | ||||
| 	// | ||||
| 	// If <=0, disables batching. | ||||
| 	// | ||||
| 	// Do not change concurrently with calls to Batch. | ||||
| 	MaxBatchSize int | ||||
|  | ||||
| 	// MaxBatchDelay is the maximum delay before a batch starts. | ||||
| 	// Default value is copied from DefaultMaxBatchDelay in Open. | ||||
| 	// | ||||
| 	// If <=0, effectively disables batching. | ||||
| 	// | ||||
| 	// Do not change concurrently with calls to Batch. | ||||
| 	MaxBatchDelay time.Duration | ||||
|  | ||||
| 	// AllocSize is the amount of space allocated when the database | ||||
| 	// needs to create new pages. This is done to amortize the cost | ||||
| 	// of truncate() and fsync() when growing the data file. | ||||
| 	AllocSize int | ||||
|  | ||||
| 	path     string | ||||
| 	file     *os.File | ||||
| 	lockfile *os.File // windows only | ||||
| 	dataref  []byte // mmap'ed readonly, write throws SEGV | ||||
| 	data     *[maxMapSize]byte | ||||
| 	datasz   int | ||||
| 	filesz   int // current on disk file size | ||||
| 	meta0    *meta | ||||
| 	meta1    *meta | ||||
| 	pageSize int | ||||
| 	opened   bool | ||||
| 	rwtx     *Tx | ||||
| 	txs      []*Tx | ||||
| 	freelist *freelist | ||||
| 	stats    Stats | ||||
|  | ||||
| 	batchMu sync.Mutex | ||||
| 	batch   *batch | ||||
|  | ||||
| 	rwlock   sync.Mutex   // Allows only one writer at a time. | ||||
| 	metalock sync.Mutex   // Protects meta page access. | ||||
| 	mmaplock sync.RWMutex // Protects mmap access during remapping. | ||||
| 	statlock sync.RWMutex // Protects stats access. | ||||
|  | ||||
| 	ops struct { | ||||
| 		writeAt func(b []byte, off int64) (n int, err error) | ||||
| 	} | ||||
|  | ||||
| 	// Read only mode. | ||||
| 	// When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately. | ||||
| 	readOnly bool | ||||
| } | ||||
|  | ||||
| // Path returns the path to currently open database file. | ||||
| func (db *DB) Path() string { | ||||
| 	return db.path | ||||
| } | ||||
|  | ||||
| // GoString returns the Go string representation of the database. | ||||
| func (db *DB) GoString() string { | ||||
| 	return fmt.Sprintf("bolt.DB{path:%q}", db.path) | ||||
| } | ||||
|  | ||||
| // String returns the string representation of the database. | ||||
| func (db *DB) String() string { | ||||
| 	return fmt.Sprintf("DB<%q>", db.path) | ||||
| } | ||||
|  | ||||
| // Open creates and opens a database at the given path. | ||||
| // If the file does not exist then it will be created automatically. | ||||
| // 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) { | ||||
| 	var db = &DB{opened: true} | ||||
|  | ||||
| 	// Set default options if no options are provided. | ||||
| 	if options == nil { | ||||
| 		options = DefaultOptions | ||||
| 	} | ||||
| 	db.NoGrowSync = options.NoGrowSync | ||||
| 	db.MmapFlags = options.MmapFlags | ||||
|  | ||||
| 	// Set default values for later DB operations. | ||||
| 	db.MaxBatchSize = DefaultMaxBatchSize | ||||
| 	db.MaxBatchDelay = DefaultMaxBatchDelay | ||||
| 	db.AllocSize = DefaultAllocSize | ||||
|  | ||||
| 	flag := os.O_RDWR | ||||
| 	if options.ReadOnly { | ||||
| 		flag = os.O_RDONLY | ||||
| 		db.readOnly = true | ||||
| 	} | ||||
|  | ||||
| 	// Open data file and separate sync handler for metadata writes. | ||||
| 	db.path = path | ||||
| 	var err error | ||||
| 	if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil { | ||||
| 		_ = db.close() | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Lock file so that other processes using Bolt in read-write mode cannot | ||||
| 	// use the database  at the same time. This would cause corruption since | ||||
| 	// the two processes would write meta pages and free pages separately. | ||||
| 	// The database file is locked exclusively (only one process can grab the lock) | ||||
| 	// if !options.ReadOnly. | ||||
| 	// 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). | ||||
| 	if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil { | ||||
| 		_ = db.close() | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Default values for test hooks | ||||
| 	db.ops.writeAt = db.file.WriteAt | ||||
|  | ||||
| 	// Initialize the database if it doesn't exist. | ||||
| 	if info, err := db.file.Stat(); err != nil { | ||||
| 		return nil, err | ||||
| 	} else if info.Size() == 0 { | ||||
| 		// Initialize new files with meta pages. | ||||
| 		if err := db.init(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		// Read the first meta page to determine the page size. | ||||
| 		var buf [0x1000]byte | ||||
| 		if _, err := db.file.ReadAt(buf[:], 0); err == nil { | ||||
| 			m := db.pageInBuffer(buf[:], 0).meta() | ||||
| 			if err := m.validate(); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			db.pageSize = int(m.pageSize) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Memory map the data file. | ||||
| 	if err := db.mmap(options.InitialMmapSize); err != nil { | ||||
| 		_ = db.close() | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Read in the freelist. | ||||
| 	db.freelist = newFreelist() | ||||
| 	db.freelist.read(db.page(db.meta().freelist)) | ||||
|  | ||||
| 	// Mark the database as opened and return. | ||||
| 	return db, nil | ||||
| } | ||||
|  | ||||
| // mmap opens the underlying memory-mapped file and initializes the meta references. | ||||
| // minsz is the minimum size that the new mmap can be. | ||||
| func (db *DB) mmap(minsz int) error { | ||||
| 	db.mmaplock.Lock() | ||||
| 	defer db.mmaplock.Unlock() | ||||
|  | ||||
| 	info, err := db.file.Stat() | ||||
| 	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. | ||||
| 	var size = int(info.Size()) | ||||
| 	if size < minsz { | ||||
| 		size = minsz | ||||
| 	} | ||||
| 	size, err = db.mmapSize(size) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Dereference all mmap references before unmapping. | ||||
| 	if db.rwtx != nil { | ||||
| 		db.rwtx.root.dereference() | ||||
| 	} | ||||
|  | ||||
| 	// Unmap existing data before continuing. | ||||
| 	if err := db.munmap(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Memory-map the data file as a byte slice. | ||||
| 	if err := mmap(db, size); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Save references to the meta pages. | ||||
| 	db.meta0 = db.page(0).meta() | ||||
| 	db.meta1 = db.page(1).meta() | ||||
|  | ||||
| 	// Validate the meta pages. | ||||
| 	if err := db.meta0.validate(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := db.meta1.validate(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // munmap unmaps the data file from memory. | ||||
| func (db *DB) munmap() error { | ||||
| 	if err := munmap(db); err != nil { | ||||
| 		return fmt.Errorf("unmap error: " + err.Error()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // mmapSize determines the appropriate size for the mmap given the current size | ||||
| // of the database. The minimum size is 32KB and doubles until it reaches 1GB. | ||||
| // Returns an error if the new mmap size is greater than the max allowed. | ||||
| func (db *DB) mmapSize(size int) (int, error) { | ||||
| 	// Double the size from 32KB until 1GB. | ||||
| 	for i := uint(15); i <= 30; i++ { | ||||
| 		if size <= 1<<i { | ||||
| 			return 1 << i, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Verify the requested size is not above the maximum allowed. | ||||
| 	if size > maxMapSize { | ||||
| 		return 0, fmt.Errorf("mmap too large") | ||||
| 	} | ||||
|  | ||||
| 	// If larger than 1GB then grow by 1GB at a time. | ||||
| 	sz := int64(size) | ||||
| 	if remainder := sz % int64(maxMmapStep); remainder > 0 { | ||||
| 		sz += int64(maxMmapStep) - remainder | ||||
| 	} | ||||
|  | ||||
| 	// Ensure that the mmap size is a multiple of the page size. | ||||
| 	// This should always be true since we're incrementing in MBs. | ||||
| 	pageSize := int64(db.pageSize) | ||||
| 	if (sz % pageSize) != 0 { | ||||
| 		sz = ((sz / pageSize) + 1) * pageSize | ||||
| 	} | ||||
|  | ||||
| 	// If we've exceeded the max size then only grow up to the max size. | ||||
| 	if sz > maxMapSize { | ||||
| 		sz = maxMapSize | ||||
| 	} | ||||
|  | ||||
| 	return int(sz), nil | ||||
| } | ||||
|  | ||||
| // init creates a new database file and initializes its meta pages. | ||||
| func (db *DB) init() error { | ||||
| 	// Set the page size to the OS page size. | ||||
| 	db.pageSize = os.Getpagesize() | ||||
|  | ||||
| 	// Create two meta pages on a buffer. | ||||
| 	buf := make([]byte, db.pageSize*4) | ||||
| 	for i := 0; i < 2; i++ { | ||||
| 		p := db.pageInBuffer(buf[:], pgid(i)) | ||||
| 		p.id = pgid(i) | ||||
| 		p.flags = metaPageFlag | ||||
|  | ||||
| 		// Initialize the meta page. | ||||
| 		m := p.meta() | ||||
| 		m.magic = magic | ||||
| 		m.version = version | ||||
| 		m.pageSize = uint32(db.pageSize) | ||||
| 		m.freelist = 2 | ||||
| 		m.root = bucket{root: 3} | ||||
| 		m.pgid = 4 | ||||
| 		m.txid = txid(i) | ||||
| 	} | ||||
|  | ||||
| 	// Write an empty freelist at page 3. | ||||
| 	p := db.pageInBuffer(buf[:], pgid(2)) | ||||
| 	p.id = pgid(2) | ||||
| 	p.flags = freelistPageFlag | ||||
| 	p.count = 0 | ||||
|  | ||||
| 	// Write an empty leaf page at page 4. | ||||
| 	p = db.pageInBuffer(buf[:], pgid(3)) | ||||
| 	p.id = pgid(3) | ||||
| 	p.flags = leafPageFlag | ||||
| 	p.count = 0 | ||||
|  | ||||
| 	// Write the buffer to our data file. | ||||
| 	if _, err := db.ops.writeAt(buf, 0); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := fdatasync(db); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Close releases all database resources. | ||||
| // All transactions must be closed before closing the database. | ||||
| func (db *DB) Close() error { | ||||
| 	db.rwlock.Lock() | ||||
| 	defer db.rwlock.Unlock() | ||||
|  | ||||
| 	db.metalock.Lock() | ||||
| 	defer db.metalock.Unlock() | ||||
|  | ||||
| 	db.mmaplock.RLock() | ||||
| 	defer db.mmaplock.RUnlock() | ||||
|  | ||||
| 	return db.close() | ||||
| } | ||||
|  | ||||
| func (db *DB) close() error { | ||||
| 	if !db.opened { | ||||
| 		return nil | ||||
| 	} | ||||
| 	 | ||||
| 	db.opened = false | ||||
|  | ||||
| 	db.freelist = nil | ||||
| 	db.path = "" | ||||
|  | ||||
| 	// Clear ops. | ||||
| 	db.ops.writeAt = nil | ||||
|  | ||||
| 	// Close the mmap. | ||||
| 	if err := db.munmap(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Close file handles. | ||||
| 	if db.file != nil { | ||||
| 		// No need to unlock read-only file. | ||||
| 		if !db.readOnly { | ||||
| 			// Unlock the file. | ||||
| 			if err := funlock(db); err != nil { | ||||
| 				log.Printf("bolt.Close(): funlock error: %s", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Close the file descriptor. | ||||
| 		if err := db.file.Close(); err != nil { | ||||
| 			return fmt.Errorf("db file close: %s", err) | ||||
| 		} | ||||
| 		db.file = nil | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Begin starts a new transaction. | ||||
| // Multiple read-only transactions can be used concurrently but only one | ||||
| // write transaction can be used at a time. Starting multiple write transactions | ||||
| // will cause the calls to block and be serialized until the current write | ||||
| // transaction finishes. | ||||
| // | ||||
| // Transactions should not be dependent on one another. Opening a read | ||||
| // transaction and a write transaction in the same goroutine can cause the | ||||
| // writer to deadlock because the database periodically needs to re-mmap itself | ||||
| // as it grows and it cannot do that while a read transaction is open. | ||||
| // | ||||
| // If a long running read transaction (for example, a snapshot transaction) is | ||||
| // needed, you might want to set DB.InitialMmapSize to a large enough value | ||||
| // to avoid potential blocking of write transaction. | ||||
| // | ||||
| // IMPORTANT: You must close read-only transactions after you are finished or | ||||
| // else the database will not reclaim old pages. | ||||
| func (db *DB) Begin(writable bool) (*Tx, error) { | ||||
| 	if writable { | ||||
| 		return db.beginRWTx() | ||||
| 	} | ||||
| 	return db.beginTx() | ||||
| } | ||||
|  | ||||
| func (db *DB) beginTx() (*Tx, error) { | ||||
| 	// 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 | ||||
| 	// write transaction will obtain them. | ||||
| 	db.metalock.Lock() | ||||
|  | ||||
| 	// Obtain a read-only lock on the mmap. When the mmap is remapped it will | ||||
| 	// obtain a write lock so all transactions must finish before it can be | ||||
| 	// remapped. | ||||
| 	db.mmaplock.RLock() | ||||
|  | ||||
| 	// Exit if the database is not open yet. | ||||
| 	if !db.opened { | ||||
| 		db.mmaplock.RUnlock() | ||||
| 		db.metalock.Unlock() | ||||
| 		return nil, ErrDatabaseNotOpen | ||||
| 	} | ||||
|  | ||||
| 	// Create a transaction associated with the database. | ||||
| 	t := &Tx{} | ||||
| 	t.init(db) | ||||
|  | ||||
| 	// Keep track of transaction until it closes. | ||||
| 	db.txs = append(db.txs, t) | ||||
| 	n := len(db.txs) | ||||
|  | ||||
| 	// Unlock the meta pages. | ||||
| 	db.metalock.Unlock() | ||||
|  | ||||
| 	// Update the transaction stats. | ||||
| 	db.statlock.Lock() | ||||
| 	db.stats.TxN++ | ||||
| 	db.stats.OpenTxN = n | ||||
| 	db.statlock.Unlock() | ||||
|  | ||||
| 	return t, nil | ||||
| } | ||||
|  | ||||
| func (db *DB) beginRWTx() (*Tx, error) { | ||||
| 	// If the database was opened with Options.ReadOnly, return an error. | ||||
| 	if db.readOnly { | ||||
| 		return nil, ErrDatabaseReadOnly | ||||
| 	} | ||||
|  | ||||
| 	// Obtain writer lock. This is released by the transaction when it closes. | ||||
| 	// This enforces only one writer transaction at a time. | ||||
| 	db.rwlock.Lock() | ||||
|  | ||||
| 	// Once we have the writer lock then we can lock the meta pages so that | ||||
| 	// we can set up the transaction. | ||||
| 	db.metalock.Lock() | ||||
| 	defer db.metalock.Unlock() | ||||
|  | ||||
| 	// Exit if the database is not open yet. | ||||
| 	if !db.opened { | ||||
| 		db.rwlock.Unlock() | ||||
| 		return nil, ErrDatabaseNotOpen | ||||
| 	} | ||||
|  | ||||
| 	// Create a transaction associated with the database. | ||||
| 	t := &Tx{writable: true} | ||||
| 	t.init(db) | ||||
| 	db.rwtx = t | ||||
|  | ||||
| 	// Free any pages associated with closed read-only transactions. | ||||
| 	var minid txid = 0xFFFFFFFFFFFFFFFF | ||||
| 	for _, t := range db.txs { | ||||
| 		if t.meta.txid < minid { | ||||
| 			minid = t.meta.txid | ||||
| 		} | ||||
| 	} | ||||
| 	if minid > 0 { | ||||
| 		db.freelist.release(minid - 1) | ||||
| 	} | ||||
|  | ||||
| 	return t, nil | ||||
| } | ||||
|  | ||||
| // removeTx removes a transaction from the database. | ||||
| func (db *DB) removeTx(tx *Tx) { | ||||
| 	// Release the read lock on the mmap. | ||||
| 	db.mmaplock.RUnlock() | ||||
|  | ||||
| 	// Use the meta lock to restrict access to the DB object. | ||||
| 	db.metalock.Lock() | ||||
|  | ||||
| 	// Remove the transaction. | ||||
| 	for i, t := range db.txs { | ||||
| 		if t == tx { | ||||
| 			db.txs = append(db.txs[:i], db.txs[i+1:]...) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	n := len(db.txs) | ||||
|  | ||||
| 	// Unlock the meta pages. | ||||
| 	db.metalock.Unlock() | ||||
|  | ||||
| 	// Merge statistics. | ||||
| 	db.statlock.Lock() | ||||
| 	db.stats.OpenTxN = n | ||||
| 	db.stats.TxStats.add(&tx.stats) | ||||
| 	db.statlock.Unlock() | ||||
| } | ||||
|  | ||||
| // Update executes a function within the context of a read-write managed transaction. | ||||
| // If no error is returned from the function then the transaction is committed. | ||||
| // If an error is returned then the entire transaction is rolled back. | ||||
| // Any error that is returned from the function or returned from the commit is | ||||
| // returned from the Update() method. | ||||
| // | ||||
| // Attempting to manually commit or rollback within the function will cause a panic. | ||||
| func (db *DB) Update(fn func(*Tx) error) error { | ||||
| 	t, err := db.Begin(true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Make sure the transaction rolls back in the event of a panic. | ||||
| 	defer func() { | ||||
| 		if t.db != nil { | ||||
| 			t.rollback() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Mark as a managed tx so that the inner function cannot manually commit. | ||||
| 	t.managed = true | ||||
|  | ||||
| 	// If an error is returned from the function then rollback and return error. | ||||
| 	err = fn(t) | ||||
| 	t.managed = false | ||||
| 	if err != nil { | ||||
| 		_ = t.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return t.Commit() | ||||
| } | ||||
|  | ||||
| // View executes a function within the context of a managed read-only transaction. | ||||
| // Any error that is returned from the function is returned from the View() method. | ||||
| // | ||||
| // Attempting to manually rollback within the function will cause a panic. | ||||
| func (db *DB) View(fn func(*Tx) error) error { | ||||
| 	t, err := db.Begin(false) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Make sure the transaction rolls back in the event of a panic. | ||||
| 	defer func() { | ||||
| 		if t.db != nil { | ||||
| 			t.rollback() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Mark as a managed tx so that the inner function cannot manually rollback. | ||||
| 	t.managed = true | ||||
|  | ||||
| 	// If an error is returned from the function then pass it through. | ||||
| 	err = fn(t) | ||||
| 	t.managed = false | ||||
| 	if err != nil { | ||||
| 		_ = t.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := t.Rollback(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Batch calls fn as part of a batch. It behaves similar to Update, | ||||
| // except: | ||||
| // | ||||
| // 1. concurrent Batch calls can be combined into a single Bolt | ||||
| // transaction. | ||||
| // | ||||
| // 2. the function passed to Batch may be called multiple times, | ||||
| // regardless of whether it returns error or not. | ||||
| // | ||||
| // This means that Batch function side effects must be idempotent and | ||||
| // take permanent effect only after a successful return is seen in | ||||
| // caller. | ||||
| // | ||||
| // The maximum batch size and delay can be adjusted with DB.MaxBatchSize | ||||
| // and DB.MaxBatchDelay, respectively. | ||||
| // | ||||
| // Batch is only useful when there are multiple goroutines calling it. | ||||
| func (db *DB) Batch(fn func(*Tx) error) error { | ||||
| 	errCh := make(chan error, 1) | ||||
|  | ||||
| 	db.batchMu.Lock() | ||||
| 	if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) { | ||||
| 		// There is no existing batch, or the existing batch is full; start a new one. | ||||
| 		db.batch = &batch{ | ||||
| 			db: db, | ||||
| 		} | ||||
| 		db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger) | ||||
| 	} | ||||
| 	db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh}) | ||||
| 	if len(db.batch.calls) >= db.MaxBatchSize { | ||||
| 		// wake up batch, it's ready to run | ||||
| 		go db.batch.trigger() | ||||
| 	} | ||||
| 	db.batchMu.Unlock() | ||||
|  | ||||
| 	err := <-errCh | ||||
| 	if err == trySolo { | ||||
| 		err = db.Update(fn) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| type call struct { | ||||
| 	fn  func(*Tx) error | ||||
| 	err chan<- error | ||||
| } | ||||
|  | ||||
| type batch struct { | ||||
| 	db    *DB | ||||
| 	timer *time.Timer | ||||
| 	start sync.Once | ||||
| 	calls []call | ||||
| } | ||||
|  | ||||
| // trigger runs the batch if it hasn't already been run. | ||||
| func (b *batch) trigger() { | ||||
| 	b.start.Do(b.run) | ||||
| } | ||||
|  | ||||
| // run performs the transactions in the batch and communicates results | ||||
| // back to DB.Batch. | ||||
| func (b *batch) run() { | ||||
| 	b.db.batchMu.Lock() | ||||
| 	b.timer.Stop() | ||||
| 	// Make sure no new work is added to this batch, but don't break | ||||
| 	// other batches. | ||||
| 	if b.db.batch == b { | ||||
| 		b.db.batch = nil | ||||
| 	} | ||||
| 	b.db.batchMu.Unlock() | ||||
|  | ||||
| retry: | ||||
| 	for len(b.calls) > 0 { | ||||
| 		var failIdx = -1 | ||||
| 		err := b.db.Update(func(tx *Tx) error { | ||||
| 			for i, c := range b.calls { | ||||
| 				if err := safelyCall(c.fn, tx); err != nil { | ||||
| 					failIdx = i | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
|  | ||||
| 		if failIdx >= 0 { | ||||
| 			// take the failing transaction out of the batch. it's | ||||
| 			// safe to shorten b.calls here because db.batch no longer | ||||
| 			// points to us, and we hold the mutex anyway. | ||||
| 			c := b.calls[failIdx] | ||||
| 			b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1] | ||||
| 			// tell the submitter re-run it solo, continue with the rest of the batch | ||||
| 			c.err <- trySolo | ||||
| 			continue retry | ||||
| 		} | ||||
|  | ||||
| 		// pass success, or bolt internal errors, to all callers | ||||
| 		for _, c := range b.calls { | ||||
| 			if c.err != nil { | ||||
| 				c.err <- err | ||||
| 			} | ||||
| 		} | ||||
| 		break retry | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // trySolo is a special sentinel error value used for signaling that a | ||||
| // transaction function should be re-run. It should never be seen by | ||||
| // callers. | ||||
| var trySolo = errors.New("batch function returned an error and should be re-run solo") | ||||
|  | ||||
| type panicked struct { | ||||
| 	reason interface{} | ||||
| } | ||||
|  | ||||
| func (p panicked) Error() string { | ||||
| 	if err, ok := p.reason.(error); ok { | ||||
| 		return err.Error() | ||||
| 	} | ||||
| 	return fmt.Sprintf("panic: %v", p.reason) | ||||
| } | ||||
|  | ||||
| func safelyCall(fn func(*Tx) error, tx *Tx) (err error) { | ||||
| 	defer func() { | ||||
| 		if p := recover(); p != nil { | ||||
| 			err = panicked{p} | ||||
| 		} | ||||
| 	}() | ||||
| 	return fn(tx) | ||||
| } | ||||
|  | ||||
| // Sync executes fdatasync() against the database file handle. | ||||
| // | ||||
| // 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. | ||||
| func (db *DB) Sync() error { return fdatasync(db) } | ||||
|  | ||||
| // Stats retrieves ongoing performance stats for the database. | ||||
| // This is only updated when a transaction closes. | ||||
| func (db *DB) Stats() Stats { | ||||
| 	db.statlock.RLock() | ||||
| 	defer db.statlock.RUnlock() | ||||
| 	return db.stats | ||||
| } | ||||
|  | ||||
| // This is for internal access to the raw data bytes from the C cursor, use | ||||
| // carefully, or not at all. | ||||
| func (db *DB) Info() *Info { | ||||
| 	return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize} | ||||
| } | ||||
|  | ||||
| // page retrieves a page reference from the mmap based on the current page size. | ||||
| func (db *DB) page(id pgid) *page { | ||||
| 	pos := id * pgid(db.pageSize) | ||||
| 	return (*page)(unsafe.Pointer(&db.data[pos])) | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)])) | ||||
| } | ||||
|  | ||||
| // meta retrieves the current meta page reference. | ||||
| func (db *DB) meta() *meta { | ||||
| 	if db.meta0.txid > db.meta1.txid { | ||||
| 		return db.meta0 | ||||
| 	} | ||||
| 	return db.meta1 | ||||
| } | ||||
|  | ||||
| // allocate returns a contiguous block of memory starting at a given page. | ||||
| func (db *DB) allocate(count int) (*page, error) { | ||||
| 	// Allocate a temporary buffer for the page. | ||||
| 	buf := make([]byte, count*db.pageSize) | ||||
| 	p := (*page)(unsafe.Pointer(&buf[0])) | ||||
| 	p.overflow = uint32(count - 1) | ||||
|  | ||||
| 	// Use pages from the freelist if they are available. | ||||
| 	if p.id = db.freelist.allocate(count); p.id != 0 { | ||||
| 		return p, nil | ||||
| 	} | ||||
|  | ||||
| 	// Resize mmap() if we're at the end. | ||||
| 	p.id = db.rwtx.meta.pgid | ||||
| 	var minsz = int((p.id+pgid(count))+1) * db.pageSize | ||||
| 	if minsz >= db.datasz { | ||||
| 		if err := db.mmap(minsz); err != nil { | ||||
| 			return nil, fmt.Errorf("mmap allocate error: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Move the page id high water mark. | ||||
| 	db.rwtx.meta.pgid += pgid(count) | ||||
|  | ||||
| 	return p, nil | ||||
| } | ||||
|  | ||||
| // grow grows the size of the database to the given sz. | ||||
| func (db *DB) grow(sz int) error { | ||||
| 	// Ignore if the new size is less than available file size. | ||||
| 	if sz <= db.filesz { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// If the data is smaller than the alloc size then only allocate what's needed. | ||||
| 	// Once it goes over the allocation size then allocate in chunks. | ||||
| 	if db.datasz < db.AllocSize { | ||||
| 		sz = db.datasz | ||||
| 	} else { | ||||
| 		sz += db.AllocSize | ||||
| 	} | ||||
|  | ||||
| 	// Truncate and fsync to ensure file size metadata is flushed. | ||||
| 	// https://github.com/boltdb/bolt/issues/284 | ||||
| 	if !db.NoGrowSync && !db.readOnly { | ||||
| 		if runtime.GOOS != "windows" { | ||||
| 			if err := db.file.Truncate(int64(sz)); err != nil { | ||||
| 				return fmt.Errorf("file resize error: %s", err) | ||||
| 			} | ||||
| 		} | ||||
| 		if err := db.file.Sync(); err != nil { | ||||
| 			return fmt.Errorf("file sync error: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	db.filesz = sz | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (db *DB) IsReadOnly() bool { | ||||
| 	return db.readOnly | ||||
| } | ||||
|  | ||||
| // Options represents the options that can be set when opening a database. | ||||
| type Options struct { | ||||
| 	// 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 | ||||
| 	// available on Darwin and Linux. | ||||
| 	Timeout time.Duration | ||||
|  | ||||
| 	// Sets the DB.NoGrowSync flag before memory mapping the file. | ||||
| 	NoGrowSync bool | ||||
|  | ||||
| 	// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to | ||||
| 	// grab a shared lock (UNIX). | ||||
| 	ReadOnly bool | ||||
|  | ||||
| 	// Sets the DB.MmapFlags flag before memory mapping the file. | ||||
| 	MmapFlags int | ||||
|  | ||||
| 	// InitialMmapSize is the initial mmap size of the database | ||||
| 	// in bytes. Read transactions won't block write transaction | ||||
| 	// if the InitialMmapSize is large enough to hold database mmap | ||||
| 	// size. (See DB.Begin for more information) | ||||
| 	// | ||||
| 	// If <=0, the initial map size is 0. | ||||
| 	// If initialMmapSize is smaller than the previous database size, | ||||
| 	// it takes no effect. | ||||
| 	InitialMmapSize int | ||||
| } | ||||
|  | ||||
| // DefaultOptions represent the options used if nil options are passed into Open(). | ||||
| // No timeout is used which will cause Bolt to wait indefinitely for a lock. | ||||
| var DefaultOptions = &Options{ | ||||
| 	Timeout:    0, | ||||
| 	NoGrowSync: false, | ||||
| } | ||||
|  | ||||
| // Stats represents statistics about the database. | ||||
| type Stats struct { | ||||
| 	// Freelist stats | ||||
| 	FreePageN     int // total number of free pages on the freelist | ||||
| 	PendingPageN  int // total number of pending pages on the freelist | ||||
| 	FreeAlloc     int // total bytes allocated in free pages | ||||
| 	FreelistInuse int // total bytes used by the freelist | ||||
|  | ||||
| 	// Transaction stats | ||||
| 	TxN     int // total number of started read transactions | ||||
| 	OpenTxN int // number of currently open read transactions | ||||
|  | ||||
| 	TxStats TxStats // global, ongoing stats. | ||||
| } | ||||
|  | ||||
| // Sub calculates and returns the difference between two sets of database stats. | ||||
| // This is useful when obtaining stats at two different points and time and | ||||
| // you need the performance counters that occurred within that time span. | ||||
| func (s *Stats) Sub(other *Stats) Stats { | ||||
| 	if other == nil { | ||||
| 		return *s | ||||
| 	} | ||||
| 	var diff Stats | ||||
| 	diff.FreePageN = s.FreePageN | ||||
| 	diff.PendingPageN = s.PendingPageN | ||||
| 	diff.FreeAlloc = s.FreeAlloc | ||||
| 	diff.FreelistInuse = s.FreelistInuse | ||||
| 	diff.TxN = other.TxN - s.TxN | ||||
| 	diff.TxStats = s.TxStats.Sub(&other.TxStats) | ||||
| 	return diff | ||||
| } | ||||
|  | ||||
| func (s *Stats) add(other *Stats) { | ||||
| 	s.TxStats.add(&other.TxStats) | ||||
| } | ||||
|  | ||||
| type Info struct { | ||||
| 	Data     uintptr | ||||
| 	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.checksum != 0 && m.checksum != m.sum64() { | ||||
| 		return ErrChecksum | ||||
| 	} else if m.magic != magic { | ||||
| 		return ErrInvalid | ||||
| 	} else if m.version != version { | ||||
| 		return ErrVersionMismatch | ||||
| 	} | ||||
| 	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 { | ||||
| 		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...)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func warn(v ...interface{})              { fmt.Fprintln(os.Stderr, v...) } | ||||
| func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) } | ||||
|  | ||||
| func printstack() { | ||||
| 	stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n") | ||||
| 	fmt.Fprintln(os.Stderr, stack) | ||||
| } | ||||
							
								
								
									
										44
									
								
								vendor/github.com/boltdb/bolt/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								vendor/github.com/boltdb/bolt/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
| Package bolt implements a low-level key/value store in pure Go. It supports | ||||
| fully serializable transactions, ACID semantics, and lock-free MVCC with | ||||
| multiple readers and a single writer. Bolt can be used for projects that | ||||
| want a simple data store without the need to add large dependencies such as | ||||
| Postgres or MySQL. | ||||
|  | ||||
| Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is | ||||
| optimized for fast read access and does not require recovery in the event of a | ||||
| system crash. Transactions which have not finished committing will simply be | ||||
| rolled back in the event of a crash. | ||||
|  | ||||
| The design of Bolt is based on Howard Chu's LMDB database project. | ||||
|  | ||||
| Bolt currently works on Windows, Mac OS X, and Linux. | ||||
|  | ||||
|  | ||||
| Basics | ||||
|  | ||||
| There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is | ||||
| a collection of buckets and is represented by a single file on disk. A bucket is | ||||
| a collection of unique keys that are associated with values. | ||||
|  | ||||
| Transactions provide either read-only or read-write access to the database. | ||||
| Read-only transactions can retrieve key/value pairs and can use Cursors to | ||||
| iterate over the dataset sequentially. Read-write transactions can create and | ||||
| delete buckets and can insert and remove keys. Only one read-write transaction | ||||
| is allowed at a time. | ||||
|  | ||||
|  | ||||
| Caveats | ||||
|  | ||||
| The database uses a read-only, memory-mapped data file to ensure that | ||||
| applications cannot corrupt the database, however, this means that keys and | ||||
| values returned from Bolt cannot be changed. Writing to a read-only byte slice | ||||
| will cause Go to panic. | ||||
|  | ||||
| Keys and values retrieved from the database are only valid for the life of | ||||
| the transaction. When used outside the transaction, these byte slices can | ||||
| point to different data or can point to invalid memory which will cause a panic. | ||||
|  | ||||
|  | ||||
| */ | ||||
| package bolt | ||||
							
								
								
									
										70
									
								
								vendor/github.com/boltdb/bolt/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/github.com/boltdb/bolt/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| package bolt | ||||
|  | ||||
| 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") | ||||
|  | ||||
| 	// ErrDatabaseOpen is returned when opening a database that is | ||||
| 	// already open. | ||||
| 	ErrDatabaseOpen = errors.New("database already open") | ||||
|  | ||||
| 	// ErrInvalid is returned when a data file is not a Bolt-formatted database. | ||||
| 	ErrInvalid = errors.New("invalid database") | ||||
|  | ||||
| 	// ErrVersionMismatch is returned when the data file was created with a | ||||
| 	// different version of Bolt. | ||||
| 	ErrVersionMismatch = errors.New("version mismatch") | ||||
|  | ||||
| 	// ErrChecksum is returned when either meta page checksum does not match. | ||||
| 	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") | ||||
| ) | ||||
|  | ||||
| // 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 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") | ||||
| ) | ||||
							
								
								
									
										242
									
								
								vendor/github.com/boltdb/bolt/freelist.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								vendor/github.com/boltdb/bolt/freelist.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // 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 { | ||||
| 	ids     []pgid          // all free and available free page ids. | ||||
| 	pending map[txid][]pgid // mapping of soon-to-be free page ids by tx. | ||||
| 	cache   map[pgid]bool   // fast lookup of all free and pending page ids. | ||||
| } | ||||
|  | ||||
| // newFreelist returns an empty, initialized freelist. | ||||
| func newFreelist() *freelist { | ||||
| 	return &freelist{ | ||||
| 		pending: make(map[txid][]pgid), | ||||
| 		cache:   make(map[pgid]bool), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // size returns the size of the page after serialization. | ||||
| func (f *freelist) size() int { | ||||
| 	return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * f.count()) | ||||
| } | ||||
|  | ||||
| // count returns count of pages on the freelist | ||||
| func (f *freelist) count() int { | ||||
| 	return f.free_count() + f.pending_count() | ||||
| } | ||||
|  | ||||
| // free_count returns count of free pages | ||||
| func (f *freelist) free_count() int { | ||||
| 	return len(f.ids) | ||||
| } | ||||
|  | ||||
| // pending_count returns count of pending pages | ||||
| func (f *freelist) pending_count() int { | ||||
| 	var count int | ||||
| 	for _, list := range f.pending { | ||||
| 		count += len(list) | ||||
| 	} | ||||
| 	return count | ||||
| } | ||||
|  | ||||
| // all returns a list of all free ids and all pending ids in one sorted list. | ||||
| func (f *freelist) all() []pgid { | ||||
| 	m := make(pgids, 0) | ||||
|  | ||||
| 	for _, list := range f.pending { | ||||
| 		m = append(m, list...) | ||||
| 	} | ||||
|  | ||||
| 	sort.Sort(m) | ||||
| 	return pgids(f.ids).merge(m) | ||||
| } | ||||
|  | ||||
| // allocate 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) allocate(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) | ||||
| 			} | ||||
|  | ||||
| 			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. | ||||
| 	var ids = f.pending[txid] | ||||
| 	for id := p.id; id <= p.id+pgid(p.overflow); id++ { | ||||
| 		// Verify that page is not already free. | ||||
| 		if f.cache[id] { | ||||
| 			panic(fmt.Sprintf("page %d already freed", id)) | ||||
| 		} | ||||
|  | ||||
| 		// Add to the freelist and cache. | ||||
| 		ids = append(ids, id) | ||||
| 		f.cache[id] = true | ||||
| 	} | ||||
| 	f.pending[txid] = ids | ||||
| } | ||||
|  | ||||
| // 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, ids := 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, ids...) | ||||
| 			delete(f.pending, tid) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Sort(m) | ||||
| 	f.ids = pgids(f.ids).merge(m) | ||||
| } | ||||
|  | ||||
| // rollback removes the pages from a given pending tx. | ||||
| func (f *freelist) rollback(txid txid) { | ||||
| 	// Remove page ids from cache. | ||||
| 	for _, id := range f.pending[txid] { | ||||
| 		delete(f.cache, id) | ||||
| 	} | ||||
|  | ||||
| 	// Remove pages from pending list. | ||||
| 	delete(f.pending, txid) | ||||
| } | ||||
|  | ||||
| // freed returns whether a given page is in the free list. | ||||
| func (f *freelist) freed(pgid pgid) bool { | ||||
| 	return f.cache[pgid] | ||||
| } | ||||
|  | ||||
| // read initializes the freelist from a freelist page. | ||||
| func (f *freelist) read(p *page) { | ||||
| 	// 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. | ||||
| 	idx, count := 0, int(p.count) | ||||
| 	if count == 0xFFFF { | ||||
| 		idx = 1 | ||||
| 		count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0]) | ||||
| 	} | ||||
|  | ||||
| 	// Copy the list of page ids from the freelist. | ||||
| 	ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] | ||||
| 	f.ids = make([]pgid, len(ids)) | ||||
| 	copy(f.ids, ids) | ||||
|  | ||||
| 	// Make sure they're sorted. | ||||
| 	sort.Sort(pgids(f.ids)) | ||||
|  | ||||
| 	// Rebuild the page cache. | ||||
| 	f.reindex() | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| 	ids := f.all() | ||||
|  | ||||
| 	// 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. | ||||
| 	if len(ids) < 0xFFFF { | ||||
| 		p.count = uint16(len(ids)) | ||||
| 		copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids) | ||||
| 	} else { | ||||
| 		p.count = 0xFFFF | ||||
| 		((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(len(ids)) | ||||
| 		copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:], ids) | ||||
| 	} | ||||
|  | ||||
| 	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 _, pendingIDs := range f.pending { | ||||
| 		for _, pendingID := range pendingIDs { | ||||
| 			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.ids { | ||||
| 		if !pcache[id] { | ||||
| 			a = append(a, id) | ||||
| 		} | ||||
| 	} | ||||
| 	f.ids = a | ||||
|  | ||||
| 	// Once the available list is rebuilt then rebuild the free cache so that | ||||
| 	// it includes the available and pending free pages. | ||||
| 	f.reindex() | ||||
| } | ||||
|  | ||||
| // reindex rebuilds the free cache based on available and pending free lists. | ||||
| func (f *freelist) reindex() { | ||||
| 	f.cache = make(map[pgid]bool) | ||||
| 	for _, id := range f.ids { | ||||
| 		f.cache[id] = true | ||||
| 	} | ||||
| 	for _, pendingIDs := range f.pending { | ||||
| 		for _, pendingID := range pendingIDs { | ||||
| 			f.cache[pendingID] = true | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										599
									
								
								vendor/github.com/boltdb/bolt/node.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										599
									
								
								vendor/github.com/boltdb/bolt/node.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,599 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // node represents an in-memory, deserialized page. | ||||
| type node struct { | ||||
| 	bucket     *Bucket | ||||
| 	isLeaf     bool | ||||
| 	unbalanced bool | ||||
| 	spilled    bool | ||||
| 	key        []byte | ||||
| 	pgid       pgid | ||||
| 	parent     *node | ||||
| 	children   nodes | ||||
| 	inodes     inodes | ||||
| } | ||||
|  | ||||
| // root returns the top-level node this node is attached to. | ||||
| func (n *node) root() *node { | ||||
| 	if n.parent == nil { | ||||
| 		return n | ||||
| 	} | ||||
| 	return n.parent.root() | ||||
| } | ||||
|  | ||||
| // minKeys returns the minimum number of inodes this node should have. | ||||
| func (n *node) minKeys() int { | ||||
| 	if n.isLeaf { | ||||
| 		return 1 | ||||
| 	} | ||||
| 	return 2 | ||||
| } | ||||
|  | ||||
| // size returns the size of the node after serialization. | ||||
| func (n *node) size() int { | ||||
| 	sz, elsz := pageHeaderSize, n.pageElementSize() | ||||
| 	for i := 0; i < len(n.inodes); i++ { | ||||
| 		item := &n.inodes[i] | ||||
| 		sz += elsz + len(item.key) + len(item.value) | ||||
| 	} | ||||
| 	return sz | ||||
| } | ||||
|  | ||||
| // sizeLessThan returns true if the node is less than a given size. | ||||
| // This is an optimization to avoid calculating a large node when we only need | ||||
| // to know if it fits inside a certain page size. | ||||
| func (n *node) sizeLessThan(v int) bool { | ||||
| 	sz, elsz := pageHeaderSize, n.pageElementSize() | ||||
| 	for i := 0; i < len(n.inodes); i++ { | ||||
| 		item := &n.inodes[i] | ||||
| 		sz += elsz + len(item.key) + len(item.value) | ||||
| 		if sz >= v { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // pageElementSize returns the size of each page element based on the type of node. | ||||
| func (n *node) pageElementSize() int { | ||||
| 	if n.isLeaf { | ||||
| 		return leafPageElementSize | ||||
| 	} | ||||
| 	return branchPageElementSize | ||||
| } | ||||
|  | ||||
| // childAt returns the child node at a given index. | ||||
| func (n *node) childAt(index int) *node { | ||||
| 	if n.isLeaf { | ||||
| 		panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index)) | ||||
| 	} | ||||
| 	return n.bucket.node(n.inodes[index].pgid, n) | ||||
| } | ||||
|  | ||||
| // childIndex returns the index of a given child node. | ||||
| func (n *node) childIndex(child *node) int { | ||||
| 	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 }) | ||||
| 	return index | ||||
| } | ||||
|  | ||||
| // numChildren returns the number of children. | ||||
| func (n *node) numChildren() int { | ||||
| 	return len(n.inodes) | ||||
| } | ||||
|  | ||||
| // nextSibling returns the next node with the same parent. | ||||
| func (n *node) nextSibling() *node { | ||||
| 	if n.parent == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	index := n.parent.childIndex(n) | ||||
| 	if index >= n.parent.numChildren()-1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return n.parent.childAt(index + 1) | ||||
| } | ||||
|  | ||||
| // prevSibling returns the previous node with the same parent. | ||||
| func (n *node) prevSibling() *node { | ||||
| 	if n.parent == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	index := n.parent.childIndex(n) | ||||
| 	if index == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return n.parent.childAt(index - 1) | ||||
| } | ||||
|  | ||||
| // put inserts a key/value. | ||||
| func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) { | ||||
| 	if pgid >= n.bucket.tx.meta.pgid { | ||||
| 		panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid)) | ||||
| 	} else if len(oldKey) <= 0 { | ||||
| 		panic("put: zero-length old key") | ||||
| 	} else if len(newKey) <= 0 { | ||||
| 		panic("put: zero-length new key") | ||||
| 	} | ||||
|  | ||||
| 	// Find insertion index. | ||||
| 	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 }) | ||||
|  | ||||
| 	// Add capacity and shift nodes if we don't have an exact match and need to insert. | ||||
| 	exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey)) | ||||
| 	if !exact { | ||||
| 		n.inodes = append(n.inodes, inode{}) | ||||
| 		copy(n.inodes[index+1:], n.inodes[index:]) | ||||
| 	} | ||||
|  | ||||
| 	inode := &n.inodes[index] | ||||
| 	inode.flags = flags | ||||
| 	inode.key = newKey | ||||
| 	inode.value = value | ||||
| 	inode.pgid = pgid | ||||
| 	_assert(len(inode.key) > 0, "put: zero-length inode key") | ||||
| } | ||||
|  | ||||
| // del removes a key from the node. | ||||
| func (n *node) del(key []byte) { | ||||
| 	// Find index of key. | ||||
| 	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 }) | ||||
|  | ||||
| 	// Exit if the key isn't found. | ||||
| 	if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Delete inode from the node. | ||||
| 	n.inodes = append(n.inodes[:index], n.inodes[index+1:]...) | ||||
|  | ||||
| 	// Mark the node as needing rebalancing. | ||||
| 	n.unbalanced = true | ||||
| } | ||||
|  | ||||
| // read initializes the node from a page. | ||||
| func (n *node) read(p *page) { | ||||
| 	n.pgid = p.id | ||||
| 	n.isLeaf = ((p.flags & leafPageFlag) != 0) | ||||
| 	n.inodes = make(inodes, int(p.count)) | ||||
|  | ||||
| 	for i := 0; i < int(p.count); i++ { | ||||
| 		inode := &n.inodes[i] | ||||
| 		if n.isLeaf { | ||||
| 			elem := p.leafPageElement(uint16(i)) | ||||
| 			inode.flags = elem.flags | ||||
| 			inode.key = elem.key() | ||||
| 			inode.value = elem.value() | ||||
| 		} else { | ||||
| 			elem := p.branchPageElement(uint16(i)) | ||||
| 			inode.pgid = elem.pgid | ||||
| 			inode.key = elem.key() | ||||
| 		} | ||||
| 		_assert(len(inode.key) > 0, "read: zero-length inode key") | ||||
| 	} | ||||
|  | ||||
| 	// Save first key so we can find the node in the parent when we spill. | ||||
| 	if len(n.inodes) > 0 { | ||||
| 		n.key = n.inodes[0].key | ||||
| 		_assert(len(n.key) > 0, "read: zero-length node key") | ||||
| 	} else { | ||||
| 		n.key = nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // write writes the items onto one or more pages. | ||||
| func (n *node) write(p *page) { | ||||
| 	// Initialize page. | ||||
| 	if n.isLeaf { | ||||
| 		p.flags |= leafPageFlag | ||||
| 	} else { | ||||
| 		p.flags |= branchPageFlag | ||||
| 	} | ||||
|  | ||||
| 	if len(n.inodes) >= 0xFFFF { | ||||
| 		panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id)) | ||||
| 	} | ||||
| 	p.count = uint16(len(n.inodes)) | ||||
|  | ||||
| 	// Loop over each item and write it to the page. | ||||
| 	b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):] | ||||
| 	for i, item := range n.inodes { | ||||
| 		_assert(len(item.key) > 0, "write: zero-length inode key") | ||||
|  | ||||
| 		// Write the page element. | ||||
| 		if n.isLeaf { | ||||
| 			elem := p.leafPageElement(uint16(i)) | ||||
| 			elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) | ||||
| 			elem.flags = item.flags | ||||
| 			elem.ksize = uint32(len(item.key)) | ||||
| 			elem.vsize = uint32(len(item.value)) | ||||
| 		} else { | ||||
| 			elem := p.branchPageElement(uint16(i)) | ||||
| 			elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) | ||||
| 			elem.ksize = uint32(len(item.key)) | ||||
| 			elem.pgid = item.pgid | ||||
| 			_assert(elem.pgid != p.id, "write: circular dependency occurred") | ||||
| 		} | ||||
|  | ||||
| 		// If the length of key+value is larger than the max allocation size | ||||
| 		// then we need to reallocate the byte array pointer. | ||||
| 		// | ||||
| 		// See: https://github.com/boltdb/bolt/pull/335 | ||||
| 		klen, vlen := len(item.key), len(item.value) | ||||
| 		if len(b) < klen+vlen { | ||||
| 			b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:] | ||||
| 		} | ||||
|  | ||||
| 		// Write data for the element to the end of the page. | ||||
| 		copy(b[0:], item.key) | ||||
| 		b = b[klen:] | ||||
| 		copy(b[0:], item.value) | ||||
| 		b = b[vlen:] | ||||
| 	} | ||||
|  | ||||
| 	// DEBUG ONLY: n.dump() | ||||
| } | ||||
|  | ||||
| // split breaks up a node into multiple smaller nodes, if appropriate. | ||||
| // This should only be called from the spill() function. | ||||
| func (n *node) split(pageSize int) []*node { | ||||
| 	var nodes []*node | ||||
|  | ||||
| 	node := n | ||||
| 	for { | ||||
| 		// Split node into two. | ||||
| 		a, b := node.splitTwo(pageSize) | ||||
| 		nodes = append(nodes, a) | ||||
|  | ||||
| 		// If we can't split then exit the loop. | ||||
| 		if b == nil { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Set node to b so it gets split on the next iteration. | ||||
| 		node = b | ||||
| 	} | ||||
|  | ||||
| 	return nodes | ||||
| } | ||||
|  | ||||
| // splitTwo breaks up a node into two smaller nodes, if appropriate. | ||||
| // This should only be called from the split() function. | ||||
| func (n *node) splitTwo(pageSize int) (*node, *node) { | ||||
| 	// Ignore the split if the page doesn't have at least enough nodes for | ||||
| 	// two pages or if the nodes can fit in a single page. | ||||
| 	if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) { | ||||
| 		return n, nil | ||||
| 	} | ||||
|  | ||||
| 	// Determine the threshold before starting a new node. | ||||
| 	var fillPercent = n.bucket.FillPercent | ||||
| 	if fillPercent < minFillPercent { | ||||
| 		fillPercent = minFillPercent | ||||
| 	} else if fillPercent > maxFillPercent { | ||||
| 		fillPercent = maxFillPercent | ||||
| 	} | ||||
| 	threshold := int(float64(pageSize) * fillPercent) | ||||
|  | ||||
| 	// Determine split position and sizes of the two pages. | ||||
| 	splitIndex, _ := n.splitIndex(threshold) | ||||
|  | ||||
| 	// Split node into two separate nodes. | ||||
| 	// If there's no parent then we'll need to create one. | ||||
| 	if n.parent == nil { | ||||
| 		n.parent = &node{bucket: n.bucket, children: []*node{n}} | ||||
| 	} | ||||
|  | ||||
| 	// Create a new node and add it to the parent. | ||||
| 	next := &node{bucket: n.bucket, isLeaf: n.isLeaf, parent: n.parent} | ||||
| 	n.parent.children = append(n.parent.children, next) | ||||
|  | ||||
| 	// Split inodes across two nodes. | ||||
| 	next.inodes = n.inodes[splitIndex:] | ||||
| 	n.inodes = n.inodes[:splitIndex] | ||||
|  | ||||
| 	// Update the statistics. | ||||
| 	n.bucket.tx.stats.Split++ | ||||
|  | ||||
| 	return n, next | ||||
| } | ||||
|  | ||||
| // splitIndex finds the position where a page will fill a given threshold. | ||||
| // It returns the index as well as the size of the first page. | ||||
| // This is only be called from split(). | ||||
| func (n *node) splitIndex(threshold int) (index, sz int) { | ||||
| 	sz = pageHeaderSize | ||||
|  | ||||
| 	// Loop until we only have the minimum number of keys required for the second page. | ||||
| 	for i := 0; i < len(n.inodes)-minKeysPerPage; i++ { | ||||
| 		index = i | ||||
| 		inode := n.inodes[i] | ||||
| 		elsize := n.pageElementSize() + len(inode.key) + len(inode.value) | ||||
|  | ||||
| 		// If we have at least the minimum number of keys and adding another | ||||
| 		// node would put us over the threshold then exit and return. | ||||
| 		if i >= minKeysPerPage && sz+elsize > threshold { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Add the element size to the total size. | ||||
| 		sz += elsize | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // spill writes the nodes to dirty pages and splits nodes as it goes. | ||||
| // Returns an error if dirty pages cannot be allocated. | ||||
| func (n *node) spill() error { | ||||
| 	var tx = n.bucket.tx | ||||
| 	if n.spilled { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Spill child nodes first. Child nodes can materialize sibling nodes in | ||||
| 	// the case of split-merge so we cannot use a range loop. We have to check | ||||
| 	// the children size on every loop iteration. | ||||
| 	sort.Sort(n.children) | ||||
| 	for i := 0; i < len(n.children); i++ { | ||||
| 		if err := n.children[i].spill(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// We no longer need the child list because it's only used for spill tracking. | ||||
| 	n.children = nil | ||||
|  | ||||
| 	// Split nodes into appropriate sizes. The first node will always be n. | ||||
| 	var nodes = n.split(tx.db.pageSize) | ||||
| 	for _, node := range nodes { | ||||
| 		// Add node's page to the freelist if it's not new. | ||||
| 		if node.pgid > 0 { | ||||
| 			tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid)) | ||||
| 			node.pgid = 0 | ||||
| 		} | ||||
|  | ||||
| 		// Allocate contiguous space for the node. | ||||
| 		p, err := tx.allocate((node.size() / tx.db.pageSize) + 1) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Write the node. | ||||
| 		if p.id >= tx.meta.pgid { | ||||
| 			panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid)) | ||||
| 		} | ||||
| 		node.pgid = p.id | ||||
| 		node.write(p) | ||||
| 		node.spilled = true | ||||
|  | ||||
| 		// Insert into parent inodes. | ||||
| 		if node.parent != nil { | ||||
| 			var key = node.key | ||||
| 			if key == nil { | ||||
| 				key = node.inodes[0].key | ||||
| 			} | ||||
|  | ||||
| 			node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0) | ||||
| 			node.key = node.inodes[0].key | ||||
| 			_assert(len(node.key) > 0, "spill: zero-length node key") | ||||
| 		} | ||||
|  | ||||
| 		// Update the statistics. | ||||
| 		tx.stats.Spill++ | ||||
| 	} | ||||
|  | ||||
| 	// If the root node split and created a new root then we need to spill that | ||||
| 	// as well. We'll clear out the children to make sure it doesn't try to respill. | ||||
| 	if n.parent != nil && n.parent.pgid == 0 { | ||||
| 		n.children = nil | ||||
| 		return n.parent.spill() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // rebalance attempts to combine the node with sibling nodes if the node fill | ||||
| // size is below a threshold or if there are not enough keys. | ||||
| func (n *node) rebalance() { | ||||
| 	if !n.unbalanced { | ||||
| 		return | ||||
| 	} | ||||
| 	n.unbalanced = false | ||||
|  | ||||
| 	// Update statistics. | ||||
| 	n.bucket.tx.stats.Rebalance++ | ||||
|  | ||||
| 	// Ignore if node is above threshold (25%) and has enough keys. | ||||
| 	var threshold = n.bucket.tx.db.pageSize / 4 | ||||
| 	if n.size() > threshold && len(n.inodes) > n.minKeys() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Root node has special handling. | ||||
| 	if n.parent == nil { | ||||
| 		// If root node is a branch and only has one node then collapse it. | ||||
| 		if !n.isLeaf && len(n.inodes) == 1 { | ||||
| 			// Move root's child up. | ||||
| 			child := n.bucket.node(n.inodes[0].pgid, n) | ||||
| 			n.isLeaf = child.isLeaf | ||||
| 			n.inodes = child.inodes[:] | ||||
| 			n.children = child.children | ||||
|  | ||||
| 			// Reparent all child nodes being moved. | ||||
| 			for _, inode := range n.inodes { | ||||
| 				if child, ok := n.bucket.nodes[inode.pgid]; ok { | ||||
| 					child.parent = n | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Remove old child. | ||||
| 			child.parent = nil | ||||
| 			delete(n.bucket.nodes, child.pgid) | ||||
| 			child.free() | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// If node has no keys then just remove it. | ||||
| 	if n.numChildren() == 0 { | ||||
| 		n.parent.del(n.key) | ||||
| 		n.parent.removeChild(n) | ||||
| 		delete(n.bucket.nodes, n.pgid) | ||||
| 		n.free() | ||||
| 		n.parent.rebalance() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	_assert(n.parent.numChildren() > 1, "parent must have at least 2 children") | ||||
|  | ||||
| 	// Destination node is right sibling if idx == 0, otherwise left sibling. | ||||
| 	var target *node | ||||
| 	var useNextSibling = (n.parent.childIndex(n) == 0) | ||||
| 	if useNextSibling { | ||||
| 		target = n.nextSibling() | ||||
| 	} else { | ||||
| 		target = n.prevSibling() | ||||
| 	} | ||||
|  | ||||
| 	// If both this node and the target node are too small then merge them. | ||||
| 	if useNextSibling { | ||||
| 		// Reparent all child nodes being moved. | ||||
| 		for _, inode := range target.inodes { | ||||
| 			if child, ok := n.bucket.nodes[inode.pgid]; ok { | ||||
| 				child.parent.removeChild(child) | ||||
| 				child.parent = n | ||||
| 				child.parent.children = append(child.parent.children, child) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Copy over inodes from target and remove target. | ||||
| 		n.inodes = append(n.inodes, target.inodes...) | ||||
| 		n.parent.del(target.key) | ||||
| 		n.parent.removeChild(target) | ||||
| 		delete(n.bucket.nodes, target.pgid) | ||||
| 		target.free() | ||||
| 	} else { | ||||
| 		// Reparent all child nodes being moved. | ||||
| 		for _, inode := range n.inodes { | ||||
| 			if child, ok := n.bucket.nodes[inode.pgid]; ok { | ||||
| 				child.parent.removeChild(child) | ||||
| 				child.parent = target | ||||
| 				child.parent.children = append(child.parent.children, child) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Copy over inodes to target and remove node. | ||||
| 		target.inodes = append(target.inodes, n.inodes...) | ||||
| 		n.parent.del(n.key) | ||||
| 		n.parent.removeChild(n) | ||||
| 		delete(n.bucket.nodes, n.pgid) | ||||
| 		n.free() | ||||
| 	} | ||||
|  | ||||
| 	// Either this node or the target node was deleted from the parent so rebalance it. | ||||
| 	n.parent.rebalance() | ||||
| } | ||||
|  | ||||
| // removes a node from the list of in-memory children. | ||||
| // This does not affect the inodes. | ||||
| func (n *node) removeChild(target *node) { | ||||
| 	for i, child := range n.children { | ||||
| 		if child == target { | ||||
| 			n.children = append(n.children[:i], n.children[i+1:]...) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // dereference causes the node to copy all its inode key/value references to heap memory. | ||||
| // This is required when the mmap is reallocated so inodes are not pointing to stale data. | ||||
| func (n *node) dereference() { | ||||
| 	if n.key != nil { | ||||
| 		key := make([]byte, len(n.key)) | ||||
| 		copy(key, n.key) | ||||
| 		n.key = key | ||||
| 		_assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node") | ||||
| 	} | ||||
|  | ||||
| 	for i := range n.inodes { | ||||
| 		inode := &n.inodes[i] | ||||
|  | ||||
| 		key := make([]byte, len(inode.key)) | ||||
| 		copy(key, inode.key) | ||||
| 		inode.key = key | ||||
| 		_assert(len(inode.key) > 0, "dereference: zero-length inode key") | ||||
|  | ||||
| 		value := make([]byte, len(inode.value)) | ||||
| 		copy(value, inode.value) | ||||
| 		inode.value = value | ||||
| 	} | ||||
|  | ||||
| 	// Recursively dereference children. | ||||
| 	for _, child := range n.children { | ||||
| 		child.dereference() | ||||
| 	} | ||||
|  | ||||
| 	// Update statistics. | ||||
| 	n.bucket.tx.stats.NodeDeref++ | ||||
| } | ||||
|  | ||||
| // free adds the node's underlying page to the freelist. | ||||
| func (n *node) free() { | ||||
| 	if n.pgid != 0 { | ||||
| 		n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid)) | ||||
| 		n.pgid = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // dump writes the contents of the node to STDERR for debugging purposes. | ||||
| /* | ||||
| func (n *node) dump() { | ||||
| 	// Write node header. | ||||
| 	var typ = "branch" | ||||
| 	if n.isLeaf { | ||||
| 		typ = "leaf" | ||||
| 	} | ||||
| 	warnf("[NODE %d {type=%s count=%d}]", n.pgid, typ, len(n.inodes)) | ||||
|  | ||||
| 	// Write out abbreviated version of each item. | ||||
| 	for _, item := range n.inodes { | ||||
| 		if n.isLeaf { | ||||
| 			if item.flags&bucketLeafFlag != 0 { | ||||
| 				bucket := (*bucket)(unsafe.Pointer(&item.value[0])) | ||||
| 				warnf("+L %08x -> (bucket root=%d)", trunc(item.key, 4), bucket.root) | ||||
| 			} else { | ||||
| 				warnf("+L %08x -> %08x", trunc(item.key, 4), trunc(item.value, 4)) | ||||
| 			} | ||||
| 		} else { | ||||
| 			warnf("+B %08x -> pgid=%d", trunc(item.key, 4), item.pgid) | ||||
| 		} | ||||
| 	} | ||||
| 	warn("") | ||||
| } | ||||
| */ | ||||
|  | ||||
| type nodes []*node | ||||
|  | ||||
| func (s nodes) Len() int           { return len(s) } | ||||
| func (s nodes) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } | ||||
| func (s nodes) Less(i, j int) bool { return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 } | ||||
|  | ||||
| // inode represents an internal node inside of a node. | ||||
| // It can be used to point to elements in a page or point | ||||
| // to an element which hasn't been added to a page yet. | ||||
| type inode struct { | ||||
| 	flags uint32 | ||||
| 	pgid  pgid | ||||
| 	key   []byte | ||||
| 	value []byte | ||||
| } | ||||
|  | ||||
| type inodes []inode | ||||
							
								
								
									
										172
									
								
								vendor/github.com/boltdb/bolt/page.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								vendor/github.com/boltdb/bolt/page.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| const pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr)) | ||||
|  | ||||
| const minKeysPerPage = 2 | ||||
|  | ||||
| const branchPageElementSize = int(unsafe.Sizeof(branchPageElement{})) | ||||
| const leafPageElementSize = int(unsafe.Sizeof(leafPageElement{})) | ||||
|  | ||||
| const ( | ||||
| 	branchPageFlag   = 0x01 | ||||
| 	leafPageFlag     = 0x02 | ||||
| 	metaPageFlag     = 0x04 | ||||
| 	freelistPageFlag = 0x10 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bucketLeafFlag = 0x01 | ||||
| ) | ||||
|  | ||||
| type pgid uint64 | ||||
|  | ||||
| type page struct { | ||||
| 	id       pgid | ||||
| 	flags    uint16 | ||||
| 	count    uint16 | ||||
| 	overflow uint32 | ||||
| 	ptr      uintptr | ||||
| } | ||||
|  | ||||
| // typ returns a human readable page type string used for debugging. | ||||
| func (p *page) typ() string { | ||||
| 	if (p.flags & branchPageFlag) != 0 { | ||||
| 		return "branch" | ||||
| 	} else if (p.flags & leafPageFlag) != 0 { | ||||
| 		return "leaf" | ||||
| 	} else if (p.flags & metaPageFlag) != 0 { | ||||
| 		return "meta" | ||||
| 	} else if (p.flags & freelistPageFlag) != 0 { | ||||
| 		return "freelist" | ||||
| 	} | ||||
| 	return fmt.Sprintf("unknown<%02x>", p.flags) | ||||
| } | ||||
|  | ||||
| // meta returns a pointer to the metadata section of the page. | ||||
| func (p *page) meta() *meta { | ||||
| 	return (*meta)(unsafe.Pointer(&p.ptr)) | ||||
| } | ||||
|  | ||||
| // leafPageElement retrieves the leaf node by index | ||||
| func (p *page) leafPageElement(index uint16) *leafPageElement { | ||||
| 	n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index] | ||||
| 	return n | ||||
| } | ||||
|  | ||||
| // leafPageElements retrieves a list of leaf nodes. | ||||
| func (p *page) leafPageElements() []leafPageElement { | ||||
| 	return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:] | ||||
| } | ||||
|  | ||||
| // branchPageElement retrieves the branch node by index | ||||
| func (p *page) branchPageElement(index uint16) *branchPageElement { | ||||
| 	return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index] | ||||
| } | ||||
|  | ||||
| // branchPageElements retrieves a list of branch nodes. | ||||
| func (p *page) branchPageElements() []branchPageElement { | ||||
| 	return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:] | ||||
| } | ||||
|  | ||||
| // dump writes n bytes of the page to STDERR as hex output. | ||||
| func (p *page) hexdump(n int) { | ||||
| 	buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:n] | ||||
| 	fmt.Fprintf(os.Stderr, "%x\n", buf) | ||||
| } | ||||
|  | ||||
| type pages []*page | ||||
|  | ||||
| func (s pages) Len() int           { return len(s) } | ||||
| func (s pages) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } | ||||
| func (s pages) Less(i, j int) bool { return s[i].id < s[j].id } | ||||
|  | ||||
| // branchPageElement represents a node on a branch page. | ||||
| type branchPageElement struct { | ||||
| 	pos   uint32 | ||||
| 	ksize uint32 | ||||
| 	pgid  pgid | ||||
| } | ||||
|  | ||||
| // key returns a byte slice of the node key. | ||||
| func (n *branchPageElement) key() []byte { | ||||
| 	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) | ||||
| 	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize] | ||||
| } | ||||
|  | ||||
| // leafPageElement represents a node on a leaf page. | ||||
| type leafPageElement struct { | ||||
| 	flags uint32 | ||||
| 	pos   uint32 | ||||
| 	ksize uint32 | ||||
| 	vsize uint32 | ||||
| } | ||||
|  | ||||
| // key returns a byte slice of the node key. | ||||
| func (n *leafPageElement) key() []byte { | ||||
| 	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) | ||||
| 	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize] | ||||
| } | ||||
|  | ||||
| // value returns a byte slice of the node value. | ||||
| func (n *leafPageElement) value() []byte { | ||||
| 	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) | ||||
| 	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize] | ||||
| } | ||||
|  | ||||
| // PageInfo represents human readable information about a page. | ||||
| type PageInfo struct { | ||||
| 	ID            int | ||||
| 	Type          string | ||||
| 	Count         int | ||||
| 	OverflowCount int | ||||
| } | ||||
|  | ||||
| type pgids []pgid | ||||
|  | ||||
| func (s pgids) Len() int           { return len(s) } | ||||
| func (s pgids) Swap(i, j int)      { s[i], s[j] = s[j], s[i] } | ||||
| func (s pgids) Less(i, j int) bool { return s[i] < s[j] } | ||||
|  | ||||
| // merge returns the sorted union of a and b. | ||||
| func (a pgids) merge(b pgids) pgids { | ||||
| 	// Return the opposite slice if one is nil. | ||||
| 	if len(a) == 0 { | ||||
| 		return b | ||||
| 	} else if len(b) == 0 { | ||||
| 		return a | ||||
| 	} | ||||
|  | ||||
| 	// Create a list to hold all elements from both lists. | ||||
| 	merged := make(pgids, 0, len(a)+len(b)) | ||||
|  | ||||
| 	// Assign lead to the slice with a lower starting value, follow to the higher value. | ||||
| 	lead, follow := a, b | ||||
| 	if b[0] < a[0] { | ||||
| 		lead, follow = b, a | ||||
| 	} | ||||
|  | ||||
| 	// Continue while there are elements in the lead. | ||||
| 	for len(lead) > 0 { | ||||
| 		// Merge largest prefix of lead that is ahead of follow[0]. | ||||
| 		n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] }) | ||||
| 		merged = append(merged, lead[:n]...) | ||||
| 		if n >= len(lead) { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Swap lead and follow. | ||||
| 		lead, follow = follow, lead[n:] | ||||
| 	} | ||||
|  | ||||
| 	// Append what's left in follow. | ||||
| 	merged = append(merged, follow...) | ||||
|  | ||||
| 	return merged | ||||
| } | ||||
							
								
								
									
										666
									
								
								vendor/github.com/boltdb/bolt/tx.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										666
									
								
								vendor/github.com/boltdb/bolt/tx.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,666 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // txid represents the internal transaction identifier. | ||||
| type txid uint64 | ||||
|  | ||||
| // Tx represents a read-only or read/write transaction on the database. | ||||
| // Read-only transactions can be used for retrieving values for keys and creating cursors. | ||||
| // Read/write transactions can create and remove buckets and create and remove keys. | ||||
| // | ||||
| // IMPORTANT: You must commit or rollback transactions when you are done with | ||||
| // them. Pages can not be reclaimed by the writer until no more transactions | ||||
| // are using them. A long running read transaction can cause the database to | ||||
| // quickly grow. | ||||
| type Tx struct { | ||||
| 	writable       bool | ||||
| 	managed        bool | ||||
| 	db             *DB | ||||
| 	meta           *meta | ||||
| 	root           Bucket | ||||
| 	pages          map[pgid]*page | ||||
| 	stats          TxStats | ||||
| 	commitHandlers []func() | ||||
|  | ||||
| 	// WriteFlag specifies the flag for write-related methods like WriteTo(). | ||||
| 	// Tx opens the database file with the specified flag to copy the data. | ||||
| 	// | ||||
| 	// By default, the flag is unset, which works well for mostly in-memory | ||||
| 	// workloads. For databases that are much larger than available RAM, | ||||
| 	// set the flag to syscall.O_DIRECT to avoid trashing the page cache. | ||||
| 	WriteFlag int | ||||
| } | ||||
|  | ||||
| // init initializes the transaction. | ||||
| func (tx *Tx) init(db *DB) { | ||||
| 	tx.db = db | ||||
| 	tx.pages = nil | ||||
|  | ||||
| 	// Copy the meta page since it can be changed by the writer. | ||||
| 	tx.meta = &meta{} | ||||
| 	db.meta().copy(tx.meta) | ||||
|  | ||||
| 	// Copy over the root bucket. | ||||
| 	tx.root = newBucket(tx) | ||||
| 	tx.root.bucket = &bucket{} | ||||
| 	*tx.root.bucket = tx.meta.root | ||||
|  | ||||
| 	// Increment the transaction id and add a page cache for writable transactions. | ||||
| 	if tx.writable { | ||||
| 		tx.pages = make(map[pgid]*page) | ||||
| 		tx.meta.txid += txid(1) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ID returns the transaction id. | ||||
| func (tx *Tx) ID() int { | ||||
| 	return int(tx.meta.txid) | ||||
| } | ||||
|  | ||||
| // DB returns a reference to the database that created the transaction. | ||||
| func (tx *Tx) DB() *DB { | ||||
| 	return tx.db | ||||
| } | ||||
|  | ||||
| // Size returns current database size in bytes as seen by this transaction. | ||||
| func (tx *Tx) Size() int64 { | ||||
| 	return int64(tx.meta.pgid) * int64(tx.db.pageSize) | ||||
| } | ||||
|  | ||||
| // Writable returns whether the transaction can perform write operations. | ||||
| func (tx *Tx) Writable() bool { | ||||
| 	return tx.writable | ||||
| } | ||||
|  | ||||
| // Cursor creates a cursor associated with the root bucket. | ||||
| // All items in the cursor will return a nil value because all root bucket keys point to buckets. | ||||
| // The cursor is only valid as long as the transaction is open. | ||||
| // Do not use a cursor after the transaction is closed. | ||||
| func (tx *Tx) Cursor() *Cursor { | ||||
| 	return tx.root.Cursor() | ||||
| } | ||||
|  | ||||
| // Stats retrieves a copy of the current transaction statistics. | ||||
| func (tx *Tx) Stats() TxStats { | ||||
| 	return tx.stats | ||||
| } | ||||
|  | ||||
| // Bucket retrieves a bucket by name. | ||||
| // Returns nil if the bucket does not exist. | ||||
| // The bucket instance is only valid for the lifetime of the transaction. | ||||
| func (tx *Tx) Bucket(name []byte) *Bucket { | ||||
| 	return tx.root.Bucket(name) | ||||
| } | ||||
|  | ||||
| // CreateBucket creates a new bucket. | ||||
| // Returns an error if the bucket 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. | ||||
| func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) { | ||||
| 	return tx.root.CreateBucket(name) | ||||
| } | ||||
|  | ||||
| // CreateBucketIfNotExists creates a new bucket if it doesn't already exist. | ||||
| // 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. | ||||
| func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) { | ||||
| 	return tx.root.CreateBucketIfNotExists(name) | ||||
| } | ||||
|  | ||||
| // DeleteBucket deletes a bucket. | ||||
| // Returns an error if the bucket cannot be found or if the key represents a non-bucket value. | ||||
| func (tx *Tx) DeleteBucket(name []byte) error { | ||||
| 	return tx.root.DeleteBucket(name) | ||||
| } | ||||
|  | ||||
| // ForEach executes a function for each bucket in the root. | ||||
| // If the provided function returns an error then the iteration is stopped and | ||||
| // the error is returned to the caller. | ||||
| func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error { | ||||
| 	return tx.root.ForEach(func(k, v []byte) error { | ||||
| 		if err := fn(k, tx.root.Bucket(k)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // OnCommit adds a handler function to be executed after the transaction successfully commits. | ||||
| func (tx *Tx) OnCommit(fn func()) { | ||||
| 	tx.commitHandlers = append(tx.commitHandlers, fn) | ||||
| } | ||||
|  | ||||
| // Commit writes all changes to disk and updates the meta page. | ||||
| // Returns an error if a disk write error occurs, or if Commit is | ||||
| // called on a read-only transaction. | ||||
| func (tx *Tx) Commit() error { | ||||
| 	_assert(!tx.managed, "managed tx commit not allowed") | ||||
| 	if tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} else if !tx.writable { | ||||
| 		return ErrTxNotWritable | ||||
| 	} | ||||
|  | ||||
| 	// TODO(benbjohnson): Use vectorized I/O to write out dirty pages. | ||||
|  | ||||
| 	// Rebalance nodes which have had deletions. | ||||
| 	var startTime = time.Now() | ||||
| 	tx.root.rebalance() | ||||
| 	if tx.stats.Rebalance > 0 { | ||||
| 		tx.stats.RebalanceTime += time.Since(startTime) | ||||
| 	} | ||||
|  | ||||
| 	// spill data onto dirty pages. | ||||
| 	startTime = time.Now() | ||||
| 	if err := tx.root.spill(); err != nil { | ||||
| 		tx.rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	tx.stats.SpillTime += time.Since(startTime) | ||||
|  | ||||
| 	// Free the old root bucket. | ||||
| 	tx.meta.root.root = tx.root.root | ||||
|  | ||||
| 	opgid := tx.meta.pgid | ||||
|  | ||||
| 	// Free the freelist and allocate new pages for it. This will overestimate | ||||
| 	// the size of the freelist but not underestimate the size (which would be bad). | ||||
| 	tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist)) | ||||
| 	p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1) | ||||
| 	if err != nil { | ||||
| 		tx.rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := tx.db.freelist.write(p); err != nil { | ||||
| 		tx.rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	tx.meta.freelist = p.id | ||||
|  | ||||
| 	// If the high water mark has moved up then attempt to grow the database. | ||||
| 	if tx.meta.pgid > opgid { | ||||
| 		if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil { | ||||
| 			tx.rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Write dirty pages to disk. | ||||
| 	startTime = time.Now() | ||||
| 	if err := tx.write(); err != nil { | ||||
| 		tx.rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// If strict mode is enabled then perform a consistency check. | ||||
| 	// Only the first consistency error is reported in the panic. | ||||
| 	if tx.db.StrictMode { | ||||
| 		ch := tx.Check() | ||||
| 		var errs []string | ||||
| 		for { | ||||
| 			err, ok := <-ch | ||||
| 			if !ok { | ||||
| 				break | ||||
| 			} | ||||
| 			errs = append(errs, err.Error()) | ||||
| 		} | ||||
| 		if len(errs) > 0 { | ||||
| 			panic("check fail: " + strings.Join(errs, "\n")) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Write meta to disk. | ||||
| 	if err := tx.writeMeta(); err != nil { | ||||
| 		tx.rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	tx.stats.WriteTime += time.Since(startTime) | ||||
|  | ||||
| 	// Finalize the transaction. | ||||
| 	tx.close() | ||||
|  | ||||
| 	// Execute commit handlers now that the locks have been removed. | ||||
| 	for _, fn := range tx.commitHandlers { | ||||
| 		fn() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Rollback closes the transaction and ignores all previous updates. Read-only | ||||
| // transactions must be rolled back and not committed. | ||||
| func (tx *Tx) Rollback() error { | ||||
| 	_assert(!tx.managed, "managed tx rollback not allowed") | ||||
| 	if tx.db == nil { | ||||
| 		return ErrTxClosed | ||||
| 	} | ||||
| 	tx.rollback() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (tx *Tx) rollback() { | ||||
| 	if tx.db == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if tx.writable { | ||||
| 		tx.db.freelist.rollback(tx.meta.txid) | ||||
| 		tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist)) | ||||
| 	} | ||||
| 	tx.close() | ||||
| } | ||||
|  | ||||
| func (tx *Tx) close() { | ||||
| 	if tx.db == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if tx.writable { | ||||
| 		// Grab freelist stats. | ||||
| 		var freelistFreeN = tx.db.freelist.free_count() | ||||
| 		var freelistPendingN = tx.db.freelist.pending_count() | ||||
| 		var freelistAlloc = tx.db.freelist.size() | ||||
|  | ||||
| 		// Remove transaction ref & writer lock. | ||||
| 		tx.db.rwtx = nil | ||||
| 		tx.db.rwlock.Unlock() | ||||
|  | ||||
| 		// Merge statistics. | ||||
| 		tx.db.statlock.Lock() | ||||
| 		tx.db.stats.FreePageN = freelistFreeN | ||||
| 		tx.db.stats.PendingPageN = freelistPendingN | ||||
| 		tx.db.stats.FreeAlloc = (freelistFreeN + freelistPendingN) * tx.db.pageSize | ||||
| 		tx.db.stats.FreelistInuse = freelistAlloc | ||||
| 		tx.db.stats.TxStats.add(&tx.stats) | ||||
| 		tx.db.statlock.Unlock() | ||||
| 	} else { | ||||
| 		tx.db.removeTx(tx) | ||||
| 	} | ||||
|  | ||||
| 	// Clear all references. | ||||
| 	tx.db = nil | ||||
| 	tx.meta = nil | ||||
| 	tx.root = Bucket{tx: tx} | ||||
| 	tx.pages = nil | ||||
| } | ||||
|  | ||||
| // Copy writes the entire database to a writer. | ||||
| // This function exists for backwards compatibility. Use WriteTo() instead. | ||||
| func (tx *Tx) Copy(w io.Writer) error { | ||||
| 	_, err := tx.WriteTo(w) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // WriteTo writes the entire database to a writer. | ||||
| // If err == nil then exactly tx.Size() bytes will be written into the writer. | ||||
| func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) { | ||||
| 	// Attempt to open reader with WriteFlag | ||||
| 	f, err := os.OpenFile(tx.db.path, os.O_RDONLY|tx.WriteFlag, 0) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	defer func() { _ = f.Close() }() | ||||
|  | ||||
| 	// Generate a meta page. We use the same page data for both meta pages. | ||||
| 	buf := make([]byte, tx.db.pageSize) | ||||
| 	page := (*page)(unsafe.Pointer(&buf[0])) | ||||
| 	page.flags = metaPageFlag | ||||
| 	*page.meta() = *tx.meta | ||||
|  | ||||
| 	// Write meta 0. | ||||
| 	page.id = 0 | ||||
| 	page.meta().checksum = page.meta().sum64() | ||||
| 	nn, err := w.Write(buf) | ||||
| 	n += int64(nn) | ||||
| 	if err != nil { | ||||
| 		return n, fmt.Errorf("meta 0 copy: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Write meta 1 with a lower transaction id. | ||||
| 	page.id = 1 | ||||
| 	page.meta().txid -= 1 | ||||
| 	page.meta().checksum = page.meta().sum64() | ||||
| 	nn, err = w.Write(buf) | ||||
| 	n += int64(nn) | ||||
| 	if err != nil { | ||||
| 		return n, fmt.Errorf("meta 1 copy: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Move past the meta pages in the file. | ||||
| 	if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil { | ||||
| 		return n, fmt.Errorf("seek: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Copy data pages. | ||||
| 	wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)) | ||||
| 	n += wn | ||||
| 	if err != nil { | ||||
| 		return n, err | ||||
| 	} | ||||
|  | ||||
| 	return n, f.Close() | ||||
| } | ||||
|  | ||||
| // CopyFile copies the entire database to file at the given path. | ||||
| // A reader transaction is maintained during the copy so it is safe to continue | ||||
| // using the database while a copy is in progress. | ||||
| func (tx *Tx) CopyFile(path string, mode os.FileMode) error { | ||||
| 	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = tx.Copy(f) | ||||
| 	if err != nil { | ||||
| 		_ = f.Close() | ||||
| 		return err | ||||
| 	} | ||||
| 	return f.Close() | ||||
| } | ||||
|  | ||||
| // Check performs several consistency checks on the database for this transaction. | ||||
| // An error is returned if any inconsistency is found. | ||||
| // | ||||
| // It can be safely run concurrently on a writable transaction. However, this | ||||
| // incurs a high cost for large databases and databases with a lot of subbuckets | ||||
| // because of caching. This overhead can be removed if running on a read-only | ||||
| // transaction, however, it is not safe to execute other writer transactions at | ||||
| // the same time. | ||||
| func (tx *Tx) Check() <-chan error { | ||||
| 	ch := make(chan error) | ||||
| 	go tx.check(ch) | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| func (tx *Tx) check(ch chan error) { | ||||
| 	// Check if any pages are double freed. | ||||
| 	freed := make(map[pgid]bool) | ||||
| 	for _, id := range tx.db.freelist.all() { | ||||
| 		if freed[id] { | ||||
| 			ch <- fmt.Errorf("page %d: already freed", id) | ||||
| 		} | ||||
| 		freed[id] = true | ||||
| 	} | ||||
|  | ||||
| 	// Track every reachable page. | ||||
| 	reachable := make(map[pgid]*page) | ||||
| 	reachable[0] = tx.page(0) // meta0 | ||||
| 	reachable[1] = tx.page(1) // meta1 | ||||
| 	for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ { | ||||
| 		reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist) | ||||
| 	} | ||||
|  | ||||
| 	// Recursively check buckets. | ||||
| 	tx.checkBucket(&tx.root, reachable, freed, ch) | ||||
|  | ||||
| 	// Ensure all pages below high water mark are either reachable or freed. | ||||
| 	for i := pgid(0); i < tx.meta.pgid; i++ { | ||||
| 		_, isReachable := reachable[i] | ||||
| 		if !isReachable && !freed[i] { | ||||
| 			ch <- fmt.Errorf("page %d: unreachable unfreed", int(i)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Close the channel to signal completion. | ||||
| 	close(ch) | ||||
| } | ||||
|  | ||||
| func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) { | ||||
| 	// Ignore inline buckets. | ||||
| 	if b.root == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Check every page used by this bucket. | ||||
| 	b.tx.forEachPage(b.root, 0, func(p *page, _ int) { | ||||
| 		if p.id > tx.meta.pgid { | ||||
| 			ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid)) | ||||
| 		} | ||||
|  | ||||
| 		// Ensure each page is only referenced once. | ||||
| 		for i := pgid(0); i <= pgid(p.overflow); i++ { | ||||
| 			var id = p.id + i | ||||
| 			if _, ok := reachable[id]; ok { | ||||
| 				ch <- fmt.Errorf("page %d: multiple references", int(id)) | ||||
| 			} | ||||
| 			reachable[id] = p | ||||
| 		} | ||||
|  | ||||
| 		// We should only encounter un-freed leaf and branch pages. | ||||
| 		if freed[p.id] { | ||||
| 			ch <- fmt.Errorf("page %d: reachable freed", int(p.id)) | ||||
| 		} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 { | ||||
| 			ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ()) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Check each bucket within this bucket. | ||||
| 	_ = b.ForEach(func(k, v []byte) error { | ||||
| 		if child := b.Bucket(k); child != nil { | ||||
| 			tx.checkBucket(child, reachable, freed, ch) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // allocate returns a contiguous block of memory starting at a given page. | ||||
| func (tx *Tx) allocate(count int) (*page, error) { | ||||
| 	p, err := tx.db.allocate(count) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Save to our page cache. | ||||
| 	tx.pages[p.id] = p | ||||
|  | ||||
| 	// Update statistics. | ||||
| 	tx.stats.PageCount++ | ||||
| 	tx.stats.PageAlloc += count * tx.db.pageSize | ||||
|  | ||||
| 	return p, nil | ||||
| } | ||||
|  | ||||
| // write writes any dirty pages to disk. | ||||
| func (tx *Tx) write() error { | ||||
| 	// Sort pages by id. | ||||
| 	pages := make(pages, 0, len(tx.pages)) | ||||
| 	for _, p := range tx.pages { | ||||
| 		pages = append(pages, p) | ||||
| 	} | ||||
| 	sort.Sort(pages) | ||||
|  | ||||
| 	// Write pages to disk in order. | ||||
| 	for _, p := range pages { | ||||
| 		size := (int(p.overflow) + 1) * tx.db.pageSize | ||||
| 		offset := int64(p.id) * int64(tx.db.pageSize) | ||||
|  | ||||
| 		// Write out page in "max allocation" sized chunks. | ||||
| 		ptr := (*[maxAllocSize]byte)(unsafe.Pointer(p)) | ||||
| 		for { | ||||
| 			// Limit our write to our max allocation size. | ||||
| 			sz := size | ||||
| 			if sz > maxAllocSize-1 { | ||||
| 				sz = maxAllocSize - 1 | ||||
| 			} | ||||
|  | ||||
| 			// Write chunk to disk. | ||||
| 			buf := ptr[:sz] | ||||
| 			if _, err := tx.db.ops.writeAt(buf, offset); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// Update statistics. | ||||
| 			tx.stats.Write++ | ||||
|  | ||||
| 			// Exit inner for loop if we've written all the chunks. | ||||
| 			size -= sz | ||||
| 			if size == 0 { | ||||
| 				break | ||||
| 			} | ||||
|  | ||||
| 			// Otherwise move offset forward and move pointer to next chunk. | ||||
| 			offset += int64(sz) | ||||
| 			ptr = (*[maxAllocSize]byte)(unsafe.Pointer(&ptr[sz])) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Ignore file sync if flag is set on DB. | ||||
| 	if !tx.db.NoSync || IgnoreNoSync { | ||||
| 		if err := fdatasync(tx.db); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Clear out page cache. | ||||
| 	tx.pages = make(map[pgid]*page) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // writeMeta writes the meta to the disk. | ||||
| func (tx *Tx) writeMeta() error { | ||||
| 	// Create a temporary buffer for the meta page. | ||||
| 	buf := make([]byte, tx.db.pageSize) | ||||
| 	p := tx.db.pageInBuffer(buf, 0) | ||||
| 	tx.meta.write(p) | ||||
|  | ||||
| 	// Write the meta page to file. | ||||
| 	if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !tx.db.NoSync || IgnoreNoSync { | ||||
| 		if err := fdatasync(tx.db); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Update statistics. | ||||
| 	tx.stats.Write++ | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // page returns a reference to the page with a given id. | ||||
| // If page has been written to then a temporary buffered page is returned. | ||||
| func (tx *Tx) page(id pgid) *page { | ||||
| 	// Check the dirty pages first. | ||||
| 	if tx.pages != nil { | ||||
| 		if p, ok := tx.pages[id]; ok { | ||||
| 			return p | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise return directly from the mmap. | ||||
| 	return tx.db.page(id) | ||||
| } | ||||
|  | ||||
| // forEachPage iterates over every page within a given page and executes a function. | ||||
| func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) { | ||||
| 	p := tx.page(pgid) | ||||
|  | ||||
| 	// Execute function. | ||||
| 	fn(p, depth) | ||||
|  | ||||
| 	// Recursively loop over children. | ||||
| 	if (p.flags & branchPageFlag) != 0 { | ||||
| 		for i := 0; i < int(p.count); i++ { | ||||
| 			elem := p.branchPageElement(uint16(i)) | ||||
| 			tx.forEachPage(elem.pgid, depth+1, fn) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Page returns page information for a given page number. | ||||
| // This is only safe for concurrent use when used by a writable transaction. | ||||
| func (tx *Tx) Page(id int) (*PageInfo, error) { | ||||
| 	if tx.db == nil { | ||||
| 		return nil, ErrTxClosed | ||||
| 	} else if pgid(id) >= tx.meta.pgid { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// Build the page info. | ||||
| 	p := tx.db.page(pgid(id)) | ||||
| 	info := &PageInfo{ | ||||
| 		ID:            id, | ||||
| 		Count:         int(p.count), | ||||
| 		OverflowCount: int(p.overflow), | ||||
| 	} | ||||
|  | ||||
| 	// Determine the type (or if it's free). | ||||
| 	if tx.db.freelist.freed(pgid(id)) { | ||||
| 		info.Type = "free" | ||||
| 	} else { | ||||
| 		info.Type = p.typ() | ||||
| 	} | ||||
|  | ||||
| 	return info, nil | ||||
| } | ||||
|  | ||||
| // TxStats represents statistics about the actions performed by the transaction. | ||||
| type TxStats struct { | ||||
| 	// Page statistics. | ||||
| 	PageCount int // number of page allocations | ||||
| 	PageAlloc int // total bytes allocated | ||||
|  | ||||
| 	// Cursor statistics. | ||||
| 	CursorCount int // number of cursors created | ||||
|  | ||||
| 	// Node statistics | ||||
| 	NodeCount int // number of node allocations | ||||
| 	NodeDeref int // number of node dereferences | ||||
|  | ||||
| 	// Rebalance statistics. | ||||
| 	Rebalance     int           // number of node rebalances | ||||
| 	RebalanceTime time.Duration // total time spent rebalancing | ||||
|  | ||||
| 	// Split/Spill statistics. | ||||
| 	Split     int           // number of nodes split | ||||
| 	Spill     int           // number of nodes spilled | ||||
| 	SpillTime time.Duration // total time spent spilling | ||||
|  | ||||
| 	// Write statistics. | ||||
| 	Write     int           // number of writes performed | ||||
| 	WriteTime time.Duration // total time spent writing to disk | ||||
| } | ||||
|  | ||||
| func (s *TxStats) add(other *TxStats) { | ||||
| 	s.PageCount += other.PageCount | ||||
| 	s.PageAlloc += other.PageAlloc | ||||
| 	s.CursorCount += other.CursorCount | ||||
| 	s.NodeCount += other.NodeCount | ||||
| 	s.NodeDeref += other.NodeDeref | ||||
| 	s.Rebalance += other.Rebalance | ||||
| 	s.RebalanceTime += other.RebalanceTime | ||||
| 	s.Split += other.Split | ||||
| 	s.Spill += other.Spill | ||||
| 	s.SpillTime += other.SpillTime | ||||
| 	s.Write += other.Write | ||||
| 	s.WriteTime += other.WriteTime | ||||
| } | ||||
|  | ||||
| // Sub calculates and returns the difference between two sets of transaction stats. | ||||
| // This is useful when obtaining stats at two different points and time and | ||||
| // you need the performance counters that occurred within that time span. | ||||
| func (s *TxStats) Sub(other *TxStats) TxStats { | ||||
| 	var diff TxStats | ||||
| 	diff.PageCount = s.PageCount - other.PageCount | ||||
| 	diff.PageAlloc = s.PageAlloc - other.PageAlloc | ||||
| 	diff.CursorCount = s.CursorCount - other.CursorCount | ||||
| 	diff.NodeCount = s.NodeCount - other.NodeCount | ||||
| 	diff.NodeDeref = s.NodeDeref - other.NodeDeref | ||||
| 	diff.Rebalance = s.Rebalance - other.Rebalance | ||||
| 	diff.RebalanceTime = s.RebalanceTime - other.RebalanceTime | ||||
| 	diff.Split = s.Split - other.Split | ||||
| 	diff.Spill = s.Spill - other.Spill | ||||
| 	diff.SpillTime = s.SpillTime - other.SpillTime | ||||
| 	diff.Write = s.Write - other.Write | ||||
| 	diff.WriteTime = s.WriteTime - other.WriteTime | ||||
| 	return diff | ||||
| } | ||||
		Reference in New Issue
	
	Block a user