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 +5/−0
- postgresql-simple-named.cabal +3/−3
- src/PgNamed.hs +84/−24
- test/Spec.hs +33/−2
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{..}