module Hasql.Postgres.Parser where
import Hasql.Postgres.Prelude hiding (take)
import Data.Attoparsec.ByteString
import Data.Attoparsec.ByteString.Char8 hiding (double)
import qualified Data.ByteString
import qualified Data.ByteString.Lazy
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.Encoding
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Database.PostgreSQL.LibPQ as PQ
type P = Parser
run :: ByteString -> P a -> Either Text a
run input parser =
left fromString $ parseOnly (parser <* endOfInput) input
labeling :: String -> Parser a -> Parser a
labeling n p =
p <?> n
scientific :: P Scientific
scientific =
A.scientific
float :: P Float
float =
realToFrac <$> double
double :: P Double
double =
labeling "double" $ A.double
bool :: P Bool
bool =
labeling "bool" $
((string "true" <|> string "t" <|> string "True" <|> string "1") *> pure True) <|>
((string "false" <|> string "f" <|> string "False" <|> string "0") *> pure False)
byteString :: P ByteString
byteString =
labeling "byteString" $
takeByteString >>= maybe (fail "Improper encoding") return . unsafePerformIO . PQ.unescapeBytea
lazyByteString :: P LazyByteString
lazyByteString =
labeling "lazyByteString" $
Data.ByteString.Lazy.fromStrict <$> byteString
utf8Char :: P Char
utf8Char =
labeling "utf8Char" $
asum $ map byLength [1..4]
where
byLength l =
do
b <- take l
t <- either (const empty) return $ Data.Text.Encoding.decodeUtf8' b
(c, _) <- maybe empty return $ Data.Text.uncons t
return c
utf8LazyText :: P Data.Text.Lazy.Text
utf8LazyText =
labeling "utf8LazyText" $ do
b <- takeLazyByteString
either (const empty) return $ Data.Text.Lazy.Encoding.decodeUtf8' b
utf8Text :: P Text
utf8Text =
Data.Text.Lazy.toStrict <$> utf8LazyText
charUnit :: Char -> P ()
charUnit c =
skip ((==) (fromIntegral (ord c)))
integral :: (Integral a, Num a) => P a
integral =
signed decimal
unsignedIntegral :: (Integral a, Num a) => P a
unsignedIntegral =
decimal
integralDigit :: Integral a => P a
integralDigit =
satisfyWith (subtract 48 . fromIntegral) (\n -> n < 10 && n >= 0)
day :: P Day
day =
do
y <- unsignedIntegral
charUnit '-'
m <- unsignedIntegral
charUnit '-'
d <- unsignedIntegral
maybe empty return (fromGregorianValid y m d)
timeOfDay :: P TimeOfDay
timeOfDay =
do
h <- unsignedIntegral
charUnit ':'
m <- unsignedIntegral
charUnit ':'
s <- unsignedIntegral
p <- (charUnit '.' *> decimals) <|> pure 0
maybe empty return
(makeTimeOfDayValid h m (fromIntegral s + p))
where
decimals = do
(b, i) <- match unsignedIntegral
return $ fromIntegral i / (10 ^ Data.ByteString.length b)
localTime :: P LocalTime
localTime =
LocalTime <$> day <*> (charUnit ' ' *> timeOfDay)
timeZoneTuple :: P (Bool, Int, Int, Int)
timeZoneTuple =
do
p <- (charUnit '+' *> pure True) <|> (charUnit '-' *> pure False)
h <- unsignedIntegral
m <- (charUnit ':' *> unsignedIntegral) <|> pure 0
s <- (charUnit ':' *> unsignedIntegral) <|> pure 0
return $! (p, h, m, s)
timeZone :: P TimeZone
timeZone =
do
(p, h, m, s) <- timeZoneTuple
return $!
minutesToTimeZone ((Hasql.Postgres.Prelude.bool negate id p) (60 * h + m))
zonedTime :: P ZonedTime
zonedTime =
do
LocalTime d t <- localTime
(zp, zh, zm, zs) <- timeZoneTuple
return $ ZonedTime (LocalTime d (timeOfDayDiffSecs zs t)) (composeTimezone zp zh zm)
where
timeOfDayDiffSecs s =
if s /= 0
then \t -> timeToTimeOfDay $ timeOfDayToTime t fromIntegral s
else id
composeTimezone p h m =
minutesToTimeZone ((Hasql.Postgres.Prelude.bool negate id p) (60 * h + m))
utcTime :: P UTCTime
utcTime =
UTCTime <$> day <*> (charUnit ' ' *> diffTime)
diffTime :: P DiffTime
diffTime = timeOfDayToTime <$> timeOfDay