dylex / postgresql-typed

Haskell PostgreSQL library with compile-time type inference
http://hackage.haskell.org/package/postgresql-typed
Other
83 stars 12 forks source link

LISTEN on NOTIFYs? #12

Open albertov opened 6 years ago

albertov commented 6 years ago

Is it possible to use this library to receive asynchronous notifications from the database server? I'm currently using postgresql-simple's Notification just for this functionality as I couldn't find a way to do it with this library but I'd rather avoid the extra dependency if possible.

Thanks for this amazing library BTW, it's been a pleasure to use so far.

dylex commented 6 years ago

Not currently. It looks like should be fairly easy to add a simple call to check/wait for a notification, though.

It would be more tricky to make this thread-safe (so that you could issue a query on the same connection while waiting for a notification), but that's mostly because nothing is thread safe currently.

If the simple version is sufficient, I'm happy to take a pass. Let me know if you have any thoughts on a better interface (like, registering a callback for a notify somehow?).

albertov commented 6 years ago

The simple version is enough for my case. I'm dedicating a connection exclusively to a single thread that listens to notifies so I don't foresee any thread-safety issues. Besides my case, I believe no ever driver I've ever seen allows a connection to be used among multiple threads safely (unless it is pooled or mutexed) so the simple version would be on par feature-wise anyway.

Regarding a higher-level interface, I'm currently exposing the notification services to the rest of the app in a (~100 loc) module with an interface that looks like this:

type Notification = ByteString
type Topic = ByteString
type TopicChan = (Int, BroadcastChan In Notification)
type Topics = M.HashMap Topic TopicChan

data Dispatcher = Dispatcher Connection (MVar Topics)

newtype Outlet = Outlet (BroadcastChan Out Notification)

-- | Starts a thread to wait for Notifications. A connection is created for this thread.
-- Both the connection and thread are closed/killed after exiting this function
withDispatcher
  :: (MonadLoggerIO m, MonadBaseControl IO m)
  => DBConfig -> (Dispatcher -> m a) -> m (Either ConnectionError a)

-- | Subscribes to a Topic, use getNotification to wait for Notifications.
-- Subscription is unregistered when exiting this function so don't use the Outlet
-- afterwards
withOutlet
  :: (MonadLogger m, MonadBaseControl IO m)
  => Dispatcher -> Topic -> (Outlet -> m a) -> m a

-- | Blocks for a notification
getNotification :: MonadBase IO m => Outlet -> m Notification

-- | Sends a notification. All processes listening on the 'Topic' will receive it
sendNotification
  :: MonadBase IO m
  => Dispatcher -> Topic -> Notification -> m ()

I would gladly submit a PR adapting it use postgresql-typed if you're OK with the interface and think it belongs in here.

The only thing it needs from the db driver is to be able to open a connection, execute a LISTEN (to implement withOutlet, this can be done right now) and receive async NOTIFYs (to implement 'getNotification')

It uses broadcast-chan since it was I was using when experimenting and never got to clean it up but it should work fine with plain STM channels since a channel (ie: Outlet) never has no listeners which could cause the kind of space-leaks broadcast-chan is designed to prevent.

It can also be specialized to IO to follow the style of the rest of the library.

dylex commented 6 years ago

Interesting, I guess that makes sense. I think I'll just start with getNotification for now, probably quite similar to what postgresql-simple has, and see how that goes.

albertov commented 6 years ago

Great, thanks! I'll keep an eye so I try it out and submit the Notifications module for review.

dylex commented 6 years ago

See if that works for you. Also, feel free to add some extra tests if you have any.

Thinking about the higher level interface, I guess one concern is that I'd rather not add more dependencies to the main library. I'll keep thinking about it, but it might be better as a separate library.

albertov commented 6 years ago

Sorry for the delay... I'll try to migrate this weekend to use this new functionality and let you know how it goes. I think the higher-level interface might be doable without any extra dependencies by specializing it to IO and uisng Control.Concurrent.Chan from base . I'll give it a shot and report back.

Thanks!