This is an embedded document/json store based on badger key-value store.
- Document
id
must be database-wide unique. There is no notion of grouping documents (tables, collections, buckets, etc). id
andrev
are mandatory fields that must be present inside document json.
First, make sure to close the db when it is no longer needed:
db := createDB()
defer db.Close()
Database operations are safe to be done concurrently. In fact it is a characteristic of badger
that writes will get a performance boost when done concurrently.
Define a struct that has id
and rev
json tags. These two fields are not optional.
type post struct {
ID string `json:"id"`
Rev string `json:"rev"`
By string `json:"by,omitempty"`
Text string `json:"text,omitempty"`
At time.Time `json:"at,omitempty"`
Tags []string `json:"tags,omitempty"`
}
Now to put a post document inside the database:
p := &post{
ID: "POST:001",
By: "Frodo Baggins",
Text: "Awesome blog post!",
At: time.Now(),
Tags: []string{"golang", "nosql"},
}
db.Put(p)
Documents can be read from database, using their id
.
var result []post
db.Get(&result, "POST:001")
Deleting documents is also done using their id
.
db.Delete("POST:001")
Do not set the field with rev
tag. It is used for optimistic concurrency and is filled automatically by Put()
method.
Queries can be performed using Views, which are predefined queries. Queries must be defined right after opening the database.
This View emits the day of posts:
db.AddView(NewView("time-day",
func(em Emitter, id string, doc interface{}) {
c, ok := doc.(*post)
if !ok {
return
}
em.Emit([]byte(c.At.Format("2006-01-02")), nil)
return
}))
View functions will be executed after put operations, inside the same write transaction, right before commit. So the Views are also consistent. An Emitter
, the id
of the document and the document itself is passed to View function. time-day
is a View that only indexes posts and ignores other type of documents.
Now we can query this view:
db.Query(Q{View: "time-day", Start: []byte("2018-05-20"), End: []byte("2018-05-30")})
Queries return the document id (view key) and other stuff generated by the view. For querying all documents, leave View
field empty.
Nice thing about views is that it is possible to index and query nested structs.
Let's define a view that allows us to query posts based on their tags:
db.AddView(NewView("tags",
func(em Emitter, id string, doc interface{}) {
c, ok := doc.(*post)
if !ok {
return
}
for _, v := range c.Tags {
em.Emit([]byte(v), nil)
}
return
}))
Here Emit()
method is called multiple times, for each tag.
To query and find posts that are tagged with golang
:
db.Query(Q{View: "tags", Start: []byte("golang"), Prefix: []byte("golang")})
Prefix
is set because we do not need tags greater than golang
- like gozoo
!