module Hmssql.Tds.Parameter
( ParamValue (..), Source (..), sourceOf, sourceIO, paramValueProblem
, streamed, writeParamValue, writeParamValueWith
) where
import Control.Exception (throwIO)
import Control.Monad (when)
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Builder as BB
import Data.Word (Word8)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Ptr (plusPtr)
import Foreign.Storable (pokeByteOff)
import Streaming (Stream, Of)
import qualified Streaming.Prelude as S
import Hmssql.Errors (EncodeError (..))
import Hmssql.Tds.TypeInfo
import Hmssql.Tds.PacketWriter
newtype Source a = Source (forall r. (Stream (Of a) IO () -> IO r) -> IO r)
sourceOf :: Stream (Of a) IO () -> Source a
sourceOf :: forall a. Stream (Of a) IO () -> Source a
sourceOf Stream (Of a) IO ()
stream = (forall r. (Stream (Of a) IO () -> IO r) -> IO r) -> Source a
forall a.
(forall r. (Stream (Of a) IO () -> IO r) -> IO r) -> Source a
Source (\Stream (Of a) IO () -> IO r
use -> Stream (Of a) IO () -> IO r
use Stream (Of a) IO ()
stream)
sourceIO :: IO (Stream (Of a) IO ()) -> Source a
sourceIO :: forall a. IO (Stream (Of a) IO ()) -> Source a
sourceIO IO (Stream (Of a) IO ())
open = (forall r. (Stream (Of a) IO () -> IO r) -> IO r) -> Source a
forall a.
(forall r. (Stream (Of a) IO () -> IO r) -> IO r) -> Source a
Source (\Stream (Of a) IO () -> IO r
use -> IO (Stream (Of a) IO ())
open IO (Stream (Of a) IO ()) -> (Stream (Of a) IO () -> IO r) -> IO r
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Stream (Of a) IO () -> IO r
use)
data ParamValue = ParamBytes !RawBytes | ParamLazy LB.ByteString | ParamStream !(Source B.ByteString)
instance Show ParamValue where
showsPrec :: Int -> ParamValue -> ShowS
showsPrec Int
n (ParamBytes RawBytes
b) = Bool -> ShowS -> ShowS
showParen (Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
10) (String -> ShowS
showString String
"ParamBytes " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> RawBytes -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 RawBytes
b)
showsPrec Int
_ (ParamLazy ByteString
_) = String -> ShowS
showString String
"ParamLazy <bytes>"
showsPrec Int
_ (ParamStream Source ByteString
_) = String -> ShowS
showString String
"ParamStream <source>"
streamed :: ParamValue -> Bool
streamed :: ParamValue -> Bool
streamed (ParamBytes RawBytes
_) = Bool
False
streamed ParamValue
_ = Bool
True
paramValueProblem :: TypeInfo -> ParamValue -> Maybe String
paramValueProblem :: TypeInfo -> ParamValue -> Maybe String
paramValueProblem TypeInfo
ti (ParamBytes RawBytes
value) = TypeInfo -> RawBytes -> Maybe String
valueProblem TypeInfo
ti RawBytes
value
paramValueProblem TypeInfo
ti ParamValue
_ = case TypeInfo -> LengthKind
lengthKind TypeInfo
ti of
LengthKind
Plp -> TypeInfo -> Maybe String
typeInfoProblem TypeInfo
ti
LengthKind
_ -> String -> Maybe String
forall a. a -> Maybe a
Just String
"a lazy or streamed parameter requires a PLP (max or xml) type"
writeParamValue :: PacketWriter -> TypeInfo -> ParamValue -> IO ()
writeParamValue :: PacketWriter -> TypeInfo -> ParamValue -> IO ()
writeParamValue PacketWriter
out = PacketWriter -> Builder -> TypeInfo -> ParamValue -> IO ()
writeParamValueWith PacketWriter
out Builder
forall a. Monoid a => a
mempty
writeParamValueWith :: PacketWriter -> BB.Builder -> TypeInfo -> ParamValue -> IO ()
writeParamValueWith :: PacketWriter -> Builder -> TypeInfo -> ParamValue -> IO ()
writeParamValueWith PacketWriter
out Builder
prefix TypeInfo
ti ParamValue
value = case ParamValue
value of
ParamBytes RawBytes
bytes
| LengthKind
ByteLen <- TypeInfo -> LengthKind
lengthKind TypeInfo
ti -> PacketWriter -> Builder -> IO ()
writeBuilder PacketWriter
out Builder
prefix IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> PacketWriter -> RawBytes -> IO ()
writeByteLengthValue PacketWriter
out RawBytes
bytes
| Bool
otherwise -> PacketWriter -> Builder -> IO ()
writeBuilder PacketWriter
out (Builder
prefix Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> TypeInfo -> RawBytes -> Builder
writeValue TypeInfo
ti RawBytes
bytes)
ParamLazy ByteString
bytes -> Source ByteString -> IO ()
send (Stream (Of ByteString) IO () -> Source ByteString
forall a. Stream (Of a) IO () -> Source a
sourceOf ([ByteString] -> Stream (Of ByteString) IO ()
forall (m :: * -> *) (f :: * -> *) a.
(Monad m, Foldable f) =>
f a -> Stream (Of a) m ()
S.each (ByteString -> [ByteString]
LB.toChunks ByteString
bytes)))
ParamStream Source ByteString
source -> Source ByteString -> IO ()
send Source ByteString
source
where
send :: Source ByteString -> IO ()
send (Source forall r. (Stream (Of ByteString) IO () -> IO r) -> IO r
use) = PacketWriter -> forall a. IO a -> IO a
inSource PacketWriter
out (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
PacketWriter -> Builder -> IO ()
writeBuilder PacketWriter
out (Builder
prefix Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
BB.word64LE Word64
0xFFFFFFFFFFFFFFFE)
oddBytes <- (Stream (Of ByteString) IO () -> IO Bool) -> IO Bool
forall r. (Stream (Of ByteString) IO () -> IO r) -> IO r
use (Bool -> Stream (Of ByteString) IO () -> IO Bool
go Bool
False)
when (unicode && oddBytes) (throwIO (EncodeError "UTF-16 stream ended with an odd byte count"))
writeBuilder out (BB.word32LE 0)
go :: Bool -> Stream (Of ByteString) IO () -> IO Bool
go !Bool
parity Stream (Of ByteString) IO ()
source = Stream (Of ByteString) IO ()
-> IO (Either () (ByteString, Stream (Of ByteString) IO ()))
forall (m :: * -> *) a r.
Monad m =>
Stream (Of a) m r -> m (Either r (a, Stream (Of a) m r))
S.next Stream (Of ByteString) IO ()
source IO (Either () (ByteString, Stream (Of ByteString) IO ()))
-> (Either () (ByteString, Stream (Of ByteString) IO ())
-> IO Bool)
-> IO Bool
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left () -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
parity
Right (ByteString
bytes, Stream (Of ByteString) IO ()
next) -> do
ByteString -> IO ()
pieces ByteString
bytes
Bool -> Stream (Of ByteString) IO () -> IO Bool
go (Bool
parity Bool -> Bool -> Bool
forall a. Eq a => a -> a -> Bool
/= Int -> Bool
forall a. Integral a => a -> Bool
odd (ByteString -> Int
B.length ByteString
bytes)) Stream (Of ByteString) IO ()
next
pieces :: ByteString -> IO ()
pieces ByteString
bytes
| ByteString -> Bool
B.null ByteString
bytes = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
| Bool
otherwise = do
let (ByteString
chunk, ByteString
rest) = Int -> ByteString -> (ByteString, ByteString)
B.splitAt Int
8000 ByteString
bytes
PacketWriter -> Builder -> IO ()
writeBuilder PacketWriter
out (Word32 -> Builder
BB.word32LE (Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
B.length ByteString
chunk)) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> ByteString -> Builder
BB.byteString ByteString
chunk)
ByteString -> IO ()
pieces ByteString
rest
unicode :: Bool
unicode = case TypeInfo
ti of TINVarChar Word16
_ Collation
_ -> Bool
True; TypeInfo
_ -> Bool
False
writeByteLengthValue :: PacketWriter -> RawBytes -> IO ()
writeByteLengthValue :: PacketWriter -> RawBytes -> IO ()
writeByteLengthValue PacketWriter
writer RawBytes
bytes =
let payload :: ByteString
payload = ByteString -> (ByteString -> ByteString) -> RawBytes -> ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ByteString
B.empty ByteString -> ByteString
forall a. a -> a
id RawBytes
bytes
n :: Int
n = ByteString -> Int
B.length ByteString
payload
in PacketWriter -> Int -> (Ptr Word8 -> IO ()) -> IO ()
unsafeWriteSized PacketWriter
writer (Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
n) ((Ptr Word8 -> IO ()) -> IO ()) -> (Ptr Word8 -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Ptr Word8
p -> do
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p Int
0 (Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
n :: Word8)
case ByteString
payload of
BI.BS ForeignPtr Word8
src Int
_ -> ForeignPtr Word8 -> (Ptr Word8 -> IO ()) -> IO ()
forall a b. ForeignPtr a -> (Ptr a -> IO b) -> IO b
BI.unsafeWithForeignPtr ForeignPtr Word8
src ((Ptr Word8 -> IO ()) -> IO ()) -> (Ptr Word8 -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Ptr Word8
from -> Ptr Word8 -> Ptr Word8 -> Int -> IO ()
forall a. Ptr a -> Ptr a -> Int -> IO ()
copyBytes (Ptr Word8
p Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
1) Ptr Word8
from Int
n