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 +66/−15
- Database/SQLite/Simple/FromField.hs +9/−11
- Database/SQLite/Simple/FromRow.hs +10/−31
- Database/SQLite/Simple/Internal.hs +11/−4
- Database/SQLite/Simple/Ok.hs +0/−1
- Database/SQLite/Simple/Time.hs +21/−0
- Database/SQLite/Simple/Time/Implementation.hs +200/−0
- Database/SQLite/Simple/ToField.hs +5/−5
- Database/SQLite/Simple/ToRow.hs +0/−1
- Database/SQLite/Simple/Types.hs +0/−1
- README.markdown +11/−4
- sqlite-simple.cabal +9/−4
- test/Main.hs +3/−0
- test/Simple.hs +69/−0
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).+ [](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