packages feed

sqlite-simple 0.4.4.0 → 0.4.5.0

raw patch · 14 files changed

+414/−77 lines, 14 filesdep +attoparsecdep +blaze-builderdep +blaze-textualdep −old-localedep ~basedep ~bytestringdep ~time

Dependencies added: attoparsec, blaze-builder, blaze-textual

Dependencies removed: old-locale

Dependency ranges changed: base, bytestring, time

Files

Database/SQLite/Simple.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}  ------------------------------------------------------------------------------ -- |@@ -8,7 +8,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- ------------------------------------------------------------------------------@@ -37,6 +36,10 @@      -- ** Type conversions     -- $types++    -- *** Conversion to/from UTCTime+    -- $utctime+     Query(..)   , Connection(..)   , ToRow(..)@@ -55,12 +58,13 @@   , query   , query_   , lastInsertRowId+    -- * Queries that stream results+  , fold+  , fold_     -- * Statements that do not return results   , execute   , execute_   , field-  , fold-  , fold_     -- * Low-level statement API for stream access and prepared statements   , openStatement   , closeStatement@@ -332,20 +336,32 @@  convertRow :: (FromRow r) => [Base.SQLData] -> Int -> IO r convertRow rowRes ncols = do-  let rw = Row rowRes-  case runStateT (runReaderT (unRP fromRow) rw) 0 of-    Ok (val,col) | col == ncols -> return val-                 | otherwise -> do-                     let vals = map (\f -> (gettypename f, f)) rowRes-                     throwIO (ConversionFailed-                       (show ncols ++ " values: " ++ show vals)-                       (show col ++ " slots in target type")-                       "mismatch between number of columns to \-                       \convert and number in target type")+  let rw = RowParseRO ncols+  case runStateT (runReaderT (unRP fromRow) rw) (0, rowRes) of+    Ok (val,(col,_))+       | col == ncols -> return val+       | otherwise -> errorColumnMismatch (ColumnOutOfBounds col)     Errors []  -> throwIO $ ConversionFailed "" "" "unknown error"-    Errors [x] -> throwIO x+    Errors [x] ->+      throw x `catch` (\e -> errorColumnMismatch (e :: ColumnOutOfBounds))     Errors xs  -> throwIO $ ManyErrors xs+  where+    errorColumnMismatch :: ColumnOutOfBounds -> IO r+    errorColumnMismatch (ColumnOutOfBounds c) = do+      let vals = map (\f -> (gettypename f, ellipsis f)) rowRes+      throwIO (ConversionFailed+               (show ncols ++ " values: " ++ show vals)+               ("at least " ++ show c ++ " slots in target type")+               "mismatch between number of columns to convert and number in target type") +    ellipsis :: Base.SQLData -> T.Text+    ellipsis sql+      | T.length bs > 20 = T.take 15 bs `T.append` "[...]"+      | otherwise        = bs+      where+        bs = T.pack $ show sql++ -- | Returns the rowid of the most recent successful INSERT on the -- given database connection. --@@ -572,3 +588,38 @@ -- -- You can extend conversion support to your own types be adding your -- own 'FromField' / 'ToField' instances.++-- $utctime+--+-- SQLite's datetime allows for multiple string representations of UTC+-- time.  The following formats are supported for reading SQLite times+-- into Haskell UTCTime values:+--+-- * YYYY-MM-DD HH:MM+--+-- * YYYY-MM-DD HH:MM:SS+--+-- * YYYY-MM-DD HH:MM:SS.SSS+--+-- * YYYY-MM-DDTHH:MM+--+-- * YYYY-MM-DDTHH:MM:SS+--+-- * YYYY-MM-DDTHH:MM:SS.SSS+--+-- The above may also be optionally followed by a timezone indicator+-- of the form \"[+-]HH:MM\" or just \"Z\".+--+-- When Haskell UTCTime values are converted into SQLite values (e.g.,+-- parameters for a 'query'), the following format is used:+--+-- * YYYY-MM-DD HH:MM:SS.SSS+--+-- The last \".SSS\" subsecond part is dropped if it's zero.  No+-- timezone indicator is used when converting from a UTCTime value+-- into an SQLite string.  SQLite assumes all datetimes are in UTC+-- time.+--+-- The parser and printers are implemented in <Database-SQLite-Simple-Time.html Database.SQLite.Simple.Time>.+--+-- Read more about SQLite's time strings in <http://sqlite.org/lang_datefunc.html>
Database/SQLite/Simple/FromField.hs view
@@ -10,7 +10,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- The 'FromField' typeclass, for converting a single value in a row@@ -42,18 +41,16 @@ import qualified Data.ByteString.Lazy as LB import           Data.Int (Int16, Int32, Int64) import           Data.Time (UTCTime, Day)-import           Data.Time.Format (parseTime) import qualified Data.Text as T import qualified Data.Text.Lazy as LT import           Data.Typeable (Typeable, typeOf) import           GHC.Float (double2Float) -import           System.Locale (defaultTimeLocale)- import           Database.SQLite3 as Base import           Database.SQLite.Simple.Types import           Database.SQLite.Simple.Internal import           Database.SQLite.Simple.Ok+import           Database.SQLite.Simple.Time  -- | Exception thrown if conversion from a SQL value to a Haskell -- value fails.@@ -164,18 +161,19 @@  instance FromField UTCTime where   fromField f@(Field (SQLText t) _) =-    case parseTime defaultTimeLocale "%F %X%Q" . T.unpack $ t of-      Just t -> Ok t-      Nothing -> returnError ConversionFailed f "couldn't parse UTCTime field"+    case parseUTCTime t of+      Right t -> Ok t+      Left e -> returnError ConversionFailed f ("couldn't parse UTCTime field: " ++ e) -  fromField f                     = returnError ConversionFailed f "expecting SQLText column type"+  fromField f = returnError ConversionFailed f "expecting SQLText column type"   instance FromField Day where   fromField f@(Field (SQLText t) _) =-    case parseTime defaultTimeLocale "%Y-%m-%d" . T.unpack $ t of-      Just t -> Ok t-      Nothing -> returnError ConversionFailed f "couldn't parse Day field"+    case parseDay t of+      Right t -> Ok t+      Left e -> returnError ConversionFailed f ("couldn't parse Day field: " ++ e)+   fromField f = returnError ConversionFailed f "expecting SQLText column type"  fieldTypename :: Field -> String
Database/SQLite/Simple/FromRow.hs view
@@ -7,7 +7,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- The 'FromRow' typeclass, for converting a row of results@@ -31,14 +30,11 @@ import           Control.Monad.Trans.State.Strict import           Control.Monad.Trans.Reader import           Control.Monad.Trans.Class-import           Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as B  import           Database.SQLite.Simple.FromField import           Database.SQLite.Simple.Internal import           Database.SQLite.Simple.Ok import           Database.SQLite.Simple.Types-import qualified Database.SQLite3 as Base  -- | A collection type that can be converted from a sequence of fields. -- Instances are provided for tuples up to 10 elements and lists of any length.@@ -64,42 +60,25 @@  fieldWith :: FieldParser a -> RowParser a fieldWith fieldP = RP $ do-    Row{..} <- ask-    column <- lift get-    lift (put (column + 1))-    let ncols = length rowresult-    if column >= ncols-    then do-      let vals = map (\c -> (gettypename (rowresult !! c)-                           , ellipsis (rowresult !! c)))-                     [0..ncols-1]-          convertError = ConversionFailed-              (show ncols ++ " values: " ++ show vals)-              ("at least " ++ show (column + 1)-                ++ " slots in target type")-              "mismatch between number of columns to \-              \convert and number in target type"-      lift (lift (Errors [SomeException convertError]))+    RowParseRO{..} <- ask+    (column, remaining) <- lift get+    lift (put (column + 1, tail remaining))+    if column >= nColumns+    then+      lift (lift (Errors [SomeException (ColumnOutOfBounds (column+1))]))     else do-      let r = rowresult !! column+      let r = head remaining           field = Field r column       lift (lift (fieldP field))  field :: FromField a => RowParser a field = fieldWith fromField -ellipsis :: Base.SQLData -> ByteString-ellipsis sql-    | B.length bs > 15 = B.take 10 bs `B.append` "[...]"-    | otherwise        = bs-  where-    bs = B.pack $ show sql- numFieldsRemaining :: RowParser Int numFieldsRemaining = RP $ do-    Row{..} <- ask-    column <- lift get-    return $! length rowresult - column+  RowParseRO{..} <- ask+  (columnIdx,_) <- lift get+  return $! nColumns - columnIdx  instance (FromField a) => FromRow (Only a) where     fromRow = Only <$> field
Database/SQLite/Simple/Internal.hs view
@@ -6,7 +6,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- Internal bits.  This interface is less stable and can change at any time.@@ -22,10 +21,12 @@  import           Prelude hiding (catch) +import           Control.Exception (Exception) import           Control.Monad import           Control.Applicative import           Data.ByteString (ByteString) import           Data.ByteString.Char8()+import           Data.Typeable (Typeable) import           Control.Monad.Trans.State.Strict import           Control.Monad.Trans.Reader @@ -42,16 +43,22 @@ -- discouraged. newtype Connection = Connection { connectionHandle :: Base.Database } --- | A Field represents metadata about a particular field+data ColumnOutOfBounds = ColumnOutOfBounds { errorColumnIndex :: Int }+                      deriving (Eq, Show, Typeable) +instance Exception ColumnOutOfBounds++-- | A Field represents metadata about a particular field data Field = Field {      result   :: Base.SQLData    , column   :: {-# UNPACK #-} !Int    } -newtype Row = Row { rowresult  :: [Base.SQLData] }+-- Named type for holding RowParser read-only state.  Just for making+-- it easier to make sense out of types in FromRow.+newtype RowParseRO = RowParseRO { nColumns :: Int } -newtype RowParser a = RP { unRP :: ReaderT Row (StateT Int Ok) a }+newtype RowParser a = RP { unRP :: ReaderT RowParseRO (StateT (Int, [Base.SQLData]) Ok) a }    deriving ( Functor, Applicative, Alternative, Monad, MonadPlus )  gettypename :: Base.SQLData -> ByteString
Database/SQLite/Simple/Ok.hs view
@@ -8,7 +8,6 @@ --             (c) 2012-2013 Janne Hellsten -- License:    BSD3 -- Maintainer: Janne Hellsten <jjhellst@gmail.com>--- Stability:  experimental -- -- The 'Ok' type is a simple error handler,  basically equivalent to -- @Either [SomeException]@.
+ Database/SQLite/Simple/Time.hs view
@@ -0,0 +1,21 @@+------------------------------------------------------------------------------+-- |+-- Module:      Database.SQLite.Simple.Time+-- Copyright:   (c) 2012 Leon P Smith+--              (c) 2012-2014 Janne Hellsten+-- License:     BSD3+-- Maintainer:  Janne Hellsten <jjhellst@gmail.com>+--+-- Conversions to/from Haskell 'UTCTime' and 'Day' types for SQLite3.+-- Offers better performance than direct use of time package's+-- 'read'/'show' functionality.+--+-- The parsers are heavily adapted for the specific variant of ISO 8601 that+-- SQLite uses,  and the printers attempt to duplicate this syntax.+------------------------------------------------------------------------------++module Database.SQLite.Simple.Time (+    module Database.SQLite.Simple.Time.Implementation+  ) where++import Database.SQLite.Simple.Time.Implementation
+ Database/SQLite/Simple/Time/Implementation.hs view
@@ -0,0 +1,200 @@+------------------------------------------------------------------------------+-- |+-- Module:      Database.SQLite.Simple.Time.Implementation+-- Copyright:   (c) 2012 Leon P Smith+--              (c) 2012-2014 Janne Hellsten+-- License:     BSD3+-- Maintainer:  Janne Hellsten <jjhellst@gmail.com>+--+-- Adapted from Leon P Smith's code for SQLite.+--+-- See <http://sqlite.org/lang_datefunc.html> for date formats used in SQLite.+------------------------------------------------------------------------------++module Database.SQLite.Simple.Time.Implementation (+    parseUTCTime+  , parseDay+  , utcTimeToBuilder+  , dayToBuilder+  , timeOfDayToBuilder+  , timeZoneToBuilder+  ) where+import           Blaze.ByteString.Builder (Builder)+import           Blaze.ByteString.Builder.Char8 (fromChar)+import           Blaze.Text.Int (integral)+import           Control.Applicative+import           Control.Monad (when)+import qualified Data.Attoparsec.Text as A+import           Data.Bits ((.&.))+import           Data.ByteString.Internal (w2c)+import           Data.Char (isDigit, ord)+import           Data.Fixed (Pico)+import           Data.Monoid (Monoid(..))+import qualified Data.Text as T+import           Data.Time hiding (getTimeZone, getZonedTime)+import           Prelude hiding (take, (++))+import           Unsafe.Coerce++(++) :: Monoid a => a -> a -> a+(++) = mappend+infixr 5 ++++parseUTCTime   :: T.Text -> Either String UTCTime+parseUTCTime   = A.parseOnly (getUTCTime <* A.endOfInput)++parseDay :: T.Text -> Either String Day+parseDay = A.parseOnly (getDay <* A.endOfInput)++getDay :: A.Parser Day+getDay = do+    yearStr <- A.takeWhile isDigit+    when (T.length yearStr < 4) (fail "year must consist of at least 4 digits")++    let !year = toNum yearStr+    _       <- A.char '-'+    month   <- digits "month"+    _       <- A.char '-'+    day     <- digits "day"++    case fromGregorianValid year month day of+      Nothing -> fail "invalid date"+      Just x  -> return $! x++decimal :: Fractional a => T.Text -> a+decimal str = toNum str / 10^(T.length str)+{-# INLINE decimal #-}++getTimeOfDay :: A.Parser TimeOfDay+getTimeOfDay = do+    hour   <- digits "hours"+    _      <- A.char ':'+    minute <- digits "minutes"+    -- Allow omission of seconds.  If seconds is omitted, don't try to+    -- parse the sub-second part.+    (sec,subsec)+           <- ((,) <$> (A.char ':' *> digits "seconds") <*> fract) <|> pure (0,0)++    let !picos' = sec + subsec++    case makeTimeOfDayValid hour minute picos' of+      Nothing -> fail "invalid time of day"+      Just x  -> return $! x++    where+      fract =+        (A.char '.' *> (decimal <$> A.takeWhile1 isDigit)) <|> pure 0++getTimeZone :: A.Parser TimeZone+getTimeZone = do+    sign  <- A.satisfy (\c -> c == '+' || c == '-')+    hours <- digits "timezone"+    mins  <- (A.char ':' *> digits "timezone minutes") <|> pure 0+    let !absset = 60 * hours + mins+        !offset = if sign == '+' then absset else -absset+    return $! minutesToTimeZone offset++getUTCTime :: A.Parser UTCTime+getUTCTime = do+    day  <- getDay+    _    <- A.char ' ' <|> A.char 'T'+    time <- getTimeOfDay+    -- SQLite doesn't require a timezone postfix.  So make that+    -- optional and default to +0.  'Z' means UTC (zulu time).+    zone <- getTimeZone <|> (A.char 'Z' *> pure utc) <|> (pure utc)+    let (!dayDelta,!time') = localToUTCTimeOfDay zone time+    let !day' = addDays dayDelta day+    let !time'' = timeOfDayToTime time'+    return (UTCTime day' time'')++toNum :: Num n => T.Text -> n+toNum = T.foldl' (\a c -> 10*a + digit c) 0+{-# INLINE toNum #-}++digit :: Num n => Char -> n+digit c = fromIntegral (ord c .&. 0x0f)+{-# INLINE digit #-}++digits :: Num n => String -> A.Parser n+digits msg = do+  x <- A.anyChar+  y <- A.anyChar+  if isDigit x && isDigit y+  then return $! (10 * digit x + digit y)+  else fail (msg ++ " is not 2 digits")+{-# INLINE digits #-}++dayToBuilder :: Day -> Builder+dayToBuilder (toGregorian -> (y,m,d)) = do+    pad4 y ++ fromChar '-' ++ pad2 m ++ fromChar '-' ++ pad2 d++timeOfDayToBuilder :: TimeOfDay -> Builder+timeOfDayToBuilder (TimeOfDay h m s) = do+    pad2 h ++ fromChar ':' ++ pad2 m ++ fromChar ':' ++ showSeconds s++timeZoneToBuilder :: TimeZone -> Builder+timeZoneToBuilder tz+    | m == 0     =  sign h ++ pad2 (abs h)+    | otherwise  =  sign h ++ pad2 (abs h) ++ fromChar ':' ++ pad2 (abs m)+  where+    (h,m) = timeZoneMinutes tz `quotRem` 60+    sign h | h >= 0    = fromChar '+'+           | otherwise = fromChar '-'++-- | Output YYYY-MM-DD HH:MM:SS with an optional .SSS fraction part.+-- Explicit timezone attribute is not appended as per SQLite3's+-- datetime conventions.+utcTimeToBuilder :: UTCTime -> Builder+utcTimeToBuilder (UTCTime day time) =+    dayToBuilder day ++ fromChar ' ' ++ timeOfDayToBuilder (timeToTimeOfDay time)++showSeconds :: Pico -> Builder+showSeconds xyz+    | yz == 0   = pad2 x+    | z  == 0   = pad2 x ++ fromChar '.' ++  showD6 y+    | otherwise = pad2 x ++ fromChar '.' ++  pad6   y ++ showD6 z+  where+    -- A kludge to work around the fact that Data.Fixed isn't very fast and+    -- doesn't give me access to the MkFixed constructor.+    (x_,yz) = (unsafeCoerce xyz :: Integer)     `quotRem` 1000000000000+    x = fromIntegral x_ :: Int+    (fromIntegral -> y, fromIntegral -> z) = yz `quotRem` 1000000++pad6 :: Int -> Builder+pad6 xy = let (x,y) = xy `quotRem` 1000+           in pad3 x ++ pad3 y++showD6 :: Int -> Builder+showD6 xy = case xy `quotRem` 1000 of+              (x,0) -> showD3 x+              (x,y) -> pad3 x ++ showD3 y++pad3 :: Int -> Builder+pad3 abc = let (ab,c) = abc `quotRem` 10+               (a,b)  = ab  `quotRem` 10+            in p a ++ p b ++ p c++showD3 :: Int -> Builder+showD3 abc = case abc `quotRem` 100 of+              (a, 0) -> p a+              (a,bc) -> case bc `quotRem` 10 of+                          (b,0) -> p a ++ p b+                          (b,c) -> p a ++ p b ++ p c++-- | p assumes its input is in the range [0..9]+p :: Integral n => n -> Builder+p n = fromChar (w2c (fromIntegral (n + 48)))+{-# INLINE p #-}++-- | pad2 assumes its input is in the range [0..99]+pad2 :: Integral n => n -> Builder+pad2 n = let (a,b) = n `quotRem` 10 in p a ++ p b+{-# INLINE pad2 #-}++-- | pad4 assumes its input is positive+pad4 :: (Integral n, Show n) => n -> Builder+pad4 abcd | abcd >= 10000 = integral abcd+          | otherwise     = p a ++ p b ++ p c ++ p d+  where (ab,cd) = abcd `quotRem` 100+        (a,b)   = ab   `quotRem` 10+        (c,d)   = cd   `quotRem` 10+{-# INLINE pad4 #-}
Database/SQLite/Simple/ToField.hs view
@@ -9,7 +9,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- The 'ToField' typeclass, for rendering a parameter to an SQLite@@ -19,19 +18,20 @@  module Database.SQLite.Simple.ToField (ToField(..)) where +import           Blaze.ByteString.Builder (toByteString) import qualified Data.ByteString as SB import qualified Data.ByteString.Lazy as LB import           Data.Int (Int8, Int16, Int32, Int64) import qualified Data.Text as T import qualified Data.Text.Lazy as LT+import qualified Data.Text.Encoding as T import           Data.Time (Day, UTCTime)-import           Data.Time.Format (formatTime) import           Data.Word (Word, Word8, Word16, Word32, Word64) import           GHC.Float-import           System.Locale (defaultTimeLocale)  import           Database.SQLite3 as Base import           Database.SQLite.Simple.Types (Null)+import           Database.SQLite.Simple.Time  -- | A type that may be used as a single parameter to a SQL query. class ToField a where@@ -129,11 +129,11 @@     {-# INLINE toField #-}  instance ToField UTCTime where-    toField = SQLText . T.pack . formatTime defaultTimeLocale "%F %X%Q"+    toField = SQLText . T.decodeUtf8 . toByteString . utcTimeToBuilder     {-# INLINE toField #-}  instance ToField Day where-    toField = SQLText . T.pack . show+    toField = SQLText . T.decodeUtf8 . toByteString . dayToBuilder     {-# INLINE toField #-}  -- TODO enable these
Database/SQLite/Simple/ToRow.hs view
@@ -6,7 +6,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- The 'ToRow' typeclass, for rendering a collection of
Database/SQLite/Simple/Types.hs view
@@ -8,7 +8,6 @@ --              (c) 2012-2013 Janne Hellsten -- License:     BSD3 -- Maintainer:  Janne Hellsten <jjhellst@gmail.com>--- Stability:   experimental -- Portability: portable -- -- Top-level module for sqlite-simple.
README.markdown view
@@ -10,10 +10,17 @@ in turn borrows from [mysql-simple](https://github.com/bos/mysql-simple). -The library has been fairly well unit tested.  It's also been benchmarked using-[db-bench](https://github.com/nurpax/db-bench) which is a performance comparison suite -for measuring database access overhead when using the various Haskell database libraries.+[The sqlite-simple API+reference](https://hackage.haskell.org/package/sqlite-simple/docs/Database-SQLite-Simple.html)+contains more examples of use and information on its features. +The library is well tested and stable.  The library should also be+reasonably performant.  You can find its benchmark suite here:+[db-bench](https://github.com/nurpax/db-bench).  You can read more+about sqlite-simple's expected performance in [my blog about+sqlite-simple performance against direct-sqlite, Python and+C](http://nurpax.github.io/posts/2013-08-17-sqlite-simple-benchmarking.html).+ [![Build Status](https://secure.travis-ci.org/nurpax/sqlite-simple.png)](http://travis-ci.org/nurpax/sqlite-simple)  Installation@@ -84,7 +91,7 @@  ### Contributing -If you send pull requests for new features, it'd be great if you could also develop unit +If you send pull requests for new features, it'd be great if you could also develop unit tests for any such features.  
sqlite-simple.cabal view
@@ -1,5 +1,5 @@ Name:                sqlite-simple-Version:             0.4.4.0+Version:             0.4.5.0 Synopsis:            Mid-Level SQLite client library Description:     Mid-level SQLite client library, based on postgresql-simple.@@ -15,9 +15,10 @@ Maintainer:          Janne Hellsten <jjhellst@gmail.com> Copyright:           (c) 2011 MailRank, Inc.,                      (c) 2011-2012 Leon P Smith,-                     (c) 2012-2013 Janne Hellsten+                     (c) 2012-2014 Janne Hellsten Homepage:            http://github.com/nurpax/sqlite-simple bug-reports:         http://github.com/nurpax/sqlite-simple/issues+Stability:           stable Category:            Database Build-type:          Simple @@ -36,15 +37,19 @@      Database.SQLite.Simple.ToField      Database.SQLite.Simple.ToRow      Database.SQLite.Simple.Types+     Database.SQLite.Simple.Time+     Database.SQLite.Simple.Time.Implementation    Build-depends:+    attoparsec >= 0.10.3,     base < 5,+    blaze-builder,+    blaze-textual,     bytestring >= 0.9,     containers,     direct-sqlite >= 2.3.4 && < 2.4,     text >= 0.11,     time,-    old-locale >= 1.0.0.0,     transformers    default-extensions:@@ -89,7 +94,7 @@    build-depends: base                , base16-bytestring-               , bytestring+               , bytestring >= 0.9                , HUnit                , sqlite-simple                , direct-sqlite
test/Main.hs view
@@ -24,6 +24,9 @@     , TestLabel "Simple"    . testSimpleTime     , TestLabel "Simple"    . testSimpleTimeFract     , TestLabel "Simple"    . testSimpleInsertId+    , TestLabel "Simple"    . testSimpleUTCTime+    , TestLabel "Simple"    . testSimpleUTCTimeTZ+    , TestLabel "Simple"    . testSimpleUTCTimeParams     , TestLabel "ParamConv" . testParamConvInt     , TestLabel "ParamConv" . testParamConvFloat     , TestLabel "ParamConv" . testParamConvDateTime
test/Simple.hs view
@@ -7,6 +7,9 @@   , testSimpleTime   , testSimpleTimeFract   , testSimpleInsertId+  , testSimpleUTCTime+  , testSimpleUTCTimeTZ+  , testSimpleUTCTimeParams   ) where  import qualified Data.Text as T@@ -126,3 +129,69 @@   (Only "test string") @=? (head rows)   [Only row] <- query conn "SELECT t FROM test_row_id WHERE id = ?" (Only (2 :: Int)) :: IO [Only String]   "test2" @=? row++testSimpleUTCTime :: TestEnv -> Test+testSimpleUTCTime TestEnv{..} = TestCase $ do+  -- Time formats understood by sqlite: http://sqlite.org/lang_datefunc.html+  let timestrs = [ "2012-08-17 13:25"+                 , "2012-08-17 13:25:44"+                 , "2012-08-17 13:25:44.123"+                 ]+      timestrsWithT = map (T.map (\c -> if c == ' ' then 'T' else c)) timestrs+  execute_ conn "CREATE TABLE utctimes (t TIMESTAMP)"+  mapM_ (\t -> execute conn "INSERT INTO utctimes (t) VALUES (?)" (Only t)) timestrs+  mapM_ (\t -> execute conn "INSERT INTO utctimes (t) VALUES (?)" (Only t)) timestrsWithT+  dates <- query_ conn "SELECT t from utctimes" :: IO [Only UTCTime]+  mapM_ matchDates (zip (timestrs ++ timestrsWithT) dates)+  let zulu = "2012-08-17 13:25"+  [d] <- query conn "SELECT ?" (Only (T.append zulu "Z"))+  matchDates (zulu, d)+  let zulu = "2012-08-17 13:25:00"+  [d] <- query conn "SELECT ?" (Only (T.append zulu "Z"))+  matchDates (zulu, d)+  where+    matchDates (str,(Only date)) = do+      -- Remove 'T' when reading in to Haskell+      let t = read (makeReadable str) :: UTCTime+      t @=? date++    makeReadable s =+      let s' = if T.length s < T.length "YYYY-MM-DD HH:MM:SS" then T.append s ":00" else s+      in T.unpack . T.replace "T" " " $ s'++testSimpleUTCTimeTZ :: TestEnv -> Test+testSimpleUTCTimeTZ TestEnv{..} = TestCase $ do+  -- Time formats understood by sqlite: http://sqlite.org/lang_datefunc.html+  let timestrs = [ "2013-02-03 13:00:00-02:00"+                 , "2013-02-03 13:00:00-01:00"+                 , "2013-02-03 13:00:00-03:00"+                 , "2013-02-03 13:00:00Z"+                 , "2013-02-03 13:00:00+00:00"+                 , "2013-02-03 13:00:00+03:00"+                 , "2013-02-03 13:00:00+02:00"+                 , "2013-02-03 13:00:00+04:00"+                 ]+  execute_ conn "CREATE TABLE utctimestz (t TIMESTAMP)"+  mapM_ (\t -> execute conn "INSERT INTO utctimestz (t) VALUES (?)" (Only t)) timestrs+  dates <- query_ conn "SELECT t from utctimestz" :: IO [Only UTCTime]+  mapM_ matchDates (zip (timestrs) dates)+  where+    matchDates (str,(Only date)) = do+      -- Remove 'T' when reading in to Haskell+      let t = read . T.unpack $ str :: UTCTime+      t @=? date++testSimpleUTCTimeParams :: TestEnv -> Test+testSimpleUTCTimeParams TestEnv{..} = TestCase $ do+  let times = [ "2012-08-17 08:00:03"+              , "2012-08-17 08:00:03.2"+              , "2012-08-17 08:00:03.256"+              , "2012-08-17 08:00:03.4192"+              ]+  -- Try inserting timestamp directly as a string+  mapM_ assertResult times+  where+    assertResult tstr = do+      let utct = read . T.unpack $ tstr :: UTCTime+      [Only t] <- query conn "SELECT ?" (Only utct) :: IO [Only T.Text]+      assertEqual "UTCTime" tstr t