rebecca

package module
v0.0.0-...-e4bb11d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2016 License: MIT Imports: 6 Imported by: 0

README

rebecca Build Status Coverage

Simple database convenience wrapper for Go language.

Documentation

Docs are available on http://godoc.org/github.com/waterlink/rebecca

Installation

go get -u github.com/waterlink/rebecca

Usage

import "github.com/waterlink/rebecca"

It is recommended to import it as a bec shortcut to save some keystrokes:

import bec "github.com/waterlink/rebecca"
Designing record
type Person struct {
        rebecca.ModelMetadata `tablename:"people"`

        ID   int    `rebecca:"id" rebecca_primary:"true"`
        Name string `rebecca:"name"`
        Age  int    `rebecca:"age"`
}
Enabling specific driver
import "github.com/waterlink/rebecca/driver/pg"

And in code:

rebecca.SetupDriver(pg.NewDriver("postgres://user:pass@host:port/database?sslmode=sslmode"))
List of supported drivers
  • github.com/waterlink/rebecca/driver/pg - driver for postgresql. Docs
  • TODO:
    • github.com/waterlink/rebecca/driver/cassandra - driver for cassandra.
    • github.com/waterlink/rebecca/driver/mysql - driver for mysql.
    • github.com/waterlink/rebecca/driver/mongo - driver for mongodb.
Fetching the record
var p Person
if err := rebecca.Get(&p, ID); err != nil {
        // handle error here
}

// use &p at this point as a found model instance
Saving record
// creates new record
p := &Person{Name: "John Smith", Age: 31}
if err := rebecca.Save(p); err != nil {
        // handle error here
}

// updates the record
p := &Person{}
if err := rebecca.Get(p, ID); err != nil {
        // handle error here
}

p.Age++
if err := rebecca.Save(p); err != nil {
        // handle error here
}
Fetching all records
people := []Person{}
if err := rebecca.All(&people); err != nil {
        // handle error here
}

// people slice will contain found records
Fetching specific records
kids := []Person{}
if err := rebecca.Where(&kids, "age < $1", 12); err != nil {
        // handle error here
}

// kids slice will contain found records
Fetching only first record
kid := &Person{}
if err := rebecca.First(kid, "age < $1", 12); err != nil {
        // handle error here
}

// kid will contain found first record
Removing record
// Given p is *Person:
if err := rebecca.Remove(p); err != nil {
        // handle error here
}
Executing query and discarding its result
if err := rebecca.Exec("UPDATE counters SET value = value + 1 WHERE id = $1", ID); err != nil {
        // handle error here
}
Fetching count for something

First lets define the view for this purpose:

type PeopleCount struct {
          rebecca.ModelMetadata `tablename:"people"`

          Count int `rebecca:"count(id)"`
}

And then, lets query for this count:

kidsCount := &PeopleCount{}
if err := rebecca.First(kidsCount, "age < $1", 12); err != nil {
        // handle error here
}

// Now you can use `kidsCount.Count`
Using order, limit and skip

For example, to fetch second 300 records ordered by age.

For that purpose use rebecca.Context struct, documentation on which and all available options can be found here: rebecca.Context

ctx := &rebecca.Context{Order: "age ASC", Limit: 300, Skip: 300}
// you can also use `Offset: 300`

kidsBatch := []Person{}
if err := ctx.Where(&kidsBatch, "age < $1", 12); err != nil {
        // handle error here
}

// Now you can use kidsBatch as a second batch of 300 records ordered by age.

This example uses following options:

  • Order - ordering of the query, maps to ORDER BY clause in various SQL dialects.
  • Limit - maximum amount of records to be queried, maps to LIMIT clause.
  • Skip (or its alias Offset) - defines amount of records to skip, maps to OFFSET clause.

Don't confuse rebecca.Context with this interface: rebecca/context. This interface is internal and used only by drivers. rebecca.Context implements it. This interface is required to avoid circular dependencies.

Using aggregation

First, lets define our view for aggregation results:

type PeopleByAge struct {
        rebecca.ModelMetadata `tablename:"people"`

        Age   int `rebecca:"age"`
        Count int `rebecca:"count(distinct(id))"`
}

Next, lets query this using the Context:

ctx := &rebecca.Context{Group: "age"}

