packages feed

persistent-qq 2.9.1.1 → 2.9.2

raw patch · 5 files changed

+103/−45 lines, 5 filesdep +bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring

API changes (from Hackage documentation)

+ Database.Persist.Sql.Raw.QQ: class ToRow a
+ Database.Persist.Sql.Raw.QQ: instance (Database.Persist.Class.PersistField.PersistField a, Database.Persist.Class.PersistField.PersistField b) => Database.Persist.Sql.Raw.QQ.ToRow (a, b)
+ Database.Persist.Sql.Raw.QQ: instance (Database.Persist.Class.PersistField.PersistField a, Database.Persist.Class.PersistField.PersistField b, Database.Persist.Class.PersistField.PersistField c) => Database.Persist.Sql.Raw.QQ.ToRow (a, b, c)
+ Database.Persist.Sql.Raw.QQ: instance (Database.Persist.Class.PersistField.PersistField a, Database.Persist.Class.PersistField.PersistField b, Database.Persist.Class.PersistField.PersistField c, Database.Persist.Class.PersistField.PersistField d) => Database.Persist.Sql.Raw.QQ.ToRow (a, b, c, d)
+ Database.Persist.Sql.Raw.QQ: instance (Database.Persist.Class.PersistField.PersistField a, Database.Persist.Class.PersistField.PersistField b, Database.Persist.Class.PersistField.PersistField c, Database.Persist.Class.PersistField.PersistField d, Database.Persist.Class.PersistField.PersistField e) => Database.Persist.Sql.Raw.QQ.ToRow (a, b, c, d, e)
+ Database.Persist.Sql.Raw.QQ: instance Database.Persist.Class.PersistField.PersistField a => Database.Persist.Sql.Raw.QQ.ToRow (Database.Persist.Sql.Types.Single a)
+ Database.Persist.Sql.Raw.QQ: toRow :: ToRow a => a -> NonEmpty PersistValue

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for persistent-qq +## 2.9.2++* Add interpolation support for multirow VALUES syntax (`*{rows}`) [#1111](https://github.com/yesodweb/persistent/pull/1111)+ ## 2.9.1.1  * Compatibility with latest persistent-template for test suite [#1002](https://github.com/yesodweb/persistent/pull/1002/files)
persistent-qq.cabal view
@@ -7,7 +7,7 @@ -- hash: bef2b585278826bc60fe813d4918833dc06cec43f2a8e4567190448ccfdee163  name:           persistent-qq-version:        2.9.1.1+version:        2.9.2 synopsis:       Provides a quasi-quoter for raw SQL for persistent description:    Please see README and API docs at <http://www.stackage.org/package/persistent>. category:       Database, Yesod@@ -70,4 +70,5 @@     , template-haskell     , text     , unliftio+    , bytestring   default-language: Haskell2010
src/Database/Persist/Sql/Raw/QQ.hs view
@@ -25,13 +25,16 @@     , sqlQQ     , executeQQ     , executeCountQQ+    , ToRow(..)     ) where  import Prelude import Control.Arrow (first, second) import Control.Monad.Reader (ask)-import qualified Data.List.NonEmpty (toList)-import Data.Text (pack, unpack, intercalate)+import           Data.List.NonEmpty (NonEmpty(..), (<|))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.List as List+import Data.Text (Text, pack, unpack, intercalate) import Data.Maybe (fromMaybe, Maybe(..)) import Data.Monoid (mempty, (<>)) import qualified Language.Haskell.TH as TH@@ -42,17 +45,36 @@ import Database.Persist import Database.Persist.Sql +class ToRow a where+    toRow :: a -> NonEmpty PersistValue++instance PersistField a => ToRow (Single a) where+    toRow (Single a) = toPersistValue a :| []++instance (PersistField a, PersistField b) => ToRow (a, b) where+    toRow (a, b) = toPersistValue a <| toRow (Single b)++instance (PersistField a, PersistField b, PersistField c) => ToRow (a, b, c) where+    toRow (a, b, c) = toPersistValue a <| toRow (b, c)++instance (PersistField a, PersistField b, PersistField c, PersistField d) => ToRow (a, b, c, d) where+    toRow (a, b, c, d) = toPersistValue a <| toRow (b, c, d)++instance (PersistField a, PersistField b, PersistField c, PersistField d, PersistField e) => ToRow (a, b, c, d, e) where+    toRow (a, b, c, d, e) = toPersistValue a <| toRow (b, c, d, e)+ data Token   = Literal String   | Value String   | Values String+  | Rows String   | TableName String   | ColumnName String   deriving Show  parseHaskell :: (String -> Token) -> String -> String -> [Token] parseHaskell cons = go-    where+  where     go a []          = [Literal (reverse a)]     go a ('\\':x:xs) = go (x:a) xs     go a ['\\']      = go ('\\':a) []@@ -65,10 +87,38 @@ parseStr a ['\\']       = parseStr ('\\':a) [] parseStr a ('#':'{':xs) = Literal (reverse a) : parseHaskell Value      [] xs parseStr a ('%':'{':xs) = Literal (reverse a) : parseHaskell Values     [] xs+parseStr a ('*':'{':xs) = Literal (reverse a) : parseHaskell Rows       [] xs parseStr a ('^':'{':xs) = Literal (reverse a) : parseHaskell TableName  [] xs parseStr a ('@':'{':xs) = Literal (reverse a) : parseHaskell ColumnName [] xs parseStr a (x:xs)       = parseStr (x:a) xs +interpolateValues :: PersistField a => NonEmpty a -> (Text, [[PersistValue]]) -> (Text, [[PersistValue]])+interpolateValues xs =+    first (mkPlaceholders values <>) .+    second (NonEmpty.toList values :)+  where+    values = NonEmpty.map toPersistValue xs++interpolateRows :: ToRow a => NonEmpty a -> (Text, [[PersistValue]]) -> (Text, [[PersistValue]])+interpolateRows xs =+    first (placeholders <>)+  . second (values :)+  where+    rows :: NonEmpty (NonEmpty PersistValue)+    rows = NonEmpty.map toRow xs++    n = NonEmpty.length rows+    placeholders = n `timesCommaSeparated` mkPlaceholders (NonEmpty.head rows)+    values = List.concatMap NonEmpty.toList $ NonEmpty.toList rows++mkPlaceholders :: NonEmpty a -> Text+mkPlaceholders values = "(" <> n `timesCommaSeparated` "?" <> ")"+  where+    n = NonEmpty.length values++timesCommaSeparated :: Int -> Text -> Text+timesCommaSeparated n = intercalate "," . replicate n+ makeExpr :: TH.ExpQ -> [Token] -> TH.ExpQ makeExpr fun toks = do     TH.infixE@@ -76,7 +126,7 @@         ([| (=<<) |])         (Just $ go toks) -    where+  where     go :: [Token] -> TH.ExpQ     go [] = [| return (mempty, []) |]     go (Literal a:xs) =@@ -89,12 +139,12 @@             (go xs)     go (Values a:xs) =         TH.appE-            [| let a' = Data.List.NonEmpty.toList $(reifyExp a) in-               fmap $-                 first (("(" <> intercalate ", " (replicate (length a') "?") <> ")") <>) .-                 second (map toPersistValue a' :)-             |]+            [| fmap $ interpolateValues $(reifyExp a) |]             (go xs)+    go (Rows a:xs) =+        TH.appE+            [| fmap $ interpolateRows $(reifyExp a) |]+            (go xs)     go (ColumnName a:xs) = do         colN <- TH.newName "field"         TH.infixE@@ -154,33 +204,34 @@ -- -- @ -- Category---   rgt Int---   lft Int+--   rgt Int default=0+--   lft Int default=0 --   nam Text -- @ -- -- We can now execute this raw query: -- -- @--- let lft = 10 :: Int---     rgt = 20 :: Int---     width = rgt - lft---     nams = "first" :| ["second", "third"]+-- let lft = 10 :: `Int`+--     rgt = 20 :: `Int`+--     width = rgt `-` lft+--     nams = "first" `:|` ["second", "third"] --  in [sqlQQ|---       DELETE FROM ^{Category} WHERE @{CategoryLft} BETWEEN #{lft} AND #{rgt};---       UPDATE category SET @{CategoryRgt} = @{CategoryRgt} - #{width} WHERE @{CategoryRgt} > #{rgt};---       UPDATE category SET @{CategoryLft} = @{CategoryLft} - #{width} WHERE @{CategoryLft} > #{rgt};---       SELECT ?? FROM ^{Category} WHERE ^{Category}.@{CategoryNam} IN %{nams};+--       DELETE FROM ^{Category} WHERE \@{CategoryLft} BETWEEN \#{lft} AND \#{rgt};+--       UPDATE category SET \@{CategoryRgt} = \@{CategoryRgt} - \#{width} WHERE \@{CategoryRgt} > \#{rgt};+--       UPDATE category SET \@{CategoryLft} = \@{CategoryLft} - \#{width} WHERE \@{CategoryLft} > \#{rgt};+--       SELECT ?? FROM ^{Category} WHERE ^{Category}.\@{CategoryNam} IN %{nams};+--       INSERT INTO ^{Category}(\@{CategoryNam}) VALUES *{`Single` `<$>` nams}; --     |] -- @ ----- @^{TableName}@ looks up the table's name and escapes it, @\@{ColumnName}@--- looks up the column's name and properly escapes it, @#{value}@ inserts--- the value via the usual parameter substitution mechanism and @%{values}@--- inserts comma separated values (of a 'Data.List.NonEmpty.NonEmpty' list).+-- - @^{TableName}@ looks up the table's name and escapes it+-- - @\@{ColumnName}@ looks up the column's name and properly escapes it+-- - @#{value}@ inserts the value via the usual parameter substitution mechanism+-- - @%{values}@ inserts comma separated values (of a 'Data.List.NonEmpty.NonEmpty' list) (since 2.9.1)+-- - @*{rows}@ inserts a 'Data.List.NonEmpty.NonEmpty' list of tuples for use with a multirow @INSERT@ statement (since 2.9.2) -- -- @since 2.9.0--- @%{values}@ was added with 2.9.1 sqlQQ :: QuasiQuoter sqlQQ = makeQQ [| rawSql |] 
test/PersistentTestModels.hs view
@@ -102,6 +102,7 @@     !yes Int     ~no Int     def Int+ |]  deriving instance Show (BackendKey backend) => Show (PetGeneric backend)
test/Spec.hs view
@@ -2,15 +2,16 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TypeFamilies #-} -import Control.Monad.Logger+import Control.Monad.Logger (LoggingT, runLoggingT) import Control.Monad.Trans.Resource import Control.Monad.Reader+import qualified Data.ByteString.Char8 as B import Data.List.NonEmpty (NonEmpty(..))+import Data.List (sort) import Data.Text (Text)-import System.Log.FastLogger+import System.Log.FastLogger (fromLogStr) import Test.Hspec import Test.HUnit ((@?=))-import UnliftIO  import Database.Persist.Sql import Database.Persist.Sql.Raw.QQ@@ -19,33 +20,25 @@ import PersistentTestModels  main :: IO ()-main = hspec specs+main = hspec spec  _debugOn :: Bool _debugOn = False -sqlite_database_file :: Text-sqlite_database_file = "testdb.sqlite3"-sqlite_database :: SqliteConnectionInfo-sqlite_database = mkSqliteConnectionInfo sqlite_database_file--runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m ()+runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) a -> m a runConn f = do-  let debugPrint = _debugOn-  let printDebug = if debugPrint then print . fromLogStr else void . return+  let printDebug = when _debugOn . B.putStrLn . fromLogStr   flip runLoggingT (\_ _ _ s -> printDebug s) $ do-    _ <- withSqlitePoolInfo sqlite_database 1 $ runSqlPool f-    return ()+    withSqliteConn ":memory:" $ runSqlConn f -db :: SqlPersistT (LoggingT (ResourceT IO)) () -> IO ()+db :: SqlPersistT (LoggingT (ResourceT IO)) a -> IO a db actions = do   runResourceT $ runConn $ do-      runMigration testMigrate-      actions-      transactionUndo+      _ <- runMigrationSilent testMigrate+      actions <* transactionUndo -specs :: Spec-specs = describe "persistent-qq" $ do+spec :: Spec+spec = describe "persistent-qq" $ do     it "sqlQQ/?-?" $ db $ do         ret <- [sqlQQ| SELECT #{2 :: Int}+#{2 :: Int} |]         liftIO $ ret @?= [Single (4::Int)]@@ -153,3 +146,11 @@         |]         liftIO $ ret @?= [ (Entity p1k p1)                          , (Entity p3k p3) ]++    it "sqlQQ/rows syntax" $ do+        let rows :: NonEmpty (Text, Int)+            rows = ("Mathias", 23) :| ("Norbert", 44) : ("Cassandra", 19) : []+        db $ do+            [executeQQ|INSERT INTO ^{Person} (@{PersonName}, @{PersonAge}) VALUES *{rows}|]+            map unSingle <$> [sqlQQ|SELECT @{PersonName} FROM ^{Person} ORDER BY @{PersonName}|]+        `shouldReturn` sort ["Mathias", "Norbert", "Cassandra" :: Text]