All reads and writes must be performed from inside a transaction. BuntDB can have one write transaction opened at a time, but can have many concurrent read transactions. Each transaction maintains a stable view of the database. In other words, once a transaction has begun, the data for that transaction cannot be changed by other transactions.
Transactions run in a function that exposes a `Tx` object, which represents the transaction state. While inside a transaction, all database operations should be performed using this object. You should never access the origin `DB` object while inside a transaction. Doing so may have side-effects, such as blocking your application.
When a transaction fails, it will roll back, and revert all changes that occurred to the database during that transaction. There's a single return value that you can use to close the transaction. For read/write transactions, returning an error this way will force the transaction to roll back. When a read/write transaction succeeds all changes are persisted to disk.
A read-only transaction should be used when you don't need to make changes to the data. The advantage of a read-only transaction is that there can be many running concurrently.
```go
err := db.View(func(tx *buntdb.Tx) error {
...
return nil
})
```
### Read/write Transactions
A read/write transaction is used when you need to make changes to your data. There can only be one read/write transaction running at a time. So make sure you close it as soon as you are done with it.
Getting non-existent values will case an `ErrNotFound` error.
### Iterating
All keys/value pairs are ordered in the database by the key. To iterate over the keys:
```go
err := db.View(func(tx *buntdb.Tx) error {
err := tx.Ascend("", func(key, value string) bool{
fmt.Printf("key: %s, value: %s\n", key, value)
})
return err
})
```
There is also `AscendGreaterOrEqual`, `AscendLessThan`, `AscendRange`, `Descend`, `DescendLessOrEqual`, `DescendGreaterThan`, and `DescendRange`. Please see the [documentation](https://godoc.org/github.com/tidwall/buntdb) for more information on these functions.
Initially all data is stored in a single [B-tree](https://en.wikipedia.org/wiki/B-tree) with each item having one key and one value. All of these items are ordered by the key. This is great for quickly getting a value from a key or [iterating](#iterating) over the keys. Feel free to peruse the [B-tree implementation](https://github.com/tidwall/btree).
You can also create custom indexes that allow for ordering and [iterating](#iterating) over values. A custom index also uses a B-tree, but it's more flexible because it allows for custom ordering.
For example, let's say you want to create an index for ordering names:
```go
db.CreateIndex("names", "*", buntdb.IndexString)
```
This will create an index named `names` which stores and sorts all values. The second parameter is a pattern that is used to filter on keys. A `*` wildcard argument means that we want to accept all keys. `IndexString` is a built-in function that performs case-insensitive ordering on the values
Now you can add various names:
```go
db.Update(func(tx *buntdb.Tx) error {
tx.Set("user:0:name", "tom", nil)
tx.Set("user:1:name", "Randi", nil)
tx.Set("user:2:name", "jane", nil)
tx.Set("user:4:name", "Janet", nil)
tx.Set("user:5:name", "Paula", nil)
tx.Set("user:6:name", "peter", nil)
tx.Set("user:7:name", "Terri", nil)
return nil
})
```
Finally you can iterate over the index:
```go
db.View(func(tx *buntdb.Tx) error {
tx.Ascend("names", func(key, val string) bool {
fmt.Printf(buf, "%s %s\n", key, val)
return true
})
return nil
})
```
The output should be:
```
user:2:name jane
user:4:name Janet
user:5:name Paula
user:6:name peter
user:1:name Randi
user:7:name Terri
user:0:name tom
```
The pattern parameter can be used to filter on keys like this:
BuntDB has support for spatial indexes by storing rectangles in an [R-tree](https://en.wikipedia.org/wiki/R-tree). An R-tree is organized in a similar manner as a [B-tree](https://en.wikipedia.org/wiki/B-tree), and both are balanced trees. But, an R-tree is special because it can operate on data that is in multiple dimensions. This is super handy for Geospatial applications.
Then `IndexRect` is a built-in function that converts rect strings to a format that the R-tree can use. It's easy to use this function out of the box, but you might find it better to create a custom one that renders from a different format, such as [Well-known text](https://en.wikipedia.org/wiki/Well-known_text) or [GeoJSON](http://geojson.org/).
To add some lon,lat points to the `fleet` index:
```go
db.Update(func(tx *buntdb.Tx) error {
tx.Set("fleet:0:pos", "[-115.567 33.532]", nil)
tx.Set("fleet:1:pos", "[-116.671 35.735]", nil)
tx.Set("fleet:2:pos", "[-113.902 31.234]", nil)
return nil
})
```
And then you can run the `Intersects` function on the index:
```go
db.View(func(tx *buntdb.Tx) error {
tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool {
The bracket syntax `[-117 30],[-112 36]` is unique to BuntDB, and it's how the built-in rectangles are processed, but you are not limited to this syntax. Whatever Rect function you choose to use during `CreateSpatialIndex` will be used to process the parameter, in this case it's `IndexRect`.
Now `mykey` will automatically be deleted after one second. You can remove the TTL by setting the value again with the same key/value, but with the options parameter set to nil.
BuntDB executes an `fsync` once every second on the [aof file](#append-only-file). Which simply means that there's a chance that up to 1 second of data might be lost. The likelihood of this happening is dependent on a lot of factors, but suffice to say this may not be good enough for environments that require very high durability.
Here are some example [benchmarks](https://github.com/tidwall/raft-buntdb#raftstore-performance-comparison) when using BuntDB in a Raft Store implementation.