/genji

Document-oriented, embedded SQL database, works with Bolt, Badger and memory

Primary LanguageGoMIT LicenseMIT

Genji

Genji

Document-oriented, embedded, SQL database

Table of contents

Introduction

Build Status go.dev reference Slack channel Fuzz

Genji is a schemaless database that allows running SQL queries on documents.

Checkout the SQL documentation, the Go doc and the usage example in the README to get started quickly.

⚠️ Genji's API is still unstable: Database compatibility is not guaranteed before reaching v1.0.0

Features

  • Optional schemas: Genji tables are schemaless, but it is possible to add constraints on any field to ensure the coherence of data within a table.
  • Multiple Storage Engines: It is possible to store data on disk or in ram, but also to choose between B-Trees and LSM trees. Genji relies on BoltDB and Badger to manage data.
  • Transaction support: Read-only and read/write transactions are supported by default.
  • SQL and Documents: Genji mixes the best of both worlds by combining powerful SQL commands with JSON.
  • Easy to use, easy to learn: Genji was designed for simplicity in mind. It is really easy to insert and read documents of any shape.
  • Compatible with the database/sql package

Installation

Install the Genji database

go get github.com/genjidb/genji

Usage

There are two ways of using Genji, either by using Genji's API or by using the database/sql package.

Using Genji's API

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/genjidb/genji"
    "github.com/genjidb/genji/document"
)

func main() {
    // 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()

    // Attach context, e.g. (*http.Request).Context().
    db = db.WithContext(context.Background())

    // Create a table. Schemas are optional, you don't need to specify one 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"]
    )`)

    // Go structures can be passed directly
    type User struct {
        ID              uint
        Name            string
        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)

    // 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, &u)
        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
    })
}

Using database/sql

// import Genji as a blank import
import _ "github.com/genjidb/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(...)

Engines

Genji currently supports storing data in BoltDB, Badger and in-memory.

Using the BoltDB engine

import (
    "log"

    "github.com/genjidb/genji"
)

func main() {
    db, err := genji.Open("my.db")
    defer db.Close()
}

Using the memory engine

import (
    "log"

    "github.com/genjidb/genji"
)

func main() {
    db, err := genji.Open(":memory:")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

Using the Badger engine

First install the module

go get github.com/genjidb/genji/engine/badgerengine
import (
    "context"
    "log"

    "github.com/genjidb/genji"
    "github.com/genjidb/genji/engine/badgerengine"
    "github.com/dgraph-io/badger/v2"
)

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(context.Background(), ng)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

Genji shell

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/genjidb/genji/cmd/genji

Example:

# Opening an in-memory database:
genji

# Opening a BoltDB database:
genji my.db

# Opening a Badger database:
genji --badger pathToData

Contributing

Contributions are welcome!

See CONTRIBUTING.md.

Thank you, contributors!

If you have any doubt, join the Gophers Slack channel or open an issue.