shestakovda / wpgx

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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!

About

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

License:MIT License


Languages

Language:Go 99.4%Language:Makefile 0.6%