Bryan O'Sullivan avatar Bryan O'Sullivan committed 5ceb863

Initial commit.

Comments (0)

Files changed (11)

+^(?:cabal-dev|dist)$
+\.(?:aux|eventlog|h[ip]|log|[oa]|orig|prof|ps|rej|swp)$
+~$
+syntax: glob
+.\#*

Database/MySQL/Simple.hs

+module Database.MySQL.Simple
+    (
+      execute
+    , query
+    , formatQuery
+    ) where
+
+import Control.Applicative
+import Data.Int (Int64)
+import Control.Monad.Fix
+import Blaze.ByteString.Builder
+import qualified Data.ByteString.Char8 as B
+import Data.ByteString (ByteString)
+import Data.Monoid
+import Database.MySQL.Base (Connection)
+import qualified Database.MySQL.Base as Base
+import Database.MySQL.Simple.Param
+import Database.MySQL.Simple.QueryParams
+import Database.MySQL.Simple.QueryResults
+import Database.MySQL.Simple.Types
+
+formatQuery :: QueryParams q => Connection -> Query -> q -> IO ByteString
+formatQuery conn (Query template) qs
+    | '?' `B.notElem` template = return template
+    | otherwise =
+        toByteString . zipParams (split template) <$> mapM sub (renderParams qs)
+  where sub (Plain b)  = pure b
+        sub (Escape s) = (inQuotes . fromByteString) <$> Base.escape conn s
+        split q = fromByteString h : if B.null t then [] else split (B.tail t)
+            where (h,t) = B.break (=='?') q
+        zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps
+        zipParams [] []         = mempty
+        zipParams [] _ = fmtError "more parameters than '?' characters"
+        zipParams _ [] = fmtError "more '?' characters than parameters"
+
+execute :: (QueryParams q) => Connection -> Query -> q -> IO Int64
+execute conn template qs = do
+  Base.query conn =<< formatQuery conn template qs
+  ncols <- Base.fieldCount (Left conn)
+  if ncols /= 0
+    then error "execute: executed a select!"
+    else Base.affectedRows conn
+  
+query :: (QueryParams q, QueryResults r) => Connection -> Query -> q -> IO [r]
+query conn template qs = do
+  Base.query conn =<< formatQuery conn template qs
+  r <- Base.storeResult conn
+  ncols <- Base.fieldCount (Right r)
+  if ncols == 0
+    then return []
+    else do
+      fs <- Base.fetchFields r
+      flip fix [] $ \loop acc -> do
+        row <- Base.fetchRow r
+        case row of
+          [] -> return (reverse acc)
+          _  -> loop (convertResults fs row:acc)
+
+fmtError :: String -> a
+fmtError msg = error $ "Database.MySQL.formatQuery: " ++ msg

Database/MySQL/Simple/Param.hs

