From 6d82106b68f26cdb01c6a219d22b3061071f0287 Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:40:01 -0600 Subject: [PATCH 1/7] renderQueryToText --- changelog.md | 5 +- src/Database/Esqueleto.hs | 6 ++ src/Database/Esqueleto/Internal/Internal.hs | 84 +++++++++++++++++++++ src/Database/Esqueleto/Internal/Sql.hs | 5 ++ test/Common/Test.hs | 5 +- 5 files changed, 100 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 1b0c222e1..222bc40f4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,9 @@ -Unreleased +Unreleased (3.1.1) ======== +- @parsonsmatt + - [](): Added `renderQueryToText` and related functions. + 3.1.0 ======= diff --git a/src/Database/Esqueleto.hs b/src/Database/Esqueleto.hs index 52175c637..d082191ad 100644 --- a/src/Database/Esqueleto.hs +++ b/src/Database/Esqueleto.hs @@ -86,6 +86,12 @@ module Database.Esqueleto , insertSelectCount , (<#) , (<&>) + -- ** Rendering Queries + , renderQueryToText + , renderQuerySelect + , renderQueryUpdate + , renderQueryDelete + , renderQueryInsertInto -- * Internal.Language , From -- * RDBMS-specific modules diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index 151af41eb..ec4a88673 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -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 = diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 6ab2be033..78186240c 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -61,6 +61,11 @@ module Database.Esqueleto.Internal.Sql , veryUnsafeCoerceSqlExprValue , veryUnsafeCoerceSqlExprValueList -- * Helper functions + , renderQueryToText + , renderQuerySelect + , renderQueryUpdate + , renderQueryDelete + , renderQueryInsertInto , makeOrderByNoNewline , uncommas' , parens diff --git a/test/Common/Test.hs b/test/Common/Test.hs index ce8805805..5cdc7b736 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -1437,10 +1437,6 @@ testCountingRows run = do [Value n] <- select $ from $ return . countKind liftIO $ (n :: Int) `shouldBe` expected - - - - tests :: Run -> Spec tests run = do describe "Tests that are common to all backends" $ do @@ -1460,6 +1456,7 @@ tests run = do testMathFunctions run testCase run testCountingRows run + testRenderSql run From 677868b07c570aa6f06ae193d067e989a607e3c8 Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:40:57 -0600 Subject: [PATCH 2/7] update note --- src/Database/Esqueleto/Internal/Internal.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index ec4a88673..4a506969d 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -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]) From b4a92ed33afb68a30ffdeed2cda51ecc88c35dd6 Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:41:39 -0600 Subject: [PATCH 3/7] add github note --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 222bc40f4..1eec4fa39 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ Unreleased (3.1.1) ======== - @parsonsmatt - - [](): Added `renderQueryToText` and related functions. + - [#133](https://github.com/bitemyapp/esqueleto/pull/133): Added `renderQueryToText` and related functions. 3.1.0 ======= From a36f3f7bfe751eaf01b139c3c27dbd2633a70ba9 Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:40:01 -0600 Subject: [PATCH 4/7] renderQueryToText --- changelog.md | 5 +- src/Database/Esqueleto.hs | 6 ++ src/Database/Esqueleto/Internal/Internal.hs | 84 +++++++++++++++++++++ src/Database/Esqueleto/Internal/Sql.hs | 5 ++ test/Common/Test.hs | 5 +- 5 files changed, 100 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 1b0c222e1..222bc40f4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,9 @@ -Unreleased +Unreleased (3.1.1) ======== +- @parsonsmatt + - [](): Added `renderQueryToText` and related functions. + 3.1.0 ======= diff --git a/src/Database/Esqueleto.hs b/src/Database/Esqueleto.hs index 52175c637..d082191ad 100644 --- a/src/Database/Esqueleto.hs +++ b/src/Database/Esqueleto.hs @@ -86,6 +86,12 @@ module Database.Esqueleto , insertSelectCount , (<#) , (<&>) + -- ** Rendering Queries + , renderQueryToText + , renderQuerySelect + , renderQueryUpdate + , renderQueryDelete + , renderQueryInsertInto -- * Internal.Language , From -- * RDBMS-specific modules diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index 151af41eb..ec4a88673 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -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 = diff --git a/src/Database/Esqueleto/Internal/Sql.hs b/src/Database/Esqueleto/Internal/Sql.hs index 6ab2be033..78186240c 100644 --- a/src/Database/Esqueleto/Internal/Sql.hs +++ b/src/Database/Esqueleto/Internal/Sql.hs @@ -61,6 +61,11 @@ module Database.Esqueleto.Internal.Sql , veryUnsafeCoerceSqlExprValue , veryUnsafeCoerceSqlExprValueList -- * Helper functions + , renderQueryToText + , renderQuerySelect + , renderQueryUpdate + , renderQueryDelete + , renderQueryInsertInto , makeOrderByNoNewline , uncommas' , parens diff --git a/test/Common/Test.hs b/test/Common/Test.hs index ce8805805..5cdc7b736 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -1437,10 +1437,6 @@ testCountingRows run = do [Value n] <- select $ from $ return . countKind liftIO $ (n :: Int) `shouldBe` expected - - - - tests :: Run -> Spec tests run = do describe "Tests that are common to all backends" $ do @@ -1460,6 +1456,7 @@ tests run = do testMathFunctions run testCase run testCountingRows run + testRenderSql run From 330a36b27e0b224709fcc8d0b5cf50a2ae05bfef Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:40:57 -0600 Subject: [PATCH 5/7] update note --- src/Database/Esqueleto/Internal/Internal.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index ec4a88673..4a506969d 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -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]) From c7a24bd968d0035676e569dfdf3ab6fa83ac02b1 Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Wed, 28 Aug 2019 09:41:39 -0600 Subject: [PATCH 6/7] add github note --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 222bc40f4..1eec4fa39 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ Unreleased (3.1.1) ======== - @parsonsmatt - - [](): Added `renderQueryToText` and related functions. + - [#133](https://github.com/bitemyapp/esqueleto/pull/133): Added `renderQueryToText` and related functions. 3.1.0 ======= From 30cba150944d9cbb27fb104e65f2a7c9016de5ad Mon Sep 17 00:00:00 2001 From: parsonsmatt Date: Tue, 24 Sep 2019 08:50:52 -0600 Subject: [PATCH 7/7] Fix test --- test/Common/Test.hs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/test/Common/Test.hs b/test/Common/Test.hs index 5cdc7b736..a5450fb40 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -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 @@ -1437,6 +1439,29 @@ 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)] + + + tests :: Run -> Spec tests run = do describe "Tests that are common to all backends" $ do @@ -1459,8 +1484,6 @@ tests run = do testRenderSql run - - insert' :: ( Functor m , BaseBackend backend ~ PersistEntityBackend val , PersistStore backend