wpgx

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

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

Go to latest
Published: Jul 30, 2019 License: MIT Imports: 12 Imported by: 0

README

wpgx

License Travis TestCoverage GoReportCard Codebeat GoDoc

Wrapped PGX - some utility add-on to the perfect https://github.com/jackc/pgx package

Package wpgx helps to improve loading performance and to simplify code.

Package wpgx uses concept of Shaper and Translator interfaces, that helps to map database names with struct fields.

Imagine, we have a simple User struct with an access Role field:

type User struct {
    ID   int      `json:"id"`
    Name string   `json:"name"`
    Role UserRole `json:"role"`
}

type UserRole struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

It is business-level objects, let's create a database model:

type userModel struct {
    ID       int
    Name     string
    RoleID   sql.NullInt64
    RoleName sql.NullString
}

Note that model can have unexported name, it's because we use interfaces in wpgx.

To convert business to database we can implement Extrude method in Shaper interface:

func (u *User) Extrude() wpgx.Translator {
    return &userModel{
        ID:   u.ID,
        Name: u.Name,
        RoleID: sql.NullInt64{
            Int64: int64(u.Role.ID),
            Valid: u.Role.ID > 0,
        },
        RoleName: sql.NullString{
            String: u.Role.Name,
            Valid:  u.Role.Name != "",
        },
    }
}

Okay, to convert from database to business we can implement Receive method in Shaper interface:

func (u *User) Receive(item wpgx.Translator) error {
    m, ok := item.(*userModel)
    if !ok {
        return wpgx.ErrUnknownType
    }

    u.ID = m.ID
    u.Name = m.Name

    // No role selected, set empty
    if !m.RoleID.Valid {
        u.Role = UserRole{}
        return nil
    }

    u.Role.ID = int(m.RoleID.Int64)

    if m.RoleName.Valid {
        u.Role.Name = m.RoleName.String
    }
    return nil
}

Now, we can use something like this query to fetch user:

SELECT
    u.id,
    u.name,
    u.role_id,
    r.name as role_name
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.id = $1;

Let's implement Translator interface to map names:

func (um *userModel) Translate(name string) interface{} {
    switch name {
    case "id":
        return &um.ID
    case "name":
        return &um.Name
    case "role_id":
        return &um.RoleID
    case "role_name":
        return &um.RoleName
    }
    return nil
}

To create database connection, just call Connect:

db, err := wpgx.Connect("postgresql://user:pass@host:port/database?options")
if err != nil {
    return err
}

For performance issues we can to prepare statements but it is not required for select:

sqlSelectUser, err := db.Cook(`
SELECT
    u.id,
    u.name,
    u.role_id,
    r.name as role_name
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.id = $1;
`)
if err != nil {
    return err
}

Now we can load user with ID=42:

var user User

if err = db.Load(&user, sqlSelectUser, 42); err != nil {
    return err
}

Okay, but what if we need a list of users? No problems, let's implement Collector interface:

type UserList []*User

func (ul *UserList) NewItem() wpgx.Shaper {
    return new(User)
}

func (ul *UserList) Collect(item wpgx.Shaper) error {
    user, ok := item.(*User)
    if !ok {
        return wpgx.ErrUnknownType
    }
    *ul = append(*ul, user)
    return nil
}

Now we can select list like this. For example all users with role:

sqlSelectUsers, err := db.Cook(`
SELECT
    u.id,
    u.name,
    u.role_id,
    r.name as role_name
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.role_id IS NOT NULL LIMIT 100;
`)
if err != nil {
    return err
}

users := make(UserList, 0, 100)

err = db.Deal(&users, sqlSelectUsers)
if err != nil {
    return err
}

Collector can use maps, channels or another type you want.

But what if we need to insert new user and fetch this id? For this task we have to prepare insert query with columns:

sqlInsertUser, err := db.Cook(`
INSERT INTO users (name, role_id)
VALUES ($1, $2)
RETURNING id;
`, "name", "role_id") // describe Translator columns for save
if err != nil {
    return err
}

var ids wpgx.Ints
user := &User{Name: "John"}

if err = db.Save(user, sqlInsertUser, &ids); err != nil {
    return err
}
if len(ids) > 0 {
    user.ID = ids[0]
}

