Documentation ¶
Overview ¶
Package postgres wraps https://github.com/lib/pq and implements a set of new features on top of it:
- Get, Filter, Insert, Update, Save and Delete convenience functions
- Advanced encoding & decoding between Go and Postgres column types
- Create tables, indexes and foreign keys for Go structs
Index ¶
- Variables
- func MustQuoteIdentifier(in string) string
- func NewID(s Struct) string
- func NewPrefixID(prefix string) string
- func ParseID(id string) (prefix string, kid ksuid.KSUID, err error)
- func QuoteIdentifier(in string) (string, error)
- func QuoteLiteral(in string) string
- func Register(s Struct, alias string)
- func RegisterWithPrefix(s Struct, alias string, prefixID string)
- type ColumnTyper
- type DefaultLogger
- type Logger
- type Postgres
- func (p *Postgres) Close() error
- func (p *Postgres) DB() *sql.DB
- func (p *Postgres) Delete(ctx context.Context, s Struct) error
- func (p *Postgres) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
- func (p *Postgres) Filter(ctx context.Context, s StructSlice, q *QueryStmt) error
- func (p *Postgres) Get(ctx context.Context, s Struct) error
- func (p *Postgres) Insert(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
- func (p *Postgres) Migrate(ctx context.Context) error
- func (p *Postgres) NewID(s Struct) string
- func (p *Postgres) NewTransaction() (*Transaction, error)
- func (p *Postgres) Ping(ctx context.Context) error
- func (p *Postgres) Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
- func (p *Postgres) QueryRow(ctx context.Context, query string, args ...interface{}) *sql.Row
- func (p *Postgres) Save(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
- func (p *Postgres) SetConnMaxLifetime(d time.Duration)
- func (p *Postgres) SetMaxIdleConns(n int)
- func (p *Postgres) SetMaxOpenConns(n int)
- func (p *Postgres) Stats() sql.DBStats
- func (p *Postgres) Transaction(fn func(*Transaction) error) error
- func (p *Postgres) Update(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
- type QueryStmt
- type Struct
- type StructFieldName
- type StructSlice
- type Transaction
- func (t *Transaction) Commit() error
- func (t *Transaction) Delete(ctx context.Context, s Struct) error
- func (t *Transaction) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
- func (t *Transaction) Filter(ctx context.Context, s StructSlice, q *QueryStmt) error
- func (t *Transaction) Get(ctx context.Context, s Struct) error
- func (t *Transaction) Insert(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
- func (t *Transaction) Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
- func (t *Transaction) QueryRow(ctx context.Context, query string, args ...interface{}) *sql.Row
- func (t *Transaction) Rollback() error
- func (t *Transaction) Save(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
- func (t *Transaction) Update(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ConnectTimeout is the max time to wait for Postgres to be reachable. ConnectTimeout = 5 * time.Second // MigrateKey is a random key for Postgres' advisory lock. // It must be the same for all running Migrate funcs. MigrateKey = 8267205493056421913 )
var ( ErrNoLock = fmt.Errorf("no lock") ErrNotUnlocked = fmt.Errorf("not unlocked") )
var ( // QueryLimit sets the default LIMIT clause QueryLimit = 10 // MaxQueryLength is the max size of a query string MaxQueryLength = 1000 )
var ( ErrScannerNotImplemented = fmt.Errorf("type does not implement sql.Scanner") ErrColumnTyperNotImplemented = fmt.Errorf("type does not implement ColumnTyper") )
var ErrInvalidId = fmt.Errorf("invalid ID")
var MaxIdentifierLength = 63
var StructTag = "db"
StructTag is the default struct tag
Functions ¶
func MustQuoteIdentifier ¶
func NewID ¶
NewID returns a new ramdon ID, prefixed with the registered name of the given struct. Example: `user_1R0D8rn6jP870lrtSpgb1y6M5tG`
func NewPrefixID ¶
NewPrefixID returns a new random ID, prefixed with the given prefix. Example: `user_1R0D8rn6jP870lrtSpgb1y6M5tG`
func QuoteIdentifier ¶
func QuoteLiteral ¶
func RegisterWithPrefix ¶
RegisterWithPrefix registers a struct. Optional alias has to be globally unique. Optional prefixID is used in NewID().
Types ¶
type ColumnTyper ¶
type ColumnTyper interface { // ColumnType returns postgres' column type ColumnType() string }
ColumnTyper is an interface to be implemented by a custom type
type DefaultLogger ¶
type DefaultLogger struct{}
func NewDefaultLogger ¶
func NewDefaultLogger() *DefaultLogger
type Logger ¶
type Logger interface { // Query will be called with the SQL query and the arguments. Query(query string, duration time.Duration, args ...interface{}) }
Logger is a logging interface and can be used to implement a custom logger.
type Postgres ¶
type Postgres struct { Logger Logger // contains filtered or unexported fields }
func Open ¶
Open creates a new Postgres client.
To set a schema, specify `search_path` in URI.
To only create temporary tables, i.e. for testing purposes, specify `createTempTables=true` in URI.
func (*Postgres) Close ¶
Close closes the database and prevents new queries from starting. Close then waits for all queries that have started processing on the server to finish.
It is rare to Close a DB, as the DB handle is meant to be long-lived and shared between many goroutines.
func (*Postgres) Delete ¶
Delete deletes a record by looking at the primary keys of a struct.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_4", // primary key, via `db:"pk"` struct tag Name: "Peter", Email: "peter@foobar.com", } _ = db.Save(context.Background(), user) // Delete user by Id user = &User{ Id: "user_4", } _ = db.Delete(context.Background(), user) fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET ("name", "email") = ROW("excluded"."name", "excluded"."email") RETURNING "id", "name", "email" DELETE FROM "user" WHERE "id" = $1 RETURNING "id", "name", "email" &{Id:user_4 Name:Peter Email:peter@foobar.com}
func (*Postgres) Exec ¶
Exec executes a query that doesn't return rows. For example: an INSERT and UPDATE.
func (*Postgres) Filter ¶
Filter finds records based on QueryStmt. See QueryStmt for more details.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_5", Name: "Max", Email: "max@example.com", } _ = db.Save(context.Background(), user) // Filter users by email users := []User{} _ = db.Filter(context.Background(), &users, Query("Email LIKE $1", "%example.com")) fmt.Printf("%+v", users)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET ("name", "email") = ROW("excluded"."name", "excluded"."email") RETURNING "id", "name", "email" SELECT "id", "name", "email" FROM "user" WHERE "email" LIKE $1 LIMIT 10 [{Id:user_5 Name:Max Email:max@example.com}]
Example (UntrustedQuery) ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_6", Name: "Karl", Email: "karl@company.co", } _ = db.Save(context.Background(), user) // Simulate an incoming HTTP request, with an URL like: // ?filter=Name = $1 and Email = $2 // &vars=Karl // &vars=karl@company.co request := http.Request{ URL: &url.URL{ RawQuery: "filter=Name%20%3D%20%241%20and%20Email%20%3D%20%242&vars=Karl&vars=karl%40company.co", }, } // Get ?filter from request URL urlFilter := request.URL.Query().Get("filter") // Get &vars from request URL and convert from []string to []interface{} urlVars := stringSliceToInterfaceSlice(request.URL.Query()["vars"]) // Assemble new UntrustedQuery from URL input query := UntrustedQuery(urlFilter, urlVars...).Whitelist("Name", "Email") users := []User{} _ = db.Filter(context.Background(), &users, query) fmt.Printf("%+v", users)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET ("name", "email") = ROW("excluded"."name", "excluded"."email") RETURNING "id", "name", "email" SELECT "id", "name", "email" FROM "user" WHERE "name" = $1 and "email" = $2 LIMIT 10 [{Id:user_6 Name:Karl Email:karl@company.co}]
func (*Postgres) Get ¶
Get finds a record by its primary keys.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_1", // primary key, via `db:"pk"` struct tag Name: "Peter", Email: "peter@foobar.com", } _ = db.Save(context.Background(), user) // Get user by Id user = &User{ Id: "user_1", } _ = db.Get(context.Background(), user) fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET ("name", "email") = ROW("excluded"."name", "excluded"."email") RETURNING "id", "name", "email" SELECT "id", "name", "email" FROM "user" WHERE "id" = $1 LIMIT 1 &{Id:user_1 Name:Peter Email:peter@foobar.com}
func (*Postgres) Insert ¶
Insert creates a new record.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user user := &User{ Id: "user_6", // primary key, via `db:"pk"` struct tag Name: "Peter", Email: "peter@foobar.com", } _ = db.Insert(context.Background(), user) fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) RETURNING "id", "name", "email" &{Id:user_6 Name:Peter Email:peter@foobar.com}
Example (FieldMask) ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user user := &User{ Id: "user_9", // primary key, via `db:"pk"` struct tag Email: "peter@foobar.com", } _ = db.Insert(context.Background(), user, "Id", "Email") fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "email") VALUES ($1, $2) RETURNING "id", "name", "email" &{Id:user_9 Name: Email:peter@foobar.com}
func (*Postgres) Migrate ¶
Migrate runs SQL migrations for structs registered with `Register`. Migrations are non-destructive and only backwards-compatible changes will be performed, in particular:
- New tables for structs are created (this includes creation of primary key)
- New columns for struct fields are created
- New indexes are created
- New unique indexes are created (if possible)
- New foreign keys are created (if possible)
Migrate blocks until it successfully acquired a global lock using Postgres' advisory locks. This guarantees that only one Migrate function can run at a time across different processes.
The performed migrations as mentioned above are idempotent.
Example ¶
db, _ := Open(postgresURI) // Register "example" struct with prefix "ex" RegisterWithPrefix(&Example{}, "example", "ex") // Run migrations ... _ = db.Migrate(context.Background())
Output:
func (*Postgres) NewID ¶
NewID is a convenience function calling NewID. It exists so that the package doesn't have to be imported if just a *Postgres instance is passed around.
func (*Postgres) NewTransaction ¶
func (p *Postgres) NewTransaction() (*Transaction, error)
NewTransaction starts a new transaction.
func (*Postgres) Ping ¶
Ping verifies a connection to the database is still alive, establishing a connection if necessary.
func (*Postgres) QueryRow ¶
QueryRow executes a query that is expected to return at most one row. QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called. If the query selects no rows, the *Row's Scan will return ErrNoRows. Otherwise, the *Row's Scan scans the first selected row and discards the rest.
func (*Postgres) Save ¶
Save creates a new record or updates an existing record by looking at the primary keys of a struct.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create or update user user := &User{ Id: "user_2", // primary key, via `db:"pk"` struct tag Name: "Peter", Email: "peter@foobar.com", } _ = db.Save(context.Background(), user) fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) ON CONFLICT ("id") DO UPDATE SET ("name", "email") = ROW("excluded"."name", "excluded"."email") RETURNING "id", "name", "email" &{Id:user_2 Name:Peter Email:peter@foobar.com}
Example (FieldMask) ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create or update user (only save Id and Email) user := &User{ Id: "user_3", // primary key, via `db:"pk"` struct tag Email: "peter@foobar.com", } _ = db.Save(context.Background(), user, "Id", "Email") fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "email") VALUES ($1, $2) ON CONFLICT ("id") DO UPDATE SET ("email") = ROW("excluded"."email") RETURNING "id", "name", "email" &{Id:user_3 Name: Email:peter@foobar.com}
func (*Postgres) SetConnMaxLifetime ¶
SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
Expired connections may be closed lazily before reuse.
If d <= 0, connections are reused forever.
func (*Postgres) SetMaxIdleConns ¶
SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
If MaxOpenConns is greater than 0 but less than the new MaxIdleConns, then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
If n <= 0, no idle connections are retained.
The default max idle connections is currently 2. This may change in a future release.
func (*Postgres) SetMaxOpenConns ¶
SetMaxOpenConns sets the maximum number of open connections to the database.
If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than MaxIdleConns, then MaxIdleConns will be reduced to match the new MaxOpenConns limit.
If n <= 0, then there is no limit on the number of open connections. The default is 0 (unlimited).
func (*Postgres) Transaction ¶
func (p *Postgres) Transaction(fn func(*Transaction) error) error
Transaction starts a new transaction and automatically commits or rolls back the transaction if TransactionFunc returns an error.
Example ¶
// Open connection to Postgres db, err := Open(postgresURI) if err != nil { return err } // Start a new transaction err = db.Transaction(func(tx *Transaction) error { // Execute SQL if _, err := tx.Exec(context.Background(), "query"); err != nil { return err } // Execute more SQL if _, err := tx.Exec(context.Background(), "another query"); err != nil { return err } return nil }) // The transaction ended. If no error was returned, the transaction // was commited, otherwise the transaction was rolled back and the // original error is returned. return err
Output:
func (*Postgres) Update ¶
Update updates an existing record by looking at the orimary keys of a struct.
Example ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_7", Name: "Peter", Email: "peter@foobar.com", } _ = db.Insert(context.Background(), user) // Then update the user user = &User{ Id: "user_7", // primary key, via `db:"pk"` struct tag Name: "Karl", Email: "karl@foobar.com", } _ = db.Update(context.Background(), user) fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) RETURNING "id", "name", "email" UPDATE "user" SET ("name", "email") = ROW($1, $2) WHERE "id" = $3 RETURNING "id", "name", "email" &{Id:user_7 Name:Karl Email:karl@foobar.com}
Example (FieldMask) ¶
db, _ := Open(postgresURI) db.Migrate(context.Background()) db.Logger = print() // Create a new user first user := &User{ Id: "user_8", Name: "Peter", Email: "peter@foobar.com", } _ = db.Insert(context.Background(), user) // Then update the user user = &User{ Id: "user_8", // primary key, via `db:"pk"` struct tag Email: "karl@foobar.com", } _ = db.Update(context.Background(), user, "Email") fmt.Printf("%+v", user)
Output: INSERT INTO "user" ("id", "name", "email") VALUES ($1, $2, $3) RETURNING "id", "name", "email" UPDATE "user" SET ("email") = ROW($1) WHERE "id" = $2 RETURNING "id", "name", "email" &{Id:user_8 Name:Peter Email:karl@foobar.com}
type QueryStmt ¶
type QueryStmt struct {
// contains filtered or unexported fields
}
QueryStmt is a query builder, used by Postgres.Filter
func UntrustedQuery ¶
UntrustedQuery builds a query statement that will use prepared statements. The given query is verified to be valid SQL to prevent SQL injections and accepts untrusted user input, i.e. from URL query parameters.
QueryStmt.Whitelist is required to whitelist queryable fields.
The given query must follow a `field operator wildcard` syntax, i.e.
Email like $1 and (Active != $2 or Admin = $3)
The following operators are allowed:
=|!=|<|<=|>|>=|ilike|like
func (*QueryStmt) Asc ¶
func (q *QueryStmt) Asc(field StructFieldName) *QueryStmt
Asc instructs the result to be ordered ascending by field.
func (*QueryStmt) Desc ¶
func (q *QueryStmt) Desc(field StructFieldName) *QueryStmt
Desc instructs the result to be ordered descending by field.
func (*QueryStmt) Whitelist ¶
func (q *QueryStmt) Whitelist(fields ...StructFieldName) *QueryStmt
Whitelist sets acceptable fields that can be queried. Required when using UntrustedQuery.
type StructFieldName ¶
type StructFieldName interface{}
StructFieldName defines a struct's field name where interface{} must be "resolvable" as string.
type Transaction ¶
type Transaction struct {
// contains filtered or unexported fields
}
func (*Transaction) Commit ¶
func (t *Transaction) Commit() error
Commit commits the transaction. Commit or Rollback must be called at least once, so the connection can be returned to the connection pool.
Example ¶
// Open connection to Postgres db, err := Open(postgresURI) if err != nil { return err } // Start a new transaction tx, err := db.NewTransaction() if err != nil { return err } // Execute SQL if _, err := tx.Exec(context.Background(), "query"); err != nil { _ = tx.Rollback() // Rollback and end the transaction return err // Return the original error } // Execute more SQL if _, err := tx.Exec(context.Background(), "another query"); err != nil { _ = tx.Rollback() // Rollback and end the transaction return err // Return the original error } // Commit and end the transaction if err := tx.Commit(); err != nil { return err } return nil
Output:
func (*Transaction) Delete ¶
func (t *Transaction) Delete(ctx context.Context, s Struct) error
Delete deletes a record by looking at the primary keys of a struct. See Postgres.Delete for more details.
func (*Transaction) Exec ¶
func (t *Transaction) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Exec executes a query that doesn't return rows. For example: an INSERT and UPDATE.
func (*Transaction) Filter ¶
func (t *Transaction) Filter(ctx context.Context, s StructSlice, q *QueryStmt) error
Filter finds records based on QueryStmt. See Postgres.Filter for more details.
func (*Transaction) Get ¶
func (t *Transaction) Get(ctx context.Context, s Struct) error
Get finds a record by its primary keys. See Postgres.Get for more details.
func (*Transaction) Insert ¶
func (t *Transaction) Insert(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
Insert creates a new record. See Postgres.Insert for more details.
func (*Transaction) Query ¶
func (t *Transaction) Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
Query executes a query that returns rows, typically a SELECT.
func (*Transaction) QueryRow ¶
QueryRow executes a query that is expected to return at most one row. QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called. If the query selects no rows, the *Row's Scan will return ErrNoRows. Otherwise, the *Row's Scan scans the first selected row and discards the rest.
func (*Transaction) Rollback ¶
func (t *Transaction) Rollback() error
Rollback aborts the transaction. Rollback or Commit must be called at least once, so the connection can be returned to the connection pool. See Commit for an example.
func (*Transaction) Save ¶
func (t *Transaction) Save(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
Save creates a new record or updates an existing record by looking at the primary keys of a struct. See Postgres.Filter for more details.
func (*Transaction) Update ¶
func (t *Transaction) Update(ctx context.Context, s Struct, fieldMask ...StructFieldName) error
Update updates an existing record by looking at the orimary keys of a struct. See Postgres.Update for more details.