+{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}
+
+module Database.MySQL.Simple.Param
+    (
+      Action(..)
+    , Param(..)
+    , inQuotes
+    ) where
+
+import Blaze.ByteString.Builder (Builder, fromByteString, toByteString)
+import Blaze.Text (integral, double, float)
+import Data.ByteString (ByteString)
+import Data.Monoid (mappend)
+import Database.MySQL.Simple.Types (Null)
+import Data.Int (Int8, Int16, Int32, Int64)
+import Data.Time.Calendar (Day, showGregorian)
+import Data.Time.Clock (UTCTime)
+import Data.Time.LocalTime (TimeOfDay)
+import Data.Time.Format (formatTime)
+import Data.Word (Word, Word8, Word16, Word32, Word64)
+import System.Locale (defaultTimeLocale)
+import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8
+import qualified Data.ByteString as SB
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.Text as ST
+import qualified Data.Text.Encoding as ST
+import qualified Data.Text.Lazy as LT
+
+data Action = Plain Builder
+            | Escape ByteString
+
+class Param a where
+    render :: a -> Action
+
+instance Param Action where
+    render a = a
+    {-# INLINE render #-}
+
+instance (Param a) => Param (Maybe a) where
+    render Nothing  = renderNull
+    render (Just a) = render a
+    {-# INLINE render #-}
+
+renderNull :: Action
+renderNull = Plain (fromByteString "null")
+
+instance Param Null where
+    render _ = renderNull
+    {-# INLINE render #-}
+
+instance Param Bool where
+    render = Plain . integral . fromEnum
+    {-# INLINE render #-}
+
+instance Param Int8 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Int16 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Int32 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Int where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Int64 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Integer where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Word8 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Word16 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Word32 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Word where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Word64 where
+    render = Plain . integral
+    {-# INLINE render #-}
+
+instance Param Float where
+    render v | isNaN v || isInfinite v = renderNull
+             | otherwise               = Plain (float v)
+    {-# INLINE render #-}
+
+instance Param Double where
+    render v | isNaN v || isInfinite v = renderNull
+             | otherwise               = Plain (double v)
+    {-# INLINE render #-}
+
+instance Param SB.ByteString where
+    render = Escape
+    {-# INLINE render #-}
+
+instance Param LB.ByteString where
+    render = render . SB.concat . LB.toChunks
+    {-# INLINE render #-}
+
+instance Param ST.Text where
+    render = Escape . ST.encodeUtf8
+    {-# INLINE render #-}
+
+instance Param [Char] where
+    render = Escape . toByteString . Utf8.fromString
+    {-# INLINE render #-}
+
+instance Param LT.Text where
+    render = render . LT.toStrict
+    {-# INLINE render #-}
+
+instance Param UTCTime where
+    render = Plain . Utf8.fromString . formatTime defaultTimeLocale "'%F %T'"
+    {-# INLINE render #-}
+
+instance Param Day where
+    render = Plain . inQuotes . Utf8.fromString . showGregorian
+    {-# INLINE render #-}
+
+instance Param TimeOfDay where
+    render = Plain . inQuotes . Utf8.fromString . show
+    {-# INLINE render #-}
+
+inQuotes :: Builder -> Builder
+inQuotes b = quote `mappend` b `mappend` quote
+  where quote = Utf8.fromChar '\''

Database/MySQL/Simple/QueryParams.hs

+module Database.MySQL.Simple.QueryParams
+    (
+      QueryParams(..)
+    ) where
+
+import Database.MySQL.Simple.Param
+import Database.MySQL.Simple.Types
+
+class QueryParams a where
+    renderParams :: a -> [Action]
+
+instance QueryParams () where
+    renderParams _ = []
+
+instance (Param a) => QueryParams (Only a) where
+    renderParams (Only v) = [render v]
+
+instance (Param a, Param b) => QueryParams (a,b) where
+    renderParams (a,b) = [render a, render b]
+
+instance (Param a, Param b, Param c) => QueryParams (a,b,c) where
+    renderParams (a,b,c) = [render a, render b, render c]
+
+instance (Param a, Param b, Param c, Param d) => QueryParams (a,b,c,d) where
+    renderParams (a,b,c,d) = [render a, render b, render c, render d]
+
+instance (Param a, Param b, Param c, Param d, Param e)
+    => QueryParams (a,b,c,d,e) where
+    renderParams (a,b,c,d,e) =
+        [render a, render b, render c, render d, render e]
+
+instance (Param a, Param b, Param c, Param d, Param e, Param f)
+    => QueryParams (a,b,c,d,e,f) where
+    renderParams (a,b,c,d,e,f) =
+        [render a, render b, render c, render d, render e, render f]
+
+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g)
+    => QueryParams (a,b,c,d,e,f,g) where
+    renderParams (a,b,c,d,e,f,g) =
+        [render a, render b, render c, render d, render e, render f, render g]
+
+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
+          Param h)
+    => QueryParams (a,b,c,d,e,f,g,h) where
+    renderParams (a,b,c,d,e,f,g,h) =
+        [render a, render b, render c, render d, render e, render f, render g,
+         render h]
+
+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
+          Param h, Param i)
+    => QueryParams (a,b,c,d,e,f,g,h,i) where
+    renderParams (a,b,c,d,e,f,g,h,i) =
+        [render a, render b, render c, render d, render e, render f, render g,
+         render h, render i]
+
+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
+          Param h, Param i, Param j)
+    => QueryParams (a,b,c,d,e,f,g,h,i,j) where
+    renderParams (a,b,c,d,e,f,g,h,i,j) =
+        [render a, render b, render c, render d, render e, render f, render g,
+         render h, render i, render j]
+
+instance (Param a) => QueryParams [a] where
+    renderParams = map render

Database/MySQL/Simple/QueryResults.hs

+module Database.MySQL.Simple.QueryResults
+    (
+      QueryResults(..)
+    ) where
+
+import Data.ByteString (ByteString)
+import Database.MySQL.Base.Types
+import Database.MySQL.Simple.Result
+import Database.MySQL.Simple.Types
+
+class QueryResults a where
+    convertResults :: [Field] -> [Maybe ByteString] -> a
+
+instance (Result a) => QueryResults (Only a) where
+    convertResults [fa] [va] = Only (convert fa va)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b) => QueryResults (a,b) where
+    convertResults [fa,fb] [va,vb] = (convert fa va, convert fb vb)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c) => QueryResults (a,b,c) where
+    convertResults [fa,fb,fc] [va,vb,vc] =
+        (convert fa va, convert fb vb, convert fc vc)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d) =>
+    QueryResults (a,b,c,d) where
+    convertResults [fa,fb,fc,fd] [va,vb,vc,vd] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e) =>
+    QueryResults (a,b,c,d,e) where
+    convertResults [fa,fb,fc,fd,fe] [va,vb,vc,vd,ve] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e, Result f) =>
+    QueryResults (a,b,c,d,e,f) where
+    convertResults [fa,fb,fc,fd,fe,ff] [va,vb,vc,vd,ve,vf] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve, convert ff vf)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e, Result f,
+          Result g) =>
+    QueryResults (a,b,c,d,e,f,g) where
+    convertResults [fa,fb,fc,fd,fe,ff,fg] [va,vb,vc,vd,ve,vf,vg] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve, convert ff vf, convert fg vg)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e, Result f,
+          Result g, Result h) =>
+    QueryResults (a,b,c,d,e,f,g,h) where
+    convertResults [fa,fb,fc,fd,fe,ff,fg,fh] [va,vb,vc,vd,ve,vf,vg,vh] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve, convert ff vf, convert fg vg, convert fh vh)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e, Result f,
+          Result g, Result h, Result i) =>
+    QueryResults (a,b,c,d,e,f,g,h,i) where
+    convertResults [fa,fb,fc,fd,fe,ff,fg,fh,fi] [va,vb,vc,vd,ve,vf,vg,vh,vi] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve, convert ff vf, convert fg vg, convert fh vh,
+         convert fi vi)
+    convertResults fs vs  = convError fs vs
+
+instance (Result a, Result b, Result c, Result d, Result e, Result f,
+          Result g, Result h, Result i, Result j) =>
+    QueryResults (a,b,c,d,e,f,g,h,i,j) where
+    convertResults [fa,fb,fc,fd,fe,ff,fg,fh,fi,fj]
+                   [va,vb,vc,vd,ve,vf,vg,vh,vi,vj] =
+        (convert fa va, convert fb vb, convert fc vc, convert fd vd,
+         convert fe ve, convert ff vf, convert fg vg, convert fh vh,
+         convert fi vi, convert fj vj)
+    convertResults fs vs  = convError fs vs
+
+convError :: [Field] -> [Maybe ByteString] -> a
+convError = error "convError"

