jackc / pgx

PostgreSQL driver and toolkit for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

INSERT with Query into Unique Field does not return error

ducndh opened this issue · comments

Describe the bug
If you use conn.Query to insert a duplicate value into your postgresql database, the err return as nil.
Workaround is using conn.QueryRow and return id to catch the error.

To Reproduce
Use this postgreSQL script

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    hashed_password CHAR(60) NOT NULL,
    created TIMESTAMP NOT NULL
);

-- Add a unique constraint on the email column
ALTER TABLE users ADD CONSTRAINT users_uc_email UNIQUE (email);

And insert a dummy value with "test@gmail.com" as email. After that running this script would return no error:

package main

import (
	"context"
	"log"
	"os"

	"github.com/jackc/pgx/v5"
)

func main() {
	conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close(context.Background())

	row, err := conn.Query(context.Background(), `INSERT INTO users (name, email, hashed_password, created) VALUES ('test', 'test@gmail.com', 'password', CURRENT_TIMESTAMP);`)
	fmt.Printf("Row: %v\n", row)
	if err != nil {
		fmt.Fprintf(os.Stderr, "First test Query failed: %v\n", err)
		os.Exit(1)
	}}

Expected behavior
An error should be printed:
"First test Query failed: ERROR: duplicate key value violates unique constraint "users_uc_email" (SQLSTATE 23505)
exit status 1"

You can achieve this behavior by running this code:

func main() {
	conn, err := pgx.Connect(context.Background(), "dbname=mydb port=5432")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close(context.Background())

	var id int
	err = conn.QueryRow(context.Background(), `INSERT INTO users (name, email, hashed_password, created) VALUES ('test', 'test@gmail.com', 'password', CURRENT_TIMESTAMP) RETURNING id;`).Scan(&id)
	if err != nil {
		fmt.Fprintf(os.Stderr, "First test QueryRow failed: %v\n", err)
		os.Exit(1)
	}}

Actual behavior
There is nothing printout in the terminal and explicitly printing out err would get a nil value

Version

  • Go: go version go1.22.1 linux/amd64
  • PostgreSQL: PostgreSQL 16.2 (Ubuntu 16.2-1.pgdg20.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit
  • pgx: v5.5.4

Additional context
Running it as a prepared statement also return an empty value

	name := "test"
	email := "test@gmail.com"
	password := "password"

	stmt := `INSERT INTO users (name, email, hashed_password, created) VALUES ($1, $2, $3, CURRENT_TIMESTAMP)`
	row, err := conn.Query(context.Background(), stmt, name, email, password)
	fmt.Printf("Row: %v\n", row)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Second test Query failed: %v\n", err)
		os.Exit(1)
	}

See https://pkg.go.dev/github.com/jackc/pgx/v5#Conn.Query - in particular the first paragraph:

Query sends a query to the server and returns a Rows to read the results. Only errors encountered sending the query and initializing Rows will be returned. Err() on the returned Rows must be checked after the Rows is closed to determine if the query executed successfully.