iktakahiro / fjord

A Golang Struct/DatabaseRecord Mapper (thin O/R Mapper) package.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

fjord

CircleCI

fjord is a Golang Struct/databaseRecord Mapper (thin O/R Mapper) package.

fjord

Driver support

  • PostgreSQL 9.6
  • (MySQL 5.6)

Go versions

  • 1.8

Install

go get "github.com/iktakahiro/fjord"

If you use glide:

glide get "github.com/iktakahiro/fjord"

Getting Started

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/iktakahiro/fjord"
)


dsn := "fj_test:password@tcp(127.0.0.1:3306)/fj_test?charset=utf8mb4,utf8"
conn, _ := fjord.Open("mysql", dsn)

// for PostgreSQL
// dsn := "user=fj_test dbname=fj_test password=password sslmode=disable"
// conn, _ := fjord.Open("postgres", dsn)


sess := conn.NewSession(nil)

var suggestion Suggestion

sess.Select("id", "title").
    From("suggestions").
    Where("id = ?", 1).
    Load(&suggestion)

Context Support

fjord supports context:

conn, _ := fjord.Open("mysql", dsn)

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)

sess := conn.NewSessionContext(ctx, nil)

options := &sql.TxOptions{
    Isolation: sql.LevelDefault,
    ReadOnly:  false,
}

// options may be nil.
tx, _ := sess.BeginTx(options)

// sleep to time out
time.Sleep(200 * time.Millisecond)

_, err = tx.Update("person").Where(Eq("id", 1)).Set("name", "john Titor").Exec()
if err != nil {
   // context.DeadlineExceeded is occurred.
}

JOIN using Tag and Identifier

This syntax is one of the key features in fjord.

import (
    _ "github.com/go-sql-driver/mysql"
    fj "github.com/iktakahiro/fjord"
)

type Person struct {
    ID   int    `db:"p.id"` // "p" is the alias name of "person table", "id" is a column name.
    Name string `db:"p.name"`
}

type Role struct {
    PersonID int    `db:"r.person_id"`
    Name     string `db:"r.name"`
}

type PersonForJoin struct {
    Person
    Role
}

// ...

person := &PersonForJoin{ID: 1}

sess.Select(fj.I("p.id"), fj.I("p.name"), fj.I("r.person_id"), fj.I("r.name")).
    From(fj.I("person").As("p")).
    LeftJoin(fj.I("role").As("r"), "p.id = r.person_id").
    Where("p.id = ?", person.ID).
    Load(person)

// In MySQL dialect:
// SELECT `p`.`id` AS p__id, `p`.`name` AS p__Name, `r`.`person_id` r__person_id, `r`.`name`
//    FROM `person` AS p
//    LEFT JOIN `role` AS r on p.id = r.person_id
//    WHERE p.id = 1;
//
// Results of mapping:
//     person.id      => PersonForJoin.Person.ID
//     person.name    => PersonForJoin.Person.Name
//     role.person_id => PersonForJoin.Role.PersonID
//     role.name      => PersonForJoin.Role.Name

When a tag contains ".", converts a column name for loading destination into the alias format.

  • db:"p.id" => p__id

And, when you use I() function in a select statement, a column alias is generated automatically.

  • Select(I("p.id")) => SELECT p.id AS p__id FROM...

The above syntax is same as:

  • Select(I("p.id").As("p__id"))
  • Select("p.id AS p__id")

CRUD

CRUD example using the bellow struct.

type Suggestion struct {
    ID    int64
    Title string
    Body  fjord.NullString
}

SELECT

var suggestion Suggestion

sess.Select("id", "title").
    From("suggestion").
    Where("id = ?", 1).
    Load(&suggestion)

You can implement as a method:

func (s *Suggestion) LoadByID(sess *fjord.session) (count int, err error) {
    count, err = sess.Select("id", "title").
    From("suggestion").
    Where("id = ?", s.ID).
    Load(&s)
           
    return
}

// suggestion := &Suggestion{ID: 1}
// count, err := suggestion.LoadByID(sess)

INSERT

suggestion := &Suggestion{Title: "Gopher", Body: "I love Go."}

sess.InsertInto("suggestion").
    Columns("title", "body").
    Record(suggestion).
    Exec()

As a method:

func(s *Suggestion) Save(sess *fjord.session) (err error) {
    err = sess.InsertInto("suggestion").
        Columns("title", "body").
        Record(s).
        Exec()
           
    return
}

You can also set values to insert directly:

sess.InsertInto("suggestion").
    Columns("title", "body").
    Values("Gopher", "I love Go.").
    Exec()

