-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lift IO exceptions from PG to MonadError #28
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make sure to handle all IO functions that can throw SQLError
src/PgNamed.hs
Outdated
withNamedArgs qNamed params >>= \(q, actions) -> | ||
liftIO $ PG.execute conn q (toList actions) | ||
withNamedArgs qNamed params >>= \(q, actions) -> do | ||
res <- liftIO $ try $ PG.execute conn q (toList actions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other places can potentially throw SQLError:
E.g. search for PG.queryWith
, PG.query
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can probably become helper function that replaces all usages of liftIO within the module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks ok to me. But let's please have @arbus take a look if he had a different error handling strategy in mind.
|
||
|
||
handleIO :: (MonadIO m, WithNamedError m) => IO a -> m a | ||
handleIO io = do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: I'd rather call this something more specific like handleSqlError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep in mind this also lifts IO to MonadIO m. I used handle as it's also function from Control.Exception
(flip catch) as it sounded better than catchIO
. With this in mind how about calling this handlePgIO
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see the word exception/error in function name for clarity, so handleIO
/handlePgIO
don't cut it for me :-)
But it doesn't matter much, since it's not exposed anyway. How about rethrowSqlError? The fact that it lifts from IO is already visible from the type. The fact that it does something with exceptions is not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point. well in my mind handle
already refers to exception handling but I agree it's not that explicit. We can change this to rethrowSqlError
to be explicit.
@turboMaCk and @jhrcek, this looks good to me! Are we waiting on @arbus' approval to merge this PR? |
Definitely this is relatively big API change which will impact all users of this package, so let's wait with merging it. |
35a2300
to
abc1b9d
Compare
@turboMaCk before you dive deeper into this, be aware that postgresq-simple can throw bunch of other execeptions:
I'm not even sure if this package should be responsible for making the decision as to which errors are "more important" than others. |
well it should not it should just provide unified interface for handling these. Right now the confusing part is that functions of this library have |
I don't know. The haddocs say
It's true that there are 2 ways that the functions can fail (IO exceptions vs. MonadError) but adding the constructors to PgNamedError feels to me like tying the api too much to the underlying postgresql-simple library and it feels to me like it's out-of-scope of what this library is trying to do. Although I like the idea of unifying the API, I'm a bit afraid that with such a change we'd be opening a can of worms. Also there are further exceptions IOExceptions that postgresql-libpq can throw (example from pi) - how would you justify that we're catching the more higher level exceptions from postgresql-simple, but no the low-level exceptions from postgresql-libpq? |
If it would be up to me I would not design this library with MonadError and lifting of io. That way consumer of the lib would have control over how this is done. Anyway since we already do it we need to take care of consequences I think. This PR was just proof of concept. How this should probably look like is that Anyway current situation is that one type of programmer error (trouble with named arguments) is being thrown in MonadError but errors that even more likely deserve exception handling (like uniqness violation for instance) are thrown in IO which makes them hard to handle. It feels wrong to make error that is unlikely to be handled be easily handable while obscuring exceptions that almost every production application will need to handle in liftedIO. |
I'm of an opinion that that can is already open since this lib already do |
Even though the package is using MonadError it's still possible for users to catch those exceptions from using stuff from lifted-base or unliftio. It's not like this package's usage of MonadError is preventing those ways of dealing with exceptions. |
I never said it makes it impossible. I said it makes it harder and that it obscures exceptions by lifting which I think it does. The fact that additional errors are using MonadError and postgresql-simple ones are lifted makes it harder to do error handling correctly. I think we should fix that and make it easier to write error handlers. That way all downstream projects will have simpler error handling. At the moment every downstream project has to handle this in some way (like using functions from lifted-base) or just give up. The Golden Rule of SW Quality |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking as request change so it's not merged without wider discussion.
As I understand, the main issue (as @turboMaCk points out) is:
@turboMaCk 's solution is to add another constructor to the error type to handle the postgres-simple errors. However, as @jhrcek correctly points out, this doesn't cover all the possible errors that might be thrown by the library, since this solution only accounts for one of the types of errors ( Wouldn't the simplest solution be to have the fourth constructor be something like Regarding the concern around the API change, this will definitely be a breaking change, so we will need to have more discussions around this. |
This wouldn't be that simple to implement, because as far as I understand there is no single exception type in postgresql-simple that would be the parent of the hierarchy of exceptions that can be thrown by that library, so we would have to list all the types throwable by that lib somewhere. Also I would recommend reading https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell/ |
I also recommend that article. The problem is that we're already doing this by introducing named errors as an ExceptT and lifting io. Exceptions from postgresql-simple should be easy to catch because it's important to handle them correctly. |
This is now old and rotted. I'm making a step back and first creating an issue to discuss the problem #40 |
This is proof of concept solution for #27
Unfortunately this is hard to test using current testing approach which seems not to be really queering any real DB table even though it requires database connection.