Sqlx: LastInsertId and postgres

Created on 28 Jul 2015  路  5Comments  路  Source: jmoiron/sqlx

Should this work? I know with PostgreSQL you need to add the RETURNING clause...

CREATE SEQUENCE foo_id_seq;
CREATE TABLE foo ( 
  id bigint PRIMARY KEY default nextval('foo_id_seq'::regclass),
  created timestamp with time zone NOT NULL,
  name varchar(255) NOT NULL
);
sql := `INSERT into foo (created, name) VALUES ($1, $2) RETURNING id`
result, err := db.Exec(sql, time.Now(), "somename")
if err {
  panic(err)
}
lastid, lastiderr := result.LastInsertId()
fmt.Println("LastId?", lastid, lastiderr) // prints: LastId? 0 no LastInsertId available

Seems like it works with lib/pq by using QueryRow

Most helpful comment

There's no one-liner for it. NamedExec and NamedQuery came before PrepareNamed, and I decided to keep them mostly for convenience. Rather than cluttering up the DB, Tx, etc structs with Named variants of everything (since sqlx adds a couple new verbs as well like Select, Get, etc.), I decided to leave most of that on the NamedStmt. So you can do:

stmt, err := db.PrepareNamed("INSERT INTO person (name, email) VALUES (:name, :email) RETURNING id")
// check error
var id int
err = stmt.Get(&id, arg)

It isn't much more difficult to use an interface and define an all-in-one type function:

type namedPreparer interface {
    PrepareNamed(string) (*sqlx.NamedStmt, error)
}

func NamedGet(db namedPreparer, dest interface{}, query string, arg interface{}) error {
    stmt, err := db.PrepareNamed(query)
    if err != nil {
        return err
    }
    return stmt.Get(dest, arg)
}

FWIW in most drivers this is basically what happens when you use eg Query or QueryRow with bindvars + args, anyway.

All 5 comments

Hello!

Quoting from https://godoc.org/github.com/lib/pq:

"pq does not support the LastInsertId() method of the Result type in database/sql. To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres RETURNING clause with a standard Query or QueryRow call".

Perhaps you misread the statement.

Hi, thanks -- I did miss that line about using Query or QueryRow -- thanks. I have it working now.

I might be missing something but there's no Named version that returns a single row? That would be handy, little weird to have all Named queries and then for a few inserts switch to positional

There's no one-liner for it. NamedExec and NamedQuery came before PrepareNamed, and I decided to keep them mostly for convenience. Rather than cluttering up the DB, Tx, etc structs with Named variants of everything (since sqlx adds a couple new verbs as well like Select, Get, etc.), I decided to leave most of that on the NamedStmt. So you can do:

stmt, err := db.PrepareNamed("INSERT INTO person (name, email) VALUES (:name, :email) RETURNING id")
// check error
var id int
err = stmt.Get(&id, arg)

It isn't much more difficult to use an interface and define an all-in-one type function:

type namedPreparer interface {
    PrepareNamed(string) (*sqlx.NamedStmt, error)
}

func NamedGet(db namedPreparer, dest interface{}, query string, arg interface{}) error {
    stmt, err := db.PrepareNamed(query)
    if err != nil {
        return err
    }
    return stmt.Get(dest, arg)
}

FWIW in most drivers this is basically what happens when you use eg Query or QueryRow with bindvars + args, anyway.

I'd like to leave a note about the suggestion above.

There's no one-liner for it. NamedExec and NamedQuery came before PrepareNamed, and I decided to keep them mostly for convenience. Rather than cluttering up the DB, Tx, etc structs with Named variants of everything (since sqlx adds a couple new verbs as well like Select, Get, etc.), I decided to leave most of that on the NamedStmt. So you can do:

stmt, err := db.PrepareNamed("INSERT INTO person (name, email) VALUES (:name, :email) RETURNING id")
// check error
var id int
err = stmt.Get(&id, arg)

Actually there are situations when you don't want to create prepared statement as suggested.
For example, when your work with PgBouncer in Transaction pooling mode, when prepared statements are disabled.

So the most accurate suggestion is to use sql.Named like this:

query, args, err := sqlx.Named("INSERT INTO person (name, email) VALUES (:name, :email) RETURNING id", arg)
if err != nil {
    <handle error>
}
query = db.Rebind(query)
var id int
err = db.Get(&id, query, args...)
... 
Was this page helpful?
0 / 5 - 0 ratings

Related issues

x1unix picture x1unix  路  4Comments

MichaelHipp picture MichaelHipp  路  5Comments

Stegoo picture Stegoo  路  6Comments

yzk0281 picture yzk0281  路  5Comments

pt-arvind picture pt-arvind  路  4Comments