postgresql-query-3.10.0: src/Database/PostgreSQL/Query/Functions.hs
module Database.PostgreSQL.Query.Functions
( -- * Raw query execution
pgQuery
, pgQueryWithMasker
, pgExecute
, pgExecuteWithMasker
-- * Transactions
, pgWithTransaction
, pgWithSavepoint
, pgWithTransactionMode
, pgWithTransactionModeRetry
, pgWithTransactionSerializable
-- * Auxiliary
, pgRepsertRow
) where
import Data.Int ( Int64 )
import Database.PostgreSQL.Query.Import
import Database.PostgreSQL.Query.Internal
import Database.PostgreSQL.Query.SqlBuilder
import Database.PostgreSQL.Query.TH
import Database.PostgreSQL.Query.Types
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.Transaction
import qualified Data.Text.Encoding as T
{- | Execute query generated by 'SqlBuilder'. Typical use case:
@
let userName = "Vovka Erohin" :: Text
pgQuery [sqlExp| SELECT id, name FROM users WHERE name = #{userName}|]
@
Or
@
let userName = "Vovka Erohin" :: Text
pgQuery $ Qp "SELECT id, name FROM users WHERE name = ?" [userName]
@
Which is almost the same. In both cases proper value escaping is performed
so you stay protected from sql injections.
-}
pgQuery
:: (MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack)
=> q
-> m [r]
pgQuery = withFrozenCallStack $ pgQueryWithMasker defaultLogMasker
-- | Execute arbitrary query and return count of affected rows
pgExecute
:: (MonadPostgres m, ToSqlBuilder q, HasCallStack)
=> q
-> m Int64
pgExecute = withFrozenCallStack $ pgExecuteWithMasker defaultLogMasker
pgQueryWithMasker
:: (MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack)
=> LogMasker
-> q
-> m [r]
pgQueryWithMasker masker q = withFrozenCallStack $ withPGConnection $ \c -> do
(queryBs, logBs) <- liftBase $ runSqlBuilder c masker $ toSqlBuilder q
logDebug $ T.decodeUtf8 logBs
liftBase $ query_ c queryBs
pgExecuteWithMasker
:: (MonadPostgres m, ToSqlBuilder q, HasCallStack)
=> LogMasker
-> q
-> m Int64
pgExecuteWithMasker masker q = withFrozenCallStack $ withPGConnection $ \c -> do
(queryBs, logBs) <- liftBase $ runSqlBuilder c masker $ toSqlBuilder q
logDebug $ T.decodeUtf8 logBs
liftBase $ execute_ c queryBs
-- | Execute all queries inside one transaction. Rollback transaction on exceptions
pgWithTransaction
:: (HasPostgres m, MonadBaseControl IO m, TransactionSafe m, HasCallStack)
=> (HasCallStack => m a)
-> m a
pgWithTransaction action = withPGConnection $ \con -> do
control $ \runInIO -> do
withTransaction con $ runInIO action
-- | Same as `pgWithTransaction` but executes queries inside savepoint
pgWithSavepoint
:: (HasPostgres m, MonadBaseControl IO m, TransactionSafe m, HasCallStack)
=> (HasCallStack => m a)
-> m a
pgWithSavepoint action = withPGConnection $ \con -> do
control $ \runInIO -> do
withSavepoint con $ runInIO action
-- | Wrapper for 'withTransactionMode': Execute an action inside a SQL
-- transaction with a given transaction mode.
pgWithTransactionMode
:: (HasPostgres m, MonadBaseControl IO m, TransactionSafe m, HasCallStack)
=> TransactionMode
-> (HasCallStack => m a)
-> m a
pgWithTransactionMode tmode ma = withPGConnection $ \con -> do
control $ \runInIO -> do
withTransactionMode tmode con $ runInIO ma
-- | Wrapper for 'withTransactionModeRetry': Like 'pgWithTransactionMode',
-- but also takes a custom callback to determine if a transaction
-- should be retried if an SqlError occurs. If the callback returns
-- True, then the transaction will be retried. If the callback returns
-- False, or an exception other than an SqlError occurs then the
-- transaction will be rolled back and the exception rethrown.
pgWithTransactionModeRetry
:: (HasPostgres m, MonadBaseControl IO m, TransactionSafe m, HasCallStack)
=> TransactionMode
-> (SqlError -> Bool)
-> (HasCallStack => m a)
-> m a
pgWithTransactionModeRetry tmode epred ma = withPGConnection $ \con -> do
control $ \runInIO -> do
withTransactionModeRetry tmode epred con $ runInIO ma
-- | Wrapper for 'withTransactionSerializable': Execute an action
-- inside of a 'Serializable' transaction. If a serialization failure
-- occurs, roll back the transaction and try again. Be warned that
-- this may execute the IO action multiple times.
--
-- A Serializable transaction creates the illusion that your program
-- has exclusive access to the database. This means that, even in a
-- concurrent setting, you can perform queries in sequence without
-- having to worry about what might happen between one statement and
-- the next.
pgWithTransactionSerializable
:: (HasPostgres m, MonadBaseControl IO m, TransactionSafe m)
=> (HasCallStack => m a)
-> m a
pgWithTransactionSerializable ma = withPGConnection $ \con -> do
control $ \runInIO -> do
withTransactionSerializable con $ runInIO ma
{- | Perform repsert of the same row, first trying "update where" then
"insert" with concatenated fields. Which means that if you run
@
pgRepsertRow "emails" (MR [("user_id", mkValue uid)]) (MR [("email", mkValue email)])
@
Then firstly will be performed
@
UPDATE "emails" SET email = 'foo@bar.com' WHERE "user_id" = 1234
@
And if no one row is affected (which is returned by 'pgExecute'), then
@
INSERT INTO "emails" ("user_id", "email") VALUES (1234, 'foo@bar.com')
@
will be performed
-}
pgRepsertRow
:: ( MonadPostgres m, MonadLogger m
, ToMarkedRow wrow, ToMarkedRow urow, HasCallStack)
=> FN -- ^ Table name
-> wrow -- ^ where condition
-> urow -- ^ update row
-> m ()
pgRepsertRow tname wrow urow = do
let wmr = toMarkedRow wrow
aff <- pgExecute $ updateTable tname urow
[sqlExp|WHERE ^{mrToBuilder "AND" wmr}|]
when (aff == 0) $ do
let umr = toMarkedRow urow
imr = wmr <> umr
_ <- pgExecute $ insertInto tname imr
return ()