Save uses Collector as result type because you may want to return many rows.

All examples above can be used in one transaction, that called Dealer. Typically Dealer can be used like this:

d, err := db.NewDealer()
if err != nil {
    return err
}

// Commit when no errors happened
defer func(){ d.Jail(err == nil) }()

// Doing something good stuff

Good luck!

Documentation

Overview

Package wpgx helps to improve loading performance and to simplify code.

Package wpgx uses concept of Shaper and Translator interfaces, that helps to map database names with struct fields.

Imagine, we have a simple User struct with an access Role field:

type User struct {
    ID   int      `json:"id"`
    Name string   `json:"name"`
    Role UserRole `json:"role"`
}

type UserRole struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

It is business-level objects, let's create a database model:

type userModel struct {
    ID       int
    Name     string
    RoleID   sql.NullInt64
    RoleName sql.NullString
}

Note that model can have unexported name, it's because we use interfaces in wpgx.

To convert business to database we can implement Extrude method in Shaper interface:

func (u *User) Extrude() wpgx.Translator {
    return &userModel{
        ID:   u.ID,
        Name: u.Name,
        RoleID: sql.NullInt64{
            Int64: int64(u.Role.ID),
            Valid: u.Role.ID > 0,
        },
        RoleName: sql.NullString{
            String: u.Role.Name,
            Valid:  u.Role.Name != "",
        },
    }
}

Okay, to convert from database to business we can implement Receive method in Shaper interface:

func (u *User) Receive(item wpgx.Translator) error {
    m, ok := item.(*userModel)
    if !ok {
        return wpgx.ErrUnknownType
    }

    u.ID = m.ID
    u.Name = m.Name

    // No role selected, set empty
    if !m.RoleID.Valid {
        u.Role = UserRole{}
        return nil
    }

    u.Role.ID = int(m.RoleID.Int64)

    if m.RoleName.Valid {
        u.Role.Name = m.RoleName.String
    }
    return nil
}

Now, we can use something like this query to fetch user:

SELECT
    u.id,
    u.name,
    u.role_id,
    r.name as role_name
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.id = $1;

Let's implement Translator interface to map names:

func (um *userModel) Translate(name string) interface{} {
    switch name {
    case "id":
        return &um.ID
    case "name":
        return &um.Name
    case "role_id":
        return &um.RoleID
    case "role_name":
        return &um.RoleName
    }
    return nil
}

To create database connection, just call Connect:

db, err := wpgx.Connect("postgresql://user:pass@host:port/database?options")
if err != nil {
    return err
}

For performance issues we can to prepare statements but it is not required for select:

sqlSelectUser, err := db.Cook(`query text`)
if err != nil {
    return err
}

Now we can load user with ID=42:

var user User

if err = db.Load(&user, sqlSelectUser, 42); err != nil {
    return err
}

Okay, but what if we need a list of users? No problems, let's implement Collector interface:

type UserList []*User

func (ul *UserList) NewItem() wpgx.Shaper {
    return new(User)
}

func (ul *UserList) Collect(item Shaper) error {
    user, ok := item.(*User)
    if !ok {
        return wpgx.ErrUnknownType
    }
    *ul = append(*ul, user)
    return nil
}

Now we can select list like this. For example all users with role:

sqlSelectUsers, err := db.Cook(`
SELECT
    u.id,
    u.name,
    u.role_id,
    r.name as role_name
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE u.role_id IS NOT NULL LIMIT 100;
`)
if err != nil {
    return err
}

users := make(UserList, 0, 100)

err = db.Deal(&users, sqlSelectUsers)
if err != nil {
    return err
}

Collector can use maps, channels or another type you want.

But what if we need to insert new user and fetch this id? For this task we have to prepare insert query with columns:

sqlInsertUser, err := db.Cook(`
INSERT INTO users (name, role_id)
VALUES ($1, $2)
RETURNING id;
`, "name", "role_id") // describe Translator columns for save
if err != nil {
    return err
}

var ids wpgx.Ints
user := &User{Name: "John"}

if err = db.Save(user, sqlInsertUser, &ids); err != nil {
    return err
}
if len(ids) > 0 {
    user.ID = ids[0]
}

