module Hmssql.Connection
(
Connection
, ConnectInfo (..)
, defaultConnectInfo
, Protocol (..)
, defaultSessionSetup
, withConnection
, connect
, close
, release
, renew
, unusableReason
, cancelActiveStatement
, reset
, Encryption (..)
, TlsSettings (..)
, defaultTlsSettings
, connectionContext
, connectionMars
, TdsContext (..)
, TdsVersion (..)
) where
import Control.Concurrent.STM (readTVarIO)
import Control.Applicative ((<|>))
import Control.Exception
import Control.Monad (forM_, unless, when)
import qualified Data.Binary.Get as Get
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import Data.IORef
import Data.List (intercalate)
import Data.Maybe (isJust, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Word (Word32, Word8)
import GHC.IO.Exception (IOException (..))
import Network.HostName (getHostName)
import qualified Network.TLS as TLS
import Text.Printf (printf)
import Hmssql.Connection.Backend
import Hmssql.Connection.Internal
import Hmssql.Connection.Tls
import Hmssql.Errors
import Hmssql.Tds.Client
import Hmssql.Tds.Context
import Hmssql.Tds.Packet
import Hmssql.Tds.Token
data ConnectInfo = ConnectInfo
{ ConnectInfo -> Text
connectHost :: !Text
, ConnectInfo -> Text
connectPort :: !Text
, ConnectInfo -> Protocol
connectProtocol :: !Protocol
, ConnectInfo -> Text
connectInstance :: !Text
, ConnectInfo -> Text
connectUser :: !Text
, ConnectInfo -> Text
connectPassword :: !Text
, ConnectInfo -> Maybe Text
connectAccessToken :: !(Maybe Text)
, ConnectInfo -> Text
connectDatabase :: !Text
, ConnectInfo -> Encryption
connectEncryption :: !Encryption
, ConnectInfo -> TlsSettings
connectTls :: !TlsSettings
, ConnectInfo -> Text
connectApplicationName :: !Text
, ConnectInfo -> Text
connectServerName :: !Text
, ConnectInfo -> Text
connectLanguage :: !Text
, ConnectInfo -> Word32
connectPacketSize :: !Word32
, ConnectInfo -> Bool
connectReadOnlyIntent :: !Bool
, ConnectInfo -> [Text]
connectSessionSetup :: ![Text]
, ConnectInfo -> Bool
connectMars :: !Bool
, ConnectInfo -> Int
connectMarsWindow :: !Int
, ConnectInfo -> Bool
connectPinReader :: !Bool
, ConnectInfo -> Int
connectValueAllocAhead :: !Int
, ConnectInfo -> ServerMessage -> IO ()
connectOnInfoMessage :: !(ServerMessage -> IO ())
}
data Protocol
= ProtocolAuto
| ProtocolTcp
| ProtocolSharedMemory
| ProtocolNamedPipes
| ProtocolCustom (IO Backend)
defaultConnectInfo :: ConnectInfo
defaultConnectInfo :: ConnectInfo
defaultConnectInfo = ConnectInfo
{ connectHost :: Text
connectHost = Text
"localhost"
, connectPort :: Text
connectPort = Text
"1433"
, connectProtocol :: Protocol
connectProtocol = Protocol
ProtocolAuto
, connectInstance :: Text
connectInstance = Text
""
, connectUser :: Text
connectUser = Text
""
, connectPassword :: Text
connectPassword = Text
""
, connectAccessToken :: Maybe Text
connectAccessToken = Maybe Text
forall a. Maybe a
Nothing
, connectDatabase :: Text
connectDatabase = Text
""
, connectEncryption :: Encryption
connectEncryption = Encryption
EncryptOn
, connectTls :: TlsSettings
connectTls = TlsSettings
defaultTlsSettings
, connectApplicationName :: Text
connectApplicationName = Text
"hmssql"
, connectServerName :: Text
connectServerName = Text
""
, connectLanguage :: Text
connectLanguage = Text
""
, connectPacketSize :: Word32
connectPacketSize = Word32
8000
, connectReadOnlyIntent :: Bool
connectReadOnlyIntent = Bool
False
, connectSessionSetup :: [Text]
connectSessionSetup = [Text]
defaultSessionSetup
, connectMars :: Bool
connectMars = Bool
False
, connectMarsWindow :: Int
connectMarsWindow = Int
64
, connectPinReader :: Bool
connectPinReader = Bool
False
, connectValueAllocAhead :: Int
connectValueAllocAhead = Int
lobAllocAhead
, connectOnInfoMessage :: ServerMessage -> IO ()
connectOnInfoMessage = \ServerMessage
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
}
defaultSessionSetup :: [Text]
defaultSessionSetup :: [Text]
defaultSessionSetup = []
withConnection :: ConnectInfo -> (Connection -> IO a) -> IO a
withConnection :: forall a. ConnectInfo -> (Connection -> IO a) -> IO a
withConnection ConnectInfo
ci = IO Connection
-> (Connection -> IO ()) -> (Connection -> IO a) -> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (ConnectInfo -> IO Connection
connect ConnectInfo
ci) Connection -> IO ()
close
connect :: ConnectInfo -> IO Connection
connect :: ConnectInfo -> IO Connection
connect ConnectInfo
ci = do
Maybe Text -> (Text -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (ConnectInfo -> Maybe Text
connectAccessToken ConnectInfo
ci) ((Text -> IO ()) -> IO ()) -> (Text -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Text
token -> do
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Text -> Bool
T.null Text
token) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
EncodeError -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (EncodeError -> IO ()) -> EncodeError -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> EncodeError
EncodeError [Char]
"connectAccessToken is set but empty"
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
T.null (ConnectInfo -> Text
connectUser ConnectInfo
ci) Bool -> Bool -> Bool
&& Text -> Bool
T.null (ConnectInfo -> Text
connectPassword ConnectInfo
ci)) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
EncodeError -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (EncodeError -> IO ()) -> EncodeError -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> EncodeError
EncodeError
[Char]
"connectAccessToken is set along with connectUser or connectPassword; an access token replaces them"
((forall a. IO a -> IO a) -> IO Connection) -> IO Connection
forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
mask (((forall a. IO a -> IO a) -> IO Connection) -> IO Connection)
-> ((forall a. IO a -> IO a) -> IO Connection) -> IO Connection
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore ->
let go :: Int -> ConnectInfo -> IO Connection
go :: Int -> ConnectInfo -> IO Connection
go Int
redirects ConnectInfo
target = (forall a. IO a -> IO a)
-> ConnectInfo -> IO (Either RoutingInfo Connection)
connectWith IO a -> IO a
forall a. IO a -> IO a
restore ConnectInfo
target IO (Either RoutingInfo Connection)
-> (Either RoutingInfo Connection -> IO Connection)
-> IO Connection
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right Connection
conn -> Connection -> IO Connection
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Connection
conn
Left routing :: RoutingInfo
routing@RoutingInfo{Word8
Word16
Text
routingProtocol :: Word8
routingPort :: Word16
routingServer :: Text
routingPort :: RoutingInfo -> Word16
routingProtocol :: RoutingInfo -> Word8
routingServer :: RoutingInfo -> Text
..}
| Int
redirects Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
maxRedirects ->
LoginRedirected -> IO Connection
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO LoginRedirected { redirectServer :: Text
redirectServer = Text
routingServer, redirectPort :: Word16
redirectPort = Word16
routingPort }
| Bool
otherwise -> RoutingInfo -> IO ConnectInfo
redirectedTo RoutingInfo
routing IO ConnectInfo -> (ConnectInfo -> IO Connection) -> IO Connection
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> ConnectInfo -> IO Connection
go (Int
redirects Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
in Int -> ConnectInfo -> IO Connection
go Int
0 ConnectInfo
ci
where
redirectedTo :: RoutingInfo -> IO ConnectInfo
redirectedTo RoutingInfo{Word8
Word16
Text
routingPort :: RoutingInfo -> Word16
routingProtocol :: RoutingInfo -> Word8
routingServer :: RoutingInfo -> Text
routingProtocol :: Word8
routingPort :: Word16
routingServer :: Text
..} = do
let host :: Text
host = (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'\\') Text
routingServer
refuse :: [Char] -> IO a
refuse [Char]
why = ProtocolError -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO a) -> ProtocolError -> IO a
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError ([Char] -> ProtocolError) -> [Char] -> ProtocolError
forall a b. (a -> b) -> a -> b
$
[Char]
"the login was redirected to " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Text -> [Char]
forall a. Show a => a -> [Char]
show Text
routingServer [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", port " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Word16 -> [Char]
forall a. Show a => a -> [Char]
show Word16
routingPort [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
why
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Word8
routingProtocol Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
[Char] -> IO ()
forall {a}. [Char] -> IO a
refuse ([Char]
"with protocol " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Word8 -> [Char]
forall a. Show a => a -> [Char]
show Word8
routingProtocol [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", not TCP")
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Text -> Bool
T.null Text
host) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> IO ()
forall {a}. [Char] -> IO a
refuse [Char]
"which names no host"
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word16
routingPort Word16 -> Word16 -> Bool
forall a. Eq a => a -> a -> Bool
== Word16
0) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> IO ()
forall {a}. [Char] -> IO a
refuse [Char]
"which is not a port"
ConnectInfo -> IO ConnectInfo
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ConnectInfo
ci { connectHost = host
, connectPort = T.pack (show routingPort)
, connectProtocol = ProtocolTcp
, connectServerName = routingServer <> "," <> T.pack (show routingPort) }
maxRedirects :: Int
maxRedirects :: Int
maxRedirects = Int
10
connectWith :: (forall a. IO a -> IO a) -> ConnectInfo -> IO (Either RoutingInfo Connection)
connectWith :: (forall a. IO a -> IO a)
-> ConnectInfo -> IO (Either RoutingInfo Connection)
connectWith forall a. IO a -> IO a
restore ConnectInfo
ci = do
backend <- (forall a. IO a -> IO a) -> ConnectInfo -> IO Backend
openBackend IO a -> IO a
forall a. IO a -> IO a
restore ConnectInfo
ci IO Backend -> (IOException -> IO Backend) -> IO Backend
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` \(IOException
e :: IOException) ->
ConnectionClosed -> IO Backend
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (CloseReason -> ConnectionClosed
ConnectionClosedBy (SomeException -> CloseReason
TransportFailed (IOException -> SomeException
forall e. Exception e => e -> SomeException
toException IOException
e)))
result <- flip onException (backendClose backend) $ restore $ do
let ctx0 = TdsContext
defaultTdsContext
conn <- (\Connection
c -> Connection
c { connSessionSetup = connectSessionSetup ci, connPinReader = connectPinReader ci
, connValueAllocAhead = max 0 (connectValueAllocAhead ci) })
<$> newConnection backend ctx0 (connectOnInfoMessage ci)
routed <- case connectEncryption ci of
Encryption
EncryptStrict -> do
Connection -> TlsSettings -> [Char] -> IO ()
strictTlsHandshake Connection
conn (ConnectInfo -> TlsSettings
connectTls ConnectInfo
ci) (Text -> [Char]
T.unpack (ConnectInfo -> Text
connectHost ConnectInfo
ci))
answer <- if Bool
usesToken Bool -> Bool -> Bool
|| Bool
mars then Connection -> Encryption -> Bool -> Bool -> IO PreloginAnswer
prelogin Connection
conn Encryption
EncryptOn Bool
usesToken Bool
mars else PreloginAnswer -> IO PreloginAnswer
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure PreloginAnswer
noPreloginAnswer
login conn Tds80 answer (pure ())
Encryption
requested -> do
answer <- Connection -> Encryption -> Bool -> Bool -> IO PreloginAnswer
prelogin Connection
conn Encryption
requested Bool
usesToken Bool
mars
serverEnc <- case plaEncryption answer of
Maybe Word8
Nothing -> ProtocolError -> IO Encryption
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO Encryption) -> ProtocolError -> IO Encryption
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError [Char]
"PRELOGIN response lacks the ENCRYPTION option"
Just Word8
w -> case Word8 -> Maybe Encryption
encryptionFromWord Word8
w of
Just Encryption
e | Encryption
e Encryption -> Encryption -> Bool
forall a. Eq a => a -> a -> Bool
/= Encryption
EncryptStrict -> Encryption -> IO Encryption
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Encryption
e
Maybe Encryption
_ -> ProtocolError -> IO Encryption
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO Encryption) -> ProtocolError -> IO Encryption
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError ([Char] -> ProtocolError) -> [Char] -> ProtocolError
forall a b. (a -> b) -> a -> b
$
[Char]
"PRELOGIN response has an invalid ENCRYPTION value " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Word8 -> [Char]
forall a. Show a => a -> [Char]
show Word8
w
mode <- negotiateEncryption requested serverEnc
when (mode /= NoEncryption) $
hostedTlsHandshake conn (connectTls ci) (T.unpack (connectHost ci))
routed <- login conn Tds74 answer $ when (mode == LoginOnly) $
writeIORef (connTransport conn) (TransportPlain backend)
case routed of
Just RoutingInfo{Word8
Word16
Text
routingPort :: RoutingInfo -> Word16
routingProtocol :: RoutingInfo -> Word8
routingServer :: RoutingInfo -> Text
routingProtocol :: Word8
routingPort :: Word16
routingServer :: Text
..} | EncryptionMode
mode EncryptionMode -> EncryptionMode -> Bool
forall a. Eq a => a -> a -> Bool
== EncryptionMode
LoginOnly ->
LoginRedirected -> IO (Maybe RoutingInfo)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO LoginRedirected { redirectServer :: Text
redirectServer = Text
routingServer, redirectPort :: Word16
redirectPort = Word16
routingPort }
Maybe RoutingInfo
_ -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe RoutingInfo
routed
case routed of
Just RoutingInfo
routing -> Either RoutingInfo Connection -> IO (Either RoutingInfo Connection)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RoutingInfo -> Either RoutingInfo Connection
forall a b. a -> Either a b
Left RoutingInfo
routing)
Maybe RoutingInfo
Nothing -> do
if Bool
mars
then Connection -> Either RoutingInfo Connection
forall a b. b -> Either a b
Right (Connection -> Either RoutingInfo Connection)
-> IO Connection -> IO (Either RoutingInfo Connection)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Connection -> Int -> IO Connection
enableMars Connection
conn (ConnectInfo -> Int
connectMarsWindow ConnectInfo
ci)
else do
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
setup) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Connection -> Text -> IO ()
runPlainBatch Connection
conn (Text -> [Text] -> Text
T.intercalate Text
"\n" [Text]
setup)
Either RoutingInfo Connection -> IO (Either RoutingInfo Connection)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Connection -> Either RoutingInfo Connection
forall a b. b -> Either a b
Right Connection
conn)
case result of
Left RoutingInfo
routing -> do
Backend -> IO ()
backendClose Backend
backend
Either RoutingInfo Connection -> IO (Either RoutingInfo Connection)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RoutingInfo -> Either RoutingInfo Connection
forall a b. a -> Either a b
Left RoutingInfo
routing)
Right Connection
conn -> do
Connection -> IO ()
startReader Connection
conn
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
mars Bool -> Bool -> Bool
&& Bool -> Bool
not ([Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
setup)) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
IO () -> IO ()
forall a. IO a -> IO a
restore (Connection -> Text -> IO ()
runPlainBatch Connection
conn (Text -> [Text] -> Text
T.intercalate Text
"\n" [Text]
setup)) IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
`onException` Connection -> IO ()
close Connection
conn
Either RoutingInfo Connection -> IO (Either RoutingInfo Connection)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Connection -> Either RoutingInfo Connection
forall a b. b -> Either a b
Right Connection
conn)
where
setup :: [Text]
setup = ConnectInfo -> [Text]
connectSessionSetup ConnectInfo
ci
packetSize :: Word32
packetSize
| Bool
mars Bool -> Bool -> Bool
&& ConnectInfo -> Encryption
connectEncryption ConnectInfo
ci Encryption -> Encryption -> Bool
forall a. Eq a => a -> a -> Bool
/= Encryption
EncryptNotSup = Word32 -> Word32 -> Word32
forall a. Ord a => a -> a -> a
min Word32
16368 (Word32 -> Word32
clampPacketSize (ConnectInfo -> Word32
connectPacketSize ConnectInfo
ci))
| Bool
otherwise = Word32 -> Word32
clampPacketSize (ConnectInfo -> Word32
connectPacketSize ConnectInfo
ci)
usesToken :: Bool
usesToken = Maybe Text -> Bool
forall a. Maybe a -> Bool
isJust (ConnectInfo -> Maybe Text
connectAccessToken ConnectInfo
ci)
mars :: Bool
mars = ConnectInfo -> Bool
connectMars ConnectInfo
ci
login :: Connection -> TdsVersion -> PreloginAnswer -> IO () -> IO (Maybe RoutingInfo)
login :: Connection
-> TdsVersion -> PreloginAnswer -> IO () -> IO (Maybe RoutingInfo)
login Connection
conn TdsVersion
version PreloginAnswer
answer IO ()
afterSend = do
hostName <- IO [Char]
getHostName
let l7 = Login7
defaultLogin7
{ l7Version = version
, l7PacketSize = packetSize
, l7HostName = T.pack hostName
, l7UserName = connectUser ci
, l7Password = connectPassword ci
, l7AppName = connectApplicationName ci
, l7ServerName = if T.null (connectServerName ci)
then connectHost ci
else connectServerName ci
, l7Language = connectLanguage ci
, l7Database = connectDatabase ci
, l7TypeFlags = if connectReadOnlyIntent ci then 0x20 else 0
, l7Features = l7Features defaultLogin7
++ [ featureFedAuthToken (plaFedAuthRequired answer) token (plaNonce answer)
| Just token <- [connectAccessToken ci] ]
}
mapM_ (throwIO . EncodeError) (login7Problem l7)
ar <- startRequest conn PTLogin7 (\TdsContext
_ -> Put -> ByteString
runBody (Login7 -> Put
putLogin7 Login7
l7))
afterSend
readLoginResponse conn ar
reset :: Connection -> IO ()
reset :: Connection -> IO ()
reset = Connection -> IO ()
requestReset
cancelActiveStatement :: Connection -> IO ()
cancelActiveStatement :: Connection -> IO ()
cancelActiveStatement Connection
conn = Connection -> IO () -> IO ()
forall a. Connection -> IO a -> IO a
withConnectionLock Connection
conn (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Connection -> IO ()
refuseUnusable Connection
conn
Connection -> IO ()
finishResponse Connection
conn
release :: Connection -> IO Bool
release :: Connection -> IO Bool
release Connection
conn = Connection -> IO Bool -> IO Bool
forall a. Connection -> IO a -> IO a
withConnectionLock Connection
conn (IO Bool -> IO Bool) -> IO Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ (IO Bool -> IO () -> IO Bool
forall a b. IO a -> IO b -> IO a
`finally` Connection -> IO ()
invalidateLease Connection
conn) (IO Bool -> IO Bool) -> IO Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ do
ended <- Maybe SomeException -> Bool
forall a. Maybe a -> Bool
isJust (Maybe SomeException -> Bool)
-> IO (Maybe SomeException) -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar (Maybe SomeException) -> IO (Maybe SomeException)
forall a. TVar a -> IO a
readTVarIO (Connection -> TVar (Maybe SomeException)
connEnded Connection
conn)
broken <- isJust <$> readIORef (connBroken conn)
if ended || broken
then pure False
else (clean >> pure True) `catchSync` \SomeException
e -> do
Connection -> UnusableReason -> IO ()
markBroken Connection
conn (SomeException -> UnusableReason
resetFailure SomeException
e)
Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False
where
clean :: IO ()
clean = do
Connection -> IO ()
finishResponse Connection
conn
IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
atomicWriteIORef (Connection -> IORef Bool
connResetPending Connection
conn) Bool
True
open <- Maybe TransactionDescriptor -> Bool
forall a. Maybe a -> Bool
isJust (Maybe TransactionDescriptor -> Bool)
-> (TdsContext -> Maybe TransactionDescriptor)
-> TdsContext
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TdsContext -> Maybe TransactionDescriptor
tdsTransaction (TdsContext -> Bool) -> IO TdsContext -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IORef TdsContext -> IO TdsContext
forall a. IORef a -> IO a
readIORef (Connection -> IORef TdsContext
connContext Connection
conn)
when open $
if null (connSessionSetup conn)
then runPlainBatch conn "SELECT 1" `catch` \(SomeException
e :: SomeException) -> do
Connection -> UnusableReason -> IO ()
markBroken Connection
conn (SomeException -> UnusableReason
resetFailure SomeException
e)
SomeException -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO SomeException
e
else resetWithSessionSetup conn
renew :: Connection -> IO Connection
renew :: Connection -> IO Connection
renew = Connection -> IO Connection
renewUsable
unusableReason :: Connection -> IO (Maybe UnusableReason)
unusableReason :: Connection -> IO (Maybe UnusableReason)
unusableReason = Connection -> IO (Maybe UnusableReason)
currentUnusableReason
close :: Connection -> IO ()
close :: Connection -> IO ()
close Connection
conn = do
Connection -> IO () -> IO ()
closeConnection Connection
conn (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ IORef Transport -> IO Transport
forall a. IORef a -> IO a
readIORef (Connection -> IORef Transport
connTransport Connection
conn) IO Transport -> (Transport -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
TransportTls Context
ctx -> Context -> IO ()
forall (m :: * -> *). MonadIO m => Context -> m ()
TLS.bye Context
ctx IO () -> (SomeException -> IO ()) -> IO ()
forall a. IO a -> (SomeException -> IO a) -> IO a
`catchSync` \SomeException
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
TransportPlain Backend
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Connection -> IO ()
releaseTransport Connection
conn
openBackend :: (forall a. IO a -> IO a) -> ConnectInfo -> IO Backend
openBackend :: (forall a. IO a -> IO a) -> ConnectInfo -> IO Backend
openBackend forall a. IO a -> IO a
restore ConnectInfo
ci = case ConnectInfo -> Protocol
connectProtocol ConnectInfo
ci of
Protocol
ProtocolTcp -> IO Backend
tcp
Protocol
ProtocolSharedMemory -> IO Backend
sharedMemory
Protocol
ProtocolNamedPipes -> IO Backend
namedPipes
ProtocolCustom IO Backend
open -> IO Backend
open
Protocol
ProtocolAuto
| Bool -> Bool
not Bool
pipesSupported Bool -> Bool -> Bool
|| Bool
portChosen -> IO Backend
tcp
| Bool
otherwise -> do
local <- [Char] -> IO Bool
isLocalHost [Char]
host
firstOf $ [("Shared Memory", sharedMemory) | local]
++ [("TCP/IP", tcp) | isDefaultInstance instanceName]
++ [("Named Pipes", namedPipes)]
where
host :: [Char]
host = Text -> [Char]
T.unpack (ConnectInfo -> Text
connectHost ConnectInfo
ci)
port :: [Char]
port = Text -> [Char]
T.unpack (ConnectInfo -> Text
connectPort ConnectInfo
ci)
instanceName :: [Char]
instanceName = Text -> [Char]
T.unpack (ConnectInfo -> Text
connectInstance ConnectInfo
ci)
portChosen :: Bool
portChosen = ConnectInfo -> Text
connectPort ConnectInfo
ci Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= ConnectInfo -> Text
connectPort ConnectInfo
defaultConnectInfo
tcp :: IO Backend
tcp = (forall a. IO a -> IO a) -> [Char] -> [Char] -> IO Backend
tcpBackendWith IO a -> IO a
forall a. IO a -> IO a
restore [Char]
host [Char]
port
sharedMemory :: IO Backend
sharedMemory = [Char] -> IO Backend
pipeBackendMasked ([Char] -> [Char]
sharedMemoryPipe [Char]
instanceName)
namedPipes :: IO Backend
namedPipes = do
local <- (Bool -> Bool -> Bool
|| [Char]
host [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"127.0.0.1", [Char]
"::1"]) (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO Bool
isLocalHost [Char]
host
pipeBackendMasked (namedPipe local host instanceName)
firstOf :: [(String, IO Backend)] -> IO Backend
firstOf :: [([Char], IO Backend)] -> IO Backend
firstOf = [([Char], IOException)] -> [([Char], IO Backend)] -> IO Backend
forall {a}. [([Char], IOException)] -> [([Char], IO a)] -> IO a
go []
where
go :: [([Char], IOException)] -> [([Char], IO a)] -> IO a
go [([Char], IOException)]
failures [] = IOException -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO ([([Char], IOException)] -> IOException
allFailed ([([Char], IOException)] -> [([Char], IOException)]
forall a. [a] -> [a]
reverse [([Char], IOException)]
failures))
go [([Char], IOException)]
failures (([Char]
name, IO a
open) : [([Char], IO a)]
rest) = IO a -> IO (Either IOException a)
forall e a. Exception e => IO a -> IO (Either e a)
try IO a
open IO (Either IOException a) -> (Either IOException a -> IO a) -> IO a
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right a
backend -> a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
backend
Left (IOException
e :: IOException) -> [([Char], IOException)] -> [([Char], IO a)] -> IO a
go (([Char]
name, IOException
e) ([Char], IOException)
-> [([Char], IOException)] -> [([Char], IOException)]
forall a. a -> [a] -> [a]
: [([Char], IOException)]
failures) [([Char], IO a)]
rest
allFailed :: [(String, IOException)] -> IOException
allFailed :: [([Char], IOException)] -> IOException
allFailed [([Char], IOException)]
failures = IOException
base
{ ioe_location = "hmssql: connect to " ++ host
, ioe_description = intercalate "; " [name ++ ": " ++ displayException e | (name, e) <- failures]
, ioe_filename = Nothing
}
where
base :: IOException
base = case [IOException
e | ([Char]
"TCP/IP", IOException
e) <- [([Char], IOException)]
failures] [IOException] -> [IOException] -> [IOException]
forall a. [a] -> [a] -> [a]
++ (([Char], IOException) -> IOException)
-> [([Char], IOException)] -> [IOException]
forall a b. (a -> b) -> [a] -> [b]
map ([Char], IOException) -> IOException
forall a b. (a, b) -> b
snd [([Char], IOException)]
failures of
IOException
e : [IOException]
_ -> IOException
e
[] -> [Char] -> IOException
userError [Char]
"no protocol to try"
data EncryptionMode = NoEncryption | LoginOnly | FullEncryption
deriving stock (EncryptionMode -> EncryptionMode -> Bool
(EncryptionMode -> EncryptionMode -> Bool)
-> (EncryptionMode -> EncryptionMode -> Bool) -> Eq EncryptionMode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: EncryptionMode -> EncryptionMode -> Bool
== :: EncryptionMode -> EncryptionMode -> Bool
$c/= :: EncryptionMode -> EncryptionMode -> Bool
/= :: EncryptionMode -> EncryptionMode -> Bool
Eq, Int -> EncryptionMode -> [Char] -> [Char]
[EncryptionMode] -> [Char] -> [Char]
EncryptionMode -> [Char]
(Int -> EncryptionMode -> [Char] -> [Char])
-> (EncryptionMode -> [Char])
-> ([EncryptionMode] -> [Char] -> [Char])
-> Show EncryptionMode
forall a.
(Int -> a -> [Char] -> [Char])
-> (a -> [Char]) -> ([a] -> [Char] -> [Char]) -> Show a
$cshowsPrec :: Int -> EncryptionMode -> [Char] -> [Char]
showsPrec :: Int -> EncryptionMode -> [Char] -> [Char]
$cshow :: EncryptionMode -> [Char]
show :: EncryptionMode -> [Char]
$cshowList :: [EncryptionMode] -> [Char] -> [Char]
showList :: [EncryptionMode] -> [Char] -> [Char]
Show)
negotiateEncryption :: Encryption -> Encryption -> IO EncryptionMode
negotiateEncryption :: Encryption -> Encryption -> IO EncryptionMode
negotiateEncryption Encryption
client Encryption
server = case (Encryption
client, Encryption
server) of
(Encryption
EncryptOff, Encryption
EncryptOff) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
LoginOnly
(Encryption
EncryptOff, Encryption
EncryptOn) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptOff, Encryption
EncryptReq) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptOff, Encryption
EncryptNotSup) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
NoEncryption
(Encryption
EncryptOn, Encryption
EncryptOn) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptOn, Encryption
EncryptReq) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptReq, Encryption
EncryptOn) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptReq, Encryption
EncryptReq) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
EncryptNotSup, Encryption
EncryptNotSup) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
NoEncryption
(Encryption
EncryptNotSup, Encryption
EncryptOff) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
NoEncryption
(Encryption
EncryptNotSup, Encryption
_) -> [Char] -> IO EncryptionMode
forall {a}. [Char] -> IO a
refuse [Char]
"the server requires encryption, but the client was configured not to encrypt"
(Encryption
EncryptStrict, Encryption
_) -> EncryptionMode -> IO EncryptionMode
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure EncryptionMode
FullEncryption
(Encryption
_, Encryption
EncryptNotSup) -> [Char] -> IO EncryptionMode
forall {a}. [Char] -> IO a
refuse [Char]
"the server does not support encryption"
(Encryption, Encryption)
_ -> [Char] -> IO EncryptionMode
forall {a}. [Char] -> IO a
refuse [Char]
"unexpected answer"
where
refuse :: [Char] -> IO a
refuse [Char]
why = ProtocolError -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO a) -> ProtocolError -> IO a
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError ([Char] -> ProtocolError) -> [Char] -> ProtocolError
forall a b. (a -> b) -> a -> b
$
[Char]
"PRELOGIN encryption negotiation failed: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
why [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" (client requested "
[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Encryption -> [Char]
forall a. Show a => a -> [Char]
show Encryption
client [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", server answered " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Encryption -> [Char]
forall a. Show a => a -> [Char]
show Encryption
server [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
data PreloginAnswer = PreloginAnswer
{ PreloginAnswer -> Maybe Word8
plaEncryption :: !(Maybe Word8)
, PreloginAnswer -> Bool
plaFedAuthRequired :: !Bool
, PreloginAnswer -> Maybe ByteString
plaNonce :: !(Maybe B.ByteString)
}
noPreloginAnswer :: PreloginAnswer
noPreloginAnswer :: PreloginAnswer
noPreloginAnswer = Maybe Word8 -> Bool -> Maybe ByteString -> PreloginAnswer
PreloginAnswer Maybe Word8
forall a. Maybe a
Nothing Bool
False Maybe ByteString
forall a. Maybe a
Nothing
prelogin :: Connection -> Encryption -> Bool -> Bool -> IO PreloginAnswer
prelogin :: Connection -> Encryption -> Bool -> Bool -> IO PreloginAnswer
prelogin Connection
conn Encryption
enc Bool
fedAuth Bool
mars = do
let msg :: Prelogin
msg = [PreloginOption] -> Prelogin
Prelogin ([PreloginOption] -> Prelogin) -> [PreloginOption] -> Prelogin
forall a b. (a -> b) -> a -> b
$
[ Word8 -> Word8 -> Word16 -> Word16 -> PreloginOption
PLVersion Word8
0 Word8
0 Word16
0 Word16
0
, Word8 -> PreloginOption
PLEncryption (Encryption -> Word8
encryptionWord Encryption
enc)
, ByteString -> PreloginOption
PLInstance ByteString
""
, Maybe Word32 -> PreloginOption
PLThreadId (Word32 -> Maybe Word32
forall a. a -> Maybe a
Just Word32
0)
, Word8 -> PreloginOption
PLMars (if Bool
mars then Word8
1 else Word8
0)
] [PreloginOption] -> [PreloginOption] -> [PreloginOption]
forall a. [a] -> [a] -> [a]
++ [Word8 -> PreloginOption
PLFedAuthRequired Word8
0x01 | Bool
fedAuth]
Connection -> PacketType -> Put -> IO ()
sendMessage Connection
conn PacketType
PTPrelogin (Prelogin -> Put
putPrelogin Prelogin
msg)
(ptype, body) <- Connection -> Int -> IO (PacketType, ByteString)
readWholeMessageUpTo Connection
conn (Int
0xFFFF Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
0xFFFF)
unless (ptype == PTTabularResult) $
throwIO $ ProtocolError $ "unexpected PRELOGIN response packet type " ++ show ptype
Prelogin opts <- case Get.runGetOrFail getPrelogin (LB.fromStrict body) of
Right (ByteString
_, ByteOffset
_, Prelogin
p) -> Prelogin -> IO Prelogin
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Prelogin
p
Left (ByteString
_, ByteOffset
_, [Char]
err) -> ProtocolError -> IO Prelogin
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO Prelogin) -> ProtocolError -> IO Prelogin
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError ([Char] -> ProtocolError) -> [Char] -> ProtocolError
forall a b. (a -> b) -> a -> b
$ [Char]
"PRELOGIN response: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
err
let marsAnswer = [Word8] -> Maybe Word8
forall a. [a] -> Maybe a
listToMaybe [Word8
m | PLMars Word8
m <- [PreloginOption]
opts]
if mars
then unless (marsAnswer == Just 1) $ throwIO MarsNotSupported
else forM_ marsAnswer $ \Word8
m ->
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Word8
m Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ ProtocolError -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO ()) -> ProtocolError -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError [Char]
"the server turned MARS on, which was not asked for"
pure PreloginAnswer
{ plaEncryption = listToMaybe [e | PLEncryption e <- opts]
, plaFedAuthRequired = listToMaybe [f | PLFedAuthRequired f <- opts] == Just 0x01
, plaNonce = listToMaybe [n | PLNonce n <- opts]
}
readLoginResponse :: Connection -> ActiveResponse -> IO (Maybe RoutingInfo)
readLoginResponse :: Connection -> ActiveResponse -> IO (Maybe RoutingInfo)
readLoginResponse Connection
conn ActiveResponse
ar = Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go Maybe LoginAck
forall a. Maybe a
Nothing Maybe RoutingInfo
forall a. Maybe a
Nothing
where
go :: Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go Maybe LoginAck
acked Maybe RoutingInfo
routed = IO (Maybe Token)
nextLoginToken IO (Maybe Token)
-> (Maybe Token -> IO (Maybe RoutingInfo))
-> IO (Maybe RoutingInfo)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe Token
Nothing -> case Maybe LoginAck
acked of
Just LoginAck
la -> LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
forall {a}. LoginAck -> Maybe a -> IO (Maybe a)
finish LoginAck
la Maybe RoutingInfo
routed
Maybe LoginAck
Nothing -> ProtocolError -> IO (Maybe RoutingInfo)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO (Maybe RoutingInfo))
-> ProtocolError -> IO (Maybe RoutingInfo)
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError [Char]
"login response did not include LOGINACK"
Just Token
tok -> case Token
tok of
TLoginAck LoginAck
la -> Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go (LoginAck -> Maybe LoginAck
forall a. a -> Maybe a
Just LoginAck
la) Maybe RoutingInfo
routed
TError ServerMessage
msg -> do
Connection -> ActiveResponse -> IO ()
drainActiveResponse Connection
conn ActiveResponse
ar IO () -> (SomeException -> IO ()) -> IO ()
forall a. IO a -> (SomeException -> IO a) -> IO a
`catchSync` \SomeException
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
LoginError -> IO (Maybe RoutingInfo)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ServerMessage -> LoginError
LoginError ServerMessage
msg)
TFeatureExtAck [FeatureAck]
acks -> do
[FeatureAck] -> (FeatureAck -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [FeatureAck]
acks ((FeatureAck -> IO ()) -> IO ()) -> (FeatureAck -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \FeatureAck{Word8
ByteString
featureId :: Word8
featureData :: ByteString
featureData :: FeatureAck -> ByteString
featureId :: FeatureAck -> Word8
..} ->
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word8
featureId Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x0A) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
IORef TdsContext -> (TdsContext -> TdsContext) -> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' (Connection -> IORef TdsContext
connContext Connection
conn) (\TdsContext
c -> TdsContext
c { tdsUtf8Support = featureData == "\1" })
Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go Maybe LoginAck
acked Maybe RoutingInfo
routed
TEnvChange (ECRouting RoutingInfo
info) -> Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go Maybe LoginAck
acked (RoutingInfo -> Maybe RoutingInfo
forall a. a -> Maybe a
Just RoutingInfo
info)
Token
_ -> Maybe LoginAck -> Maybe RoutingInfo -> IO (Maybe RoutingInfo)
go Maybe LoginAck
acked Maybe RoutingInfo
routed
nextLoginToken :: IO (Maybe Token)
nextLoginToken = Connection -> ActiveResponse -> IO (Maybe Token)
nextToken Connection
conn ActiveResponse
ar IO (Maybe Token)
-> (SqlServerError -> IO (Maybe Token)) -> IO (Maybe Token)
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` \(SqlServerError ServerMessage
msg Maybe Text
_) -> LoginError -> IO (Maybe Token)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ServerMessage -> LoginError
LoginError ServerMessage
msg)
finish :: LoginAck -> Maybe a -> IO (Maybe a)
finish LoginAck
_ (Just a
routing) = Maybe a -> IO (Maybe a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a -> Maybe a
forall a. a -> Maybe a
Just a
routing)
finish LoginAck
la Maybe a
Nothing = case Word32 -> Maybe TdsVersion
tdsVersionFromWord (LoginAck -> Word32
laTdsVersion LoginAck
la) of
Just TdsVersion
v -> Maybe a
forall a. Maybe a
Nothing Maybe a -> IO () -> IO (Maybe a)
forall a b. a -> IO b -> IO a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ IORef TdsContext -> (TdsContext -> TdsContext) -> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' (Connection -> IORef TdsContext
connContext Connection
conn) (\TdsContext
c -> TdsContext
c { tdsVersion = v })
Maybe TdsVersion
Nothing -> ProtocolError -> IO (Maybe a)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ProtocolError -> IO (Maybe a)) -> ProtocolError -> IO (Maybe a)
forall a b. (a -> b) -> a -> b
$ [Char] -> ProtocolError
ProtocolError ([Char] -> ProtocolError) -> [Char] -> ProtocolError
forall a b. (a -> b) -> a -> b
$
[Char]
"server negotiated unsupported TDS version 0x" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> Word32 -> [Char]
forall r. PrintfType r => [Char] -> r
printf [Char]
"%08x" (LoginAck -> Word32
laTdsVersion LoginAck
la)
runPlainBatch :: Connection -> Text -> IO ()
runPlainBatch :: Connection -> Text -> IO ()
runPlainBatch Connection
conn Text
sql = do
ar <- Connection
-> PacketType -> (TdsContext -> ByteString) -> IO ActiveResponse
startRequest Connection
conn PacketType
PTSqlBatch (\TdsContext
ctx -> TdsContext -> Text -> ByteString
sqlBatchBody TdsContext
ctx Text
sql)
let go Maybe ServerMessage
err Maybe Done
done = Connection -> ActiveResponse -> IO (Maybe Token)
nextToken Connection
conn ActiveResponse
ar IO (Maybe Token) -> (Maybe Token -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe Token
Nothing -> case (Maybe ServerMessage
err, Maybe Done
done) of
(Just ServerMessage
msg, Maybe Done
_) -> SqlServerError -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (ServerMessage -> Maybe Text -> SqlServerError
SqlServerError ServerMessage
msg (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
sql))
(Maybe ServerMessage
Nothing, Just Done
d) -> Connection -> Done -> Maybe Text -> IO ()
forall a. Connection -> Done -> Maybe Text -> IO a
throwSevere Connection
conn Done
d (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
sql)
(Maybe ServerMessage
Nothing, Maybe Done
Nothing) -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Just (TError ServerMessage
msg) -> Maybe ServerMessage -> Maybe Done -> IO ()
go (Maybe ServerMessage
err Maybe ServerMessage -> Maybe ServerMessage -> Maybe ServerMessage
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> ServerMessage -> Maybe ServerMessage
forall a. a -> Maybe a
Just ServerMessage
msg) Maybe Done
done
Just Token
tok -> Maybe ServerMessage -> Maybe Done -> IO ()
go Maybe ServerMessage
err (Maybe Done
done Maybe Done -> Maybe Done -> Maybe Done
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Token -> Maybe Done
failedDoneOf Token
tok)
go Nothing Nothing
catchSync :: IO a -> (SomeException -> IO a) -> IO a
catchSync :: forall a. IO a -> (SomeException -> IO a) -> IO a
catchSync IO a
act SomeException -> IO a
handler = IO a
act IO a -> (SomeException -> IO a) -> IO a
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` \SomeException
e -> case SomeException -> Maybe SomeAsyncException
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e of
Just (SomeAsyncException
_ :: SomeAsyncException) -> SomeException -> IO a
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO SomeException
e
Maybe SomeAsyncException
Nothing -> SomeException -> IO a
handler SomeException
e