Quantcast

API design question

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

API design question

Bardur Arantsson-2
Hi all,

I'm writing an API for a little database library which has -- until now
-- used io-streams, and I was wondering how one would design the query API.

In particular, I have a function similar to:

   query' :: Text -> [SqlValue] -> (Maybe Int -> InputStream [SqlValue]
-> IO a) -> IO a
   query' sql params callback = do
     ...

(I'm actually really using MonadIO, etc., but that shouldn't matter much
for the question -- I'll have to use SafeT from pipes-safe to ensure
proper transaction/connection handling, but I don't think that'll change
things too much.)

The idea is that the user-provided callback receives a "row count"
(Maybe Int) and an InputStream where it can read as much as it wants
(until EOS, obviously). Once the callback terminates, the transaction
commits and the returned value is returned from query'

My question is: How would this API look in the pipes world? The obvious
candidate would be something like

   query' :: Text -> [SqlValue] -> Producer' [SqlValue] m r

but that precludes the "row count". Another option might be

   query' :: Text -> [SqlValue] -> (Maybe Int -> Consumer' [SqlValue] m
r) -> X

... but I'm unsure whether that even makes sense and what I'd want X to
be. Any opinions?

--



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: API design question

Daniel Díaz Carrete
I would keep the API similar to the io-streams, by passing a function that consumes a Producer

    query' :: Text -> [SqlValue] -> (Maybe Int -> Producer [SqlValue] -> IO a) -> IO a 

This makes resource handling easier. It would also let you pass pipes parsers (which can be easily turned into producer-consuming functions). But perhaps "parsing" the sequence of tuples of a SQL query doesn't make a lot of sense.

Another option would be to forgo pipes and just use Folds from the "foldl" package. Perhaps monadic Folds over ExceptT that would let you "exit early".

On Thursday, January 19, 2017 at 8:11:20 PM UTC+1, Bardur Arantsson wrote:
Hi all,

I'm writing an API for a little database library which has -- until now
-- used io-streams, and I was wondering how one would design the query API.

In particular, I have a function similar to:

   query' :: Text -> [SqlValue] -> (Maybe Int -> InputStream [SqlValue]
-> IO a) -> IO a
   query' sql params callback = do
     ...

(I'm actually really using MonadIO, etc., but that shouldn't matter much
for the question -- I'll have to use SafeT from pipes-safe to ensure
proper transaction/connection handling, but I don't think that'll change
things too much.)

The idea is that the user-provided callback receives a "row count"
(Maybe Int) and an InputStream where it can read as much as it wants
(until EOS, obviously). Once the callback terminates, the transaction
commits and the returned value is returned from query'

My question is: How would this API look in the pipes world? The obvious
candidate would be something like

   query' :: Text -> [SqlValue] -> Producer' [SqlValue] m r

but that precludes the "row count". Another option might be

   query' :: Text -> [SqlValue] -> (Maybe Int -> Consumer' [SqlValue] m
r) -> X

... but I'm unsure whether that even makes sense and what I'd want X to
be. Any opinions?

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: API design question

Gabriel Gonzalez
The only thing I would add to Daniel's answer is that you can use the `managed` library to simplify the type a little bit to:

    query' :: Text -> [SqlValue] -> Managed (Maybe Int, Producer [SqlValue] IO ())

Also, since you aren't using the return value of the `Producer`, you can simplify it even further by using `ListT`:

    query' :: Text -> [SqlValue] -> Managed (Maybe Int, ListT IO [SqlValue])

It's not clear to me, though, why each read from the database returns a list of `SqlValue`s, though



On Fri, Jan 20, 2017 at 7:35 AM, Daniel Díaz <[hidden email]> wrote:
I would keep the API similar to the io-streams, by passing a function that consumes a Producer

    query' :: Text -> [SqlValue] -> (Maybe Int -> Producer [SqlValue] -> IO a) -> IO a 

This makes resource handling easier. It would also let you pass pipes parsers (which can be easily turned into producer-consuming functions). But perhaps "parsing" the sequence of tuples of a SQL query doesn't make a lot of sense.

Another option would be to forgo pipes and just use Folds from the "foldl" package. Perhaps monadic Folds over ExceptT that would let you "exit early".


On Thursday, January 19, 2017 at 8:11:20 PM UTC+1, Bardur Arantsson wrote:
Hi all,

I'm writing an API for a little database library which has -- until now
-- used io-streams, and I was wondering how one would design the query API.

In particular, I have a function similar to:

   query' :: Text -> [SqlValue] -> (Maybe Int -> InputStream [SqlValue]
-> IO a) -> IO a
   query' sql params callback = do
     ...

(I'm actually really using MonadIO, etc., but that shouldn't matter much
for the question -- I'll have to use SafeT from pipes-safe to ensure
proper transaction/connection handling, but I don't think that'll change
things too much.)

The idea is that the user-provided callback receives a "row count"
(Maybe Int) and an InputStream where it can read as much as it wants
(until EOS, obviously). Once the callback terminates, the transaction
commits and the returned value is returned from query'

My question is: How would this API look in the pipes world? The obvious
candidate would be something like

   query' :: Text -> [SqlValue] -> Producer' [SqlValue] m r

but that precludes the "row count". Another option might be

   query' :: Text -> [SqlValue] -> (Maybe Int -> Consumer' [SqlValue] m
r) -> X

... but I'm unsure whether that even makes sense and what I'd want X to
be. Any opinions?

--

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: API design question

Bardur Arantsson-2
On 2017-01-22 17:56, Gabriel Gonzalez wrote:
> The only thing I would add to Daniel's answer is that you can use the
> `managed` library to simplify the type a little bit to:
>
>     query' :: Text -> [SqlValue] -> Managed (Maybe Int, Producer
> [SqlValue] IO ())
>

Ah, thanks, I'll have a look at managed too.

> Also, since you aren't using the return value of the `Producer`, you can
> simplify it even further by using `ListT`:
>
>     query' :: Text -> [SqlValue] -> Managed (Maybe Int, ListT IO [SqlValue])
>
> It's not clear to me, though, why each read from the database returns a
> list of `SqlValue`s, though

Oh, that's just because SqlValue is a the value in a single column, so
[SqlValue] is the full row. (I have no need for anything more fancy to
map rows to data structures.)

Thanks for the suggestions Gabriel and Daniel.

Regards,

--



Loading...