pop

package module
v3.23.0+incompatible Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2017 License: MIT Imports: 39 Imported by: 0

README

POP GoDoc Build Status

A Tasty Treat For All Your Database Needs

So what does Pop do exactly? Well, it wraps the absolutely amazing https://github.com/jmoiron/sqlx library. It cleans up some of the common patterns and workflows usually associated with dealing with databases in Go.

Pop makes it easy to do CRUD operations, run migrations, and build/execute queries. Is Pop an ORM? I'll leave that up to you, the reader, to decide.

Pop, by default, follows conventions that were defined by the ActiveRecord Ruby gem, http://www.rubyonrails.org. What does this mean?

  • Tables must have an "id" column and a corresponding "ID" field on the struct being used.
  • If there is a timestamp column named "created_at", "CreatedAt" on the struct, it will be set with the current time when the record is created.
  • If there is a timestamp column named "updated_at", "UpdatedAt" on the struct, it will be set with the current time when the record is updated.
  • Default database table names are lowercase, plural, and underscored versions of the struct name. Examples: User{} is "users", FooBar{} is "foo_bars", etc...

Supported Databases

  • PostgreSQL (>= 9.3)
  • MySQL (>= 5.7)
  • SQLite (>= 3.x)

Connecting to Databases

Pop is easily configured using a YAML file. The configuration file should be stored in config/database.yml or database.yml.

Example Configuration File
development:
  dialect: "postgres"
  database: "your_db_development"
  host: "localhost"
  port: "5432"
  user: "postgres"
  password: "postgres"

test:
  dialect: "mysql"
  database: "your_db_test"
  host: "localhost"
  port: "3306"
  user: "root"
  password: "root"

staging:
  dialect: "sqlite3"
  database: "./staging.sqlite"

production:
  dialect: "postgres"
  url: {{ env "DATABASE_URL" }}

Note that the database.yml file is also a Go template, so you can use Go template syntax. There are two special functions that are included, env and envOr.

  • env - This function will look for the named environment variable and insert it into your file. This is useful for configuring production databases without having to store secret information in your repository. {{ env "DATABASE_URL" }}
  • envOr - This function will look for the named environment variable and use it. If the variable can not be found a default value will be used. {{ envOr "MYSQL_HOST" "localhost" }}

You can generate a default configuration file using the init command:

$ soda g config

The default will generate a database.yml file in the current directory for a PostgreSQL database. You can override the type of database using the -t flag and passing in any of the supported database types: postgres, mysql, or sqlite3.

In your code

Once you have a configuration file defined you can easily connect to one of these connections in your application.

db, err := pop.Connect("development")
if err != nil {
  log.Panic(err)
}

Now that you have your connection to the database you can start executing queries against it.

CLI Support

Pop features CLI support via the soda command for the following operations:

  • creating databases
  • dropping databases
  • migrating databases
Installing CLI Support
$ go get github.com/markbates/pop/...
$ go install github.com/markbates/pop/soda
Creating Databases

Assuming you defined a configuration file like that described in the above section you can automatically create those databases using the soda command:

Create All Databases
$ soda create -a
Create a Specific Database
$ soda create -e development
Dropping Databases

Assuming you defined a configuration file like that described in the above section you can automatically drop those databases using the soda command:

Drop All Databases
$ soda drop -a
Drop a Specific Database
$ soda drop -e development
Models

The soda command supports the generation of models.

A full list of commands available for model generation can be found by asking for help:

$ soda generate help
Generate Models

The soda command will generate Go models and, optionally, the associated migrations for you.

$ soda generate model user name:text email:text

Running this command with generate the following files:

models/user.go
models/user_test.go
migrations/20170115024143_create_users.up.fizz
migrations/20170115024143_create_users.down.fizz

The models/user.go file contains a structure named User with fields ID, CreatedAt, UpdatedAt, Name, and Email. The first three correspond to the columns commonly found in ActiveRecord models as mentioned before, and the last two correspond to the additional fields specified on the command line. The known types are:

  • text (string in Go)
  • time or timestamp (time.Time)
  • nulls.Text (nulls.String) which corresponds to a nullifyable string, which can be distinguished from an empty string
  • uuid (uuid.UUID)
  • Other types are passed thru and are used as Fizz types.

The models/user_test.go contains tests for the User model and they must be implemented by you.

The other two files correspond to the migrations as explained below.

Migrations

The soda command supports the creation and running of migrations.

A full list of commands available for migration can be found by asking for help:

$ soda migrate help
Create Migrations

The soda command will generate SQL migrations (both the up and down) files for you.

$ soda migrate create name_of_migration

Running this command with generate the following files:

./migrations/20160815134952_name_of_migration.up.fizz
./migrations/20160815134952_name_of_migration.down.fizz

The generated files are fizz files. Fizz lets you use a common DSL for generating migrations. This means the same .fizz file can be run against any of the supported dialects of Pop! Find out more about Fizz

If you want to generate old fashion .sql files you can use the -t flag for that:

$ soda migrate create name_of_migration -t sql

Running this command with generate the following files:

./migrations/20160815134952_name_of_migration.up.sql
./migrations/20160815134952_name_of_migration.down.sql

The soda migrate command supports both .fizz and .sql files, so you can mix and match them to suit your needs.

Running Migrations

The soda command will run the migrations using the following command:

$ soda migrate up

Migrations will be run in sequential order. The previously run migrations will be kept track of in a table named schema_migrations in the database.

Migrations can also be run in reverse to rollback the schema.

$ soda migrate down
Find
user := models.User{}
err := tx.Find(&user, id)
Query
tx := models.DB
query := pop.Q(tx)
query = tx.Where("id = 1").Where("name = 'Mark'")
users := []models.User{}
err := query.All(&users)
Join Query
// page: page number
// perpage: limit
roles := []models.UserRole{}
query := pop.Q(models.DB).LeftJoin("roles", "roles.id=user_roles.role_id").
  LeftJoin("users u", "u.id=user_roles.user_id").
  Where(`roles.name like ?`, name).Paginate(page, perpage)

count, _ := query.Count(models.UserRole{})
sql, args := query.ToSQL(&pop.Model{Value: models.UserRole{}}, "user_roles.*",
  "roles.name as role_name", "u.first_name", "u.last_name")
//log.Printf("sql: %s, args: %v", sql, args)
err := pop.Q(models.DB).RawQuery(sql, args...).All(&roles)

Documentation

Overview

So what does Pop do exactly? Well, it wraps the absolutely amazing https://github.com/jmoiron/sqlx library. It cleans up some of the common patterns and workflows usually associated with dealing with databases in Go.

Pop makes it easy to do CRUD operations, run migrations, and build/execute queries. Is Pop an ORM? I'll leave that up to you, the reader, to decide.

Pop, by default, follows conventions that were defined by the ActiveRecord Ruby gem, http://www.rubyonrails.org. What does this mean?

* Tables must have an "id" column and a corresponding "ID" field on the `struct` being used. * If there is a timestamp column named "created_at", "CreatedAt" on the `struct`, it will be set with the current time when the record is created. * If there is a timestamp column named "updated_at", "UpdatedAt" on the `struct`, it will be set with the current time when the record is updated. * Default databases are lowercase, underscored versions of the `struct` name. Examples: User{} is "users", FooBar{} is "foo_bars", etc...

Index

Constants

This section is empty.

Variables

View Source
var Color = true
View Source
var ConfigName = "database.yml"
View Source
var Connections = map[string]*Connection{}

Connections contains all of the available connections

View Source
var Debug = false
View Source
var Log = func(s string, args ...interface{}) {
	if Debug {
		if len(args) > 0 {
			xargs := make([]string, len(args))
			for i, a := range args {
				switch a.(type) {
				case string:
					xargs[i] = fmt.Sprintf("%q", a)
				default:
					xargs[i] = fmt.Sprintf("%v", a)
				}
			}
			s = fmt.Sprintf("%s | %s", s, xargs)
		}
		if Color {
			s = color.YellowString(s)
		}
		logger.Println(s)
	}
}
View Source
var PaginatorPageKey = "page"
View Source
var PaginatorPerPageDefault = 20
View Source
var PaginatorPerPageKey = "per_page"

Functions

func AddLookupPaths

func AddLookupPaths(paths ...string) error

func CreateDB

func CreateDB(c *Connection) error

func DropDB

func DropDB(c *Connection) error

func LoadConfigFile

func LoadConfigFile() error

func LoadFrom

func LoadFrom(r io.Reader) error

LoadFrom reads a configuration from the reader and sets up the connections

func LookupPaths

func LookupPaths() []string

func MapTableName

func MapTableName(name string, tableName string)

MapTableName allows for the customize table mapping between a name and the database. For example the value `User{}` will automatically map to "users". MapTableName would allow this to change.