peopleByAge := []PeopleByAge{}
if err := ctx.All(&peopleByAge); err != nil {
        // handle error here
}

// Now peopleByAge represents slice of age => count relationship.

This example uses folowing option of rebecca.Context:

  • Group - defines grouping criteria of the query, maps to GROUP BY clause in various SQL dialects.
Using transactions
Simple usage
rebecca.Transact(func(tx *rebecca.Transaction) error {
	p := &Person{...}
	if err != tx.Save(p); err != nil {
		return fmt.Errorf("Unable to save person - %s", err)
	}

  // .. do more stuff within transaction ..
})

In case provided function returned error or panicked, it will roll transaction back. Otherwise it will commit it.

Advanced usage

First, obtain rebecca.Transaction object by doing:

tx, err := rebecca.Begin()
if err != nil {
        // handle error here
}
defer tx.Rollback()

Next, use tx as if it was rebecca normally:

p := &Person{Name: "John Smith", Age: 29}
if err := tx.Save(p); err != nil {
        // handle error here
}

// use tx.Save, tx.Get, tx.Where, tx.All, tx.First, tx.Exec as you would
// normally do with `rebecca`

And finally, commit the transaction:

if err := tx.Commit(); err != nil {
        // handle error, if it is impossible to commit the transaction
}

Or rollback, if you need to:

tx.Rollback()

Don't worry about doing defer tx.Rollback() in your functions. tx.Rollback(), when done after commit, is a noop.

Using Context with Transactions

To use context with transaction, you just need to create context using your transaction instance:

ctx := tx.Context(&rebecca.Context{Order: "age DESC"})
people := []Person{}
if err := ctx.Where(&people, "age < $1", 23); err != nil {
        // handle error here
}

Development

After cloning and cd-ing into this repo, run go get ./... to get all dependencies going.

Next make sure current codebase is in green state with go test ./....

It is encouraged to use TDD, i.e.: first write/change a test for your change, then make it green by making necessary modifications to the source.

Make sure your editor runs goimports tool on save.

When you are done, make sure you have run whole test suite, golint ./... and go vet ./....

Contributing

  1. Fork it (https://github.com/waterlink/rebecca)
  2. Clone it (git clone git@github.com:my-username/rebecca.git)
  3. cd to it (cd rebecca)
  4. Create your feature branch (git checkout -b my-new-feature)
  5. Commit your changes (git commit -am 'Add some new feature')
  6. Push the branch to your fork (git push -u origin my-new-feature)
  7. Create a new Pull Request on Github

Contributors

  • waterlink - Oleksii Fedorov, author, maintainer

Documentation

Overview

Package rebecca is lightweight convenience library for work with database

See github README for instructions: https://github.com/waterlink/rebecca#rebecca

See examples: https://godoc.org/github.com/waterlink/rebecca#pkg-examples

Simple example:

type Person struct {
        rebecca.ModelMetadata `tablename:"people"`

        ID   int    `rebecca:"id" rebecca_primary:"true"`
        Name string `rebecca:"name"`
        Age  int    `rebecca:"age"`
}

// Create new record
p := &Person{Name: "John", Age: 34}
if err := rebecca.Save(p); err != nil {
        // handle error here
}
fmt.Print(p)

// Update existing record
p.Name = "John Smith"
if err := rebecca.Save(p); err != nil {
        // handle error here
}
fmt.Print(p)

// Get record by its primary key
p = &Person{}
if err := rebecca.Get(p, 25); err != nil {
        // handle error here
}
fmt.Print(p)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func All

func All(records interface{}) error

All is for fetching all records

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	people := []Person{}
	if err := rebecca.All(&people); err != nil {
		panic(err)
	}
	// At this point people contains all Person records.
	fmt.Print(people)
}
Output:

func Exec

func Exec(query string, args ...interface{}) error

Exec is for executing arbitrary query and discarding its result

Example
package main

import (
	"github.com/waterlink/rebecca"
)

func main() {
	ID := 25
	if err := rebecca.Exec("UPDATE counters SET value = value + 1 WHERE id = $1", ID); err != nil {
		panic(err)
	}
}
Output:

func First

func First(record interface{}, where string, args ...interface{}) error

First is for fetching only one specific record

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	person := &Person{}
	if err := rebecca.First(person, "name = $1", "John Smith"); err != nil {
		panic(err)
	}
	// At this point person contains first record from the database that has
	// name="John Smith".
	fmt.Print(person)
}
Output:

