packages feed

clickhouse-haskell-0.1.1.0: src/Database/ClickHouseDriver/IO/BufferedReader.hs

-- Copyright (c) 2014-present, EMQX, Inc.
-- All rights reserved.
--
-- This source code is distributed under the terms of a MIT license,
-- found in the LICENSE file.

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings        #-}

-- | Tools to analyze protocol and deserialize data sent from server. This module is for internal use only.

module Database.ClickHouseDriver.IO.BufferedReader
  ( readBinaryStrWithLength,
    readVarInt',
    readBinaryStr',
    readBinaryStr,
    readVarInt,
    readBinaryInt8,
    readBinaryInt16,
    readBinaryInt64,
    readBinaryInt32,
    readBinaryUInt8,
    readBinaryUInt128,
    readBinaryUInt64,
    readBinaryUInt32,
    readBinaryUInt16,
    Reader,
    Buffer(..),
    createBuffer,
    refill
  )
where

import Control.Monad.State.Lazy ( StateT(StateT) )
import Data.Binary
    ( Word8, Word16, Word32, Word64, Binary, decode )
import           Data.ByteString          (ByteString)
import qualified Data.ByteString          as BS
import qualified Data.ByteString.Lazy     as L
import qualified Data.ByteString.Unsafe   as UBS
import           Data.DoubleWord          (Word128 (..))
import Data.Int ( Int8, Int16, Int32, Int64 )
import Data.Maybe ( fromJust, isNothing )
import Foreign.C ( CString )
import qualified Network.Simple.TCP       as TCP
import Network.Socket ( Socket )

-- | Buffer is for receiving data from TCP stream. Whenever all bytes are read, it automatically
-- refill from the stream.
data Buffer = Buffer {
  bufSize :: !Int,
  bytesData :: ByteString,
  socket :: Maybe Socket
}

-- | create buffer with size and socket.
createBuffer :: Int->Socket->IO Buffer
createBuffer size sock = do
  receive <- TCP.recv sock size -- receive data
  return Buffer{
    bufSize = size, -- set the size 
    bytesData = if isNothing receive then "" else fromJust receive, 
    socket = Just sock
  }

-- | refill buffer from stream
refill :: Buffer->IO Buffer 
refill Buffer{socket = Just sock, bufSize = size} = do
  newData' <- TCP.recv sock size
  let newBuffer = case newData' of
        Just newData -> Buffer {
          bufSize = size,
          bytesData = newData,
          socket = Just sock
        }
        Nothing -> error "Network error"
  return newBuffer
refill Buffer{socket=Nothing} = error "empty socket"

type Reader a = StateT Buffer IO a

readBinaryStrWithLength' :: Int
                         -- ^ length of string
                         -> Buffer
                         -- ^ buffer to read
                         -> IO (ByteString, Buffer)
                         -- ^ (the string read from buffer, buffer after reading)
readBinaryStrWithLength' n buf@Buffer{bufSize=size, bytesData=str, socket=sock} = do
  let l = BS.length str
  let (part, tail) = BS.splitAt n str
  if n > l
    then do
      newbuff <- refill buf
      (unread, altbuff) <- readBinaryStrWithLength' (n - l) newbuff
      return (part <> unread, altbuff)
    else do
      return (part, Buffer size tail sock)

readVarInt' :: Buffer
              -- ^ buffer to be read
            -> IO (Word, Buffer)
              -- ^ (the word read from buffer, the buffer after reading)
readVarInt' buf@Buffer{bufSize=size,bytesData=str, socket=sock} = do
  let l = fromIntegral $ BS.length str
  skip <- UBS.unsafeUseAsCString str (\x -> c_count x l)
  if skip == 0
    then do
      varint' <- UBS.unsafeUseAsCString str (\x->c_read_varint 0 x l)
      new_buf <- refill buf
      let new_str = bytesData new_buf
      varint <- UBS.unsafeUseAsCString new_str (\x->c_read_varint varint' x l)
      skip2 <- UBS.unsafeUseAsCString new_str (\x->c_count x l)
      let tail = BS.drop (fromIntegral skip) new_str
      return (varint, Buffer size tail sock)
    else do
      varint <- UBS.unsafeUseAsCString str (\x -> c_read_varint 0 x l)
      let tail = BS.drop (fromIntegral skip) str
      return (varint, Buffer size tail sock)
-- | read binary string from buffer.
-- It first read the integer(n) in front of the desired string,
-- then it read n bytes to capture the whole string.
readBinaryStr' :: Buffer 
               -- ^ Buffer to be read
               -> IO (ByteString, Buffer)
               -- ^ (the string read from Buffer, the buffer after reading)
readBinaryStr' str = do
  (len, tail) <- readVarInt' str -- 
  (head, tail') <- readBinaryStrWithLength' (fromIntegral len) tail
  
  return (head, tail')

-- | read n bytes and then transform into a binary type such as bytestring, Int8, UInt16 etc.
readBinaryHelper :: Binary a => Int -> Buffer -> IO (a, Buffer)
readBinaryHelper fmt str = do
  (cut, tail) <- readBinaryStrWithLength' fmt str
  let v = decode ((L.fromStrict. BS.reverse) cut)
  return (v, tail)

class Readable a where
  readIn :: Reader a

instance Readable Word where
  readIn = StateT readVarInt'

instance Readable ByteString where
  readIn = StateT readBinaryStr'

instance Readable Int8 where
  readIn = StateT $ readBinaryHelper 1

instance Readable Int16 where
  readIn = StateT $ readBinaryHelper 2

instance Readable Int32 where
  readIn = StateT $ readBinaryHelper 4

instance Readable Int64 where
  readIn = StateT $ readBinaryHelper 8

instance Readable Word8 where
  readIn = StateT $ readBinaryHelper 1

instance Readable Word16 where
  readIn = StateT $ readBinaryHelper 2

instance Readable Word32 where
  readIn = StateT $ readBinaryHelper 4

instance Readable Word64 where
  readIn = StateT $ readBinaryHelper 8

readVarInt :: Reader Word
readVarInt = readIn

readBinaryStrWithLength :: Int->Reader ByteString
readBinaryStrWithLength n = StateT (readBinaryStrWithLength' $ fromIntegral n)

readBinaryStr :: Reader ByteString
readBinaryStr = readIn

readBinaryInt8 :: Reader Int8
readBinaryInt8 = readIn

readBinaryInt16 :: Reader Int16
readBinaryInt16 = readIn

readBinaryInt32 :: Reader Int32
readBinaryInt32 = readIn

readBinaryInt64 :: Reader Int64
readBinaryInt64 = readIn

readBinaryUInt32 :: Reader Word32
readBinaryUInt32 = readIn

readBinaryUInt8 :: Reader Word8
readBinaryUInt8 = readIn

readBinaryUInt16 :: Reader Word16
readBinaryUInt16 = readIn

readBinaryUInt64 :: Reader Word64
readBinaryUInt64 = readIn

readBinaryUInt128 :: Reader Word128
readBinaryUInt128 = do
  hi <- readBinaryUInt64
  lo <- readBinaryUInt64
  return $ Word128 hi lo

-- | read bytes in the little endian format and transform into integer, see CBits/varuint.c
foreign import ccall unsafe "varuint.h read_varint" c_read_varint :: Word->CString -> Word -> IO Word

-- | Helper of c_read_varint. it counts how many bits it needs to read.   
foreign import ccall unsafe "varuint.h count_read" c_count :: CString -> Word -> IO Word