tidwall/buntdbは Pure Go で書かれた KVS ライブラリ。 インメモリで処理を行うが、ディスクに永続化することもできる。またトランザクションや Index がある。 Go からは mattn/go-sqlite3を使えば SQLite が使えるが、 SQLite は cgo が必要なので、環境によってはややビルド方法などで気を使う必要がある。 そのため Pure Go で使える DB ライブラリがほしかった。 buntdb は KVS なので SQLite ほど柔軟にクエリや集計できるわけではないが、用途によっては使えるかと思った。
https://play.golang.org/p/wSHTfPSmmHp
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 }) }
key1 value1 key2 value2 key3 value3
トランザクションは、読み書き, 読み込みのみ, どちらの場合も関数として表現する
https://play.golang.org/p/tchBZ89nFwy
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 }) }
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://play.golang.org/p/eEMPdWVoy6X
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 }) }
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 中の型による。文字列型であれば文字列として、数値型であれば数値として並ぶ