Skip to content
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

Render queries as Text #133

Merged
merged 8 commits into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Unreleased
Unreleased (3.1.1)
========

- @parsonsmatt
- [#133](https://github.com/bitemyapp/esqueleto/pull/133): Added `renderQueryToText` and related functions.

3.1.0
=======

Expand Down
6 changes: 6 additions & 0 deletions src/Database/Esqueleto.hs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ module Database.Esqueleto
, insertSelectCount
, (<#)
, (<&>)
-- ** Rendering Queries
, renderQueryToText
, renderQuerySelect
, renderQueryUpdate
, renderQueryDelete
, renderQueryInsertInto
-- * Internal.Language
, From
-- * RDBMS-specific modules
Expand Down
88 changes: 86 additions & 2 deletions src/Database/Esqueleto/Internal/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1994,8 +1994,8 @@ builderToText = TL.toStrict . TLB.toLazyTextWith defaultChunkSize
--
-- Note: if you're curious about the SQL query being generated by
-- @esqueleto@, instead of manually using this function (which is
-- possible but tedious), you may just turn on query logging of
-- @persistent@.
-- possible but tedious), see the 'renderQueryToText' function (along with
-- 'renderQuerySelect', 'renderQueryUpdate', etc).
toRawSql
:: (SqlSelect a r, BackendCompatible SqlBackend backend)
=> Mode -> (backend, IdentState) -> SqlQuery a -> (TLB.Builder, [PersistValue])
Expand Down Expand Up @@ -2031,6 +2031,90 @@ toRawSql mode (conn, firstIdentState) query =
, makeLocking lockingClause
]

-- | Renders a 'SqlQuery' into a 'Text' value along with the list of
-- 'PersistValue's that would be supplied to the database for @?@ placeholders.
--
-- You must ensure that the 'Mode' you pass to this function corresponds with
-- the actual 'SqlQuery'. If you pass a query that uses incompatible features
-- (like an @INSERT@ statement with a @SELECT@ mode) then you'll get a weird
-- result.
--
-- @since 3.1.1
renderQueryToText
:: (SqlSelect a r, BackendCompatible SqlBackend backend, Monad m)
=> Mode
-- ^ Whether to render as an 'SELECT', 'DELETE', etc.
-> SqlQuery a
-- ^ The SQL query you want to render.
-> R.ReaderT backend m (T.Text, [PersistValue])
renderQueryToText mode query = do
backend <- R.ask
let (builder, pvals) = toRawSql mode (backend, initialIdentState) query
pure (builderToText builder, pvals)

-- | Renders a 'SqlQuery' into a 'Text' value along with the list of
-- 'PersistValue's that would be supplied to the database for @?@ placeholders.
--
-- You must ensure that the 'Mode' you pass to this function corresponds with
-- the actual 'SqlQuery'. If you pass a query that uses incompatible features
-- (like an @INSERT@ statement with a @SELECT@ mode) then you'll get a weird
-- result.
--
-- @since 3.1.1
renderQuerySelect
:: (SqlSelect a r, BackendCompatible SqlBackend backend, Monad m)
=> SqlQuery a
-- ^ The SQL query you want to render.
-> R.ReaderT backend m (T.Text, [PersistValue])
renderQuerySelect = renderQueryToText SELECT

-- | Renders a 'SqlQuery' into a 'Text' value along with the list of
-- 'PersistValue's that would be supplied to the database for @?@ placeholders.
--
-- You must ensure that the 'Mode' you pass to this function corresponds with
-- the actual 'SqlQuery'. If you pass a query that uses incompatible features
-- (like an @INSERT@ statement with a @SELECT@ mode) then you'll get a weird
-- result.
--
-- @since 3.1.1
renderQueryDelete
:: (SqlSelect a r, BackendCompatible SqlBackend backend, Monad m)
=> SqlQuery a
-- ^ The SQL query you want to render.
-> R.ReaderT backend m (T.Text, [PersistValue])
renderQueryDelete = renderQueryToText DELETE

-- | Renders a 'SqlQuery' into a 'Text' value along with the list of
-- 'PersistValue's that would be supplied to the database for @?@ placeholders.
--
-- You must ensure that the 'Mode' you pass to this function corresponds with
-- the actual 'SqlQuery'. If you pass a query that uses incompatible features
-- (like an @INSERT@ statement with a @SELECT@ mode) then you'll get a weird
-- result.
--
-- @since 3.1.1
renderQueryUpdate
:: (SqlSelect a r, BackendCompatible SqlBackend backend, Monad m)
=> SqlQuery a
-- ^ The SQL query you want to render.
-> R.ReaderT backend m (T.Text, [PersistValue])
renderQueryUpdate = renderQueryToText UPDATE

-- | Renders a 'SqlQuery' into a 'Text' value along with the list of
-- 'PersistValue's that would be supplied to the database for @?@ placeholders.
--
-- You must ensure that the 'Mode' you pass to this function corresponds with
-- the actual 'SqlQuery'. If you pass a query that uses incompatible features
-- (like an @INSERT@ statement with a @SELECT@ mode) then you'll get a weird
-- result.
--
-- @since 3.1.1
renderQueryInsertInto
:: (SqlSelect a r, BackendCompatible SqlBackend backend, Monad m)
=> SqlQuery a
-- ^ The SQL query you want to render.
-> R.ReaderT backend m (T.Text, [PersistValue])
renderQueryInsertInto = renderQueryToText INSERT_INTO

-- | (Internal) Mode of query being converted by 'toRawSql'.
data Mode =
Expand Down
5 changes: 5 additions & 0 deletions src/Database/Esqueleto/Internal/Sql.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ module Database.Esqueleto.Internal.Sql
, veryUnsafeCoerceSqlExprValue
, veryUnsafeCoerceSqlExprValueList
-- * Helper functions
, renderQueryToText
, renderQuerySelect
, renderQueryUpdate
, renderQueryDelete
, renderQueryInsertInto
, makeOrderByNoNewline
, uncommas'
, parens
Expand Down
26 changes: 23 additions & 3 deletions test/Common/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ import Database.Persist.TH
import Test.Hspec
import UnliftIO

import Database.Persist (PersistValue(..))
import Data.Conduit (ConduitT, (.|), runConduit)
import qualified Data.Conduit.List as CL
import qualified Data.List as L
import qualified Data.Set as S
import qualified Data.Text as Text
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Internal.Lazy as TL
import qualified Database.Esqueleto.Internal.Sql as EI
Expand Down Expand Up @@ -1437,7 +1439,26 @@ testCountingRows run = do
[Value n] <- select $ from $ return . countKind
liftIO $ (n :: Int) `shouldBe` expected


testRenderSql :: Run -> Spec
testRenderSql run =
describe "testRenderSql" $ do
it "works" $ do
(queryText, queryVals) <- run $ renderQuerySelect $
from $ \p -> do
where_ $ p ^. PersonName ==. val "Johhny Depp"
pure (p ^. PersonName, p ^. PersonAge)
-- the different backends use different quote marks, so I filter them out
-- here instead of making a duplicate test
Text.filter (\c -> c `notElem` ['`', '"']) queryText
`shouldBe`
Text.unlines
[ "SELECT Person.name, Person.age"
, "FROM Person"
, "WHERE Person.name = ?"
]
queryVals
`shouldBe`
[toPersistValue ("Johhny Depp" :: TL.Text)]



Expand All @@ -1460,8 +1481,7 @@ tests run = do
testMathFunctions run
testCase run
testCountingRows run


testRenderSql run


insert' :: ( Functor m
Expand Down