hasql 0.20.0.1 → 0.20.1
raw patch · 21 files changed
+403/−510 lines, 21 filesdep +bufferdep +ptrdep −binary-parserdep −scannerdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: buffer, ptr
Dependencies removed: binary-parser, scanner
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Hasql.DecodePrimitive: int4 :: DecodePrimitive Int32
Files
- benchmarks/Main.hs +10/−10
- hasql.cabal +8/−5
- library/Hasql/Core/DecodePrimitive.hs +14/−9
- library/Hasql/Core/Dispatcher.hs +0/−2
- library/Hasql/Core/InterpretResponses.hs +21/−24
- library/Hasql/Core/Loops/Interpreter.hs +2/−0
- library/Hasql/Core/Loops/Receiver.hs +28/−23
- library/Hasql/Core/Loops/Sender.hs +1/−0
- library/Hasql/Core/Loops/Serializer.hs +1/−0
- library/Hasql/Core/Model.hs +1/−1
- library/Hasql/Core/ParseDataRow.hs +12/−20
- library/Hasql/Core/Protocol/Decoding.hs +0/−207
- library/Hasql/Core/Protocol/Parse/Primitives.hs +62/−0
- library/Hasql/Core/Protocol/Parse/Responses.hs +137/−0
- library/Hasql/Core/Protocol/Parse/Values.hs +51/−0
- library/Hasql/Core/Protocol/Peek.hs +43/−0
- library/Hasql/Core/Request.hs +0/−1
- library/Hasql/Core/Scanner.hs +0/−193
- library/Hasql/DecodePrimitive.hs +1/−0
- profiling/Main.hs +3/−7
- tests/Main.hs +8/−8
benchmarks/Main.hs view
@@ -52,42 +52,42 @@ -- * Sessions ------------------------- -singleLargeResultInVectorSession :: F.Session (Vector (Int64, Int64))+singleLargeResultInVectorSession :: F.Session (Vector (Int32, Int32)) singleLargeResultInVectorSession = F.batch (manyRowsBatch B.vector) -singleLargeResultInRevListSession :: F.Session [(Int64, Int64)]+singleLargeResultInRevListSession :: F.Session [(Int32, Int32)] singleLargeResultInRevListSession = F.batch (manyRowsBatch B.revList) -manyLargeResultsInVectorSession :: F.Session [Vector (Int64, Int64)]+manyLargeResultsInVectorSession :: F.Session [Vector (Int32, Int32)] manyLargeResultsInVectorSession = replicateM 1000 (F.batch (manyRowsBatch B.vector)) -manyLargeResultsInVectorInBatchSession :: F.Session [Vector (Int64, Int64)]+manyLargeResultsInVectorInBatchSession :: F.Session [Vector (Int32, Int32)] manyLargeResultsInVectorInBatchSession = F.batch (replicateM 1000 (manyRowsBatch B.vector)) -manySmallResultsSession :: F.Session [(Int64, Int64)]+manySmallResultsSession :: F.Session [(Int32, Int32)] manySmallResultsSession = replicateM 1000 (F.batch singleRowBatch) -manySmallResultsInBatchSession :: F.Session [(Int64, Int64)]+manySmallResultsInBatchSession :: F.Session [(Int32, Int32)] manySmallResultsInBatchSession = F.batch (replicateM 1000 singleRowBatch) -- * Queries ------------------------- -singleRowBatch :: J.Batch (Int64, Int64)+singleRowBatch :: J.Batch (Int32, Int32) singleRowBatch = J.statement (G.prepared "select 1, 2" conquer decode) () where decode =- B.head ((,) <$> C.primitive D.int8 <*> C.primitive D.int8)+ B.head ((,) <$> C.primitive D.int4 <*> C.primitive D.int4) {-# INLINE manyRowsBatch #-}-manyRowsBatch :: (C.DecodeRow (Int64, Int64) -> B.DecodeResult result) -> J.Batch result+manyRowsBatch :: (C.DecodeRow (Int32, Int32) -> B.DecodeResult result) -> J.Batch result manyRowsBatch decodeResult = J.statement (G.prepared template mempty decode) () where@@ -95,7 +95,7 @@ "SELECT generate_series(0,1000) as a, generate_series(1000,2000) as b" decode = decodeResult $- tuple <$> C.primitive D.int8 <*> C.primitive D.int8+ tuple <$> C.primitive D.int4 <*> C.primitive D.int4 where tuple !a !b = (a, b)
hasql.cabal view
@@ -1,7 +1,7 @@ name: hasql version:- 0.20.0.1+ 0.20.1 category: Hasql, Database, PostgreSQL synopsis:@@ -78,7 +78,6 @@ Hasql.Core.MessageTypePredicates Hasql.Core.NoticeFieldTypes Hasql.Core.MessageTypeNames- Hasql.Core.Scanner Hasql.Core.Loops.Serializer Hasql.Core.Loops.Receiver Hasql.Core.Loops.Sender@@ -87,8 +86,11 @@ Hasql.Core.OID.Array Hasql.Core.OID.Primitive Hasql.Core.PreparedStatementRegistry- Hasql.Core.Protocol.Decoding Hasql.Core.Protocol.Encoding+ Hasql.Core.Protocol.Parse.Primitives+ Hasql.Core.Protocol.Parse.Responses+ Hasql.Core.Protocol.Parse.Values+ Hasql.Core.Protocol.Peek Hasql.Core.Protocol.Model build-depends: -- concurrency:@@ -99,11 +101,12 @@ -- template-haskell: template-haskell == 2.*, -- parsing:- scanner == 0.2.*,- binary-parser >= 0.5.5 && < 0.6, attoparsec >= 0.10 && < 0.14, -- database: postgresql-binary == 0.12.*,+ -- IO:+ ptr >= 0.15.2 && < 0.16,+ buffer >= 0.5 && < 0.6, -- data: bytestring-strict-builder >= 0.4.5 && < 0.5, vector-builder == 0.3.*,
library/Hasql/Core/DecodePrimitive.hs view
@@ -2,12 +2,12 @@ import Hasql.Prelude hiding (bool) import Hasql.Core.Model-import qualified BinaryParser as A-import qualified PostgreSQL.Binary.Decoding as B+import qualified Ptr.Parse as A+import qualified Hasql.Core.Protocol.Parse.Values as B newtype DecodePrimitive primitive =- DecodePrimitive (ReaderT Bool A.BinaryParser primitive)+ DecodePrimitive (ReaderT Bool A.Parse primitive) deriving (Functor) @@ -15,12 +15,12 @@ ------------------------- {-# INLINE nonDateTime #-}-nonDateTime :: A.BinaryParser primitive -> DecodePrimitive primitive+nonDateTime :: A.Parse primitive -> DecodePrimitive primitive nonDateTime parser = DecodePrimitive (ReaderT (const parser)) {-# INLINE dateTime #-}-dateTime :: A.BinaryParser primitive -> A.BinaryParser primitive -> DecodePrimitive primitive+dateTime :: A.Parse primitive -> A.Parse primitive -> DecodePrimitive primitive dateTime intParser floatParser = DecodePrimitive (ReaderT (\case False -> floatParser; True -> intParser)) @@ -32,10 +32,15 @@ bool = nonDateTime B.bool +{-# INLINE int4 #-}+int4 :: DecodePrimitive Int32+int4 =+ nonDateTime B.int4+ {-# INLINE int8 #-} int8 :: DecodePrimitive Int64 int8 =- nonDateTime B.int+ nonDateTime B.int8 -- * Blobs -------------------------@@ -43,12 +48,12 @@ {-# INLINE text #-} text :: DecodePrimitive Text text =- nonDateTime B.text_strict+ nonDateTime B.text {-# INLINE bytea #-} bytea :: DecodePrimitive ByteString bytea =- nonDateTime B.bytea_strict+ nonDateTime B.bytea -- * Time -------------------------@@ -56,4 +61,4 @@ {-# INLINE timestamptz #-} timestamptz :: DecodePrimitive UTCTime timestamptz =- dateTime B.timestamptz_int B.timestamptz_float+ dateTime B.intTimestamptz B.floatTimestamptz
library/Hasql/Core/Dispatcher.hs view
@@ -3,8 +3,6 @@ import Hasql.Prelude import Hasql.Core.Model import qualified Hasql.Core.Socket as A-import qualified ByteString.StrictBuilder as B-import qualified BinaryParser as D import qualified Hasql.Core.Request as C import qualified Hasql.Core.UnauthenticatedSession as G import qualified Hasql.Core.Loops.Serializer as H
library/Hasql/Core/InterpretResponses.hs view
@@ -6,6 +6,9 @@ import qualified Hasql.Core.MessageTypeNames as H import qualified Hasql.Core.ParseDataRow as A import qualified Data.Vector as B+import qualified Hasql.Core.Protocol.Parse.Responses as E+import qualified Ptr.Parse as C+import qualified Ptr.ByteString as D newtype InterpretResponses result =@@ -57,7 +60,7 @@ processResponse response foldRows :: FoldM IO row result -> A.ParseDataRow row -> InterpretResponses (result, Int)-foldRows (FoldM foldStep foldStart foldEnd) (A.ParseDataRow rowLength vectorFn) =+foldRows (FoldM foldStep foldStart foldEnd) pdr = InterpretResponses def where def fetchResponse discardResponse =@@ -67,19 +70,16 @@ where processResponse !state response = case response of- DataRowResponse values ->- if B.length values == rowLength- then case vectorFn values 0 of- Left error -> return (Left (DecodingError error))- Right row -> do+ DataRowResponse bytes ->+ D.parse bytes+ (do+ row <- E.dataRowBody pdr+ return $ do nextState <- foldStep state row nextResponse <- fetchResponse- processResponse nextState nextResponse- else return (Left (DecodingError (fromString- (showString "Invalid amount of columns: "- (shows (B.length values)- (showString ", expecting "- (show rowLength)))))))+ processResponse nextState nextResponse)+ (\ n -> return (Left (ProtocolError ("Missing " <> (fromString . show) n <> " bytes"))))+ (return . Left . ProtocolError) CommandCompleteResponse amount -> do result <- foldEnd state@@ -97,7 +97,7 @@ processResponse state nextResponse singleRow :: A.ParseDataRow row -> InterpretResponses row-singleRow (A.ParseDataRow rowLength vectorFn) =+singleRow pdr = InterpretResponses def where def fetchResponse discardResponse =@@ -105,18 +105,15 @@ where processResponseWithoutRow response = case response of- DataRowResponse values ->- if B.length values == rowLength- then case vectorFn values 0 of- Left error -> return (Left (DecodingError error))- Right row -> do+ DataRowResponse bytes ->+ D.parse bytes+ (do+ row <- E.dataRowBody pdr+ return $ do nextResponse <- fetchResponse- processResponseWithRow row nextResponse- else return (Left (DecodingError (fromString- (showString "Invalid amount of columns: "- (shows (B.length values)- (showString ", expecting "- (show rowLength)))))))+ processResponseWithRow row nextResponse)+ (\ n -> return (Left (ProtocolError ("Missing " <> (fromString . show) n <> " bytes"))))+ (return . Left . ProtocolError) CommandCompleteResponse _ -> return (Left (DecodingError "Not a single row")) ErrorResponse state message ->
library/Hasql/Core/Loops/Interpreter.hs view
@@ -12,6 +12,7 @@ loop :: IO Response -> IO (Maybe ResultProcessor) -> (Notification -> IO ()) -> IO () loop fetchResponse fetchResultProcessor sendNotification =+ {-# SCC "loop" #-} forever $ do response <- fetchResponse fetchResult <- fetchResultProcessor@@ -34,6 +35,7 @@ -} backtrackFetch :: a -> IO a -> IO (IO a) backtrackFetch element fetch =+ {-# SCC "backtrackFetch" #-} do notFirstRef <- newIORef False return $ do
library/Hasql/Core/Loops/Receiver.hs view
@@ -3,31 +3,36 @@ import Hasql.Prelude import Hasql.Core.Model import qualified Hasql.Core.Socket as A-import qualified Data.ByteString as B-import qualified Scanner as C-import qualified Hasql.Core.Scanner as D+import qualified Hasql.Core.Protocol.Peek as E+import qualified Buffer as C+import qualified Ptr.Peek as D {-# INLINABLE loop #-} loop :: A.Socket -> (Response -> IO ()) -> (Text -> IO ()) -> IO ()-loop socket sendResponse reportError =- processScannerResult (C.More (C.scan D.response))+loop socket sendResponse reportTransportError =+ {-# SCC "loop" #-} + do+ buffer <- C.new 16384+ withBuffer buffer where- processScannerResult =- \case- C.More consume -> do- receivingResult <- A.receive socket (shiftL 2 12)- case receivingResult of- Right bytes ->- if B.null bytes- then reportError "Connection interrupted"- else processScannerResult (consume bytes)- Left msg ->- reportError msg- C.Done remainders responseMaybe -> do- traverse_ sendResponse responseMaybe- if B.null remainders- then processScannerResult (C.More (C.scan D.response))- else processScannerResult (C.scan D.response remainders)- C.Fail remainders message -> do- reportError (fromString message)+ withBuffer buffer =+ load+ where+ receiveToBuffer failure success =+ C.push buffer 8192 $ \ptr -> do+ result <- A.receiveToPtr socket ptr 8192+ case result of+ Right amountReceived -> return (amountReceived, success)+ Left error -> return (0, failure error)+ peekFromBuffer :: D.Peek a -> (a -> IO ()) -> IO ()+ peekFromBuffer (D.Peek amount ptrIO) succeed =+ fix $ \recur ->+ join $ C.pull buffer amount (fmap succeed . {-# SCC "loop/peeking" #-} ptrIO) $ \_ ->+ receiveToBuffer reportTransportError recur+ load =+ peekFromBuffer+ (E.response+ $(todo "Handle corrupt response")+ (maybe load (\response -> sendResponse response >> load)))+ (\ bodyPeek -> peekFromBuffer bodyPeek id)
library/Hasql/Core/Loops/Sender.hs view
@@ -8,6 +8,7 @@ {-# INLINABLE loop #-} loop :: A.Socket -> IO ByteString -> (Text -> IO ()) -> IO () loop socket getNextChunk reportError =+ {-# SCC "loop" #-} forever $ do bytes <- getNextChunk resultOfSending <- A.send socket bytes
library/Hasql/Core/Loops/Serializer.hs view
@@ -12,6 +12,7 @@ loop :: IO Message -> (ByteString -> IO ()) -> IO () loop getMessage sendBytes =+ {-# SCC "loop" #-} startAnew where size =
library/Hasql/Core/Model.hs view
@@ -4,7 +4,7 @@ data Response =- DataRowResponse !(Vector (Maybe ByteString)) |+ DataRowResponse {-# UNPACK #-} !ByteString | CommandCompleteResponse !Int | ReadyForQueryResponse !TransactionStatus | ParseCompleteResponse |
library/Hasql/Core/ParseDataRow.hs view
@@ -1,8 +1,8 @@ module Hasql.Core.ParseDataRow where import Hasql.Prelude-import qualified BinaryParser as D-import qualified Data.Vector as A+import qualified Ptr.Parse as B+import qualified Hasql.Core.Protocol.Parse.Primitives as C {-|@@ -11,28 +11,20 @@ It is assumed that the size of the input vector is checked externally. -} data ParseDataRow result =- ParseDataRow !Int !(Vector (Maybe ByteString) -> Int -> Either Text result)+ ParseDataRow {-# UNPACK #-} !Int (B.Parse result) deriving instance Functor ParseDataRow instance Applicative ParseDataRow where pure x =- ParseDataRow 0 (\_ _ -> Right x)- (<*>) (ParseDataRow leftSize leftInterpreter) (ParseDataRow rightSize rightInterpreter) =- ParseDataRow- (leftSize + rightSize)- (\vec !index -> leftInterpreter vec index <*> rightInterpreter vec (index + leftSize))+ ParseDataRow 0 (pure x)+ (<*>) (ParseDataRow leftSize leftParse) (ParseDataRow rightSize rightParse) =+ ParseDataRow (leftSize + rightSize) (leftParse <*> rightParse) -nullableColumn :: D.BinaryParser column -> ParseDataRow (Maybe column)-nullableColumn parser =- ParseDataRow 1 $ \vec !index ->- either (Left . mappend ("Column " <> (fromString . show) index <> ": ")) Right $- traverse (D.run parser) (A.unsafeIndex vec index)+nullableColumn :: B.Parse column -> ParseDataRow (Maybe column)+nullableColumn parseColumn =+ ParseDataRow 1 (C.nullableDataRowColumn parseColumn) -column :: D.BinaryParser column -> ParseDataRow column-column parser =- ParseDataRow 1 $ \vec !index ->- either (Left . mappend ("Column " <> (fromString . show) index <> ": ")) Right $- case A.unsafeIndex vec index of- Just bytes -> D.run parser bytes- Nothing -> Left "Unexpected NULL"+column :: B.Parse column -> ParseDataRow column+column parseColumn =+ ParseDataRow 1 (C.nonNullDataRowColumn parseColumn)
− library/Hasql/Core/Protocol/Decoding.hs
@@ -1,207 +0,0 @@-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---{-# INLINE word8 #-}-word8 :: BinaryParser Word8-word8 =- byte--{-# INLINE word16 #-}-word16 :: BinaryParser Word16-word16 =- beWord16--{-# INLINE word32 #-}-word32 :: BinaryParser Word32-word32 =- beWord32--{-# INLINE int32 #-}-int32 :: BinaryParser Int32-int32 =- fromIntegral <$> beWord32--{-# INLINE messageTypeAndLength #-}-messageTypeAndLength :: (MessageType -> PayloadLength -> a) -> BinaryParser a-messageTypeAndLength cont =- cont <$> messageType <*> payloadLength--{-# INLINE messageType #-}-messageType :: BinaryParser MessageType-messageType =- MessageType <$> word8--{-# INLINE payloadLength #-}-payloadLength :: BinaryParser PayloadLength-payloadLength =- PayloadLength . subtract 4 . fromIntegral <$> word32--{-# INLINE nullableSizedValue #-}-nullableSizedValue :: BinaryParser a -> BinaryParser (Maybe a)-nullableSizedValue value =- do- size <- int32- case size of- -1 -> return Nothing- _ -> sized (fromIntegral size) (fmap Just value)--{-# INLINE sizedValue #-}-sizedValue :: BinaryParser a -> BinaryParser a-sizedValue value =- do- size <- int32- case size of- -1 -> failure "Unexpected null"- _ -> sized (fromIntegral size) value--{-|-Extracts the number of affected rows from the body of the CommandComplete message.--}-{-# INLINE commandCompleteMessageAffectedRows #-}-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--{-|-The essential components of the error (or notice) message.--}-{-# INLINE errorMessage #-}-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--{-# INLINE noticeField #-}-noticeField :: (NoticeFieldType -> ByteString -> a) -> BinaryParser a-noticeField cont =- cont <$> noticeFieldType <*> nullTerminatedString--{-# INLINE noticeFieldType #-}-noticeFieldType :: BinaryParser NoticeFieldType-noticeFieldType =- NoticeFieldType <$> word8--{-# INLINE nullTerminatedString #-}-nullTerminatedString :: BinaryParser ByteString-nullTerminatedString =- bytesWhile (/= 0) <* byte--{-# INLINE protocolVersion #-}-protocolVersion :: BinaryParser (Word16, Word16)-protocolVersion =- (,) <$> word16 <*> word16--{-# INLINE authenticationMessage #-}-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)--{-# INLINE notificationMessage #-}-notificationMessage :: (Word32 -> ByteString -> ByteString -> result) -> BinaryParser result-notificationMessage cont =- cont <$> word32 <*> nullTerminatedString <*> nullTerminatedString--{-# INLINE dataRowMessage #-}-dataRowMessage :: (Word16 -> BinaryParser a) -> BinaryParser a-dataRowMessage contentsParser =- do- amountOfColumns <- word16- contentsParser amountOfColumns--{-# INLINE parseDataRow #-}-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)--{-|-ParameterStatus (B)-Byte1('S')-Identifies the message as a run-time parameter status report.--Int32-Length of message contents in bytes, including self.--String-The name of the run-time parameter being reported.--String-The current value of the parameter.--}-{-# INLINE parameterStatusMessagePayloadKeyValue #-}-parameterStatusMessagePayloadKeyValue :: (ByteString -> ByteString -> a) -> BinaryParser a-parameterStatusMessagePayloadKeyValue cont =- cont <$> nullTerminatedString <*> nullTerminatedString--{-|-Int16-The number of column values that follow (possibly zero).--Next, the following pair of fields appear for each column:--Int32-The length of the column value, in bytes (this count does not include itself). Can be zero. As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.--Byten-The value of the column, in the format indicated by the associated format code. n is the above length.--}-vector :: BinaryParser element -> BinaryParser (Vector element)-vector element =- do- size <- fromIntegral <$> word16- A.replicateM size element--{-|-Int32-The length of the column value, in bytes (this count does not include itself). Can be zero. As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.--Byten-The value of the column, in the format indicated by the associated format code. n is the above length.--}-sizedBytes :: BinaryParser (Maybe ByteString)-sizedBytes =- do- size <- fromIntegral <$> word32- if size == -1- then return Nothing- else Just <$> bytesOfSize size
+ library/Hasql/Core/Protocol/Parse/Primitives.hs view
@@ -0,0 +1,62 @@+module Hasql.Core.Protocol.Parse.Primitives where++import Hasql.Prelude hiding (fail)+import Hasql.Core.Model+import Ptr.Parse+++-- * Primitives+-------------------------++{-|+Int32+The length of the column value, in bytes (this count does not include itself). Can be zero. As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.++Byten+The value of the column, in the format indicated by the associated format code. n is the above length.+-}+{-# INLINE sized #-}+sized :: Parse result -> Parse result -> Parse result+sized nullParse sizedParse =+ {-# SCC "sized" #-} + do+ size <- fromIntegral <$> beWord32+ if size == -1+ then nullParse+ else sizedParse++{-|+Int32+The length of the column value, in bytes (this count does not include itself). Can be zero. As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.++Byten+The value of the column, in the format indicated by the associated format code. n is the above length.+-}+{-# INLINE sizedBytes #-}+sizedBytes :: Parse (Maybe ByteString)+sizedBytes =+ {-# SCC "sizedBytes" #-} + do+ size <- fromIntegral <$> beWord32+ if size == -1+ then return Nothing+ else do+ !bytes_ <- bytes size+ return (Just bytes_)+++-- * Components+-------------------------++{-# INLINE nullableDataRowColumn #-}+nullableDataRowColumn :: Parse column -> Parse (Maybe column)+nullableDataRowColumn parseColumn =+ {-# SCC "nullableDataRowColumn" #-} + sized (return Nothing) (fmap Just parseColumn)++{-# INLINE nonNullDataRowColumn #-}+nonNullDataRowColumn :: Parse column -> Parse column+nonNullDataRowColumn parseColumn =+ {-# SCC "nonNullDataRowColumn" #-} + sized (fail "Unexpected Null column value") parseColumn+
+ library/Hasql/Core/Protocol/Parse/Responses.hs view
@@ -0,0 +1,137 @@+module Hasql.Core.Protocol.Parse.Responses where++import Hasql.Prelude hiding (fail)+import Hasql.Core.Protocol.Parse.Primitives+import Hasql.Core.Model+import Ptr.Parse+import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as A+import qualified Data.Vector as D+import qualified Hasql.Core.MessageTypePredicates as C+import qualified Hasql.Core.NoticeFieldTypes as E+import qualified Hasql.Core.ParseDataRow as F+++{-# INLINE responseBody #-}+responseBody :: Word8 -> Parse (Maybe Response)+responseBody type_ =+ {-# SCC "responseBody" #-} + if+ | C.dataRow type_ -> (Just . DataRowResponse) <$> allBytes+ | C.commandComplete type_ -> Just . CommandCompleteResponse <$> commandCompleteBody+ | C.readyForQuery type_ -> fmap (Just . ReadyForQueryResponse) readyForQueryBody+ | C.parseComplete type_ -> pure (Just ParseCompleteResponse)+ | C.bindComplete type_ -> pure (Just BindCompleteResponse)+ | C.emptyQuery type_ -> pure (Just EmptyQueryResponse)+ | C.notification type_ -> Just <$> notificationBody NotificationResponse+ | C.error type_ -> Just <$> errorResponseBody ErrorResponse+ | C.authentication type_ -> fmap (Just . AuthenticationResponse) authenticationBody+ | C.parameterStatus type_ -> Just <$> parameterStatusBody ParameterStatusResponse+ | True -> pure Nothing++{-# INLINE unparsedFieldsOfDataRowBody #-}+unparsedFieldsOfDataRowBody :: Parse (Vector (Maybe ByteString))+unparsedFieldsOfDataRowBody =+ {-# SCC "unparsedFieldsOfDataRowBody" #-} + do+ amountOfColumns <- beWord16+ D.replicateM (fromIntegral amountOfColumns) sizedBytes++{-# INLINE dataRowBody #-}+dataRowBody :: F.ParseDataRow row -> Parse row+dataRowBody (F.ParseDataRow rowLength parse) =+ do+ amountOfColumns <- beWord16+ if fromIntegral amountOfColumns == rowLength+ then parse+ else fail (fromString+ (showString "Invalid amount of columns: "+ (shows amountOfColumns+ (showString ", expecting "+ (show rowLength)))))++{-# INLINE commandCompleteBody #-}+commandCompleteBody :: Parse Int+commandCompleteBody =+ {-# SCC "commandCompleteBody" #-} + do+ header <- bytesWhile byteIsUpperLetter+ word8+ case header of+ "INSERT" -> skipWhile byteIsDigit *> word8 *> unsignedASCIIIntegral <* word8+ _ -> unsignedASCIIIntegral <* word8+ where+ byteIsUpperLetter byte =+ byte - 65 <= 25+ byteIsDigit byte =+ byte - 48 <= 9++{-# INLINE readyForQueryBody #-}+readyForQueryBody :: Parse TransactionStatus+readyForQueryBody =+ {-# SCC "readyForQueryBody" #-} + do+ statusByte <- word8+ case statusByte of+ 73 -> return IdleTransactionStatus+ 84 -> return ActiveTransactionStatus+ 69 -> return FailedTransactionStatus+ _ -> fail ("Unexpected transaction status byte: " <> (fromString . show) statusByte)++{-# INLINE notificationBody #-}+notificationBody :: (Word32 -> ByteString -> ByteString -> result) -> Parse result+notificationBody result =+ {-# SCC "notificationBody" #-} + result <$> beWord32 <*> nullTerminatedBytes <*> nullTerminatedBytes++{-# INLINE errorResponseBody #-}+errorResponseBody :: (ByteString -> ByteString -> result) -> Parse result+errorResponseBody result =+ {-# SCC "errorResponseBody" #-} + iterate Nothing Nothing+ where+ iterate code message =+ element <|> end+ where+ element =+ join (noticeField (\type_ payload ->+ if+ | type_ == E.code -> iterate (Just payload) message+ | type_ == E.message -> iterate code (Just payload)+ | True -> iterate code message))+ end =+ do+ word8+ case code of+ Just code -> case message of+ Just message -> return (result code message)+ _ -> fail "The \"message\" field is missing"+ _ -> fail "The \"code\" field is missing"++{-# INLINE noticeField #-}+noticeField :: (Word8 -> ByteString -> result) -> Parse result+noticeField result =+ {-# SCC "noticeField" #-} + result <$> word8 <*> nullTerminatedBytes++{-# INLINE authenticationBody #-}+authenticationBody :: Parse AuthenticationStatus+authenticationBody =+ {-# SCC "authenticationBody" #-} + do+ status <- beWord32+ case status of+ 0 -> return OkAuthenticationStatus+ 3 -> return NeedClearTextPasswordAuthenticationStatus+ 5 -> do+ salt <- bytes 4+ return (NeedMD5PasswordAuthenticationStatus salt)+ _ -> fail ("Unsupported authentication status: " <> (fromString . show) status)++{-# INLINE parameterStatusBody #-}+parameterStatusBody :: (ByteString -> ByteString -> result) -> Parse result+parameterStatusBody result =+ {-# SCC "parameterStatusBody" #-} + result <$> nullTerminatedBytes <*> nullTerminatedBytes++
+ library/Hasql/Core/Protocol/Parse/Values.hs view
@@ -0,0 +1,51 @@+module Hasql.Core.Protocol.Parse.Values where++import Hasql.Prelude hiding (fail)+import Ptr.Parse+import Hasql.Core.Protocol.Parse.Primitives+++{-# INLINE int4 #-}+int4 :: Parse Int32+int4 =+ fmap fromIntegral beWord32++{-# INLINE int8 #-}+int8 :: Parse Int64+int8 =+ fmap fromIntegral beWord64++{-# INLINE float4 #-}+float4 :: Parse Float+float4 =+ unsafeCoerce beWord32++{-# INLINE float8 #-}+float8 :: Parse Double+float8 =+ unsafeCoerce beWord64++{-# INLINE bool #-}+bool :: Parse Bool+bool =+ fmap (== 1) word8++{-# INLINE text #-}+text :: Parse Text+text =+ $(todo "text")++{-# INLINE bytea #-}+bytea :: Parse ByteString+bytea =+ $(todo "bytea")++{-# INLINE intTimestamptz #-}+intTimestamptz :: Parse UTCTime+intTimestamptz =+ $(todo "intTimestamptz")++{-# INLINE floatTimestamptz #-}+floatTimestamptz :: Parse UTCTime+floatTimestamptz =+ $(todo "floatTimestamptz")
+ library/Hasql/Core/Protocol/Peek.hs view
@@ -0,0 +1,43 @@+module Hasql.Core.Protocol.Peek where++import Hasql.Prelude hiding (take)+import Hasql.Core.Model+import Ptr.Peek+import qualified Hasql.Core.Protocol.Parse.Responses as G+import qualified Ptr.Parse as F+import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as A+import qualified Data.Vector as D+import qualified Hasql.Core.MessageTypePredicates as C+import qualified Hasql.Core.NoticeFieldTypes as E++++-- * Postgres Protocol-specific Primitives+-------------------------++{-# INLINE messageTypeAndLength #-}+messageTypeAndLength :: (Word8 -> Int -> a) -> Peek a+messageTypeAndLength cont =+ cont <$> word8 <*> payloadLength++{-# INLINE payloadLength #-}+payloadLength :: Peek Int+payloadLength =+ subtract 4 . fromIntegral <$> beWord32++{-# INLINE messageTypeAndPayload #-}+messageTypeAndPayload :: (Text -> a) -> (Word8 -> F.Parse a) -> Peek (Peek a)+messageTypeAndPayload error parse_ =+ messageTypeAndLength $ \ !messageType !length ->+ parse length (parse_ messageType) (\ _ -> error "Not enough data") error+++-- * Responses+-------------------------++{-# INLINE response #-}+response :: (Text -> x) -> (Maybe Response -> x) -> Peek (Peek x)+response error response =+ {-# SCC "response" #-} + messageTypeAndPayload error (fmap response . G.responseBody)
library/Hasql/Core/Request.hs view
@@ -3,7 +3,6 @@ import Hasql.Prelude import Hasql.Core.Model import qualified ByteString.StrictBuilder as B-import qualified BinaryParser as D import qualified Hasql.Core.InterpretResponses as A import qualified Hasql.Core.Protocol.Encoding as K import qualified Hasql.Core.Protocol.Model as C
− library/Hasql/Core/Scanner.hs
@@ -1,193 +0,0 @@-module Hasql.Core.Scanner where--import Hasql.Prelude-import Hasql.Core.Model-import Scanner (Scanner)-import qualified Scanner as A-import qualified Data.ByteString as B-import qualified Data.Vector as D-import qualified Hasql.Core.MessageTypePredicates as C-import qualified Hasql.Core.NoticeFieldTypes as E---{-# INLINE word8 #-}-word8 :: Scanner Word8-word8 =- A.anyWord8--{-# INLINE word16 #-}-word16 :: Scanner Word16-word16 =- numOfSize 2--{-# INLINE word32 #-}-word32 :: Scanner Word32-word32 =- numOfSize 4--{-# INLINE word64 #-}-word64 :: Scanner Word64-word64 =- numOfSize 8--{-# INLINE numOfSize #-}-numOfSize :: (Bits a, Num a) => Int -> Scanner a-numOfSize size =- B.foldl' (\n h -> shiftL n 8 .|. fromIntegral h) 0 <$> A.take size--{-# INLINE int32 #-}-int32 :: Scanner Int32-int32 =- fromIntegral <$> word32--{-# INLINE messageTypeAndLength #-}-messageTypeAndLength :: (Word8 -> Word32 -> a) -> Scanner a-messageTypeAndLength cont =- cont <$> word8 <*> payloadLength--{-# INLINE payloadLength #-}-payloadLength :: (Integral a, Bits a) => Scanner a-payloadLength =- subtract 4 <$> numOfSize 4--{-# INLINE messageTypeAndPayload #-}-messageTypeAndPayload :: (Word8 -> ByteString -> a) -> Scanner a-messageTypeAndPayload cont =- cont <$> word8 <*> (payloadLength >>= A.take)---- |--- Integral number encoded in ASCII.-{-# INLINE asciiIntegral #-}-asciiIntegral :: Integral a => Scanner a-asciiIntegral =- B.foldl' step 0 <$> A.takeWhile byteIsDigit- where- byteIsDigit byte =- byte - 48 <= 9- step !state !byte =- state * 10 + fromIntegral byte - 48--{-# INLINE nullTerminatedString #-}-nullTerminatedString :: Scanner ByteString-nullTerminatedString =- A.takeWhile (/= 0) <* A.anyWord8---- * Responses----------------------------{-# INLINE response #-}-response :: Scanner (Maybe Response)-response =- do- type_ <- word8- bodyLength <- payloadLength- if- | C.dataRow type_ -> dataRowBody (Just . DataRowResponse)- | C.commandComplete type_ -> commandCompleteBody (Just . CommandCompleteResponse)- | C.readyForQuery type_ -> readyForQueryBody (Just . ReadyForQueryResponse)- | C.parseComplete type_ -> pure (Just ParseCompleteResponse)- | C.bindComplete type_ -> pure (Just BindCompleteResponse)- | C.emptyQuery type_ -> pure (Just EmptyQueryResponse)- | C.notification type_ -> Just <$> notificationBody NotificationResponse- | C.error type_ -> Just <$> errorResponseBody bodyLength ErrorResponse- | C.authentication type_ -> Just <$> authenticationBody AuthenticationResponse- | C.parameterStatus type_ -> Just <$> parameterStatusBody ParameterStatusResponse- | True -> A.take bodyLength $> Nothing--{-# INLINE dataRowBody #-}-dataRowBody :: (Vector (Maybe ByteString) -> result) -> Scanner result-dataRowBody result =- do- amountOfColumns <- word16- bytesVector <- D.replicateM (fromIntegral amountOfColumns) sizedBytes- return (result bytesVector)--{-# INLINE commandCompleteBody #-}-commandCompleteBody :: (Int -> result) -> Scanner result-commandCompleteBody result =- do- header <- A.takeWhile byteIsUpperLetter- A.anyWord8- count <- case header of- "INSERT" -> A.skipWhile byteIsDigit *> A.anyWord8 *> asciiIntegral <* A.anyWord8- _ -> asciiIntegral <* A.anyWord8- return (result count)- where- byteIsUpperLetter byte =- byte - 65 <= 25- byteIsDigit byte =- byte - 48 <= 9--{-# INLINE readyForQueryBody #-}-readyForQueryBody :: (TransactionStatus -> result) -> Scanner result-readyForQueryBody result =- do- statusByte <- A.anyWord8- case statusByte of- 73 -> return (result IdleTransactionStatus)- 84 -> return (result ActiveTransactionStatus)- 69 -> return (result FailedTransactionStatus)- _ -> fail (showString "Unexpected transaction status byte: " (show statusByte))--{-# INLINE notificationBody #-}-notificationBody :: (Word32 -> ByteString -> ByteString -> result) -> Scanner result-notificationBody result =- result <$> word32 <*> nullTerminatedString <*> nullTerminatedString--{-# INLINE errorResponseBody #-}-errorResponseBody :: Int -> (ByteString -> ByteString -> result) -> Scanner result-errorResponseBody length result =- do- tuple <- iterate 0 Nothing Nothing- A.anyWord8- case tuple of- (Just code, Just message) -> return (result code message)- _ -> fail "Some of the required error fields are missing"- where- iterate !offset code message =- if offset < length - 1- then join (noticeField (\type_ payload ->- if- | type_ == E.code -> iterate (offset + 2 + B.length payload) (Just payload) message- | type_ == E.message -> iterate (offset + 2 + B.length payload) code (Just payload)- | True -> iterate (offset + 2 + B.length payload) code message))- else return (code, message)--{-# INLINE noticeField #-}-noticeField :: (Word8 -> ByteString -> result) -> Scanner result-noticeField result =- result <$> word8 <*> nullTerminatedString--{-# INLINE authenticationBody #-}-authenticationBody :: (AuthenticationStatus -> result) -> Scanner result-authenticationBody result =- do- status <- word32- case status of- 0 -> return (result OkAuthenticationStatus)- 3 -> return (result NeedClearTextPasswordAuthenticationStatus)- 5 -> do- salt <- A.take 4- return (result (NeedMD5PasswordAuthenticationStatus salt))- _ -> fail ("Unsupported authentication status: " <> show status)--{-# INLINE parameterStatusBody #-}-parameterStatusBody :: (ByteString -> ByteString -> result) -> Scanner result-parameterStatusBody result =- result <$> nullTerminatedString <*> nullTerminatedString--{-|-Int32-The length of the column value, in bytes (this count does not include itself). Can be zero. As a special case, -1 indicates a NULL column value. No value bytes follow in the NULL case.--Byten-The value of the column, in the format indicated by the associated format code. n is the above length.--}-{-# INLINE sizedBytes #-}-sizedBytes :: Scanner (Maybe ByteString)-sizedBytes =- do- size <- fromIntegral <$> word32- if size == -1- then return Nothing- else Just <$> A.take size
library/Hasql/DecodePrimitive.hs view
@@ -2,6 +2,7 @@ ( DecodePrimitive, bool,+ int4, int8, text, bytea,
profiling/Main.hs view
@@ -18,11 +18,7 @@ connection <- connect traceEventIO "START Session" Right !result <- fmap force <$> A.session connection (session 50 10 100)- Right !result <- fmap force <$> A.session connection (session 50 10 100) Right !result <- fmap force <$> A.session connection (session 10 50 100)- Right !result <- fmap force <$> A.session connection (session 50 10 100)- Right !result <- fmap force <$> A.session connection (session 10 50 100)- Right !result <- fmap force <$> A.session connection (session 10 50 100) traceEventIO "STOP Session" return () @@ -40,7 +36,7 @@ -- * Sessions ------------------------- -session :: Int -> Int -> Int -> F.Session [[[(Int64, Int64)]]]+session :: Int -> Int -> Int -> F.Session [[[(Int32, Int32)]]] session amountOfQueries amountOfStatements amountOfRows = replicateM amountOfQueries (F.batch (replicateM amountOfStatements (manyRowsBatch amountOfRows (B.revList)))) where@@ -54,7 +50,7 @@ -- * Queries ------------------------- -manyRowsBatch :: Int -> (C.DecodeRow (Int64, Int64) -> B.DecodeResult result) -> J.Batch result+manyRowsBatch :: Int -> (C.DecodeRow (Int32, Int32) -> B.DecodeResult result) -> J.Batch result manyRowsBatch amountOfRows decodeResult = J.statement (G.unprepared template conquer decode) () where@@ -62,7 +58,7 @@ "SELECT generate_series(0," <> fromString (show amountOfRows) <> ") as a, generate_series(10000," <> fromString (show (amountOfRows + 10000)) <> ") as b" decode = decodeResult $- tuple <$> C.primitive D.int8 <*> C.primitive D.int8+ tuple <$> C.primitive D.int4 <*> C.primitive D.int4 where tuple !a !b = (a, b)
tests/Main.hs view
@@ -46,27 +46,27 @@ testGroup "Batch" $ [ test "select 1" (Right 1) $- J.statement (E.prepared "select 1" conquer (B.head (C.primitive D.int8))) ()+ J.statement (E.prepared "select 1" conquer (B.head (C.primitive D.int4))) () , test "select '1' and select 'true'" (Right (1, True)) $ (,) <$>- J.statement (E.prepared "select 1" conquer (B.head (C.primitive D.int8))) () <*>+ J.statement (E.prepared "select 1" conquer (B.head (C.primitive D.int4))) () <*> J.statement (E.prepared "select 'true' :: bool" conquer (B.head (C.primitive D.bool))) () , test "Error" (Left (A.BackendError "42703" "column \"abc\" does not exist")) $- J.statement (E.prepared "select abc" conquer (B.head (C.primitive D.int8))) ()+ J.statement (E.prepared "select abc" conquer (B.head (C.primitive D.int4))) () , test "Errors in multiple queries" (Left (A.BackendError "42703" "column \"abc\" does not exist")) $- J.statement (E.unprepared "select 1" conquer (B.head (C.primitive D.int8))) () *>- J.statement (E.unprepared "select abc" conquer (B.head (C.primitive D.int8))) () *>- J.statement (E.unprepared "select abc" conquer (B.head (C.primitive D.int8))) ()+ J.statement (E.unprepared "select 1" conquer (B.head (C.primitive D.int4))) () *>+ J.statement (E.unprepared "select abc" conquer (B.head (C.primitive D.int4))) () *>+ J.statement (E.unprepared "select abc" conquer (B.head (C.primitive D.int4))) () , test "traverse" (Right [1,2,3]) $- traverse (\template -> J.statement (E.prepared template conquer (B.head (C.primitive D.int8))) ()) $+ traverse (\template -> J.statement (E.prepared template conquer (B.head (C.primitive D.int4))) ()) $ ["select 1", "select 2", "select 3"] , test "Not a single row" (Left (A.DecodingError "Empty query")) $- J.statement (E.prepared "" conquer (B.head (C.primitive D.int8))) ()+ J.statement (E.prepared "" conquer (B.head (C.primitive D.int4))) () , testCaseInfo "Simultaneous result decoding and counting" $ pure "Pending" ]