• Stars
    star
    161
  • Rank 233,470 (Top 5 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 11 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple, better and easy-to-use ORM library for Golang

Genmai Build Status

Simple, better and easy-to-use ORM library for Golang.

Overview

  • flexibility with SQL-like API
  • Transaction support
  • Database dialect interface
  • Query logging
  • Update/Insert/Delete hooks
  • Embedded struct

Database dialect currently supported are:

  • MySQL
  • PostgreSQL
  • SQLite3

Installation

go get -u github.com/naoina/genmai

Schema

Schema of the table will be defined as a struct.

// The struct "User" is the table name "user".
// The field name will be converted to lowercase/snakecase, and used as a column name in table.
// e.g. If field name is "CreatedAt", column name is "created_at".
type User struct {
    // PRIMARY KEY. and column name will use "tbl_id" instead of "id".
    Id int64 `db:"pk" column:"tbl_id"`

    // NOT NULL and Default is "me".
    Name string `default:"me"`

    // Nullable column must use a pointer type, or sql.Null* types.
    CreatedAt *time.Time

    // UNIQUE column if specify the db:"unique" tag.
    ScreenName string `db:"unique"`

    // Ignore column if specify the db:"-" tag.
    Active bool `db:"-"`
}

Query API

Create table

package main

import (
    "time"

    _ "github.com/mattn/go-sqlite3"
    // _ "github.com/go-sql-driver/mysql"
    // _ "github.com/lib/pq"
    "github.com/naoina/genmai"
)

// define a table schema.
type TestTable struct {
    Id        int64      `db:"pk" column:"tbl_id"`
    Name      string     `default:"me"`
    CreatedAt *time.Time
    UserName  string     `db:"unique" size:"255"`
    Active    bool       `db:"-"`
}

func main() {
    db, err := genmai.New(&genmai.SQLite3Dialect{}, ":memory:")
    // db, err := genmai.New(&genmai.MySQLDialect{}, "dsn")
    // db, err := genmai.New(&genmai.PostgresDialect{}, "dsn")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    if err := db.CreateTable(&TestTable{}); err != nil {
        panic(err)
    }
}

Insert

A single insert:

obj := &TestTable{
    Name: "alice",
    Active: true,
}
n, err := db.Insert(obj)
if err != nil {
    panic(err)
}
fmt.Printf("inserted rows: %d\n", n)

Or bulk-insert:

objs := []TestTable{
    {Name: "alice", Active: true},
    {Name: "bob", Active: true},
}
n, err := db.Insert(objs)
if err != nil {
    panic(err)
}
fmt.Printf("inserted rows: %d\n", n)

Select

var results []TestTable
if err := db.Select(&results); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Where

var results []TestTable
if err := db.Select(&results, db.Where("tbl_id", "=", 1)); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

And/Or

var results []TestTable
if err := db.Select(&results, db.Where("tbl_id", "=", 1).And(db.Where("name", "=", "alice").Or("name", "=", "bob"))); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

In

var results []TestTable
if err := db.Select(&results, db.Where("tbl_id").In(1, 2, 4)); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Like

var results []TestTable
if err := db.Select(&results, db.Where("name").Like("%li%")); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Between

var results []TestTable
if err := db.Select(&results, db.Where("name").Between(1, 3)); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Is Null/Is Not Null

var results []TestTable
if err := db.Select(&results, db.Where("created_at").IsNull()); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)
results = []TestTable{}
if err := db.Select(&results, db.Where("created_at").IsNotNull()); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Order by/Offset/Limit

var results []TestTable
if err := db.Select(&results, db.OrderBy("id", genmai.ASC).Offset(2).Limit(10)); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Distinct

var results []TestTable
if err := db.Select(&results, db.Distinct("name"), db.Where("name").Like("%")); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

Count

var n int64
if err := db.Select(&n, db.Count(), db.From(&TestTable{})); err != nil {
    panic(err)
}
fmt.Printf("%v\n", n)

With condition:

var n int64
if err := db.Select(&n, db.Count(), db.From(&TestTable{}), db.Where("id", ">", 100)); err != nil {
    panic(err)
}
fmt.Printf("%v\n", n)

Join

Inner Join:

package main

import (
    "database/sql"
    "fmt"
    "time"

    _ "github.com/mattn/go-sqlite3"
    // _ "github.com/go-sql-driver/mysql"
    // _ "github.com/lib/pq"
    "github.com/naoina/genmai"
)

type TestTable struct {
    Id        int64  `db:"pk" column:"tbl_id"`
    Name      string `default:"me"`
    CreatedAt *time.Time
    Active    bool `db:"-"` // column to ignore.
}

type Table2 struct {
    Id   int64 `db:"pk" column:"tbl_id"`
    Body sql.NullString
}

func main() {
    db, err := genmai.New(&genmai.SQLite3Dialect{}, ":memory:")
    // db, err := genmai.New(&genmai.MySQLDialect{}, "dsn")
    // db, err := genmai.New(&genmai.PostgresDialect{}, "dsn")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    if err := db.CreateTable(&TestTable{}); err != nil {
        panic(err)
    }
    if err := db.CreateTable(&Table2{}); err != nil {
        panic(err)
    }
    objs1 := []TestTable{
        {Name: "alice", Active: true},
        {Name: "bob", Active: true},
    }
    objs2 := []Table2{
        {Body: sql.NullString{String: "something"}},
    }
    if _, err = db.Insert(objs1); err != nil {
        panic(err)
    }
    if _, err := db.Insert(objs2); err != nil {
        panic(err)
    }
    // fmt.Printf("inserted rows: %d\n", n)
    var results []TestTable
    if err := db.Select(&results, db.Join(&Table2{}).On("tbl_id")); err != nil {
        panic(err)
    }
    fmt.Printf("%v\n", results)
}

