Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ newer. Previously PostgreSQL 8.4 and newer were supported.

- Support [`sslnegotiation`] to use SSL without negotiation ([#1180]).

- The `pq.Error.Error()` text includes the position of the error (if reported
by PostgreSQL) and SQLSTATE code ([#1224]):

pq: column "columndoesntexist" does not exist at column 8 (42703)
pq: syntax error at or near ")" at position 2:71 (42601)

- The `pq.Error.ErrorWithDetail()` method prints a more detailed multiline
message, with the Detail, Hint, and error position (if any) ([#1219]):

Expand Down Expand Up @@ -81,6 +87,7 @@ newer. Previously PostgreSQL 8.4 and newer were supported.
[#1214]: https://github.com/lib/pq/pull/1214
[#1219]: https://github.com/lib/pq/pull/1219
[#1223]: https://github.com/lib/pq/pull/1223
[#1224]: https://github.com/lib/pq/pull/1224


v1.10.9 (2023-04-26)
Expand Down
3 changes: 0 additions & 3 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1888,7 +1888,6 @@ func TestStmtQueryContext(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
db := pqtest.MustDB(t)
defer db.Close()

Expand Down Expand Up @@ -1950,7 +1949,6 @@ func TestStmtExecContext(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
db := pqtest.MustDB(t)
defer db.Close()

Expand Down Expand Up @@ -2435,7 +2433,6 @@ func TestAuth(t *testing.T) {

for _, tt := range tests {
t.Run(tt.conn, func(t *testing.T) {
t.Parallel()
db, err := pqtest.DB(tt.conn)
if err != nil {
t.Fatal(err)
Expand Down
18 changes: 16 additions & 2 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,24 @@ func (e *Error) SQLState() string {
}

func (e *Error) Error() string {
msg := e.Message
if e.query != "" && e.Position != "" {
pos, err := strconv.Atoi(e.Position)
if err == nil {
lines := strings.Split(e.query, "\n")
line, col := posToLine(pos, lines)
if len(lines) == 1 {
msg += " at column " + strconv.Itoa(col)
} else {
msg += " at position " + strconv.Itoa(line) + ":" + strconv.Itoa(col)
}
}
}

if e.Code != "" {
return "pq: " + e.Message + " (" + string(e.Code) + ")"
return "pq: " + msg + " (" + string(e.Code) + ")"
}
return "pq: " + e.Message
return "pq: " + msg
}

// ErrorWithDetail returns the error message with detailed information and
Expand Down
89 changes: 65 additions & 24 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ func TestError(t *testing.T) {
ERROR: cannot copy from view "x" (42809)
HINT: Try the COPY (SELECT ...) TO variant.
`},
{`select columndoesntexist`, `pq: column "columndoesntexist" does not exist (42703)`, `
{`select columndoesntexist`, `pq: column "columndoesntexist" does not exist at column 8 (42703)`, `
ERROR: column "columndoesntexist" does not exist (42703)
CONTEXT: line 1, column 8:

1 | select columndoesntexist
^
`},
{`select !@#`, "pq: syntax error at end of input (42601)", `
{`select !@#`, "pq: syntax error at end of input at column 11 (42601)", `
ERROR: syntax error at end of input (42601)
CONTEXT: line 1, column 11:

1 | select !@#
^
`},
{"select 'asd',\n\t'asd'::jsonb", "pq: invalid input syntax for type json (22P02)", `
{"select 'asd',\n\t'asd'::jsonb", "pq: invalid input syntax for type json at position 2:2 (22P02)", `
ERROR: invalid input syntax for type json (22P02)
DETAIL: Token "asd" is invalid.
CONTEXT: line 2, column 2:
Expand All @@ -54,7 +54,7 @@ func TestError(t *testing.T) {
2 | 'asd'::jsonb
^
`},
{"select 'asd'\n,'zxc',\n'def',\n123,\n'foo', 'asd'::jsonb", "pq: invalid input syntax for type json (22P02)", `
{"select 'asd'\n,'zxc',\n'def',\n123,\n'foo', 'asd'::jsonb", "pq: invalid input syntax for type json at position 5:8 (22P02)", `
ERROR: invalid input syntax for type json (22P02)
DETAIL: Token "asd" is invalid.
CONTEXT: line 5, column 8:
Expand All @@ -64,14 +64,14 @@ func TestError(t *testing.T) {
5 | 'foo', 'asd'::jsonb
^
`},
{"select '€€€', a", `pq: column "a" does not exist (42703)`, `
{"select '€€€', a", `pq: column "a" does not exist at column 15 (42703)`, `
ERROR: column "a" does not exist (42703)
CONTEXT: line 1, column 15:

1 | select '€€€', a
^
`},
{"select '€€€',\n'€',a", `pq: column "a" does not exist (42703)`, `
{"select '€€€',\n'€',a", `pq: column "a" does not exist at position 2:5 (42703)`, `
ERROR: column "a" does not exist (42703)
CONTEXT: line 2, column 5:

Expand All @@ -93,7 +93,7 @@ func TestError(t *testing.T) {
version varchar,
);
create unique index "systems#name#version" on systems(name, version);
`), `pq: syntax error at or near ")" (42601)`, `
`), `pq: syntax error at or near ")" at position 12:1 (42601)`, `
ERROR: syntax error at or near ")" (42601)
CONTEXT: line 12, column 1:

Expand All @@ -106,7 +106,7 @@ func TestError(t *testing.T) {
{pqtest.NormalizeIndent(`
create table browsers (browser_id serial, name varchar, version varchar); create unique index "browsers#name#version" on browsers(name, version);
create table systems (system_id serial, name varchar, version varchar,); create unique index "systems#name#version" on systems(name, version);
`), `pq: syntax error at or near ")" (42601)`, `
`), `pq: syntax error at or near ")" at position 2:71 (42601)`, `
ERROR: syntax error at or near ")" (42601)
CONTEXT: line 2, column 71:

Expand All @@ -116,27 +116,68 @@ func TestError(t *testing.T) {
`},
}

t.Parallel()
db := pqtest.MustDB(t)

for _, tt := range tests {
_, err := db.Exec(tt.in)
if err == nil {
t.Fatal("no error?")
t.Run("", func(t *testing.T) {
_, err := db.Exec(tt.in)
if err == nil {
t.Fatal("no error?")
}
pqErr := new(Error)
if !errors.As(err, &pqErr) {
t.Fatalf("wrong error %T: %[1]s", err)
}

if err.Error() != tt.want {
t.Errorf("\nhave: %s\nwant: %s", err.Error(), tt.want)
}
tt.wantDetail = pqtest.NormalizeIndent(tt.wantDetail)
if pqErr.query != "" && pqErr.Position != "" {
tt.wantDetail += "\n"
}
if pqErr.ErrorWithDetail() != tt.wantDetail {
t.Errorf("\nhave:\n%s\nwant:\n%s", pqErr.ErrorWithDetail(), tt.wantDetail)
}
})
}
}

func BenchmarkError(b *testing.B) {
db := pqtest.MustDB(b)
_, err := db.Exec(pqtest.NormalizeIndent(`
create table browsers (
browser_id serial,
name varchar,
version varchar
);
create unique index "browsers#name#version" on browsers(name, version);

create table systems (
system_id serial,
name varchar,
version varchar,
);
create unique index "systems#name#version" on systems(name, version);
`))
if err == nil {
b.Fatal("err is nil?")
}

b.ResetTimer()
b.Run("error", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = err.Error()
}
})
b.Run("errorWithDetail", func(b *testing.B) {
pqErr := new(Error)
if !errors.As(err, &pqErr) {
t.Fatalf("wrong error %T: %[1]s", err)
}

if err.Error() != tt.want {
t.Errorf("\nhave: %s\nwant: %s", err.Error(), tt.want)
b.Fatalf("not pq.Error: %T", err)
}
tt.wantDetail = pqtest.NormalizeIndent(tt.wantDetail)
if pqErr.query != "" && pqErr.Position != "" {
tt.wantDetail += "\n"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = pqErr.ErrorWithDetail()
}
if pqErr.ErrorWithDetail() != tt.wantDetail {
t.Errorf("\nhave:\n%s\nwant:\n%s", pqErr.ErrorWithDetail(), tt.wantDetail)
}
}
})
}
Loading