Inserting multiple records:

sess.InsertInto("suggestion").
    Columns("title", "body").
    Record(suggestion1).
    Record(suggestion2).
    Exec()

UPDATE

sess.Update("suggestions").
    Set("title", "Gopher").
    Set("body", "We love Go.").
    Where("id = ?", 1).
    Exec()

SetMap() is helpful when you need to update multiple columns.

setMap := map[string]interface{}{
    "title": "Gopher",
    "body":  "We love Go.",
}

sess.Update("suggestion").
    SetMap(setMap).
    Where("id = ?", 1).
    Exec()

DELETE

sess.DeleteFrom("suggestion").
    Where("id = ?", 1).
    Exec()

Soft Delete is not implemented, use Update() manually.

sess.Update("suggestion").
    Set("deleted_at", time.Now().Unix()).
    Where("id = ?", 1).
    Exec()

Transactions

tx, err := sess.Begin()
if err != nil {
        return err
}
defer tx.RollbackUnlessCommitted()

// do stuff...

return tx.Commit()

Load database values to struct fields

// Columns are mapped by tag then by field
type Suggestion struct {
    ID     int64            // id
    Title  string           // title
    Url    string           `db:"-"` // ignored
    secret string           // ignored
    Body   fjord.NullString `db:"content"` // content
    User   User
}

// By default, fjord converts CamelCase field names to snake_case column_names
// You can override this with struct tags, just like with JSON tags
type Suggestion struct {
    Id        int64
    Title     fjord.NullString `db:"subject"` // subjects are called titles now
    CreatedAt fjord.NullTime
}

var suggestions []Suggestion
sess.Select("*").From("suggestion").Load(&suggestions)

Table name alias

sess.Select("s.id", "s.title").
    From(fjord.I("suggestion").As("s")).
    Load(&suggestions)

JOIN

sess.Select("*").From("suggestion").
  Join("subdomain", "suggestion.subdomain_id = subdomain.id")

sess.Select("*").From("suggestion").
  LeftJoin("subdomain", "suggestions.subdomain_id = subdomain.id")

sess.Select("*").From("suggestion").
  RightJoin("subdomain", "suggestion.subdomain_id = subdomain.id")

sess.Select("*").From("suggestion").
  FullJoin("subdomain", "suggestion.subdomain_id = subdomain.id")

You can join on multiple tables:

sess.Select("*").From("suggestion").
  Join("subdomain", "suggestion.subdomain_id = subdomain.id").
  Join("account", "subdomain.account_id = account.id")

Sub Query

sess.Select("count(id)").From(
    fjord.Select("*").From("suggestion").As("count"),
)

IN

ids := []int64{1, 2, 3, 4, 5}
builder.Where("id IN ?", ids) // `id` IN ?

Union

fjord.Union(
    fjord.Select("*"),
    fjord.Select("*"),
)

fjord.UnionAll(
    fjord.Select("*"),
    fjord.Select("*"),
)

Union can be used in subquery.

fjord.Union(
    fjord.Select("*"),
    fjord.Select("*"),
).As("u1")

fjord.UnionAll(
    fjord.Select("*"),
    fjord.Select("*"),
).As("u2")

Building WHERE condition

  • And
  • Or
  • Eq
  • Neq
  • Gt
  • Gte
  • Lt
  • Lte
fjord.And(
    fjord.Or(
        fjord.Gt("created_at", "2015-09-10"),
        fjord.Lte("created_at", "2015-09-11"),
    ),
    fjord.Eq("title", "hello world"),
)

Plain SQL

builder := fjord.SelectBySql("SELECT `title`, `body` FROM `suggestion` ORDER BY `id` ASC LIMIT 10")

JSON Friendly Null* types

Every tries to JSON-encode a sql.NullString? You get:

{
    "str1": {
        "Valid": true,
        "String": "Hi!"
    },
    "str2": {
        "Valid": false,
        "String": ""
  }
}

Not quite what you want. fjord has fjord.NullString (and the rest of the Null* types) that encode correctly, giving you:

{
    "str1": "Hi!",
    "str2": null
}

Tips

Is the package name too long for humans? Set an alias.

import (
    fj "github.com/iktakahiro/fjord"
)

condition := fj.Eq("id", 1)

Thanks

gocraft/dbr

fjord is gocraft/dbr fork. gocraft/dbr is a suitable package in many cases.

I'm deeply grateful to the awesome project.

Pictures

The pretty beautiful picture is taken by @tpsdave.

About

A Golang Struct/DatabaseRecord Mapper (thin O/R Mapper) package.

License:Other


Languages

Language:Go 100.0%