Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get back newly inserted row in Postgres with sqlx

I use https://github.com/jmoiron/sqlx to make queries to Postgres.

Is it possible to get back the whole row data when inserting a new row?

Here is the query I run:

result, err := Db.Exec("INSERT INTO users (name) VALUES ($1)", user.Name)

Or should I just use my existing user struct as the source of truth about the new entry in the database?

like image 253
Sergei Basharov Avatar asked Nov 18 '16 10:11

Sergei Basharov


3 Answers

Here is docs about transaction of sqlx:

The result has two possible pieces of data: LastInsertId() or RowsAffected(), the availability of which is driver dependent. In MySQL, for instance, LastInsertId() will be available on inserts with an auto-increment key, but in PostgreSQL, this information can only be retrieved from a normal row cursor by using the RETURNING clause.

So I made a complete demo for how to execute transaction using sqlx, the demo will create an address row in addresses table and then create a user in users table using the new address_id PK as user_address_id FK of the user.

package transaction

import (
    "database/sql"
    "github.com/jmoiron/sqlx"
    "log"
    "github.com/pkg/errors"
)
import (
    "github.com/icrowley/fake"
)

type User struct {
    UserID int `db:"user_id"`
    UserNme string `db:"user_nme"`
    UserEmail string `db:"user_email"`
    UserAddressId sql.NullInt64 `db:"user_address_id"`
}

type ITransactionSamples interface {
    CreateUserTransaction() (*User, error)
}

type TransactionSamples struct {
    Db *sqlx.DB
}

func NewTransactionSamples(Db *sqlx.DB) ITransactionSamples {
    return &TransactionSamples{Db}
}

func (ts *TransactionSamples) CreateUserTransaction() (*User, error) {
    tx := ts.Db.MustBegin()
    var lastInsertId int
    err := tx.QueryRowx(`INSERT INTO addresses (address_id, address_city, address_country, address_state) VALUES ($1, $2, $3, $4) RETURNING address_id`, 3, fake.City(), fake.Country(), fake.State()).Scan(&lastInsertId)
    if err != nil {
        tx.Rollback()
        return nil, errors.Wrap(err, "insert address error")
    }
    log.Println("lastInsertId: ", lastInsertId)

    var user User
    err = tx.QueryRowx(`INSERT INTO users (user_id, user_nme, user_email, user_address_id) VALUES ($1, $2, $3, $4) RETURNING *;`, 6, fake.UserName(), fake.EmailAddress(), lastInsertId).StructScan(&user)
    if err != nil {
        tx.Rollback()
        return nil, errors.Wrap(err, "insert user error")
    }
    err = tx.Commit()
    if err != nil {
        return nil, errors.Wrap(err, "tx.Commit()")
    }
    return &user, nil
}

Here is test result:

☁  transaction [master] ⚡  go test -v -count 1 ./...
=== RUN   TestCreateUserTransaction
2019/06/27 16:38:50 lastInsertId:  3
--- PASS: TestCreateUserTransaction (0.01s)
    transaction_test.go:28: &transaction.User{UserID:6, UserNme:"corrupti", UserEmail:"[email protected]", UserAddressId:sql.NullInt64{Int64:3, Valid:true}}
PASS
ok      sqlx-samples/transaction        3.254s

like image 81
slideshowp2 Avatar answered Nov 04 '22 04:11

slideshowp2


PostgreSQL supports RETURNING syntax for INSERT statements.

Example:

INSERT INTO users(...) VALUES(...) RETURNING id, name, foo, bar

Documentaion: https://www.postgresql.org/docs/9.6/static/sql-insert.html

The optional RETURNING clause causes INSERT to compute and return value(s) based on each row actually inserted (or updated, if an ON CONFLICT DO UPDATE clause was used). This is primarily useful for obtaining values that were supplied by defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the RETURNING list is identical to that of the output list of SELECT. Only rows that were successfully inserted or updated will be returned.

like image 1
AlexM Avatar answered Nov 04 '22 02:11

AlexM


This is a sample code that works with named queries and strong type structures for inserted data and ID.

Query and struct included to cover used syntax.

const query = `INSERT INTO checks (
        start, status) VALUES (
        :start, :status)
        returning id;`

type Row struct {
    Status string `db:"status"`
    Start time.Time `db:"start"`
}

func InsertCheck(ctx context.Context, row Row, tx *sqlx.Tx) (int64, error) {
    return insert(ctx, row, insertCheck, "checks", tx)
}


// insert inserts row into table using query SQL command
// table used only for loging, actual table name defined in query
// should not be used from services directly - implement strong type wrappers
// function expects query with named parameters
func insert(ctx context.Context, row interface{}, query string, table string, tx *sqlx.Tx) (int64, error) {
    // convert named query to native parameters format
    query, args, err := tx.BindNamed(query, row)
    if err != nil {
        return 0, fmt.Errorf("cannot bind parameters for insert into %q: %w", table, err)
    }

    var id struct {
        Val int64 `db:"id"`
    }

    err = sqlx.GetContext(ctx, tx, &id, query, args...)
    if err != nil {
        return 0, fmt.Errorf("cannot insert into %q: %w", table, err)
    }

    return id.Val, nil
}
like image 1
Dmitry Harnitski Avatar answered Nov 04 '22 02:11

Dmitry Harnitski