-
Notifications
You must be signed in to change notification settings - Fork 292
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
Implement rawSql as a QuasiQuoter #717
Conversation
7856bd3
to
4732b34
Compare
@felixSchl Something like this should work: makeExpr :: TH.ExpQ -> [StringPart] -> TH.ExpQ
makeExpr f s = TH.appE [| uncurry $(f) . first pack |] (go s)
where
go [] = [| (mempty, []) |]
go (Literal a:xs) = TH.appE [| first (a ++) |] (go xs)
go (AntiQuote a:xs) = TH.appE [| first ("?" ++) . second (toPersistValue $(reify a) :) |] (go xs)
sqlQQ :: QuasiQuoter
sqlQQ = QuasiQuoter
(makeExpr [| Raw.rawSql |] . parseStr [] . filter (/= '\r'))
(error "Cannot use qc as a pattern")
(error "Cannot use qc as a type")
(error "Cannot use qc as a dec")
executeQQ :: QuasiQuoter
executeQQ = QuasiQuoter
(makeExpr [| Raw.rawExecute |] . parseStr [] . filter (/= '\r'))
(error "Cannot use qc as a pattern")
(error "Cannot use qc as a type")
(error "Cannot use qc as a dec") The problem is that you effectively want to pass a template haskell Also, it would be really nice to have some documentation for the Also a small example in the haddocks would probably help. |
Thanks @cdepillabout I managed to implement your advice, but I have doubts about the readability of this Still better than duplicate code I guess. The transform is really, really simple. It just substitutes all |
@cdepillabout This is my first feature contribution to a Haskell project. Would you be able to advise me how / where to put that documentation? |
parseStr a (x:xs) = parseStr (x:a) xs | ||
|
||
makeExpr :: TH.Q TH.Exp -> [StringPart] -> TH.ExpQ | ||
makeExpr n s = TH.appE (TH.appE [| flip (.) (first pack) |] (TH.appE [| uncurry |] n)) (go s) |
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.
Thanks @cdepillabout I managed to implement your advice, but I have doubts about the readability of this
Yeah, I think you're right. I haven't taken a hard look at the code, but it seems like you might not need to split the (frist pack)
and uncurry
stuff into two separate expressions?
Would something like the following work? I've checked to make sure that this compiles, although I'm not sure if it passes the tests:
makeExpr :: TH.ExpQ -> [StringPart] -> TH.ExpQ
makeExpr f s = TH.appE [| uncurry $(f) . first pack |] (go s)
where
go [] = [| (mempty, []) |]
go (Literal a:xs) = TH.appE [| first (a ++) |] (go xs)
go (AntiQuote a:xs) = TH.appE [| first ("?" ++) . second (toPersistValue $(reify a) :) |] (go xs)
Or maybe you could even throw the go
function inside the quasiquotes:
makeExpr :: TH.ExpQ -> [StringPart] -> TH.ExpQ
makeExpr f s = [| uncurry $(f) $ first pack ( $(go s) ) |]
where
go [] = [| (mempty, []) |]
go (Literal a:xs) = TH.appE [| first (a ++) |] (go xs)
go (AntiQuote a:xs) = TH.appE [| first ("?" ++) . second (toPersistValue $(reify a) :) |] (go xs)
Yeah, from looking at your example in the first comment, it does look like a very simple transformation. I was trying to say that it would be nice to have haddocks showing the type of the value produced by Here's an example of what I'm thinking about (using your explanation): -- | This is a quasiquoter for creating raw SQL expressions.
--
-- It substitutes all @#{foo}@ with question marks and collects them into a list. It applies
-- 'toPersistValue' on each value. It then simply applies 'rawSql' or 'rawExecute' to the
-- substituted string and collected values.
--
-- It produces a value of type
-- @('RawSql' a, 'MonadIO' m) => 'ReaderT' 'SqlBackend' m [a]@.
--
-- Here is a small example:
--
-- @
-- let lft = 10 :: Int
-- rgt = 20 :: Int
-- width = 50 :: Int
-- in [sqlQQ|
-- DELETE FROM category WHERE lft BETWEEN #{lft} AND #{rgt};
-- UPDATE category SET rgt = rgt - #{width} WHERE rgt > #{rgt};
-- UPDATE category SET lft = lft - #{width} WHERE lft > #{rgt};
-- |]
-- @
sqlQQ :: QuasiQuoter
sqlQQ = QuasiQuoter ...
Thanks a lot for your contribution! It is very much appreciated. 👍 (Although keep in mind I am not a maintainer for persistent. I just happened to be clicking around on github and realized I knew how to reduce the code duplication in |
I am still keen to work on this, but I am also somewhat complacent because I was able to simple copy that code into a module for my project, so before proceeding would be great to hear the maintainers' opinions on where this PR should go. |
Sorry, I'll help with it - we were probably all waiting to see if one of the other maintainers was more interested than ourselves:smile: I don't see any problem with this, especially since it is almost all in a new module. Could you please have a look at these things - I know a list looks daunting, but they should all be easy:
I'll check it again when you've done those things. Thanks for the PR! |
d764f99
to
cd36f01
Compare
I've made all the changes and simplified even further. I hope documenting each function becomes somewhat irrelevant now: -- | Analoguous to 'Database.Persist.Sql.Raw.rawSql'
-- @since 2.7.2
sqlQQ :: QuasiQuoter
sqlQQ = makeQQ [| rawSql |] I haven't had time to run the tests locally, let's see what CI says. Also, @paul-rouse, I was not sure where to put the |
8fd8b2e
to
9969bb2
Compare
Thanks for continuing to work on this! The test failure is due to a weakness in the tests themselves, but I would like to see a clean run - give me a day or two, and I'll try to sort them out. So far my attempts have only managed to produce a bug report against the latest pre-release version of |
Awesome, thanks. I actually hit another uncovered usecase just minutes ago: updateItemsInCart products cartPk = do
escape <- connEscapeName <$> ask
let tbl = escape $ entityDB $ entityDef (Nothing :: Maybe CartItem)
cartIdCol = escape $ fieldDB $ persistFieldDef CartItemCartId
productIdCol = escape $ fieldDB $ persistFieldDef CartItemProductId
qtyCol = escape $ fieldDB $ persistFieldDef CartItemQuantity
-- update the items
forM_ products $ \(productPk, qty) ->
if N.unpack qty <= 0
then deleteBy $ CartItemUniqueCartIdProductId cartPk productPk
else
[executeQQ|
INSERT INTO #{tbl} (#{cartIdCol}, #{productIdCol}, #{qtyCol})
VALUES (#{cartPk}, #{productPk}, #{qty})
ON CONFLICT (#{cartIdCol}, #{productIdCol})
DO UPDATE SET
#{qtyCol} = EXCLUDED.#{qtyCol}
|] This generates the following query:
But my intention was:
|
I think it would be cool if this was possible: forM_ products $ \(productPk, qty) ->
if N.unpack qty <= 0
then deleteBy $ CartItemUniqueCartIdProductId cartPk productPk
else
[executeQQ|
INSERT INTO ^{CartItem} (@{CartItemCardId}, @{CartItemProductId}, @{CartItemQuantity})
VALUES (#{cartPk}, #{productPk}, #{qty})
ON CONFLICT (@{CartItemCardId}, @{CartItemProductId})
DO UPDATE SET
@{CartItemQuantity} = EXCLUDED.@{CartItemQuantity}
|] So At least doing it this way "somewhat" type checks the program and makes sure things are escaped automatically. |
Fix tests to allow testing #717
I have fixed the travis tests in master to work around the problem meeting the requirement for |
2c7e7aa
to
915e6c1
Compare
Hm, not sure how to fix the |
The compiler's suggestion is misleading in this case - you really do need In this project, all compiler warnings are switched on and are treated as errors, which is quite strict! Of course the problem in your QQ.hs module is that the Prelude in base-4.7 didn't export the Monoid stuff, but from base-4.8 onwards it does, making the explicit import redundant. Since we know what we are doing (:smile:) it is simplest to just silence the warning:
|
Does that suggest I could do |
915e6c1
to
2d411bd
Compare
I think |
Ah yeah, of course. Hopefully this round of CI will pass. Did you have any response to the previous comments I made (interpolating table and column names)? I think that would be really cool to have, but I still haven't worked out how to do to the |
I can certainly see the benefit of having a syntax to interpolate table and column names. It is up to you - if you want to work that out now, then I'll wait for it; otherwise, if you prefer, I can merge this version and you can submit a separate PR when you are ready to extend it with the other types of substitution. Of course that assumes that the extended version would not break anything you have already done here. |
I don't see how it would break anything as the types don't have to change and the syntax is only extended. But if you bear with me, I'd like to give it another go before hand off |
OK, I'll wait for you to say! |
27f0aa0
to
277c35d
Compare
This introduces a bit more complexity into the Quasi Quoter but makes sure that no unused patterns are introduced if they are not needed
eb6c7b8
to
e712678
Compare
Not sure what's going on there but it seems the tests are failing for reasons other than the actual code changes, looks it's having trouble fetching some dependencies for the old lts2.2 https://travis-ci.org/yesodweb/persistent/jobs/299921263 |
I restarted that test, and it is OK now. Sorry to be a pain - could you add a link to the PR in ChangeLog.md, please, to be consistent with other recent entries? |
Oh sorry, yeah will do
…On 10/11/2017 20:53, "Paul Rouse" ***@***.***> wrote:
I restarted that test, and it is OK now.
Sorry to be a pain - could you add a link to the PR in ChangeLog.md,
please, to be consistent with other recent entries?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#717 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ACk_zjHHAz3NPGdoWgep_OPl2peDbysIks5s1ADlgaJpZM4P2bUD>
.
|
e712678
to
126fabc
Compare
126fabc
to
2e88e2e
Compare
Alright, updated and CI still OK :) |
Great! Thanks for all of your work on this! |
Rearrange documentation added in #717
I'm having a go at #716, now you can do:
Would be great if you could help me figure out how to pass a function into
makeExpr
so that I don't have to duplicate the whole block of code:https://github.com/felixSchl/persistent/blob/7856bd3878aadb9a703360d76934a7b7e78c66a4/persistent/Database/Persist/Sql/Raw/QQ.hs#L37-L49
I've also updated the test bed to include a few basic tests for this:
https://github.com/felixSchl/persistent/blob/7856bd3878aadb9a703360d76934a7b7e78c66a4/persistent-test/src/PersistentTest.hs#L891-L913