← 戻る

buntdb について

2021-03-18

## buntdb とは
[tidwall/buntdb](https://github.com/tidwall/buntdb)は Pure Go で書かれた KVS ライブラリ。 インメモリで処理を行うが、ディスクに永続化することもできる。またトランザクションや Index がある。 Go からは [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)を使えば SQLite が使えるが、 SQLite は cgo が必要なので、環境によってはややビルド方法などで気を使う必要がある。 そのため Pure Go で使える DB ライブラリがほしかった。 buntdb は KVS なので SQLite ほど柔軟にクエリや集計できるわけではないが、用途によっては使えるかと思った。

## シンプルな使い方
[https://play.golang.org/p/wSHTfPSmmHp](https://play.golang.org/p/wSHTfPSmmHp)

```text package main import ( "fmt" "log" "github.com/tidwall/buntdb" ) func main() { db, err := buntdb.Open(":memory:") if err != nil { log.Fatal(err) } defer db.Close() // 読み書きができるトランザクションを生成 db.Update(func(tx *buntdb.Tx) error { // レコードの登録 tx.Set("key3", "value3", nil) tx.Set("key1", "value1", nil) tx.Set("key2", "value2", nil) return nil // エラーが返らない場合コミットされる }) // 読み込みのみのトランザクションを生成 db.View(func(tx *buntdb.Tx) error { // 全レコードを昇順でイテレート tx.Ascend("", func(key, value string) bool { fmt.Println(key, value) return true }) return nil }) } ```
結果
```text key1 value1 key2 value2 key3 value3 ```
- トランザクションは、読み書き, 読み込みのみ, どちらの場合も関数として表現する - Key も Value も string 型 ## Index を使う [https://play.golang.org/p/tchBZ89nFwy](https://play.golang.org/p/tchBZ89nFwy)
```text package main import ( "fmt" "log" "github.com/tidwall/buntdb" ) func main() { db, err := buntdb.Open(":memory:") if err != nil { log.Fatal(err) } defer db.Close() // keyに`key:`というprefixがつくレコードを対象に、valueを文字列としてソートする`indexstr`という名前のインデックスを生成 db.CreateIndex("indexstr", "key:*", buntdb.IndexString) // keyに`key:`というprefixがつくレコードを対象に、valueを数値としてソートする`indexint`という名前のインデックスを生成 db.CreateIndex("indexint", "key:*", buntdb.IndexInt) db.Update(func(tx *buntdb.Tx) error { tx.Set("key:1", "13", nil) tx.Set("key:2", "2", nil) tx.Set("key:3", "9", nil) return nil // エラーが起きていなければ自動でコミットされる }) db.View(func(tx *buntdb.Tx) error { fmt.Println("iterate over indexstr") tx.Ascend("indexstr", func(key, value string) bool { fmt.Println(key, value) return true }) fmt.Println("iterate over indexint") tx.Ascend("indexint", func(key, value string) bool { fmt.Println(key, value) return true }) return nil }) } ```
結果
```text iterate over indexstr key:1 13 key:2 2 key:3 9 iterate over indexint key:2 2 key:3 9 key:1 13 ```
- Index の対象とするレコードは、Key のパターンで指定する - パターン文字列には\*(任意の文字列)と?(任意の 1 文字)が使える [https://pkg.go.dev/github.com/tidwall/match#Match](https://pkg.go.dev/github.com/tidwall/match#Match) ## Multi Value Index を使う [https://play.golang.org/p/eEMPdWVoy6X](https://play.golang.org/p/eEMPdWVoy6X)
```text package main import ( "fmt" "log" "github.com/tidwall/buntdb" ) func main() { db, err := buntdb.Open(":memory:") if err != nil { log.Fatal(err) } defer db.Close() // 全レコードを対象に、valueをJSONとみなしてi1, i2の値の順にソートする`index`という名前のインデックスを生成 db.CreateIndex("index", "*", buntdb.IndexJSON("i1"), buntdb.IndexJSON("i2")) db.Update(func(tx *buntdb.Tx) error { var err error records := []struct { Key string Value string }{ // 値が文字列 {Key: "s12", Value: `{"i1":"1", "i2":"2"}`}, {Key: "s21", Value: `{"i1":"2", "i2":"1"}`}, {Key: "s22", Value: `{"i1":"2", "i2":"2"}`}, {Key: "s322", Value: `{"i1":"3", "i2":"22"}`}, {Key: "s32", Value: `{"i1":"3", "i2":"2"}`}, {Key: "s11", Value: `{"i1":"1", "i2":"1"}`}, {Key: "s31", Value: `{"i1":"3", "i2":"1"}`}, {Key: "s33", Value: `{"i1":"3", "i2":"3"}`}, // 値が数値型 {Key: "n12", Value: `{"i1":1, "i2":2}`}, {Key: "n21", Value: `{"i1":2, "i2":1}`}, {Key: "n22", Value: `{"i1":2, "i2":2}`}, {Key: "n322", Value: `{"i1":3, "i2":22}`}, {Key: "n32", Value: `{"i1":3, "i2":2}`}, {Key: "n11", Value: `{"i1":1, "i2":1}`}, {Key: "n31", Value: `{"i1":3, "i2":1}`}, {Key: "n33", Value: `{"i1":3, "i2":3}`}, } for _, record := range records { _, _, err = tx.Set(record.Key, record.Value, nil) } return err }) db.View(func(tx *buntdb.Tx) error { // indexを昇順にイテレート fmt.Println("iterate over index") tx.Ascend("index", func(key, value string) bool { fmt.Println(key, value) return true }) // indexの値が `{"i1":3, "i2":1}` と同じレコードをイテレート fmt.Println("iterate over index equal to n31") tx.AscendEqual("index", `{"i1":3, "i2":1}`, func(key, value string) bool { fmt.Println(key, value) return true }) // indexの値が `{"i1":3, "i2":1}` より大きいレコードをイテレート fmt.Println("iterate over index greater or equal to n31") tx.AscendGreaterOrEqual("index", `{"i1":3, "i2":1}`, func(key, value string) bool { fmt.Println(key, value) return true }) // i1 = 3のレコードをi2の値で昇順にイテレート fmt.Println("iterate over index where i1 = 3") tx.AscendRange("index", `{"i1":3, "i2":0}`, `{"i1":4, "i2":0}`, func(key, value string) bool { fmt.Println(key, value) return true }) return nil }) } ```
結果
```text iterate over index n11 {"i1":1, "i2":1} n12 {"i1":1, "i2":2} n21 {"i1":2, "i2":1} n22 {"i1":2, "i2":2} n31 {"i1":3, "i2":1} n32 {"i1":3, "i2":2} n33 {"i1":3, "i2":3} n322 {"i1":3, "i2":22} s11 {"i1":"1", "i2":"1"} s12 {"i1":"1", "i2":"2"} s21 {"i1":"2", "i2":"1"} s22 {"i1":"2", "i2":"2"} s31 {"i1":"3", "i2":"1"} s32 {"i1":"3", "i2":"2"} s322 {"i1":"3", "i2":"22"} s33 {"i1":"3", "i2":"3"} iterate over index equal to n31 n31 {"i1":3, "i2":1} iterate over index greater or equal to n31 n31 {"i1":3, "i2":1} n32 {"i1":3, "i2":2} n33 {"i1":3, "i2":3} n322 {"i1":3, "i2":22} s11 {"i1":"1", "i2":"1"} s12 {"i1":"1", "i2":"2"} s21 {"i1":"2", "i2":"1"} s22 {"i1":"2", "i2":"2"} s31 {"i1":"3", "i2":"1"} s32 {"i1":"3", "i2":"2"} s322 {"i1":"3", "i2":"22"} s33 {"i1":"3", "i2":"3"} iterate over index where i1 = 3 n31 {"i1":3, "i2":1} n32 {"i1":3, "i2":2} n33 {"i1":3, "i2":3} n322 {"i1":3, "i2":22} ```
- buntdb.IndexJSON()を使うと JSON の特定のフィールドに対しての Index を作れる - buntdb.CreateIndex()の引数に複数の Index 関数を設定できる - buntdb.IndexJSON()の順は JSON 中の型による。文字列型であれば文字列として、数値型であれば数値として並ぶ