ObjectDB is a document-oriented NoSQL database for Go.
- Embedded: No database server required
- Document-oriented: Store and query structs as JSON documents
- Tiny: Simple and lightweight
Internally, ObjectDB uses the Pebble LSM key-value store as its storage engine.
go get github.com/boonsuen/objectdb
ObjectDB stores documents in collections. A collection is a set of documents. A database can have multiple collections.
db, err := objectdb.Open("db")
if err != nil {
log.Fatal(err)
return
}
defer db.Close()
Collections are created implicitly when a document is inserted into a collection. Each document is identified by a unique UUID, which is added to the document as the _id
field.
Insert a document into a collection:
type Employee struct {
Name string `json:"name"`
Age json.Number `json:"age"`
}
employee := Employee{
Name: "John",
Age: "30",
}
id, err := db.InsertOne("employees", employee)
if err != nil {
log.Fatal(err)
}
Insert multiple documents into a collection:
type Address struct {
Postcode string `json:"postcode"`
}
type Restaurant struct {
Name string `json:"name"`
Cuisine string `json:"cuisine"`
Address Address `json:"address"`
}
newRestaurants := []Restaurant{
{"Restaurant A", "Italian", Address{"80000"}},
{"Restaurant B", "Fast Food", Address{"10000"}},
{"Restaurant C", "Fast Food", Address{"10000"}},
{"Restaurant D", "Fast Food", Address{"10000"}},
}
ids, err := db.InsertMany("restaurants", newRestaurants)
if err != nil {
log.Fatalf("error inserting restaurants: %v", err)
return
} else {
log.Printf("Inserted restaurants' IDs: %v", ids)
}
A single document in a collection can be retrieved by using the FindOne
or FindOneByID
method. Use the Unmarshal
method to convert the document to a struct.
doc, err := db.FindOneById("employees", id)
if err != nil {
log.Fatal(err)
}
employee := Restaurant{}
err = objectdb.Unmarshal(doc, &employee)
if err != nil {
log.Fatalf("error unmarshalling restaurant: %v", err)
return
}
FindOne
returns the first matching document. It's similar to using FindMany
with a limit of 1.
// Find one employee with the age of 30
employee, err := db.FindOne("employees", objectdb.Query{
{"AND", []objectdb.Condition{
{Path: "age", Operator: "=", Value: 30},
}},
})
To find multiple matching documents in a collection, use the FindMany
method.
With empty query and options, it returns all documents in the collection.
employees, err := db.FindMany("employees", objectdb.Query{}, objectdb.Options{})
The Options
struct specifies the limit of the number of matching documents to return.
objectdb.Options{Limit: 2}
The Query
struct specifies the conditions to filter the documents.
The following example finds 2 Fast Food restaurants with the postcode of 10000.
resQuery := objectdb.Query{
{"AND", []objectdb.Condition{
{Path: "cuisine", Operator: "=", Value: "Fast Food"},
{Path: "address.postcode", Operator: "=", Value: "10000"},
}},
}
ffRestaurants, err := db.FindMany("restaurants", resQuery, objectdb.Options{Limit: 2})
The query accepts multiple conditions. The AND
and OR
operators can be used to combine the conditions. Top-level conditions (each element in the Query
slice) are implicitly combined with the AND
operator. Only two levels of nesting are supported.
query := objectdb.Query{
{"AND", []objectdb.Condition{
{Path: "name", Operator: "=", Value: "John"},
{Path: "age", Operator: ">=", Value: "27"},
}},
{"OR", []objectdb.Condition{
{Path: "address.city", Operator: "=", Value: "NY"},
{Path: "address.postcode", Operator: "=", Value: "10000"},
}},
}
Query above is equivalent to the following SQL where clause:
WHERE (name = 'John' AND age >= 27) AND (address.city = 'NY' OR address.postcode = '10000')
To delete a document, use the DeleteOneById
method.
err = db.DeleteOneById("collectionName", id)
ObjectDB keep tracks of the path-value pairs of the documents in a index. This allows for efficient querying of documents for certain queries. A search will fall back to a full collection scan when it is not possible to solely rely on the index to satisfy the query.
Aside from querying using the Find methods, ObjectDB also supports full-text search that scales well with large collections.
To allow full-text search on a field, annotate the field with the textIndex
tag. It will be indexed and its text content can be searched in a full-text search query. Note that the field must be of string type.
type Restaurant struct {
Name string `json:"name" objectdb:"textIndex"`
Cuisine string `json:"cuisine" objectdb:"textIndex"`
}
To perform a full-text search, use the Search
method.
documents, err := db.Search("collectionName", "search query")