hpqtypes-1.8.0.0: src/Database/PostgreSQL/PQTypes/FromSQL.hs
module Database.PostgreSQL.PQTypes.FromSQL (
FromSQL(..)
) where
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.ByteString.Lazy.Char8 as BSL
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.UUID.Types as U
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
-- | 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 TL.Text where
type PQBase TL.Text = PGbytea
fromSQL = fmap TL.fromStrict . fromSQL
-- | 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
instance FromSQL U.UUID where
type PQBase U.UUID = PGuuid
fromSQL Nothing = unexpectedNULL
fromSQL (Just (PGuuid w1 w2 w3 w4)) = return $ U.fromWords w1 w2 w3 w4
-- BYTEA
instance FromSQL BS.ByteString where
type PQBase BS.ByteString = PGbytea
fromSQL Nothing = unexpectedNULL
fromSQL (Just bytea) = BS.packCStringLen $ byteaToCStringLen bytea
instance FromSQL BSL.ByteString where
type PQBase BSL.ByteString = PGbytea
fromSQL = fmap BSL.fromStrict . fromSQL
-- 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
-- | 'FromSQL' instance for 'ZonedTime' doesn't exist because
-- PostgreSQL doesn't provide zone offset information when returning
-- timestamps with time zone in a binary format.
instance FromSQL UTCTime where
type PQBase UTCTime = PGtimestamp
fromSQL Nothing = unexpectedNULL
fromSQL jts@(Just PGtimestamp{..}) = do
localTime <- fromSQL jts
case rest of
0 -> return . localTimeToUTC (minutesToTimeZone mins) $ localTime
_ -> hpqTypesError $ "Invalid gmtoff: " ++ show gmtoff
where
gmtoff = pgTimeGMTOff pgTimestampTime
(mins, rest) = fromIntegral gmtoff `divMod` 60
-- 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