Genji is a document-oriented, embedded SQL database. It supports various engines that write data on-disk, like BoltDB and Badger, or in memory.
Genji is also compatible with the database/sql
package.
Install the Genji database
go get github.com/asdine/genji
There are two ways of using Genji, either by using Genji's API or by using the database/sql
package.
// Create a database instance, here we'll store everything on-disk using the BoltDB engine
db, err := genji.Open("my.db")
if err != nil {
log.Fatal(err)
}
// Don't forget to close the database when you're done
defer db.Close()
// Create a table. Genji tables are schemaless, you don't need to specify a schema if not needed.
err = db.Exec("CREATE TABLE user")
// Create an index.
err = db.Exec("CREATE INDEX idx_user_name ON test (name)")
// Insert some data
err = db.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 10, "Foo1", 15)
// Supported values can go from simple integers to richer data types like lists or documents
err = db.Exec(`
INSERT INTO user (id, name, age, address, friends)
VALUES (
11,
'Foo2',
20,
{"city": "Lyon", "zipcode": "69001"},
["foo", "bar", "baz"]
)`)
// It is also possible to insert values using document notation, which is a JSON-like notation with support for expressions.
err = db.Exec(`
INSERT INTO user
VALUES {
id: 11,
name: 'Foo2',
"age": 20,
"address": {"city": "Lyon", "zipcode": "69001"},
"friends": ["foo", "bar", "baz"],
single: 1 AND 1
}`)
// Or even to use structures
type User struct {
ID uint
Name []byte
TheAgeOfTheUser float64 `genji:"age"`
Address struct {
City string
ZipCode string
}
}
// Let's create a user
u := User{
ID: 20,
Name: "foo",
TheAgeOfTheUser: 40,
}
u.Address.City = "Lyon"
u.Address.ZipCode = "69001"
err := db.Exec(`INSERT INTO user VALUES ?`, &u)
// Use a transaction
tx, err := db.Begin(true)
defer tx.Rollback()
err = tx.Exec("INSERT INTO user (id, name, age) VALUES (?, ?, ?)", 12, "Foo3", 25)
...
err = tx.Commit()
// Query some documents
res, err := db.Query("SELECT id, name, age, address FROM user WHERE age >= ?", 18)
// always close the result when you're done with it
defer res.Close()
// Iterate over the results
err = res.Iterate(func(d document.Document) error {
// When querying an explicit list of fields, you can use the Scan function to scan them
// in order. Note that the types don't have to match exactly the types stored in the table
// as long as they are compatible.
var id int
var name string
var age int32
var address struct{
City string
ZipCode string
}
err = document.Scan(d, &id, &name, &age, &address)
if err != nil {
return err
}
fmt.Println(id, name, age, address)
// It is also possible to scan the results into a structure
var u User
err = document.StructScan(d, &user)
if err != nil {
return err
}
fmt.Println(u)
// Or scan into a map
var m map[string]interface{}
err = document.MapScan(d, &m)
if err != nil {
return err
}
fmt.Println(m)
return nil
})
// Count results
count, err := res.Count()
// Get first document from the results using the First method of the stream
d, err := res.First()
// Apply some transformations
err = res.
// Filter all even ids
Filter(func(d document.Document) (bool, error) {
f, err := d.GetByField("id")
...
id, err := f.DecodeToInt()
...
return id % 2 == 0, nil
}).
// Enrich the documents with a new field
Map(func(d document.Document) (document.Document, error) {
var fb document.FieldBuffer
err := fb.ScanDocument(r)
...
fb.Add(document.NewStringValue("group", "admin"))
return &fb, nil
}).
// Iterate on them
Iterate(func(d document.Document) error {
...
})
// import Genji as a blank import
import _ "github.com/asdine/genji/sql/driver"
// Create a sql/database DB instance
db, err := sql.Open("genji", "my.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Then use db as usual
res, err := db.ExecContext(...)
res, err := db.Query(...)
res, err := db.QueryRow(...)
Genji currently supports storing data in BoltDB, Badger and in-memory.
import (
"log"
"github.com/asdine/genji"
)
func main() {
db, err := genji.Open("my.db")
defer db.Close()
}
import (
"log"
"github.com/asdine/genji"
)
func main() {
db, err := genji.Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
The Badger engine must be installed first
go get github.com/asdine/genji/engine/badgerengine
Then, it can be instantiated using the genji.New
function:
import (
"log"
"github.com/asdine/genji"
"github.com/asdine/genji/engine/badgerengine"
"github.com/dgraph-io/badger"
)
func main() {
// Create a badger engine
ng, err := badgerengine.NewEngine(badger.DefaultOptions("mydb")))
if err != nil {
log.Fatal(err)
}
// Pass it to genji
db, err := genji.New(ng)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
The genji command line provides an SQL shell that can be used to create, modify and consult Genji databases.
Make sure the Genji command line is installed:
go get github.com/asdine/genji/cmd/genji
Example:
# Opening an in-memory database:
genji
# Opening a BoltDB database:
genji my.db
# Opening a Badger database:
genji --badger pathToData