Database/MySQL/Simple/Result.hs

+{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleInstances #-}
+
+module Database.MySQL.Simple.Result
+    (
+      Result(..)
+    , ResultError(..)
+    ) where
+
+#include "MachDeps.h"
+
+import Data.Typeable
+import Control.Applicative
+import Control.Exception
+import Data.ByteString (ByteString)
+import Database.MySQL.Base.Types
+import Data.Attoparsec.Char8 hiding (Result)
+import Data.Bits
+import Data.Time.Calendar (Day, fromGregorian)
+import Data.Time.Clock (UTCTime)
+import Data.Time.LocalTime (TimeOfDay, makeTimeOfDayValid)
+import System.Locale (defaultTimeLocale)
+import Data.Int (Int8, Int16, Int32, Int64)
+import Data.Word (Word, Word8, Word16, Word32, Word64)
+import Data.Ratio (Ratio)
+import Data.List
+import qualified Data.ByteString as SB
+import qualified Data.ByteString.Char8 as B8
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.Text as ST
+import qualified Data.Text.Encoding as ST
+import qualified Data.Text.Lazy as LT
+import Data.Time.Format (parseTime)
+
+data ResultError = Incompatible { errSourceType :: String
+                                , errDestType :: String
+                                , errMessage :: String }
+                 | UnexpectedNull { errSourceType :: String
+                                  , errDestType :: String
+                                  , errMessage :: String }
+                 | ConversionFailed { errSourceType :: String
+                                    , errDestType :: String
+                                    , errMessage :: String }
+                   deriving (Eq, Show, Typeable)
+
+instance Exception ResultError
+
+class Result a where
+    convert :: Field -> Maybe ByteString -> a
+
+instance (Result a) => Result (Maybe a) where
+    convert _ Nothing = Nothing
+    convert f bs      = Just (convert f bs)
+
+instance Result Bool where
+    convert = atto ok8 ((/=(0::Int)) <$> decimal)
+
+instance Result Int8 where
+    convert = atto ok8 $ signed decimal
+
+instance Result Int16 where
+    convert = atto ok16 $ signed decimal
+
+instance Result Int32 where
+    convert = atto ok32 $ signed decimal
+
+instance Result Int where
+    convert = atto okWord $ signed decimal
+
+instance Result Int64 where
+    convert = atto ok64 $ signed decimal
+
+instance Result Integer where
+    convert = atto ok64 $ signed decimal
+
+instance Result Word8 where
+    convert = atto ok8 decimal
+
+instance Result Word16 where
+    convert = atto ok16 decimal
+
+instance Result Word32 where
+    convert = atto ok32 decimal
+
+instance Result Word where
+    convert = atto okWord decimal
+
+instance Result Word64 where
+    convert = atto ok64 decimal
+
+instance Result Float where
+    convert = atto ok ((fromRational . toRational) <$> double)
+        where ok = mkCompats [Float,Double,Decimal,NewDecimal]
+
+instance Result Double where
+    convert = atto ok double
+        where ok = mkCompats [Float,Double,Decimal,NewDecimal]
+
+instance Result (Ratio Integer) where
+    convert = atto ok rational
+        where ok = mkCompats [Float,Double,Decimal,NewDecimal]
+
+instance Result SB.ByteString where
+    convert f = doConvert f okText $ id
+
+instance Result LB.ByteString where
+    convert f = LB.fromChunks . (:[]) . convert f
+
+instance Result ST.Text where
+    convert f | isText f  = doConvert f okText $ ST.decodeUtf8
+              | otherwise = incompatible f (typeOf ST.empty)
+                            "attempt to mix binary and text"
+
+instance Result LT.Text where
+    convert f = LT.fromStrict . convert f
+
+instance Result [Char] where
+    convert f = ST.unpack . convert f
+
+instance Result UTCTime where
+    convert f = doConvert f ok $ \bs ->
+                case parseTime defaultTimeLocale "%F %T" (B8.unpack bs) of
+                  Just t -> t
+                  Nothing -> conversionFailed f "UTCTime" "could not parse"
+        where ok = mkCompats [DateTime,Timestamp]
+
+instance Result Day where
+    convert f = flip (atto ok) f $ case fieldType f of
+                                     Year -> year
+                                     _    -> date
+        where ok = mkCompats [Year,Date,NewDate]
+              year = fromGregorian <$> decimal <*> pure 1 <*> pure 1
+              date = fromGregorian <$> (decimal <* char '-')
+                                   <*> (decimal <* char '-')
+                                   <*> decimal
+
+instance Result TimeOfDay where
+    convert f = flip (atto ok) f $ do
+                hours <- decimal <* char ':'
+                mins <- decimal <* char ':'
+                secs <- decimal :: Parser Int
+                case makeTimeOfDayValid hours mins (fromIntegral secs) of
+                  Just t -> return t
+                  _      -> conversionFailed f "TimeOfDay" "could not parse"
+        where ok = mkCompats [Time]
+
+isText :: Field -> Bool
+isText f = fieldCharSet f /= 63
+
+newtype Compat = Compat Word32
+    
+mkCompats :: [Type] -> Compat
+mkCompats = foldl' f (Compat 0) . map mkCompat
+  where f (Compat a) (Compat b) = Compat (a .|. b)
+
+mkCompat :: Type -> Compat
+mkCompat = Compat . shiftL 1 . fromEnum
+
+compat :: Compat -> Compat -> Bool
+compat (Compat a) (Compat b) = a .&. b /= 0
+
+okText, ok8, ok16, ok32, ok64, okWord :: Compat
+okText = mkCompats [VarChar,TinyBlob,MediumBlob,LongBlob,Blob,VarString,String,
+                    Set,Enum]
+ok8 = mkCompats [Tiny]
+ok16 = mkCompats [Tiny,Short]
+ok32 = mkCompats [Tiny,Short,Int24,Long]
+ok64 = mkCompats [Tiny,Short,Int24,Long,LongLong]
+#if WORD_SIZE_IN_BITS < 64
+okWord = ok32
+#else
+okWord = ok64
+#endif
+
+doConvert :: (Typeable a) =>
+             Field -> Compat -> (ByteString -> a) -> Maybe ByteString -> a
+doConvert f types cvt (Just bs)
+    | mkCompat (fieldType f) `compat` types = cvt bs
+    | otherwise = incompatible f (typeOf (cvt undefined)) "types incompatible"
+doConvert f _ cvt _ = throw $ UnexpectedNull (show (fieldType f))
+                              (show (typeOf (cvt undefined))) ""
+
+incompatible :: Field -> TypeRep -> String -> a
+incompatible f r = throw . Incompatible (show (fieldType f)) (show r)
+
+conversionFailed :: Field -> String -> String -> a
+conversionFailed f s = throw . ConversionFailed (show (fieldType f)) s
+
+atto :: (Typeable a) => Compat -> Parser a -> Field -> Maybe ByteString -> a
+atto types p0 f = doConvert f types $ go undefined p0
+  where
+    go :: (Typeable a) => a -> Parser a -> ByteString -> a
+    go dummy p s =
+        case parseOnly p s of
+          Left err -> conversionFailed f (show (typeOf dummy)) err
+          Right v  -> v

Database/MySQL/Simple/Types.hs

+module Database.MySQL.Simple.Types
+    (
+      Null(..)
+    , Only(..)
+    , Query(..)
+    ) where
+
+import Control.Arrow
+import Blaze.ByteString.Builder
+import Data.String (IsString(..))
+import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8
+import Data.ByteString (ByteString)
+
+data Null = Null
+
+newtype Query = Query {
+      fromQuery :: ByteString
+    } deriving (Eq, Ord)
+
+instance Show Query where
+    show = show . fromQuery
+
+instance Read Query where
+    readsPrec i = fmap (first Query) . readsPrec i
+
+instance IsString Query where
+    fromString = Query . toByteString . Utf8.fromString
+
+newtype Only a = Only a
+    deriving (Eq, Ord, Read, Show)
+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.
+# mysql-simple: mid-level bindings to the mysqlclient library
+
+This library is a mid-level Haskell binding to the MySQL `mysqlclient`
+client library.  It is aimed at speed and ease of use.
+
+# 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-simple/issues).
+
+Master [git repository](http://github.com/mailrank/mysql-simple):
+
+* `git clone git://github.com/mailrank/mysql-simple.git`
+
+There's also a [Mercurial mirror](http://bitbucket.org/bos/mysql-simple):
+
+* `hg clone http://bitbucket.org/bos/mysql-simple`
+
+(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>.
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain

mysql-simple.cabal

+name:           mysql-simple
+version:        0.1.0.0
+homepage:       https://github.com/mailrank/mysql-simple
+bug-reports:    https://github.com/mailrank/mysql-simple/issues
+synopsis:       A mid-level MySQL client library.
+description:    
+    A mid-level client library for the MySQL database, intended to be
+    fast and easy to use.
+    .
+    /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:     Simple
+cabal-version:  >= 1.6
+extra-source-files:
+    README.markdown
+
+flag developer
+  description: operate in developer mode
+  default: False
+
+library
+  exposed-modules:
+    Database.MySQL.Simple
+    Database.MySQL.Simple.Param
+    Database.MySQL.Simple.QueryParams
+    Database.MySQL.Simple.QueryResults
+    Database.MySQL.Simple.Result
+    Database.MySQL.Simple.Types
+
+  build-depends:
+    attoparsec >= 0.8.5.3,
+    base < 5,
+    blaze-builder,
+    blaze-textual,
+    bytestring >= 0.9 && < 1.0,
+    mysql,
+    old-locale,
+    text >= 0.11.0.2,
+    time
+
+  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-simple
+
+source-repository head
+  type:     mercurial
+  location: http://bitbucket.org/bos/mysql-simple
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.