Save uses Collector as result type because you may want to return many rows.

All examples above can be used in one transaction, that called Dealer. Typically Dealer can be used like this:

d, err := db.NewDealer()
if err != nil {
    return err
}

// Commit when no errors happened
defer func(){ d.Jail(err == nil) }()

// Doing something good stuff

Good luck!

Index

Constants

This section is empty.

Variables

View Source
var ErrConnClosed = errors.New("connection is closed")

ErrConnClosed occurs when an attemtp to use closed conncection

View Source
var ErrUnknownType = errors.New("unknown shaper type")

ErrUnknownType occurs when collector meets unknown shaper type

Functions

func LogLevel

func LogLevel(lvl int) func(*Config) error

LogLevel is a config helper for use glog as logger godoc: https://godoc.org/github.com/golang/glog

func PoolSize

func PoolSize(size int) func(*Config) error

PoolSize is a config helper to set pgx.ConnPoolConfig.MaxConnections field

func ReservePath

func ReservePath(possible string) func(*Config) error

ReservePath is a config helper to set catalog for saving prepared sql files and uncommitted object data as json files

Types

type Collector

type Collector interface {
	NewItem() Shaper
	Collect(item Shaper) error
}

Collector is a generic collection. It can use lists, maps or channels inside

NewItem prepares a translator entity for future loads

Collect puts a new translator item into the inside collection

type Config

type Config struct {
	ReservePath string
	pgx.ConnPoolConfig
}

Config is just a pgx.ConnPoolConfig with some extra options

type Connector

type Connector interface {
	Dealer
	NewDealer() (Dealer, error)
	Close()
}

Connector is the main database connection manager As a Dealer it can execute queries in a default transaction

NewDealer spawns new dealer on the street. It needs to be jailed (closed)

Prepare saves query for further execution

Close closes all free dealers with rollback

func Connect

func Connect(uri string, options ...func(*Config) error) (Connector, error)

Connect method initialize a new connection pool with uri in a connection string format Reserve path is for saving args of failed queries. Useful for debug or data restore

type Dealer

type Dealer interface {
	Cook(text string, cols ...string) (string, error)
	Deal(result Collector, query string, args ...interface{}) error
	Load(item Shaper, query string, args ...interface{}) error
	Save(item Shaper, key string, result Collector) error
	Jail(commit bool) error
}

Dealer is an active subject, like an opened transaction, for query performing

Deal! It executes query and loads result into a data collector. Pass nil when no result needed

Load gets just one item from the database. When no collection needed

Save inserts item into database. Result may need for getting new ID or properties

Jail (aka Close) ends a transaction with commit or rollback respective to the flag

type Ints

type Ints []int

Ints is a simple ints (like ids) collector It is useful in one-time tasks or small scripts, when no models is need

func (*Ints) Collect

func (i *Ints) Collect(item Shaper) error

Collect is used to add shaper into Ints

func (*Ints) NewItem

func (i *Ints) NewItem() Shaper

NewItem is Ints Shaper constructor

type RawList

type RawList []map[string]string

RawList is a simple data collector It is useful in one-time tasks or small scripts, when no models is need

func (*RawList) Collect

func (s *RawList) Collect(item Shaper) error

Collect is used to add shaper into RawList

func (*RawList) NewItem

func (s *RawList) NewItem() Shaper

NewItem is RawList Shaper constructor

type Shaper

type Shaper interface {
	Extrude() Translator
	Receive(Translator) error
}

Shaper helps to make database model from business model and vice versa

Extrude makes a database model from business data

Receive fills the data from a database model

type Strings

type Strings []string

Strings is a simple strings collector It is useful in one-time tasks or small scripts, when no models is need

func (*Strings) Collect

func (s *Strings) Collect(item Shaper) error

Collect is used to add shaper into Strings

func (*Strings) NewItem

func (s *Strings) NewItem() Shaper

NewItem is Strings Shaper constructor

type Translator

type Translator interface {
	Translate(name string) interface{}
}

Translator helps to isolate model from loading procedure

Translate is intended to associate structure fields with their names

Jump to

Keyboard shortcuts

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