351 lines
8.1 KiB
Markdown
351 lines
8.1 KiB
Markdown
|
# Storm
|
||
|
|
||
|
[![Build Status](https://travis-ci.org/asdine/storm.svg)](https://travis-ci.org/asdine/storm)
|
||
|
[![GoDoc](https://godoc.org/github.com/asdine/storm?status.svg)](https://godoc.org/github.com/asdine/storm)
|
||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/asdine/storm)](https://goreportcard.com/report/github.com/asdine/storm)
|
||
|
[![Coverage](http://gocover.io/_badge/github.com/asdine/storm)](http://gocover.io/github.com/asdine/storm)
|
||
|
|
||
|
Storm is simple and powerful ORM for [BoltDB](https://github.com/boltdb/bolt). The goal of this project is to provide a simple way to save any object in BoltDB and to easily retrieve it.
|
||
|
|
||
|
## Getting Started
|
||
|
|
||
|
```bash
|
||
|
go get -u github.com/asdine/storm
|
||
|
```
|
||
|
|
||
|
## Import Storm
|
||
|
|
||
|
```go
|
||
|
import "github.com/asdine/storm"
|
||
|
```
|
||
|
|
||
|
## Open a database
|
||
|
|
||
|
Quick way of opening a database
|
||
|
```go
|
||
|
db, err := storm.Open("my.db")
|
||
|
|
||
|
defer db.Close()
|
||
|
```
|
||
|
|
||
|
`Open` can receive multiple options to customize the way it behaves. See [Options](#options) below
|
||
|
|
||
|
## Simple ORM
|
||
|
|
||
|
### Declare your structures
|
||
|
|
||
|
```go
|
||
|
type User struct {
|
||
|
ID int // primary key
|
||
|
Group string `storm:"index"` // this field will be indexed
|
||
|
Email string `storm:"unique"` // this field will be indexed with a unique constraint
|
||
|
Name string // this field will not be indexed
|
||
|
Age int `storm:"index"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The primary key can be of any type as long as it is not a zero value. Storm will search for the tag `id`, if not present Storm will search for a field named `ID`.
|
||
|
|
||
|
```go
|
||
|
type User struct {
|
||
|
ThePrimaryKey string `storm:"id"`// primary key
|
||
|
Group string `storm:"index"` // this field will be indexed
|
||
|
Email string `storm:"unique"` // this field will be indexed with a unique constraint
|
||
|
Name string // this field will not be indexed
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Storm handles tags in nested structures with the `inline` tag
|
||
|
|
||
|
```go
|
||
|
type Base struct {
|
||
|
Ident bson.ObjectId `storm:"id"`
|
||
|
}
|
||
|
|
||
|
type User struct {
|
||
|
Base `storm:"inline"`
|
||
|
Group string `storm:"index"`
|
||
|
Email string `storm:"unique"`
|
||
|
Name string
|
||
|
CreatedAt time.Time `storm:"index"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Save your object
|
||
|
|
||
|
```go
|
||
|
user := User{
|
||
|
ID: 10,
|
||
|
Group: "staff",
|
||
|
Email: "john@provider.com",
|
||
|
Name: "John",
|
||
|
Age: 21,
|
||
|
CreatedAt: time.Now(),
|
||
|
}
|
||
|
|
||
|
err := db.Save(&user)
|
||
|
// err == nil
|
||
|
|
||
|
user.ID++
|
||
|
err = db.Save(&user)
|
||
|
// err == "already exists"
|
||
|
```
|
||
|
|
||
|
That's it.
|
||
|
|
||
|
`Save` creates or updates all the required indexes and buckets, checks the unique constraints and saves the object to the store.
|
||
|
|
||
|
### Fetch your object
|
||
|
|
||
|
Only indexed fields can be used to find a record
|
||
|
|
||
|
```go
|
||
|
var user User
|
||
|
err := db.One("Email", "john@provider.com", &user)
|
||
|
// err == nil
|
||
|
|
||
|
err = db.One("Name", "John", &user)
|
||
|
// err == "not found"
|
||
|
```
|
||
|
|
||
|
### Fetch multiple objects
|
||
|
|
||
|
```go
|
||
|
var users []User
|
||
|
err := db.Find("Group", "staff", &users)
|
||
|
```
|
||
|
|
||
|
### Fetch all objects
|
||
|
|
||
|
```go
|
||
|
var users []User
|
||
|
err := db.All(&users)
|
||
|
```
|
||
|
|
||
|
### Fetch all objects sorted by index
|
||
|
|
||
|
```go
|
||
|
var users []User
|
||
|
err := db.AllByIndex("CreatedAt", &users)
|
||
|
```
|
||
|
|
||
|
### Fetch a range of objects
|
||
|
|
||
|
```go
|
||
|
var users []User
|
||
|
err := db.Range("Age", 10, 21, &users)
|
||
|
```
|
||
|
|
||
|
### Skip and Limit
|
||
|
|
||
|
```go
|
||
|
var users []User
|
||
|
err := db.Find("Group", "staff", &users, storm.Skip(10))
|
||
|
err = db.Find("Group", "staff", &users, storm.Limit(10))
|
||
|
err = db.Find("Group", "staff", &users, storm.Limit(10), storm.Skip(10))
|
||
|
|
||
|
err = db.All(&users, storm.Limit(10), storm.Skip(10))
|
||
|
err = db.AllByIndex("CreatedAt", &users, storm.Limit(10), storm.Skip(10))
|
||
|
err = db.Range("Age", 10, 21, &users, storm.Limit(10), storm.Skip(10))
|
||
|
```
|
||
|
|
||
|
### Remove an object
|
||
|
|
||
|
```go
|
||
|
err := db.Remove(&user)
|
||
|
```
|
||
|
|
||
|
### Initialize buckets and indexes before saving an object
|
||
|
|
||
|
```go
|
||
|
err := db.Init(&User{})
|
||
|
```
|
||
|
|
||
|
Useful when starting your application
|
||
|
|
||
|
### Transactions
|
||
|
|
||
|
```go
|
||
|
tx, err := db.Begin(true)
|
||
|
|
||
|
accountA.Amount -= 100
|
||
|
accountB.Amount += 100
|
||
|
|
||
|
err = tx.Save(accountA)
|
||
|
if err != nil {
|
||
|
tx.Rollback()
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = tx.Save(accountB)
|
||
|
if err != nil {
|
||
|
tx.Rollback()
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
tx.Commit()
|
||
|
```
|
||
|
### Options
|
||
|
|
||
|
Storm options are functions that can be passed when constructing you Storm instance. You can pass it any number of options.
|
||
|
|
||
|
#### BoltOptions
|
||
|
|
||
|
By default, Storm opens a database with the mode `0600` and a timeout of one second.
|
||
|
You can change this behavior by using `BoltOptions`
|
||
|
|
||
|
```go
|
||
|
db, err := storm.Open("my.db", storm.BoltOptions(0600, &bolt.Options{Timeout: 1 * time.Second}))
|
||
|
```
|
||
|
|
||
|
#### EncodeDecoder
|
||
|
|
||
|
To store the data in BoltDB, Storm encodes it in GOB by default. If you wish to change this behavior you can pass a codec that implements [`codec.EncodeDecoder`](https://godoc.org/github.com/asdine/storm/codec#EncodeDecoder) via the [`storm.Codec`](https://godoc.org/github.com/asdine/storm#Codec) option:
|
||
|
|
||
|
```go
|
||
|
db := storm.Open("my.db", storm.Codec(myCodec))
|
||
|
```
|
||
|
|
||
|
##### Provided Codecs
|
||
|
|
||
|
You can easily implement your own `EncodeDecoder`, but Storm comes with built-in support for [GOB](https://godoc.org/github.com/asdine/storm/codec/gob) (default), [JSON](https://godoc.org/github.com/asdine/storm/codec/json), [Sereal](https://godoc.org/github.com/asdine/storm/codec/sereal) and [Protocol Buffers](https://godoc.org/github.com/asdine/storm/codec/protobuf)
|
||
|
|
||
|
These can be used by importing the relevant package and use that codec to configure Storm. The example below shows all three (without proper error handling):
|
||
|
|
||
|
```go
|
||
|
import (
|
||
|
"github.com/asdine/storm"
|
||
|
"github.com/asdine/storm/codec/gob"
|
||
|
"github.com/asdine/storm/codec/json"
|
||
|
"github.com/asdine/storm/codec/sereal"
|
||
|
"github.com/asdine/storm/codec/protobuf"
|
||
|
)
|
||
|
|
||
|
var gobDb, _ = storm.Open("gob.db", storm.Codec(gob.Codec))
|
||
|
var jsonDb, _ = storm.Open("json.db", storm.Codec(json.Codec))
|
||
|
var serealDb, _ = storm.Open("sereal.db", storm.Codec(sereal.Codec))
|
||
|
var protobufDb, _ = storm.Open("protobuf.db", storm.Codec(protobuf.Codec))
|
||
|
```
|
||
|
|
||
|
#### Auto Increment
|
||
|
|
||
|
Storm can auto increment integer IDs so you don't have to worry about that when saving your objects.
|
||
|
|
||
|
```go
|
||
|
db := storm.Open("my.db", storm.AutoIncrement())
|
||
|
```
|
||
|
|
||
|
## Nodes and nested buckets
|
||
|
|
||
|
Storm takes advantage of BoltDB nested buckets feature by using `storm.Node`.
|
||
|
A `storm.Node` is the underlying object used by `storm.DB` to manipulate a bucket.
|
||
|
To create a nested bucket and use the same API as `storm.DB`, you can use the `DB.From` method.
|
||
|
|
||
|
```go
|
||
|
repo := db.From("repo")
|
||
|
|
||
|
err := repo.Save(&Issue{
|
||
|
Title: "I want more features",
|
||
|
Author: user.ID,
|
||
|
})
|
||
|
|
||
|
err = repo.Save(newRelease("0.10"))
|
||
|
|
||
|
var issues []Issue
|
||
|
err = repo.Find("Author", user.ID, &issues)
|
||
|
|
||
|
var release Release
|
||
|
err = repo.One("Tag", "0.10", &release)
|
||
|
```
|
||
|
|
||
|
You can also chain the nodes to create a hierarchy
|
||
|
|
||
|
```go
|
||
|
chars := db.From("characters")
|
||
|
heroes := chars.From("heroes")
|
||
|
enemies := chars.From("enemies")
|
||
|
|
||
|
items := db.From("items")
|
||
|
potions := items.From("consumables").From("medicine").From("potions")
|
||
|
```
|
||
|
You can even pass the entire hierarchy as arguments to `From`:
|
||
|
|
||
|
```go
|
||
|
privateNotes := db.From("notes", "private")
|
||
|
workNotes := db.From("notes", "work")
|
||
|
```
|
||
|
|
||
|
## Simple Key/Value store
|
||
|
|
||
|
Storm can be used as a simple, robust, key/value store that can store anything.
|
||
|
The key and the value can be of any type as long as the key is not a zero value.
|
||
|
|
||
|
Saving data :
|
||
|
```go
|
||
|
db.Set("logs", time.Now(), "I'm eating my breakfast man")
|
||
|
db.Set("sessions", bson.NewObjectId(), &someUser)
|
||
|
db.Set("weird storage", "754-3010", map[string]interface{}{
|
||
|
"hair": "blonde",
|
||
|
"likes": []string{"cheese", "star wars"},
|
||
|
})
|
||
|
```
|
||
|
|
||
|
Fetching data :
|
||
|
```go
|
||
|
user := User{}
|
||
|
db.Get("sessions", someObjectId, &user)
|
||
|
|
||
|
var details map[string]interface{}
|
||
|
db.Get("weird storage", "754-3010", &details)
|
||
|
|
||
|
db.Get("sessions", someObjectId, &details)
|
||
|
```
|
||
|
|
||
|
Deleting data :
|
||
|
```go
|
||
|
db.Delete("sessions", someObjectId)
|
||
|
db.Delete("weird storage", "754-3010")
|
||
|
```
|
||
|
|
||
|
## BoltDB
|
||
|
|
||
|
BoltDB is still easily accessible and can be used as usual
|
||
|
|
||
|
```go
|
||
|
db.Bolt.View(func(tx *bolt.Tx) error {
|
||
|
bucket := tx.Bucket([]byte("my bucket"))
|
||
|
val := bucket.Get([]byte("any id"))
|
||
|
fmt.Println(string(val))
|
||
|
return nil
|
||
|
})
|
||
|
```
|
||
|
|
||
|
A transaction can be also be passed to Storm
|
||
|
|
||
|
```go
|
||
|
db.Bolt.Update(func(tx *bolt.Tx) error {
|
||
|
...
|
||
|
dbx := db.WithTransaction(tx)
|
||
|
err = dbx.Save(&user)
|
||
|
...
|
||
|
return nil
|
||
|
})
|
||
|
```
|
||
|
|
||
|
## TODO
|
||
|
|
||
|
- Search
|
||
|
- Reverse order
|
||
|
- More indexes
|
||
|
|
||
|
## License
|
||
|
|
||
|
MIT
|
||
|
|
||
|
## Author
|
||
|
|
||
|
**Asdine El Hrychy**
|
||
|
|
||
|
- [Twitter](https://twitter.com/asdine_)
|
||
|
- [Github](https://github.com/asdine)
|