Skip to content

Commit 9eb3fc8

Browse files
authored
Merge pull request #932 from ActiveState/ActiveState/finish-pr-921
Add support for a handler func that receives notice messages from Pg
2 parents d6fd202 + dfe11e7 commit 9eb3fc8

File tree

6 files changed

+172
-4
lines changed

6 files changed

+172
-4
lines changed

conn.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ type conn struct {
149149

150150
// If true this connection is in the middle of a COPY
151151
inCopy bool
152+
153+
// If not nil, notices will be synchronously sent here
154+
noticeHandler func(*Error)
152155
}
153156

154157
// Handle driver-side settings in parsed connection string.
@@ -971,7 +974,9 @@ func (cn *conn) recv() (t byte, r *readBuf) {
971974
case 'E':
972975
panic(parseError(r))
973976
case 'N':
974-
// ignore
977+
if n := cn.noticeHandler; n != nil {
978+
n(parseError(r))
979+
}
975980
default:
976981
return
977982
}
@@ -988,8 +993,12 @@ func (cn *conn) recv1Buf(r *readBuf) byte {
988993
}
989994

990995
switch t {
991-
case 'A', 'N':
996+
case 'A':
992997
// ignore
998+
case 'N':
999+
if n := cn.noticeHandler; n != nil {
1000+
n(parseError(r))
1001+
}
9931002
case 'S':
9941003
cn.processParameterStatus(r)
9951004
default:

copy.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ func (ci *copyin) resploop() {
152152
case 'C':
153153
// complete
154154
case 'N':
155-
// NoticeResponse
155+
if n := ci.cn.noticeHandler; n != nil {
156+
n(parseError(&r))
157+
}
156158
case 'Z':
157159
ci.cn.processReadyForQuery(&r)
158160
ci.done <- true

notice.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// +build go1.10
2+
3+
package pq
4+
5+
import (
6+
"context"
7+
"database/sql/driver"
8+
)
9+
10+
// NoticeHandler returns the notice handler on the given connection, if any. A
11+
// runtime panic occurs if c is not a pq connection. This is rarely used
12+
// directly, use ConnectorNoticeHandler and ConnectorWithNoticeHandler instead.
13+
func NoticeHandler(c driver.Conn) func(*Error) {
14+
return c.(*conn).noticeHandler
15+
}
16+
17+
// SetNoticeHandler sets the given notice handler on the given connection. A
18+
// runtime panic occurs if c is not a pq connection. A nil handler may be used
19+
// to unset it. This is rarely used directly, use ConnectorNoticeHandler and
20+
// ConnectorWithNoticeHandler instead.
21+
//
22+
// Note: Notice handlers are executed synchronously by pq meaning commands
23+
// won't continue to be processed until the handler returns.
24+
func SetNoticeHandler(c driver.Conn, handler func(*Error)) {
25+
c.(*conn).noticeHandler = handler
26+
}
27+
28+
// NoticeHandlerConnector wraps a regular connector and sets a notice handler
29+
// on it.
30+
type NoticeHandlerConnector struct {
31+
driver.Connector
32+
noticeHandler func(*Error)
33+
}
34+
35+
// Connect calls the underlying connector's connect method and then sets the
36+
// notice handler.
37+
func (n *NoticeHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
38+
c, err := n.Connector.Connect(ctx)
39+
if err == nil {
40+
SetNoticeHandler(c, n.noticeHandler)
41+
}
42+
return c, err
43+
}
44+
45+
// ConnectorNoticeHandler returns the currently set notice handler, if any. If
46+
// the given connector is not a result of ConnectorWithNoticeHandler, nil is
47+
// returned.
48+
func ConnectorNoticeHandler(c driver.Connector) func(*Error) {
49+
if c, ok := c.(*NoticeHandlerConnector); ok {
50+
return c.noticeHandler
51+
}
52+
return nil
53+
}
54+
55+
// ConnectorWithNoticeHandler creates or sets the given handler for the given
56+
// connector. If the given connector is a result of calling this function
57+
// previously, it is simply set on the given connector and returned. Otherwise,
58+
// this returns a new connector wrapping the given one and setting the notice
59+
// handler. A nil notice handler may be used to unset it.
60+
//
61+
// The returned connector is intended to be used with database/sql.OpenDB.
62+
//
63+
// Note: Notice handlers are executed synchronously by pq meaning commands
64+
// won't continue to be processed until the handler returns.
65+
func ConnectorWithNoticeHandler(c driver.Connector, handler func(*Error)) *NoticeHandlerConnector {
66+
if c, ok := c.(*NoticeHandlerConnector); ok {
67+
c.noticeHandler = handler
68+
return c
69+
}
70+
return &NoticeHandlerConnector{Connector: c, noticeHandler: handler}
71+
}

notice_example_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// +build go1.10
2+
3+
package pq_test
4+
5+
import (
6+
"database/sql"
7+
"fmt"
8+
"log"
9+
10+
"github.com/lib/pq"
11+
)
12+
13+
func ExampleConnectorWithNoticeHandler() {
14+
name := ""
15+
// Base connector to wrap
16+
base, err := pq.NewConnector(name)
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
// Wrap the connector to simply print out the message
21+
connector := pq.ConnectorWithNoticeHandler(base, func(notice *pq.Error) {
22+
fmt.Println("Notice sent: " + notice.Message)
23+
})
24+
db := sql.OpenDB(connector)
25+
defer db.Close()
26+
// Raise a notice
27+
sql := "DO language plpgsql $$ BEGIN RAISE NOTICE 'test notice'; END $$"
28+
if _, err := db.Exec(sql); err != nil {
29+
log.Fatal(err)
30+
}
31+
// Output:
32+
// Notice sent: test notice
33+
}

notice_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// +build go1.10
2+
3+
package pq
4+
5+
import (
6+
"database/sql"
7+
"database/sql/driver"
8+
"testing"
9+
)
10+
11+
func TestConnectorWithNoticeHandler_Simple(t *testing.T) {
12+
b, err := NewConnector("")
13+
if err != nil {
14+
t.Fatal(err)
15+
}
16+
var notice *Error
17+
// Make connector w/ handler to set the local var
18+
c := ConnectorWithNoticeHandler(b, func(n *Error) { notice = n })
19+
raiseNotice(c, t, "Test notice #1")
20+
if notice == nil || notice.Message != "Test notice #1" {
21+
t.Fatalf("Expected notice w/ message, got %v", notice)
22+
}
23+
// Unset the handler on the same connector
24+
prevC := c
25+
if c = ConnectorWithNoticeHandler(c, nil); c != prevC {
26+
t.Fatalf("Expected to not create new connector but did")
27+
}
28+
raiseNotice(c, t, "Test notice #2")
29+
if notice == nil || notice.Message != "Test notice #1" {
30+
t.Fatalf("Expected notice to not change, got %v", notice)
31+
}
32+
// Set it back on the same connector
33+
if c = ConnectorWithNoticeHandler(c, func(n *Error) { notice = n }); c != prevC {
34+
t.Fatal("Expected to not create new connector but did")
35+
}
36+
raiseNotice(c, t, "Test notice #3")
37+
if notice == nil || notice.Message != "Test notice #3" {
38+
t.Fatalf("Expected notice w/ message, got %v", notice)
39+
}
40+
}
41+
42+
func raiseNotice(c driver.Connector, t *testing.T, escapedNotice string) {
43+
db := sql.OpenDB(c)
44+
defer db.Close()
45+
sql := "DO language plpgsql $$ BEGIN RAISE NOTICE '" + escapedNotice + "'; END $$"
46+
if _, err := db.Exec(sql); err != nil {
47+
t.Fatal(err)
48+
}
49+
}

notify.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,12 @@ func (l *ListenerConn) listenerConnLoop() (err error) {
174174
}
175175
l.replyChan <- message{t, nil}
176176

177-
case 'N', 'S':
177+
case 'S':
178178
// ignore
179+
case 'N':
180+
if n := l.cn.noticeHandler; n != nil {
181+
n(parseError(r))
182+
}
179183
default:
180184
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
181185
}

0 commit comments

Comments
 (0)