m := &pop.Model{Value: User{}}
m.TableName() // "users"

pop.MapTableName("user", "people")
m = &pop.Model{Value: User{}}
m.TableName() // "people"

func MigrationCreate

func MigrationCreate(path, name, ext string, up, down []byte) error

Types

type Connection

type Connection struct {
	ID      string
	Store   store
	Dialect dialect
	Elapsed int64
	TX      *tX
}

Connection represents all of the necessary details for talking with a datastore

func Connect

func Connect(e string) (*Connection, error)

Connect takes the name of a connection, default is "development", and will return that connection from the available `Connections`. If a connection with that name can not be found an error will be returned. If a connection is found, and it has yet to open a connection with its underlying datastore, a connection to that store will be opened.

func NewConnection

func NewConnection(deets *ConnectionDetails) (*Connection, error)

NewConnection creates a new connection, and sets it's `Dialect` appropriately based on the `ConnectionDetails` passed into it.

func (*Connection) All

func (c *Connection) All(models interface{}) error

All retrieves all of the records in the database that match the query.

c.All(&[]User{})

func (*Connection) BelongsTo

func (c *Connection) BelongsTo(model interface{}) *Query

BelongsTo adds a "where" clause based on the "ID" of the "model" passed into it.

func (*Connection) BelongsToThrough

func (c *Connection) BelongsToThrough(bt, thru interface{}) *Query

BelongsToThrough adds a "where" clause that connects the "bt" model through the associated "thru" model.

func (*Connection) Close

func (c *Connection) Close() error

func (*Connection) Count

func (c *Connection) Count(model interface{}) (int, error)

Count the number of records in the database.

c.Count(&User{})

func (*Connection) Create

func (c *Connection) Create(model interface{}, excludeColumns ...string) error

func (*Connection) Destroy

func (c *Connection) Destroy(model interface{}) error

func (*Connection) Find

func (c *Connection) Find(model interface{}, id interface{}) error

Find the first record of the model in the database with a particular id.

c.Find(&User{}, 1)

func (*Connection) First

func (c *Connection) First(model interface{}) error

First record of the model in the database that matches the query.

c.First(&User{})

func (*Connection) Last

func (c *Connection) Last(model interface{}) error

Last record of the model in the database that matches the query.

c.Last(&User{})

func (*Connection) Limit

func (c *Connection) Limit(limit int) *Query

Limit will add a limit clause to the query.

func (*Connection) MigrateDown

func (c *Connection) MigrateDown(path string, step int) error

func (*Connection) MigrateReset

func (c *Connection) MigrateReset(path string) error

func (*Connection) MigrateStatus

func (c *Connection) MigrateStatus(path string) error

func (*Connection) MigrateUp

func (c *Connection) MigrateUp(path string) error

func (*Connection) MigrationURL

func (c *Connection) MigrationURL() string

func (*Connection) NewTransaction

func (c *Connection) NewTransaction() (*Connection, error)

func (*Connection) Open

func (c *Connection) Open() error

func (*Connection) Order

func (c *Connection) Order(stmt string) *Query

Order will append an order clause to the query.

c.Order("name desc")

func (*Connection) Paginate

func (c *Connection) Paginate(page int, per_page int) *Query

Paginate records returned from the database.

q := c.Paginate(2, 15)
q.All(&[]User{})
q.Paginator

func (*Connection) PaginateFromParams

func (c *Connection) PaginateFromParams(params PaginationParams) *Query

Paginate records returned from the database.

q := c.PaginateFromParams(req.URL.Query())
q.All(&[]User{})
q.Paginator

func (*Connection) Q

func (c *Connection) Q() *Query

Q creates a new "empty" query for the current connection.

func (*Connection) RawQuery

func (c *Connection) RawQuery(stmt string, args ...interface{}) *Query

RawQuery will override the query building feature of Pop and will use whatever query you want to execute against the `Connection`. You can continue to use the `?` argument syntax.

c.RawQuery("select * from foo where id = ?", 1)

func (*Connection) Reload

func (c *Connection) Reload(model interface{}) error

func (*Connection) Rollback

func (c *Connection) Rollback(fn func(tx *Connection)) error

Rollback will open a new transaction and automatically rollback that transaction when the inner function returns, regardless. This can be useful for tests, etc...

func (*Connection) Save

func (c *Connection) Save(model interface{}, excludeColumns ...string) error

func (*Connection) Scope

func (c *Connection) Scope(sf ScopeFunc) *Query

Scope the query by using a `ScopeFunc`

