packages feed

postgresql-simple-named 0.0.0.0 → 0.0.1.0

raw patch · 4 files changed

+125/−29 lines, 4 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ PgNamed: queryWithNamed :: (MonadIO m, WithNamedError m) => RowParser res -> Connection -> Query -> [NamedParam] -> m [res]
+ PgNamed: withNamedArgs :: WithNamedError m => Query -> [NamedParam] -> m (Query, NonEmpty Action)

Files

CHANGELOG.md view
@@ -3,6 +3,11 @@ `postgresql-simple-named` uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 0.0.1.0 — Jul 23, 2019++* [#16](https://github.com/holmusk/postgresql-simple-named/issues/16):+  Implement `queryWithNamed`.+ ## 0.0.0.0 — Jul 11, 2019  * Initially created.
postgresql-simple-named.cabal view
@@ -1,13 +1,13 @@ cabal-version:       2.4 name:                postgresql-simple-named-version:             0.0.0.0+version:             0.0.1.0 synopsis:            Implementation of named parameters for `postgresql-simple` library description:     Implementation of named parameters for @postgresql-simple@ library.     .     Here is an exaple of how it could be used in your code:     .-    > queryNamed [sql|+    > queryNamed dbConnection [sql|     >     SELECT *     >     FROM table     >     WHERE foo = ?foo@@ -21,7 +21,7 @@ bug-reports:         https://github.com/Holmusk/postgresql-simple-named/issues license:             MPL-2.0 license-file:        LICENSE-author:              Dmitrii Kovanikov, Veronika Romashkina+author:              Veronika Romashkina, Dmitrii Kovanikov maintainer:          Holmusk <tech@holmusk.com> copyright:           2019 Holmusk category:            Database, PostgreSQL
src/PgNamed.hs view
@@ -1,6 +1,5 @@-{-# LANGUAGE DerivingStrategies        #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts          #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts   #-}  {- | Introduces named parameters for @postgresql-simple@ library. It uses @?@ question mark symbol as the indicator of the named parameter which@@ -9,12 +8,12 @@ Check out the example of usage:  @-'queryNamed' [sql|-    SELECT *-    FROM users-    WHERE foo = ?foo-      AND bar = ?bar-      AND baz = ?foo+'queryNamed' dbConnection [sql|+    __SELECT__ *+    __FROM__ users+    __WHERE__ foo = ?foo+      __AND__ bar = ?bar+      __AND__ baz = ?foo |] [ "foo" '=?' "fooBar"    , "bar" '=?' "barVar"    ]@@ -37,7 +36,11 @@           -- * Database querying functions with named parameters        , queryNamed+       , queryWithNamed        , executeNamed++         -- * Internal utils+       , withNamedArgs        ) where  import Control.Monad.Except (MonadError (throwError))@@ -54,6 +57,7 @@  import qualified Data.ByteString.Char8 as BS import qualified Database.PostgreSQL.Simple as PG+import qualified Database.PostgreSQL.Simple.FromRow as PG import qualified Database.PostgreSQL.Simple.ToField as PG import qualified Database.PostgreSQL.Simple.Types as PG @@ -67,7 +71,7 @@ data NamedParam = NamedParam     { namedParamName  :: !Name     , namedParamParam :: !PG.Action-    } deriving (Show)+    } deriving stock (Show)  -- | @PostgreSQL@ error type for named parameters. data PgNamedError@@ -77,7 +81,7 @@     | PgNoNames PG.Query     -- | Query contains an empty name.     | PgEmptyName PG.Query-  deriving (Eq)+    deriving stock (Eq)   -- | Type alias for monads that can throw errors of the 'PgNamedError' type.@@ -98,7 +102,7 @@ {- | This function takes query with named parameters specified like this:  @-SELECT name, user FROM users WHERE id = ?id+__SELECT__ name, user __FROM__ users __WHERE__ id = ?id @  and returns either the error or the query with all names replaced by@@ -157,10 +161,14 @@ So it can be used in creating the list of the named arguments:  @-queryNamed [sql|-  SELECT * FROM users WHERE foo = ?foo AND bar = ?bar AND baz = ?foo"-|] [ "foo" =? "fooBar"-   , "bar" =? "barVar"+'queryNamed' dbConnection [sql|+    __SELECT__ *+    __FROM__ users+    __WHERE__ foo = ?foo+      __AND__ bar = ?bar+      __AND__ baz = ?foo+|] [ "foo" '=?' "fooBar"+   , "bar" '=?' "barVar"    ] @ -}@@ -173,9 +181,10 @@ and expects a list of rows in return.  @-queryNamed dbConnection [sql|-    SELECT id FROM table-    WHERE foo = ?foo+'queryNamed' dbConnection [sql|+    __SELECT__ id+    __FROM__ table+    __WHERE__ foo = ?foo |] [ "foo" '=?' "bar" ] @ -}@@ -189,14 +198,59 @@     withNamedArgs qNamed params >>= \(q, actions) ->         liftIO $ PG.query conn q (toList actions) +{- | Queries the database with a given row parser, 'PG.Query', and named parameters+and expects a list of rows in return.++Sometimes there are multiple ways to parse tuples returned by PostgreSQL into+the same data type. However, it's not possible to implement multiple intances of+the 'PG.FromRow' typeclass (or any other typeclass).++Consider the following data type:++@+__data__ Person = Person+    { personName :: !Text+    , personAge  :: !(Maybe Int)+    }+@++We might want to parse values of the @Person@ data type in two ways:++1. Default by parsing all fields.+2. Parse only name and @age@ to 'Nothing'.++If you want to have multiple instances, you need to create @newtype@ for each+case. However, in some cases it might not be convenient to deal with newtypes+around large data types. So you can implement custom 'PG.RowParser' and use it+with 'queryWithNamed'.++@+'queryWithNamed' rowParser dbConnection [sql|+    __SELECT__ id+    __FROM__ table+    __WHERE__ foo = ?foo+|] [ "foo" '=?' "bar" ]+@+-}+queryWithNamed+    :: (MonadIO m, WithNamedError m)+    => PG.RowParser res -- ^ Custom defined row parser+    -> PG.Connection    -- ^ Database connection+    -> PG.Query         -- ^ Query with named parameters inside+    -> [NamedParam]     -- ^ The list of named parameters to be used in the query+    -> m [res]          -- ^ Resulting rows+queryWithNamed rowParser conn qNamed params =+    withNamedArgs qNamed params >>= \(q, actions) ->+        liftIO $ PG.queryWith rowParser conn q (toList actions)+ {- | Modifies the database with a given query and named parameters and expects a number of the rows affected.  @-executeNamed dbConnection [sql|-    UPDATE table-    SET foo = 'bar'-    WHERE id = ?id+'executeNamed' dbConnection [sql|+    __UPDATE__ table+    __SET__ foo = \'bar\'+    __WHERE__ id = ?id |] [ "id" '=?' someId ] @ -}@@ -210,7 +264,13 @@     withNamedArgs qNamed params >>= \(q, actions) ->         liftIO $ PG.execute conn q (toList actions) --- | Helper to use named parameters.+{- | Helper to use named parameters. Use it to implement named wrappers around+functions from @postgresql-simple@ library. If you think that the function is+useful, consider opening feature request to the @postgresql-simple-named@+library:++* https://github.com/Holmusk/postgresql-simple-named/issues+-} withNamedArgs     :: WithNamedError m     => PG.Query
test/Spec.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass     #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts   #-}  module Main (main) where @@ -10,10 +11,11 @@ import System.IO (hSetEncoding, stderr, stdout, utf8) import Test.Hspec (Spec, describe, hspec, it, shouldReturn) -import PgNamed (NamedParam, PgNamedError (..), queryNamed, (=?))+import PgNamed (NamedParam, PgNamedError (..), queryNamed, queryWithNamed, (=?))  import qualified Data.Pool as Pool import qualified Database.PostgreSQL.Simple as Sql+import qualified Database.PostgreSQL.Simple.FromRow as Sql   connectionSettings :: ByteString@@ -36,6 +38,8 @@         emptyName `shouldReturn` Left (PgEmptyName "SELECT ?foo, ?")     it "named parameters are parsed and passed correctly" $         queryTestValue `shouldReturn` Right (TestValue 42 42 "baz")+    it "named parameters are parsed correctly by user defined row parser" $+        queryWithTestValue `shouldReturn` Right (TestValue 42 42 "baz")   where     missingNamedParam :: IO (Either PgNamedError TestValue)     missingNamedParam = run "SELECT ?foo, ?bar" ["foo" =? True]@@ -52,9 +56,29 @@         , "txtVal" =? ("baz" :: ByteString)         ] +    queryWithTestValue :: IO (Either PgNamedError TestValue)+    queryWithTestValue = runWith testValueParser "SELECT ?intVal, ?intVal, ?txtVal"+        [ "intVal" =? (42 :: Int)+        , "txtVal" =? ("baz" :: ByteString)+        ]+     run :: Sql.Query -> [NamedParam] -> IO (Either PgNamedError TestValue)-    run q params = runNamedQuery $ Pool.withResource dbPool (\conn -> queryNamed conn q params)+    run = callQuery queryNamed +    runWith+        :: Sql.RowParser TestValue+        -> Sql.Query+        -> [NamedParam]+        -> IO (Either PgNamedError TestValue)+    runWith rowParser = callQuery (queryWithNamed rowParser)++    callQuery+        :: (Sql.Connection -> Sql.Query -> [NamedParam] -> ExceptT PgNamedError IO [TestValue])+        -> Sql.Query+        -> [NamedParam]+        -> IO (Either PgNamedError TestValue)+    callQuery f q params = runNamedQuery $ Pool.withResource dbPool (\conn -> f conn q params)+ runNamedQuery :: ExceptT PgNamedError IO [TestValue] -> IO (Either PgNamedError TestValue) runNamedQuery = fmap (second head) . runExceptT @@ -64,3 +88,10 @@     , txtVal  :: !ByteString     } deriving stock (Show, Eq, Generic)       deriving anyclass (Sql.FromRow, Sql.ToRow)++testValueParser :: Sql.RowParser TestValue+testValueParser = do+    intVal1 <- Sql.field+    intVal2 <- Sql.field+    txtVal  <- Sql.field+    return TestValue{..}