module Hasql.Core.Protocol.Decoding where
import Hasql.Prelude
import Hasql.Core.Protocol.Model
import BinaryParser
import qualified Data.Vector as A
import qualified Hasql.Core.ParseDataRow as F
word8 :: BinaryParser Word8
word8 =
byte
word16 :: BinaryParser Word16
word16 =
beWord16
word32 :: BinaryParser Word32
word32 =
beWord32
int32 :: BinaryParser Int32
int32 =
fromIntegral <$> beWord32
messageTypeAndLength :: (MessageType -> PayloadLength -> a) -> BinaryParser a
messageTypeAndLength cont =
cont <$> messageType <*> payloadLength
messageType :: BinaryParser MessageType
messageType =
MessageType <$> word8
payloadLength :: BinaryParser PayloadLength
payloadLength =
PayloadLength . subtract 4 . fromIntegral <$> word32
nullableSizedValue :: BinaryParser a -> BinaryParser (Maybe a)
nullableSizedValue value =
do
size <- int32
case size of
1 -> return Nothing
_ -> sized (fromIntegral size) (fmap Just value)
sizedValue :: BinaryParser a -> BinaryParser a
sizedValue value =
do
size <- int32
case size of
1 -> failure "Unexpected null"
_ -> sized (fromIntegral size) value
commandCompleteMessageAffectedRows :: BinaryParser Int
commandCompleteMessageAffectedRows =
do
header <- bytesWhile byteIsUpperLetter
byte
case header of
"INSERT" -> unitWhile byteIsDecimal *> byte *> asciiIntegral <* byte
_ -> asciiIntegral <* byte
where
byteIsUpperLetter byte =
byte 65 <= 25
byteIsDecimal byte =
byte 48 <= 9
errorMessage :: (ByteString -> ByteString -> errorMessage) -> BinaryParser errorMessage
errorMessage errorMessage =
do
tupleFn <- loop id
case tupleFn (Nothing, Nothing) of
(Just v1, Just v2) -> return (errorMessage v1 v2)
_ -> failure "Some of the error fields are missing"
where
loop state =
(noticeField fieldState >>= id >>= loop) <|> pure state
where
fieldState =
\case
CodeNoticeFieldType -> \payload -> pure (state . (\(v1, v2) -> (Just payload, v2)))
MessageNoticeFieldType -> \payload -> pure (state . (\(v1, v2) -> (v1, Just payload)))
_ -> \_ -> pure state
noticeField :: (NoticeFieldType -> ByteString -> a) -> BinaryParser a
noticeField cont =
cont <$> noticeFieldType <*> nullTerminatedString
noticeFieldType :: BinaryParser NoticeFieldType
noticeFieldType =
NoticeFieldType <$> word8
nullTerminatedString :: BinaryParser ByteString
nullTerminatedString =
bytesWhile (/= 0) <* byte
protocolVersion :: BinaryParser (Word16, Word16)
protocolVersion =
(,) <$> word16 <*> word16
authenticationMessage :: BinaryParser AuthenticationMessage
authenticationMessage =
do
method <- word32
case method of
0 -> return OkAuthenticationMessage
3 -> return ClearTextPasswordAuthenticationMessage
5 -> MD5PasswordAuthenticationMessage <$> remainders
_ -> failure ("Unsupported authentication method: " <> (fromString . show) method)
notificationMessage :: (Word32 -> ByteString -> ByteString -> result) -> BinaryParser result
notificationMessage cont =
cont <$> word32 <*> nullTerminatedString <*> nullTerminatedString
dataRowMessage :: (Word16 -> BinaryParser a) -> BinaryParser a
dataRowMessage contentsParser =
do
amountOfColumns <- word16
contentsParser amountOfColumns
parseDataRow :: F.ParseDataRow a -> BinaryParser a
parseDataRow (F.ParseDataRow columnsAmount vectorFn) =
do
actualColumnsAmount <- fromIntegral <$> word16
if actualColumnsAmount == columnsAmount
then do
bytesVector <- A.replicateM actualColumnsAmount sizedBytes
either throwError return (vectorFn bytesVector 0)
else throwError ("Invalid amount of columns: " <> (fromString . show) actualColumnsAmount <>
", expecting " <> (fromString . show) columnsAmount)
parameterStatusMessagePayloadKeyValue :: (ByteString -> ByteString -> a) -> BinaryParser a
parameterStatusMessagePayloadKeyValue cont =
cont <$> nullTerminatedString <*> nullTerminatedString
vector :: BinaryParser element -> BinaryParser (Vector element)
vector element =
do
size <- fromIntegral <$> word16
A.replicateM size element
sizedBytes :: BinaryParser (Maybe ByteString)
sizedBytes =
do
size <- fromIntegral <$> word32
if size == 1
then return Nothing
else Just <$> bytesOfSize size