func ByName(name string) ScopeFunc {
	return func(q *Query) *Query {
		return q.Where("name = ?", name)
	}
}

c.Scope(ByName("mark")).First(&User{})

func (*Connection) String

func (c *Connection) String() string

func (*Connection) Transaction

func (c *Connection) Transaction(fn func(tx *Connection) error) error

Transaction will start a new transaction on the connection. If the inner function returns an error then the transaction will be rolled back, otherwise the transaction will automatically commit at the end.

func (*Connection) TruncateAll

func (c *Connection) TruncateAll() error

func (*Connection) URL

func (c *Connection) URL() string

func (*Connection) Update

func (c *Connection) Update(model interface{}, excludeColumns ...string) error

func (*Connection) ValidateAndCreate

func (c *Connection) ValidateAndCreate(model interface{}, excludeColumns ...string) (*validate.Errors, error)

func (*Connection) ValidateAndSave

func (c *Connection) ValidateAndSave(model interface{}, excludeColumns ...string) (*validate.Errors, error)

func (*Connection) ValidateAndUpdate

func (c *Connection) ValidateAndUpdate(model interface{}, excludeColumns ...string) (*validate.Errors, error)

func (*Connection) Where

func (c *Connection) Where(stmt string, args ...interface{}) *Query

Where will append a where clause to the query. You may use `?` in place of arguments.

c.Where("id = ?", 1)

type ConnectionDetails

type ConnectionDetails struct {
	// Example: "postgres" or "sqlite3" or "mysql"
	Dialect string
	// The name of your database. Example: "foo_development"
	Database string
	// The host of your database. Example: "127.0.0.1"
	Host string
	// The port of your database. Example: 1234
	// Will default to the "default" port for each dialect.
	Port string
	// The username of the database user. Example: "root"
	User string
	// The password of the database user. Example: "password"
	Password string
	// Instead of specifying each individual piece of the
	// connection you can instead just specify the URL of the
	// database. Example: "postgres://postgres:postgres@localhost:5432/pop_test?sslmode=disable"
	URL string
	// Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns
	Pool    int
	Options map[string]string
}

func (*ConnectionDetails) Finalize

func (cd *ConnectionDetails) Finalize() error

Finalize cleans up the connection details by normalizing names, filling in default values, etc...

func (*ConnectionDetails) Parse

func (cd *ConnectionDetails) Parse(port string) error

Parse is deprecated! Please use `ConnectionDetails.Finalize()` instead!

func (*ConnectionDetails) RetryLimit

func (cd *ConnectionDetails) RetryLimit() int

func (*ConnectionDetails) RetrySleep

func (cd *ConnectionDetails) RetrySleep() time.Duration

type Model

type Model struct {
	Value
	// contains filtered or unexported fields
}

Model is used throughout Pop to wrap the end user interface that is passed in to many functions.

func (*Model) ID

func (m *Model) ID() interface{}

ID returns the ID of the Model. All models must have an `ID` field this is of type `int`,`int64` or of type `uuid.UUID`.

func (*Model) PrimaryKeyType

func (m *Model) PrimaryKeyType() string

func (*Model) TableName

func (m *Model) TableName() string

TableName returns the corresponding name of the underlying database table for a given `Model`. See also `MapTableName` to change the default name of the table.

type PaginationParams

type PaginationParams interface {
	Get(key string) string
}

type Paginator

type Paginator struct {
	// Current page you're on
	Page int `json:"page"`
	// Number of results you want per page
	PerPage int `json:"per_page"`
	// Page * PerPage (ex: 2 * 20, Offset == 40)
	Offset int `json:"offset"`
	// Total potential records matching the query
	TotalEntriesSize int `json:"total_entries_size"`
	// Total records returns, will be <= PerPage
	CurrentEntriesSize int `json:"current_entries_size"`
	// Total pages
	TotalPages int `json:"total_pages"`
}

Paginator is a type used to represent the pagination of records from the database.

func NewPaginator

func NewPaginator(page int, per_page int) *Paginator

NewPaginator returns a new `Paginator` value with the appropriate defaults set.

func NewPaginatorFromParams

func NewPaginatorFromParams(params PaginationParams) *Paginator

NewPaginatorFromParams takes an interface of type `PaginationParams`, the `url.Values` type works great with this interface, and returns a new `Paginator` based on the params or `PaginatorPageKey` and `PaginatorPerPageKey`. Defaults are `1` for the page and PaginatorPerPageDefault for the per page value.