func Get

func Get(record interface{}, ID interface{}) error

Get is for fetching one record

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	person := &Person{}
	if err := rebecca.Get(person, 25); err != nil {
		panic(err)
	}
	// At this point person contains record with primary key equal to 25.
	fmt.Print(person)
}
Output:

func Remove

func Remove(record interface{}) error

Remove is for removing the record

Example
package main

import (
	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	// First lets find person with primary key = 25
	person := &Person{}
	if err := rebecca.Get(person, 25); err != nil {
		panic(err)
	}

	// And then, lets remove it
	if err := rebecca.Remove(person); err != nil {
		panic(err)
	}

	// At this point person with primary key = 25 was removed from database.
}
Output:

func Save

func Save(record interface{}) error

Save is for saving one record (either creating or updating)

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	// Lets first define our model:
	type Person struct {
		// the table name is people
		rebecca.ModelMetadata `tablename:"people"`

		// ID is of type int, in database it is mapped to `id` and it is a primary
		// key.
		// Name is of type string, in database it is mapped to `name`.
		// Age is of type int, in database it is mapped to `age`.
		ID   int    `rebecca:"id" rebecca_primary:"true"`
		Name string `rebecca:"name"`
		Age  int    `rebecca:"age"`
	}

	// And now, lets create new record:
	person := &Person{Name: "John", Age: 28}
	// And finally, lets save it to the database
	if err := rebecca.Save(person); err != nil {
		panic(err)
	}

	// At this point, record was saved to database as a new record and its ID
	// field was updated accordingly.
	fmt.Print(person)

	// Now lets modify our record:
	person.Name = "John Smith"
	// And save it
	if err := rebecca.Save(person); err != nil {
		panic(err)
	}

	// At this point, original record was update with new data.
	fmt.Print(person)
}
Output:

func SetupDriver

func SetupDriver(d driver.Driver)

SetupDriver is for configuring database driver

func Transact

func Transact(fn func(tx *Transaction) error) (err error)

Transact is for abstracting transaction handling. It commits transaction if fn returned nil, otherwise it rolls transaction back.

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	rebecca.Transact(func(tx *rebecca.Transaction) error {
		// Now you can use `tx` the same way as `rebecca` package, i.e.:
		people := []Person{}
		if err := tx.Where(&people, "name = $1 AND age > $2", "James", 25); err != nil {
			// returning non-nil result here will make transaction roll back
			return err
			// panicking will achieve the same result
			// panic(err)
		}

		// At this point people contains all Person records with name="James" and
		// with age > 25.
		fmt.Print(people)

		// This way you can use all main exported functions of rebecca package as
		// methods on `tx`:
		// - tx.All(records)
		// - tx.First(record, where, args...)
		// - tx.Get(record, ID)
		// - tx.Remove(record)
		// - tx.Save(record)
		// - tx.Where(records, where, args...)

		// For example:
		record := &Person{}
		return tx.Save(record)
	})
}
Output:

func Where

func Where(records interface{}, where string, args ...interface{}) error

Where is for fetching specific records

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	teenagers := []Person{}
	if err := rebecca.Where(teenagers, "age < $1", 21); err != nil {
		panic(err)
	}

	// At this point teenagers contains all Person records with age < 21.
	fmt.Print(teenagers)
}
Output:

Types

type Context

type Context struct {
	// Defines ordering of the query
	Order string

	// Defines grouping criteria of the query
	Group string

	// Defines maximum amount of records requested for the query
	Limit int

	// Defines starting record for the query
	Skip   int
	Offset int // alias of Skip
	// contains filtered or unexported fields
}

Context is for storing query context

func (*Context) All

func (c *Context) All(records interface{}) error

All is for fetching all records

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	ctx := rebecca.Context{Limit: 20, Skip: 40}
	people := []Person{}
	if err := ctx.All(&people); err != nil {
		panic(err)
	}
	// At this point people contains 20 Person records starting from 41th from
	// the database.
	fmt.Print(people)
}
Output:

Example (SkipOffsetAlias)
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	ctx := rebecca.Context{Limit: 20, Offset: 40}
	people := []Person{}
	if err := ctx.All(&people); err != nil {
		panic(err)
	}
	// At this point people contains 20 Person records starting from 41th from
	// the database.
	fmt.Print(people)
}
Output:

