packages feed

mysql (empty) → 0.1.0.0

raw patch · 7 files changed

+1330/−0 lines, 7 filesdep +basedep +bytestringdep +containersbuild-type:Customsetup-changed

Dependencies added: base, bytestring, containers

Files

+ Database/MySQL/Base.hs view
@@ -0,0 +1,543 @@+{-# LANGUAGE DeriveDataTypeable, ForeignFunctionInterface, RecordWildCards #-}++-- |+-- Module:      Database.MySQL.Base+-- Copyright:   (c) 2011 MailRank, Inc.+-- License:     BSD3+-- Maintainer:  Bryan O'Sullivan <bos@mailrank.com>+-- Stability:   experimental+-- Portability: portable+--+-- A low-level client library for the MySQL database, implemented as+-- bindings to the C @mysqlclient@ API.++module Database.MySQL.Base+    (+    -- * Licensing+    -- $license+    -- * Resource management+    -- $mgmt+    -- * Types+      ConnectInfo(..)+    , SSLInfo(..)+    , Seconds+    , Protocol(..)+    , Option(..)+    , defaultConnectInfo+    , defaultSSLInfo+    , Connection+    , Result+    , Field+    , Type(..)+    , Row+    , MySQLError(errFunction, errNumber, errMessage)+    -- * Connection management+    , connect+    , close+    , autocommit+    , ping+    , changeUser+    , selectDB+    , setCharacterSet+    -- ** Connection information+    , threadId+    , serverInfo+    , hostInfo+    , protocolInfo+    , characterSet+    , sslCipher+    , serverStatus+    -- * Querying+    , query+    -- ** Escaping+    , escape+    -- ** Results+    , fieldCount+    , affectedRows+    -- * Working with results+    , isResultValid+    , freeResult+    , storeResult+    , useResult+    , fetchRow+    , fetchFields+    , dataSeek+    , rowSeek+    , rowTell+    -- ** Multiple results+    , nextResult+    -- * Transactions+    , commit+    , rollback+    -- * General information+    , clientInfo+    , clientVersion+    ) where++import Control.Applicative ((<$>), (<*>))+import Control.Exception (Exception, throw)+import Control.Monad (forM_, unless, when)+import Data.ByteString.Char8 ()+import Data.ByteString.Internal (ByteString, create, createAndTrim, memcpy)+import Data.ByteString.Unsafe (unsafeUseAsCStringLen)+import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef, writeIORef)+import Data.Int (Int64)+import Data.List (foldl')+import Data.Typeable (Typeable)+import Data.Word (Word, Word16)+import Database.MySQL.Base.C+import Database.MySQL.Base.Types+import Foreign.C.String (CString, peekCString, withCString)+import Foreign.C.Types (CULong)+import Foreign.Concurrent (newForeignPtr)+import Foreign.ForeignPtr hiding (newForeignPtr)+import Foreign.Marshal.Array (peekArray)+import Foreign.Ptr (Ptr, castPtr, nullPtr)+import System.IO.Unsafe (unsafePerformIO)+import System.Mem.Weak (Weak, deRefWeak, mkWeakPtr)++-- $license+--+-- /Important licensing note/: This library is BSD-licensed under the+-- terms of the MySQL FOSS License Exception+-- <http://www.mysql.com/about/legal/licensing/foss-exception/>.+--+-- Since this library links against the GPL-licensed @mysqlclient@+-- library, a non-open-source application that uses it /may/ be+-- subject to the terms of the GPL.++-- $mgmt+--+-- Our rules for managing 'Connection' and 'Result' values are+-- unfortunately complicated, thanks to MySQL's lifetime rules.+--+-- At the C @libmysqlclient@ level, a single @MYSQL@ connection may+-- cause multiple @MYSQL_RES@ result values to be created over the+-- course of multiple queries, but only one of these @MYSQL_RES@+-- values may be alive at a time.  The programmer is responsible for+-- knowing when to call @mysql_free_result@.+--+-- Meanwhile, up in Haskell-land, we'd like both 'Connection' and+-- 'Result' values to be managed either manually or automatically. In+-- particular, we want finalizers to tidy up after a messy programmer,+-- and we'd prefer it if people didn't need to be mindful of calling+-- @mysql_free_result@. This means that we must wrestle with the+-- lifetime rules. An obvious approach would be to use some monad and+-- type magic to enforce those rules, but then we'd end up with an+-- awkward API.+--+-- Instead, we allow 'Result' values to stay alive for arbitrarily+-- long times, while preserving the right to mark them as+-- invalid. When a @Result@ is marked invalid, its associated+-- @MYSQL_RES@ is freed, and can no longer be used.+--+-- Since all functions over @Result@ values are in the 'IO' monad, we+-- don't risk disrupting pure code by introducing this notion of+-- invalidity. If code tries to use an invalid @Result@, a+-- 'MySQLError' will be thrown. This should /not/ occur in normal+-- code, so there should be no need to use 'isResultValid' to test a+-- @Result@ for validity.+--+-- Each of the following functions will invalidate a 'Result':+--+-- * 'close'+--+-- * 'freeResult'+--+-- * 'nextResult'+--+-- * 'storeResult'+--+-- * 'useResult'+--+-- A 'Result' must be able to keep a 'Connection' alive so that a+-- streaming @Result@ constructed by 'useResult' can continue to pull+-- data from the server, but a @Connection@ must (a) be able to cause+-- the @MYSQL_RES@ behind a @Result@ to be deleted at a moment's notice,+-- while (b) not artificially prolonging the life of either the @Result@+-- or its @MYSQL_RES@.++data ConnectInfo = ConnectInfo {+      connectHost :: String+    , connectPort :: Word16+    , connectUser :: String+    , connectPassword :: String+    , connectDatabase :: String+    , connectOptions :: [Option]+    , connectPath :: FilePath+    , connectSSL :: Maybe SSLInfo+    } deriving (Eq, Read, Show, Typeable)++data SSLInfo = SSLInfo {+      sslKey :: FilePath+    , sslCert :: FilePath+    , sslCA :: FilePath+    , sslCAPath :: FilePath+    , sslCiphers :: String -- ^ Comma-separated list of cipher names.+    } deriving (Eq, Read, Show, Typeable)++data MySQLError = ConnectionError {+      errFunction :: String+    , errNumber :: Int+    , errMessage :: String+    } | ResultError {+      errFunction :: String+    , errNumber :: Int+    , errMessage :: String+    } deriving (Eq, Show, Typeable)++instance Exception MySQLError++data Connection = Connection {+      connFP :: ForeignPtr MYSQL+    , connClose :: IO ()+    , connResult :: IORef (Maybe (Weak Result))+    }++data Result = Result {+      resFP :: ForeignPtr MYSQL_RES+    , resFields :: {-# UNPACK #-} !Int+    , resConnection :: Connection+    , resValid :: IORef Bool+    , resFetchFields :: Ptr MYSQL_RES -> IO (Ptr Field)+    , resFetchRow :: Ptr MYSQL_RES -> IO MYSQL_ROW+    , resFetchLengths :: Ptr MYSQL_RES -> IO (Ptr CULong)+    } | EmptyResult++-- | A row cursor, used by 'rowSeek' and 'rowTell'.+newtype Row = Row MYSQL_ROW_OFFSET++-- | Default information for setting up a connection.+--+-- Use as in the following example:+--+-- > connect defaultConnectInfo { connectHost = "db.example.com" }+defaultConnectInfo :: ConnectInfo+defaultConnectInfo = ConnectInfo {+                       connectHost = "localhost"+                     , connectPort = 3306+                     , connectUser = "root"+                     , connectPassword = ""+                     , connectDatabase = "test"+                     , connectOptions = []+                     , connectPath = ""+                     , connectSSL = Nothing+                     }++-- | Default (empty) information for setting up an SSL connection.+defaultSSLInfo :: SSLInfo+defaultSSLInfo = SSLInfo {+                   sslKey = ""+                 , sslCert = ""+                 , sslCA = ""+                 , sslCAPath = ""+                 , sslCiphers = ""+                 }++connect :: ConnectInfo -> IO Connection+connect ConnectInfo{..} = do+  closed <- newIORef False+  ptr0 <- mysql_init nullPtr+  case connectSSL of+    Nothing -> return ()+    Just SSLInfo{..} -> withString sslKey $ \ckey ->+                         withString sslCert $ \ccert ->+                          withString sslCA $ \cca ->+                           withString sslCAPath $ \ccapath ->+                            withString sslCiphers $ \ccipher ->+                             mysql_ssl_set ptr0 ckey ccert cca ccapath ccipher+                             >> return ()+  forM_ connectOptions $ \opt -> do+    r <- mysql_options ptr0 opt+    unless (r == 0) $ connectionError_ "connect" ptr0+  let flags = foldl' (+) 0 . map toConnectFlag $ connectOptions+  ptr <- withString connectHost $ \chost ->+          withString connectUser $ \cuser ->+           withString connectPassword $ \cpass ->+            withString connectDatabase $ \cdb ->+             withString connectPath $ \cpath ->+              withRTSSignalsBlocked $+               mysql_real_connect ptr0 chost cuser cpass cdb+                                  (fromIntegral connectPort) cpath flags+  when (ptr == nullPtr) $+    connectionError_ "connect" ptr0+  res <- newIORef Nothing+  let realClose = do+        cleanupConnResult res+        wasClosed <- atomicModifyIORef closed $ \prev -> (True, prev)+        unless wasClosed . withRTSSignalsBlocked $ mysql_close ptr+  fp <- newForeignPtr ptr realClose+  return Connection {+               connFP = fp+             , connClose = realClose+             , connResult = res+             }++-- | Delete the 'MYSQL_RES' behind a 'Result' immediately, and mark+-- the 'Result' as invalid.+cleanupConnResult :: IORef (Maybe (Weak Result)) -> IO ()+cleanupConnResult res = do+  prev <- readIORef res+  case prev of+    Nothing -> return ()+    Just w -> maybe (return ()) freeResult =<< deRefWeak w++-- | Close a connection, and mark any outstanding 'Result' as+-- invalid.+close :: Connection -> IO ()+close = connClose+{-# INLINE close #-}++ping :: Connection -> IO ()+ping conn = withConn conn $ \ptr ->+            withRTSSignalsBlocked (mysql_ping ptr) >>= check "ping" conn++threadId :: Connection -> IO Word+threadId conn = fromIntegral <$> withConn conn mysql_thread_id++serverInfo :: Connection -> IO String+serverInfo conn = withConn conn $ \ptr ->+                  peekCString =<< mysql_get_server_info ptr++hostInfo :: Connection -> IO String+hostInfo conn = withConn conn $ \ptr ->+                peekCString =<< mysql_get_host_info ptr++protocolInfo :: Connection -> IO Word+protocolInfo conn = withConn conn $ \ptr ->+                    fromIntegral <$> mysql_get_proto_info ptr++setCharacterSet :: Connection -> String -> IO ()+setCharacterSet conn cs =+  withCString cs $ \ccs ->+    withConn conn $ \ptr ->+        mysql_set_character_set ptr ccs >>= check "setCharacterSet" conn++characterSet :: Connection -> IO String+characterSet conn = withConn conn $ \ptr ->+  peekCString =<< mysql_character_set_name ptr++sslCipher :: Connection -> IO (Maybe String)+sslCipher conn = withConn conn $ \ptr ->+  withPtr peekCString =<< mysql_get_ssl_cipher ptr++serverStatus :: Connection -> IO String+serverStatus conn = withConn conn $ \ptr -> do+  st <- withRTSSignalsBlocked $ mysql_stat ptr+  checkNull "serverStatus" conn st+  peekCString st++clientInfo :: String+clientInfo = unsafePerformIO $ peekCString mysql_get_client_info+{-# NOINLINE clientInfo #-}++clientVersion :: Word+clientVersion = fromIntegral mysql_get_client_version+{-# NOINLINE clientVersion #-}++autocommit :: Connection -> Bool -> IO ()+autocommit conn onOff = withConn conn $ \ptr ->+   withRTSSignalsBlocked (mysql_autocommit ptr b) >>= check "autocommit" conn+ where b = if onOff then 1 else 0++changeUser :: Connection -> String -> String -> Maybe String -> IO ()+changeUser conn user pass mdb =+  withCString user $ \cuser ->+   withCString pass $ \cpass ->+    withMaybeString mdb $ \cdb ->+     withConn conn $ \ptr ->+      withRTSSignalsBlocked (mysql_change_user ptr cuser cpass cdb) >>=+      check "changeUser" conn++selectDB :: Connection -> String -> IO ()+selectDB conn db = +  withCString db $ \cdb ->+    withConn conn $ \ptr ->+      withRTSSignalsBlocked (mysql_select_db ptr cdb) >>= check "selectDB" conn++query :: Connection -> ByteString -> IO ()+query conn q = withConn conn $ \ptr ->+  unsafeUseAsCStringLen q $ \(p,l) ->+  mysql_real_query ptr p (fromIntegral l) >>= check "query" conn++-- | Return the number of fields (columns) in a result.+--+-- * If 'Left' 'Connection', returns the number of columns for the most+--   recent query on the connection.+--+-- * For 'Right' 'Result', returns the number of columns in each row+--   of this result.+--+-- The number of columns may legitimately be zero.+fieldCount :: Either Connection Result -> IO Int+fieldCount (Right EmptyResult) = return 0+fieldCount (Right res)         = return (resFields res)+fieldCount (Left conn)         =+    withConn conn $ fmap fromIntegral . mysql_field_count++affectedRows :: Connection -> IO Int64+affectedRows conn = withConn conn $ fmap fromIntegral . mysql_affected_rows++-- | Retrieve a complete result.+--+-- Any previous outstanding 'Result' is first marked as invalid.+storeResult :: Connection -> IO Result+storeResult = frobResult "storeResult" mysql_store_result+              mysql_fetch_fields_nonblock+              mysql_fetch_row_nonblock+              mysql_fetch_lengths_nonblock++-- | Initiate a row-by-row retrieval of a result.+--+-- Any previous outstanding 'Result' is first marked as invalid.+useResult :: Connection -> IO Result+useResult = frobResult "useResult" mysql_use_result+            (withRTSSignalsBlocked . mysql_fetch_fields)+            (withRTSSignalsBlocked . mysql_fetch_row)+            (withRTSSignalsBlocked . mysql_fetch_lengths)++frobResult :: String+           -> (Ptr MYSQL -> IO (Ptr MYSQL_RES))+           -> (Ptr MYSQL_RES -> IO (Ptr Field))+           -> (Ptr MYSQL_RES -> IO MYSQL_ROW)+           -> (Ptr MYSQL_RES -> IO (Ptr CULong))+           -> Connection -> IO Result+frobResult func frob fetchFieldsFunc fetchRowFunc fetchLengthsFunc conn =+  withConn conn $ \ptr -> do+    cleanupConnResult (connResult conn)+    res <- withRTSSignalsBlocked $ frob ptr+    fields <- mysql_field_count ptr+    valid <- newIORef True+    if res == nullPtr+      then if fields == 0+           then return EmptyResult+           else connectionError func conn+      else do+        fp <- newForeignPtr res $ freeResult_ valid res+        let ret = Result {+                    resFP = fp+                  , resFields = fromIntegral fields+                  , resConnection = conn+                  , resValid = valid+                  , resFetchFields = fetchFieldsFunc+                  , resFetchRow = fetchRowFunc+                  , resFetchLengths = fetchLengthsFunc+                  }+        weak <- mkWeakPtr ret (Just (freeResult_ valid res))+        writeIORef (connResult conn) (Just weak)+        return ret++-- | Immediately free the @MYSQL_RES@ value associated with this+-- 'Result', and mark the @Result@ as invalid.+freeResult :: Result -> IO ()+freeResult Result{..}      = withForeignPtr resFP $ freeResult_ resValid+freeResult EmptyResult{..} = return ()++-- | Check whether a 'Result' is still valid, i.e. backed by a live+-- @MYSQL_RES@ value.+isResultValid :: Result -> IO Bool+isResultValid Result{..}  = readIORef resValid+isResultValid EmptyResult = return False+            +freeResult_ :: IORef Bool -> Ptr MYSQL_RES -> IO ()+freeResult_ valid ptr = do+  wasValid <- atomicModifyIORef valid $ \prev -> (False, prev)+  when wasValid $ mysql_free_result ptr+    +fetchRow :: Result -> IO [Maybe ByteString]+fetchRow res@Result{..}  = withRes "fetchRow" res $ \ptr -> do+  rowPtr <- resFetchRow ptr+  if rowPtr == nullPtr+    then return []+    else do+      lenPtr <- resFetchLengths ptr+      checkNull "fetchRow" resConnection lenPtr+      let go len = withPtr $ \colPtr ->+                   create (fromIntegral len) $ \d ->+                   memcpy d (castPtr colPtr) (fromIntegral len)+      sequence =<< zipWith go <$> peekArray resFields lenPtr+                              <*> peekArray resFields rowPtr+fetchRow EmptyResult{..} = return []++fetchFields :: Result -> IO [Field]+fetchFields res@Result{..} = withRes "fetchFields" res $ \ptr -> do+  peekArray resFields =<< resFetchFields ptr+fetchFields EmptyResult{..} = return []++dataSeek :: Result -> Int64 -> IO ()+dataSeek res row = withRes "dataSeek" res $ \ptr ->+  mysql_data_seek ptr (fromIntegral row)++rowTell :: Result -> IO Row+rowTell res = withRes "rowTell" res $ \ptr ->+  Row <$> mysql_row_tell ptr++rowSeek :: Result -> Row -> IO Row+rowSeek res (Row row) = withRes "rowSeek" res $ \ptr ->+  Row <$> mysql_row_seek ptr row++-- | Read the next statement result. Returns 'True' if another result+-- is available, 'False' otherwise.+--+-- This function marks the current 'Result' as invalid, if one exists.+nextResult :: Connection -> IO Bool+nextResult conn = withConn conn $ \ptr -> do+  cleanupConnResult (connResult conn)+  i <- withRTSSignalsBlocked $ mysql_next_result ptr+  case i of+    0  -> return True+    -1 -> return False+    _  -> connectionError "nextResult" conn++commit :: Connection -> IO ()+commit conn = withConn conn $ \ptr ->+              mysql_commit ptr >>= check "commit" conn++rollback :: Connection -> IO ()+rollback conn = withConn conn $ \ptr ->+                mysql_rollback ptr >>= check "rollback" conn++escape :: Connection -> ByteString -> IO ByteString+escape conn bs = withConn conn $ \ptr ->+  unsafeUseAsCStringLen bs $ \(p,l) ->+    createAndTrim (l*2 + 1) $ \to ->+      fromIntegral <$> mysql_real_escape_string ptr (castPtr to) p+                                                (fromIntegral l)++withConn :: Connection -> (Ptr MYSQL -> IO a) -> IO a+withConn conn = withForeignPtr (connFP conn)++withRes :: String -> Result -> (Ptr MYSQL_RES -> IO a) -> IO a+withRes func res act = do+  valid <- readIORef (resValid res)+  unless valid . throw $ ResultError func 0 "result is no longer usable"+  withForeignPtr (resFP res) act++withString :: String -> (CString -> IO a) -> IO a+withString [] act = act nullPtr+withString xs act = withCString xs act++withMaybeString :: Maybe String -> (CString -> IO a) -> IO a+withMaybeString Nothing act = act nullPtr+withMaybeString (Just xs) act = withCString xs act++check :: Num a => String -> Connection -> a -> IO ()+check func conn r = unless (r == 0) $ connectionError func conn+{-# INLINE check #-}++checkNull :: String -> Connection -> Ptr a -> IO ()+checkNull func conn p = when (p == nullPtr) $ connectionError func conn+{-# INLINE checkNull #-}++withPtr :: (Ptr a -> IO b) -> Ptr a -> IO (Maybe b)+withPtr act p | p == nullPtr = return Nothing+              | otherwise    = Just <$> act p++connectionError :: String -> Connection -> IO a+connectionError func conn = withConn conn $ connectionError_ func++connectionError_ :: String -> Ptr MYSQL -> IO a+connectionError_ func ptr =do+  errno <- mysql_errno ptr+  msg <- peekCString =<< mysql_error ptr+  throw $ ConnectionError func (fromIntegral errno) msg
+ Database/MySQL/Base/C.hsc view
@@ -0,0 +1,325 @@+{-# LANGUAGE EmptyDataDecls, ForeignFunctionInterface #-}++-- |+-- Module:      Database.MySQL.Base.C+-- Copyright:   (c) 2011 MailRank, Inc.+-- License:     BSD3+-- Maintainer:  Bryan O'Sullivan <bos@mailrank.com>+-- Stability:   experimental+-- Portability: portable+--+-- Direct bindings to the C @mysqlclient@ API.+module Database.MySQL.Base.C+    (+    -- * Connection management+      mysql_init+    , mysql_options+    , mysql_ssl_set+    , mysql_real_connect+    , mysql_close+    , mysql_ping+    , mysql_autocommit+    , mysql_change_user+    , mysql_select_db+    , mysql_set_character_set+    -- ** Connection information+    , mysql_thread_id+    , mysql_get_server_info+    , mysql_get_host_info+    , mysql_get_proto_info+    , mysql_character_set_name+    , mysql_get_ssl_cipher+    , mysql_stat+    -- * Querying+    , mysql_real_query+    -- ** Escaping+    , mysql_real_escape_string+    -- ** Results+    , mysql_field_count+    , mysql_affected_rows+    , mysql_store_result+    , mysql_use_result+    , mysql_fetch_lengths+    , mysql_fetch_lengths_nonblock+    , mysql_fetch_row+    , mysql_fetch_row_nonblock+    -- * Working with results+    , mysql_free_result+    , mysql_fetch_fields+    , mysql_fetch_fields_nonblock+    , mysql_data_seek+    , mysql_row_seek+    , mysql_row_tell+    -- ** Multiple results+    , mysql_next_result+    -- * Transactions+    , mysql_commit+    , mysql_rollback+    -- * General information+    , mysql_get_client_info+    , mysql_get_client_version+    -- * Error handling+    , mysql_errno+    , mysql_error+    -- * Support functions+    , withRTSSignalsBlocked+    ) where++#include "mysql.h"+#include <signal.h>++import Control.Concurrent (rtsSupportsBoundThreads, runInBoundThread)+import Control.Exception (finally)+import Data.ByteString.Unsafe (unsafeUseAsCString)+import Database.MySQL.Base.Types+import Foreign.C.String (CString, withCString)+import Foreign.C.Types (CInt, CUInt, CULLong, CULong)+import Foreign.ForeignPtr (ForeignPtr, mallocForeignPtr, withForeignPtr)+import Foreign.Marshal.Utils (with)+import Foreign.Ptr (Ptr, nullPtr)+import Foreign.Storable (Storable(..))+import System.IO.Unsafe (unsafePerformIO)++-- | Execute an 'IO' action with signals used by GHC's runtime signals+-- blocked.  The @mysqlclient@ C library does not correctly restart+-- system calls if they are interrupted by signals, so many MySQL API+-- calls can unexpectedly fail when called from a Haskell application.+-- This is most likely to occur if you are linking against GHC's+-- threaded runtime (using the @-threaded@ option).+--+-- This function blocks @SIGALRM@ and @SIGVTALRM@, runs your action,+-- then unblocks those signals.  If you have a series of HDBC calls+-- that may block for a period of time, it may be wise to wrap them in+-- this action.  Blocking and unblocking signals is cheap, but not+-- free.+--+-- Here is an example of an exception that could be avoided by+-- temporarily blocking GHC's runtime signals:+--+-- >  SqlError {+-- >    seState = "", +-- >    seNativeError = 2003, +-- >    seErrorMsg = "Can't connect to MySQL server on 'localhost' (4)"+-- >  }+withRTSSignalsBlocked :: IO a -> IO a+withRTSSignalsBlocked act+    | not rtsSupportsBoundThreads = act+    | otherwise = runInBoundThread . withForeignPtr rtsSignals $ \set -> do+  pthread_sigmask (#const SIG_BLOCK) set nullPtr+  act `finally` pthread_sigmask (#const SIG_UNBLOCK) set nullPtr++rtsSignals :: ForeignPtr SigSet+rtsSignals = unsafePerformIO $ do+               fp <- mallocForeignPtr+               withForeignPtr fp $ \set -> do+                 sigemptyset set+                 sigaddset set (#const SIGALRM)+                 sigaddset set (#const SIGVTALRM)+               return fp+{-# NOINLINE rtsSignals #-}++data SigSet++instance Storable SigSet where+    sizeOf    _ = #{size sigset_t}+    alignment _ = alignment (undefined :: Ptr CInt)++foreign import ccall unsafe "signal.h sigaddset" sigaddset+    :: Ptr SigSet -> CInt -> IO ()++foreign import ccall unsafe "signal.h sigemptyset" sigemptyset+    :: Ptr SigSet -> IO ()++foreign import ccall unsafe "signal.h pthread_sigmask" pthread_sigmask+    :: CInt -> Ptr SigSet -> Ptr SigSet -> IO ()++foreign import ccall safe mysql_init+    :: Ptr MYSQL                -- ^ should usually be 'nullPtr'+    -> IO (Ptr MYSQL)++mysql_options :: Ptr MYSQL -> Option -> IO CInt+mysql_options ptr opt =+    case opt of+      ConnectTimeout secs ->+        withIntegral secs $ go (#const MYSQL_OPT_CONNECT_TIMEOUT)+      Compress ->+        go (#const MYSQL_OPT_COMPRESS) nullPtr+      NamedPipe ->+        go (#const MYSQL_OPT_NAMED_PIPE) nullPtr+      InitCommand cmd ->+        unsafeUseAsCString cmd $ go (#const MYSQL_INIT_COMMAND)+      ReadDefaultFile path ->+        withCString path $ go (#const MYSQL_READ_DEFAULT_FILE)+      ReadDefaultGroup group ->+        unsafeUseAsCString group $ go (#const MYSQL_READ_DEFAULT_GROUP)+      CharsetDir path ->+        withCString path $ go (#const MYSQL_SET_CHARSET_DIR)+      CharsetName cs ->+        withCString cs $ go (#const MYSQL_SET_CHARSET_NAME)+      LocalInFile b ->+        withBool b $ go (#const MYSQL_OPT_LOCAL_INFILE)+      Protocol proto ->+        withIntegral (fromEnum proto) $ go (#const MYSQL_OPT_PROTOCOL)+      SharedMemoryBaseName name ->+        unsafeUseAsCString name $ go (#const MYSQL_SHARED_MEMORY_BASE_NAME)+      ReadTimeout secs ->+        withIntegral secs $ go (#const MYSQL_OPT_READ_TIMEOUT)+      WriteTimeout secs ->+        withIntegral secs $ go (#const MYSQL_OPT_WRITE_TIMEOUT)+      UseRemoteConnection ->+        go (#const MYSQL_OPT_USE_REMOTE_CONNECTION) nullPtr+      UseEmbeddedConnection ->+        go (#const MYSQL_OPT_USE_EMBEDDED_CONNECTION) nullPtr+      GuessConnection ->+        go (#const MYSQL_OPT_GUESS_CONNECTION) nullPtr+      ClientIP ip ->+        unsafeUseAsCString ip $ go (#const MYSQL_SET_CLIENT_IP)+      SecureAuth b ->+        withBool b $ go (#const MYSQL_SECURE_AUTH)+      ReportDataTruncation b ->+        withBool b $ go (#const MYSQL_REPORT_DATA_TRUNCATION)+      Reconnect b ->+        withBool b $ go (#const MYSQL_OPT_RECONNECT)+      SSLVerifyServerCert b ->+        withBool b $ go (#const MYSQL_OPT_SSL_VERIFY_SERVER_CERT)+      -- Other options are accepted by mysql_real_connect, so ignore them.+      _ -> return 0+  where+    go = mysql_options_ ptr+    withBool b = with (if b then 1 else 0 :: CUInt)+    withIntegral i = with (fromIntegral i :: CUInt)++foreign import ccall safe "mysql.h mysql_options" mysql_options_+    :: Ptr MYSQL -> CInt -> Ptr a -> IO CInt++foreign import ccall unsafe mysql_real_connect+    :: Ptr MYSQL -- ^ Context (from 'mysql_init').+    -> CString   -- ^ Host name.+    -> CString   -- ^ User name.+    -> CString   -- ^ Password.+    -> CString   -- ^ Database.+    -> CInt      -- ^ Port.+    -> CString   -- ^ Unix socket.+    -> CULong    -- ^ Flags.+    -> IO (Ptr MYSQL)++foreign import ccall safe mysql_ssl_set+    :: Ptr MYSQL+    -> CString                  -- ^ Key.+    -> CString                  -- ^ Cert.+    -> CString                  -- ^ CA.+    -> CString                  -- ^ CA path.+    -> CString                  -- ^ Ciphers.+    -> IO MyBool++foreign import ccall unsafe mysql_close+    :: Ptr MYSQL -> IO ()++foreign import ccall unsafe mysql_ping+    :: Ptr MYSQL -> IO CInt++foreign import ccall safe mysql_thread_id+    :: Ptr MYSQL -> IO CULong++foreign import ccall unsafe mysql_autocommit+    :: Ptr MYSQL -> MyBool -> IO MyBool++foreign import ccall unsafe mysql_change_user+    :: Ptr MYSQL+    -> CString                  -- ^ user+    -> CString                  -- ^ password+    -> CString                  -- ^ database+    -> IO MyBool++foreign import ccall unsafe mysql_select_db+    :: Ptr MYSQL+    -> CString+    -> IO CInt++foreign import ccall safe mysql_get_server_info+    :: Ptr MYSQL -> IO CString++foreign import ccall safe mysql_get_host_info+    :: Ptr MYSQL -> IO CString++foreign import ccall safe mysql_get_proto_info+    :: Ptr MYSQL -> IO CUInt++foreign import ccall safe mysql_character_set_name+    :: Ptr MYSQL -> IO CString++foreign import ccall safe mysql_set_character_set+    :: Ptr MYSQL -> CString -> IO CInt++foreign import ccall safe mysql_get_ssl_cipher+    :: Ptr MYSQL -> IO CString++foreign import ccall unsafe mysql_stat+    :: Ptr MYSQL -> IO CString++foreign import ccall unsafe mysql_real_query+    :: Ptr MYSQL -> CString -> CULong -> IO CInt++foreign import ccall safe mysql_field_count+    :: Ptr MYSQL -> IO CUInt++foreign import ccall safe mysql_affected_rows+    :: Ptr MYSQL -> IO CULLong++foreign import ccall unsafe mysql_store_result+    :: Ptr MYSQL -> IO (Ptr MYSQL_RES)++foreign import ccall unsafe mysql_use_result+    :: Ptr MYSQL -> IO (Ptr MYSQL_RES)++foreign import ccall unsafe mysql_free_result+    :: Ptr MYSQL_RES -> IO ()++foreign import ccall unsafe mysql_fetch_fields+    :: Ptr MYSQL_RES -> IO (Ptr Field)++foreign import ccall safe "mysql.h mysql_fetch_fields" mysql_fetch_fields_nonblock+    :: Ptr MYSQL_RES -> IO (Ptr Field)++foreign import ccall safe mysql_data_seek+    :: Ptr MYSQL_RES -> CULLong -> IO ()++foreign import ccall safe mysql_row_seek+    :: Ptr MYSQL_RES -> MYSQL_ROW_OFFSET -> IO MYSQL_ROW_OFFSET++foreign import ccall safe mysql_row_tell+    :: Ptr MYSQL_RES -> IO MYSQL_ROW_OFFSET++foreign import ccall unsafe mysql_next_result+    :: Ptr MYSQL -> IO CInt++foreign import ccall unsafe mysql_commit+    :: Ptr MYSQL -> IO MyBool++foreign import ccall unsafe mysql_rollback+    :: Ptr MYSQL -> IO MyBool++foreign import ccall unsafe mysql_fetch_row+    :: Ptr MYSQL_RES -> IO MYSQL_ROW++foreign import ccall safe "mysql.h mysql_fetch_row" mysql_fetch_row_nonblock+    :: Ptr MYSQL_RES -> IO MYSQL_ROW++foreign import ccall unsafe mysql_fetch_lengths+    :: Ptr MYSQL_RES -> IO (Ptr CULong)++foreign import ccall safe "mysql.h mysql_fetch_lengths" mysql_fetch_lengths_nonblock+    :: Ptr MYSQL_RES -> IO (Ptr CULong)++foreign import ccall safe mysql_real_escape_string+    :: Ptr MYSQL -> CString -> CString -> CULong -> IO CULong++foreign import ccall safe mysql_get_client_info :: CString++foreign import ccall safe mysql_get_client_version :: CULong++foreign import ccall safe mysql_errno+    :: Ptr MYSQL -> IO CInt++foreign import ccall safe mysql_error+    :: Ptr MYSQL -> IO CString
+ Database/MySQL/Base/Types.hsc view
@@ -0,0 +1,284 @@+{-# LANGUAGE DeriveDataTypeable, EmptyDataDecls, ForeignFunctionInterface #-}++-- |+-- Module:      Database.MySQL.Base.C+-- Copyright:   (c) 2011 MailRank, Inc.+-- License:     BSD3+-- Maintainer:  Bryan O'Sullivan <bos@mailrank.com>+-- Stability:   experimental+-- Portability: portable+--+-- Types for working with the direct bindings to the C @mysqlclient@+-- API.++module Database.MySQL.Base.Types+    (+    -- * Types+    -- * High-level types+      Type(..)+    , Seconds+    , Protocol(..)+    , Option(..)+    , Field(..)+    , FieldFlag+    , FieldFlags+    -- * Low-level types+    , MYSQL+    , MYSQL_RES+    , MYSQL_ROW+    , MYSQL_ROWS+    , MYSQL_ROW_OFFSET+    , MyBool+    -- * Field flags+    , hasAllFlags+    , flagNotNull+    , flagPrimaryKey+    , flagUniqueKey+    , flagMultipleKey+    , flagUnsigned+    , flagZeroFill+    , flagBinary+    , flagAutoIncrement+    , flagNumeric+    , flagNoDefaultValue+    -- * Connect flags+    , toConnectFlag+    ) where++#include "mysql.h"++import Control.Applicative ((<$>), (<*>), pure)+import Data.Bits ((.|.), (.&.))+import Data.ByteString hiding (intercalate)+import Data.ByteString.Internal (create, memcpy)+import Data.List (intercalate)+import Data.Maybe (catMaybes)+import Data.Monoid (Monoid(..))+import Data.Typeable (Typeable)+import Data.Word (Word, Word8)+import Foreign.C.Types (CChar, CInt, CUInt, CULong)+import Foreign.Ptr (Ptr)+import Foreign.Storable (Storable(..), peekByteOff)+import qualified Data.IntMap as IntMap++data MYSQL+data MYSQL_RES+data MYSQL_ROWS+type MYSQL_ROW = Ptr (Ptr CChar)+type MYSQL_ROW_OFFSET = Ptr MYSQL_ROWS+type MyBool = CChar++-- | Column types supported by MySQL.+data Type = Decimal+          | Tiny+          | Short+          | Long+          | Float+          | Double+          | Null+          | Timestamp+          | LongLong+          | Int24+          | Date+          | Time+          | DateTime+          | Year+          | NewDate+          | VarChar+          | Bit+          | NewDecimal+          | Enum+          | Set+          | TinyBlob+          | MediumBlob+          | LongBlob+          | Blob+          | VarString+          | String+          | Geometry+            deriving (Enum, Eq, Show, Typeable)++toType :: CInt -> Type+toType v = IntMap.findWithDefault oops (fromIntegral v) typeMap+  where+    oops = error $ "Database.MySQL: unknown field type " ++ show v+    typeMap = IntMap.fromList [+               ((#const MYSQL_TYPE_DECIMAL), Decimal),+               ((#const MYSQL_TYPE_TINY), Tiny),+               ((#const MYSQL_TYPE_SHORT), Short),+               ((#const MYSQL_TYPE_LONG), Long),+               ((#const MYSQL_TYPE_FLOAT), Float),+               ((#const MYSQL_TYPE_DOUBLE), Double),+               ((#const MYSQL_TYPE_NULL), Null),+               ((#const MYSQL_TYPE_TIMESTAMP), Timestamp),+               ((#const MYSQL_TYPE_LONGLONG), LongLong),+               ((#const MYSQL_TYPE_DATE), Date),+               ((#const MYSQL_TYPE_TIME), Time),+               ((#const MYSQL_TYPE_DATETIME), DateTime),+               ((#const MYSQL_TYPE_YEAR), Year),+               ((#const MYSQL_TYPE_NEWDATE), NewDate),+               ((#const MYSQL_TYPE_VARCHAR), VarChar),+               ((#const MYSQL_TYPE_BIT), Bit),+               ((#const MYSQL_TYPE_NEWDECIMAL), NewDecimal),+               ((#const MYSQL_TYPE_ENUM), Enum),+               ((#const MYSQL_TYPE_SET), Set),+               ((#const MYSQL_TYPE_TINY_BLOB), TinyBlob),+               ((#const MYSQL_TYPE_MEDIUM_BLOB), MediumBlob),+               ((#const MYSQL_TYPE_LONG_BLOB), LongBlob),+               ((#const MYSQL_TYPE_BLOB), Blob),+               ((#const MYSQL_TYPE_VAR_STRING), VarString),+               ((#const MYSQL_TYPE_STRING), String),+               ((#const MYSQL_TYPE_GEOMETRY), Geometry)+              ]++-- | A description of a field (column) of a table.+data Field = Field {+      fieldName :: ByteString   -- ^ Name of column.+    , fieldOrigName :: ByteString -- ^ Original column name, if an alias.+    , fieldTable :: ByteString -- ^ Table of column, if column was a field.+    , fieldOrigTable :: ByteString -- ^ Original table name, if table was an alias.+    , fieldDB :: ByteString        -- ^ Database for table.+    , fieldCatalog :: ByteString   -- ^ Catalog for table.+    , fieldDefault :: Maybe ByteString   -- ^ Default value.+    , fieldLength :: Word          -- ^ Width of column (create length).+    , fieldMaxLength :: Word    -- ^ Maximum width for selected set.+    , fieldFlags :: FieldFlags        -- ^ Div flags.+    , fieldDecimals :: Word -- ^ Number of decimals in field.+    , fieldCharSet :: Word -- ^ Character set number.+    , fieldType :: Type+    } deriving (Eq, Show, Typeable)++newtype FieldFlags = FieldFlags CUInt+    deriving (Eq, Typeable)++instance Show FieldFlags where+    show f = '[' : z ++ "]"+      where z = intercalate "," . catMaybes $ [+                          flagNotNull ??? "flagNotNull"+                        , flagPrimaryKey ??? "flagPrimaryKey"+                        , flagUniqueKey ??? "flagUniqueKey"+                        , flagMultipleKey ??? "flagMultipleKey"+                        , flagUnsigned ??? "flagUnsigned"+                        , flagZeroFill ??? "flagZeroFill"+                        , flagBinary ??? "flagBinary"+                        , flagAutoIncrement ??? "flagAutoIncrement"+                        , flagNumeric ??? "flagNumeric"+                        , flagNoDefaultValue ??? "flagNoDefaultValue"+                        ]+            flag ??? name | f `hasAllFlags` flag = Just name+                          | otherwise            = Nothing++type FieldFlag = FieldFlags++instance Monoid FieldFlags where+    mempty = FieldFlags 0+    {-# INLINE mempty #-}+    mappend (FieldFlags a) (FieldFlags b) = FieldFlags (a .|. b)+    {-# INLINE mappend #-}++flagNotNull, flagPrimaryKey, flagUniqueKey, flagMultipleKey :: FieldFlag+flagNotNull = FieldFlags #const NOT_NULL_FLAG+flagPrimaryKey = FieldFlags #const PRI_KEY_FLAG+flagUniqueKey = FieldFlags #const UNIQUE_KEY_FLAG+flagMultipleKey = FieldFlags #const MULTIPLE_KEY_FLAG++flagUnsigned, flagZeroFill, flagBinary, flagAutoIncrement :: FieldFlag+flagUnsigned = FieldFlags #const UNSIGNED_FLAG+flagZeroFill = FieldFlags #const ZEROFILL_FLAG+flagBinary = FieldFlags #const BINARY_FLAG+flagAutoIncrement = FieldFlags #const AUTO_INCREMENT_FLAG++flagNumeric, flagNoDefaultValue :: FieldFlag+flagNumeric = FieldFlags #const NUM_FLAG+flagNoDefaultValue = FieldFlags #const NO_DEFAULT_VALUE_FLAG++hasAllFlags :: FieldFlags -> FieldFlags -> Bool+FieldFlags a `hasAllFlags` FieldFlags b = a .&. b == b+{-# INLINE hasAllFlags #-}++peekField :: Ptr Field -> IO Field+peekField ptr = do+  flags <- FieldFlags <$> (#peek MYSQL_FIELD, flags) ptr+  Field+   <$> peekS ((#peek MYSQL_FIELD, name)) ((#peek MYSQL_FIELD, name_length))+   <*> peekS ((#peek MYSQL_FIELD, org_name)) ((#peek MYSQL_FIELD, org_name_length))+   <*> peekS ((#peek MYSQL_FIELD, table)) ((#peek MYSQL_FIELD, table_length))+   <*> peekS ((#peek MYSQL_FIELD, org_table)) ((#peek MYSQL_FIELD, org_table_length))+   <*> peekS ((#peek MYSQL_FIELD, db)) ((#peek MYSQL_FIELD, db_length))+   <*> peekS ((#peek MYSQL_FIELD, catalog)) ((#peek MYSQL_FIELD, catalog_length))+   <*> (if flags `hasAllFlags` flagNoDefaultValue+       then pure Nothing+       else Just <$> peekS ((#peek MYSQL_FIELD, def)) ((#peek MYSQL_FIELD, def_length)))+   <*> (uint <$> (#peek MYSQL_FIELD, length) ptr)+   <*> (uint <$> (#peek MYSQL_FIELD, max_length) ptr)+   <*> pure flags+   <*> (uint <$> (#peek MYSQL_FIELD, decimals) ptr)+   <*> (uint <$> (#peek MYSQL_FIELD, charsetnr) ptr)+   <*> (toType <$> (#peek MYSQL_FIELD, type) ptr)+ where+   uint = fromIntegral :: CUInt -> Word+   peekS :: (Ptr Field -> IO (Ptr Word8)) -> (Ptr Field -> IO CUInt)+         -> IO ByteString+   peekS peekPtr peekLen = do+     p <- peekPtr ptr+     l <- peekLen ptr+     create (fromIntegral l) $ \d -> memcpy d p (fromIntegral l)++instance Storable Field where+    sizeOf _    = #{size MYSQL_FIELD}+    alignment _ = alignment (undefined :: Ptr CChar)+    peek = peekField++type Seconds = Word++data Protocol = TCP+              | Socket+              | Pipe+              | Memory+                deriving (Eq, Read, Show, Enum, Typeable)++data Option =+            -- Options accepted by mysq_options.+              ConnectTimeout Seconds+            | Compress+            | NamedPipe+            | InitCommand ByteString+            | ReadDefaultFile FilePath+            | ReadDefaultGroup ByteString+            | CharsetDir FilePath+            | CharsetName String+            | LocalInFile Bool+            | Protocol Protocol+            | SharedMemoryBaseName ByteString+            | ReadTimeout Seconds+            | WriteTimeout Seconds+            | UseRemoteConnection+            | UseEmbeddedConnection+            | GuessConnection+            | ClientIP ByteString+            | SecureAuth Bool+            | ReportDataTruncation Bool+            | Reconnect Bool+            | SSLVerifyServerCert Bool+            -- Flags accepted by mysql_real_connect.+            | FoundRows+            | IgnoreSIGPIPE+            | IgnoreSpace+            | Interactive+            | LocalFiles+            | MultiResults+            | MultiStatements+            | NoSchema+              deriving (Eq, Read, Show, Typeable)++toConnectFlag :: Option -> CULong+toConnectFlag Compress  = #const CLIENT_COMPRESS+toConnectFlag FoundRows = #const CLIENT_FOUND_ROWS+toConnectFlag IgnoreSIGPIPE = #const CLIENT_IGNORE_SIGPIPE+toConnectFlag IgnoreSpace = #const CLIENT_IGNORE_SPACE+toConnectFlag Interactive = #const CLIENT_INTERACTIVE+toConnectFlag LocalFiles = #const CLIENT_LOCAL_FILES+toConnectFlag MultiResults = #const CLIENT_MULTI_RESULTS+toConnectFlag MultiStatements = #const CLIENT_MULTI_STATEMENTS+toConnectFlag NoSchema = #const CLIENT_NO_SCHEMA+toConnectFlag _        = 0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2011, MailRank, Inc.++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.markdown view
@@ -0,0 +1,47 @@+# mysql: bindings to the mysqlclient library++This library is a Haskell binding to the MySQL `mysqlclient` client+library.  It is a fairly faithful, low level library that implements+most of the MySQL client API.++This library deliberately avoids the question of providing a "good"+API. Its purpose is to serve as a base on which higher-level libraries+can be build.++# Licensing++This library is BSD-licensed under the terms of the+[MySQL FOSS License Exception](http://www.mysql.com/about/legal/licensing/foss-exception/).++Since this library links against the GPL-licensed `mysqlclient`+library, a non-open-source application that uses it *may* be subject+to the terms of the GPL.++# To do++* Add support for prepared statements. This API is huge and of dubious+  performance worth, so it's not currently a priority for me. Patches+  welcome!++# Get involved!++We are happy to receive bug reports, fixes, documentation enhancements,+and other improvements.++Please report bugs via the+[github issue tracker](http://github.com/mailrank/mysql/issues).++Master [git repository](http://github.com/mailrank/mysql):++* `git clone git://github.com/mailrank/mysql.git`++There's also a [Mercurial mirror](http://bitbucket.org/bos/mysql):++* `hg clone http://bitbucket.org/bos/mysql`++(You can create and contribute changes using either git or Mercurial.)++# Authors++This library is written and maintained by Bryan O'Sullivan,+<bos@mailrank.com>.
+ Setup.lhs view
@@ -0,0 +1,44 @@+#!/usr/bin/env runhaskell++\begin{code}+{- OPTIONS_GHC -Wall #-}++import Control.Monad (liftM2, mplus)+import Data.List (isPrefixOf)+import Distribution.PackageDescription+import Distribution.Simple+import Distribution.Simple.LocalBuildInfo+import Distribution.Simple.Program+import Distribution.Verbosity++main = defaultMainWithHooks simpleUserHooks {+  hookedPrograms = [mysqlConfigProgram],++  confHook = \pkg flags -> do+    lbi <- confHook simpleUserHooks pkg flags+    bi  <- mysqlBuildInfo lbi+    return lbi {+      localPkgDescr = updatePackageDescription (Just bi, []) (localPkgDescr lbi)+    }+}++mysqlConfigProgram = (simpleProgram "mysql_config") {+    programFindLocation = \verbosity -> liftM2 mplus+      (findProgramLocation verbosity "mysql_config")+      (findProgramLocation verbosity "mysql_config5")+  }++mysqlBuildInfo :: LocalBuildInfo -> IO BuildInfo+mysqlBuildInfo lbi = do+  let mysqlConfig = fmap words . rawSystemProgramStdoutConf normal+                    mysqlConfigProgram (withPrograms lbi)++  include <- mysqlConfig ["--include"]+  libs <- mysqlConfig ["--libs"]++  return emptyBuildInfo {+    extraLibDirs = map (drop 2) . filter ("-L" `isPrefixOf`) $ libs+  , extraLibs = map (drop 2) . filter ("-l" `isPrefixOf`) $ libs+  , includeDirs = map (drop 2) include+  }+\end{code}
+ mysql.cabal view
@@ -0,0 +1,57 @@+name:           mysql+version:        0.1.0.0+homepage:       https://github.com/mailrank/mysql+bug-reports:    https://github.com/mailrank/mysql/issues+synopsis:       A low-level MySQL client library.+description:    +    A low-level client library for the MySQL database, implemented as+    bindings to the C @mysqlclient@ API.+    .+    /Important licensing note/: This library is BSD-licensed under the+    terms of the MySQL FOSS License Exception+    <http://www.mysql.com/about/legal/licensing/foss-exception/>.+    .+    Since this library links against the GPL-licensed @mysqlclient@+    library, a non-open-source application that uses it /may/ be+    subject to the terms of the GPL.+license:        BSD3+license-file:   LICENSE+author:         Bryan O'Sullivan <bos@mailrank.com>+maintainer:     Bryan O'Sullivan <bos@mailrank.com>+copyright:      2011 MailRank, Inc.+category:       Database+build-type:     Custom+cabal-version:  >= 1.6+extra-source-files:+    README.markdown++flag developer+  description: operate in developer mode+  default: False++library+  exposed-modules:+    Database.MySQL.Base+    Database.MySQL.Base.C+    Database.MySQL.Base.Types++  build-depends:+    base       < 5,+    bytestring >= 0.9 && < 1.0,+    containers++  ghc-options: -Wall+  if impl(ghc >= 6.8)+    ghc-options: -fwarn-tabs+  if flag(developer)+    ghc-prof-options: -auto-all+    ghc-options: -Werror+    cpp-options: -DASSERTS++source-repository head+  type:     git+  location: http://github.com/mailrank/mysql++source-repository head+  type:     mercurial+  location: http://bitbucket.org/bos/mysql