{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module OpenTelemetry.Instrumentation.PostgresqlSimple ( staticConnectionAttributes {- -- * Queries that return results query , query_ -- ** Queries taking parser as argument , queryWith , queryWith_ -- * Queries that stream results , fold , foldWithOptions , fold_ , foldWithOptions_ , forEach , forEach_ , returning -- ** Queries that stream results taking a parser as an argument , foldWith , foldWithOptionsAndParser , foldWith_ , foldWithOptionsAndParser_ , forEachWith , forEachWith_ , returningWith -- * Statements that do not return results , execute , execute_ , executeMany -- * Reexported functions , module X -} ) where import Control.Monad.IO.Class import qualified Database.PostgreSQL.Simple as Simple import Database.PostgreSQL.Simple as X hiding ( query , query_ , queryWith , queryWith_ , fold , foldWithOptions , fold_ , foldWithOptions_ , forEach , forEach_ , returning , foldWith , foldWithOptionsAndParser , foldWith_ , foldWithOptionsAndParser_ , forEachWith , forEachWith_ , returningWith , execute , execute_ , executeMany ) import Database.PostgreSQL.Simple.Internal ( Connection(Connection, connectionHandle) ) import qualified Data.ByteString.Char8 as C import qualified Database.PostgreSQL.LibPQ as LibPQ import qualified Data.Text as T import OpenTelemetry.Trace.Core import OpenTelemetry.Trace.Monad import OpenTelemetry.Resource ((.=), (.=?)) import Data.Maybe (catMaybes) import qualified Data.Text.Encoding as TE import Text.Read (readMaybe) import Data.IP import qualified Database.PostgreSQL.Simple.FromRow as Simple import Data.Int (Int64) import UnliftIO import Data.Text (Text) import GHC.Stack staticConnectionAttributes :: MonadIO m => Connection -> m [(T.Text, Attribute)] staticConnectionAttributes Connection{connectionHandle} = liftIO $ do (mDb, mUser, mHost, mPort) <- withMVar connectionHandle $ \pqConn -> do (,,,) <$> LibPQ.db pqConn <*> LibPQ.user pqConn <*> LibPQ.host pqConn <*> LibPQ.port pqConn pure $ ("db.system", toAttribute ("postgresql" :: T.Text)) : catMaybes [ "db.user" .=? (TE.decodeUtf8 <$> mUser) , "db.name" .=? (TE.decodeUtf8 <$> mDb) , "net.peer.port" .=? (do port <- TE.decodeUtf8 <$> mPort (readMaybe $ T.unpack port) :: Maybe Int ) , case (readMaybe . C.unpack) =<< mHost of Nothing -> "net.peer.name" .=? (TE.decodeUtf8 <$> mHost) Just (IPv4 ipv4) -> "net.peer.ip" .= T.pack (show ipv4) Just (IPv6 ipv6) -> "net.peer.ip" .= T.pack (show ipv6) ] {- -- | Perform a @SELECT@ or other SQL query that is expected to return -- results. All results are retrieved and converted before this -- function returns. -- -- When processing large results, this function will consume a lot of -- client-side memory. Consider using 'fold' instead. -- -- Exceptions that may be thrown: -- -- * 'FormatError': the query string could not be formatted correctly. -- -- * 'QueryError': the result contains no columns (i.e. you should be -- using 'execute' instead of 'query'). -- -- * 'ResultError': result conversion failed. -- -- * 'SqlError': the postgresql backend returned an error, e.g. -- a syntax or type error, or an incorrect table or column name. query :: (MonadIO m, MonadGetContext m, ToRow q, FromRow r) => Connection -> Query -> q -> m [r] query = liftIO $ Simple.query -- | A version of 'query' that does not perform query substitution. query_ :: (MonadIO m, MonadGetContext m, FromRow r) => Connection -> Query -> m [r] query_ = _ -- | A version of 'query' taking parser as argument queryWith :: (MonadIO m, MonadGetContext m, ToRow q) => Simple.RowParser r -> Connection -> Query -> q -> m [r] queryWith parser conn template qs = _ -- | A version of 'query_' taking parser as argument queryWith_ :: (MonadIO m, MonadGetContext m) => Simple.RowParser r -> Connection -> Query -> m [r] queryWith_ parser conn q = _ -- | Perform a @SELECT@ or other SQL query that is expected to return -- results. Results are streamed incrementally from the server, and -- consumed via a left fold. -- -- When dealing with small results, it may be simpler (and perhaps -- faster) to use 'query' instead. -- -- This fold is /not/ strict. The stream consumer is responsible for -- forcing the evaluation of its result to avoid space leaks. -- -- This is implemented using a database cursor. As such, this requires -- a transaction. This function will detect whether or not there is a -- transaction in progress, and will create a 'ReadCommitted' 'ReadOnly' -- transaction if needed. The cursor is given a unique temporary name, -- so the consumer may itself call fold. -- -- Exceptions that may be thrown: -- -- * 'FormatError': the query string could not be formatted correctly. -- -- * 'QueryError': the result contains no columns (i.e. you should be -- using 'execute' instead of 'query'). -- -- * 'ResultError': result conversion failed. -- -- * 'SqlError': the postgresql backend returned an error, e.g. -- a syntax or type error, or an incorrect table or column name. fold :: (MonadBracketError m, MonadLocalContext m, FromRow row, ToRow params) => Connection -> Query -> params -> a -> (a -> row -> m a) -> m a fold = _ -- | A version of 'fold' taking a parser as an argument foldWith :: (MonadBracketError m, MonadLocalContext m, ToRow params) => Simple.RowParser row -> Connection -> Query -> params -> a -> (a -> row -> m a) -> m a foldWith = _ -- | The same as 'fold', but this provides a bit more control over -- lower-level details. Currently, the number of rows fetched per -- round-trip to the server and the transaction mode may be adjusted -- accordingly. If the connection is already in a transaction, -- then the existing transaction is used and thus the 'transactionMode' -- option is ignored. foldWithOptions :: (MonadBracketError m, MonadLocalContext m, FromRow row, ToRow params) => FoldOptions -> Connection -> Query -> params -> a -> (a -> row -> m a) -> m a foldWithOptions opts = _ -- | A version of 'foldWithOptions' taking a parser as an argument foldWithOptionsAndParser :: (MonadBracketError m, MonadLocalContext m, ToRow params) => FoldOptions -> Simple.RowParser row -> Connection -> Query -> params -> a -> (a -> row -> m a) -> m a foldWithOptionsAndParser opts parser conn template qs a f = _ -- | A version of 'fold' that does not perform query substitution. fold_ :: (MonadBracketError m, MonadLocalContext m, FromRow r) => Connection -> Query -- ^ Query. -> a -- ^ Initial state for result consumer. -> (a -> r -> m a) -- ^ Result consumer. -> m a fold_ = _ -- | A version of 'fold_' taking a parser as an argument foldWith_ :: (MonadUnliftIO m, MonadBracketError m, MonadLocalContext m) => Simple.RowParser r -> Connection -> Query -> a -> (a -> r -> m a) -> m a foldWith_ = _ foldWithOptions_ :: (MonadUnliftIO m, MonadBracketError m, MonadLocalContext m, FromRow r) => FoldOptions -> Connection -> Query -- ^ Query. -> a -- ^ Initial state for result consumer. -> (a -> r -> m a) -- ^ Result consumer. -> m a foldWithOptions_ opts conn query' a f = Simple.foldWithOptions_ opts conn query' a f -- | A version of 'foldWithOptions_' taking a parser as an argument foldWithOptionsAndParser_ :: FoldOptions -> Simple.RowParser r -> Connection -> Query -- ^ Query. -> a -- ^ Initial state for result consumer. -> (a -> r -> IO a) -- ^ Result consumer. -> IO a foldWithOptionsAndParser_ opts parser conn query' a f = _ -- | A version of 'fold' that does not transform a state value. forEach :: (MonadUnliftIO m, MonadBracketError m, MonadLocalContext m, ToRow q, FromRow r) => Connection -> Query -- ^ Query template. -> q -- ^ Query parameters. -> (r -> m ()) -- ^ Result consumer. -> m () forEach = _ {-# INLINE forEach #-} -- | A version of 'forEach' taking a parser as an argument forEachWith :: (MonadBracketError m, MonadLocalContext m, ToRow q) => Simple.RowParser r -> Connection -> Query -> q -> (r -> m ()) -> m () forEachWith parser conn template qs = _ {-# INLINE forEachWith #-} -- | A version of 'forEach' that does not perform query substitution. forEach_ :: (MonadBracketError m, MonadLocalContext m, FromRow r) => Connection -> Query -- ^ Query template. -> (r -> m ()) -- ^ Result consumer. -> m () forEach_ = _ {-# INLINE forEach_ #-} forEachWith_ :: (MonadBracketError m, MonadLocalContext m) => Simple.RowParser r -> Connection -> Query -> (r -> m ()) -> m () forEachWith_ parser conn template = _ {-# INLINE forEachWith_ #-} -- | Execute @INSERT ... RETURNING@, @UPDATE ... RETURNING@, or other SQL -- query that accepts multi-row input and is expected to return results. -- Note that it is possible to write -- @'query' conn "INSERT ... RETURNING ..." ...@ -- in cases where you are only inserting a single row, and do not need -- functionality analogous to 'executeMany'. -- -- If the list of parameters is empty, this function will simply return @[]@ -- without issuing the query to the backend. If this is not desired, -- consider using the 'Values' constructor instead. -- -- Throws 'FormatError' if the query could not be formatted correctly. returning :: (MonadIO m, MonadGetContext m, ToRow q, FromRow r) => Connection -> Query -> [q] -> m [r] returning = _ -- | A version of 'returning' taking parser as argument returningWith :: (MonadIO m, MonadGetContext m, ToRow q) => Simple.RowParser r -> Connection -> Query -> [q] -> m [r] returningWith = _ -- | Execute an @INSERT@, @UPDATE@, or other SQL query that is not -- expected to return results. -- -- Returns the number of rows affected. -- -- Throws 'FormatError' if the query could not be formatted correctly, or -- a 'SqlError' exception if the backend returns an error. execute :: (MonadIO m, MonadGetContext m, ToRow q) => Connection -> Query -> q -> m Int64 execute conn template qs = _ -- | A version of 'execute' that does not perform query substitution. execute_ :: (MonadIO m, MonadGetContext m) => Connection -> Query -> m Int64 execute_ = _ -- | Execute a multi-row @INSERT@, @UPDATE@, or other SQL query that is not -- expected to return results. -- -- Returns the number of rows affected. If the list of parameters is empty, -- this function will simply return 0 without issuing the query to the backend. -- If this is not desired, consider using the 'Values' constructor instead. -- -- Throws 'FormatError' if the query could not be formatted correctly, or -- a 'SqlError' exception if the backend returns an error. -- -- For example, here's a command that inserts two rows into a table -- with two columns: -- -- @ -- executeMany c [sql| -- INSERT INTO sometable VALUES (?,?) -- |] [(1, \"hello\"),(2, \"world\")] -- @ -- -- Here's an canonical example of a multi-row update command: -- -- @ -- executeMany c [sql| -- UPDATE sometable -- SET y = upd.y -- FROM (VALUES (?,?)) as upd(x,y) -- WHERE sometable.x = upd.x -- |] [(1, \"hello\"),(2, \"world\")] -- @ executeMany :: (MonadIO m, MonadGetContext m, ToRow q) => Connection -> Query -> [q] -> m Int64 executeMany = _ -}