func (Paginator) String

func (p Paginator) String() string

type Query

type Query struct {
	RawSQL *clause

	Paginator  *Paginator
	Connection *Connection
	// contains filtered or unexported fields
}

Query is the main value that is used to build up a query to be executed against the `Connection`.

func Q

func Q(c *Connection) *Query

Q will create a new "empty" query from the current connection.

func (*Query) All

func (q *Query) All(models interface{}) error

All retrieves all of the records in the database that match the query.

q.Where("name = ?", "mark").All(&[]User{})

func (*Query) BelongsTo

func (q *Query) BelongsTo(model interface{}) *Query

BelongsTo adds a "where" clause based on the "ID" of the "model" passed into it.

func (*Query) BelongsToThrough

func (q *Query) BelongsToThrough(bt, thru interface{}) *Query

BelongsToThrough adds a "where" clause that connects the "bt" model through the associated "thru" model.

func (Query) Count

func (q Query) Count(model interface{}) (int, error)

Count the number of records in the database.

q.Where("name = ?", "mark").Count(&User{})

func (*Query) Exec

func (q *Query) Exec() error

func (*Query) Exists

func (q *Query) Exists(model interface{}) (bool, error)

Exists returns true/false if a record exists in the database that matches the query.

q.Where("name = ?", "mark").Exists(&User{})

func (*Query) Find

func (q *Query) Find(model interface{}, id interface{}) error

Find the first record of the model in the database with a particular id.

q.Find(&User{}, 1)

func (*Query) First

func (q *Query) First(model interface{}) error

First record of the model in the database that matches the query.

q.Where("name = ?", "mark").First(&User{})

func (*Query) Join

func (q *Query) Join(table string, on string, args ...interface{}) *Query

func (*Query) Last

func (q *Query) Last(model interface{}) error

Last record of the model in the database that matches the query.

q.Where("name = ?", "mark").Last(&User{})

func (*Query) LeftInnerJoin

func (q *Query) LeftInnerJoin(table string, on string, args ...interface{}) *Query

func (*Query) LeftJoin

func (q *Query) LeftJoin(table string, on string, args ...interface{}) *Query

func (*Query) LeftOuterJoin

func (q *Query) LeftOuterJoin(table string, on string, args ...interface{}) *Query

func (*Query) Limit

func (q *Query) Limit(limit int) *Query

Limit will add a limit clause to the query.

func (*Query) Order

func (q *Query) Order(stmt string) *Query

Order will append an order clause to the query.

q.Order("name desc")

func (*Query) Paginate

func (q *Query) Paginate(page int, per_page int) *Query

Paginate records returned from the database.

q = q.Paginate(2, 15)
q.All(&[]User{})
q.Paginator

func (*Query) PaginateFromParams

func (q *Query) PaginateFromParams(params PaginationParams) *Query

Paginate records returned from the database.

q = q.PaginateFromParams(req.URL.Query())
q.All(&[]User{})
q.Paginator

func (*Query) RawQuery

func (q *Query) RawQuery(stmt string, args ...interface{}) *Query

RawQuery will override the query building feature of Pop and will use whatever query you want to execute against the `Connection`. You can continue to use the `?` argument syntax.

q.RawQuery("select * from foo where id = ?", 1)

func (*Query) RightInnerJoin

func (q *Query) RightInnerJoin(table string, on string, args ...interface{}) *Query

func (*Query) RightJoin

func (q *Query) RightJoin(table string, on string, args ...interface{}) *Query

func (*Query) RightOuterJoin

func (q *Query) RightOuterJoin(table string, on string, args ...interface{}) *Query

func (*Query) Scope

func (q *Query) Scope(sf ScopeFunc) *Query

Scope the query by using a `ScopeFunc`

func ByName(name string) ScopeFunc {
	return func(q *Query) *Query {
		return q.Where("name = ?", name)
	}
}

q.Scope(ByName("mark").Where("id = ?", 1).First(&User{})

func (Query) ToSQL

func (q Query) ToSQL(model *Model, addColumns ...string) (string, []interface{})

ToSQL will generate SQL and the appropriate arguments for that SQL from the `Model` passed in.

func (*Query) Where

func (q *Query) Where(stmt string, args ...interface{}) *Query

Where will append a where clause to the query. You may use `?` in place of arguments.

q.Where("id = ?", 1)

type ScopeFunc

type ScopeFunc func(q *Query) *Query

type Value

type Value interface{}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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