I am writing a SQL combiner that allows you to compose SQL fragments as a monoid. I have something like this (this is a simplified implementation):
data SQLFragment = { selects :: [String], froms :[String], wheres :: [String]} instance Monoid SQL Fragment where ...
This allows me to easily combine the SQL bits that I often use and do things like:
email = select "email" <> from "user" name = select "name" <> from "user" administrators = from "user" <> where_ "isAdmin = 1" toSql $ email <> name <> administrators => "SELECT email, name FROM user WHERE isAdmin = 1"
It works very well and I am pleased with it. Now I am using MySQL.Simple , and to execute it, it must know the type of string.
main = do conn <- SQL.connect connectInfo rows <- SQL.query_ conn $ toSql (email <> name <> administrators) forM_ (rows :: [(String, String)]) print
That's why i need
rows :: [(String, String)]
In order not to manually add this explicit (and useless) type signature, I had the following idea: I add the phantom type to my SQLFragment and use it to force the query_ function query_ . So I could be something like this
email = select "email" <> from "user" :: SQLFragment String name = select "name" <> from "user" :: SQLFragment String administrators = from "user" <> where_ "isAdmin = 1" :: SQLFragment ()
etc.
Then i can do
query_ :: SQL.Connection -> SQLFragment a -> IO [a] query_ con q = SQL.query_ conn (toSql q)
My first problem is that I can no longer use <> because SQLFragment a no longer Monoid . Secondly, how can I implement my new <> for proper phantom type calculation?
I found a way that I think is ugly, and I hope there is a much better solution. I created a typed version from SQLFragment and used the phantom attribute, which is an HList .
data TQuery e = TQuery { fragment :: SQLFragment , doNotUse :: e }
then I create a new typed operator typed !<>! I am not an undestand type signature, so I do not write it
(TQuery qe) !<>! (TQuery q' e') = TQuery (q<>q') (e.*.e')
Now I canβt combine my typed fragment and keep track of the type (although it is not yet a tuple, but something really strange).
To convert this strange type to a tuple, I create a type family:
type family Result e :: *
and create an instance for some sets
Another solution would probably be to use a type family and manually record each tuple combination
type instance Result (HList '[a]) = (SQL.Only a) type instance Result (HList '[HList '[a], b]) = (a, b) type instance Result (HList '[HList '[HList '[a], b], c]) = (a, b, c) type instance Result (HList '[HList '[HList '[HList '[a], b], c], d]) = (a, b, c, d) type instance Result (HList '[HList '[HList '[HList '[HList '[a], b], c], d], e]) = (a, b, c,d, e)
etc.
And it works. I can write my function using the Result family
execute :: (SQL.QueryResults (Result e)) => SQL.Connection -> TQuery e -> SQL.Connection -> IO [Result e] execute conn (TQuery q _ ) = SQL.query_ conn (toSql q)
My main program is as follows:
email = TQuery (select "email" <> from "user") ((undefined :: String ) .*. HNil) name = TQuery (select "name" <> from "user" ) ((undefined :: String ) .*. HNil) administrators = TQuery (from "user" <> where_ "isAdmin = 1") (HNil) main = do conn <- SQL.connect connectInfo rows <- execute conn $ email !<>! name !<>! administrators forM_ rows print
and it works!
However, is there a better way to do this, especially without using an HList and, if possible, smaller extensions?
If I somehow βhideβ the phantom type (so that I can have a real Monoid and be able to use <> instead of !<>! ), Is there a way to return the type?