func (*Context) First

func (c *Context) First(record interface{}, query string, args ...interface{}) error

First is for fetching only one specific record

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	ctx := rebecca.Context{Order: "age DESC"}
	oldestTeenager := &Person{}
	if err := ctx.First(oldestTeenager, "age < $1", 21); err != nil {
		panic(err)
	}
	// At this point oldestTeenager will contain a Person record that is of
	// maximum age and that is of age < 21.
	fmt.Print(oldestTeenager)
}
Output:

func (*Context) GetGroup

func (c *Context) GetGroup() string

GetGroup is for fetching context's Group. Used by drivers

func (*Context) GetLimit

func (c *Context) GetLimit() int

GetLimit is for fetching context's Limit. Used by drivers

func (*Context) GetOrder

func (c *Context) GetOrder() string

GetOrder is for fetching context's Order. Used by drivers

func (*Context) GetSkip

func (c *Context) GetSkip() int

GetSkip is for fetching context's Skip. Also it fetches Offset if present, hence the alias. Used by drivers

func (*Context) GetTx

func (c *Context) GetTx() interface{}

GetTx is for fetching context's driver transaction state. Used by drivers

func (*Context) SetGroup

func (c *Context) SetGroup(group string) context.Context

SetGroup is for setting context's Group. Used by drivers

func (*Context) SetLimit

func (c *Context) SetLimit(limit int) context.Context

SetLimit is for setting context's Limit. Used by drivers

func (*Context) SetOrder

func (c *Context) SetOrder(order string) context.Context

SetOrder is for setting context's Order, it creates new Context. Used by drivers

func (*Context) SetSkip

func (c *Context) SetSkip(skip int) context.Context

SetSkip is for setting context's Skip. Used by drivers

func (*Context) SetTx

func (c *Context) SetTx(tx interface{}) context.Context

SetTx is for setting context's Tx. Used by drivers

func (*Context) Where

func (c *Context) Where(records interface{}, query string, args ...interface{}) error

Where is for fetching specific records

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	ctx := rebecca.Context{Order: "age DESC"}
	teenagers := []Person{}
	if err := ctx.Where(&teenagers, "age < $1", 21); err != nil {
		panic(err)
	}
	// At this point teenagers will contain a list of Person records sorted by
	// age in descending order and where age < 21.
	fmt.Print(teenagers)
}
Output:

type ModelMetadata

type ModelMetadata struct{}

ModelMetadata is for storing any metadata for the whole model

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ModelMetadata allows to provide table name for the model
		rebecca.ModelMetadata `tablename:"people"`

		ID   int    `rebecca:"id" rebecca_primary:"true"`
		Name string `rebecca:"name"`
		Age  int    `rebecca:"age"`
	}

	type PostsOfPerson struct {
		// Additionally you can have any expression as a table name, that your
		// driver will allow, for example, simple join:
		rebecca.ModelMetadata `tablename:"people JOIN posts ON posts.author_id = people.id"`

		// .. fields are defined here ..
	}

	type PersonCountByAge struct {
		rebecca.ModelMetadata `tablename:"people"`

		// Additionally you can use any expressions as a database mapping for
		// fields, that your driver will allow, for example,
		// count(distinct(field_name)):
		Count int `rebecca:"count(distinct(id))"`
		Age   int `rebecca:"age"`
	}

	// PersonCountByAge is useful, because it can be nicely used with
	// aggregation:
	byAge := []PersonCountByAge{}
	ctx := rebecca.Context{Group: "age"}
	if err := ctx.All(&byAge); err != nil {
		panic(err)
	}

	// At this point byAge contains counts of people per each distinct age.
	// Ordering depends on your chosen driver and database.
	fmt.Print(byAge)
}
Output:

type Recovered

type Recovered struct {
	Err error
}

Recovered represents recovered error

func (*Recovered) Error

func (r *Recovered) Error() string

Error implements error interface

type Transaction

type Transaction struct {
	// contains filtered or unexported fields
}

Transaction is for managing transactions for drivers that allow it

func Begin

func Begin() (*Transaction, error)

