module Database.PostgreSQL.Simple.Internal where
import Prelude hiding (catch)
import Control.Applicative
import Control.Exception
import Control.Concurrent.MVar
import Data.ByteString(ByteString)
import qualified Data.IntMap as IntMap
import Data.String
import Data.Typeable
import Data.Word
import Database.PostgreSQL.LibPQ(Oid(..))
import qualified Database.PostgreSQL.LibPQ as PQ
import Database.PostgreSQL.Simple.BuiltinTypes (BuiltinType)
import System.IO.Unsafe (unsafePerformIO)
data Field = Field {
result :: PQ.Result
, column :: PQ.Column
, typename :: ByteString
}
name :: Field -> Maybe ByteString
name Field{..} = unsafePerformIO (PQ.fname result column)
tableOid :: Field -> PQ.Oid
tableOid Field{..} = unsafePerformIO (PQ.ftable result column)
tableColumn :: Field -> Int
tableColumn Field{..} = fromCol (unsafePerformIO (PQ.ftablecol result column))
where
fromCol (PQ.Col x) = fromIntegral x
format :: Field -> PQ.Format
format Field{..} = unsafePerformIO (PQ.fformat result column)
typeOid :: Field -> PQ.Oid
typeOid Field{..} = unsafePerformIO (PQ.ftype result column)
data Connection = Connection {
connectionHandle :: MVar (Maybe PQ.Connection)
, connectionObjects :: MVar (IntMap.IntMap ByteString)
}
data SqlType
= Builtin BuiltinType
| Other Oid
data SqlError = SqlError {
sqlState :: ByteString
, sqlNativeError :: Int
, sqlErrorMsg :: ByteString
} deriving (Show, Typeable)
instance Exception SqlError
data ConnectInfo = ConnectInfo {
connectHost :: String
, connectPort :: Word16
, connectUser :: String
, connectPassword :: String
, connectDatabase :: String
} deriving (Eq,Read,Show,Typeable)
defaultConnectInfo :: ConnectInfo
defaultConnectInfo = ConnectInfo {
connectHost = "127.0.0.1"
, connectPort = 5432
, connectUser = "postgres"
, connectPassword = ""
, connectDatabase = ""
}
connect :: ConnectInfo -> IO Connection
connect = connectPostgreSQL . postgreSQLConnectionString
connectPostgreSQL :: ByteString -> IO Connection
connectPostgreSQL connstr = do
conn <- PQ.connectdb connstr
connectionHandle <- newMVar (Just conn)
connectionObjects <- newMVar (IntMap.empty)
return Connection{..}
postgreSQLConnectionString :: ConnectInfo -> ByteString
postgreSQLConnectionString connectInfo = fromString connstr
where
connstr = str "host=" connectHost
$ num "port=" connectPort
$ str "user=" connectUser
$ str "password=" connectPassword
$ str "dbname=" connectDatabase
$ []
str name field
| null value = id
| otherwise = (name ++) . quote value . space
where value = field connectInfo
num name field
| value <= 0 = id
| otherwise = (name ++) . (show value ++) . space
where value = field connectInfo
quote str rest = '\'' : foldr delta ('\'' : rest) str
where
delta c cs = case c of
'\\' -> '\\' : '\\' : cs
'\'' -> '\\' : '\'' : cs
_ -> c : cs
space [] = []
space xs = ' ':xs
oid2int :: Oid -> Int
oid2int (Oid x) = fromIntegral x
exec :: Connection
-> ByteString
-> IO PQ.Result
exec conn sql =
withConnection conn $ \h -> do
mres <- PQ.exec h sql
case mres of
Nothing -> do
msg <- maybe "execute error" id <$> PQ.errorMessage h
throwIO $ SqlError { sqlNativeError = 1
, sqlErrorMsg = msg
, sqlState = "" }
Just res -> do
return res
disconnectedError = SqlError {
sqlNativeError = 1,
sqlErrorMsg = "connection disconnected",
sqlState = ""
}
withConnection :: Connection -> (PQ.Connection -> IO a) -> IO a
withConnection Connection{..} m = do
withMVar connectionHandle $ \h -> do
case h of
Just h -> m h
Nothing -> throwIO disconnectedError
close :: Connection -> IO ()
close Connection{..} = do
mconn <- takeMVar connectionHandle
case mconn of
Just conn -> PQ.finish conn
Nothing -> return ()
`finally` putMVar connectionHandle Nothing
data RawResult = RawResult { rawField :: Field, rawData :: Maybe ByteString }