Skip to content

Commit ce27864

Browse files
committedMay 3, 2020
Add NotificationConnector and methods
The previous commit was kind of incomplete - it let you add a notification handler on a single connection, but didn't have the standard factory method (aka Connector) that let you customize the handlers for all new connections. This commit remedies the issue and adds a test.
1 parent 6a6cbca commit ce27864

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed
 

‎notify.go

+46
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package pq
44
// This module contains support for Postgres LISTEN/NOTIFY.
55

66
import (
7+
"context"
78
"database/sql/driver"
89
"errors"
910
"fmt"
@@ -40,6 +41,51 @@ func SetNotificationHandler(c driver.Conn, handler func(*Notification)) {
4041
c.(*conn).notificationHandler = handler
4142
}
4243

44+
// NotificationHandlerConnector wraps a regular connector and sets a notification handler
45+
// on it.
46+
type NotificationHandlerConnector struct {
47+
driver.Connector
48+
notificationHandler func(*Notification)
49+
}
50+
51+
// Connect calls the underlying connector's connect method and then sets the
52+
// notification handler.
53+
func (n *NotificationHandlerConnector) Connect(ctx context.Context) (driver.Conn, error) {
54+
c, err := n.Connector.Connect(ctx)
55+
if err == nil {
56+
SetNotificationHandler(c, n.notificationHandler)
57+
}
58+
return c, err
59+
}
60+
61+
// ConnectorNotificationHandler returns the currently set notification handler, if any. If
62+
// the given connector is not a result of ConnectorWithNotificationHandler, nil is
63+
// returned.
64+
func ConnectorNotificationHandler(c driver.Connector) func(*Notification) {
65+
if c, ok := c.(*NotificationHandlerConnector); ok {
66+
return c.notificationHandler
67+
}
68+
return nil
69+
}
70+
71+
// ConnectorWithNotificationHandler creates or sets the given handler for the given
72+
// connector. If the given connector is a result of calling this function
73+
// previously, it is simply set on the given connector and returned. Otherwise,
74+
// this returns a new connector wrapping the given one and setting the notification
75+
// handler. A nil notification handler may be used to unset it.
76+
//
77+
// The returned connector is intended to be used with database/sql.OpenDB.
78+
//
79+
// Note: Notification handlers are executed synchronously by pq meaning commands
80+
// won't continue to be processed until the handler returns.
81+
func ConnectorWithNotificationHandler(c driver.Connector, handler func(*Notification)) *NotificationHandlerConnector {
82+
if c, ok := c.(*NotificationHandlerConnector); ok {
83+
c.notificationHandler = handler
84+
return c
85+
}
86+
return &NotificationHandlerConnector{Connector: c, notificationHandler: handler}
87+
}
88+
4389
const (
4490
connStateIdle int32 = iota
4591
connStateExpectResponse

‎notify_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package pq
22

33
import (
4+
"database/sql"
5+
"database/sql/driver"
46
"errors"
57
"fmt"
68
"io"
@@ -568,3 +570,43 @@ func TestListenerPing(t *testing.T) {
568570
t.Fatalf("expected errListenerClosed; got %v", err)
569571
}
570572
}
573+
574+
func TestConnectorWithNotificationHandler_Simple(t *testing.T) {
575+
b, err := NewConnector("")
576+
if err != nil {
577+
t.Fatal(err)
578+
}
579+
var notification *Notification
580+
// Make connector w/ handler to set the local var
581+
c := ConnectorWithNotificationHandler(b, func(n *Notification) { notification = n })
582+
sendNotification(c, t, "Test notification #1")
583+
if notification == nil || notification.Extra != "Test notification #1" {
584+
t.Fatalf("Expected notification w/ message, got %v", notification)
585+
}
586+
// Unset the handler on the same connector
587+
prevC := c
588+
if c = ConnectorWithNotificationHandler(c, nil); c != prevC {
589+
t.Fatalf("Expected to not create new connector but did")
590+
}
591+
sendNotification(c, t, "Test notification #2")
592+
if notification == nil || notification.Extra != "Test notification #1" {
593+
t.Fatalf("Expected notification to not change, got %v", notification)
594+
}
595+
// Set it back on the same connector
596+
if c = ConnectorWithNotificationHandler(c, func(n *Notification) { notification = n }); c != prevC {
597+
t.Fatal("Expected to not create new connector but did")
598+
}
599+
sendNotification(c, t, "Test notification #3")
600+
if notification == nil || notification.Extra != "Test notification #3" {
601+
t.Fatalf("Expected notification w/ message, got %v", notification)
602+
}
603+
}
604+
605+
func sendNotification(c driver.Connector, t *testing.T, escapedNotification string) {
606+
db := sql.OpenDB(c)
607+
defer db.Close()
608+
sql := fmt.Sprintf("LISTEN foo; NOTIFY foo, '%s';", escapedNotification)
609+
if _, err := db.Exec(sql); err != nil {
610+
t.Fatal(err)
611+
}
612+
}

0 commit comments

Comments
 (0)
Please sign in to comment.