Begin is for creating proper transaction

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	// Lets begin transaction:
	tx, err := rebecca.Begin()
	if err != nil {
		// Handle error when unable to begin transaction here.
		panic(err)
	}
	// Lets make sure, that transaction gets rolled back if we return prematurely:
	defer tx.Rollback()

	// Now you can use `tx` the same way as `rebecca` package, i.e.:
	people := []Person{}
	if err := tx.Where(&people, "name = $1 AND age > $2", "James", 25); err != nil {
		panic(err)
	}

	// At this point people contains all Person records with name="James" and
	// with age > 25.
	fmt.Print(people)

	// This way you can use all main exported functions of rebecca package as
	// methods on `tx`:
	// - tx.All(records)
	// - tx.First(record, where, args...)
	// - tx.Get(record, ID)
	// - tx.Remove(record)
	// - tx.Save(record)
	// - tx.Where(records, where, args...)
	// - tx.Exec(query, args...)
}
Output:

func (*Transaction) All

func (tx *Transaction) All(records interface{}) error

All is for fetching all records

func (*Transaction) Commit

func (tx *Transaction) Commit() error

Commit is for committing the transaction

Example
package main

import (
	"github.com/waterlink/rebecca"
)

func main() {
	tx, err := rebecca.Begin()
	if err != nil {
		panic(err)
	}
	defer tx.Rollback()

	// .. doing some hard work with tx ..

	// And finally, lets commit the transaction:
	if err := tx.Commit(); err != nil {
		// Handle error, when transaction can not be committed, here.
		panic(err)
	}
	// At this point transaction `tx` is committed and should not be used
	// further.
}
Output:

func (*Transaction) Context

func (tx *Transaction) Context(ctx *Context) *Context

Context is for instantiating proper context for transaction

Example
package main

import (
	"fmt"

	"github.com/waterlink/rebecca"
)

func main() {
	type Person struct {
		// ...
	}

	tx, err := rebecca.Begin()
	if err != nil {
		panic(err)
	}
	defer tx.Rollback()

	// When you need to use rebecca.Context features (like Order or Limit/Skip,
	// or even Group) together with transaction, you can instantiate
	// rebecca.Context using transaction method Context:
	ctx := tx.Context(&rebecca.Context{Order: "age ASC", Limit: 30, Skip: 90})

	// And then use `ctx` as usual:
	people := []Person{}
	if err := ctx.All(&people); err != nil {
		panic(err)
	}
	fmt.Print(people)
}
Output:

func (*Transaction) Exec

func (tx *Transaction) Exec(query string, args ...interface{}) error

Exec is for executing a query within transaction and discarding its result

func (*Transaction) First

func (tx *Transaction) First(record interface{}, where string, args ...interface{}) error

First is for fetching only one specific record

func (*Transaction) Get

func (tx *Transaction) Get(record interface{}, ID interface{}) error

Get is for fetching one record

func (*Transaction) Remove

func (tx *Transaction) Remove(record interface{}) error

Remove is for removing the record

func (*Transaction) Rollback

func (tx *Transaction) Rollback()

Rollback is for rolling back the transaction

Example
package main

import (
	"github.com/waterlink/rebecca"
)

func main() {
	tx, err := rebecca.Begin()
	if err != nil {
		panic(err)
	}
	defer tx.Rollback()

	// Sometimes `defer tx.Rollback()` is not acceptable and you might need
	// better control, in that case, you can just call `tx.Rollback()` when
	// necessary:
	if someBadCondition() {
		tx.Rollback()
	}
}

func someBadCondition() bool {
	return true
}
Output:

func (*Transaction) Save

func (tx *Transaction) Save(record interface{}) error

Save is for saving one record (either creating or updating)

func (*Transaction) Where

func (tx *Transaction) Where(records interface{}, where string, args ...interface{}) error

Where is for fetching specific records

Directories

Path Synopsis
fake
Package fake is a limited in-memory implementation of rebecca.Driver It does not implement any rebecca.Context features.
Package fake is a limited in-memory implementation of rebecca.Driver It does not implement any rebecca.Context features.
pg
Package pg provides implementation of rebecca driver for postgres.
Package pg provides implementation of rebecca driver for postgres.
pg/auto
Package auto allows one to setup rebecca/driver/pg in a convenient way from environment variables.
Package auto allows one to setup rebecca/driver/pg in a convenient way from environment variables.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL