hpqtypes-1.2.2: src/Database/PostgreSQL/PQTypes/FromSQL.hs
{-# LANGUAGE BangPatterns, FlexibleContexts, FlexibleInstances
, RecordWildCards, ScopedTypeVariables, TypeFamilies #-}
module Database.PostgreSQL.PQTypes.FromSQL (
FromSQL(..)
) where
import Control.Applicative
import Data.Int
import Data.Ratio
import Data.Text.Encoding
import Data.Time
import Data.Word
import Foreign.C
import Foreign.Storable
import qualified Control.Exception as E
import qualified Data.ByteString.Char8 as BS
import qualified Data.Text as T
import Database.PostgreSQL.PQTypes.Format
import Database.PostgreSQL.PQTypes.Internal.C.Types
import Database.PostgreSQL.PQTypes.Internal.Utils
-- | Class which represents \"from SQL (libpqtypes)
-- type to Haskell type\" transformation.
class (PQFormat t, Storable (PQBase t)) => FromSQL t where
-- | Base type (used by libpqtypes).
type PQBase t :: *
-- | Convert value of base type to target one.
fromSQL :: Maybe (PQBase t) -- ^ base value (Nothing if NULL was delivered)
-> IO t
-- NULLables
instance FromSQL t => FromSQL (Maybe t) where
type PQBase (Maybe t) = PQBase t
fromSQL mbase = case mbase of
Just _ -> Just <$> fromSQL mbase
Nothing -> return Nothing
-- NUMERICS
instance FromSQL Int16 where
type PQBase Int16 = CShort
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = return . fromIntegral $ n
instance FromSQL Int32 where
type PQBase Int32 = CInt
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = return . fromIntegral $ n
instance FromSQL Int64 where
type PQBase Int64 = CLLong
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = return . fromIntegral $ n
instance FromSQL Float where
type PQBase Float = CFloat
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = return . realToFrac $ n
instance FromSQL Double where
type PQBase Double = CDouble
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = return . realToFrac $ n
-- CHAR
instance FromSQL Char where
type PQBase Char = CChar
fromSQL Nothing = unexpectedNULL
fromSQL (Just c) = return . castCCharToChar $ c
instance FromSQL Word8 where
type PQBase Word8 = CChar
fromSQL Nothing = unexpectedNULL
fromSQL (Just c) = return . fromIntegral $ c
-- VARIABLE-LENGTH CHARACTER TYPES
instance FromSQL BS.ByteString where
type PQBase BS.ByteString = PGbytea
fromSQL Nothing = unexpectedNULL
fromSQL (Just bytea) = BS.packCStringLen $ byteaToCStringLen bytea
-- | Assumes that source C string is UTF-8, so if you are working
-- with a different encoding, you should not rely on this instance.
instance FromSQL T.Text where
type PQBase T.Text = PGbytea
fromSQL mbytea = either E.throwIO return . decodeUtf8' =<< fromSQL mbytea
-- | Assumes that source C string is UTF-8, so if you are working
-- with a different encoding, you should not rely on this instance.
instance FromSQL String where
type PQBase String = PGbytea
fromSQL mbytea = T.unpack <$> fromSQL mbytea
-- DATE
instance FromSQL Day where
type PQBase Day = PGdate
fromSQL Nothing = unexpectedNULL
fromSQL (Just date) = return . pgDateToDay $ date
-- TIME
instance FromSQL TimeOfDay where
type PQBase TimeOfDay = PGtime
fromSQL Nothing = unexpectedNULL
fromSQL (Just time) = return . pgTimeToTimeOfDay $ time
-- TIMESTAMP
instance FromSQL LocalTime where
type PQBase LocalTime = PGtimestamp
fromSQL Nothing = unexpectedNULL
fromSQL (Just PGtimestamp{..}) = return $ LocalTime day tod
where
day = pgDateToDay pgTimestampDate
tod = pgTimeToTimeOfDay pgTimestampTime
-- TIMESTAMPTZ
instance FromSQL UTCTime where
type PQBase UTCTime = PGtimestamp
fromSQL = localToZoned localTimeToUTC
instance FromSQL ZonedTime where
type PQBase ZonedTime = PGtimestamp
fromSQL = localToZoned (flip ZonedTime)
-- BOOL
instance FromSQL Bool where
type PQBase Bool = CInt
fromSQL Nothing = unexpectedNULL
fromSQL (Just n) = case n of
0 -> return False
_ -> return True
----------------------------------------
-- | Convert PGtime to Day.
pgDateToDay :: PGdate -> Day
pgDateToDay PGdate{..} = fromGregorian year mon mday
where
year = adjustBC $ fromIntegral pgDateYear
-- Note: libpqtypes represents months as numbers in range
-- [0, 11], whereas Haskell uses [1, 12], hence plus one.
mon = fromIntegral $ pgDateMon + 1
mday = fromIntegral pgDateMDay
-- Note: PostgreSQL has no notion of '0th year', it's 1 AD
-- and then before that 1 BC for it. Since Haskell represents
-- date according to ISO-8601, where 0th year means 1 BC, we
-- want to change the sign and adjust the year by one here,
-- if appropriate.
adjustBC = if pgDateIsBC == 1 then negate . pred else id
-- | Convert PGtime to TimeOfDay.
pgTimeToTimeOfDay :: PGtime -> TimeOfDay
pgTimeToTimeOfDay PGtime{..} = TimeOfDay hour mins $ sec + fromRational (usec % 1000000)
where
hour = fromIntegral pgTimeHour
mins = fromIntegral pgTimeMin
sec = fromIntegral pgTimeSec
usec = fromIntegral pgTimeUSec
-- | Helper for converting local time to either 'ZonedTime' or 'UTCTime'.
localToZoned :: (TimeZone -> LocalTime -> a) -> Maybe PGtimestamp -> IO a
localToZoned _ Nothing = unexpectedNULL
localToZoned construct jts@(Just PGtimestamp{..}) = do
localTime <- fromSQL jts
case rest of
0 -> return . construct (minutesToTimeZone mins) $ localTime
_ -> hpqTypesError $ "Invalid gmtoff: " ++ show gmtoff
where
gmtoff = pgTimeGMTOff pgTimestampTime
(mins, rest) = fromIntegral gmtoff `divMod` 60