Left Join:

var results []TestTable
if err := db.Select(&results, db.LeftJoin(&Table2{}).On("name", "=", "body")); err != nil {
    panic(err)
}
fmt.Printf("%v\n", results)

RIGHT OUTER JOIN and FULL OUTER JOIN are still unsupported.

Update

var results []TestTable
if err := db.Select(&results); err != nil {
    panic(err)
}
obj := results[0]
obj.Name = "nico"
if _, err := db.Update(&obj); err != nil {
    panic(err)
}

Delete

A single delete:

obj := TestTable{Id: 1}
if _, err := db.Delete(&obj); err != nil {
    panic(err)
}

Or bulk-delete:

objs := []TestTable{
    {Id: 1}, {Id: 3},
}
if _, err := db.Delete(objs); err != nil {
    panic(err)
}

Transaction

defer func() {
    if err := recover(); err != nil {
        db.Rollback()
    } else {
        db.Commit()
    }
}()
if err := db.Begin(); err != nil {
    panic(err)
}
// do something.

Using any table name

You can implement TableNamer interface to use any table name.

type UserTable struct {
    Id int64 `db:"pk"`
}

func (u *UserTable) TableName() string {
    return "user"
}

In the above example, the table name user is used instead of user_table.

Using raw database/sql interface

rawDB := db.DB()
// do something with using raw database/sql interface...

Query logging

By default, query logging is disabled. You can enable Query logging as follows.

db.SetLogOutput(os.Stdout) // Or any io.Writer can be passed.

Also you can change the format of output as follows.

db.SetLogFormat("format string")

Format syntax uses Go's template. And you can use the following data object in that template.

- .time        time.Time object in current time.
- .duration    Processing time of SQL. It will format to "%.2fms".
- .query       string of SQL query. If it using placeholder,
               placeholder parameters will append to the end of query.

The default format is:

[{{.time.Format "2006-01-02 15:04:05"}}] [{{.duration}}] {{.query}}

In production, it is recommended to disable this feature in order to somewhat affect performance.

db.SetLogOutput(nil) // To disable logging by nil.

Update/Insert/Delete hooks

Genmai calls Before/After hook method if defined in model struct.

func (t *TestTable) BeforeInsert() error {
    t.CreatedAt = time.Now()
    return nil
}

If Before prefixed hook returns an error, it query won't run.

All hooks are:

  • BeforeInsert/AfterInsert
  • BeforeUpdate/AfterUpdate
  • BeforeDelete/AfterDelete

If use bulk-insert or bulk-delete, hooks method run for each object.

Embedded struct

Common fields can be defined on struct and embed that.

package main

import "time"

type TimeStamp struct {
    CreatedAt time.Time
    UpdatedAt time.Time
}

type User struct {
    Id   int64
    Name string

    TimeStamp
}

Also Genmai has defined TimeStamp struct for commonly used fields.

type User struct {
    Id int64

    genmai.TimeStamp
}

See the Godoc of TimeStamp for more information.

If you'll override hook method defined in embedded struct, you'll should call the that hook in overridden method. For example in above struct case:

func (u *User) BeforeInsert() error {
	if err := u.TimeStamp.BeforeInsert(); err != nil {
		return err
	}
	// do something.
	return nil
}

Documentation

API document and more examples are available here:

http://godoc.org/github.com/naoina/genmai

TODO

  • Benchmark
  • More SQL support
  • Migration

License

Genmai is licensed under the MIT

More Repositories

1

toml

TOML parser and encoder library for Golang
Go
293
star
2

denco

The fast and flexible HTTP request router for Go
Go
220
star
3

kocha

A convenient web application framework for Go
Go
165
star
4

migu

Database schema migration tool for Go
Go
94
star
5

miyabi

Graceful shutdown and restart for Go's net/http handlers
Go
31
star
6

pyroonga

Python interface for groonga
Python
21
star
7

kocha-urlrouter

Better URL router collection for Go
Go
19
star
8

go-stringutil

Faster string utilities implementation for Go
Go
11
star
9

alnair

A simple system integration framework
Python
10
star
10

generator-django-angular

Django with AngularJS generator
Python
9
star
11

nodemailer-browserpreview-transport

In-browser preview transport module for Nodemailer
JavaScript
7
star
12

dotfiles

dotfiles
Shell
7
star
13

asynql

Channel-based asynchronous extensions on Go's database/sql
Go
6
star
14

hugo-jupyter-handler

Jupyter Notebook Handler for Hugo (Deprecated)
Go
6
star
15

vocanew

vocanew
CSS
4
star
16

html5-pronama-demo

WebSocket demonstration for HTML5 pronama study session.
Python
4
star
17

nicovideo

A library for nicovideo
Python
4
star
18

go-adventcalendar-2014-bench

Go Advent Calendar 2014 17日目ベンチマーク
Go
3
star
19

flight-mocha

Extensions to the Mocha test framework for use with Flight
JavaScript
2
star
20

kuune.org

Jupyter Notebook
2
star
21

revel-boilerplate

Revel Boilerplate
Go
2
star
22

gocchan

FeatureToggle library for golang
Go
2
star
23

github-checkbox-checker

GitHub checkbox checker
Go
1
star
24

travis-win-test

Go
1
star
25

loggan

Leveled structured logger for Go
Go
1
star
26

syntastic-MQL

A syntastic syntax checker for MQL4/5
Vim Script
1
star